Skip to content

fix(intent): a step arg the kind does not read is an error (#6749) - #6751

Merged
delchev merged 1 commit into
masterfrom
fix/intent-step-args-keys
Aug 16, 2026
Merged

fix(intent): a step arg the kind does not read is an error (#6749)#6751
delchev merged 1 commit into
masterfrom
fix/intent-step-args-keys

Conversation

@delchev

@delchev delchev commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Closes #6749. Follow-up to #6541 / #6748.

The gap

#6748 made an unknown key an error for every typed node and for seed rows, deriving the known-key set reflectively off the model classes. The maps it left opaque turn out to split in two, and only one half is genuinely author-keyed:

Author-keyed — stays opaque, forever A closed vocabulary that merely happens to be a Map
a map: / defaults: projection, a relation's where:, a widget's at:, a delegate's injected fields: a step's args:, a process trigger: / abortOn:, a glue event: binding (incl. the nested onStepReached: { process, step }), a posting's rule:, a generate child's forEach:, a lookup's between: / found: / notFound: / ambiguous:

Everything in the right column was as silent as the keys #6748 fixed: the BPMN was emitted, the task existed, and only the behaviour the author asked for was missing.

The fix

  • UnknownKeyValidator.MAP_KEYS — a registry keyed <SimpleClassName>#<field>[.<nested key>], consulted by the existing walk. Nothing else about the walk changes.
  • IntentParser.validateStepArgs — per kind, because a step's vocabulary depends on its sibling kind. Two messages, because there are two mistakes and the second is the same silent drop:
process [InvoiceApproval] step [approve] declares unknown arg [assigne] - did you mean [assignee]?
process [InvoiceApproval] step [approve] declares arg [if] but is a userTask - if is a decision argument

The six keys whose misplacement already has a dedicated, better-worded validator (setField / setRelationField / delegate / notify / timeout / expire) stay in the vocabulary — so they are never called unknown — but are excluded from the generic misplacement line, so nobody is told the same thing twice in two registers. aMisplacedSetFieldKeepsItsOwnMessageOnly pins that.

The nested notify block validates against NotificationIntent.BLOCK_KEYS, the set its own fromMap reads. NotificationIntentTest pins that constant to the class's embeddable properties, so a new block property cannot ship unauthorable — the failure this whole family is about, one level up.

Why this was a separate change

Unlike #6748's reflected sets, these lists are authored: a missing entry rejects an intent that works today. Every entry mirrors what the readers actually consult (TriggerSupport, EventBinding, StepEventSupport, SetFieldSupport, ProcessTimerSupport, ProcessWaitSupport, ProcessParallelSupport, BpmnIntentGenerator, ServiceTaskHandlerGenerator), and the corpus sweep is what proves it.

Verification

  • Red-first: 10 of the 13 new StepArgsIntentTest cases fail with the two call sites disabled (the other 3 are the "still parses" / "no duplicate message" guards).
  • No false positives: the same sweep fix(intent): an unknown key is an error, never a silent drop (#6541) #6748 used — 259 in-repo fixtures + 63 real production intents, carrying 389 authored args: blocks (assignee / setRelationField / if / setField / delegate / onCreate / branches / after / next / path) — zero hits.
  • mvn test -pl components/engine/engine-intent: 581 tests green (16 new).
  • IntentEngineIT: 48 tests green locally (headless), including the new parse_rejects_an_unknown_step_arg_and_an_unknown_trigger_key, which asserts the exact 422 messages over HTTP — the outermost layer this is observable at.
  • formatter:validate clean; javadoc clean under -P release.

Docs

🤖 Generated with Claude Code

Follow-up to #6541/#6748, which made an unknown key an error for every TYPED
node and for seed rows. The maps it left opaque split in two, and only one half
is genuinely author-keyed:

- a `map:`/`defaults:` projection, a relation's `where:`, a widget's `at:` and a
  delegate's injected `fields:` carry names from the model being described -
  they stay opaque, forever;
- a step's `args:`, a process `trigger:`/`abortOn:`, a glue `event:` binding
  (incl. the nested `onStepReached: { process, step }`), a posting's `rule:`, a
  generate child's `forEach:` and a lookup's `between:`/`found:`/`notFound:`/
  `ambiguous:` are closed vocabularies that merely happen to be typed as `Map` -
  an invented key there was as silent as anywhere else. The BPMN was emitted,
  the task existed, and only the behaviour the author asked for was missing.

The second group is now checked: the fixed vocabularies from the registry
`UnknownKeyValidator.MAP_KEYS`, and a step's `args:` per KIND
(`IntentParser.STEP_ARGS_BY_KIND`), because its vocabulary depends on the
sibling `kind`. A step therefore gets two messages - `declares unknown arg
[assigne] - did you mean [assignee]?` for a typo, and `declares arg [if] but is
a userTask - if is a decision argument` for a misplacement, which is the same
silent drop. The six keys whose misplacement already has a dedicated,
better-worded validator (setField/setRelationField/delegate/notify/timeout/
expire) stay in the vocabulary but are excluded from the generic line, so
nobody is told the same thing twice.

Unlike #6748's reflected sets these lists are AUTHORED, which is why this was a
separate change: a missing entry rejects an intent that works today. Every entry
mirrors what the readers actually consult, and the notify block validates
against `NotificationIntent.BLOCK_KEYS` - the set its own `fromMap` reads,
pinned to the class's embeddable properties by a test, so a new block property
cannot ship unauthorable.

Verified red-first (10 of 13 unit cases fail without it) and against the corpus
sweep: 259 in-repo fixtures + 63 real production intents carrying 389 authored
`args:` blocks - no false positives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev merged commit 485819b into master Aug 16, 2026
10 checks passed
@delchev
delchev deleted the fix/intent-step-args-keys branch August 16, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

intent: an unknown process-step args key is still silently dropped

1 participant