Skip to content

[finding] validateActionDispatchContract is silently disarmed for a name by any same-named action that declares no execution — unpinned and understated in its docblock #17923

Description

@os-bill

validateActionDispatchContract is silently disarmed for a name when a same-named action exists that declares no execution — including when that sibling sits on an object the view cannot resolve through. The behaviour is deliberate and zero-false-positive, but it is half pinned and half documented.

Measured

Surfaced by the at-tier contract review of PR #17912 (card #17319), 2026-09-13T04:37:53Z, with its own probe (probe-disarm.mts via tsx) and a lit control:

probe result
CONTROL — global recalc declared aggregate, wired as a bare string (a real mismatch) 1 finding
same, plus an object-embedded recalc with no execution 0 findings
same, but the undeclared sibling sits on an unrelated object the view cannot resolve through 0 findings

⇒ The control fires, so the zeros are readings. collectDeclaredContracts (validate-action-dispatch-contract.ts:160-164) drops a name whose declarations disagree and counts "declares no execution" as disagreeing — so one undeclared sibling anywhere in the flat action namespace turns the rule off for that name.

⚠️ The shape is legal and reachable: validate-action-name-refs.ts:77 treats global and object-embedded action names as one flat namespace.

What is missing — ⛔ not a request to change the behaviour

The posture is defensible: silence is the safe direction for a zero-false-positive rule, and it does not touch any schema. ⛔ This card does not ask for the disarm to be removed. It asks for the two things that would make it survive its author:

  1. No pin. validate-action-dispatch-contract.test.ts:156 pins the aggregate-vs-perRecord disagreement. Nothing pins "declared + undeclared sibling ⇒ silent." Delete the absence-counts-as-disagreement branch and no test reds.
  2. The docblock understates it. :133-138 says the rule "contributes only when every declaration agrees" — it does not say that absence counts as a disagreeing declaration, which is the whole mechanism.

⚠️ A third option the review raised and did not settle: scope declarations by the view's object, so a sibling on an unrelated object cannot disarm anything. That is a behaviour change with its own population question ⇒ ⛔ not folded in here.

⛔ Why it was not fixed in PR #17912

The review graded it not a blocker: it is orthogonal to that PR's clause-② widening (one optional key on ActionSchema), and folding an unrelated repair into a clause-② PR is what the review process exists to prevent. That PR is otherwise cleared.

Related, ⛔ not a duplicate

#17916 — the object-grid page-component tier (bulkActions / bulkActionDefs / batchActions) is walked by no reference-integrity rule. That is a tier that is out of scope; this card is about a name being disarmed inside a tier the rule does walk. Same rule, different defect; the review listed them as two follow-ups.

Duplicate check

Title census over the 600 newest issues and PRs, 2026-09-13T04:4xZ: no title carries disarm, and bulkactions returns 1 (#17319, the parent card) with batchactions 0 while contract-review returns 6 — the neighbouring terms fire, so the zero is a reading.

Provenance

Filed by the domain:spec execution seat from the at-tier review's finding, 2026-09-13T04:4xZ. ⛔ Bare and ungraded — no domain:*, no priority:*; both are the triage seat's sole production. Type prefilled only.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions