@@ -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+
424551const isTestFile = ( rel ) =>
425552 / \. ( t e s t | s p e c ) \. [ c m ] ? t s x ? $ / . test ( rel ) || / ( ^ | \/ ) ( _ _ t e s t s _ _ | _ _ m o c k s _ _ | f i x t u r e s ) \/ / . 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