From e8a9b04e26df0019cd3703fb2835e53a1965deb9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 03:04:20 +0000 Subject: [PATCH] fix(docs-audit): give the `route:`/`client:` lead one character class `declarationsIn` spelled the run between the colon and the value `[ \t]*` while the other seven scans in the file spelled it `\s*`. One character of difference -- a newline -- and a declaration whose value sits on the NEXT line was seen by BOTH scans, in two different buckets, so one declaration reached the denominator twice. The partition kept balancing while it happened (`rows + declined === routesDeclared`), which is why no count comparison could see it: what moved was the population, not the arithmetic. The reader was shown two entries for one line, the second reading `route: ` with an empty value. `\s*` wins because the ROW RECOGNIZER already spells it: a wrapped single-quoted value is read as a row and was then billed unread by the same file, raising a PARTIAL-read verdict with exit 1 on a wholly accurate ledger. Making `[ \t]*` win instead would have had to move the recognizer -- a change to the measured population -- and would have kept the empty-valued entry. `declLead` is now the one place the class is spelled, and all eight lead scans are built from it; the other seven regexes are byte-identical in source and flags. Priced free on today's tree: 268 of 268 route / 222 of 222 client / 177 UNREACHABLE, delta 0, `unreachableRows` byte-identical. The KEY part stays each call site's own -- `declarationsIn` anchors with `\b` and the other seven do not, so `subroute:` mints a silent phantom row. That moves the measured population, so it is filed separately. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx --- scripts/docs-audit/affected-docs.mjs | 205 +++++++++++++++++++++++++-- 1 file changed, 197 insertions(+), 8 deletions(-) diff --git a/scripts/docs-audit/affected-docs.mjs b/scripts/docs-audit/affected-docs.mjs index cac567a9bf..f934b2679f 100644 --- a/scripts/docs-audit/affected-docs.mjs +++ b/scripts/docs-audit/affected-docs.mjs @@ -1301,6 +1301,69 @@ function typeDeclRegions(code) { return regions; } +/** + * THE ONE SPELLING of a `route:` / `client:` LEAD — the key, the colon, and whatever may + * sit between the colon and the VALUE. Every scan below that asks "is a declaration written + * here?" builds its regex from this, so the eight of them cannot answer the same question + * differently while all eight look right. + * + * WHAT THE EIGHTH COPY COST (#11494). `declarationsIn` spelled the run after the colon + * `[ \t]*` and the other seven spelled it `\s*`. One character class, one character of + * difference — a NEWLINE — and a declaration whose value sits on the NEXT line was seen by + * BOTH scans, in two different buckets, and billed to the denominator twice: `declinedIn` + * read it as a declined quote and named it correctly, while `declarationsIn` saw `\n` as + * the character after the colon, classified `quote === null`, and `unreadableIn` billed the + * same declaration a second time as a non-literal. Measured on `cd932772`, one file + * declaring TWO `route:` values: + * + * export const L = [ + * { route: + * "GET /api/v1/gone", family: 'metadata', disposition: 'sdk' }, + * { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' }, + * ]; + * ⇒ rows 1 · routesDeclared 3 · declined 2 · brokenScan 1 + * + * …and the second declined entry read `route: ` — an EMPTY value, naming nothing a reader + * can act on, which is the silence every other report in this file exists to break. Note + * `rows + declined === routesDeclared` still BALANCED (1 + 2 === 3): the partition held + * arithmetically over a population that counts one declaration twice, which is exactly the + * "correct from inside" shape #10500 / #10793 / #10901 each closed one instance of. + * + * `\s*` IS THE CLASS THAT WINS, and it is not a coin flip. The ROW RECOGNIZER spells + * `\s*`, so a wrapped SINGLE-quoted value is ALREADY read as a row — and was then billed + * unread by the same file, firing a PARTIAL-read verdict with exit 1 on a wholly accurate + * ledger (measured: `rows 1 · routesDeclared 2 · declined 1 · brokenScan 1` on a file + * declaring ONE row, the declined entry again empty-valued; the `client:` column did the + * same to `clientsDeclared`). That is the FALSE RED direction, which this file prices as + * costing the same trust a false green does. Making `[ \t]*` win instead would have had to + * move the recognizer too — a change to the MEASURED POPULATION, which the header of + * `--bridge-coverage` attaches a before/after standard to — and would have kept the + * empty-valued entry as the surviving one. + * + * Priced on the tree where the move is provably free: none of the seven ledgers ends a line + * at a `route:` / `client:` colon (`grep -nE '\b(route|client)[ \t]*:[ \t]*$'`, 0 hits), + * and `268 of 268` / `222 of 222` / 177 UNREACHABLE are byte-identical across the change. + * + * A FUNCTION DECLARATION rather than a `const`, for `declinedIn`'s reason: `--self-test` + * short-circuits near the top of this file, before any `const` down here has initialized, + * and a TDZ error there takes the whole self-test down instead of failing one check. + * + * ⛔ THE KEY PART STAYS EACH CALL SITE'S OWN, and that is a deliberate boundary, not an + * oversight. `declarationsIn` anchors with `\b` and the other seven do not, so + * `subroute: 'GET /x'` is a declaration to seven of them and not to the eighth — the SAME + * family of divergence one spelling further out, and it mints a silent phantom ROW. Unifying + * it MOVES the measured population (`rows`, `routesDeclared`, the 177 UNREACHABLE), which + * is the separate decision with a before/after standard attached, so it is filed as #11542 + * rather than folded in here. + * + * @param {string} keys the key part exactly as the call site writes it — `\b(route|client)`, + * `route`, `(?:route|client)`. The capture groups and the word boundary are the call + * site's question; what may follow the colon is this function's. + */ +function declLead(keys) { + return String.raw`${keys}\s*:\s*`; +} + /** * Every `route:` / `client:` declaration a ledger makes IN CODE POSITION, with the quote its * value opens in (`'`, `"`, backtick) or `null` for a value that is not a string literal at @@ -1329,7 +1392,7 @@ function declarationsIn(text) { const code = codeOnly(text); const skip = typeDeclRegions(code); const out = []; - const re = /\b(route|client)\s*:[ \t]*/g; + const re = new RegExp(declLead(String.raw`\b(route|client)`), 'g'); let m; while ((m = re.exec(code)) !== null) { if (skip.some(([a, b]) => m.index >= a && m.index <= b)) continue; @@ -1458,7 +1521,7 @@ function unclaimedClientsIn(text, claimed) { */ function declinedIn(s, offset, inTypeDecl) { const out = []; - for (const d of s.matchAll(/(route|client)\s*:\s*(["`])([^\n]{0,120})/g)) { + for (const d of s.matchAll(new RegExp(declLead('(route|client)') + /(["`])([^\n]{0,120})/.source, 'g'))) { const index = offset + d.index; // …and never a TYPE member (#10901), on the SAME region list the recognizer and the // denominator read — not a second idea of what a type member is. @@ -1596,16 +1659,18 @@ function parseLedgerSource(text) { // — 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; + const routeRe = new RegExp(declLead('route') + /'([^']+)'/.source, 'g'); + const nextRouteRe = new RegExp(declLead('route') + "'"); + const windowClientRe = new RegExp(declLead('client') + /'([^']+)'/.source); let m; while ((m = routeRe.exec(code)) !== null) { // …and never a TYPE member (#10793). A literal-union member opens with the very quote // this regex reads, which is exactly why `typeDeclRegions` and not a spelling test. if (inTypeDecl(m.index)) continue; const rest = code.slice(m.index, routeRe.lastIndex + 1200); - const nextRoute = rest.slice(1).search(/route\s*:\s*'/); + const nextRoute = rest.slice(1).search(nextRouteRe); const window = nextRoute === -1 ? rest : rest.slice(0, nextRoute + 1); - const client = window.match(/client\s*:\s*'([^']+)'/); + const client = window.match(windowClientRe); rows.push({ route: valueAt(routeRe.lastIndex, m[1].length), client: client ? valueAt(m.index + client.index + client[0].length, client[1].length) : null, @@ -1679,7 +1744,7 @@ function parseLedgerSource(text) { // move together or the partition `rows + declined === routesDeclared` breaks: a member // counted here but skipped there would read as a row this parse declined to read, and // fire a PARTIAL-read verdict on an accurate ledger. - const routesDeclared = [...code.matchAll(/route\s*:\s*'/g)].filter((d) => !inTypeDecl(d.index)).length + const routesDeclared = [...code.matchAll(new RegExp(declLead('route') + "'", 'g'))].filter((d) => !inTypeDecl(d.index)).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)); @@ -1695,8 +1760,8 @@ function parseLedgerSource(text) { // 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)] + const codeLeads = new Set([...code.matchAll(new RegExp(declLead('(?:route|client)') + "'", 'g'))].map((d) => d.index)); + const outsideCode = [...text.matchAll(new RegExp(declLead('(route|client)') + "'", '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 @@ -2589,6 +2654,119 @@ function selfTest() { check('parseLedgerSource', 'and its denominator still counts it — the type-member fix moved this none', 'declared', '1 row / 2 route', `${proseDeclined.rows.length} row / ${proseDeclined.routesDeclared} route`); + + // ⛔ THE TWO SCANS NOW AGREE ON THE CHARACTER CLASS AFTER THE COLON (#11494) — the thing + // `declLead` exists to make structural rather than coincidental. A declaration whose VALUE + // SITS ON THE NEXT LINE used to be seen by BOTH: `declinedIn` named it correctly, while + // `declarationsIn` read the `\n` as the character after the colon, classified + // `quote === null`, and `unreadableIn` billed the SAME declaration a second time. One + // declaration, two entries, and the denominator counted it twice. + // + // ⛔ THE PARTITION KEPT BALANCING WHILE IT HAPPENED — `1 + 2 === 3` on a file declaring + // TWO `route:` values — which is exactly why no count comparison could see it. What moved + // was the POPULATION, not the arithmetic, and the reader was shown two entries for one line. + const wrappedDeclined = parseLedgerSource([ + 'export const L = [', + ' { route:', + ' "GET /api/v1/gone", family: \'metadata\', disposition: \'sdk\' },', + " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a wrapped DECLINED `route:` reaches the denominator ONCE, not once per scan', + 'declared', '1 row / 2 route / 1 declined', + `${wrappedDeclined.rows.length} row / ${wrappedDeclined.routesDeclared} route / ${wrappedDeclined.declined.length} declined`); + // The second entry it used to emit read `route: ` — an EMPTY value, naming nothing a reader + // can act on, which is the silence every other report in this file exists to break. + check('parseLedgerSource', 'and the one entry NAMES the value, with its line', 'line 2 double-quoted route', + 'line 2: route: "GET /api/v1/gone"', + wrappedDeclined.declined.map((d) => `line ${d.line}: ${d.text}`).join(', ')); + check('parseLedgerSource', 'read + declined still accounts for every declared `route:`', 'partition', true, + wrappedDeclined.rows.length + wrappedDeclined.declined.filter((d) => d.key === 'route').length === wrappedDeclined.routesDeclared); + + // …AND THE LOUDER HALF, which is why `\s*` won and not `[ \t]*`: a wrapped SINGLE-quoted + // value. The row recognizer's own `\s*` already reads it AS A ROW — and the file then + // billed that same declaration unread, firing a PARTIAL-read verdict with exit 1 on a + // wholly accurate ledger. That is the FALSE RED direction, which costs the same trust a + // false green does. Measured before the fix: rows 1 · routesDeclared 2 · declined 1 · + // brokenScan 1, the declined entry again empty-valued. + const wrappedRead = parseLedgerSource([ + 'export const L = [', + ' { route:', + " 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a wrapped single-quoted `route:` is READ, and never ALSO billed unread', + 'declared', '1 row / 1 route / 0 declined', + `${wrappedRead.rows.length} row / ${wrappedRead.routesDeclared} route / ${wrappedRead.declined.length} declined`); + check('parseLedgerSource', 'and the row carries its value and its binding', 'row', + 'GET /api/v1/meta → meta.getTypes', `${wrappedRead.rows[0]?.route} → ${wrappedRead.rows[0]?.client}`); + check('bridgeCoverageFrom', 'so a ledger that merely WRAPS a value carries NO verdict', 'brokenScan', + 0, bridgeCoverageFrom([{ file: 'l-route-ledger.ts', ...wrappedRead }], ['/api/v1/meta']).brokenScan.length); + + // The `client:` column, the same shape: it reached `clientsDeclared` twice — once as the + // row's binding, once as a non-literal — and fired the same verdict on an accurate ledger. + const wrappedClient = parseLedgerSource([ + 'export const L = [', + " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client:", + " 'meta.getTypes' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a wrapped `client:` is counted once too', 'declared', + '1 row / 1 client / 0 declined', + `${wrappedClient.rows.length} row / ${wrappedClient.clientsDeclared} client / ${wrappedClient.declined.length} declined`); + check('bridgeCoverageFrom', 'and carries no verdict either', 'brokenScan', + 0, bridgeCoverageFrom([{ file: 'l-route-ledger.ts', ...wrappedClient }], ['/api/v1/meta']).brokenScan.length); + + // A wrapped NON-LITERAL value was already counted once — but it NAMED nothing, because the + // snippet was cut at the newline. Widening the class moved the cut to the value itself. + const wrappedUnreadable = parseLedgerSource([ + 'export const L = [', + ' { route:', + " ROUTES.health, family: 'metadata', disposition: 'sdk' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a wrapped non-literal `route:` is counted once', 'declared', + '0 row / 1 route / 1 declined', + `${wrappedUnreadable.rows.length} row / ${wrappedUnreadable.routesDeclared} route / ${wrappedUnreadable.declined.length} declined`); + check('parseLedgerSource', 'and NAMES the value, where it used to report an empty one', + 'line 2 ROUTES.health', 'line 2: route: ROUTES.health', + wrappedUnreadable.declined.map((d) => `line ${d.line}: ${d.text}`).join(', ')); + + // ⛔ AND THE TYPE-MEMBER DISCRIMINATOR STILL OUTRANKS THE WRAP (#10901). A wrapped + // literal-union member is skipped by BOTH scans on the SAME region list, so widening the + // class handed `declinedIn` no member to bill — the regression #10901 closed does not + // reopen one spelling further out. + const wrappedTypeMember = parseLedgerSource([ + 'export interface Entry { route:', + ' "GET /api/v1/gone" | "GET /api/v1/meta"; client: string }', + 'export const L = [', + " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a WRAPPED literal-union `route:` TYPE member still contributes no count', + 'declared', '1 row / 1 route / 0 declined', + `${wrappedTypeMember.rows.length} row / ${wrappedTypeMember.routesDeclared} route / ${wrappedTypeMember.declined.length} declined`); + + // ⛔ THE BOUNDARY THIS CARD DELIBERATELY DID NOT CROSS. `declLead` unifies what may sit + // BETWEEN the colon and the value; the KEY part is still each call site's own argument, and + // `declarationsIn` anchors it with `\b` while the other seven do not. So `subroute:` is a + // declaration to seven of the eight scans and mints a silent phantom ROW — the same family + // of divergence one spelling further out. That is #11542, kept out because unifying the key + // MOVES the measured population, which the header of `--bridge-coverage` attaches a + // before/after standard to. Pinned so the card that closes #11542 MOVES this pin rather + // than finding none. + const unanchoredKey = parseLedgerSource([ + 'export const L = [', + " { subroute: 'GET /api/v1/gone', family: 'metadata', disposition: 'sdk' },", + " { route: 'GET /api/v1/meta', family: 'metadata', disposition: 'sdk', client: 'meta.getTypes' },", + '];', + ].join('\n')); + check('parseLedgerSource', 'a `subroute:` still mints a phantom row — #11542, deliberately unmoved', + 'row count', 2, unanchoredKey.rows.length); + check('parseLedgerSource', 'and it is still SILENT — both terms of the denominator move together', + 'declared', '2 route / 0 declined', + `${unanchoredKey.routesDeclared} route / ${unanchoredKey.declined.length} declined`); + // ⛔ 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']); @@ -3217,6 +3395,17 @@ function selfTest() { check('emit', 'and nothing else restates the suffix test inline', 'affected-docs.mjs', 1, (ownSource.match(/\.some\(\(t\) => route\.replace\(/g) || []).length); + // WRITTEN ONCE, pinned at the source (#11494). The defect was not that `declarationsIn` + // chose the wrong class — it was that eight scans each spelled the class for themselves, + // so seven agreeing and one differing looked exactly like eight agreeing. A ninth inline + // copy is the same hole re-opening, and only a source pin can see it: every behavioural + // fixture above would keep passing while the new scan drifted on its own. + check('declLead', 'the run between a `route:`/`client:` colon and its value is spelled ONCE', 'affected-docs.mjs', + 1, (ownSource.match(/String\.raw`\$\{keys\}\\s\*:\\s\*`/g) || []).length); + check('declLead', 'and all eight lead scans are built from it, none inline', 'affected-docs.mjs', + 8, (ownSource.match(/new RegExp\(declLead\(/g) || []).length); + + if (failed) { console.error(`\n✗ affected-docs self-test failed (${failed} case(s)).`); process.exit(1);