From 1146c948f277a20712a56bfaf7f78f14c442ea6e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 11:45:32 +0000 Subject: [PATCH] fix(devx): read the ledger row recognizer through codeOnly, not raw text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `parseLedgerSource` assembled rows with `routeRe` over the RAW source while every other scan in the file read through `codeOnly`. A `route:` followed by a single-quoted literal inside a COMMENT or a string PAYLOAD therefore did not merely mis-count — it became a ROW, and no verdict could fire, because the partial-read guard is keyed on the gap between `rows` and `routesDeclared` and both terms read raw, so both moved together. A prose line quoting a `client:` too minted a fully client-bound phantom, which then joined the UNREACHABLE population (no registrar tail can match a route nobody mounts) and inflated it silently. The recognizer, its row window, its `client:` read and the first `routesDeclared` term now all read `code`; values are cut out of the raw text by the masked match's offsets, which `blank` preserves byte for byte. Population move, priced before landing: none. Measured on all seven live ledgers, before and after — rows 259, routesDeclared 259, clientsDeclared 221, declined 0, reachable 45, UNREACHABLE 176, and the sha256 of both the full 259-row population and the 176-row unreachable population identical across the change. `--bridge-coverage` differs by exactly one added report line. `outsideCode` is the reporting half: the mask closes the hole by making a prose-quoted lead produce nothing, and "produces nothing" is the silence every other report in this file exists to break. It NAMES every lead the mask dropped, enters neither ratio and carries no verdict — a comment quoting a retired path is legitimate prose, not a broken scan. Two residuals measured and filed rather than ridden along, each a separate population move wanting its own before/after: #10793 (`typeDeclRegions` still not applied to the recognizer) and #10794 (`declinedIn` still reads raw). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt --- scripts/docs-audit/affected-docs.mjs | 197 +++++++++++++++++++++++++-- 1 file changed, 183 insertions(+), 14 deletions(-) diff --git a/scripts/docs-audit/affected-docs.mjs b/scripts/docs-audit/affected-docs.mjs index 0d188eca79..e11e41d7cd 100644 --- a/scripts/docs-audit/affected-docs.mjs +++ b/scripts/docs-audit/affected-docs.mjs @@ -543,6 +543,14 @@ if (args.includes('--bridge-coverage')) { // difference visible without anyone having to know the number by heart. console.log(` ledger rows read ........... ${coverage.rowsParsed} of ${coverage.routesDeclared} declared`); console.log(` client-bound ledger rows ... ${coverage.clientRows} of ${coverage.clientsDeclared} declared`); + // #10683, and normally 0. A lead spelled the way the recognizer reads, sitting where + // the mask says code is not — a comment, or a string payload. Printed on every run + // rather than only when non-zero, for the reason the fraction above is printed whole: + // a number nobody ever sees at rest is a number nobody notices moving. + console.log(` prose-quoted leads (no row) . ${coverage.leadsOutsideCode}`); + for (const l of coverage.ledgers) { + for (const d of l.outsideCode) console.log(` ${l.file}:${d.line} ${d.text}`); + } console.log(` reachable ................ ${coverage.reachable}`); console.log(` UNREACHABLE .............. ${coverage.unreachable}`); for (const l of coverage.ledgers) { @@ -1122,11 +1130,12 @@ function parseRegistrarSource(text) { * recognizer narrower than the repo must report "unrecognised" rather than render as a * verdict, and this is that report for this bridge. * - * @param {Array<{file: string, rows: Array<{route: string, client: string|null}>, declined: Array<{key: string, line: number, text: string}>, routesDeclared: number, clientsDeclared: number}>} ledgers + * @param {Array<{file: string, rows: Array<{route: string, client: string|null}>, declined: Array<{key: string, line: number, text: string}>, routesDeclared: number, clientsDeclared: number, outsideCode: Array<{key: string, line: number, text: string}>}>} ledgers * as `parseLedgerSource` returns them. ⛔ No `?? []` / `?? rows.length` defaults are * applied to the declared counts below: an ABSENT report of what a ledger declared must * never render as "it declared exactly what we read", which is the one reading this - * whole verdict exists to prevent. A caller that omits them throws here instead. + * whole verdict exists to prevent. A caller that omits them throws here instead — and + * `outsideCode` (#10683) is read on the same terms, for the same reason. * @param {Iterable} tails every route tail the registrar scan produced */ function bridgeCoverageFrom(ledgers, tails) { @@ -1141,7 +1150,8 @@ function bridgeCoverageFrom(ledgers, tails) { let rowsParsed = 0; let routesDeclaredAll = 0; let clientsDeclaredAll = 0; - for (const { file, rows, declined, routesDeclared, clientsDeclared } of ledgers) { + let leadsOutsideCode = 0; + for (const { file, rows, declined, routesDeclared, clientsDeclared, outsideCode } of ledgers) { const bound = rows.filter((r) => r.client); const hit = bound.filter((r) => selects(r.route)); clientRows += bound.length; @@ -1149,7 +1159,8 @@ function bridgeCoverageFrom(ledgers, tails) { rowsParsed += rows.length; routesDeclaredAll += routesDeclared; clientsDeclaredAll += clientsDeclared; - byLedger.push({ file, clientRows: bound.length, reachable: hit.length, unreachable: bound.length - hit.length, rowsParsed: rows.length, routesDeclared, clientsDeclared, declined }); + leadsOutsideCode += outsideCode.length; + byLedger.push({ file, clientRows: bound.length, reachable: hit.length, unreachable: bound.length - hit.length, rowsParsed: rows.length, routesDeclared, clientsDeclared, declined, outsideCode }); } // ZERO IS NOT A CLEAN REPO, IT IS A BROKEN SCAN — `check-engine-double-contract`'s // invariant, applied to this population (#9747 quotes it as the germ worth @@ -1193,7 +1204,11 @@ function bridgeCoverageFrom(ledgers, tails) { : '')); } } - return { measured: true, clientRows, reachable, unreachable: clientRows - reachable, tails: tailList.length, rowsParsed, routesDeclared: routesDeclaredAll, clientsDeclared: clientsDeclaredAll, ledgers: byLedger, brokenScan }; + // ⛔ `leadsOutsideCode` is deliberately NOT pushed to `brokenScan` above. It is the one + // number here that describes the ledger's PROSE rather than its route surface, and a + // comment that quotes a retired path is not a broken scan — before #10683 it was a + // phantom ROW, which is what makes counting it worth doing and gating it wrong. + return { measured: true, clientRows, reachable, unreachable: clientRows - reachable, tails: tailList.length, rowsParsed, routesDeclared: routesDeclaredAll, clientsDeclared: clientsDeclaredAll, leadsOutsideCode, ledgers: byLedger, brokenScan }; } /** @@ -1225,8 +1240,8 @@ function scanRouteSurface() { let text; try { text = readFileSync(join(repoRoot, rel), 'utf8'); } catch { continue; } if (LEDGER_FILE_RE.test(rel)) { - const { rows, declined, routesDeclared, clientsDeclared } = parseLedgerSource(text); - ledgers.push({ file: rel, rows, declined, routesDeclared, clientsDeclared }); + const { rows, declined, routesDeclared, clientsDeclared, outsideCode } = parseLedgerSource(text); + ledgers.push({ file: rel, rows, declined, routesDeclared, clientsDeclared, outsideCode }); ledgerRows.push(...rows); } if (REGISTRAR_FILE_RE.test(rel)) { @@ -1454,7 +1469,15 @@ function declinedIn(s) { * row never does. The denominator below is therefore every declared `route:` value, in any * spelling, and the partition `rows + declined === routesDeclared` is pinned in `--self-test`. * - * @returns {{rows: Array<{route: string, client: string|null}>, declined: Array<{key: string, line: number, text: string}>, routesDeclared: number, clientsDeclared: number}} + * THE RECOGNIZER NOW READS CODE, NOT PROSE (#10683). It used to scan the RAW text while + * every other scan here read `codeOnly`, so a `route:` quoted in a comment or inside a + * string payload became a ROW — silently, because the partial-read verdict is keyed on the + * gap between `rows` and `routesDeclared` and both terms read raw. The population move that + * closing it implies was priced on the tree that had no instance of the shape: delta 0 on + * all seven ledgers. `outsideCode` is what keeps the fix from trading a phantom row for a + * new silence — it NAMES every lead the mask dropped, and carries no verdict. + * + * @returns {{rows: Array<{route: string, client: string|null}>, declined: Array<{key: string, line: number, text: string}>, routesDeclared: number, clientsDeclared: number, outsideCode: Array<{key: string, line: number, text: string}>}} */ function parseLedgerSource(text) { const rows = []; @@ -1464,21 +1487,67 @@ function parseLedgerSource(text) { // same file, is what #10636 was: declared, unread, and uncounted. const claimed = new Set(); const lineAt = (i) => 1 + (text.slice(0, i).match(/\n/g) || []).length; + // THE LENS (#10683). Every other scan in this file reads the source through `codeOnly`; + // this one read RAW TEXT, and that asymmetry WAS the defect. A `route:` followed by a + // single-quoted literal inside a COMMENT or a string PAYLOAD did not merely mis-count — + // it became a ROW, and no verdict could fire, because the partial-read guard is keyed on + // the gap between `rows` and `routesDeclared` and BOTH terms read raw text, so both moved + // together. A prose line quoting a `client:` too minted a fully client-bound phantom, + // which then joined the UNREACHABLE population (no registrar tail can match a route + // nobody mounts). Measured before the mask, on a file declaring ONE row: + // + // // A row we removed used to read route: 'GET /api/v1/gone' before #1234. + // export const L = [ + // { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }, + // ]; + // ⇒ rows 2 · routesDeclared 2 · clientsDeclared 1 · declined [] + // + // Byte offsets survive the mask untouched (`blank` writes spaces and keeps newlines), so + // every index below indexes BOTH strings: leads are found in `code`, values are read back + // out of `text` at the same offsets. Moving the recognizer MOVES THE MEASURED POPULATION, + // which the header of `--bridge-coverage` attaches a standard to — so it was done on the + // one tree where the move is provably free: no ledger quotes a `route:` in prose today, + // and `rows`, `routesDeclared`, `clientsDeclared` and the 176 UNREACHABLE rows are + // byte-identical across the change (all seven ledgers, delta 0). + // ⛔ ONE of #10500's two discriminators, not both. `typeDeclRegions` is still NOT applied + // here, so a literal-union `route: 'GET /a' | 'GET /b'` TYPE member is still read as a row + // — the same silent shape, through the other discriminator. Measured on this tree: 0 such + // members across all seven ledgers (every one declares `route: string;`). Applying it is a + // SECOND population move and wants its own before/after, which is why it is #10793 and not + // a rider here. + const code = codeOnly(text); + // The value as WRITTEN. `codeOnly` blanks string CONTENTS, so a capture group taken off + // `code` is a run of spaces of the right length and never the route. Blanking preserves + // LENGTH, so cutting the raw text by the masked match's offsets is exact for any content + // — including one carrying an escaped quote, which re-running the regex over the raw text + // would cut short at the `\'` instead of at the literal's real end. + const valueAt = (end, len) => text.slice(end - 1 - len, end - 1); const routeRe = /route\s*:\s*'([^']+)'/g; let m; - while ((m = routeRe.exec(text)) !== null) { - const rest = text.slice(m.index, routeRe.lastIndex + 1200); + while ((m = routeRe.exec(code)) !== null) { + const rest = code.slice(m.index, routeRe.lastIndex + 1200); const nextRoute = rest.slice(1).search(/route\s*:\s*'/); const window = nextRoute === -1 ? rest : rest.slice(0, nextRoute + 1); const client = window.match(/client\s*:\s*'([^']+)'/); - rows.push({ route: m[1], client: client ? client[1] : null }); + rows.push({ + route: valueAt(routeRe.lastIndex, m[1].length), + client: client ? valueAt(m.index + client.index + client[0].length, client[1].length) : null, + }); if (client) claimed.add(m.index + client.index); // A `client:` is BOUND inside THIS row's window — the same window the line above reads // — and never file-wide: a `client:` written in prose elsewhere would otherwise be // billed to a row that never declared it, and a false red costs the same trust a false // green does. That is a rule about ATTRIBUTION and it still holds; the denominator's // file-wide sweep at the bottom bills its findings to no row at all (#10636). - for (const d of declinedIn(window)) { + // …over the RAW bytes of that same window. `declinedIn`'s whole job is to NAME an + // unread spelling, and the masked window would name `client: ""` for every one of them. + // The BYTE RANGE is the code-derived window's, so this is the same span, read for its + // text. ⛔ That leaves `declinedIn` itself still reading prose — a `route: "GET /x"` + // written in a comment is still billed as a declined row. It is the SAME asymmetry one + // scan over, but it fails in the opposite direction (a declined entry is NAMED and + // carries a verdict, so it is a false RED, not a phantom row), which makes it a + // separate call with its own before/after rather than a rider here. Filed as #10794. + for (const d of declinedIn(text.slice(m.index, m.index + window.length))) { if (d.key !== 'client') continue; claimed.add(m.index + d.index); declined.push({ key: 'client', line: lineAt(m.index + d.index), text: d.text }); @@ -1516,10 +1585,41 @@ function parseLedgerSource(text) { // ⛔ The two sides must move together. A denominator widened past what its numerator can // ever count trades an undercount for a permanently red ratio — which is why the sweep // pushes to `declined` (a value NAMED as unread) instead of only bumping a number. - const routesDeclared = [...text.matchAll(/route\s*:\s*'/g)].length + declined.filter((d) => d.key === 'route').length; + // + // The first term reads `code`, not `text`, for the recognizer's reason (#10683): a + // denominator that counted prose-quoted leads while the numerator no longer read them + // would turn this migration into a permanently red ratio on the very files it fixed. + // Both terms move together, which is the invariant the block above insists on. + const routesDeclared = [...code.matchAll(/route\s*:\s*'/g)].length + declined.filter((d) => d.key === 'route').length; const clientsDeclared = rows.filter((r) => r.client).length + declined.filter((d) => d.key === 'client').length; declined.sort((a, b) => a.line - b.line || a.key.localeCompare(b.key)); - return { rows, declined, routesDeclared, clientsDeclared }; + // THE REPORTING HALF (#10683). The mask closes the phantom-row hole by making a + // prose-quoted lead produce NOTHING — and "produces nothing" is the silence every other + // report in this file exists to break. So the DIFFERENCE THE MASK MADE is itself named: + // a lead spelled exactly the way the recognizer reads, sitting where the mask says code + // is not. That is the card's own third direction, kept as the cheap half of the second. + // + // ⛔ A REPORT, NEVER A VERDICT, and it enters NEITHER ratio — it is not pushed to + // `declined`, so it moves no denominator and fires no `brokenScan`. A comment explaining + // a retired row by quoting its old path is legitimate prose, and reddening CI over it + // would be exactly the false red the #9747 family's ruling declines, the same reason the + // 45-of-221 reach ratio is reported rather than gated. It is here so the NEXT hole of + // this shape is loud instead of silent. Measured across all seven live ledgers: 0. + const codeLeads = new Set([...code.matchAll(/(?:route|client)\s*:\s*'/g)].map((d) => d.index)); + const outsideCode = [...text.matchAll(/(route|client)\s*:\s*'/g)] + .filter((d) => !codeLeads.has(d.index)) + .map((d) => { + // The LEAD and its VALUE are matched in two passes, not one. A single expression that + // swallowed the value would consume the rest of the line with it, and one prose line + // mentioning BOTH keys — the exact shape that mints a client-bound phantom — would + // report as one finding instead of two (measured: it did, until this was split). + // Trimmed back to the closing quote in JS rather than matched to it, for `declinedIn`'s + // reason: an unterminated literal must still NAME itself instead of dropping out. + const body = text.slice(d.index + d[0].length).split('\n')[0]; + const end = body.indexOf("'"); + return { key: d[1], line: lineAt(d.index), text: `${d[1]}: '${end === -1 ? body.slice(0, 60) : body.slice(0, end)}'` }; + }); + return { rows, declined, routesDeclared, clientsDeclared, outsideCode }; } /** @@ -2122,6 +2222,74 @@ function selfTest() { '];', ].join('\n')); check('parseLedgerSource', 'prose and string payloads are not unread rows', 'declined', 0, prose.declined.length); + check('parseLedgerSource', 'and a sentence that never quotes a value names nothing either', 'outsideCode', + 0, prose.outsideCode.length); + + // …AND THEY ARE NOT READ ROWS EITHER (#10683). The check above is one-sided: it pins what + // the DECLINED report says about prose and says nothing at all about `rows`. The + // recognizer read RAW text, so a comment one quote further along than the live near-miss + // in `runtime/src/route-ledger.ts` did not mis-count — it became a ROW, and the + // partial-read verdict could not see it, because that verdict is keyed on the gap between + // `rows` and `routesDeclared` and both terms read raw, so both moved together. + // + // ⚠️ PINNED IN BOTH DIRECTIONS, deliberately. A mask that reached the comment case by + // breaking the CODE case — reading the blanked span as the row's value, say — would pass + // a test that only asserted the phantom is gone, and would trade a phantom row for 259 + // corrupted ones. So every case below asserts what the mask DROPPED and what it KEPT, + // value included. + const phantom = parseLedgerSource([ + "// A row we removed used to read route: 'GET /api/v1/gone' before #1234.", + 'export const L = [', + " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a `route:` quoted in a COMMENT is not a row', 'row count', 1, phantom.rows.length); + check('parseLedgerSource', 'and the row in CODE still is — carrying its VALUE, not the mask\'s blanks', 'row', + 'GET /api/v1/meta → meta.getTypes', `${phantom.rows[0]?.route} → ${phantom.rows[0]?.client}`); + check('parseLedgerSource', 'the denominator drops it too, so no phantom gap opens', 'declared', + '1 route / 1 client', `${phantom.routesDeclared} route / ${phantom.clientsDeclared} client`); + check('parseLedgerSource', 'and nothing is billed as declined for it', 'declined', 0, phantom.declined.length); + check('parseLedgerSource', 'the dropped lead is NAMED, with its line — dropped is not silent', 'outsideCode', + "1 route: 'GET /api/v1/gone'", phantom.outsideCode.map((d) => `${d.line} ${d.text}`).join('; ')); + + // The shape the card called the expensive one: prose that quotes a `client:` as well used + // to mint a FULLY CLIENT-BOUND phantom, which then joined the UNREACHABLE population — no + // registrar tail can ever match a route nobody mounts — and inflated it with no verdict. + const phantomClient = parseLedgerSource([ + "// The retired row read route: 'GET /api/v1/gone' bound to client: 'meta.getGone'.", + 'export const L = [', + " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a prose lead quoting a `client:` too mints no client-bound phantom', 'client rows', + 1, phantomClient.rows.filter((r) => r.client).length); + check('parseLedgerSource', 'and the surviving row keeps its OWN binding, never the comment\'s', 'client', + 'meta.getTypes', phantomClient.rows[0]?.client); + check('parseLedgerSource', 'both prose leads are named', 'outsideCode', 2, phantomClient.outsideCode.length); + + // A string PAYLOAD is the other half of the mask, and the half an allowlist of comment + // shapes would miss: the bytes are code position by any line-based test. + const payload = parseLedgerSource([ + 'export const NOTE = "the retired row read route: \'GET /api/v1/gone\'";', + 'export const L = [', + " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a `route:` inside a string PAYLOAD is not a row', 'row count', 1, payload.rows.length); + check('parseLedgerSource', 'and it is named, not silently dropped', 'outsideCode', 1, payload.outsideCode.length); + + // ⛔ REPORTED, NEVER A VERDICT. A comment explaining a retired row by quoting its old path + // is legitimate prose; reddening CI over it is the false red the #9747 family declines. + const phantomCov = bridgeCoverageFrom([{ file: 'h-route-ledger.ts', ...phantom }], ['/api/v1/meta']); + check('bridgeCoverageFrom', 'a prose-quoted lead carries NO broken-scan verdict', 'brokenScan', + 0, phantomCov.brokenScan.length); + check('bridgeCoverageFrom', 'and the ratios it must not move are whole', 'read', + '1 of 1 route / 1 of 1 client', + `${phantomCov.rowsParsed} of ${phantomCov.routesDeclared} route / ${phantomCov.clientRows} of ${phantomCov.clientsDeclared} client`); + check('bridgeCoverageFrom', 'yet it is counted where a reader will see it', 'leadsOutsideCode', + 1, phantomCov.leadsOutsideCode); + check('bridgeCoverageFrom', 'and an accurate ledger reports none', 'leadsOutsideCode', 0, + bridgeCoverageFrom([{ file: 'h-route-ledger.ts', ...prose }], ['/api/v1/meta']).leadsOutsideCode); // End to end over those three fixtures: the #9192 recall miss must come back. // `auditMetaItem` (changed) → `/:type/:name/audit` (registrar) → `meta.getAudit` @@ -2653,6 +2821,7 @@ function selfTest() { declined: [], routesDeclared: rows.length, clientsDeclared: rows.filter((r) => r.client).length, + outsideCode: [], }); const covLedgers = [ covLedger('a-route-ledger.ts', [