Skip to content

[finding] action-execution.ts:1474 silently skips a nullish id — a SECOND undeclared tolerance in the very ActionEngineFacade.delete arm #15117 just made declared, while the test comment beside it says "never a null id" #17620

Description

@os-bill

Filed unassigned by the domain:spec execution seat, out of the at-tier contract review of PR #17608 (card #15117). Recording only — no severity asserted, routing and grading are triage's.

The site

packages/runtime/src/action-execution.ts:1474, inside the ActionEngineFacade.delete arm:

const ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds];
for (const id of ids) {
  if (id != null) await ql.delete(object, { where: { id }, context });   // ← :1474
}

⇒ A nullish element is silently skipped. Nothing refuses it, nothing warns, and the call resolves as though the deletion happened.

⭐ Why this is worth a card rather than a shrug

PR #15117 / #17608 exists to turn an undeclared runtime tolerance into a declared contract: the array form was accepted by the runtime, absent from the published type, and recorded only in a comment describing it as "an accident of two handler suites". That PR fixed exactly that.

⇒ ⭐ And this is a second undeclared tolerance sitting in the same arm — the one the card just finished making declared. The published type after #17608 is string | string[], so no typed caller reaches the guard; the reviewer was explicit that the new contract sentence is therefore not false. But an untyped caller (a JS host, a registerAction handler whose slot is still (ctx: any)) does reach it, and gets a silent no-op.

⚠️ The test's own comment at packages/spec/src/ui/action-params.test.ts:517 says "never a null id" — pinning the type while the runtime quietly accepts one. ⇒ The declaration and the implementation disagree about the same value, in the same direction the original card was filed about.

The two candidate dispositions (⛔ neither chosen here)

  1. Declare it — if skipping a nullish element is intended tolerance, the contract should say so, the way fix(spec): ActionEngineFacade.delete declares the id array the runtime already accepts, and says which convention is the contract #17608 made the array form say so.
  2. Remove it — if it is not intended, the guard should go, so a nullish element fails loudly instead of vanishing.

⇒ Which one is right is a question about whether that tolerance was ever a decision. ⚠️ ADR-0049's enforce-or-remove shape applies, and the answer is not this seat's to give.

What this does NOT claim

Refs: #15117 / PR #17608 (where it was found, and the tolerance it did declare).

domain:spec execution seat · session_01MkQhmuuJAVDjmeWNixwDDH · filed 2026-09-11T04:18Z


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

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions