Skip to content

Commit 98c0257

Browse files
test(dogfood): derive the authz probe reach from the route ledgers; retire the hand-written census (#26491)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi99G8ApwiZzLyCouRQisBjZd)_ Fixes #26410 Clause-②: no Metadata-protocol: none Ruling-ref: 6433001130 ## 维护者速读 - 改了什么:删掉 dogfood 里 1,691 行手写的权限普查表 `authz-probe-blind-spot.census.ts`,`authz-probe-blind-spot.test.ts` 改成直接从两张路由账本推导:只有账本里出现一行 `discover()` 读不到的路由才红,不再钉任何条数。matrix 注释里两本账的「N rows」也去掉,只留 families / domains 数,#17111 的钉相应只钉组数。 - 为什么改:10 天里 3 次 PR 绿着合进去、main 才红,全是「加了一条路由、条数没同步」。按裁决 C:普查从账本派生,纯数字变动不再红;`discover()` 的 UNCLASSIFIED / STALE 棘轮不动,也不加任何 CI 车道。 - 风险与回滚:只动测试文件和三处注释,不动任何路由代码和发布内容;回滚即 revert。代价是不再记录 rest-server.ts 注册点的盲点读数(那份记录留在 commit 6746140 的历史里)。 - 先看:`packages/qa/dogfood/test/authz-probe-blind-spot.test.ts`(新的推导检查),其次 `authz-conformance.test.ts` 的 #17111 段,再看 `authz-conformance.matrix.ts` 第 27 行一带。 - 两分钟验证:`pnpm --filter @objectstack/dogfood exec vitest run test/authz-probe-blind-spot.test.ts test/authz-conformance.test.ts`,预期 2 个文件、70 个测试全绿。 - 需要你做的:无,席位落地。 ## What changes **C, as ruled.** The hand-written census `packages/qa/dogfood/test/authz-probe-blind-spot.census.ts` (1,691 lines) is deleted. It pinned per-file registration-site counts, exact positive-control counts, the probe-table shape (25 / 20 / 33) and two blind-spot totals (65 / 70), so every route added anywhere in `@objectstack/rest` had to be re-measured by hand. If it was not, `main` went red after a green merge. `authz-probe-blind-spot.test.ts` is rewritten to derive everything and pin no count: - **Population and reach come from the ledgers.** For `REST_ROUTE_LEDGER` (`family`) and `ROUTE_LEDGER` (`domain`), the imported table is the population. The ledger probe's own scoped text read (the same `export const NAME` … closing-bracket scope `discover()` uses) is the reach. The test asserts the two line up row by row. It goes red only on a ledger row whose grouping value the probe cannot read. Such a row is a route `discover()` can never name. A row with a literal family or domain moves nothing. - **Kept, derived on both sides:** the matrix header's "`discover()`: N probes over M named source files" must equal the `PROBES` table it describes. Every `PROBES` entry must declare its `kind` before its `file`. All three kinds must still be in use. - **Kept, presence only:** each file a TRIPWIRE probe reads keeps one in-file positive control, so a tripwire's zero still reads the file. Each module on the `RestServer` registrar list must still register routes, since the two `rest-server.ts` tripwires read them all. No exact counts. **The #17111 docblock pin, in place (same defect class).** The claim's premise was "the three reds were all the blind-spot pin". That is only partly true. #26406's before-run shows `authz-conformance.test.ts`'s #17111 pin also red on #26361 (`docblock says 80 rows, the table holds 81`), so deleting the census alone would leave a route-only PR still editing `authz-conformance.matrix.ts`. The matrix docblock now states only `(18 families)` / `(23 domains)`. The pin parses that shape, and the "stale ROW count" doctoring case is gone. The group, deletion, second-copy and anchor cases stay. A new family or domain still moves the figure, alongside the `discover()` classification it needs anyway. The seat amended the claim's file surface for this. **Untouched:** `discover()` and its UNCLASSIFIED / STALE verdicts, the baseline, the matrix rows, every CI workflow. No census step was added to PRs or the merge queue. **Other readers of the census path, re-read before deleting.** None needs the table: | Reader | What it held | Action | |:--|:--|:--| | `packages/rest/src/rest-route-ledger.ts`, `packages/runtime/src/route-ledger.ts` | a comment pointer: "the full reading lives in … census.ts" | pointer now names the census at commit 6746140 and its derived successor | | `packages/rest/src/rest-meta-auth.test.ts` | a comment: the test "licenses the census counting those 8 as REACHABLE" | reworded to the ratchet's `meta:rest-server.ts:registerMetadataEndpoints` key | | `authz-conformance.test.ts` (5 comments) | pointers to the census's prose and per-file controls | re-pointed (the controls now live in the new test) | | `scripts/pm/dispatch-gates.mjs:2822` | a dated incident narrative naming the path | unchanged; it describes a past edit | | `scripts/pm/dispatch-gates.self-test.mjs:12013` | the path as a change-set fixture for `jobFilteredSteps` | unchanged; pure path-pattern matching, no file read, and the path still lies under `packages/qa/dogfood/**` | No `scripts/pm/**` wording moved, so this PR is not Tier H. ## Why Three `main` reds in ten days (#24057, #25988, #26361 → #26400) came from a PR that changed a counted file and merged green. These census files run only on `main`'s push and scheduled batteries. Every one was a count moving, never an unclassified route. The ruling (6433001130) withdrew a PR-time census step (letter A) and kept C: derive the population, so a bump-only change reds nothing, and leave `discover()` as the only gate. ## Tests <details><summary>Both files on <code>origin/main</code> 6746140 (before): 108 passed</summary> ``` Test Files 2 passed (2) Tests 108 passed (108) os-verify-lock: VERDICT command-exit 0 · held the lock 19s ``` </details> <details><summary>Both files on bd9b1ea (after): 70 passed</summary> ``` Test Files 2 passed (2) Tests 70 passed (70) os-verify-lock: VERDICT command-exit 0 · held the lock 22s ``` The 38 fewer tests are the census's per-file parametrised rows and its table and totals pins, all deleted with it. </details> <details><summary>Controls on bd9b1ea: one row added to <code>REST_ROUTE_LEDGER</code> via <code>scripts/ablation-replace.mjs</code>, restored and hash-checked after each leg</summary> - **Positive control: a route-only change.** Added `GET /api/v1/approvals/zz-control-probe` with `family: 'approvals'`, an existing family. Result: `Test Files 2 passed (2) · Tests 70 passed (70)`, nothing to edit. The same shape of change on `main` (#26361) needed #26406 (+37/−14 in the census and matrix) to clear 10 reds. - **Negative control: an unclassified route.** Added a row with `family: 'zz-unclassified-probe'`. `discover()` still refuses: `UNCLASSIFIED surface — add a ledger row (ADR-0060): rest-family:rest-route-ledger.ts:zz-unclassified-probe`, and the #17111 families figure reds too. 11 failed / 59 passed. - **Negative control: a row the probe cannot read.** Added a row with `family: 'approv' + 'als'`. `authz-probe-blind-spot.test.ts` reds on `the probe reads EVERY row`, naming the row. 12 failed / 58 passed. - After each leg: `ablation-replace: ok restored: blob == HEAD (b6dd227) and git diff HEAD is empty`. </details> ## Acceptance notes - The `rest-server.ts` registration-site blind-spot reading (70 sites, 19 reachable, 51 not) is no longer recorded anywhere live. The route-level bound it measured is still stated in the matrix header: a route inside an existing family mints no key. The full record stays in history at 6746140. - `rest-server-registrar-files.ts`'s header says each reader pins the list's `length`. The derived check walks every module on the list instead, so no length is pinned. That header is not edited here. - Leg 3 also shows a pre-existing probe weakness: `family: 'approv' + 'als'` makes the ledger probe mint a phantom key `approv`, because its pattern stops at the first quote. It is caught twice (UNCLASSIFIED, and the new row-by-row check), so this is noted only. - Dogfood shards run only on `main` push and schedule runs (#24073 stands), so the first CI reading of these two files is after merge. ## Verification All readings on head `bd9b1ea5b0`, clone unshallowed, every package under `packages/**` built (`turbo run build --concurrency=2 --filter='./packages/**'`, 75 tasks, under the verify lock). - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: **56** derived commands, every exit captured before any pipe, **56 exit 0**. The first pass had 2 exit-3 prerequisite refusals (`check-plugin-teardown-shape --self-test` on the shallow clone, `check:dual-build-cjs-loads` without `dist/`). Both exit 0 after unshallowing and building. - `--ran`: `56 derived famil(ies) accounted for — 56 run, 0 NOT-MEASURED (a DERIVED zero)`. - The three artifact-roster families whose roster sits under `packages/` were run too: `check:authz-resolver`, `check:error-code-casing` and `check:filter-alias-parity`, all exit 0. - `pnpm --filter @objectstack/dogfood typecheck`: exit 0, 0 errors. - Not run locally: `@objectstack/rest` and `@objectstack/runtime` tests and typecheck. Their diff is comment-only (three comment blocks). `Test Core` runs them on this PR. - Dogfood shards: not run whole. The title has no `!` and no `(narrowing)` arm. The two affected files are the ones run above. Changeset: `skip-changeset`. `@objectstack/dogfood` is private, and the two `src/` edits in `@objectstack/rest` / `@objectstack/runtime` are comments only. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (CI-owned) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset. (nothing publishes: `skip-changeset`) - [x] 6. `packages/spec` touched: n/a. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English, except the 维护者速读 block. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01ADHkmPfdxeQ7cdMHou5dY1 --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmPfdxeQ7cdMHou5dY1)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 45de8c6 commit 98c0257

7 files changed

Lines changed: 227 additions & 1870 deletions

File tree

‎packages/qa/dogfood/test/authz-conformance.matrix.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@
2424
// hand-curated regex table reaching 1 of 17 REST registrars and 4 of 17
2525
// dispatcher domain files.
2626
//
27-
// The population comes from `packages/rest/src/rest-route-ledger.ts` (81 rows
28-
// / 18 families) and `packages/runtime/src/route-ledger.ts` (105 rows / 23
27+
// The population comes from `packages/rest/src/rest-route-ledger.ts` (18
28+
// families) and `packages/runtime/src/route-ledger.ts` (23
2929
// domains) because those two are enumerated from a RUNNING server and guarded
3030
// in both directions by their own conformance tests — so a new family or
3131
// domain cannot be silently absent from them, and therefore cannot be silently
3232
// absent from here.
3333
//
34-
// [#17111] Those four figures are DERIVED, not hand-typed. The companion test
34+
// [#17111] Those two figures are DERIVED, not hand-typed. The companion test
3535
// parses THIS sentence and asserts each number against a scoped read of the
36-
// ledger the sentence names, so a row, family or domain arriving or leaving
37-
// reds CI here instead of rotting into a false present tense. That pin is the
36+
// ledger the sentence names, so a family or domain arriving or leaving reds CI
37+
// here instead of rotting into a false present tense. ⛔ Row counts are not
38+
// stated, on purpose (ruling 6433001130): a route added inside an existing
39+
// family or domain changes nothing this claim depends on, and a stated row
40+
// count made every such route edit this file or red `main`. That pin is the
3841
// only reason the present tense above is safe to write at all; an undated
3942
// hand-typed count in this position is what #17111 was filed about, and what
4043
// #16307/#16954 were filed about before it.

‎packages/qa/dogfood/test/authz-conformance.test.ts‎

Lines changed: 33 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ const tripwireKey = (surface: string): string => `${surface}(${TRANSPORT_WIRED_M
9898
// them with one number is the mistake this declaration exists to prevent: it
9999
// reads working tripwires as holes and gate pins as route enumerations. The
100100
// taxonomy was established by the reach census
101-
// (`authz-probe-blind-spot.census.ts`) and lived there as PROSE; it lives here
101+
// (`authz-probe-blind-spot.census.ts`, retired after commit 67461403) and
102+
// lived there as PROSE; it lives here
102103
// now, as data on the probe it describes, because a comment cannot be checked
103104
// and a `kind` can.
104105
//
@@ -144,7 +145,7 @@ interface Probe {
144145
* registrar bodies into modules of their own, so they read every file on the
145146
* registrar list (`packages/rest/src/rest-server-registrar-files.ts`,
146147
* imported above) and a transport wired in a moved body still trips them.
147-
* `file` stays the one file the entry NAMES — what the probe-table census and
148+
* `file` stays the one file the entry NAMES — what `authz-probe-blind-spot.test.ts` and
148149
* the matrix header count — and the minted key is unchanged.
149150
*/
150151
alsoReads?: readonly string[];
@@ -200,8 +201,9 @@ const PROBES: readonly Probe[] = [
200201
// stayed 80. Only the headline split moved, earlier and unrelatedly: 50/30
201202
// became 51/29 when `registerUiEndpoints`, the one route in that file
202203
// resolving no identity, was guarded. Recorded so the next reader does not
203-
// re-derive a figure that has now been checked; the full reading lives in
204-
// `authz-probe-blind-spot.census.ts`, the authority on this population.
204+
// re-derive a figure that has now been checked; the full reading is in
205+
// `authz-probe-blind-spot.census.ts` at commit 67461403 (the census was
206+
// retired after it; its derived successor is `authz-probe-blind-spot.test.ts`).
205207
//
206208
// What these two probes supply is the POPULATION; the classification stays a
207209
// reviewed row.
@@ -337,8 +339,8 @@ const PROBES: readonly Probe[] = [
337339
//
338340
// ⚠️ It is re-declared TRIPWIRE, NOT deleted, and the difference matters:
339341
// deleting it would make this gate see LESS. As a declared tripwire its zero
340-
// is a CHECKED reading rather than an accident — the census carries a
341-
// positive control from this same file so a zero from a moved or emptied
342+
// is a CHECKED reading rather than an accident — `authz-probe-blind-spot.test.ts`
343+
// holds a positive control from this same file so a zero from a moved or emptied
342344
// file cannot pass as "nothing found" — and the day a `/data` route is
343345
// mounted here again the key appears and the surface is UNCLASSIFIED, which
344346
// is exactly a tripwire's promise.
@@ -352,7 +354,8 @@ const PROBES: readonly Probe[] = [
352354
// ⚠️ The live spelling this pattern watches for exists one file away, in
353355
// `current-user-endpoints.ts` (3 mounts, none of them `/data`), which this
354356
// table does not name. Naming it is a POPULATION decision and is deliberately
355-
// not taken here — see the census's population-source record.
357+
// not taken here — see the retired census's population-source record
358+
// (`authz-probe-blind-spot.census.ts` at commit 67461403).
356359
{
357360
kind: 'TRIPWIRE',
358361
file: 'packages/plugins/plugin-hono-server/src/hono-plugin.ts',
@@ -715,7 +718,8 @@ function checkLedgerPopulationBaselineWith(
715718
// ⛔ The exemption is not an escape hatch for a probe that has gone blind.
716719
// Re-declaring a ROUTE_ENUMERATION probe as TRIPWIRE to clear a red is only
717720
// honest when its population really is gone AND the zero is backed by a
718-
// positive control from that same file (the reach census holds one per file) —
721+
// positive control from that same file (`authz-probe-blind-spot.test.ts` holds
722+
// one per TRIPWIRE file) —
719723
// the hono probe above is written that way, dated evidence and all.
720724
function checkProbeInstrumentIntegrity(probes: readonly Probe[]): string[] {
721725
const problems: string[] = [];
@@ -1813,10 +1817,18 @@ describe('#7976 — row ↔ proof attribution is mutual', () => {
18131817
// reads ZERO against the very file that carries it. The control leg below
18141818
// pins that failure, because a zero from an instrument that cannot see the
18151819
// shape is not a reading of absence.
1820+
//
1821+
// ⛔ ONLY THE GROUP COUNT IS STATED AND PINNED (ruling 6433001130 on #26410).
1822+
// The docblock used to state each ledger's ROW count too, and this pin held it:
1823+
// every route added inside an existing family or domain then had to edit the
1824+
// matrix docblock or red `main`, though the claim the docblock makes is at
1825+
// family / domain granularity and a new row changes nothing it says. A new
1826+
// family or domain still moves the stated figure here, alongside the
1827+
// `discover()` classification it needs anyway.
18161828

18171829
const MATRIX_FILE = 'authz-conformance.matrix.ts';
18181830

1819-
/** One figure pair the matrix docblock states, and how its ledger spells a row. */
1831+
/** One figure the matrix docblock states, and how its ledger spells a row. */
18201832
interface DocblockLedgerClaim {
18211833
/** Repo-relative ledger path, exactly as the docblock backticks it. */
18221834
file: string;
@@ -1886,22 +1898,15 @@ function flattenLineComments(src: string): string {
18861898
* Every figure the (already-folded) docblock states for one ledger.
18871899
*
18881900
* Anchored on the backticked ledger PATH immediately followed by the
1889-
* parenthesised pair — not on the bare numbers, which also appear in the dated
1901+
* parenthesised group count — not on the bare numbers, which also appear in the dated
18901902
* drift note one paragraph down and must not be pinned as present-tense
18911903
* claims. Every match is returned so the caller can refuse a SECOND copy: an
18921904
* unpinned duplicate is exactly how a third carrier of a stale figure appears.
18931905
*/
1894-
function claimedFigures(flat: string, claim: DocblockLedgerClaim): { rows: number; groups: number }[] {
1906+
function claimedFigures(flat: string, claim: DocblockLedgerClaim): { groups: number }[] {
18951907
const path = claim.file.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1896-
const re = new RegExp('`' + path + '` \\((\\d+) rows / (\\d+) ' + claim.groupNoun + '\\)', 'g');
1897-
return allMatchPairs(flat, re);
1898-
}
1899-
1900-
function allMatchPairs(flat: string, re: RegExp): { rows: number; groups: number }[] {
1901-
const out: { rows: number; groups: number }[] = [];
1902-
let m: RegExpExecArray | null;
1903-
while ((m = re.exec(flat)) !== null) out.push({ rows: Number(m[1]), groups: Number(m[2]) });
1904-
return out;
1908+
const re = new RegExp('`' + path + '` \\((\\d+) ' + claim.groupNoun + '\\)', 'g');
1909+
return allMatches(flat, re).map((n) => ({ groups: Number(n) }));
19051910
}
19061911

19071912
/** Disagreements between the docblock's stated figures and the ledgers, on folded text. */
@@ -1911,7 +1916,7 @@ function checkFoldedDocblockFigures(flat: string): string[] {
19111916
const claimed = claimedFigures(flat, claim);
19121917
if (claimed.length === 0) {
19131918
problems.push(
1914-
`${claim.file}: the docblock states no (N rows / M ${claim.groupNoun}) figure for it — the pin has lost its subject`,
1919+
`${claim.file}: the docblock states no (N ${claim.groupNoun}) figure for it — the pin has lost its subject`,
19151920
);
19161921
continue;
19171922
}
@@ -1928,9 +1933,6 @@ function checkFoldedDocblockFigures(flat: string): string[] {
19281933
continue;
19291934
}
19301935
for (const c of claimed) {
1931-
if (c.rows !== actual.rows) {
1932-
problems.push(`${claim.file}: docblock says ${c.rows} rows, the table holds ${actual.rows}`);
1933-
}
19341936
if (c.groups !== actual.groups) {
19351937
problems.push(
19361938
`${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
19831985
expect(checkDocblockFigures(matrixText())).toEqual([]);
19841986
});
19851987

1986-
it('a stale ROW count is RED — the #14503 drift, replayed', () => {
1988+
it('a stale GROUP count is RED — the #20102 drift (a family leaving), replayed', () => {
19871989
// Doctored from the MEASURED value, so this case hand-types no count of
19881990
// its own — the thing the card was filed about.
1989-
const { rows, groups } = measureLedgerTable(REST);
1991+
const { groups } = measureLedgerTable(REST);
19901992
const flat = flattenLineComments(matrixText());
1991-
const doctored = flat.replace(
1992-
`(${rows} rows / ${groups} families)`,
1993-
`(${rows + 3} rows / ${groups} families)`,
1994-
);
1993+
const doctored = flat.replace(`\`${REST.file}\` (${groups} families)`, `\`${REST.file}\` (${groups + 1} families)`);
19951994
expect(doctored, 'the doctoring must land, or this case proves nothing').not.toBe(flat);
1996-
expect(
1997-
checkFoldedDocblockFigures(doctored).some((p) =>
1998-
p.includes(`docblock says ${rows + 3} rows, the table holds ${rows}`),
1999-
),
2000-
).toBe(true);
2001-
});
2002-
2003-
it('a stale GROUP count is RED too — both halves of the pair are pinned', () => {
2004-
const { rows, groups } = measureLedgerTable(REST);
2005-
const flat = flattenLineComments(matrixText());
2006-
const doctored = flat.replace(
2007-
`(${rows} rows / ${groups} families)`,
2008-
`(${rows} rows / ${groups + 1} families)`,
2009-
);
2010-
expect(doctored).not.toBe(flat);
20111995
expect(
20121996
checkFoldedDocblockFigures(doctored).some((p) =>
20131997
p.includes(`docblock says ${groups + 1} families, the table holds ${groups}`),
@@ -2018,18 +2002,18 @@ describe("#17111 — the matrix docblock's ledger figures are pinned to the ledg
20182002
it('DELETING the figure is RED — the pin cannot be silenced by dropping its subject', () => {
20192003
// Otherwise the cheapest way to green this gate would be to remove the
20202004
// sentence, which is the claim, not the defect.
2021-
const { rows, groups } = measureLedgerTable(REST);
2005+
const { groups } = measureLedgerTable(REST);
20222006
const flat = flattenLineComments(matrixText());
2023-
const doctored = flat.replace(`\`${REST.file}\` (${rows} rows / ${groups} families)`, `\`${REST.file}\``);
2007+
const doctored = flat.replace(`\`${REST.file}\` (${groups} families)`, `\`${REST.file}\``);
20242008
expect(doctored).not.toBe(flat);
20252009
expect(
20262010
checkFoldedDocblockFigures(doctored).some((p) => p.includes('the pin has lost its subject')),
20272011
).toBe(true);
20282012
});
20292013

20302014
it('a SECOND present-tense copy of a figure is RED — a new carrier cannot arrive unpinned', () => {
2031-
const { rows, groups } = measureLedgerTable(REST);
2032-
const one = `\`${REST.file}\` (${rows} rows / ${groups} families)`;
2015+
const { groups } = measureLedgerTable(REST);
2016+
const one = `\`${REST.file}\` (${groups} families)`;
20332017
const flat = flattenLineComments(matrixText());
20342018
const doctored = flat.replace(one, `${one} and again ${one}`);
20352019
expect(doctored).not.toBe(flat);

0 commit comments

Comments
 (0)