Skip to content

check-dispatcher-error-vocabulary is blind to a non-literal code: in an object literal — a constant or template produces no finding, silently #9223

Description

@os-project-manager

Found while implementing #9098 (the REST door-typing half, PR #9222). Filed unassigned; nobody is on it. Not fixed there — that card's scope was the door's typing, and widening this gate's scan is a separate, self-contained change with its own self-test obligations.

The gap

scripts/check-dispatcher-error-vocabulary.mjs publishes its recognised spellings in SHAPES, and its own header states the cost precisely:

the price of a source scan is that it sees only the spellings it knows, and an unrecognised one produces no finding, SILENTLY. Reaching for a spelling that is not here? Extend SHAPES and add a --self-test case in the same edit.

Of the four shapes, only one resolves a non-literal value — classconst, which handles readonly code = CONST on a class field. The objlit shape requires a quoted literal:

{name: 'objlit',re: /\bcode:\s*'([A-Za-z][A-Za-z0-9_]*)'/g,literal: true},

So both of these stamp a wire code and are matched by nothing:

throwObject.assign(newError(msg),{code: SOME_MODULE_CONST});// constant in an object literalsendDeclaredFault(res,{code: `APPROVAL_${action}_FAILED`, ... });// template-generated

Neither reaches resolveConstant, because that is only called for shapes declared literal: false, and objlit is literal: true. The value is not reported as unresolved either — the site is simply never seen. That is a different outcome from the header's stated bound ("A constant this gate cannot resolve is REPORTED as unresolved, never dropped"), which holds for classconst and not here.

Why it is worth a card rather than a note

This is not hypothetical, and it has already cost a round. #8885 could not lean on this gate for the template-generated APPROVAL_*_FAILED family and had to add a bespoke runtime pin instead — packages/rest/src/rest-approvals-wire-codes.test.ts — to mechanically redden a future approvals route whose generated code goes unregistered. One package now has a hand-written class pin doing the job the repo-wide gate advertises, and no equivalent exists for any other package that generates or indirects a code.

The blind spot is also the quiet kind: adding a producer in either spelling leaves the gate green and prints nothing in its declared bounds, so a partial gate keeps reading as a complete one.

Possible shapes, not a recommendation

  • Extend objlit into a literal: false sibling (code: IDENT in an object literal) so resolveConstant runs on it, with --self-test cases in the same edit as the header demands.
  • Report a template-literal code: as unresolved rather than dropping it — the gate cannot evaluate it, and the header's own rule says an unresolvable value is a finding, never a silent pass.
  • Consider whether rest-approvals-wire-codes.test.ts then becomes redundant, or stays as the runtime half.

Related: #8087 (the gate), #8885 (the approvals workaround), #9098 / PR #9222 (where this was noticed; that PR added a door-typing half to the same script but did not touch SHAPES).


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions