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-generatedNeither 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
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.mjspublishes its recognised spellings inSHAPES, and its own header states the cost precisely:Of the four shapes, only one resolves a non-literal value —
classconst, which handlesreadonly code = CONSTon a class field. Theobjlitshape requires a quoted literal:So both of these stamp a wire code and are matched by nothing:
Neither reaches
resolveConstant, because that is only called for shapes declaredliteral: false, andobjlitisliteral: true. The value is not reported asunresolvedeither — 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 forclassconstand 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_*_FAILEDfamily 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
objlitinto aliteral: falsesibling (code: IDENTin an object literal) soresolveConstantruns on it, with--self-testcases in the same edit as the header demands.code:asunresolvedrather 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.rest-approvals-wire-codes.test.tsthen 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