From cd26dd16b6569e06775a68efce038d5f3851d0ec Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 01:09:53 +0000 Subject: [PATCH 1/2] fix(devx): check-error-status-conformance derives a { status, body } terminal with a nested body and the err.code/err.status assignment form (#15633) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R5b matched the mapper's `{ status: N, body: { … } }` terminal with a body group that forbade nested braces, so every arm whose body carries a conditional spread was skipped — silently, which is the one outcome this gate's header rules out ("the runtime side is DERIVED, never listed … REPORTED as unresolved, never silently dropped"). The 409 `UNIQUE_VIOLATION` answer that `structuredCodeAnswer`'s `DuplicateRecordError` arm returns is exactly that shape, and two tests pin it as emitted while the deriver found no producer for it at all. The body span is now brace-BALANCED, walked on a projection with comments AND string/template/regex content blanked, so a brace that is text cannot open or close it. A second producer of the same code declares its envelope by ASSIGNMENT (`err.code = …; err.status = …`), a shape no rule read at all; R6 reads it, bounded to one identifier inside one block and a line window, and reports a pair it cannot resolve rather than dropping it. Self-test: 40 → 47 cases, two new batteries — the balanced span (the conditional-spread body derives, a body with no code fabricates nothing, a brace inside a string does not close the span early) and the assignment form (constants resolve, an unresolvable status is reported, and neither a different identifier nor a different block ever pairs). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/check-error-status-conformance.mjs | 238 ++++++++++++++++++++- 1 file changed, 230 insertions(+), 8 deletions(-) diff --git a/scripts/check-error-status-conformance.mjs b/scripts/check-error-status-conformance.mjs index 605a41d954..759d420098 100644 --- a/scripts/check-error-status-conformance.mjs +++ b/scripts/check-error-status-conformance.mjs @@ -110,7 +110,7 @@ // `scripts/error-status-unpinned-baseline.json`; a NEW one fails the gate, and a // row that becomes pinned fails it too (ratchet down with `--update`). import { readdirSync, readFileSync, writeFileSync, statSync, existsSync } from 'node:fs'; -import { maskComments } from './js-comment-mask.mjs'; +import { maskComments, scanSource, blank } from './js-comment-mask.mjs'; import { join, relative } from 'node:path'; import { isEntrypoint } from './invoked-as.mjs'; @@ -154,11 +154,13 @@ const SELF_TEST_BATTERIES = Object.freeze({ '20 — the ungraded census: an entry the parser READ but for which no page': 3, '21 — nowPinned, the PRODUCER branch: a baselined code that GAINS a': 2, '22 — nowPinned, the DOC-REMOVED branch: the #9266/#9563 counterfactual,': 2, + '23 — R5b: the body span is brace-BALANCED, and the braces it counts are': 3, + '24 — R6, the ASSIGNMENT form, and the two bounds that keep it honest.': 4, }); // DELETING an entry silences that battery's floor exactly as effectively as // zeroing it, so the roster's own size is pinned too. -const SELF_TEST_BATTERY_FLOOR = 23; +const SELF_TEST_BATTERY_FLOOR = 25; // The key an assertion is filed under when no battery is open. It is not a // declared battery, so it reds by the same set difference rather than silently @@ -323,6 +325,106 @@ function classBodies(src) { const lineOf = (src, idx) => src.slice(0, idx).split('\n').length; +/** + * The two projections a rule may read, from ONE scan of the source. + * + * `src` comments blanked, string/template/regex CONTENT intact — what + * every rule matches on, because a gate's signal (`code: + * 'UNIQUE_VIOLATION'`) is itself a string literal. + * `structural` comments AND literal content blanked — the ONLY projection a + * BRACE WALK may read. A brace inside a string, a template or a + * regex is not a delimiter, and counting one flips the parity of + * every brace after it: the span then closes early (blinding) or + * runs to end of file (fabricating). Both offsets survive the + * mask, so a line number read off either is true of the original. + */ +function projections(raw) { + const { comment, literal } = scanSource(raw); + const both = new Uint8Array(raw.length); + for (let k = 0; k < both.length; k++) both[k] = comment[k] || literal[k]; + return { src: blank(raw, comment), structural: blank(raw, both) }; +} + +/** + * The index of the `}` matching the `{` at `open`, walked on the STRUCTURAL + * projection, or -1 when the source ends first. + */ +function matchingBrace(structural, open) { + let depth = 0; + for (let i = open; i < structural.length; i++) { + if (structural[i] === '{') depth++; + else if (structural[i] === '}' && --depth === 0) return i; + } + return -1; +} + +/** + * True when `a` and `b` sit in the SAME block: no brace opened between them is + * still open at `b`, and no brace closed between them was opened before `a`. + * Read on the structural projection, so a brace in prose or in a string cannot + * join two blocks or split one. + */ +function sameBlock(structural, a, b) { + const [lo, hi] = a <= b ? [a, b] : [b, a]; + let depth = 0; + for (let i = lo; i < hi; i++) { + if (structural[i] === '{') depth++; + else if (structural[i] === '}') { if (depth === 0) return false; depth--; } + } + return depth === 0; +} + +/** + * How far apart the two halves of an ASSIGNMENT-form declaration may sit. The + * bound is `sameBlock` first — a window alone would pair two unrelated + * statements in two neighbouring functions — and this line count second, so a + * long top-level block cannot marry a `code` assignment near its top to a + * `status` assignment near its bottom. The real producer this rule was written + * for writes them on consecutive lines. + */ +const ASSIGNMENT_PAIR_WINDOW_LINES = 20; + +/** + * `.code = ;` paired with the same identifier's `.status = + * ;` (or `.statusCode`), nearest partner first, bounded by `sameBlock` + * and `ASSIGNMENT_PAIR_WINDOW_LINES`. + * + * A pair is refused outright when another assignment to the SAME identifier's + * `code` sits between the two halves — that identifier was restamped in + * between, so which `code` the `status` belongs to is a guess, and this gate + * reports rather than guesses. + * + * @returns {{ ident: string, code: string, status: string, line: number }[]} + */ +function assignmentPairs(src, structural) { + const hits = []; + for (const m of src.matchAll(/\b([A-Za-z_$][\w$]*)\.(code|status|statusCode)\s*=\s*([^;\n]+);/g)) { + hits.push({ + ident: m[1], + prop: m[2] === 'statusCode' ? 'status' : m[2], + expr: m[3].trim(), + at: m.index, + line: lineOf(src, m.index), + }); + } + const out = []; + for (const codeHit of hits) { + if (codeHit.prop !== 'code') continue; + let best; + for (const other of hits) { + if (other === codeHit || other.ident !== codeHit.ident) continue; + if (other.prop !== 'status') continue; + if (Math.abs(other.line - codeHit.line) > ASSIGNMENT_PAIR_WINDOW_LINES) continue; + if (!sameBlock(structural, codeHit.at, other.at)) continue; + const [lo, hi] = codeHit.at <= other.at ? [codeHit.at, other.at] : [other.at, codeHit.at]; + if (hits.some((h) => h.prop === 'code' && h.ident === codeHit.ident && h.at > lo && h.at < hi)) continue; + if (!best || Math.abs(other.at - codeHit.at) < Math.abs(best.at - codeHit.at)) best = other; + } + if (best) out.push({ ident: codeHit.ident, code: codeHit.expr, status: best.expr, line: codeHit.line }); + } + return out; +} + /** * Every (code, status) pair the scanned sources prove reachable, with the * evidence that proves it. @@ -346,7 +448,7 @@ export function deriveRuntimeStatuses(sources, index) { unresolved.push(`${where}: code=${String(code).trim()} status=${String(status).trim()}`); for (const [path, raw] of sources) { - const src = maskComments(raw); + const { src, structural } = projections(raw); // R1 — error classes declaring their own code + status/statusCode. for (const { name, body } of classBodies(src)) { const codeM = /^[ \t]*(?:public\s+|protected\s+|private\s+)?readonly\s+code\s*(?::[^=\n]+)?=\s*([^;\n]+);/m.exec(body); @@ -390,14 +492,41 @@ export function deriveRuntimeStatuses(sources, index) { if (!/^[A-Z][A-Z0-9_]*$/.test(code)) continue; record(code, status, `${path}:${lineOf(src, m.index)}: { code, status }`); } - // R5b — `{ status: N, body: { …, code: 'X' } }`, the mapper's two sanitised - // 5xx terminals, where the pair straddles a nested brace. - for (const m of src.matchAll(/\bstatus\s*:\s*(\d{3})\s*,\s*body\s*:\s*\{([^{}]*)\}/g)) { - const c = /\bcode\s*:\s*'([A-Z][A-Z0-9_]*)'/.exec(m[2]); + // R5b — `{ status: N, body: { … } }`, the REST mapper's terminals, where the + // pair straddles a nested brace. The body span is BRACE-BALANCED and walked + // on `structural`. + // + // It was a brace-FREE character class, which read the two flat 5xx terminals + // and skipped every arm whose body carries a conditional spread — the + // `DuplicateRecordError` arm in `packages/rest/src/error-response.ts` builds + // its 409 `UNIQUE_VIOLATION` body that way, so the code documented as 409 and + // pinned by two tests derived NO producer at all. Skipped, not reported: + // exactly the silent blindness this file's header rules out. + for (const m of src.matchAll(/\bstatus\s*:\s*(\d{3})\s*,\s*body\s*:\s*\{/g)) { + const open = m.index + m[0].length - 1; + const close = matchingBrace(structural, open); + if (close < 0) continue; + const c = /\bcode\s*:\s*'([A-Z][A-Z0-9_]*)'/.exec(src.slice(open + 1, close)); const status = resolveStatus(m[1], index); if (!c || status === undefined) continue; record(c[1], status, `${path}:${lineOf(src, m.index)}: { status, body }`); } + // R6 — the ASSIGNMENT form: `err.code = …;` and `err.status = …;` stamped on + // the same identifier. Every rule above reads a DECLARATION — a class + // property, a call argument, an object literal — so a producer that builds + // its envelope by assigning onto a plain `Error` matched nothing at all. + // `driver-memory`'s `conflictRefusal` is exactly that shape, and its 409 + // was the second of this code's two invisible producers. + for (const pair of assignmentPairs(src, structural)) { + const where = `${path}:${pair.line}: assignment`; + const code = resolveString(pair.code, index); + const status = resolveStatus(pair.status, index); + // A resolved code that is not SCREAMING_SNAKE is not an error code: some + // other `.code`/`.status` pair, dropped the way R3/R4 drop theirs. + if (code !== undefined && !/^[A-Z][A-Z0-9_]*$/.test(code)) continue; + if (code === undefined || status === undefined) { refuse(where, pair.code, pair.status); continue; } + record(code, status, where); + } } return { emitted, unresolved, sites }; } @@ -1160,7 +1289,100 @@ function selfTest() { nowPinnedDocRemovedMessage('TRANSACTION_FAILED').includes('doc entry was removed') && !nowPinnedDocRemovedMessage('TRANSACTION_FAILED').includes('a producer now declares')); - const CASES = 40; + // 23 — R5b: the body span is brace-BALANCED, and the braces it counts are + // the STRUCTURAL ones. The first case is the real shape this rule was + // blind to — `structuredCodeAnswer`'s `DuplicateRecordError` arm, whose + // 409 body carries conditional spreads, so a brace-free character class + // matched nothing and the code derived NO producer at all. The other two + // are the directions a widened span can go wrong in: fabricating a code + // out of a body that has none, and closing early on a brace that is text. + battery('23 — R5b: the body span is brace-BALANCED, and the braces it counts are'); + const spread = runFixture({ + files: { + 'a/m.ts': + 'function answer(error, field) {\n' + + ' return {\n' + + ' status: 409,\n' + + ' body: {\n' + + " error: 'A record with this value already exists',\n" + + " code: 'UNIQUE_VIOLATION',\n" + + " ...(typeof error?.message === 'string' ? { developerMessage: error.message } : {}),\n" + + ' ...(field ? { field } : {}),\n' + + ' },\n' + + ' };\n' + + '}\n', + }, + handling: '', catalog: '', members: ['VALIDATION_ERROR'], + }); + check('23 a conditional-spread body derives across its nested braces', + spread.emitted.get('UNIQUE_VIOLATION')?.has(409) === true, [...spread.emitted.keys()].join(',')); + const noCode = runFixture({ + files: { 'a/m.ts': "const answer = () => ({ status: 409, body: { error: 'conflict', details: { hint: 'retry' } } });" }, + handling: '', catalog: '', members: ['VALIDATION_ERROR'], + }); + check('23b a body with no code inside the span fabricates nothing', + noCode.emitted.size === 0, [...noCode.emitted.keys()].join(',')); + const braceInText = runFixture({ + files: { 'a/m.ts': "const answer = () => ({ status: 403, body: { error: 'unbalanced } brace', code: 'PERMISSION_DENIED' } });" }, + handling: '', catalog: '', members: ['PERMISSION_DENIED'], + }); + check('23c a brace inside a string does not close the span early', + braceInText.emitted.get('PERMISSION_DENIED')?.has(403) === true, [...braceInText.emitted.keys()].join(',')); + + // 24 — R6, the ASSIGNMENT form, and the two bounds that keep it honest. + // `driver-memory`'s `conflictRefusal` stamps its ADR-0112 envelope by + // assigning onto a plain `Error`; every rule before R6 reads a + // DECLARATION, so that producer matched nothing. The bounds are the + // whole risk of reading assignments: two halves that never belonged to + // each other pair into a status nobody declared. + battery('24 — R6, the ASSIGNMENT form, and the two bounds that keep it honest.'); + const stamped = runFixture({ + files: { + 'a/c.ts': "export const REFUSAL_CODE = 'TIMEOUT';\nexport const REFUSAL_STATUS = 504;", + 'a/e.ts': + 'function refuse(message) {\n const err = new Error(message);\n' + + ' err.code = REFUSAL_CODE;\n err.status = REFUSAL_STATUS;\n return err;\n}', + }, + handling: '#### `TIMEOUT`\n**HTTP Status:** 504 \n', catalog: '', members: ['TIMEOUT'], + }); + check('24 the assignment form derives through the constant index', + stamped.reconciledPairs === 1 && stamped.unresolved.length === 0, JSON.stringify(stamped.unresolved)); + const opaqueStatus = runFixture({ + files: { + 'a/e.ts': + 'function refuse(message) {\n const err = new Error(message);\n' + + " err.code = 'TIMEOUT';\n err.status = statusFor(message);\n return err;\n}", + }, + handling: '', catalog: '', members: ['TIMEOUT'], + }); + check('24b an assignment pair whose status will not resolve is REPORTED, not dropped', + opaqueStatus.unresolved.length === 1 && opaqueStatus.unresolved[0].includes('assignment') + && !opaqueStatus.emitted.has('TIMEOUT'), + JSON.stringify(opaqueStatus.unresolved)); + const twoObjects = runFixture({ + files: { + 'a/e.ts': + "function pair() {\n const a = new Error('a');\n const b = new Error('b');\n" + + " a.code = 'TIMEOUT';\n b.status = 504;\n return [a, b];\n}", + }, + handling: '', catalog: '', members: ['TIMEOUT'], + }); + check('24c a code and a status on DIFFERENT identifiers never pair', + !twoObjects.emitted.has('TIMEOUT') && twoObjects.unresolved.length === 0, + JSON.stringify([...twoObjects.emitted.keys(), ...twoObjects.unresolved])); + const twoBlocks = runFixture({ + files: { + 'a/e.ts': + "function one() {\n const err = new Error('x');\n err.code = 'TIMEOUT';\n return err;\n}\n" + + "function two() {\n const err = new Error('y');\n err.status = 504;\n return err;\n}", + }, + handling: '', catalog: '', members: ['TIMEOUT'], + }); + check('24d the same identifier in two BLOCKS never pairs across the boundary', + !twoBlocks.emitted.has('TIMEOUT') && twoBlocks.unresolved.length === 0, + JSON.stringify([...twoBlocks.emitted.keys(), ...twoBlocks.unresolved])); + + const CASES = 47; // ── The floor: every declared battery RAN, and ran its cases (#13489) ─── // // Evaluated after every battery has had its chance and BEFORE the verdict, so From 87ad251784373e08a6338ac3b881310c320c8f9b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 01:36:10 +0000 Subject: [PATCH 2/2] fix(devx): ratchet the unpinned baseline down by the six rows the widened deriver pinned (#15633) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deriver change in the previous commit reads 135 further producer sites, and six codes the baseline still recorded as having NO derivable producer now have one: INVALID_FIELD, INVALID_FILTER, INVALID_QUERY, INVALID_SORT, OBJECT_NOT_FOUND and RECORD_NOT_ACCESSIBLE. Each row had become a false statement about the tree. Removed BY HAND, six rows and nothing else — deliberately not with `--update`, which regenerates the whole list from the current run and could admit a row nobody read. This is the SHRINK direction the gate prescribes in its own failure text ("ratchet the baseline down") and the file's own note requires ("a row that becomes pinned must be removed"); the ⛔ MAINTAINER-ONLY convention covers the baseline-EXPANDING remedy, which the script's self-test battery 11b pins as the asymmetry it is. Authorised by the PM seat `domain:devx @ objectstack` (#6023) on #15633. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- scripts/error-status-unpinned-baseline.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/error-status-unpinned-baseline.json b/scripts/error-status-unpinned-baseline.json index ac8e1f7031..0596eb3368 100644 --- a/scripts/error-status-unpinned-baseline.json +++ b/scripts/error-status-unpinned-baseline.json @@ -12,21 +12,15 @@ "INSUFFICIENT_PRIVILEGES", "INTEGRATION_ERROR", "INVALID_CREDENTIALS", - "INVALID_FIELD", - "INVALID_FILTER", "INVALID_FORMAT", - "INVALID_QUERY", "INVALID_REFERENCE", - "INVALID_SORT", "INVALID_TOKEN", "IP_RESTRICTED", "LICENSE_REQUIRED", "LOCK_CONFLICT", "MAX_RECORDS_EXCEEDED", "MFA_REQUIRED", - "OBJECT_NOT_FOUND", "QUOTA_EXCEEDED", - "RECORD_NOT_ACCESSIBLE", "SESSION_EXPIRED", "TIME_RESTRICTED", "VALUE_OUT_OF_RANGE",