110110// `scripts/error-status-unpinned-baseline.json`; a NEW one fails the gate, and a
111111// row that becomes pinned fails it too (ratchet down with `--update`).
112112import { readdirSync , readFileSync , writeFileSync , statSync , existsSync } from 'node:fs' ;
113- import { maskComments } from './js-comment-mask.mjs' ;
113+ import { maskComments , scanSource , blank } from './js-comment-mask.mjs' ;
114114import { join , relative } from 'node:path' ;
115115import { isEntrypoint } from './invoked-as.mjs' ;
116116
@@ -154,11 +154,13 @@ const SELF_TEST_BATTERIES = Object.freeze({
154154 '20 — the ungraded census: an entry the parser READ but for which no page' : 3 ,
155155 '21 — nowPinned, the PRODUCER branch: a baselined code that GAINS a' : 2 ,
156156 '22 — nowPinned, the DOC-REMOVED branch: the #9266/#9563 counterfactual,' : 2 ,
157+ '23 — R5b: the body span is brace-BALANCED, and the braces it counts are' : 3 ,
158+ '24 — R6, the ASSIGNMENT form, and the two bounds that keep it honest.' : 4 ,
157159} ) ;
158160
159161// DELETING an entry silences that battery's floor exactly as effectively as
160162// zeroing it, so the roster's own size is pinned too.
161- const SELF_TEST_BATTERY_FLOOR = 23 ;
163+ const SELF_TEST_BATTERY_FLOOR = 25 ;
162164
163165// The key an assertion is filed under when no battery is open. It is not a
164166// declared battery, so it reds by the same set difference rather than silently
@@ -323,6 +325,106 @@ function classBodies(src) {
323325
324326const lineOf = ( src , idx ) => src . slice ( 0 , idx ) . split ( '\n' ) . length ;
325327
328+ /**
329+ * The two projections a rule may read, from ONE scan of the source.
330+ *
331+ * `src` comments blanked, string/template/regex CONTENT intact — what
332+ * every rule matches on, because a gate's signal (`code:
333+ * 'UNIQUE_VIOLATION'`) is itself a string literal.
334+ * `structural` comments AND literal content blanked — the ONLY projection a
335+ * BRACE WALK may read. A brace inside a string, a template or a
336+ * regex is not a delimiter, and counting one flips the parity of
337+ * every brace after it: the span then closes early (blinding) or
338+ * runs to end of file (fabricating). Both offsets survive the
339+ * mask, so a line number read off either is true of the original.
340+ */
341+ function projections ( raw ) {
342+ const { comment, literal } = scanSource ( raw ) ;
343+ const both = new Uint8Array ( raw . length ) ;
344+ for ( let k = 0 ; k < both . length ; k ++ ) both [ k ] = comment [ k ] || literal [ k ] ;
345+ return { src : blank ( raw , comment ) , structural : blank ( raw , both ) } ;
346+ }
347+
348+ /**
349+ * The index of the `}` matching the `{` at `open`, walked on the STRUCTURAL
350+ * projection, or -1 when the source ends first.
351+ */
352+ function matchingBrace ( structural , open ) {
353+ let depth = 0 ;
354+ for ( let i = open ; i < structural . length ; i ++ ) {
355+ if ( structural [ i ] === '{' ) depth ++ ;
356+ else if ( structural [ i ] === '}' && -- depth === 0 ) return i ;
357+ }
358+ return - 1 ;
359+ }
360+
361+ /**
362+ * True when `a` and `b` sit in the SAME block: no brace opened between them is
363+ * still open at `b`, and no brace closed between them was opened before `a`.
364+ * Read on the structural projection, so a brace in prose or in a string cannot
365+ * join two blocks or split one.
366+ */
367+ function sameBlock ( structural , a , b ) {
368+ const [ lo , hi ] = a <= b ? [ a , b ] : [ b , a ] ;
369+ let depth = 0 ;
370+ for ( let i = lo ; i < hi ; i ++ ) {
371+ if ( structural [ i ] === '{' ) depth ++ ;
372+ else if ( structural [ i ] === '}' ) { if ( depth === 0 ) return false ; depth -- ; }
373+ }
374+ return depth === 0 ;
375+ }
376+
377+ /**
378+ * How far apart the two halves of an ASSIGNMENT-form declaration may sit. The
379+ * bound is `sameBlock` first — a window alone would pair two unrelated
380+ * statements in two neighbouring functions — and this line count second, so a
381+ * long top-level block cannot marry a `code` assignment near its top to a
382+ * `status` assignment near its bottom. The real producer this rule was written
383+ * for writes them on consecutive lines.
384+ */
385+ const ASSIGNMENT_PAIR_WINDOW_LINES = 20 ;
386+
387+ /**
388+ * `<ident>.code = <expr>;` paired with the same identifier's `<ident>.status =
389+ * <expr>;` (or `.statusCode`), nearest partner first, bounded by `sameBlock`
390+ * and `ASSIGNMENT_PAIR_WINDOW_LINES`.
391+ *
392+ * A pair is refused outright when another assignment to the SAME identifier's
393+ * `code` sits between the two halves — that identifier was restamped in
394+ * between, so which `code` the `status` belongs to is a guess, and this gate
395+ * reports rather than guesses.
396+ *
397+ * @returns {{ ident: string, code: string, status: string, line: number }[] }
398+ */
399+ function assignmentPairs ( src , structural ) {
400+ const hits = [ ] ;
401+ for ( const m of src . matchAll ( / \b ( [ A - Z a - z _ $ ] [ \w $ ] * ) \. ( c o d e | s t a t u s | s t a t u s C o d e ) \s * = \s * ( [ ^ ; \n ] + ) ; / g) ) {
402+ hits . push ( {
403+ ident : m [ 1 ] ,
404+ prop : m [ 2 ] === 'statusCode' ? 'status' : m [ 2 ] ,
405+ expr : m [ 3 ] . trim ( ) ,
406+ at : m . index ,
407+ line : lineOf ( src , m . index ) ,
408+ } ) ;
409+ }
410+ const out = [ ] ;
411+ for ( const codeHit of hits ) {
412+ if ( codeHit . prop !== 'code' ) continue ;
413+ let best ;
414+ for ( const other of hits ) {
415+ if ( other === codeHit || other . ident !== codeHit . ident ) continue ;
416+ if ( other . prop !== 'status' ) continue ;
417+ if ( Math . abs ( other . line - codeHit . line ) > ASSIGNMENT_PAIR_WINDOW_LINES ) continue ;
418+ if ( ! sameBlock ( structural , codeHit . at , other . at ) ) continue ;
419+ const [ lo , hi ] = codeHit . at <= other . at ? [ codeHit . at , other . at ] : [ other . at , codeHit . at ] ;
420+ if ( hits . some ( ( h ) => h . prop === 'code' && h . ident === codeHit . ident && h . at > lo && h . at < hi ) ) continue ;
421+ if ( ! best || Math . abs ( other . at - codeHit . at ) < Math . abs ( best . at - codeHit . at ) ) best = other ;
422+ }
423+ if ( best ) out . push ( { ident : codeHit . ident , code : codeHit . expr , status : best . expr , line : codeHit . line } ) ;
424+ }
425+ return out ;
426+ }
427+
326428/**
327429 * Every (code, status) pair the scanned sources prove reachable, with the
328430 * evidence that proves it.
@@ -346,7 +448,7 @@ export function deriveRuntimeStatuses(sources, index) {
346448 unresolved . push ( `${ where } : code=${ String ( code ) . trim ( ) } status=${ String ( status ) . trim ( ) } ` ) ;
347449
348450 for ( const [ path , raw ] of sources ) {
349- const src = maskComments ( raw ) ;
451+ const { src, structural } = projections ( raw ) ;
350452 // R1 — error classes declaring their own code + status/statusCode.
351453 for ( const { name, body } of classBodies ( src ) ) {
352454 const codeM = / ^ [ \t ] * (?: p u b l i c \s + | p r o t e c t e d \s + | p r i v a t e \s + ) ? r e a d o n l y \s + c o d e \s * (?: : [ ^ = \n ] + ) ? = \s * ( [ ^ ; \n ] + ) ; / m. exec ( body ) ;
@@ -390,14 +492,41 @@ export function deriveRuntimeStatuses(sources, index) {
390492 if ( ! / ^ [ A - Z ] [ A - Z 0 - 9 _ ] * $ / . test ( code ) ) continue ;
391493 record ( code , status , `${ path } :${ lineOf ( src , m . index ) } : { code, status }` ) ;
392494 }
393- // R5b — `{ status: N, body: { …, code: 'X' } }`, the mapper's two sanitised
394- // 5xx terminals, where the pair straddles a nested brace.
395- for ( const m of src . matchAll ( / \b s t a t u s \s * : \s * ( \d { 3 } ) \s * , \s * b o d y \s * : \s * \{ ( [ ^ { } ] * ) \} / g) ) {
396- const c = / \b c o d e \s * : \s * ' ( [ A - Z ] [ A - Z 0 - 9 _ ] * ) ' / . exec ( m [ 2 ] ) ;
495+ // R5b — `{ status: N, body: { … } }`, the REST mapper's terminals, where the
496+ // pair straddles a nested brace. The body span is BRACE-BALANCED and walked
497+ // on `structural`.
498+ //
499+ // It was a brace-FREE character class, which read the two flat 5xx terminals
500+ // and skipped every arm whose body carries a conditional spread — the
501+ // `DuplicateRecordError` arm in `packages/rest/src/error-response.ts` builds
502+ // its 409 `UNIQUE_VIOLATION` body that way, so the code documented as 409 and
503+ // pinned by two tests derived NO producer at all. Skipped, not reported:
504+ // exactly the silent blindness this file's header rules out.
505+ for ( const m of src . matchAll ( / \b s t a t u s \s * : \s * ( \d { 3 } ) \s * , \s * b o d y \s * : \s * \{ / g) ) {
506+ const open = m . index + m [ 0 ] . length - 1 ;
507+ const close = matchingBrace ( structural , open ) ;
508+ if ( close < 0 ) continue ;
509+ const c = / \b c o d e \s * : \s * ' ( [ A - Z ] [ A - Z 0 - 9 _ ] * ) ' / . exec ( src . slice ( open + 1 , close ) ) ;
397510 const status = resolveStatus ( m [ 1 ] , index ) ;
398511 if ( ! c || status === undefined ) continue ;
399512 record ( c [ 1 ] , status , `${ path } :${ lineOf ( src , m . index ) } : { status, body }` ) ;
400513 }
514+ // R6 — the ASSIGNMENT form: `err.code = …;` and `err.status = …;` stamped on
515+ // the same identifier. Every rule above reads a DECLARATION — a class
516+ // property, a call argument, an object literal — so a producer that builds
517+ // its envelope by assigning onto a plain `Error` matched nothing at all.
518+ // `driver-memory`'s `conflictRefusal` is exactly that shape, and its 409
519+ // was the second of this code's two invisible producers.
520+ for ( const pair of assignmentPairs ( src , structural ) ) {
521+ const where = `${ path } :${ pair . line } : assignment` ;
522+ const code = resolveString ( pair . code , index ) ;
523+ const status = resolveStatus ( pair . status , index ) ;
524+ // A resolved code that is not SCREAMING_SNAKE is not an error code: some
525+ // other `.code`/`.status` pair, dropped the way R3/R4 drop theirs.
526+ if ( code !== undefined && ! / ^ [ A - Z ] [ A - Z 0 - 9 _ ] * $ / . test ( code ) ) continue ;
527+ if ( code === undefined || status === undefined ) { refuse ( where , pair . code , pair . status ) ; continue ; }
528+ record ( code , status , where ) ;
529+ }
401530 }
402531 return { emitted, unresolved, sites } ;
403532}
@@ -1160,7 +1289,100 @@ function selfTest() {
11601289 nowPinnedDocRemovedMessage ( 'TRANSACTION_FAILED' ) . includes ( 'doc entry was removed' )
11611290 && ! nowPinnedDocRemovedMessage ( 'TRANSACTION_FAILED' ) . includes ( 'a producer now declares' ) ) ;
11621291
1163- const CASES = 40 ;
1292+ // 23 — R5b: the body span is brace-BALANCED, and the braces it counts are
1293+ // the STRUCTURAL ones. The first case is the real shape this rule was
1294+ // blind to — `structuredCodeAnswer`'s `DuplicateRecordError` arm, whose
1295+ // 409 body carries conditional spreads, so a brace-free character class
1296+ // matched nothing and the code derived NO producer at all. The other two
1297+ // are the directions a widened span can go wrong in: fabricating a code
1298+ // out of a body that has none, and closing early on a brace that is text.
1299+ battery ( '23 — R5b: the body span is brace-BALANCED, and the braces it counts are' ) ;
1300+ const spread = runFixture ( {
1301+ files : {
1302+ 'a/m.ts' :
1303+ 'function answer(error, field) {\n'
1304+ + ' return {\n'
1305+ + ' status: 409,\n'
1306+ + ' body: {\n'
1307+ + " error: 'A record with this value already exists',\n"
1308+ + " code: 'UNIQUE_VIOLATION',\n"
1309+ + " ...(typeof error?.message === 'string' ? { developerMessage: error.message } : {}),\n"
1310+ + ' ...(field ? { field } : {}),\n'
1311+ + ' },\n'
1312+ + ' };\n'
1313+ + '}\n' ,
1314+ } ,
1315+ handling : '' , catalog : '' , members : [ 'VALIDATION_ERROR' ] ,
1316+ } ) ;
1317+ check ( '23 a conditional-spread body derives across its nested braces' ,
1318+ spread . emitted . get ( 'UNIQUE_VIOLATION' ) ?. has ( 409 ) === true , [ ...spread . emitted . keys ( ) ] . join ( ',' ) ) ;
1319+ const noCode = runFixture ( {
1320+ files : { 'a/m.ts' : "const answer = () => ({ status: 409, body: { error: 'conflict', details: { hint: 'retry' } } });" } ,
1321+ handling : '' , catalog : '' , members : [ 'VALIDATION_ERROR' ] ,
1322+ } ) ;
1323+ check ( '23b a body with no code inside the span fabricates nothing' ,
1324+ noCode . emitted . size === 0 , [ ...noCode . emitted . keys ( ) ] . join ( ',' ) ) ;
1325+ const braceInText = runFixture ( {
1326+ files : { 'a/m.ts' : "const answer = () => ({ status: 403, body: { error: 'unbalanced } brace', code: 'PERMISSION_DENIED' } });" } ,
1327+ handling : '' , catalog : '' , members : [ 'PERMISSION_DENIED' ] ,
1328+ } ) ;
1329+ check ( '23c a brace inside a string does not close the span early' ,
1330+ braceInText . emitted . get ( 'PERMISSION_DENIED' ) ?. has ( 403 ) === true , [ ...braceInText . emitted . keys ( ) ] . join ( ',' ) ) ;
1331+
1332+ // 24 — R6, the ASSIGNMENT form, and the two bounds that keep it honest.
1333+ // `driver-memory`'s `conflictRefusal` stamps its ADR-0112 envelope by
1334+ // assigning onto a plain `Error`; every rule before R6 reads a
1335+ // DECLARATION, so that producer matched nothing. The bounds are the
1336+ // whole risk of reading assignments: two halves that never belonged to
1337+ // each other pair into a status nobody declared.
1338+ battery ( '24 — R6, the ASSIGNMENT form, and the two bounds that keep it honest.' ) ;
1339+ const stamped = runFixture ( {
1340+ files : {
1341+ 'a/c.ts' : "export const REFUSAL_CODE = 'TIMEOUT';\nexport const REFUSAL_STATUS = 504;" ,
1342+ 'a/e.ts' :
1343+ 'function refuse(message) {\n const err = new Error(message);\n'
1344+ + ' err.code = REFUSAL_CODE;\n err.status = REFUSAL_STATUS;\n return err;\n}' ,
1345+ } ,
1346+ handling : '#### `TIMEOUT`\n**HTTP Status:** 504 \n' , catalog : '' , members : [ 'TIMEOUT' ] ,
1347+ } ) ;
1348+ check ( '24 the assignment form derives through the constant index' ,
1349+ stamped . reconciledPairs === 1 && stamped . unresolved . length === 0 , JSON . stringify ( stamped . unresolved ) ) ;
1350+ const opaqueStatus = runFixture ( {
1351+ files : {
1352+ 'a/e.ts' :
1353+ 'function refuse(message) {\n const err = new Error(message);\n'
1354+ + " err.code = 'TIMEOUT';\n err.status = statusFor(message);\n return err;\n}" ,
1355+ } ,
1356+ handling : '' , catalog : '' , members : [ 'TIMEOUT' ] ,
1357+ } ) ;
1358+ check ( '24b an assignment pair whose status will not resolve is REPORTED, not dropped' ,
1359+ opaqueStatus . unresolved . length === 1 && opaqueStatus . unresolved [ 0 ] . includes ( 'assignment' )
1360+ && ! opaqueStatus . emitted . has ( 'TIMEOUT' ) ,
1361+ JSON . stringify ( opaqueStatus . unresolved ) ) ;
1362+ const twoObjects = runFixture ( {
1363+ files : {
1364+ 'a/e.ts' :
1365+ "function pair() {\n const a = new Error('a');\n const b = new Error('b');\n"
1366+ + " a.code = 'TIMEOUT';\n b.status = 504;\n return [a, b];\n}" ,
1367+ } ,
1368+ handling : '' , catalog : '' , members : [ 'TIMEOUT' ] ,
1369+ } ) ;
1370+ check ( '24c a code and a status on DIFFERENT identifiers never pair' ,
1371+ ! twoObjects . emitted . has ( 'TIMEOUT' ) && twoObjects . unresolved . length === 0 ,
1372+ JSON . stringify ( [ ...twoObjects . emitted . keys ( ) , ...twoObjects . unresolved ] ) ) ;
1373+ const twoBlocks = runFixture ( {
1374+ files : {
1375+ 'a/e.ts' :
1376+ "function one() {\n const err = new Error('x');\n err.code = 'TIMEOUT';\n return err;\n}\n"
1377+ + "function two() {\n const err = new Error('y');\n err.status = 504;\n return err;\n}" ,
1378+ } ,
1379+ handling : '' , catalog : '' , members : [ 'TIMEOUT' ] ,
1380+ } ) ;
1381+ check ( '24d the same identifier in two BLOCKS never pairs across the boundary' ,
1382+ ! twoBlocks . emitted . has ( 'TIMEOUT' ) && twoBlocks . unresolved . length === 0 ,
1383+ JSON . stringify ( [ ...twoBlocks . emitted . keys ( ) , ...twoBlocks . unresolved ] ) ) ;
1384+
1385+ const CASES = 47 ;
11641386 // ── The floor: every declared battery RAN, and ran its cases (#13489) ───
11651387 //
11661388 // Evaluated after every battery has had its chance and BEFORE the verdict, so
0 commit comments