diff --git a/packages/qa/dogfood/test/authz-conformance.matrix.ts b/packages/qa/dogfood/test/authz-conformance.matrix.ts index f362b782aa9..db7836e1318 100644 --- a/packages/qa/dogfood/test/authz-conformance.matrix.ts +++ b/packages/qa/dogfood/test/authz-conformance.matrix.ts @@ -24,17 +24,20 @@ // 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` (81 rows -// / 18 families) and `packages/runtime/src/route-ledger.ts` (105 rows / 23 +// The population comes from `packages/rest/src/rest-route-ledger.ts` (18 +// families) and `packages/runtime/src/route-ledger.ts` (23 // 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. // -// [#17111] Those four figures are DERIVED, not hand-typed. The companion test +// [#17111] Those two 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 +// ledger the sentence names, so a family or domain arriving or leaving reds CI +// here instead of rotting into a false present tense. ⛔ Row counts are not +// stated, on purpose (ruling 6433001130): a route added inside an existing +// family or domain changes nothing this claim depends on, and a stated row +// count made every such route edit this file or red `main`. 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. diff --git a/packages/qa/dogfood/test/authz-conformance.test.ts b/packages/qa/dogfood/test/authz-conformance.test.ts index 4e1dd5090f1..44ed4668712 100644 --- a/packages/qa/dogfood/test/authz-conformance.test.ts +++ b/packages/qa/dogfood/test/authz-conformance.test.ts @@ -98,7 +98,8 @@ const tripwireKey = (surface: string): string => `${surface}(${TRANSPORT_WIRED_M // them with one number is the mistake this declaration exists to prevent: it // reads working tripwires as holes and gate pins as route enumerations. The // taxonomy was established by the reach census -// (`authz-probe-blind-spot.census.ts`) and lived there as PROSE; it lives here +// (`authz-probe-blind-spot.census.ts`, retired after commit 67461403) and +// lived there as PROSE; it lives here // now, as data on the probe it describes, because a comment cannot be checked // and a `kind` can. // @@ -144,7 +145,7 @@ interface Probe { * registrar bodies into modules of their own, so they read every file on the * registrar list (`packages/rest/src/rest-server-registrar-files.ts`, * imported above) and a transport wired in a moved body still trips them. - * `file` stays the one file the entry NAMES — what the probe-table census and + * `file` stays the one file the entry NAMES — what `authz-probe-blind-spot.test.ts` and * the matrix header count — and the minted key is unchanged. */ alsoReads?: readonly string[]; @@ -200,8 +201,9 @@ const PROBES: readonly Probe[] = [ // stayed 80. Only the headline split moved, earlier and unrelatedly: 50/30 // became 51/29 when `registerUiEndpoints`, the one route in that file // resolving no identity, was guarded. Recorded so the next reader does not - // re-derive a figure that has now been checked; the full reading lives in - // `authz-probe-blind-spot.census.ts`, the authority on this population. + // re-derive a figure that has now been checked; the full reading is in + // `authz-probe-blind-spot.census.ts` at commit 67461403 (the census was + // retired after it; its derived successor is `authz-probe-blind-spot.test.ts`). // // What these two probes supply is the POPULATION; the classification stays a // reviewed row. @@ -337,8 +339,8 @@ const PROBES: readonly Probe[] = [ // // ⚠️ It is re-declared TRIPWIRE, NOT deleted, and the difference matters: // deleting it would make this gate see LESS. As a declared tripwire its zero - // is a CHECKED reading rather than an accident — the census carries a - // positive control from this same file so a zero from a moved or emptied + // is a CHECKED reading rather than an accident — `authz-probe-blind-spot.test.ts` + // holds a positive control from this same file so a zero from a moved or emptied // file cannot pass as "nothing found" — and the day a `/data` route is // mounted here again the key appears and the surface is UNCLASSIFIED, which // is exactly a tripwire's promise. @@ -352,7 +354,8 @@ const PROBES: readonly Probe[] = [ // ⚠️ The live spelling this pattern watches for exists one file away, in // `current-user-endpoints.ts` (3 mounts, none of them `/data`), which this // table does not name. Naming it is a POPULATION decision and is deliberately - // not taken here — see the census's population-source record. + // not taken here — see the retired census's population-source record + // (`authz-probe-blind-spot.census.ts` at commit 67461403). { kind: 'TRIPWIRE', file: 'packages/plugins/plugin-hono-server/src/hono-plugin.ts', @@ -715,7 +718,8 @@ function checkLedgerPopulationBaselineWith( // ⛔ The exemption is not an escape hatch for a probe that has gone blind. // Re-declaring a ROUTE_ENUMERATION probe as TRIPWIRE to clear a red is only // honest when its population really is gone AND the zero is backed by a -// positive control from that same file (the reach census holds one per file) — +// positive control from that same file (`authz-probe-blind-spot.test.ts` holds +// one per TRIPWIRE file) — // the hono probe above is written that way, dated evidence and all. function checkProbeInstrumentIntegrity(probes: readonly Probe[]): string[] { const problems: string[] = []; @@ -1813,10 +1817,18 @@ describe('#7976 — row ↔ proof attribution is mutual', () => { // 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. +// +// ⛔ ONLY THE GROUP COUNT IS STATED AND PINNED (ruling 6433001130 on #26410). +// The docblock used to state each ledger's ROW count too, and this pin held it: +// every route added inside an existing family or domain then had to edit the +// matrix docblock or red `main`, though the claim the docblock makes is at +// family / domain granularity and a new row changes nothing it says. A new +// family or domain still moves the stated figure here, alongside the +// `discover()` classification it needs anyway. const MATRIX_FILE = 'authz-conformance.matrix.ts'; -/** One figure pair the matrix docblock states, and how its ledger spells a row. */ +/** One figure 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; @@ -1886,22 +1898,15 @@ function flattenLineComments(src: string): string { * 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 + * parenthesised group count — 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 }[] { +function claimedFigures(flat: string, claim: DocblockLedgerClaim): { 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; + const re = new RegExp('`' + path + '` \\((\\d+) ' + claim.groupNoun + '\\)', 'g'); + return allMatches(flat, re).map((n) => ({ groups: Number(n) })); } /** Disagreements between the docblock's stated figures and the ledgers, on folded text. */ @@ -1911,7 +1916,7 @@ function checkFoldedDocblockFigures(flat: string): string[] { 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`, + `${claim.file}: the docblock states no (N ${claim.groupNoun}) figure for it — the pin has lost its subject`, ); continue; } @@ -1928,9 +1933,6 @@ function checkFoldedDocblockFigures(flat: string): string[] { 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}`, @@ -1983,31 +1985,13 @@ describe("#17111 — the matrix docblock's ledger figures are pinned to the ledg expect(checkDocblockFigures(matrixText())).toEqual([]); }); - it('a stale ROW count is RED — the #14503 drift, replayed', () => { + it('a stale GROUP count is RED — the #20102 drift (a family leaving), 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 { groups } = measureLedgerTable(REST); const flat = flattenLineComments(matrixText()); - const doctored = flat.replace( - `(${rows} rows / ${groups} families)`, - `(${rows + 3} rows / ${groups} families)`, - ); + const doctored = flat.replace(`\`${REST.file}\` (${groups} families)`, `\`${REST.file}\` (${groups + 1} 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}`), @@ -2018,9 +2002,9 @@ describe("#17111 — the matrix docblock's ledger figures are pinned to the ledg 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 { groups } = measureLedgerTable(REST); const flat = flattenLineComments(matrixText()); - const doctored = flat.replace(`\`${REST.file}\` (${rows} rows / ${groups} families)`, `\`${REST.file}\``); + const doctored = flat.replace(`\`${REST.file}\` (${groups} families)`, `\`${REST.file}\``); expect(doctored).not.toBe(flat); expect( checkFoldedDocblockFigures(doctored).some((p) => p.includes('the pin has lost its subject')), @@ -2028,8 +2012,8 @@ describe("#17111 — the matrix docblock's ledger figures are pinned to the ledg }); 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 { groups } = measureLedgerTable(REST); + const one = `\`${REST.file}\` (${groups} families)`; const flat = flattenLineComments(matrixText()); const doctored = flat.replace(one, `${one} and again ${one}`); expect(doctored).not.toBe(flat); diff --git a/packages/qa/dogfood/test/authz-probe-blind-spot.census.ts b/packages/qa/dogfood/test/authz-probe-blind-spot.census.ts deleted file mode 100644 index 34ee08db90f..00000000000 --- a/packages/qa/dogfood/test/authz-probe-blind-spot.census.ts +++ /dev/null @@ -1,1691 +0,0 @@ -// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. -// -// THE REACH OF `authz-conformance.test.ts`'s `discover()` — a measurement, not -// a repair. -// -// `authz-conformance.matrix.ts`'s header states that the companion test -// "ratchets completeness over a CURATED table of HTTP/transport entry points" -// and that "a new ungated route there is UNCLASSIFIED ... and breaks CI". That -// promise is true only for the entry points a probe can actually mint a key -// for. This module measures, for EVERY one of the 15 files the `PROBES` table -// names, how far that reach extends — and records the result so it cannot rot. -// -// ⭐ Since 2026-08-31 two of those files are the ROUTE LEDGERS, and they are -// the reason the matrix header now claims family/domain completeness rather -// than route completeness. Their rows read `blindSpot: 0` for a reason no -// other row here has — every ledger row is reachable by a mintable key — and -// that must NOT be read as "the route blind spot is closed". It is not: -// `BLIND_SPOT_TOTAL_STATIC` / `_RUNTIME` below are UNCHANGED at 75 / 80. -// -// ⛔ WHAT IS **NOT** CLAIMED HERE. Nothing in this file asserts that any route -// is unguarded, unauthenticated or exploitable. Route-level enforcement is -// mostly in-handler (`enforceAuth` / `shouldDenyAnonymous` / per-object -// permission checks) and is not measured here at all. What IS measured is what -// the RATCHET can SEE: an entry point outside every mintable key can gain — or -// lose — a guard without this gate changing colour. That is a defect in a -// GUARANTEE, not a breach. Exactly one route in this population has ever been -// measured unguarded, and that measurement belongs to its own card. -// -// ── THE THREE PROBE KINDS, because one number does not fit them ──────────── -// -// Reading every probe as "route discovery" is the mistake this file exists to -// prevent; the table mixes three instruments with three different promises. -// -// ⭐ These kinds are no longer prose. Each `PROBES` entry now DECLARES its -// `kind`, `deriveProbeFileCensus()` reads the declarations back out of the -// companion test's source, and the census test holds the `kinds` recorded -// below equal to them — so the taxonomy is a checked artifact rather than a -// comment two files away from the thing it describes. The declaration also -// earns its keep at runtime: a ROUTE_ENUMERATION or GATE_PIN probe minting -// ZERO keys now fails as a DEAD PROBE, which is the mechanism row 7 below was -// measured losing. -// -// ROUTE_ENUMERATION — the probe is pattern-based over a route/handler -// population, and its stated promise is auto-discovery: "ANY new route -// matching this pattern mints a new key". For these, "entry points outside -// every mintable key" is well defined and IS the blind spot. -// -// GATE_PIN — the key exists only while a named gate call still exists in the -// file (`shouldDenyAnonymous(`, `buildMcpBridge(deps, context)`, -// `resolveStdioExecutionContext(`). Its promise is ANTI-REGRESSION on one -// named gate, never route completeness. It mints exactly one key by -// construction, and a blind spot of 0 means "this file funnels through the -// pinned gate", not "every route here is enumerated". -// -// TRIPWIRE — deliberately matches nothing today (#2992 realtime transports). -// Zero keys is the CORRECT reading, not a gap. It is recorded here so that -// a future reader does not count six armed alarms as six holes. -// -// ── HOW EVERY NUMBER BELOW IS RE-DERIVED ────────────────────────────────── -// -// `deriveProbeFileCensus()` re-measures all of it from the same source files -// the probes read, and `authz-probe-blind-spot.test.ts` asserts the derived -// reading equals the record below. Counting is by OCCURRENCE (regex match -// count), never by matching LINES — the difference is not cosmetic: the -// control set that reverse-checks this census reads 20 / 78 by occurrence and -// 15 / 74 by line, and only the occurrence reading reproduces the original -// measurement. -// -// ⛔ EVERY ZERO CARRIES A POSITIVE CONTROL FROM THE SAME FILE. A zero from a -// file that moved, was renamed, or was emptied is INSTRUMENT FAILURE and reads -// byte-for-byte identical to a real zero. Each row therefore names control -// terms known present IN THAT FILE (never in a different one), the test asserts -// every control is non-zero, and a control that goes to zero fails this census -// rather than quietly re-labelling a broken probe as "nothing found". -// -// ── ONE READING THAT NEEDS A RUNTIME MOUNT, AND IS THEREFORE PROSE ──────── -// -// `rest-server.ts` is pinned below at its STATIC reading: 80 `routeManager` -// call sites, 17 registrars, 19 sites inside the one mintable registrar, 61 -// outside. ⭐ [#15542] Those 80 are counted across TWO spellings since the -// per-item family gained a switch-carrying helper — 72 direct -// `this.routeManager.register(` sites plus 8 `registerPerItemRoute(` calls, the -// helper's own forwarding call excluded so it is not counted twice. The total -// did not move; the rule had to learn the second spelling to keep reading it. -// A RUNTIME census — construct `RestServer` against a recording -// `RouteManager` and a protocol implementing every optional capability, then -// call each registrar — reads 85 / 17 / 19 / 66 instead. Both are correct and -// the delta is fully explained: `registerApprovalsEndpoints` builds 12 routes -// from 7 call sites through three route factories (`decisionRoute` x3, -// `flowMoveRoute` x2, `threadRoute` x4), and `registerBatchEndpoints` mounts -// all 5 of its routes only when the protocol implements `batchData` / -// `createManyData` / `updateManyData` / `deleteManyData` (1 of 5 against a bare -// protocol). The static reading is the one pinned because it is the one this -// package can re-derive without depending on `@objectstack/rest`. -// -// ── THE POPULATION SOURCE: measured, then DECIDED (2026-08-31) ──────────── -// -// The obvious repair is to source this ratchet's route population from the -// route ledgers instead of from a regex table, and that was measured before -// anything was written. The reading, in full, because it is the kind of -// conclusion that gets re-derived from scratch otherwise: -// -// WHAT THE LEDGERS DO COVER — richly, and more than this table ever has. -// `packages/rest/src/rest-route-ledger.ts`: 82 audited rows over 18 families, -// every route `@objectstack/rest` mounts, enumerated through -// `RestServer.getRoutes()` on a booted server and guarded per route by -// `rest-route-ledger.conformance.test.ts`. It reaches all 17 registrars; -// this table reaches 1. -// `packages/runtime/src/route-ledger.ts`: 105 rows over 23 domains. Its -// machine contract is DOMAIN-level, by live registry introspection -// (`domainRegistry.list()`), the per-route rows being documentation. It -// covers all 15 `async handle*(` methods in `http-dispatcher.ts` and all -// 18 `DomainRoute` prefixes declared by the 17 domain files. -// Nine more ledgers exist repo-wide (290 rows in total). -// -// ⭐ On FILE SELECTION the ledgers are simply the right answer, and that is -// worth stating separately: a domain file that no probe names emits no signal -// at all today — no key, no STALE, no UNCLASSIFIED — so its absence is -// structurally unobservable, and a probe table naming 4 of 17 domain files -// cannot see the other 13. Ledger domains are enumerated from the LIVE -// registry, so a new domain file cannot be silently absent from them. -// -// ⛔ WHAT THEY CANNOT SUPPLY IS THIS RATCHET'S GUARANTEE. Three measured -// blockers, each independently sufficient: -// -// 1. NO NOTION OF "GATED", and this ratchet's promise is about UNGATED -// routes. Ledger dispositions grade SDK expressibility, not authorization: -// REST reads `sdk` 81 / `server-only` 7 / `public` 3. Cross-checked -// directly rather than assumed — of the 7 REST route mounts measured to -// carry no `enforceAuth`, the ledger grades 3 `server-only`, 3 `public` -// and 1 `sdk`, and that one row is `GET /api/v1/discovery`. -// -// ⚠️ RE-MEASURED 2026-09-09 against `f6b7c53db7`. Both halves of the -// sentence above moved, FOR TWO UNRELATED REASONS, and separating them is -// the whole value of re-recording it. -// -// THE SET LOST A ROW: 8 -> 7, and the row it lost is -// `GET /api/v1/ui/view/:object/:type`, the single route in this whole -// population ever measured unguarded. Guarding it at `cc837dbfec` is the -// same repair that moved 30 ungated to 29 one paragraph below, so it is -// gated at the call site now and leaves this set, taking the second `sdk` -// with it: 3 / 3 / 2 became 3 / 3 / 1. ⛔ Its LEDGER row did not move at -// all — still `sdk`, still shape-identical to every other `sdk` row, the -// 79 that are gated and the 1 that is not. That is this blocker restated -// by a live example: the grade did not notice the gate arriving, and it -// would not notice one leaving either. -// -// THE LEDGER TOTAL MOVED FOR A REASON THAT IS NOT ABOUT GATES AT ALL, and -// ⛔ must not be read as evidence about them: `sdk` 84 -> 81 when #14503 -// took the three REST package read/delete rows out of the ledger -// (94 rows -> 91, already recorded on the `rest-route-ledger.ts` probe row -// in the PROBES table below). It is written down here only because both -// figures live in one sentence, where a reader has no way to tell which of -// them moved for which reason — the failure this whole census is named for. -// -// `public` states INTENT for 3 browser-facing form routes; it is not a -// gate measurement and was never built as one. -// -// 2. DERIVING "gated" FROM SOURCE SYNTAX IS UNSAFE — measured, not assumed. -// Scanning each of the 80 registration sites in `rest-server.ts` for -// `enforceAuth` — the SAME two spellings the `populationRule` below counts, -// 72 direct `this.routeManager.register(` call sites plus 8 -// `registerPerItemRoute(` calls — reads 51 gated / 29 ungated, and 22 of -// those 29 are FALSE, in two structural shapes: `registerMetadataEndpoints` -// installs a wrapping `guardedRouteManager` so its 19 inner routes are -// gated with no `enforceAuth` at the call site, and -// `registerSecurityExplainEndpoints` shares one `handler` const declared -// outside its 3 `register(` calls. A 76% false-ungated rate, concentrated -// on the largest registrar, and hand-annotating the exceptions is the same -// rot this instrument already has. -// -// ⚠️ RE-MEASURED 2026-09-08 against `5abca1792e`, because the 19 is a count -// inside the very registrar the per-item helper re-spelled, and because -// this paragraph attributed all 80 sites to the direct spelling alone -// while `:82` above already knew there were two — the authority on this -// population contradicting itself 57 lines apart. -// -// WHAT DID NOT MOVE: 22 = 19 + 3. The same 19 routes, 11 still direct and -// 8 now helper-routed, all through the same wrapping; the same 3 sharing -// one handler const; the population still 80. The re-spelling moved none -// of the five figures. -// -// WHAT DID MOVE, and not here: 50 gated / 30 ungated became 51 / 29 when -// `registerUiEndpoints` — the one route in this file that resolved no -// identity, the same repair recorded as `enforceAuth` 61 -> 64 on the -// rest-server.ts row below — was guarded. That landed the day AFTER this -// paragraph was first written and hours BEFORE it was copied into -// `rest-route-ledger.ts`, `route-ledger.ts` and `authz-conformance.test.ts`, -// which is why four sites carried 50/30 in step. ⛔ Written down as a -// checked figure rather than left as one nobody dared touch: the two read -// identically on the page, and only this note tells them apart. -// -// ⛔ The rejection stands whatever the numbers do, and the second spelling -// strengthens it: a naive scanner now has to know both spellings before it -// can read the file even this badly. -// -// 3. A LEDGER IS A DERIVED DATA FILE, ONE GUARDED STEP BEHIND THE SOURCE. -// Adding a route to a registrar in `rest-server.ts` does not touch -// `rest-route-ledger.ts`, so a ledger-sourced population mints no new key -// and this ratchet stays GREEN on exactly the mutation it should catch. -// The red lands in `rest-route-ledger.conformance.test.ts` instead — a -// different gate, in a different package, promising something else. A -// COMPOSED guarantee (no route without a ledger row; no ledger row without -// a classification) is a defensible design, but it is a different promise -// from the one this matrix header states, and adopting it is a contract -// decision rather than a repair. -// -// ⇒ ⭐ DECIDED 2026-08-31, and the three blockers above are why the answer is -// what it is rather than the obvious one. The ledgers now supply the -// POPULATION at FAMILY / DOMAIN granularity — 19 REST families + 21 dispatcher -// domains = 40 keys — and nothing else. They do NOT supply the -// CLASSIFICATION: blocker 1 stands, so "is it gated" remains a reviewed matrix -// row, never a ledger disposition. Blocker 2 stands as the reason no syntactic -// reading was attempted anywhere. Blocker 3 is not fixed and is not hidden: a -// route added inside an existing family mints nothing and this gate stays -// green, which is stated at that exact granularity in the matrix header with -// no caveat attached to a wider claim. -// -// What the change buys is the FILE-SELECTION layer rather than the route -// layer: a family or domain can no longer be silently absent, because both -// ledgers are enumerated from a running server and guarded in both directions. -// 6 of the 40 keys are classified by rows that already pinned the same -// surface; the other 34 are enumerated, dated and pinned shrink-only in -// `authz-ledger-population.baseline.ts`. Before that date those 34 surfaces -// minted no key at all — no UNCLASSIFIED, no STALE, nothing. -// -// The DIRECTION half went to the producer: a ledger row may declare the authz -// posture it has been reviewed to have (`authz:`, phased exactly like -// `responseSchema` — optional, no coverage no fill, never mass-produced), and -// the companion test resolves every declaration against an `enforced` matrix -// row. That is what eventually makes blocker 3 answerable at the ledger review -// point, where a new route is already being read. -// -// ⛔ Two readings stay REJECTED and are recorded here so they are not -// re-proposed: deriving "gated" from source syntax (76% false-ungated), and -// taking a ledger disposition as an authorization fact (blocker 1). - -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname, join } from 'node:path'; -// [#23050 slice 3] THE list of the files `RestServer`'s registrars live in — -// see the `rest-server.ts` row's population rule. -// [#23050 slice 13a] …and its named subset of the modules that hold a moved -// `/meta` group — see the same row's `reachable` rule. -import { - REST_SERVER_META_ROUTE_FILES, - REST_SERVER_REGISTRAR_DIR, - REST_SERVER_REGISTRAR_FILES, -} from '../../../rest/src/rest-server-registrar-files.js'; - -const HERE = dirname(fileURLToPath(import.meta.url)); -// packages/qa/dogfood/test → repo root. -const REPO_ROOT = join(HERE, '../../../..'); - -const read = (rel: string): string => readFileSync(join(REPO_ROOT, rel), 'utf8'); - -/** Occurrence count, never a line count. See the header. */ -export const occurrences = (src: string, re: RegExp): number => (src.match(re) ?? []).length; - -export type ProbeKind = 'ROUTE_ENUMERATION' | 'GATE_PIN' | 'TRIPWIRE'; - -export interface ProbeFileReading { - /** Repo-relative path, exactly as the `PROBES` table spells it. */ - file: string; - /** Which instruments the table points at this file. */ - kinds: ProbeKind[]; - /** `PROBES` entries naming this file. */ - probes: number; - /** Keys those entries mint against today's source. */ - keys: number; - /** - * What the file exposes, under the rule named in `populationRule`. For files - * that mount no HTTP entry point at all this is 0 — a fact about the file, - * not a coverage claim. - */ - population: number; - /** Entry points some mintable key can name. */ - reachable: number; - /** `population - reachable`: entry points no key can ever name. */ - blindSpot: number; - /** How `population` is counted — executed by `deriveProbeFileCensus`. */ - populationRule: string; - /** In-file terms that must stay non-zero for this row's zeros to be readings. */ - controls: Readonly>; - note: string; -} - -/** The `PROBES` table's own shape, re-derived from the companion test's source. */ -export interface ProbeTableReading { - entries: number; - files: number; - keys: number; -} - -// [#21061] 18 / 13 / 15 -> 19 / 14 / 17: the `/analytics` domain gate joined the -// table as a GATE_PIN on `packages/runtime/src/domains/analytics.ts`, and the -// `anonymous-deny-analytics` row covers its key plus the `/analytics` -// dispatcher-domain key that left the shrink-only ledger baseline. -// [#22430] 19 / 14 / 17 -> 19 / 14 / 18: no probe and no file joined; the -// `anonymous-deny-api-description` row covers one more key, the `openapi` REST -// family, which left the shrink-only ledger baseline. -// [#22432] 19 / 14 / 18 -> 20 / 15 / 20: the `/i18n` domain gate joined the -// table as a GATE_PIN on `packages/runtime/src/domains/i18n.ts`, and the -// `anonymous-deny-i18n` row covers its key plus the `/i18n` dispatcher-domain -// key that left the shrink-only ledger baseline. -// [#22576] 20 / 15 / 20 -> 20 / 15 / 21: no probe and no file joined; the -// `approval-action-token` row covers one more key, the `/approvals/act` -// dispatcher-domain key, which arrived classified rather than through the -// baseline (the baseline still holds 30 and its ceiling did not move). -// [#22755] 20 / 15 / 21 -> 20 / 15 / 22: no probe and no file joined; the -// `webhook-redeliver-session` row covers one more key, the -// `/webhooks/redeliver` dispatcher-domain key, which arrived classified rather -// than through the baseline (the baseline still holds 30, its ceiling unmoved). -// [ADR-0138 E2] 20 / 15 / 22 -> 23 / 18 / 29: the `/notifications`, `/keys` and -// `/share-links` domain gates joined the table as GATE_PINs on their three -// domain files, once each domain's anonymous decision moved to its first -// statement; the three `anonymous-deny-*` rows cover each gate key plus its -// dispatcher-domain key, and `anonymous-door-public-form` covers the `forms` -// REST family key — four keys that left the shrink-only baseline (30 -> 26). -// [ADR-0138 E2, second slice] 23 / 18 / 29 -> 25 / 20 / 33: the `/ui` and -// `/data` domain gates joined the table as GATE_PINs on their two domain -// files, once each domain's anonymous decision became its first statement; -// `anonymous-deny-ui` and `anonymous-deny-data` cover each gate key plus its -// dispatcher-domain key — two keys that left the baseline (26 -> 24). -export const PROBE_TABLE: ProbeTableReading = { entries: 25, files: 20, keys: 33 }; - -/** - * The probe count `authz-conformance.matrix.ts`'s header states. - * - * ⭐ REPAIRED, and the pin INVERTED with it. This was recorded drift: the - * header said "15 probes" from 2026-08-16, when it was true, and the table has - * held 16 since a 16th probe landed on 2026-08-18 without the sentence moving. - * A measurement pinned the discrepancy deliberately rather than fixing it, so - * that its own no-repair fence stayed unambiguous; the repair belonged with - * whoever touched the probe table, and this is that change. - * - * ⚠️ The assertion that reads this constant flipped from "these must DIFFER" - * (drift pinned) to "these must be EQUAL" (drift closed). That inversion is - * the point: the sentence is now held equal to the table by a test, so the - * next probe added without moving the prose is RED instead of a fact recorded - * in a third file. ⛔ Do not re-point this at a hand-written number — it is - * read out of the matrix header's own text. - * - * [#21061] 18 -> 19, moved in the same change as the header sentence and the - * probe it counts, which is exactly what the inverted pin asks for. - * [#22432] 19 -> 20, the same way: the `/i18n` gate pin and the header - * sentence moved together. - * [ADR-0138 E2] 20 -> 23, the same way: the three domain gate pins and the - * header sentence moved together. - * [ADR-0138 E2, second slice] 23 -> 25, the same way: the `/ui` and `/data` - * gate pins and the header sentence moved together. - */ -export const MATRIX_HEADER_PROBE_CLAIM = 25; - -export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [ - // ── the two LEDGER files: the population source since 2026-08-31 ─────── - // - // ⭐ These two rows read differently from every other row here, and the - // difference is the point of the change that added them: `blindSpot` is 0 - // NOT because the file mounts nothing (the reading four rows below give) but - // because EVERY row in the table is reachable by a mintable key. Each ledger - // row carries a `family` / `domain`, and each distinct value mints a key, so - // there is no row the ratchet cannot name. - // - // ⚠️ `population` here counts LEDGER ROWS, not registrar call sites, and the - // two are not interchangeable. A `blindSpot` of 0 says every ledger row is - // covered by some key at FAMILY / DOMAIN granularity; it does NOT say every - // route mints its own key, and it does not touch the 75/80 route-level blind - // spot recorded below — that number is unchanged by this change and Leg A of - // the reverse verification is what proves it unchanged. - { - file: 'packages/rest/src/rest-route-ledger.ts', - kinds: ['ROUTE_ENUMERATION'], - probes: 1, - keys: 18, - // [#21180] 83 / 83 / 0 -> 82 / 82 / 0: the anonymous lookup-picker row - // (`GET /api/v1/forms/:slug/lookup/:field`) left the ledger with its route. - // It carried `family: 'forms'`, a family the two surviving form rows still - // carry, so `reachable` moves with `population`, the blind spot stays 0 and - // `keys` stays 18 (18 distinct families before and after, re-derived). - // [ADR-0131 cutover, stage 7-pre] 82 / 82 / 0 -> 81 / 81 / 0: the - // permission-set overlay-discard row left with its route. It carried - // `family: 'security'`, which the surviving security rows still carry, so - // `reachable` moves with `population` and `keys` stays 18. - // [#25104] 81 / 81 / 0 -> 80 / 80 / 0: the OpenAPI document's `/docs` - // viewer page row (`GET /api/v1/docs`) left the ledger with its route. It - // carried `family: 'openapi'`, which the surviving document row still - // carries, so `reachable` moves with `population` and `keys` stays 18. - // [#26400] 80 / 80 / 0 -> 81 / 81 / 0: the approval record-lock read - // (`GET /api/v1/approvals/records/:object/:recordId/lock`, landed in - // #26361) joined the ledger with its route. It carries `family: - // 'approvals'`, a family the other approval rows already carry, so - // `reachable` moves with `population`, the blind spot stays 0 and `keys` - // stays 18 (18 distinct families before and after, re-derived). - population: 81, - reachable: 81, - blindSpot: 0, - populationRule: 'ledger rows inside REST_ROUTE_LEDGER; reachable = rows carrying a `family` (each distinct value mints a key)', - controls: { "route: '": 81, "family: '": 81, RestRouteLedgerEntry: 2 }, - // [#22430] The `openapi` family is the second classified one, by the - // `anonymous-deny-api-description` row; the note's 2 / 16 counts it. - // [ADR-0138 E2] The `forms` family is the third, by the D2 class 1 row - // `anonymous-door-public-form` (both of its routes are the class): 3 / 15. - note: - 'The audited disposition of every route @objectstack/rest mounts, enumerated through ' + - 'RestServer.getRoutes() on a booted server and guarded per route by rest-route-ledger.conformance.test.ts. ' + - 'That guard is why this file can be a population source and a regex table cannot: a mounted route with no ' + - 'row here is already RED in another package, so a new family cannot be silently absent from this file, ' + - 'and therefore cannot be silently absent from the authz ratchet either. 18 families; 3 classified by ' + - 'matrix rows (metadata, openapi and forms), 15 enumerated in the shrink-only baseline. Re-measured 80 -> 81 when the ' + - 'approval record-lock read joined with its route; it carries `family: approvals`, which the other approval rows ' + - 'already carry. Earlier re-measured 81 -> 80 when the ' + - 'OpenAPI document viewer page row left with its route; it carried `family: openapi`, which the surviving document row ' + - 'still carries. Earlier re-measured 82 -> 81 when the permission-set ' + - 'overlay-discard row left with its route; it carried `family: security`, which the surviving security rows still carry. ' + - 'Earlier re-measured 83 -> 82 when the anonymous ' + - 'public-form lookup-picker row left with its route; it carried `family: forms`, which the surviving form rows ' + - 'still carry, so `reachable` moved with `population` and the families stay 18. Earlier re-measured 91 -> 83 (19 -> 18 families) ' + - 'when the whole saved-report `reports` family left with its eight routes, all eight carrying the family, so ' + - '`reachable` moved with `population`. Earlier re-measured 94 -> 91 when the ' + - 'three REST package read/delete rows (GET /packages, GET /packages/:id, DELETE /packages/:id) left the ' + - 'ledger with their routes; each carried `family: packages`, so `reachable` moved with ' + - '`population` (91/91) and the blind spot stays 0 -- the family itself survives on the publish row.', - // The 94 -> 91 re-measurement above landed with #14503 (the REST registrar - // keeps only POST /packages/publish; the dispatcher domain is the single - // implementation of the reads and the delete). The 91 -> 83 one landed with - // #20102 (the saved-report stack retired whole). The ids live here, not in - // the string: a runtime string reaches readers who cannot resolve it. - }, - { - file: 'packages/runtime/src/route-ledger.ts', - kinds: ['ROUTE_ENUMERATION'], - probes: 1, - keys: 23, - // [#13953] 80 -> 82: the two operator run-lifecycle rows - // (`POST /automation/:name/runs/:runId/cancel` and `.../restore-suspension`). - // Both carry `domain: '/automation'`, an EXISTING key, so `reachable` moves - // with `population`, `blindSpot` stays 0 and `keys` stays 21 — a population - // that grows inside an already-classified domain mints nothing new. - // [#19543] 82 -> 81: the `GET /automation` flow-list row left with its - // route (door ④ — the list is `GET /meta/flow`). It carried - // `domain: '/automation'`, a key other rows still carry, so `reachable` - // moves with `population`, `blindSpot` stays 0 and `keys` stays 21. - // [#20676] 81 -> 82: the `POST /automation/:name/clone` row arrived with its - // mount (ADR-0126 §7.1). It carries `domain: '/automation'`, an EXISTING - // key, so `reachable` moves with `population`, `blindSpot` stays 0 and - // `keys` stays 21 (21 distinct domains before and after, re-derived). - // [#22576] 82 -> 84: the `GET` and `POST /approvals/act` rows arrived with - // the dispatcher's `/approvals/act` domain (the ADR-0043 action page for a - // kernel with no raw app). Both carry `domain: '/approvals/act'`, so both - // are REACHABLE — `reachable` moves with `population` and `blindSpot` - // stays 0 — and, unlike the rows above, they carry a NEW distinct domain: - // `keys` 21 -> 22, one new key, classified by `approval-action-token` - // rather than added to the baseline. Re-derived by the companion test; the - // controls moved with the rows ("route: '" and "domain: '" 82 -> 84, - // RouteLedgerEntry unchanged at 2). - // [ADR-0131 D6] 84 -> 85: the `POST /security/_activation/:type/:name` row - // arrived with its mount (the catalog activation door). It carries - // `domain: '/security'`, an EXISTING key, so `reachable` moves with - // `population`, `blindSpot` stays 0 and `keys` stays 22. Its module, - // `domains/catalog-activation.ts`, declares no DomainRoute prefix — the - // `/security` domain dispatches to it — so the domain-file counts in the - // note below are unchanged. Controls moved with the row ("route: '" and - // "domain: '" 84 -> 85, RouteLedgerEntry unchanged at 2). - // [#22755] 85 -> 86: the `POST /webhooks/redeliver` row arrived with the - // dispatcher's `/webhooks/redeliver` domain (the webhook redeliver door for - // a kernel with no raw app). It carries `domain: '/webhooks/redeliver'`, so - // it is REACHABLE — `reachable` moves with `population`, `blindSpot` stays - // 0 — and it carries a NEW distinct domain: `keys` 22 -> 23, one new key, - // classified by `webhook-redeliver-session` rather than added to the - // baseline. Controls moved with the row ("route: '" and "domain: '" - // 85 -> 86, RouteLedgerEntry unchanged at 2). - // [#22773] 86 -> 87: the `POST /automation/hooks/:flowName/:hookId` row - // arrived with the inbound-hook door (segment 2 of ruling A on #22757). It - // carries `domain: '/automation'`, an EXISTING key — the `/automation` - // domain hands the route to `domains/automation-hooks.ts`, which declares - // no DomainRoute prefix — so `reachable` moves with `population`, - // `blindSpot` stays 0, `keys` stays 23 and the domain-file counts in the - // note below are unchanged. Controls moved with the row ("route: '" and - // "domain: '" 86 -> 87, RouteLedgerEntry unchanged at 2). - // [ADR-0126 §7.1] 87 -> 88: the `POST /security/permission-sets/:name/clone` - // row arrived with its mount (the permission-set clone door). It carries - // `domain: '/security'`, an EXISTING key, so `reachable` moves with - // `population`, `blindSpot` stays 0 and `keys` stays 23. Its module, - // `domains/permission-set-clone.ts`, declares no DomainRoute prefix — the - // `/security` domain dispatches to it — so the domain-file counts in the - // note below are unchanged. Controls moved with the row ("route: '" and - // "domain: '" 87 -> 88, RouteLedgerEntry unchanged at 2). - // [#23909] 88 -> 92: the `GET`, `PUT`, `PATCH` and `DELETE /keys` rows - // arrived with their mounts (every `/api/v1/keys` verb is mounted, so an - // anonymous caller is answered 401 on the standalone server). All four - // carry `domain: '/keys'`, the EXISTING key the `POST /keys` row already - // carries, so `reachable` moves with `population`, `blindSpot` stays 0 and - // `keys` stays 23 (23 distinct domains before and after, re-derived). - // Controls moved with the rows ("route: '" and "domain: '" 88 -> 92, - // RouteLedgerEntry unchanged at 2). - // [#24024] 92 -> 105: twelve refusal-only `/notifications` rows (the four - // verbs each of the three faces does not serve) and the `* /share-links/**` - // catch-all row arrived with their mounts (an anonymous caller is answered - // 401 on the standalone server). They carry `domain: '/notifications'` and - // `domain: '/share-links'`, EXISTING keys, so `reachable` moves with - // `population`, `blindSpot` stays 0 and `keys` stays 23 (23 distinct - // domains before and after, re-derived). Controls moved with the rows - // ("route: '" and "domain: '" 92 -> 105, RouteLedgerEntry unchanged at 2). - population: 105, - reachable: 105, - blindSpot: 0, - populationRule: 'ledger rows inside ROUTE_LEDGER; reachable = rows carrying a `domain` (each distinct value mints a key)', - controls: { "route: '": 105, "domain: '": 105, RouteLedgerEntry: 2 }, - // [#22432] Re-derived on the merged ref: the note's two counts had stood at - // 11 files / 5 classified / 16 baselined since before the `/analytics` - // domain was classified (#21061), which named one more file and moved one - // more key. The `/i18n` domain names a sixth file and moves a seventh key: - // 15 domain files declare a DomainRoute prefix, 6 of them are named by a - // probe (actions, automation, packages, analytics, i18n, mcp), and the - // baseline's dispatcher block holds 14 of the 21 domains. - // [#22576] `domains/approvals.ts` is a sixteenth domain file with a - // seventeenth plain prefix, named by no probe, and its key is the eighth - // classified: 22 domains, 14 still in the baseline. - // [#22755] `domains/webhooks.ts` is a seventeenth domain file with an - // eighteenth plain prefix, named by no probe, and its key is the ninth - // classified: 23 domains, 14 still in the baseline. - // [ADR-0138 E2] `domains/notifications.ts`, `domains/keys.ts` and - // `domains/share-links.ts` are now named by a probe (their domain floors' - // gate pins), and their three keys are the tenth to twelfth classified: - // 23 domains, 11 still in the baseline, 8 domain files no probe names. - // [ADR-0138 E2, second slice] `domains/ui.ts` and `domains/data.ts` are now - // named by a probe too (their domain floors' gate pins), and their two keys - // are the thirteenth and fourteenth classified: 23 domains, 9 still in the - // baseline, 6 domain files no probe names. - note: - 'The dispatcher half. Its machine contract is DOMAIN-level by live registry introspection ' + - '(domainRegistry.list()), guarded in BOTH directions by route-ledger.conformance.test.ts: every ' + - 'registered domain needs a row, and every ledger domain must be a live prefix or a pinned legacy / ' + - 'non-dispatch branch. That two-way guard is what settles the FILE-SELECTION layer by ' + - 'construction — all 18 DomainRoute prefixes declared across the 17 domain files that declare one are ' + - 'ledger domains today, including the 6 files no probe names. 23 domains; 14 classified ' + - '(/meta, /actions, /automation, /packages, /mcp, /analytics, /i18n, /approvals/act, ' + - '/webhooks/redeliver, /notifications, /keys, /share-links, /ui, /data), 9 in the shrink-only baseline.', - }, - { - file: 'packages/rest/src/rest-server.ts', - kinds: ['ROUTE_ENUMERATION', 'TRIPWIRE'], - probes: 3, - keys: 1, - population: 70, - reachable: 19, - blindSpot: 51, - populationRule: - 'route registration sites — `this.routeManager.register(` call sites, LESS the one inside ' + - '`registerPerItemRoute` (the shared forwarder, not a route; its extent is bounded by the declaration\'s own ' + - 'indentation and the subtrahend is pinned at 1 by the `forwarder slice:` control, never inferred from a ' + - 'terminator spelling), PLUS `registerPerItemRoute(` call sites; counted over this file AND every ' + - 'module on the registrar list (`packages/rest/src/rest-server-registrar-files.ts`) — ' + - '`data-action-routes.ts`, `batch-routes.ts`, `approvals-routes.ts`, `sharing-routes.ts`, ' + - '`analytics-routes.ts`, `security-routes.ts`, `discovery-routes.ts`, `email-routes.ts`, ' + - '`search-routes.ts`, `ui-routes.ts`, `form-routes.ts`, `crud-routes.ts`, `meta-collection-routes.ts`, ' + - '`meta-item-read-routes.ts`, `meta-item-write-routes.ts`, `meta-item-history-routes.ts` and ' + - '`meta-object-state-routes.ts`, the modules the bodies of ' + - '`registerDataActionEndpoints`, `registerBatchEndpoints`, `registerApprovalsEndpoints`, ' + - '`registerSharingEndpoints`, `registerSharingRuleEndpoints`, `registerAnalyticsEndpoints`, ' + - '`registerSecurityExplainEndpoints`, `registerSecurityEndpoints`, `registerDiscoveryEndpoints`, ' + - '`registerOpenApiEndpoints`, `registerEmailEndpoints`, `registerSearchEndpoints`, ' + - '`registerUiEndpoints`, `registerFormEndpoints` and `registerCrudEndpoints`, and all eight `/meta` ' + - 'groups of `registerMetadataEndpointsInner`, moved to; ' + - 'reachable = those inside registerMetadataEndpoints, PLUS every site in the registrar list\'s named `/meta` ' + - 'subset (`REST_SERVER_META_ROUTE_FILES`: `meta-collection-routes.ts`, `meta-item-read-routes.ts`, ' + - '`meta-item-write-routes.ts`, `meta-item-history-routes.ts` and `meta-object-state-routes.ts`), whose ' + - 'registrations the inner method ' + - 'runs inside the umbrella\'s swap', - // [#15542 / #15854] ⭐ THE POPULATION RULE LEARNED A SECOND SPELLING, and - // the numbers it produces did NOT move: 80 / 19 / 61, exactly as before. - // - // WHAT MOVED IN THE SOURCE. The per-item family's later members (`PUT`, - // `DELETE`, `/history`, `/audit`, `/diff`, `/published`, `/publish`, - // `/rollback`) stopped being direct `this.routeManager.register(` call - // sites and became `registerPerItemRoute(` calls — a local helper carrying - // the `endpoints.item` switch. Net -7 on the old one-spelling reading: 8 - // sites left that spelling and the helper's own forwarding call added 1 - // back. So the old rule read population 73 / reachable 12. - // - // ⛔ 73 / 12 WAS NOT RE-RECORDED, and the reason is this file's whole - // purpose. Those 8 routes are still mounted and still registered exactly - // where they were; only the spelling of the call changed. Writing 73 down - // would have ratified a population 7 short of the real one and encoded a - // 7-route hole in the very blind-spot count this census exists to keep - // honest — the failure it is named for, committed by its own record. - // - // ⚠️ REACHABILITY WAS CHECKED BEFORE THE COUNT WAS WIDENED, because if the - // helper HID those routes from the probe the repair would belong in - // `rest-server.ts` and not here. It does not. `registerPerItemRoute` reads - // `this.routeManager` at CALL time and every one of its 8 call sites is - // lexically inside `registerMetadataEndpointsInner`, which - // `registerMetadataEndpoints` runs with `this.routeManager` swapped to the - // anonymous-deny `guardedRouteManager` and restored in a `finally`. So a - // helper-routed registration goes through the identical wrapping the 11 - // remaining direct sites in that registrar do, and the umbrella key - // `meta:rest-server.ts:registerMetadataEndpoints` covers it unchanged. - // Pinned at runtime rather than argued from source: - // `packages/rest/src/rest-meta-auth.test.ts` drives an anonymous - // `GET {meta}/:type/:name/history` — a helper-routed route — to 401. - // - // The decomposition, so the two halves stay legible: 72 direct route - // registrations + 8 helper-routed = 80 population; 11 + 8 = 19 reachable. - // `this.routeManager.register(` reads 73 because the helper's forwarder is - // one of them, and it is sliced out before counting. - // - // [commit cc837dbfe] `enforceAuth` 61 -> 64. ⛔ RE-ANCHORED, not relaxed: the control - // exists to prove this census is still reading the file it thinks it is, and - // a rising `enforceAuth` is precisely what the 2026-08-30 ruling (commit cc837dbfe) - // was supposed to cause — `registerUiEndpoints` was the ONE route in this - // file that resolved no identity, and it is now guarded. The move is +3 over - // the whole file (`occurrences` counts the bare term, comments included): - // one new call site — `if (this.enforceAuth(req, res, context)) return;`, - // 52 -> 53 — plus two prose mentions in the new doc-comments. ⛔ Kept as an - // EXACT count rather than a range or a floor: a range would stop this row - // noticing the next move, which is the only thing it is for. - // - // ⚠️ The three sibling numbers were re-derived and did NOT move, which is - // what says this is a guard change and not a surface change: `population` - // 80, `reachable` 19, `private register*Endpoints(` 17 and - // `this.routeManager.register(` 80 are all unchanged — commit cc837dbfe added no route - // and no registrar. `blindSpot` therefore stays 61 as well. - // ⚠️ That last figure is the reading AS OF commit cc837dbfe and is left as written: - // the control is 73 today for the spelling reason recorded above, and the - // population it feeds is still 80. Do not "correct" the paragraph — it is a - // dated measurement, not a live claim. - // - // [#16306] ⭐ A THIRD CONTROL, AND WHAT THE OTHER TWO CANNOT SEE. The two - // above were measured insufficient rather than argued insufficient. Respell - // the helper's terminator `};` as `}` — no semicolon, nothing lints it — - // and BOTH stay green (the declaration is still present and still matches; - // only its terminator moved) while the rule's old unanchored - // `indexOf('\n };', at)` ran the forwarder slice 5132 → 9004, 3873 - // lines, swallowing 21 registrations: population read 60 and reachable read - // 20, one measurement LOW and one HIGH, from a single edit. Measured - // 2026-09-08 against 44c849c7d6, before and after the repair. - // - // ⚠️ 60 is what a genuine removal of 20 routes reads too, and NOTHING in - // this record separated the two. `forwarder slice: - // this.routeManager.register(` is the reading that does: it is the - // subtrahend itself, pinned at 1, so a low population with it at 1 is a - // real drop and a low population with it off 1 is the slice eating too - // much. It is deliberately the one SLICE-scoped control on this row. - // [#20102] 80 / 19 / 61 -> 72 / 19 / 53, and `private register*Endpoints(` - // 17 -> 16: `registerReportsEndpoints` was deleted whole with the retired - // saved-report stack — eight direct `this.routeManager.register(` sites - // (73 -> 65), each guarded, so `enforceAuth` 64 -> 56 (eight call sites; the - // registrar's comments did not name the term). None of the eight was - // inside `registerMetadataEndpoints`, so `reachable` does not move and the - // blind spot shrinks by exactly the eight routes that no longer exist. - // [#21180] 72 / 19 / 53 -> 71 / 19 / 52: the anonymous lookup-picker route - // (`GET /forms/:slug/lookup/:field`) was deleted with its handler — one - // direct `this.routeManager.register(` site (65 -> 64) inside - // `registerFormEndpoints`, NOT inside `registerMetadataEndpoints`, so it - // was a BLIND SPOT: `reachable` does not move and the blind spot shrinks by - // exactly that one route. Measured on the tree before the deletion and - // after it: 72 / 19 / 53 there, 71 / 19 / 52 here. The other controls did - // not move: the handler never called `enforceAuth` (the public-form routes - // bypass it), so that stays 56, and `registerFormEndpoints` survives, so - // `private register*Endpoints(` stays 16. - // [#22430] `enforceAuth` 56 -> 58, and nothing else on this row moved: - // 71 / 19 / 52 and every other control re-derived unchanged. The two - // API-description handlers in `registerOpenApiEndpoints` (the document and - // its viewer) now open with `if (this.enforceAuth(req, res, context)) - // return;` — two call sites, no prose mention. Both routes were already in - // the population and stay outside `registerMetadataEndpoints`, so the blind - // spot is unchanged: guarding a route is not reaching it with a key. - // [ADR-0131 cutover, stage 7-pre] 71 / 19 / 52 -> 70 / 19 / 51, and - // `enforceAuth` 58 -> 57: the permission-set overlay-discard route was - // deleted with its handler, one direct `this.routeManager.register(` site - // (64 -> 63) inside `registerSecurityEndpoints`, NOT inside - // `registerMetadataEndpoints`, so it was a blind spot; its handler opened - // with `enforceAuth`, so that control drops by one. The registrar - // survives, so `private register*Endpoints(` stays 16. - // [#23050 slice 1] 70 / 19 / 51 UNCHANGED, and that is the measurement: - // `registerDataActionEndpoints`' body (nine routes, all outside - // `registerMetadataEndpoints`, so all nine in the blind spot) moved to - // `data-action-routes.ts`, and the population rule reads that module too — - // see the derivation. `this.routeManager.register(` stays 63 over both files - // (54 here + 9 there, the new control), `private register*Endpoints(` stays - // 16 (the registrar is still declared here), and `enforceAuth` 57 -> 61 is - // RE-ANCHORED, not relaxed: the nine call sites moved with their routes and - // are counted in both readings; the four new occurrences are the seam that - // carries the move — the host literal's forwarder (`enforceAuth: (...a) => - // this.enforceAuth(...a)`, two) and the host interface's member - // (`enforceAuth: RestServer['enforceAuth']`, two). No call site was added. - // [commit 1d79556766] `enforceAuth` 61 -> 64, and nothing else on this row - // moved: 70 / 19 / 51 and every other control re-derived unchanged. - // ⛔ RE-ANCHORED, not relaxed: PR #23541 (#23367, the API-key lifecycle) - // added three PROSE mentions and no call site, and `occurrences` counts the - // bare term, comments included. At `fa6a6085c` they are - // `rest-server.ts:2395` and `:2397`, the docblock on - // `apiKeyLifecycleRefusals` ("so {@link enforceAuth} can answer it", - // "every door that never reaches `enforceAuth`"), and `:3471`, the inline - // comment in `computeExecCtx`'s outer `catch` ("for `enforceAuth`, which - // answers its `403` and code"). The same commit edited `enforceAuth`'s own - // body and docblock in lines that do not spell the term, removed no - // mention, and left `data-action-routes.ts` untouched. No call site was - // added. ⚠️ The dogfood shards skip on PRs and in the merge queue, so this - // move was seen only by the hourly full run on `main`. - // [#23050 slice 2] 70 / 19 / 51 UNCHANGED, the same measurement as slice 1: - // `registerBatchEndpoints`' body (five routes, all outside - // `registerMetadataEndpoints`, so all five in the blind spot) moved to - // `batch-routes.ts`, and the population rule reads that module too. - // `this.routeManager.register(` stays 63 over the three files (49 here + 9 - // + 5, the new control), `private register*Endpoints(` stays 16 (the - // registrar is still declared here), and `enforceAuth` 64 -> 68 is - // RE-ANCHORED, not relaxed: the five call sites moved with their routes and - // are counted in both readings; the four new occurrences are that slice's - // seam, the same two spellings as slice 1's (the host literal's forwarder, - // two; the host interface's member, two). No call site was added. - // [#23050 slice 3] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerApprovalsEndpoints`' body (seven registration sites, all outside - // `registerMetadataEndpoints`, so all in the blind spot) moved to - // `approvals-routes.ts`. The population rule now reads the registrar LIST - // rather than naming each module, so the slice that moves the next body - // widens it by adding its module there; each module keeps its own control - // and the list's length is one too. `this.routeManager.register(` stays 63 - // over the four files (42 here + 9 + 5 + 7), `private register*Endpoints(` - // stays 16, and `enforceAuth` 68 -> 72 is RE-ANCHORED, not relaxed: the - // seven call sites moved with their routes and are counted in both - // readings; the four new occurrences are this slice's seam, the same two - // spellings (the host literal's forwarder, two; the host interface's - // member, two). No call site was added. - // [#23050 slice 4] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerSharingEndpoints`' and `registerSharingRuleEndpoints`' bodies - // (eight registration sites, three and five, all outside - // `registerMetadataEndpoints`, so all in the blind spot) moved to ONE - // module, `sharing-routes.ts`, which joins the list (length 4 -> 5) with its - // own control. `this.routeManager.register(` stays 63 over the five files - // (34 here + 9 + 5 + 7 + 8), `private register*Endpoints(` stays 16 (both - // registrars are still declared here), and `enforceAuth` 72 -> 80 is - // RE-ANCHORED, not relaxed: the eight call sites moved with their routes - // and are counted in both readings; the eight new occurrences are this - // slice's seam, the same two spellings TWICE, because the module carries - // two registrars (two host literals' forwarders, four; two host - // interfaces' members, four). No call site was added. - // [#23050 slice 5] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerAnalyticsEndpoints`' body (one registration site, outside - // `registerMetadataEndpoints`, so in the blind spot) moved to - // `analytics-routes.ts`, which joins the list (length 5 -> 6) with its own - // control. `this.routeManager.register(` stays 63 over the six files - // (33 here + 9 + 5 + 7 + 8 + 1), `private register*Endpoints(` stays 16 - // (the registrar is still declared here), and `enforceAuth` 80 -> 84 is - // RE-ANCHORED, not relaxed: the one call site moved with its route and is - // counted in both readings; the four new occurrences are this slice's - // seam, the same two spellings once (the host literal's forwarder, two; - // the host interface's member, two). No call site was added. - // [#23050 slice 6] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerSecurityExplainEndpoints`' and `registerSecurityEndpoints`' - // bodies (six registration sites, three and three, all outside - // `registerMetadataEndpoints`, so all six in the blind spot) moved to - // `security-routes.ts`, which joins the list (length 6 -> 7) with its own - // control. `this.routeManager.register(` stays 63 over the seven files - // (27 here + 9 + 5 + 7 + 8 + 1 + 6), `private register*Endpoints(` stays - // 16 (both registrars are still declared here), and `enforceAuth` 84 -> 92 - // is RE-ANCHORED, not relaxed: the five call sites moved with their routes - // and are counted in both readings; the eight new occurrences are this - // slice's seam, the same two spellings TWICE, because the module carries - // two registrars (two host literals' forwarders, four; two host - // interfaces' members, four). No call site was added. - // [#23050 slice 7] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerDiscoveryEndpoints`' and `registerOpenApiEndpoints`' bodies - // (four registration sites, two and two — the API root and `/discovery`, - // `/openapi.json` and `/docs` — all outside `registerMetadataEndpoints`, so - // all four in the blind spot) moved to `discovery-routes.ts`, which joins - // the list (length 7 -> 8) with its own control. - // `this.routeManager.register(` stays 63 over the eight files - // (23 here + 9 + 5 + 7 + 8 + 1 + 6 + 4), `private register*Endpoints(` - // stays 16 (both registrars are still declared here), and `enforceAuth` - // 92 -> 96 is RE-ANCHORED, not relaxed: the OpenAPI pair's two call sites - // moved with their routes and are counted in both readings; the four new - // occurrences are this slice's seam, the two spellings ONCE, because only - // the OpenAPI body reads `enforceAuth` (its host literal's forwarder, two; - // its host interface's member, two) — the discovery document is served to - // anyone, so its literal forwards no `enforceAuth`. No call site was added. - // [#23050 slice 8] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerEmailEndpoints`' body (one registration site, `POST - // {base}/email/send`, outside `registerMetadataEndpoints`, so in the blind - // spot) moved to `email-routes.ts`, which joins the list (length 8 -> 9) - // with its own control. `this.routeManager.register(` stays 63 over the - // nine files (22 here + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1), - // `private register*Endpoints(` stays 16 (the registrar is still declared - // here), and `enforceAuth` 96 -> 100 is RE-ANCHORED, not relaxed: the one - // call site moved with its route and is counted in both readings; the four - // new occurrences are this slice's seam, the same two spellings once (the - // host literal's forwarder, two; the host interface's member, two). No call - // site was added. - // [#23050 slice 9] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerSearchEndpoints`' body (one registration site, `GET - // {base}/search`, outside `registerMetadataEndpoints`, so in the blind spot) - // moved to `search-routes.ts`, which joins the list (length 9 -> 10) with - // its own control. `this.routeManager.register(` stays 63 over the ten - // files (21 here + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1), - // `private register*Endpoints(` stays 16 (the registrar is still declared - // here), and `enforceAuth` 100 -> 104 is RE-ANCHORED, not relaxed: the one - // call site moved with its route and is counted in both readings; the four - // new occurrences are this slice's seam, the same two spellings once (the - // host literal's forwarder, two; the host interface's member, two). No call - // site was added. - // [#23050 slice 10] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerUiEndpoints`' body (one registration site, `GET - // {base}/ui/view/:object/:type`, outside `registerMetadataEndpoints`, so in - // the blind spot) moved to `ui-routes.ts`, which joins the list (length - // 10 -> 11) with its own control. `this.routeManager.register(` stays 63 - // over the eleven files (20 here + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1), - // `private register*Endpoints(` stays 16 (the registrar is still declared - // here), and `enforceAuth` 104 -> 108 is RE-ANCHORED, not relaxed: the one - // call site moved with its route and is counted in both readings; the four - // new occurrences are this slice's seam, the same two spellings once (the - // host literal's forwarder, two; the host interface's member, two). No call - // site was added. - // [#23050 slice 11] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerFormEndpoints`' body (two registration sites, `GET - // {base}/forms/:slug` and `POST {base}/forms/:slug/submit`, outside - // `registerMetadataEndpoints`, so in the blind spot) moved to - // `form-routes.ts`, which joins the list (length 11 -> 12) with its own - // control. `this.routeManager.register(` stays 63 over the twelve files - // (18 here + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1 + 2), - // `private register*Endpoints(` stays 16 (the registrar is still declared - // here), and `enforceAuth` stays 108: the two doors never call it (their - // authorization is the form's own declaration), so no call site moved, and - // this slice's host literal and interface name no such member. Measured at - // the move commit, before the list learned the module: population 68 and - // `this.routeManager.register(` 61 over eleven files, the two sites out of - // reach. - // [#23050 slice 12] 70 / 19 / 51 UNCHANGED, the same measurement again: - // `registerCrudEndpoints`' body (six registration sites, the list, read, - // create, QueryAST query, update and delete doors under - // `{base}{crud.dataPrefix}`, all outside `registerMetadataEndpoints`, so - // all six in the blind spot) moved to `crud-routes.ts`, which joins the list - // (length 12 -> 13) with its own control. `this.routeManager.register(` - // stays 63 over the thirteen files - // (12 here + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1 + 2 + 6), - // `private register*Endpoints(` stays 16 (the registrar is still declared - // here), and `enforceAuth` 108 -> 112 is RE-ANCHORED, not relaxed: the six - // call sites moved with their routes and are counted in both readings; the - // four new occurrences are this slice's seam, the same two spellings once - // (the host literal's forwarder, two; the host interface's member, two). No - // call site was added. - // [#23050 slice 13a] 70 / 19 / 51 UNCHANGED, and this time `reachable` is the - // reading that had to be taught: the first three `/meta` groups of - // `registerMetadataEndpointsInner` (six direct registration sites: `GET - // {metaPath}` and `/types`, `/diagnostics`, `/_drafts`, `POST - // /_migrate-stored` and `GET /:type`, all REACHABLE through the umbrella key - // `meta:rest-server.ts:registerMetadataEndpoints`) moved to - // `meta-collection-routes.ts`, which joins the list (length 13 -> 14) with - // its own control. The six still register inside the umbrella's swap — the - // inner method builds their host literal, capturing the wrapping registrar - // by value, and calls the module at their old position — but no line of - // them is lexically inside `registerMetadataEndpoints` any more, so the - // lexical rule alone read 70 / 13 / 57: six guarded routes counted as blind - // spots, a false reading. Measured, not argued, before the rule widened: an - // anonymous request at each of the six (both bases) answers `401` with the - // module in place, and hoisting the route manager's capture above the swap - // makes every one of them answer without the gate. The rule therefore adds - // every site in `REST_SERVER_META_ROUTE_FILES`, the list's NAMED `/meta` - // subset — ⛔ never a file-name convention — and the new control - // `/meta subset ↔ .call( inside registerMetadataEndpointsInner` holds that - // subset's length against the module calls inside the inner method, so a - // `/meta` module called without joining the subset (or a subset entry the - // inner method no longer calls) moves it. `this.routeManager.register(` - // stays 63 over the fourteen files - // (6 here + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1 + 2 + 6 + 6), - // `private register*Endpoints(` stays 16, `registerPerItemRoute(` stays 8 and - // `enforceAuth` stays 112: the moved groups spell neither term, and the seam - // spells neither (the umbrella's wrapper is what gates them). - // [#23050 slice 13b] 70 / 19 / 51 UNCHANGED, and `reachable` holds by the rule - // 13a taught, with no rule change: the fourth `/meta` group, the per-item reads - // (four direct registration sites: `/references`, `/layers`, the book tree and - // the item read, all REACHABLE through the same umbrella key), moved to - // `meta-item-read-routes.ts`, which joins the list (length 14 -> 15) and the - // named `/meta` subset (1 -> 2) with its own control. The inner method builds - // its host literal inside the umbrella's swap and calls the module at the - // group's old position, so the subset's length and the module calls inside the - // inner method move together (both 2). Measured at the move commit, before the - // list learned the module: population 66 and `this.routeManager.register(` 59 - // over fourteen files, the four sites out of reach. `this.routeManager.register(` stays 63 - // over the fifteen files (2 here + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1 + 2 + - // 6 + 6 + 4), `private register*Endpoints(` stays 16, `registerPerItemRoute(` - // stays 8 and `enforceAuth` stays 112: the moved group spells neither term, - // and neither does the seam. - // [#23050 slice 13c] 70 / 19 / 51 UNCHANGED, `reachable` again held by 13a's - // rule with no rule change, and TWO controls re-keyed, as ruled for this - // slice. The fifth `/meta` group, the per-item writes (`PUT` and `DELETE - // {metaPath}/:type/:name`, two helper-routed registration sites, REACHABLE - // through the same umbrella key), moved to `meta-item-write-routes.ts`, which - // joins the list (length 15 -> 16) and the named `/meta` subset (2 -> 3). It - // is the first module on the list with NO direct site: both of its routes - // register through the inner method's per-item registrar, which reaches it in - // the `/meta` scope as the one closure the inner head declares and which - // still reads the route manager at call time, inside the swap. So (1) its own - // control counts `registerPerItemRoute(` (2), the population rule's second - // spelling, instead of `this.routeManager.register(`, which reads 0 there and - // would be a zero read as a reading; and (2) the `registerPerItemRoute(` - // control is RE-ANCHORED, not relaxed, over the same haystack as - // `this.routeManager.register(` and the population rule (this file plus every - // module on the list), where it stays 8 (6 here + 2 in the module); over this - // file alone it would read 6. The subset's length and the module calls - // inside the inner method move together (both 3). Measured at the move - // commit, before the list learned the module: population 68, the two sites - // out of reach, and the `.call(` control at 3 against a subset of 2. - // `this.routeManager.register(` stays 63 over the sixteen files (the module - // spells it nowhere), `private register*Endpoints(` stays 16, - // `const registerPerItemRoute =` stays 1 and `enforceAuth` stays 112: the - // moved group spells none of them, and neither does the seam. - // [#23050 slice 13d] 70 / 19 / 51 UNCHANGED, `reachable` held by 13a's rule - // and every control keyed as 13c keyed them, with no rule change. The sixth - // `/meta` group, the history family (`/history`, `/audit`, `/publish`, - // `/rollback` and `/diff`, five helper-routed registration sites, REACHABLE - // through the same umbrella key), moved to `meta-item-history-routes.ts`, - // which joins the list (length 16 -> 17) and the named `/meta` subset - // (3 -> 4). Like the per-item writes' module it has NO direct site, so its - // own control counts `registerPerItemRoute(` (5), and the whole-population - // `registerPerItemRoute(` control stays 8 over this file plus every module on - // the list (1 here, the published read's, + 2 + 5); over this file alone it - // would now read 1. The subset's length and the module calls inside the - // inner method move together (both 4). Measured at the move commit, before - // the list learned the module: population 65, the five sites out of reach, - // and the `.call(` control at 4 against a subset of 3. - // `this.routeManager.register(` stays 63 over the seventeen files (the module - // spells it nowhere), `private register*Endpoints(` stays 16, - // `const registerPerItemRoute =` stays 1, `forwarder slice:` stays 1 and - // `enforceAuth` stays 112: the moved group spells none of them, and neither - // does the seam. - // [#23050 slice 13e] 70 / 19 / 51 UNCHANGED, `reachable` held by 13a's rule - // with no rule change, and ONE control re-keyed. The seventh `/meta` group, - // the object state-machine read (one DIRECT registration site, in no - // per-family switch), moved to `meta-object-state-routes.ts`, which joins the - // list (length 17 -> 18) and the named `/meta` subset (4 -> 5) with its own - // control on `this.routeManager.register(` (1). The eighth and last, the - // published read (one helper-routed site), joined `meta-item-history-routes.ts`, - // whose own control goes 5 -> 6, and the whole-population - // `registerPerItemRoute(` control stays 8 over this file plus every module on - // the list (0 here + 2 + 6). Measured at the move commit, before the list - // learned the new module: population 69, the state read's site out of reach. - // RE-KEYED, not relaxed: the inner method now calls the history module TWICE - // (the history family, and the published read at its own position after the - // state read), so `.call(` inside the inner method reads 6 against a subset of - // 5 files, and is recorded as the subset's length PLUS that one second call; - // a `/meta` module called without joining the subset, or a subset entry no - // longer called, still moves it. `this.routeManager.register(` stays 63 over - // the eighteen files (one site moved from here to the new module), - // `private register*Endpoints(` stays 16, `const registerPerItemRoute =` - // stays 1, `forwarder slice:` stays 1 and `enforceAuth` stays 112: the moved - // groups spell none of the last four, and neither does the seam. - // [#25104] 70 / 19 / 51 -> 69 / 19 / 50, and `enforceAuth` 112 -> 111: the - // OpenAPI document's `/docs` viewer page was deleted with its handler, one - // direct `this.routeManager.register(` site (63 -> 62; 4 -> 3 in - // `discovery-routes.ts`) inside `registerOpenApiEndpoints`, NOT inside - // `registerMetadataEndpoints`, so it was a blind spot; its handler opened - // with `enforceAuth`, so that control drops by one. The registrar survives - // for the document, so `private register*Endpoints(` stays 16. - // [credential-environment comparison in `enforceAuth`] `enforceAuth` - // 111 -> 115, and nothing else on this row moved. ⛔ RE-ANCHORED, not - // relaxed: four PROSE mentions and no call site. They are the docblock of - // the new `credentialAnchoredElsewhere` predicate (`rest-server.ts:1783`), - // the anchored-elsewhere case in `enforceEnvironmentOwnership`'s docblock - // (`:2545`) and the two inline comments on the internal context keys in - // `computeExecCtx` (`:3444`, `:3454`). `enforceAuth`'s own body gained a - // comparison in lines that do not spell the term. - // [#26400] 69 / 19 / 50 -> 70 / 19 / 51, and `enforceAuth` 115 -> 116: the - // approval record-lock read (`GET {basePath}/approvals/records/:object/ - // :recordId/lock`, landed in #26361) is one new direct - // `this.routeManager.register(` site (62 -> 63; 7 -> 8 in - // `approvals-routes.ts`) inside `registerApprovalsEndpoints`, NOT inside - // `registerMetadataEndpoints`, so it is a NEW BLIND SPOT, the same as its - // seven siblings; its handler opens with `enforceAuth`, so that control - // gains one. No registrar was added, so `private register*Endpoints(` - // stays 16 and the registrar list stays 18. - controls: { - 'private register*Endpoints(': 16, - 'this.routeManager.register(': 63, - 'registrar list length': 18, - 'data-action-routes.ts: this.routeManager.register(': 9, - 'batch-routes.ts: this.routeManager.register(': 5, - 'approvals-routes.ts: this.routeManager.register(': 8, - 'sharing-routes.ts: this.routeManager.register(': 8, - 'analytics-routes.ts: this.routeManager.register(': 1, - 'security-routes.ts: this.routeManager.register(': 6, - 'discovery-routes.ts: this.routeManager.register(': 3, - 'email-routes.ts: this.routeManager.register(': 1, - 'search-routes.ts: this.routeManager.register(': 1, - 'ui-routes.ts: this.routeManager.register(': 1, - 'form-routes.ts: this.routeManager.register(': 2, - 'crud-routes.ts: this.routeManager.register(': 6, - 'meta-collection-routes.ts: this.routeManager.register(': 6, - 'meta-item-read-routes.ts: this.routeManager.register(': 4, - // [#23050 slice 13c] Controlled on the helper's call spelling: this module - // has no direct site (see above). - 'meta-item-write-routes.ts: registerPerItemRoute(': 2, - // [#23050 slice 13d] The same: the history family's module has no direct site. - // [#23050 slice 13e] 5 -> 6: the published read joined it, helper-routed too. - 'meta-item-history-routes.ts: registerPerItemRoute(': 6, - // [#23050 slice 13e] The object state-machine read registers directly. - 'meta-object-state-routes.ts: this.routeManager.register(': 1, - // [#23050 slice 13a] Recorded AS the subset's length, not as a literal: the - // measured side is the module calls inside the inner method, so the two - // can only move together. - // [#23050 slice 13e] …PLUS ONE: `meta-item-history-routes.ts` is the one - // module the inner method calls at two positions (see above). - '/meta subset ↔ .call( inside registerMetadataEndpointsInner': REST_SERVER_META_ROUTE_FILES.length + 1, - // Both halves of the new rule carry their own control, so neither can go - // silently to zero: a helper deleted and its routes inlined back would - // still read population 80, and only these two controls would notice the - // shape moved and force this provenance to be re-read. - 'registerPerItemRoute(': 8, - 'const registerPerItemRoute =': 1, - 'forwarder slice: this.routeManager.register(': 1, - enforceAuth: 116, - }, - note: - 'The single non-tripwire probe names ONE registrar of 16. The other 15 can never mint a key: ' + - 'registerCrudEndpoints, registerApprovalsEndpoints, registerDataActionEndpoints, ' + - 'registerSharingRuleEndpoints, registerUiEndpoints and the rest. A runtime mount census reads 76/19/57. ' + - 'registerUiEndpoints is NOT special — it is simply the registrar a census happened to walk past.', - }, - { - file: 'packages/runtime/src/http-dispatcher.ts', - kinds: ['ROUTE_ENUMERATION', 'TRIPWIRE'], - probes: 3, - keys: 2, - population: 15, - reachable: 2, - blindSpot: 13, - populationRule: '`async handle*(` methods; reachable = handleMetadata + handleMcp, the two named by probes', - controls: { 'async handleMetadata(': 1, 'async handleMcp(': 1, HttpDispatcherResult: 18 }, - note: - 'The exclusion is ON RECORD in the probe comment ("curated NAME only, NOT handleAI / handleData / ' + - 'handleSecurity, which are separate surfaces/rows") — but "separate rows" is not "separate ratcheted ' + - 'keys": none of the other 13 handlers carries a covers key, so a new ungated sibling beside them mints ' + - 'nothing and breaks nothing.', - }, - { - file: 'packages/runtime/src/domains/actions.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleActionsRequest: 2 }, - note: 'Whole domain funnels through one handler whose FIRST statement is the pinned gate. The pin is complete for this file.', - }, - { - file: 'packages/runtime/src/domains/automation.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleAutomationRequest: 2 }, - note: 'Same shape as domains/actions.ts.', - }, - { - file: 'packages/runtime/src/domains/packages.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handlePackagesRequest: 2 }, - note: 'Same shape as domains/actions.ts.', - }, - { - // [#21061] The analytics domain joined the anonymous-deny floor; the gate is - // the handler's first statement, so this row has the actions.ts shape. Its - // handler name occurs 3 times: the declaration, the DomainRoute call site - // and the file docblock's link to it. - file: 'packages/runtime/src/domains/analytics.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleAnalyticsRequest: 3 }, - note: 'Same shape as domains/actions.ts.', - }, - { - // [#22432] The `/i18n` domain joined the anonymous-deny floor; the gate is - // the handler's first statement, so this row has the actions.ts shape. Its - // handler name occurs 4 times: the declaration, the DomainRoute call site, - // the file docblock's link to it and the cache-directive docblock's link - // to it. Its name carries digits, which is why the population rule's - // handler-name class admits them. - // - // [#25988] 3 -> 4, re-read rather than bumped: the fourth is the - // `{@link handleI18nRequest}` in `TRANSLATIONS_CACHE_CONTROL`'s docblock, - // added with the translations validator (#25785). That change added three - // module-private helpers (`requestHeader`, `ifNoneMatchHits`, - // `translationsAnswer`) and no handler, route or gate call, so population, - // reach and `shouldDenyAnonymous(` are unchanged at 1 / 1 / 1. - file: 'packages/runtime/src/domains/i18n.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleI18nRequest: 4 }, - note: 'Same shape as domains/actions.ts.', - }, - { - // [ADR-0138 E2] The three domains whose anonymous decision moved from a - // face to the handler's first statement; each row has the actions.ts - // shape. The handler-name control counts every occurrence of the name as a - // substring: notifications.ts carries it twice (the declaration and the - // DomainRoute call site); keys.ts four times (those two and two comment - // mentions in the mint body); share-links.ts six times, because the body it - // delegates to is `handleShareLinksRequestBody` and two comments name it. - file: 'packages/runtime/src/domains/notifications.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleNotificationRequest: 2 }, - note: 'Same shape as domains/actions.ts.', - }, - { - file: 'packages/runtime/src/domains/keys.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleKeysRequest: 4 }, - note: 'Same shape as domains/actions.ts.', - }, - { - file: 'packages/runtime/src/domains/share-links.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleShareLinksRequest: 6 }, - note: - 'Same shape as domains/actions.ts, with the one exception ADR-0138 D2 names for this domain: the two ' + - 'token faces (class 2) are selected past the floor by the predicate that marks them public.', - }, - { - // [ADR-0138 E2, second slice] The two domains that made no anonymous - // decision of their own until it became the handler's first statement; - // each row has the actions.ts shape. The handler-name control counts the - // declaration and the DomainRoute call site, twice in each file. - file: 'packages/runtime/src/domains/ui.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleUiRequest: 2 }, - note: 'Same shape as domains/actions.ts.', - }, - { - file: 'packages/runtime/src/domains/data.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 1, - reachable: 1, - blindSpot: 0, - populationRule: '`export async function handle*Request` entry points', - controls: { 'shouldDenyAnonymous(': 1, handleDataRequest: 2 }, - note: 'Same shape as domains/actions.ts.', - }, - { - file: 'packages/runtime/src/domains/mcp.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 2, - reachable: 1, - blindSpot: 1, - populationRule: '`export async function handle*Request` entry points', - controls: { 'buildMcpBridge(deps, context)': 1, "prefix: '/": 4 }, - note: - 'Two handlers over four DomainRoute prefixes. The pin sits inside handleMcpRequest; handleMcpSkillRequest ' + - '(/mcp/skill) mints nothing — the same shape as its dispatcher twin handleMcpSkill.', - }, - { - file: 'packages/plugins/plugin-hono-server/src/hono-plugin.ts', - // Re-declared from ROUTE_ENUMERATION: the probe's population was deleted - // and the probe stayed, which is the dead-probe mechanism this census - // measured. It is re-aimed as an armed tripwire, NOT deleted — deleting it - // would make the ratchet see less. - kinds: ['TRIPWIRE'], - probes: 1, - keys: 0, - population: 6, - reachable: 0, - blindSpot: 6, - populationRule: '`rawApp.(` mount sites', - controls: { rawApp: 11, 'rawApp.get(': 3, serveStatic: 3 }, - note: - 'WAS a dead probe, dated; now a declared TRIPWIRE. Its comment claimed live discovery ("ANY new ' + - 'rawApp.(`${prefix}/data...`) mints a new key"), but that spelling occurs ZERO times in this ' + - 'file: commit e5a4d26901 (2026-07-31) deleted the plugin CRUD/discovery surface — 3 matching mounts ' + - 'before, 0 after — and the probe stayed. It minted nothing for the whole time since, in silence, ' + - 'because STALE only fires for a key some row COVERS and no row ever covered a data:hono-plugin.ts key. ' + - 'That silence is now closed generically: a non-tripwire probe minting zero keys fails as a DEAD PROBE, ' + - 'so this probe had to be either repaired or honestly re-declared, and re-declaring is what its measured ' + - 'population supports. The spelling it watches is alive one file away, in current-user-endpoints.ts ' + - '(3 mounts, none of them /data), which the table does not name — naming it is a POPULATION decision, ' + - 'recorded below and not taken here. The 6 mounts counted here are middleware, static-asset and ' + - 'SPA-fallback routes, not data surfaces.', - }, - { - file: 'packages/services/service-realtime/src/in-memory-realtime-adapter.ts', - kinds: ['GATE_PIN', 'TRIPWIRE'], - probes: 2, - keys: 1, - population: 0, - reachable: 0, - blindSpot: 0, - populationRule: 'HTTP route mounts in this file', - controls: { 'async publish(': 1, subscriptions: 12 }, - note: 'Mounts no HTTP route. The pin records the trusted-internal-only posture of the fan-out; the tripwire is correctly silent.', - }, - { - file: 'packages/services/service-realtime/src/realtime-service-plugin.ts', - kinds: ['TRIPWIRE'], - probes: 1, - keys: 0, - population: 0, - reachable: 0, - blindSpot: 0, - populationRule: 'HTTP route mounts in this file', - // ⚠️ `RealtimeService` read 10 until #14646 added a comment to that file - // recording why its occupant names no discovery channel route. The pattern - // is a bare `/RealtimeService/g`, so it matches inside `IRealtimeService` - // and PROSE about the symbol moves the symbol's count exactly as code - // does — the mirror image of a retirement whose count goes UP because the - // codebase started documenting an absence. Re-measured here rather than - // reworded there: this control's job is to prove the file is still present - // and readable (the non-zero assertion), and shrinking a comment to hold a - // counter still is how the documentation gets worse to keep a number. - // Nothing else in the row moves — the file still mounts no HTTP route, so - // population / reachable / blindSpot / keys stay 0. - controls: { RealtimeService: 11, 'async init(': 1 }, - // The designed-silence decision is the #2992 realtime-transport tripwire record. - note: 'Tripwire only. Zero keys is the designed reading: no end-user realtime transport is wired.', - }, - { - file: 'packages/client/src/realtime-api.ts', - kinds: ['TRIPWIRE'], - probes: 1, - keys: 0, - population: 0, - reachable: 0, - blindSpot: 0, - populationRule: 'HTTP route mounts in this file', - controls: { subscriptions: 14, WebSocket: 7 }, - // The arming proof is the #9083 measurement, recorded in authz-conformance.test.ts. - note: - 'Tripwire only, and the one whose arming was PROVEN: a recorded measurement wired ' + - '`new EventSource(...)` into this file and the ratchet went red as UNCLASSIFIED. Note that the client ' + - 'transport words appear in prose and types here, which is why the probe keys on `new WebSocket` / ' + - '`new EventSource` construction rather than on the bare word.', - }, - { - file: 'packages/mcp/src/plugin.ts', - kinds: ['GATE_PIN'], - probes: 1, - keys: 1, - population: 0, - reachable: 0, - blindSpot: 0, - populationRule: 'HTTP route mounts in this file', - controls: { 'resolveStdioExecutionContext(': 3, 'async start(': 1 }, - note: 'Mounts no HTTP route — the stdio transport. Pin holds the ADR-0101 principal binding.', - }, -]; - -/** - * Entry points inside the probe table's OWN files that no mintable key can - * reach, counting only the route/handler surfaces the ratchet's completeness - * claim is about: rest-server.ts (51 static / 56 runtime), http-dispatcher.ts - * (13) and domains/mcp.ts (1). - * - * hono-plugin.ts's 6 mounts are deliberately EXCLUDED from this total and - * reported beside it: they are middleware and static-asset routes, and folding - * them in would overstate the data surface. Its real finding is the dead probe, - * not the six. - */ -// [#20102] 75 / 80 -> 67 / 72: the retired saved-report routes left rest-server.ts. -// [#21180] 67 / 72 -> 66 / 71: the retired anonymous lookup-picker route left -// rest-server.ts as one blind-spot call site and one runtime mount (measured: -// `RestServer.getRoutes()` against a bare protocol, 73 mounts -> 72). -// [ADR-0131 cutover, stage 7-pre] 66 / 71 -> 65 / 70: the retired permission-set -// overlay-discard route left rest-server.ts as one blind-spot call site and -// one runtime mount. -// [#25104] 65 / 70 -> 64 / 69: the retired `/docs` viewer page left -// `discovery-routes.ts` (on rest-server.ts's registrar list) as one blind-spot -// call site and one runtime mount. -// [#26400] 64 / 69 -> 65 / 70: the approval record-lock read joined -// `approvals-routes.ts` (on rest-server.ts's registrar list) as one blind-spot -// call site and one runtime mount (one ledger row, 80 -> 81: a direct -// registration, not a route factory, so static and runtime move together). -export const BLIND_SPOT_TOTAL_STATIC = 65; -export const BLIND_SPOT_TOTAL_RUNTIME = 70; - -/** - * Re-measure every row above from the same sources the probes read. - * - * `kinds` comes back from the companion test's OWN source: each `PROBES` entry - * declares its instrument kind, and reading the declarations back is what makes - * the taxonomy recorded above a checked artifact instead of a comment that can - * quietly stop describing the table. - */ -export function deriveProbeFileCensus(): { - table: ProbeTableReading; - files: Map }>; - kinds: Map; -} { - const files = new Map }>(); - - // ── the two ledger files (the population source) ──────────────────────── - // - // Scoped to the exported array literal, exactly as the probes are: the - // patterns are the ledger's own row vocabulary, so a doc-comment or a type - // declaration spelling the same tokens outside the table would inflate the - // reading. `controls` here stay WHOLE-FILE counts — they answer "is this - // still the file I think it is", which is a question about the file and not - // about the table. - // - // ⚠️ [#16306] That is the rule on every row but one. `rest-server.ts` carries - // a single SLICE-scoped control (`forwarder slice: - // this.routeManager.register(`) because its population rule SUBTRACTS a - // slice, and no whole-file count can see that slice grow — the four - // whole-file controls on that row were measured staying green while the - // slice ran 3873 lines long. A subtracted slice needs a control on the - // slice; the exception is exactly that wide and no wider. - for (const [rel, marker, keyField] of [ - ['packages/rest/src/rest-route-ledger.ts', 'REST_ROUTE_LEDGER', 'family'], - ['packages/runtime/src/route-ledger.ts', 'ROUTE_LEDGER', 'domain'], - ] as ReadonlyArray) { - const src = read(rel); - const from = src.indexOf(`export const ${marker}`); - const to = from < 0 ? -1 : src.indexOf('\n];', from); - // A marker that has moved reads as an EMPTY table, never as the whole file: - // a silently wider scope would still produce plausible numbers. - const table = from < 0 || to < 0 ? '' : src.slice(from, to); - const rowRe = /route: '/g; - const keyRe = new RegExp(`${keyField}: '`, 'g'); - const entryName = keyField === 'family' ? 'RestRouteLedgerEntry' : 'RouteLedgerEntry'; - files.set(rel, { - population: occurrences(table, rowRe), - // Every row carrying the key field is reachable: each distinct value - // mints a key. A row that ever loses it shows up as a blind spot here. - reachable: occurrences(table, keyRe), - controls: { - "route: '": occurrences(src, /route: '/g), - [`${keyField}: '`]: occurrences(src, new RegExp(`${keyField}: '`, 'g')), - [entryName]: occurrences(src, new RegExp(entryName, 'g')), - }, - }); - } - - // ── rest-server.ts ────────────────────────────────────────────────────── - // - // [#15542 / #15854] TWO SPELLINGS, ONE POPULATION. A route registration in - // this file is EITHER a direct `this.routeManager.register(` call site OR a - // `registerPerItemRoute(` call — the local helper the per-item family's later - // members go through, which carries the `endpoints.item` switch and forwards - // to `this.routeManager.register(entry)`. Both are registrations; counting - // only the first spelling reads 7 short. - // - // ⛔ The helper's OWN forwarding call is NOT a registration site — it is the - // one shared mechanism 8 sites go through — so its body is sliced out before - // counting. Counting it would double-count every helper-routed route. - { - const src = read('packages/rest/src/rest-server.ts'); - // [#23050 slice 1] ONE POPULATION, NOW TWO FILES — the same lesson as the - // second spelling above. `registerDataActionEndpoints`' body moved, whole and - // unedited, to `data-action-routes.ts`: its nine `this.routeManager.register(` - // sites still register `RestServer` routes (the private registrar is still - // here and calls the moved body), so they are still this row's population. - // Reading `rest-server.ts` alone would record 61 / 19 / 42 — a nine-route hole - // in the blind spot this census keeps honest, opened by a move that changed - // no route. The moved module defines no registrar (`private register…(`) and - // no `registerPerItemRoute`, so appending it moves neither the forwarder - // slice nor the `reachable` slice; its own control below proves it is read. - // - // [#23050 slice 2] The same lesson, again: `registerBatchEndpoints`' body - // moved to `batch-routes.ts` with its five registration sites. Appended for - // the same reason and on the same terms — it declares no registrar and no - // `registerPerItemRoute` either — with its own control below. - // - // [#23050 slice 3] …and `registerApprovalsEndpoints`' body to - // `approvals-routes.ts`, with its seven. The modules are no longer named - // here one by one: they are read off THE registrar list, in its order, so - // the slice that moves the next body adds its module there once. Every - // module on it is appended on the same terms and gets its own control - // below, and the list's length is a control too. - const movedModules = REST_SERVER_REGISTRAR_FILES - .filter((file) => file !== 'rest-server.ts') - .map((file) => [file, read(`${REST_SERVER_REGISTRAR_DIR}/${file}`)] as const); - const all = [src, ...movedModules.map(([, text]) => text)].join('\n'); - const registrarRe = /^\s*private\s+register[A-Za-z]*Endpoints\s*\(/gm; - const mountRe = /this\.routeManager\.register\(/g; - // Matches the CALL sites only. The declaration reads - // `const registerPerItemRoute = (` and the docblock mentions read - // `{@link registerPerItemRoute}` — in neither is the name followed by `(`. - const helperCallRe = /registerPerItemRoute\(/g; - const helperDeclRe = /const\s+registerPerItemRoute\s*=/; - - /** - * The helper's OWN extent, bounded by its OWN indentation. - * - * ⛔ NEVER a forward search for the terminator's literal text. The rule - * this replaced ended the slice at `hay.indexOf('\n };', at)` — an - * unanchored forward search with no upper bound. Respell that terminator - * as `}` with no semicolon (the single most ordinary way that line - * changes, and nothing lints it — there is no ESLint `semi` rule in this - * repo) and `indexOf` does not fail: it finds the NEXT `\n };` - * anywhere later in the file. Measured 2026-09-08 against 44c849c7d6: the - * slice ran from line 5132 to line 9004 — 3873 lines — and swallowed 21 - * direct `this.routeManager.register(` sites. - * - * The extent ends instead at the first non-blank line indented no deeper - * than the declaration itself, whatever that line is spelled as. That is - * spelling-independent, so the respelling above moves nothing. - * - * ⛔ It is still not TRUSTED — see `sites` below. An indentation scan can - * land short (a body line dedented to the declaration's own level) or land - * long (the closing line indented deeper), so the number of forwarding - * calls it returns is read back as an exact control rather than assumed. - */ - const forwarderSlice = (hay: string): string => { - const at = hay.search(helperDeclRe); - if (at < 0) return ''; - const indent = at - (hay.lastIndexOf('\n', at) + 1); - let cursor = hay.indexOf('\n', at); - while (cursor >= 0) { - const nl = hay.indexOf('\n', cursor + 1); - const line = hay.slice(cursor + 1, nl < 0 ? hay.length : nl); - if (line.trim() !== '' && line.length - line.trimStart().length <= indent) { - return hay.slice(at, cursor + 1 + line.length); - } - if (nl < 0) break; - cursor = nl; - } - return ''; - }; - - /** - * Registration sites in one haystack: direct call sites, LESS the helper's - * own forwarding call, PLUS the helper's call sites. - * - * ⭐ THE SUBTRAHEND IS CHECKED, NOT TRUSTED, and that is the repair. - * `occurrences(forwarderSlice(hay), mountRe)` is recorded as its own exact - * control, pinned at 1 — the helper forwards exactly once. So: - * - * slice lands SHORT — declaration gone, or the extent scan stops early - * ⇒ subtrahend 0, the reading comes out ONE HIGH, and the control - * reads 0 against a recorded 1; - * slice lands LONG — the extent scan overshoots the helper's own body - * ⇒ subtrahend > 1, the reading comes out low, and the control reads - * > 1 against a recorded 1. - * - * ⛔ It never silently shrinks. Not "it cannot shrink" — it can; the word - * carrying the weight is SILENTLY. A low reading with the forwarder - * control at 1 is a real population drop; a low reading with that control - * off 1 is the slice eating too much. Before this control existed the two - * were indistinguishable — the day someone genuinely removes 20 routes the - * census reads 60 either way — and the two exact controls the spelling - * change added (`registerPerItemRoute(` = 8, - * `const registerPerItemRoute =` = 1) stay GREEN right through it, because - * the declaration is still present and still matches; only its terminator - * moved. Both legs measured, not argued. - * - * The control IS the subtrahend, which is what keeps it from being noise: - * it fires exactly when an overshoot actually distorts the reading, and - * stays at 1 through an overshoot over text that registers nothing — where - * there is no distortion to report. - * - * ⚠️ The ledger marker slice above is NOT symmetric with this one, which is - * why borrowing its "fail-loud" reasoning was the mistake. Its `\n];` - * overshoot can only ADD rows, so it reads HIGH; this slice's overshoot - * SUBTRACTS registrations, so it reads LOW — and low is the direction that - * looks like an ordinary answer. - */ - const sites = (hay: string): number => - occurrences(hay, mountRe) - - occurrences(forwarderSlice(hay), mountRe) + - occurrences(hay, helperCallRe); - - // Slice the mintable registrar's body: from its declaration to the next one. - const decls = [...src.matchAll(registrarRe)].map((m) => ({ at: m.index ?? 0, text: m[0] })); - const metaIdx = decls.findIndex((d) => d.text.includes('registerMetadataEndpoints')); - const start = decls[metaIdx]?.at ?? 0; - const end = decls[metaIdx + 1]?.at ?? src.length; - // [#23050 slice 13a] ONE REACH, NOW TWO PLACES. A `/meta` group moved out of - // `registerMetadataEndpointsInner` still registers inside the umbrella's - // swap — the inner method builds its host literal (the wrapping registrar - // captured by value) and calls its module from inside its own body — so its - // sites are as reachable as the ones still lexically inside the slice above. - // They are read off the registrar list's NAMED `/meta` subset, never off a - // file-name convention, and its control below holds that subset against the - // module calls inside the inner method (that method's own extent: from its - // declaration to the umbrella slice's end). - const metaModules = movedModules.filter(([file]) => REST_SERVER_META_ROUTE_FILES.includes(file)); - const innerAt = src.search(/private\s+registerMetadataEndpointsInner\s*\(/); - const inner = innerAt < start || innerAt >= end ? '' : src.slice(innerAt, end); - files.set('packages/rest/src/rest-server.ts', { - population: sites(all), - reachable: sites(src.slice(start, end)) + metaModules.reduce((n, [, text]) => n + sites(text), 0), - controls: { - 'private register*Endpoints(': occurrences(src, /private\s+register[A-Za-z]*Endpoints\s*\(/g), - 'this.routeManager.register(': occurrences(all, /this\.routeManager\.register\(/g), - // [#23050 slice 3] The registrar list's length, so a module added to it - // makes this row re-read its numbers. - 'registrar list length': REST_SERVER_REGISTRAR_FILES.length, - // [#23050 slice 1] The moved module's own share, so a reading that - // silently stopped appending it cannot hide behind the total above. - // [#23050 slice 3] One such control per module on the list. - // [#23050 slice 13c] …one per SPELLING the module registers through, as - // the population rule counts both: a module whose routes all go through - // the per-item registrar has no direct site, and a control that read 0 - // there would be instrument failure by this census's own rule. A - // spelling a module does not spell mints no control, so a module that - // stops spelling the one its control was recorded on makes that control - // VANISH, which is as loud as moving it. - ...Object.fromEntries(movedModules.flatMap(([file, text]) => [ - [`${file}: this.routeManager.register(`, occurrences(text, /this\.routeManager\.register\(/g)] as const, - [`${file}: registerPerItemRoute(`, occurrences(text, /registerPerItemRoute\(/g)] as const, - ].filter(([, count]) => count > 0))), - // [#23050 slice 13c] Over `all`, the population rule's own haystack, since - // a `/meta` module now carries helper call sites too (re-anchored, not - // relaxed: 6 here + 2 in `meta-item-write-routes.ts` = 8). - 'registerPerItemRoute(': occurrences(all, /registerPerItemRoute\(/g), - 'const registerPerItemRoute =': occurrences(src, /const\s+registerPerItemRoute\s*=/g), - // ⭐ The one control here that is NOT a whole-file count, deliberately: - // it is the SHAPE of the slice the population rule subtracts, and it is - // the only reading that can tell "the slice ate too much" apart from a - // real population drop. The four counts around it cannot — all four are - // green while the slice is running 3873 lines long. - 'forwarder slice: this.routeManager.register(': occurrences(forwarderSlice(src), mountRe), - // [#23050 slice 13a] The module calls inside the inner method — one per - // moved `/meta` group's module — recorded against the subset's length. - // [#23050 slice 13e] Two for `meta-item-history-routes.ts`, recorded as such. - '/meta subset ↔ .call( inside registerMetadataEndpointsInner': occurrences(inner, /\.call\(/g), - enforceAuth: occurrences(all, /enforceAuth/g), - }, - }); - } - - // ── http-dispatcher.ts ────────────────────────────────────────────────── - { - const src = read('packages/runtime/src/http-dispatcher.ts'); - files.set('packages/runtime/src/http-dispatcher.ts', { - population: occurrences(src, /^\s+async\s+handle[A-Za-z]*\s*\(/gm), - // handleMetadata + handleMcp, the two the PROBES table names by name. - reachable: - occurrences(src, /async\s+handleMetadata\s*\(/g) + occurrences(src, /async\s+handleMcp\s*\(/g), - controls: { - 'async handleMetadata(': occurrences(src, /async\s+handleMetadata\s*\(/g), - 'async handleMcp(': occurrences(src, /async\s+handleMcp\s*\(/g), - HttpDispatcherResult: occurrences(src, /HttpDispatcherResult/g), - }, - }); - } - - // ── the ten runtime domain files whose floor is a GATE_PIN ─────────────── - // - // [#22432] The handler-name class admits digits: `handleI18nRequest` is a - // `handle*Request` entry point like the others, and a letters-only class - // read it as zero — a population of 0 under a reach of 1. No other file's - // count moves with the wider class (none of their handler names carries a - // digit; re-derived). - const domains: Array<[string, string, string]> = [ - ['packages/runtime/src/domains/actions.ts', 'shouldDenyAnonymous(', 'handleActionsRequest'], - ['packages/runtime/src/domains/automation.ts', 'shouldDenyAnonymous(', 'handleAutomationRequest'], - ['packages/runtime/src/domains/packages.ts', 'shouldDenyAnonymous(', 'handlePackagesRequest'], - ['packages/runtime/src/domains/analytics.ts', 'shouldDenyAnonymous(', 'handleAnalyticsRequest'], - ['packages/runtime/src/domains/i18n.ts', 'shouldDenyAnonymous(', 'handleI18nRequest'], - ['packages/runtime/src/domains/notifications.ts', 'shouldDenyAnonymous(', 'handleNotificationRequest'], - ['packages/runtime/src/domains/keys.ts', 'shouldDenyAnonymous(', 'handleKeysRequest'], - ['packages/runtime/src/domains/share-links.ts', 'shouldDenyAnonymous(', 'handleShareLinksRequest'], - ['packages/runtime/src/domains/ui.ts', 'shouldDenyAnonymous(', 'handleUiRequest'], - ['packages/runtime/src/domains/data.ts', 'shouldDenyAnonymous(', 'handleDataRequest'], - ]; - for (const [rel, gate, handler] of domains) { - const src = read(rel); - files.set(rel, { - population: occurrences(src, /^export async function handle[A-Za-z0-9]+Request/gm), - reachable: 1, - controls: { - [gate]: occurrences(src, /shouldDenyAnonymous\s*\(/g), - [handler]: occurrences(src, new RegExp(handler, 'g')), - }, - }); - } - { - const src = read('packages/runtime/src/domains/mcp.ts'); - files.set('packages/runtime/src/domains/mcp.ts', { - population: occurrences(src, /^export async function handle[A-Za-z0-9]+Request/gm), - reachable: 1, - controls: { - 'buildMcpBridge(deps, context)': occurrences(src, /buildMcpBridge\(deps, context\)/g), - "prefix: '/": occurrences(src, /prefix: '\//g), - }, - }); - } - - // ── hono-plugin.ts ────────────────────────────────────────────────────── - { - const src = read('packages/plugins/plugin-hono-server/src/hono-plugin.ts'); - files.set('packages/plugins/plugin-hono-server/src/hono-plugin.ts', { - population: occurrences(src, /rawApp\.(?:get|post|put|patch|delete|all|use|on)\(/g), - reachable: 0, - controls: { - rawApp: occurrences(src, /rawApp/g), - 'rawApp.get(': occurrences(src, /rawApp\.get\(/g), - serveStatic: occurrences(src, /serveStatic/g), - }, - }); - } - - // ── the four files that mount no HTTP route ───────────────────────────── - const noMount: Array<[string, Record]> = [ - ['packages/services/service-realtime/src/in-memory-realtime-adapter.ts', { 'async publish(': /async\s+publish\s*\(/g, subscriptions: /subscriptions/g }], - ['packages/services/service-realtime/src/realtime-service-plugin.ts', { RealtimeService: /RealtimeService/g, 'async init(': /async\s+init\s*\(/g }], - ['packages/client/src/realtime-api.ts', { subscriptions: /subscriptions/g, WebSocket: /WebSocket/g }], - ['packages/mcp/src/plugin.ts', { 'resolveStdioExecutionContext(': /resolveStdioExecutionContext\s*\(/g, 'async start(': /async\s+start\s*\(/g }], - ]; - for (const [rel, controlSpec] of noMount) { - const src = read(rel); - const controls: Record = {}; - for (const [term, re] of Object.entries(controlSpec)) controls[term] = occurrences(src, re); - files.set(rel, { - population: - occurrences(src, /routeManager\.register\(/g) + - occurrences(src, /\b(?:app|rawApp)\.(?:get|post|put|patch|delete)\(/g), - reachable: 0, - controls, - }); - } - - // ── the PROBES table's own shape, read from the companion test's source ── - const testSrc = readFileSync(join(HERE, 'authz-conformance.test.ts'), 'utf8'); - const from = testSrc.indexOf('const PROBES'); - const to = testSrc.indexOf('\n];', from); - const block = testSrc.slice(from, to); - const probeFiles = [...block.matchAll(/^\s*file: '([^']+)'/gm)].map((m) => m[1]); - - // Kind ↔ file pairing, read in DOCUMENT ORDER. Every entry spells `kind` - // immediately before `file`, so walking both tokens in order pairs them - // without parsing TypeScript. The pairing is self-checking: if the two counts - // ever disagree, an entry is missing one of them and the census test's - // `entries` assertion catches it rather than a silently short map. - const kinds = new Map(); - const tokens = [...block.matchAll(/^\s*(kind|file): '([^']+)'/gm)]; - let pendingKind: ProbeKind | undefined; - for (const t of tokens) { - if (t[1] === 'kind') { pendingKind = t[2] as ProbeKind; continue; } - const file = t[2]; - const list = kinds.get(file) ?? []; - if (pendingKind && !list.includes(pendingKind)) list.push(pendingKind); - kinds.set(file, list.sort()); - pendingKind = undefined; - } - const matrixSrc = readFileSync(join(HERE, 'authz-conformance.matrix.ts'), 'utf8'); - const coverKeys = [...matrixSrc.matchAll(/covers: \[([^\]]*)\]/g)] - .flatMap((m) => [...m[1].matchAll(/'([^']+)'/g)].map((k) => k[1])); - - return { - table: { entries: probeFiles.length, files: new Set(probeFiles).size, keys: new Set(coverKeys).size }, - files, - kinds, - }; -} diff --git a/packages/qa/dogfood/test/authz-probe-blind-spot.test.ts b/packages/qa/dogfood/test/authz-probe-blind-spot.test.ts index 17f6b82c08d..21c98ed70c4 100644 --- a/packages/qa/dogfood/test/authz-probe-blind-spot.test.ts +++ b/packages/qa/dogfood/test/authz-probe-blind-spot.test.ts @@ -1,150 +1,209 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// Holds `authz-probe-blind-spot.census.ts` equal to what the probe table's own -// source files actually say today. +// The reach of `authz-conformance.test.ts`'s `discover()`, DERIVED from the +// route ledgers — never transcribed. // -// ⛔ This is NOT a second authorization gate and it classifies nothing. It is a -// MEASUREMENT PIN: it fails when the reach of `authz-conformance.test.ts`'s -// `discover()` changes in either direction — a probe added, a registrar added, -// a dispatcher handler added, a dead probe revived — so the census is re-read -// by whoever moved it instead of aging into a stale claim about a stale table. +// ⛔ This is NOT a second authorization gate and it classifies nothing. The +// gate is `discover()`: a ledger family or dispatcher domain no matrix row +// covers is UNCLASSIFIED, a covered key nobody mints is STALE, and either is +// red there. What this file holds is the one property that verdict rests on: +// EVERY ledger row is reachable by a key the ledger probes can mint. A row the +// probes cannot read — its `family` / `domain` spelled through a constant, a +// spread, a template, or missing — would be a route `discover()` can never +// name, and that is the only shape that reds here. // -// ⛔ It also refuses to let a zero pass unchallenged. Every row's controls are -// terms known present IN THAT ROW'S OWN FILE; if one goes to zero the file has -// moved, been renamed or been emptied, and every zero measured against it is -// instrument failure rather than a reading. A borrowed control — a term present -// in a different file — proves nothing about this one and is why the controls -// are per row rather than shared. +// ⭐ Population and reach are both read from the ledgers themselves: the +// imported table is the population, the probe's own scoped text reading is +// the reach, and the test asserts they are the same set. No count is written +// down, so a route that arrives with a ledger row inside an existing family +// or domain moves nothing here — and a route that arrives with a NEW family +// or domain is `discover()`'s UNCLASSIFIED to classify, not this file's. +// +// ⛔ What this replaced, and why (ruling 6433001130 on the card, letter C): +// a hand-written census (`authz-probe-blind-spot.census.ts`, 1,691 lines) +// that pinned per-file registration-site counts, positive-control counts and +// two blind-spot totals, and that had to be re-measured by hand on every +// route added anywhere in `@objectstack/rest`. It went red on `main` after +// three green merges in ten days, every time for a count moving and never +// for an unclassified route. Its measured record — the three probe kinds, the +// `rest-server.ts` static vs runtime readings, and the 2026-08-31 decision to +// source the population from the ledgers — is kept in history at commit +// 67461403; the kinds now live as data on each `PROBES` entry. +// +// The route-level bound is unchanged and stated where the claim is made: a +// route added inside an existing family or domain mints no key of its own +// (`authz-conformance.matrix.ts` header). The ledgers' own conformance tests +// (`rest-route-ledger.conformance.test.ts`, `route-ledger.conformance.test.ts`) +// are what put every mounted route into a ledger row in the first place. import { describe, expect, it } from 'vitest'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; +import { REST_ROUTE_LEDGER } from '../../../rest/src/rest-route-ledger.js'; +import { ROUTE_LEDGER } from '../../../runtime/src/route-ledger.js'; import { - BLIND_SPOT_TOTAL_RUNTIME, - BLIND_SPOT_TOTAL_STATIC, - MATRIX_HEADER_PROBE_CLAIM, - PROBE_FILE_CENSUS, - PROBE_TABLE, - deriveProbeFileCensus, -} from './authz-probe-blind-spot.census.js'; + REST_SERVER_REGISTRAR_DIR, + REST_SERVER_REGISTRAR_FILES, +} from '../../../rest/src/rest-server-registrar-files.js'; const HERE = dirname(fileURLToPath(import.meta.url)); +// packages/qa/dogfood/test → repo root. +const REPO_ROOT = join(HERE, '../../../..'); -describe('authz probe blind-spot census (#13260)', () => { - const derived = deriveProbeFileCensus(); +interface LedgerReach { + /** Repo-relative ledger path, exactly as the `PROBES` table spells it. */ + file: string; + /** The exported table the ledger probe scopes its read to (`within`). */ + within: string; + /** The grouping field whose value the probe mints a key from. */ + field: 'family' | 'domain'; + /** The imported table — the population, as the ledger itself holds it. */ + rows: ReadonlyArray<{ route: string; family?: string; domain?: string }>; +} - it('the census covers exactly the files the PROBES table names', () => { - expect(PROBE_FILE_CENSUS.map((r) => r.file).sort()).toEqual([...derived.files.keys()].sort()); - expect(PROBE_FILE_CENSUS).toHaveLength(PROBE_TABLE.files); - }); +const LEDGERS: readonly LedgerReach[] = [ + { file: 'packages/rest/src/rest-route-ledger.ts', within: 'REST_ROUTE_LEDGER', field: 'family', rows: REST_ROUTE_LEDGER }, + { file: 'packages/runtime/src/route-ledger.ts', within: 'ROUTE_LEDGER', field: 'domain', rows: ROUTE_LEDGER }, +]; - it('the PROBES table still has the shape this census was measured against', () => { - expect(derived.table).toEqual(PROBE_TABLE); - }); +/** + * The body of ONE exported array literal — `export const ` up to the + * closing `\n];` — the scope the ledger probes in `authz-conformance.test.ts` + * read. ⛔ An export that is not there reads EMPTY, never the whole file: a + * silently wider scope would still produce plausible keys. + */ +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); +} - it('every classified key is accounted for — 33 `covers` keys, no more', () => { - // The matrix's `covers` keys number 33 today: the 9 probe-minted keys this - // census was first measured against, plus the 6 ledger family/domain keys - // classified when the population moved (2026-08-31), plus the `/analytics` - // domain's gate-pin key and its dispatcher-domain key (#21061), plus the - // `openapi` REST family key (#22430), plus the `/i18n` domain's gate-pin - // key and its dispatcher-domain key (#22432), plus the `/approvals/act` - // dispatcher-domain key, classified by `approval-action-token` (#22576), - // plus the `/webhooks/redeliver` dispatcher-domain key, classified by - // `webhook-redeliver-session` (#22755), plus — under ADR-0138 card E2 — - // the `/notifications`, `/keys` and `/share-links` domains' gate-pin keys - // and dispatcher-domain keys, and the `forms` REST family key, classified - // by the D2 class 1 row, plus — under its second slice — the `/ui` and - // `/data` domains' gate-pin keys and dispatcher-domain keys. If a probe - // starts minting a key no row covers, the ratchet itself goes red as - // UNCLASSIFIED — that is its job, and this pin does not duplicate it. What - // this asserts is only that the census's key count is current. - // - // ⚠️ This is NOT the size of the population. The ledgers mint 41 keys; 17 - // are classified here and 24 are enumerated in the shrink-only baseline, - // which `authz-conformance.test.ts` holds to its own four rules. - expect(PROBE_TABLE.keys).toBe(33); - }); +/** What the ledger probe reads: one `route:` and one grouping value per row, in order. */ +function probeReading(ledger: LedgerReach): { routes: string[]; values: string[] } { + const body = scopedSource(readFileSync(join(REPO_ROOT, ledger.file), 'utf8'), ledger.within); + return { + routes: [...body.matchAll(/route: '([^']+)'/g)].map((m) => m[1]!), + values: [...body.matchAll(new RegExp(`${ledger.field}: '([^']+)'`, 'g'))].map((m) => m[1]!), + }; +} - it.each(PROBE_FILE_CENSUS.map((r) => [r.file, r] as const))( - '%s — population, reach and blind spot are unchanged', - (_file, row) => { - const d = derived.files.get(row.file); - expect(d, `no derivation for ${row.file}`).toBeDefined(); - expect(d!.population).toBe(row.population); - expect(d!.reachable).toBe(row.reachable); - expect(row.blindSpot).toBe(row.population - row.reachable); +describe('authz probe reach — derived from the route ledgers', () => { + it.each(LEDGERS.map((l) => [l.file, l] as const))( + '%s — CONTROL: the table imports and the scoped read sees it', + (_file, ledger) => { + // Non-vacuity, without a count: an empty import or an empty scope would + // make every equality below hold over nothing. + expect(ledger.rows.length, `${ledger.file} imported no rows`).toBeGreaterThan(0); + expect(probeReading(ledger).routes.length, `${ledger.file}: the scoped read found no rows`).toBeGreaterThan(0); }, ); - it.each(PROBE_FILE_CENSUS.map((r) => [r.file, r] as const))( - '%s — every positive control is still present in THAT file', - (_file, row) => { - const d = derived.files.get(row.file)!; - for (const [term, recorded] of Object.entries(row.controls)) { - // Non-zero first: a zero control means the reading is instrument failure. - expect(d.controls[term], `control "${term}" vanished from ${row.file}`).toBeGreaterThan(0); - expect(d.controls[term], `control "${term}" moved in ${row.file}`).toBe(recorded); - } + it.each(LEDGERS.map((l) => [l.file, l] as const))( + '%s — every row carries a %s value the probe can mint a key from', + (_file, ledger) => { + const unreachable = ledger.rows + .filter((r) => typeof r[ledger.field] !== 'string' || r[ledger.field] === '') + .map((r) => r.route); + expect(unreachable, `rows no ${ledger.field} key can name`).toEqual([]); }, ); - it('the repo-wide totals still add up from the rows', () => { - const routeSurfaces = [ - 'packages/rest/src/rest-server.ts', - 'packages/runtime/src/http-dispatcher.ts', - 'packages/runtime/src/domains/mcp.ts', - ]; - const staticTotal = PROBE_FILE_CENSUS.filter((r) => routeSurfaces.includes(r.file)).reduce( - (n, r) => n + r.blindSpot, - 0, + it.each(LEDGERS.map((l) => [l.file, l] as const))( + '%s — the probe reads EVERY row, and reads each one as the table holds it', + (_file, ledger) => { + // The population (the imported table) and the reach (the probe's text + // reading) are compared row by row, in order. A row whose grouping value + // is not a literal the probe pattern matches drops out of `values`, so + // the two lists stop lining up and this names the first route it lost. + const { routes, values } = probeReading(ledger); + expect(routes).toEqual(ledger.rows.map((r) => r.route)); + expect(values).toEqual(ledger.rows.map((r) => r[ledger.field])); + }, + ); + + it('every TRIPWIRE file still holds its positive control — a zero there is a reading, not a moved file', () => { + // A tripwire mints nothing by design, so its zero cannot tell "nothing + // wired" from "the file this probe reads no longer holds the code". Each + // file a TRIPWIRE reads names one term known present in THAT file, and the + // term must be there. Presence only, never a count: an edit that leaves + // the file's role intact moves nothing here. + const tripwireFiles = new Set( + [...probeTableBlock().matchAll(/kind: 'TRIPWIRE',\s*\n\s*file: '([^']+)'/g)].map((m) => m[1]!), + ); + expect([...tripwireFiles].sort(), 'a TRIPWIRE file has no positive control below').toEqual( + Object.keys(TRIPWIRE_CONTROLS).sort(), + ); + const missing: string[] = []; + for (const [file, control] of Object.entries(TRIPWIRE_CONTROLS)) { + if (!control.test(readFileSync(join(REPO_ROOT, file), 'utf8'))) missing.push(`${file}: ${control}`); + } + expect(missing).toEqual([]); + }); + + it('every module on the RestServer registrar list still registers routes — the rest-server.ts tripwires read them all', () => { + // The two `rest-server.ts` tripwires read every file on this list with it + // (`alsoReads`). A module that stopped registering anything would leave + // them reading a file that no longer holds registrar code. + const empty = REST_SERVER_REGISTRAR_FILES.filter( + (file) => + !/this\.routeManager\.register\(|registerPerItemRoute\(/.test( + readFileSync(join(REPO_ROOT, REST_SERVER_REGISTRAR_DIR, file), 'utf8'), + ), ); - expect(staticTotal).toBe(BLIND_SPOT_TOTAL_STATIC); - // The runtime reading differs only for rest-server.ts (77 mounts vs 72 call - // sites — the approvals route factories and the capability-gated batch - // routes; see the census header). - expect(BLIND_SPOT_TOTAL_RUNTIME - BLIND_SPOT_TOTAL_STATIC).toBe(5); + expect(REST_SERVER_REGISTRAR_FILES.length).toBeGreaterThan(0); + expect(empty, 'registrar-list modules with no registration site').toEqual([]); }); - it('the matrix header probe count EQUALS the table — the drift is closed, not recorded', () => { + it('the matrix header states the PROBES table it describes — both sides read, neither typed here', () => { const matrix = readFileSync(join(HERE, 'authz-conformance.matrix.ts'), 'utf8'); const m = /`discover\(\)`: (\d+) probes over (\d+) named source files/.exec(matrix); expect(m, 'the matrix header no longer states a probe count in the pinned shape').not.toBeNull(); - expect(Number(m![1])).toBe(MATRIX_HEADER_PROBE_CLAIM); - expect(Number(m![2])).toBe(PROBE_TABLE.files); - // ⭐ INVERTED. This assertion used to pin the drift (`not.toBe`): the prose - // said 15 while the table held 16, and the measurement that found it left - // the sentence alone on purpose so its no-repair fence stayed unambiguous. - // The sentence is repaired, so the pin becomes the permanent invariant it - // should always have been — a probe added without moving the prose is now - // RED here instead of a discrepancy recorded in a third file. - expect(MATRIX_HEADER_PROBE_CLAIM).toBe(PROBE_TABLE.entries); - // …and the header's own text is what was read, not a hand-copied number. - expect(Number(m![1])).toBe(PROBE_TABLE.entries); - }); - - it('every probe DECLARES its instrument kind, and the census records what it declares', () => { - // The taxonomy used to live only as prose in the census. It is now data on - // each probe, read back out of the companion test's source — so a probe - // whose kind changes (a dead ROUTE_ENUMERATION honestly re-declared - // TRIPWIRE, say) cannot leave this record describing the old table. - for (const row of PROBE_FILE_CENSUS) { - const derivedKinds = derived.kinds.get(row.file); - expect(derivedKinds, `no kind declaration derived for ${row.file}`).toBeDefined(); - expect(derivedKinds).toEqual([...row.kinds].sort()); - } + const probes = probeTableFiles(); + expect(Number(m![1])).toBe(probes.length); + expect(Number(m![2])).toBe(new Set(probes).size); }); - it('every PROBES entry carries a kind — the pairing is complete, not short', () => { - // Non-vacuity for the walk above: `kinds` is built by pairing `kind` and - // `file` tokens in document order, so an entry missing its `kind` would - // silently pair with the wrong file. The total is what catches that. - const declared = [...derived.kinds.values()].flat().length; - expect(declared).toBeGreaterThanOrEqual(new Set(PROBE_FILE_CENSUS.map((r) => r.file)).size); - // All three instruments are still represented — a table that had lost one - // would make the kind-specific readings below vacuous. - const all = new Set([...derived.kinds.values()].flat()); - expect([...all].sort()).toEqual(['GATE_PIN', 'ROUTE_ENUMERATION', 'TRIPWIRE']); + it('every PROBES entry declares its instrument kind, and all three kinds are still in use', () => { + // Every entry spells `kind` immediately before `file`, so the two token + // streams pair in document order. An entry missing its `kind` makes them + // disagree, which is what this catches. + const block = probeTableBlock(); + const tokens = [...block.matchAll(/^\s*(kind|file): '([^']+)'/gm)].map((t) => t[1]); + expect(tokens, 'an entry is missing its `kind`, or spells it after its `file`').toEqual( + probeTableFiles().flatMap(() => ['kind', 'file']), + ); + const kinds = new Set([...block.matchAll(/^\s*kind: '([^']+)'/gm)].map((t) => t[1])); + // A table that had lost one instrument would make that instrument's + // integrity checks in `authz-conformance.test.ts` vacuous. + expect([...kinds].sort()).toEqual(['GATE_PIN', 'ROUTE_ENUMERATION', 'TRIPWIRE']); }); }); + +/** + * One in-file positive control per file a TRIPWIRE probe names: a term known + * present in THAT file (never borrowed from another), so the tripwire's zero + * is a reading of the file and not of its absence. + */ +const TRIPWIRE_CONTROLS: Readonly> = { + 'packages/plugins/plugin-hono-server/src/hono-plugin.ts': /rawApp\./, + 'packages/services/service-realtime/src/in-memory-realtime-adapter.ts': /subscriptions/, + 'packages/services/service-realtime/src/realtime-service-plugin.ts': /RealtimeService/, + 'packages/runtime/src/http-dispatcher.ts': /HttpDispatcherResult/, + 'packages/client/src/realtime-api.ts': /subscriptions/, + 'packages/rest/src/rest-server.ts': /this\.routeManager\.register\(/, +}; + +/** The `PROBES` array literal in the companion test's own source. */ +function probeTableBlock(): string { + const src = readFileSync(join(HERE, 'authz-conformance.test.ts'), 'utf8'); + const from = src.indexOf('const PROBES'); + const to = from < 0 ? -1 : src.indexOf('\n];', from); + return from < 0 || to < 0 ? '' : src.slice(from, to); +} + +/** One entry per `PROBES` entry: the file it names. */ +function probeTableFiles(): string[] { + return [...probeTableBlock().matchAll(/^\s*file: '([^']+)'/gm)].map((m) => m[1]!); +} diff --git a/packages/rest/src/rest-meta-auth.test.ts b/packages/rest/src/rest-meta-auth.test.ts index 3623f123ef0..ae2724609dd 100644 --- a/packages/rest/src/rest-meta-auth.test.ts +++ b/packages/rest/src/rest-meta-auth.test.ts @@ -78,8 +78,8 @@ describe('RestServer metadata routes — anonymous-deny gate (#3963)', () => { // captured `this.routeManager` at definition time would register all eight // past the gate with every existing test still green. // - // It is also what licenses `authz-probe-blind-spot.census.ts` counting those - // 8 as REACHABLE by `meta:rest-server.ts:registerMetadataEndpoints`. + // It is also what licenses counting those 8 as REACHABLE by the dogfood + // authz ratchet's `meta:rest-server.ts:registerMetadataEndpoints` key. it('401s an anonymous helper-routed per-item route — the gate travels with `registerPerItemRoute` (#15542)', async () => { const protocol: any = { historyMetaItem: vi.fn().mockResolvedValue({ events: [] }) }; const rest = new RestServer(makeServer() as any, protocol, {} as any); diff --git a/packages/rest/src/rest-route-ledger.ts b/packages/rest/src/rest-route-ledger.ts index 7a0ab35283e..b6dd2271294 100644 --- a/packages/rest/src/rest-route-ledger.ts +++ b/packages/rest/src/rest-route-ledger.ts @@ -130,9 +130,10 @@ export interface RestRouteLedgerEntry { * identity, was guarded. Recorded so the next reader does not re-derive a * figure that has now been checked. ⛔ The rejection stands either way, and * the second spelling strengthens it — a syntactic scanner has to know both - * before it can read the file even this badly. The full reading lives in - * `packages/qa/dogfood/test/authz-probe-blind-spot.census.ts`, the - * authority on this population. + * before it can read the file even this badly. The full reading is in + * `packages/qa/dogfood/test/authz-probe-blind-spot.census.ts` at commit + * 67461403; that census was retired after it for a reach check derived + * from this ledger (`authz-probe-blind-spot.test.ts` beside it). * * ABSENT MEANS "UNDECLARED", and that is the state of nearly the whole * surface. This field is filled INCREMENTALLY, exactly like `responseSchema` diff --git a/packages/runtime/src/route-ledger.ts b/packages/runtime/src/route-ledger.ts index 46d8d419a16..f7c0f1e2c22 100644 --- a/packages/runtime/src/route-ledger.ts +++ b/packages/runtime/src/route-ledger.ts @@ -182,9 +182,10 @@ export interface RouteLedgerEntry { * identity, was guarded. Recorded so the next reader does not re-derive a * figure that has now been checked. ⛔ The rejection stands either way, and * the second spelling strengthens it — a syntactic scanner has to know both - * before it can read the file even this badly. The full reading lives in - * `packages/qa/dogfood/test/authz-probe-blind-spot.census.ts`, the - * authority on this population. + * before it can read the file even this badly. The full reading is in + * `packages/qa/dogfood/test/authz-probe-blind-spot.census.ts` at commit + * 67461403; that census was retired after it for a reach check derived + * from this ledger (`authz-probe-blind-spot.test.ts` beside it). * * ABSENT MEANS "UNDECLARED", and that is the state of nearly the whole * surface. This field is filled INCREMENTALLY, exactly like `responseSchema`