diff --git a/packages/qa/dogfood/test/authz-conformance.matrix.ts b/packages/qa/dogfood/test/authz-conformance.matrix.ts index e779fa7b29..ec4cfeb4ec 100644 --- a/packages/qa/dogfood/test/authz-conformance.matrix.ts +++ b/packages/qa/dogfood/test/authz-conformance.matrix.ts @@ -24,12 +24,40 @@ // hand-curated regex table reaching 1 of 17 REST registrars and 4 of 17 // dispatcher domain files. // -// The population comes from `packages/rest/src/rest-route-ledger.ts` (94 rows +// The population comes from `packages/rest/src/rest-route-ledger.ts` (91 rows // / 19 families) and `packages/runtime/src/route-ledger.ts` (82 rows / 21 // domains) because those two are enumerated from a RUNNING server and guarded // in both directions by their own conformance tests — so a new family or // domain cannot be silently absent from them, and therefore cannot be silently -// absent from here. Widening a regex instead was refused: it rots on the next +// absent from here. +// +// [#17111] Those four figures are DERIVED, not hand-typed. The companion test +// parses THIS sentence and asserts each number against a scoped read of the +// ledger the sentence names, so a row, family or domain arriving or leaving +// reds CI here instead of rotting into a false present tense. That pin is the +// only reason the present tense above is safe to write at all; an undated +// hand-typed count in this position is what #17111 was filed about, and what +// #16307/#16954 were filed about before it. +// +// ⚠️ The pin folds the `//` continuations before it reads, because every +// figure in this docblock WRAPS mid-phrase — the row/family phrase above is +// split across two comment lines. A one-line literal search for it therefore +// matches NOTHING in this file, and a zero from one is not a reading, it is +// the instrument missing. ⛔ Do not re-flow these figures into a shape the +// parser cannot anchor: it keys on the backticked ledger path immediately +// followed by the parenthesised pair, and requires EXACTLY ONE of each, so a +// second unpinned copy of either figure is red rather than a fresh carrier. +// +// ⚠️ The REST row count read 94 here until #17111 corrected it, and the +// cause was NOT the route-guarding move (`cc837dbfec`) whose own stale figures +// sat seven lines below it: guarding a route never changes its ledger +// disposition, and reading this drift as the gate move's sends the next reader +// to the wrong change. It moved when #14503 took the three REST package +// read/delete rows out of the ledger — 94 rows → 91, `sdk` 84 → 81, with +// families unmoved at 19 because all three departing rows carried +// `family: packages`, which survives on the publish row. Two stale figures in +// one docblock with two entirely different causes is the failure mode the pin +// above ends. Widening a regex instead was refused: it rots on the next // added file, which is the mechanism this replaces. Deriving "gated" from // source syntax was refused too, on a measurement — 22 of 29 apparently // ungated `register(` sites in `rest-server.ts` are false, a 76% false-ungated diff --git a/packages/qa/dogfood/test/authz-conformance.test.ts b/packages/qa/dogfood/test/authz-conformance.test.ts index 41e9467b24..527d18d8d7 100644 --- a/packages/qa/dogfood/test/authz-conformance.test.ts +++ b/packages/qa/dogfood/test/authz-conformance.test.ts @@ -461,8 +461,21 @@ const PROBES: readonly Probe[] = [ */ function probeSource(probe: Probe): string { const src = readFileSync(join(REPO_ROOT, probe.file), 'utf8'); - if (!probe.within) return src; - const from = src.indexOf(`export const ${probe.within}`); + return probe.within ? scopedSource(src, probe.within) : src; +} + +/** + * The body of ONE exported array literal in `src` — `export const ` + * up to the closing `\n];` — or the EMPTY string when that export is not + * there, per the rule above. + * + * Extracted so the probe reader and the #17111 docblock-figure pin read the + * ledger tables through ONE scoping rule: two copies would let a rename widen + * one of them while the other stayed correct, and the wider one would still + * mint plausible numbers. + */ +function scopedSource(src: string, within: string): string { + const from = src.indexOf(`export const ${within}`); if (from < 0) return ''; const to = src.indexOf('\n];', from); return to < 0 ? '' : src.slice(from, to); @@ -1458,3 +1471,260 @@ describe('#7976 — row ↔ proof attribution is mutual', () => { ).toBe(true); }); }); + +// ── #17111 — the docblock's ledger figures are DERIVED, not hand-typed ────── +// +// The matrix docblock states the size of BOTH route ledgers in the present +// tense. It read "94 rows / 19 families" for the nine days after #14503 took +// three REST package read/delete rows out of the ledger, and it was UNDATED — +// so nothing in the sentence told a reader it described a past state, and +// nothing in CI could tell either. It was the sixth hand-typed count to go +// stale in one triage round, which is why the deliverable here is the +// derivation and not the corrected constant. +// +// ⛔ This pin reads the PROSE, deliberately — not an exported constant. The +// defect is a sentence that went false; lifting the number out of the sentence +// into a symbol would leave the sentence free to rot again while the symbol +// stayed green, which is the same trade that produced the card. +// +// ⚠️ It folds the `//` continuations before matching, and that fold is +// load-bearing rather than tidying: every figure in that docblock WRAPS +// mid-phrase, so a line-at-a-time literal search for the row/family phrase +// reads ZERO against the very file that carries it. The control leg below +// pins that failure, because a zero from an instrument that cannot see the +// shape is not a reading of absence. + +const MATRIX_FILE = 'authz-conformance.matrix.ts'; + +/** One figure pair the matrix docblock states, and how its ledger spells a row. */ +interface DocblockLedgerClaim { + /** Repo-relative ledger path, exactly as the docblock backticks it. */ + file: string; + /** The exported table inside it — the read is SCOPED, never the whole file. */ + within: string; + /** The noun the docblock groups by. */ + groupNoun: 'families' | 'domains'; + /** The grouping field — one occurrence per row, counted DISTINCT. */ + groupRe: RegExp; +} + +const DOCBLOCK_LEDGER_CLAIMS: readonly DocblockLedgerClaim[] = [ + { + file: 'packages/rest/src/rest-route-ledger.ts', + within: 'REST_ROUTE_LEDGER', + groupNoun: 'families', + groupRe: /family: '([^']+)'/g, + }, + { + file: 'packages/runtime/src/route-ledger.ts', + within: 'ROUTE_LEDGER', + groupNoun: 'domains', + groupRe: /domain: '([^']+)'/g, + }, +]; + +/** Both ledgers open every row with `route:`, so counting it counts rows. */ +const LEDGER_ROW_RE = /route: '([^']+)'/g; + +function allMatches(body: string, re: RegExp): string[] { + re.lastIndex = 0; // Fresh lastIndex per read (module-level, `g`-flagged). + const out: string[] = []; + let m: RegExpExecArray | null; + while ((m = re.exec(body)) !== null) out.push(m[1]!); + return out; +} + +/** + * What one ledger table holds TODAY. + * + * `groupOccurrences` is returned alongside `rows` on purpose: both fields are + * one-per-row, so the two counts disagreeing means the scope has swallowed + * something that is not a row (a doc-comment spelling `route:`, a type + * declaration) and NEITHER number is trustworthy. The check refuses that + * instead of reporting the plausible one. + */ +function measureLedgerTable(claim: DocblockLedgerClaim): { + rows: number; + groups: number; + groupOccurrences: number; +} { + const body = scopedSource(readFileSync(join(REPO_ROOT, claim.file), 'utf8'), claim.within); + const groupValues = allMatches(body, claim.groupRe); + return { + rows: allMatches(body, LEDGER_ROW_RE).length, + groups: new Set(groupValues).size, + groupOccurrences: groupValues.length, + }; +} + +/** `//` continuations folded away, so a phrase split across two comment lines reads as one. */ +function flattenLineComments(src: string): string { + return src.replace(/\n[ \t]*\/\/ ?/g, ' ').replace(/[ \t]+/g, ' '); +} + +/** + * Every figure the (already-folded) docblock states for one ledger. + * + * Anchored on the backticked ledger PATH immediately followed by the + * parenthesised pair — not on the bare numbers, which also appear in the dated + * drift note one paragraph down and must not be pinned as present-tense + * claims. Every match is returned so the caller can refuse a SECOND copy: an + * unpinned duplicate is exactly how a third carrier of a stale figure appears. + */ +function claimedFigures(flat: string, claim: DocblockLedgerClaim): { rows: number; groups: number }[] { + const path = claim.file.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const re = new RegExp('`' + path + '` \\((\\d+) rows / (\\d+) ' + claim.groupNoun + '\\)', 'g'); + return allMatchPairs(flat, re); +} + +function allMatchPairs(flat: string, re: RegExp): { rows: number; groups: number }[] { + const out: { rows: number; groups: number }[] = []; + let m: RegExpExecArray | null; + while ((m = re.exec(flat)) !== null) out.push({ rows: Number(m[1]), groups: Number(m[2]) }); + return out; +} + +/** Disagreements between the docblock's stated figures and the ledgers, on folded text. */ +function checkFoldedDocblockFigures(flat: string): string[] { + const problems: string[] = []; + for (const claim of DOCBLOCK_LEDGER_CLAIMS) { + const claimed = claimedFigures(flat, claim); + if (claimed.length === 0) { + problems.push( + `${claim.file}: the docblock states no (N rows / M ${claim.groupNoun}) figure for it — the pin has lost its subject`, + ); + continue; + } + if (claimed.length > 1) { + problems.push( + `${claim.file}: ${claimed.length} present-tense figures state its size; exactly ONE pinned copy is allowed`, + ); + } + const actual = measureLedgerTable(claim); + if (actual.rows !== actual.groupOccurrences) { + problems.push( + `${claim.file}: the scoped table yields ${actual.rows} \`route:\` but ${actual.groupOccurrences} \`${claim.groupNoun.slice(0, -3)}\`-field occurrences — the scope is reading non-rows, so neither count is a reading`, + ); + continue; + } + for (const c of claimed) { + if (c.rows !== actual.rows) { + problems.push(`${claim.file}: docblock says ${c.rows} rows, the table holds ${actual.rows}`); + } + if (c.groups !== actual.groups) { + problems.push( + `${claim.file}: docblock says ${c.groups} ${claim.groupNoun}, the table holds ${actual.groups}`, + ); + } + } + } + return problems; +} + +/** Disagreements, from the raw file text. */ +function checkDocblockFigures(text: string): string[] { + return checkFoldedDocblockFigures(flattenLineComments(text)); +} + +describe("#17111 — the matrix docblock's ledger figures are pinned to the ledgers", () => { + const matrixText = (): string => readFileSync(join(HERE, MATRIX_FILE), 'utf8'); + const REST = DOCBLOCK_LEDGER_CLAIMS[0]!; + + it('CONTROL — both ledger tables are readable and non-trivial (the counts below are readings)', () => { + // Without this, every assertion here would be satisfied just as well by + // two claims pointed at files that no longer carry the table. + for (const claim of DOCBLOCK_LEDGER_CLAIMS) { + const { rows, groups, groupOccurrences } = measureLedgerTable(claim); + expect(rows, `${claim.file} rows`).toBeGreaterThan(10); + expect(groups, `${claim.file} ${claim.groupNoun}`).toBeGreaterThan(1); + // One grouping field per row, so these must agree exactly. + expect(groupOccurrences, `${claim.file} group-field occurrences`).toBe(rows); + expect(groups).toBeLessThanOrEqual(rows); + } + // …and the scope is what makes those readings OF the table: a renamed + // export reads EMPTY rather than falling back to the whole file. + const src = readFileSync(join(REPO_ROOT, REST.file), 'utf8'); + expect(scopedSource(src, 'NO_SUCH_EXPORT')).toBe(''); + expect(scopedSource(src, REST.within).length).toBeGreaterThan(0); + }); + + it('CONTROL — the figures WRAP, so folding is the instrument, not decoration', () => { + // The card's own instrument failure, pinned: read line-at-a-time, the + // claim is not in the file at all. Any future reader reaching for + // `git grep -F` on these phrases gets a zero that means nothing. + const raw = matrixText(); + for (const claim of DOCBLOCK_LEDGER_CLAIMS) { + expect(claimedFigures(raw, claim), `${claim.file} unfolded`).toEqual([]); + expect(claimedFigures(flattenLineComments(raw), claim).length, `${claim.file} folded`).toBe(1); + } + }); + + it('every figure the docblock states equals what its ledger holds TODAY', () => { + expect(checkDocblockFigures(matrixText())).toEqual([]); + }); + + it('a stale ROW count is RED — the #14503 drift, replayed', () => { + // Doctored from the MEASURED value, so this case hand-types no count of + // its own — the thing the card was filed about. + const { rows, groups } = measureLedgerTable(REST); + const flat = flattenLineComments(matrixText()); + const doctored = flat.replace( + `(${rows} rows / ${groups} families)`, + `(${rows + 3} rows / ${groups} families)`, + ); + expect(doctored, 'the doctoring must land, or this case proves nothing').not.toBe(flat); + expect( + checkFoldedDocblockFigures(doctored).some((p) => + p.includes(`docblock says ${rows + 3} rows, the table holds ${rows}`), + ), + ).toBe(true); + }); + + it('a stale GROUP count is RED too — both halves of the pair are pinned', () => { + const { rows, groups } = measureLedgerTable(REST); + const flat = flattenLineComments(matrixText()); + const doctored = flat.replace( + `(${rows} rows / ${groups} families)`, + `(${rows} rows / ${groups + 1} families)`, + ); + expect(doctored).not.toBe(flat); + expect( + checkFoldedDocblockFigures(doctored).some((p) => + p.includes(`docblock says ${groups + 1} families, the table holds ${groups}`), + ), + ).toBe(true); + }); + + it('DELETING the figure is RED — the pin cannot be silenced by dropping its subject', () => { + // Otherwise the cheapest way to green this gate would be to remove the + // sentence, which is the claim, not the defect. + const { rows, groups } = measureLedgerTable(REST); + const flat = flattenLineComments(matrixText()); + const doctored = flat.replace(`\`${REST.file}\` (${rows} rows / ${groups} families)`, `\`${REST.file}\``); + expect(doctored).not.toBe(flat); + expect( + checkFoldedDocblockFigures(doctored).some((p) => p.includes('the pin has lost its subject')), + ).toBe(true); + }); + + it('a SECOND present-tense copy of a figure is RED — a new carrier cannot arrive unpinned', () => { + const { rows, groups } = measureLedgerTable(REST); + const one = `\`${REST.file}\` (${rows} rows / ${groups} families)`; + const flat = flattenLineComments(matrixText()); + const doctored = flat.replace(one, `${one} and again ${one}`); + expect(doctored).not.toBe(flat); + expect( + checkFoldedDocblockFigures(doctored).some((p) => /present-tense figures state its size/.test(p)), + ).toBe(true); + }); + + it('the anchor is the PATH, so the dated drift note is NOT pinned as a present-tense claim', () => { + // The docblock records "94 rows → 91" as the #14503 transition. That is a + // dated attribution, not a claim about today, and a pin keyed on bare + // numbers would have to treat it as one. + const flat = flattenLineComments(matrixText()); + expect(flat).toContain('#14503'); + expect(claimedFigures(flat, REST).length).toBe(1); + expect(checkFoldedDocblockFigures(flat)).toEqual([]); + }); +});