Skip to content

Commit f4489e3

Browse files
os-elonclaude
andauthored
docs(gates): record the object-literal code-helper blindness with its measured blast radius (#13228)
`check-dispatcher-error-vocabulary.mjs`'s `codehelper` shape is anchored on an assignment (`.code = code`), so a helper that stamps through an object literal (`return { severity, code, message }`, shorthand or longhand) matches no shape in either gate: no site AND no unresolved entry. A second, independent blindness sits beside it — `enclosingDeclaration` has no header form for a class METHOD, so a method helper is unreachable even in the assignment position the gate already implements. Measured rather than widened, per triage's scoping of this round. The numbers, the method and both blindnesses are recorded in `OBJECT_LITERAL_CODE_HELPER_BLINDNESS`, printed in the run's declared bounds, and pinned by `--self-test` with a same-genre positive control on each axis, so a later widening of either half cannot land silently. No shape is widened and no verdict row is added: the gate's verdict on the tree is unchanged at 22 classified sites. Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9be57a6 commit f4489e3

1 file changed

Lines changed: 234 additions & 1 deletion

File tree

scripts/check-dispatcher-error-vocabulary.mjs

Lines changed: 234 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,133 @@ export const KEBAB_DIAGNOSTIC_VOCABULARY = Object.freeze({
421421
governedBy: 'neither — an author-time diagnostic vocabulary, ADR-0112 D6c genre',
422422
});
423423

424+
/**
425+
* ## [#13131] A code-carrying helper that stamps through an OBJECT LITERAL is
426+
* ## invisible to `codehelper` — MEASURED, and deliberately NOT closed here
427+
*
428+
* `codehelper` above is anchored on an ASSIGNMENT: its regex is `.code = ident`.
429+
* The reasoning it implements — "the identifier is a PARAMETER, so the literals
430+
* live at the CALL SITES" — is a property of the HELPER, not of the assignment
431+
* operator, and it holds just as well for the equally ordinary helper that
432+
* builds an object literal:
433+
*
434+
* function postureError(code: string, message: string) {
435+
* return { severity: 'error', code, message }; // <- nothing matches
436+
* }
437+
*
438+
* No shape in either gate anchors there. `objlit` needs a quote, `objlitconst`
439+
* needs a SCREAMING_SNAKE identifier after the colon (and the conventional
440+
* parameter name is `code`), `objlittemplate` needs backticks, and `codehelper`
441+
* needs the `.code =`. So this is worse than a wrong verdict: no pattern fires
442+
* at all, which means there is no site AND no unresolved entry either — nothing
443+
* is reported, and nothing says so. That is precisely the bound this gate
444+
* states for itself ("a value it cannot reduce is REPORTED, never dropped")
445+
* failing in the one way the bound cannot notice.
446+
*
447+
* ## TWO blindnesses, not one — and the second is why the first is not enough
448+
*
449+
* ① POSITION. The object-literal stamp position has no shape (above).
450+
* ② DECLARATION FORM. `enclosingDeclaration`'s `DECL_HEADER_RE` recognises
451+
* `function f(`, `constructor(` and `const|let|var f = (` — and NO class
452+
* method. So a code-carrying helper that is a METHOD is out of reach even in
453+
* the position `codehelper` already implements: the same body that produces
454+
* a site as a free function produces nothing as `private error(code, …)`.
455+
* Measured with a same-genre positive control, and pinned below.
456+
*
457+
* These are independent, and the card's own live instance needs BOTH:
458+
* `Parser#error(code, message, start?, tag?)` in `packages/sdui-parser` is a
459+
* class method that stamps through an object literal. Widening the position
460+
* alone would still not reach it.
461+
*
462+
* ## The measurement (blast radius), on `packages/**` non-test source
463+
*
464+
* Method: the predicate — a `code` property of an object literal whose value is
465+
* an identifier that is a PARAMETER of the enclosing function — evaluated on
466+
* the real TypeScript AST, with call-site arguments reduced by THIS gate's own
467+
* literal grammar (`/^[A-Za-z][A-Za-z0-9_]*$/`, so a hyphen does not reduce)
468+
* and checked against the registered vocabulary and the declaration table.
469+
* A regex instrument was tried first and discarded: it matched `${code}` inside
470+
* a template and `f(a, code, b)` inside an argument list, and then LOST a true
471+
* positive because a bracket inside a regex literal unbalanced its scan.
472+
*
473+
* predicate matches 13 helpers (12 named, 1 anonymous)
474+
* (a) call sites newly reached 106 — 76 reduce to a literal, 30 do not
475+
* (b) NEW verdict rows needed 29 — 0 of them already carry a row
476+
* (c) undischargeable `unresolved` findings 4
477+
*
478+
* Split by whether `enclosingDeclaration` can see the declaration at all:
479+
*
480+
* reachable today (blindness ① only) 9 helpers · 81 call sites · 29 rows · 1 unresolved
481+
* blocked by blindness ② 4 helpers · 25 call sites · 0 rows · 3 unresolved
482+
*
483+
* ⚠️ (b) is not the whole cost, and reading it as the whole cost is the trap
484+
* this note exists to prevent. All 29 new rows are LOWERCASE `FieldErrorCode`
485+
* diagnostics (`required`, `invalid_type`, `min_value`, …) in four files —
486+
* ADR-0112 D6 genre, not wire codes. The four `unresolved` findings are the
487+
* expensive half: an `unresolved` entry is pushed UNCONDITIONALLY and no
488+
* declaration row discharges it (see `reconcile`), so each one is a RED gate
489+
* with no verdict available — including `Parser#error`, whose 16 call sites all
490+
* pass kebab literals that this gate's grammar refuses to reduce.
491+
*
492+
* ## No victim today — verified, not assumed
493+
*
494+
* Of the 33 SCREAMING_SNAKE values reached, 33 are already registered; the
495+
* count of values that are BOTH ADR-0112 D1 shaped AND unregistered — i.e. a
496+
* real wire code hiding behind this blindness — is ZERO. `check:error-code-casing`
497+
* likewise reports nothing on any of the files involved. So the value of
498+
* closing this is preventing a future defect, not fixing a present one.
499+
*
500+
* ⚠️ One bound the measurement does NOT have. `helperCodesFor` scans only the
501+
* DECLARING file, so every count here is over IN-FILE call sites. An exported
502+
* helper called from other packages contributes 0 — `sendError` in
503+
* `packages/types/src/response-envelope.ts` has 0 in-file calls and is one of
504+
* the four `unresolved`. Counting cross-file callers needs resolution this
505+
* source scan does not have; that number is UNMEASURED, and it is a lower bound
506+
* on (a), never an upper one.
507+
*
508+
* ## ⛔ What this declaration deliberately does NOT do
509+
*
510+
* ⛔ It does not widen `codehelper`. Triage ruled the measuring round scoped to
511+
* measurement: 「先量出加宽后的爆炸半径, ⛔ 不先加宽。数没量出来之前,"该不该加宽"
512+
* 不是一个可裁的问题。」 The numbers above are that measurement; whether to
513+
* widen is now a decidable question and belongs on its own card, with the
514+
* verdict rows (`domain:cli`) as a rider on the same change.
515+
*
516+
* ⛔ It does not add verdict rows. A row for a site no shape derives is a
517+
* `stale-row` finding, so the rows cannot land before the widening.
518+
*/
519+
export const OBJECT_LITERAL_CODE_HELPER_BLINDNESS = Object.freeze({
520+
/**
521+
* The two spellings of the unseen position. Each is a complete helper whose
522+
* only difference from `SAME_GENRE_CONTROL` is the stamping line.
523+
*/
524+
shorthand: ` return { severity: 'error', code, message };`,
525+
longhand: ` return { severity: 'error', code: code, message };`,
526+
/**
527+
* The SAME-GENRE POSITIVE CONTROL: the identical helper stamping through the
528+
* assignment `codehelper` does implement. `--self-test` runs both, so the
529+
* zero on the object-literal spellings is a READING rather than a harness
530+
* that stopped working — the discipline `KEBAB_DIAGNOSTIC_VOCABULARY` above
531+
* records, applied to a different axis.
532+
*/
533+
control: ` const e = new Error(message);\n (e as any).code = code;\n return e;`,
534+
/** Unregistered in both `StandardErrorCode` and the ledger, in both casings. */
535+
probe: 'HELPER_SCREAMING',
536+
probeLowercase: 'helper_lowercase',
537+
/** Blindness ②: the declaration forms `enclosingDeclaration` cannot see. */
538+
invisibleDeclarationForms: Object.freeze(['class method', 'anonymous arrow']),
539+
/** The live instance, read as evidence only — it is NOT edited by this card. */
540+
liveInstance: 'packages/sdui-parser/src/parse.ts Parser#error(code, message, start?, tag?)',
541+
/** Measured blast radius, `packages/**` non-test source. See the prose above. */
542+
measured: Object.freeze({
543+
helpers: 13,
544+
callSitesNewlyReached: 106,
545+
newVerdictRows: 29,
546+
undischargeableUnresolved: 4,
547+
unregisteredWireCodesHiding: 0,
548+
}),
549+
});
550+
424551
const isTestFile = (rel) =>
425552
/\.(test|spec)\.[cm]?tsx?$/.test(rel) || /(^|\/)(__tests__|__mocks__|fixtures)\//.test(rel);
426553

@@ -2330,6 +2457,104 @@ function selfTest() {
23302457
}
23312458
}
23322459

2460+
// [#13131] The OBJECT-LITERAL stamp position inside a code-carrying helper,
2461+
// and the CLASS-METHOD declaration form, are both outside this gate —
2462+
// MEASURED (see OBJECT_LITERAL_CODE_HELPER_BLINDNESS above) and declared out
2463+
// rather than closed, because the round that measured them was scoped to
2464+
// measurement. Pinned HERE, in the one place the declaration lives, so a
2465+
// later widening of either half cannot land silently: something fails, and
2466+
// what fails names the decision.
2467+
//
2468+
// ⚠️ Nothing below asserts a bare zero. Every zero is paired with the
2469+
// SAME-GENRE POSITIVE CONTROL — the identical helper stamping through the
2470+
// assignment this gate does implement — so a harness that stopped working
2471+
// fails on the control instead of passing on the subject.
2472+
{
2473+
const B = OBJECT_LITERAL_CODE_HELPER_BLINDNESS;
2474+
const DECISION =
2475+
'a code-carrying helper that stamps through an OBJECT LITERAL, or one declared as a CLASS ' +
2476+
'METHOD, is now visible to a gate. That is a gate-POPULATION change: #13131 measured the ' +
2477+
'blast radius at 106 newly reached call sites, 29 new verdict rows in ' +
2478+
`${DECLARATION}, and 4 UNDISCHARGEABLE unresolved findings. Land the rows and the ` +
2479+
'declaration together, and rewrite OBJECT_LITERAL_CODE_HELPER_BLINDNESS — do not adjust ' +
2480+
'this pin to match.';
2481+
2482+
const helper = (body, probe) =>
2483+
`export function postureError(code: string, message: string) {\n${body}\n}\n` +
2484+
`export function deny() { throw postureError('${probe}', 'x'); }\n`;
2485+
const REL = 'packages/x/src/a.ts';
2486+
const derive = (source) =>
2487+
deriveSites({ registered: new Set(['ALREADY_REGISTERED']), files: [{ rel: REL, source }], readFile: () => '' });
2488+
// Population = MATCHES across BOTH gates' recognizers, never the finding
2489+
// list — the lesson the kebab pin above paid for in an ablation.
2490+
const matched = (source) => {
2491+
let n = 0;
2492+
for (const shape of SHAPES) n += [...source.matchAll(new RegExp(shape.re.source, shape.re.flags))].length;
2493+
return n + findViolations(source, REL).length;
2494+
};
2495+
2496+
// ① BLINDNESS ①, the stamp POSITION. The control fires; the two
2497+
// object-literal spellings of the same helper are seen by NOTHING —
2498+
// not matched, so not reported, and not unresolved either.
2499+
const control = helper(B.control, B.probe);
2500+
ok(matched(control) > 0, `the same-genre control matches no recognizer at all — the pin below cannot read as a zero`);
2501+
ok(
2502+
derive(control).sites.some((s) => s.shape === 'codehelper' && s.code === B.probe),
2503+
'the assignment-spelled code helper no longer derives a `codehelper` site — the control for #13131 is dead',
2504+
);
2505+
for (const [name, body] of [['shorthand', B.shorthand], ['longhand', B.longhand]]) {
2506+
for (const probe of [B.probe, B.probeLowercase]) {
2507+
const source = helper(body, probe);
2508+
ok(matched(source) === 0, `a recognizer now MATCHES the ${name} object-literal code helper ('${probe}') — ${DECISION}`);
2509+
const { sites, unresolved } = derive(source);
2510+
ok(
2511+
sites.length === 0 && unresolved.length === 0,
2512+
`the ${name} object-literal code helper ('${probe}') now derives ${sites.length} site(s) and ` +
2513+
`${unresolved.length} unresolved — ${DECISION}`,
2514+
);
2515+
}
2516+
}
2517+
2518+
// ② BLINDNESS ②, the DECLARATION FORM — and it is pinned on SITES rather
2519+
// than on matches, deliberately. Here the recognizer DOES match: the
2520+
// body is the very `.code = code` `codehelper` is written for. What
2521+
// drops it is structural — `enclosingDeclaration`'s DECL_HEADER_RE has
2522+
// no header for a class method, so the identifier is never recognised
2523+
// as a parameter. Same body, same probe, one declaration form apart.
2524+
const body = B.control.replace(/^/gm, ' ');
2525+
const asFunction =
2526+
`export function fail(code: string, message: string) {\n${body}\n}\n` +
2527+
`export function run() { throw fail('${B.probe}', 'x'); }\n`;
2528+
const asMethod =
2529+
`class Thing {\n private fail(code: string, message: string) {\n${body}\n }\n` +
2530+
` run(): void { throw this.fail('${B.probe}', 'x'); }\n}\n`;
2531+
ok(
2532+
derive(asFunction).sites.some((s) => s.shape === 'codehelper' && s.code === B.probe),
2533+
'the free-function control for #13131 blindness ② derives no site — the comparison below is dead',
2534+
);
2535+
ok(matched(asMethod) > 0, 'the class-method form matches no recognizer — blindness ② is not what was measured');
2536+
{
2537+
const { sites, unresolved } = derive(asMethod);
2538+
ok(
2539+
sites.length === 0 && unresolved.length === 0,
2540+
`a CLASS-METHOD code helper now derives ${sites.length} site(s) and ${unresolved.length} unresolved — ${DECISION}`,
2541+
);
2542+
}
2543+
// The mechanism itself, named so the failure above is diagnosable.
2544+
{
2545+
const at = asMethod.indexOf('.code = code');
2546+
const decl = enclosingDeclaration(asMethod, at);
2547+
ok(
2548+
!decl || !parseParamNames(decl.params).includes('code'),
2549+
'enclosingDeclaration now resolves a CLASS METHOD, so `codehelper` reaches method helpers — ' + DECISION,
2550+
);
2551+
}
2552+
ok(
2553+
B.invisibleDeclarationForms.includes('class method'),
2554+
'OBJECT_LITERAL_CODE_HELPER_BLINDNESS no longer declares the class-method form out',
2555+
);
2556+
}
2557+
23332558
if (fail.length) {
23342559
console.error('check-dispatcher-error-vocabulary --self-test FAILED:');
23352560
for (const f of fail) console.error(` - ${f}`);
@@ -2388,7 +2613,15 @@ function main() {
23882613
`${KEBAB_DIAGNOSTIC_VOCABULARY.owner}) are a SEPARATE vocabulary, governed by `+
23892614
`${KEBAB_DIAGNOSTIC_VOCABULARY.governedBy}: no grammar in this gate or in check:error-code-casing `+
23902615
`admits a hyphen, so coverage of them is zero BY DECLARATION, not by accident — see `+
2391-
`KEBAB_DIAGNOSTIC_VOCABULARY in this file, pinned by --self-test.`;
2616+
`KEBAB_DIAGNOSTIC_VOCABULARY in this file, pinned by --self-test.` +
2617+
`\n [#13131] a code-carrying helper that stamps through an OBJECT LITERAL ({ code }) — and one ` +
2618+
`declared as a CLASS METHOD, in either stamp position — is outside this gate: no shape matches, so ` +
2619+
`there is no site AND no unresolved. Coverage of them is zero BY DECLARATION, not by accident. ` +
2620+
`Measured blast radius of closing it: ${OBJECT_LITERAL_CODE_HELPER_BLINDNESS.measured.callSitesNewlyReached} ` +
2621+
`call sites, ${OBJECT_LITERAL_CODE_HELPER_BLINDNESS.measured.newVerdictRows} new verdict rows, ` +
2622+
`${OBJECT_LITERAL_CODE_HELPER_BLINDNESS.measured.undischargeableUnresolved} undischargeable unresolved, ` +
2623+
`${OBJECT_LITERAL_CODE_HELPER_BLINDNESS.measured.unregisteredWireCodesHiding} unregistered wire code(s) ` +
2624+
`hiding today — see OBJECT_LITERAL_CODE_HELPER_BLINDNESS in this file, pinned by --self-test.`;
23922625

23932626
if (argv.includes('--report')) {
23942627
console.log('Derived sites (code / shape / file):');

0 commit comments

Comments
 (0)