eca_render: action_base.weight typed as integer contradicts the token-capable textfield defaulting to an empty string

Problem

eca_render.action_base declares:

weight:
  type: weight
  label: 'Weight'

weight is an integer type, but the form element is a token-capable textfield that defaults to an empty string:

  • RenderElementActionBase::defaultConfiguration() sets 'weight' => ''.
  • buildConfigurationForm() builds it as a textfield with '#eca_token_replacement' => TRUE and '#required' => FALSE.
  • execute() explicitly treats '' as "no weight" and otherwise runs the value through token replacement.

This does not produce a schema validation error. StorableConfigBase::castValue() sanitizes values before ConfigSchemaChecker ever sees them, so validation passes.

The actual consequence is silent data loss — a weight defined by a token is cast to integer 0 on save:

input stored
'' NULL
'[my:weight]' 0
'_eca_token' 0
'7' 7

Because the token never survives the save, the replaceClear() call in RenderElementActionBase is unreachable dead code, and the user silently gets weight 0 — itself a legal weight, so nothing surfaces.

Where it comes from

This is pre-existing: action.configuration.eca_render_build already inherited it. It became more widespread with #3590379 (closed), which added the missing action.configuration.* sections for the remaining render actions — those new sections inherit the same base. eca_htmx also descends from eca_render.action_base, so the affected surface spans three sub-modules.

Proposed resolution

Complete the existing eca_integer_or_token convention from #3556019 (closed) rather than introducing a third pattern. Those types are never hand-written in a schema file; they are applied by ConfigSchemaHooks.

Two structural gaps stop that convention from reaching this bug:

  1. Base-type keys are invisible to the alter hook. hook_config_schema_info_alter() fires against raw per-file definitions, while type inheritance is resolved lazily afterwards in TypedConfigManager::getDefinitionWithReplacements(). A key declared on a shared base type such as eca_render.action_base cannot be reached by an alter that walks only leaf keys, so adding weight to the match arm alone is a silent no-op. The alter must walk the declared-type ancestry — with an allowlist, since rewriting a base type owned by another module would retype that module's configuration site-wide.
  2. Events are never altered. The hook iterates actions and conditions only; eca.event.plugin.* is untouched.

Corrections to the original summary

  • eca_render.element_action_base does not exist in either branch. Only eca_render.action_base does.
  • tests/src/Kernel/PluginConfigSchemaTest.php does not enforce this. It is a coverage test — it asserts each plugin has a schema definition whose type is not undefined, and passes whether or not the token is destroyed.
  • The problem is not a schema violation. Validation passes; the value is destroyed before the checker runs.

Change record: #3616767

Edited by drupalbot