Uh oh!
There was an error while loading. Please reload this page.
fix(verify): a conformance row's proof must NAME the row it proves, not merely exist (#7976) - #8040
Conversation
… path (#7976) `checkLedger` asserted only that a cited `proof` file EXISTS — nothing ever read it — so a row could cite a test exercising a neighbouring primitive and stay green forever. `rls-read` and `rls-by-id-write` cite the same file, and until PR #7975 read it by hand nothing could tell whether it exercised one or both. "Does this test prove this row" is not mechanically decidable and is NOT attempted. It is converted into a checkable question: a proof file NAMES the rows it is the proof for (`// authz-row: <id>`), and the new opt-in `CheckLedgerOptions.attribution` asserts the pairing is mutual — a cited file must claim the citing row, and every claim must be reciprocated by the ledger (with `scan` reaching claims in files no row cites, so a stale claim fails rather than rots). A comment marker, not an exported manifest: proof files are test modules whose import boots real stacks, so the claim must be readable without executing them. The keyword is deliberately not `@proof:` — that channel carries ADR-0054 liveness ids, a different vocabulary from matrix row ids. All 24 authz rows citing a proof were annotated by reading the cited file. One citation did not survive that read: `requireAuth-removed` cited showcase-anonymous-deny.dogfood.test.ts, which proves the anonymous-deny posture (what the `anonymous-deny` row already claims) and never authors `requireAuth: false`, reads the spec tombstone, or boots an auth-less stack — so it cannot prove this row's distinguishing half, that there is no opt-out. The citation is dropped rather than rubber-stamped; state and enforcement site are unchanged and the note now records where the retirement is actually pinned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019zbHQZ3tutYjjZwvsw3DoF
…hz-proof-attribution
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also reference the affected code. These are read-only:
|
…N, not just existence (#7976) The authorization page described the matrix's proof contract as "a deleted proof fails CI", which was the whole of it. It now also asserts the row <-> proof pairing is mutual, so say that: a cited proof must name the rows it proves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019zbHQZ3tutYjjZwvsw3DoF
hotlong
commented
Aug 12, 2026
PM review — Attribution rather than semantics, both directions, opt-in, ~24 rows annotated by reading — every binding point delivered. Two things beyond the brief: The A separate keyword from The marker-form argument is also decisive rather than aesthetic: proof files are test modules whose import boots real stacks, so a claim must be readable without executing them. That rules out a manifest on mechanics, not taste. Ruling on |
Fixes#7976
The defect
checkLedger(packages/verify/src/conformance.ts) asserted exactly one thing about aproof:Existence. Nothing ever read the file, and
proofRequiredForEnforcedonly asserted a proof was named. So a row could cite a test exercising a neighbouring primitive and stay green forever — on the one artifact ADR-0056 D10 exists so that reviewers do not re-derive the audit by hand.rls-readandrls-by-id-writecite the same file, and until PR #7975 read it line by line nothing could tell whether it exercised one, the other, or both.What this does — attribution, not semantics
"Does this test actually prove this row" is not mechanically decidable, and this does not attempt it: no heuristic, no coverage inference. It converts the undecidable question into a checkable one — the proof file names the rows it is the proof for, and the pairing must be MUTUAL.
New opt-in
CheckLedgerOptions.attribution(every existing ledger is unchanged until it opts in) takes a marker keyword — the authz matrix usesauthz-row— and asserts both directions:Direction 2 is why the option also takes a
scan: a claim in a file no row cites is invisible to the citation walk by construction, which is exactly what a renamed row or a re-pointed proof leaves behind. The authz ledger scans*.dogfood.test.tsin the proof root.Marker form chosen: a header comment line,
// authz-row: <id>Picked over an exported manifest, for three reasons:
readFileSyncthe existence check already implied; a manifest would need either an import (side effects) or a TS AST parse (heavier, no more truthful).@proof: <id>in their header for the ADR-0054 liveness registry, so "the machine reads my header" is established.controlled-by-parent.dogfood.test.tsis@proof: cbp-controlled-by-parentandauthz-row: controlled-by-parent. Reusing@proof:would let one gate's rename silently re-point the other's.The claim is anchored to the start of a comment line, so the marker appearing in prose or a string literal is not mistaken for a claim.
Scope — 24 rows, annotated by reading, not by guessing
All 24 authz rows carrying a
proof(17 distinct files) were annotated after reading the cited file and marking it only for what it demonstrably exercises. The shared files were the point of the exercise and each says which rows it covers:rls-fixture.dogfood.test.tsrls-read,rls-by-id-writeshowcase-anonymous-deny-surfaces.dogfood.test.tsanonymous-deny-meta,-actions,-automation,-packagesowner-anchor-and-bulk-writes.dogfood.test.tsownership-anchor-guard,bulk-write-owner-scopingshowcase-bu-hierarchy-sharing.dogfood.test.tssharing-rules,hierarchy-wideningsys_record_share; subordinate-unit member reading through the treerls-by-id-writeandcontrolled-by-parentwere not re-opened — both keepstate: 'enforced', and their markers are worded to match what PR #7975 established on each row's own evidence.Rows whose cited proof did NOT cover them — 1 of 24
requireAuth-removed→ citedshowcase-anonymous-deny.dogfood.test.ts.That file drives the platform default and observes 401 on an anonymous read and write — which is precisely what the
anonymous-denyrow (the same file) already claims. What distinguishesrequireAuth-removedis that the deployment-wide opt-out is retired:api.requireAuthtombstoned in spec, a stack that mounts no auth failing at boot. The file authors norequireAuth: false, reads no tombstone, and boots no auth-less stack — and its own header still frames itself as proving the flipped default (requireAuthdefault(true)), the pre-#3963 world. It was borrowing its sibling's credibility, which is the exact shape #7976 filed.The citation is dropped rather than rubber-stamped. The row is not in
HIGH_RISKand the authz ledger does not setproofRequiredForEnforced, so it is sound without one.stateand the enforcement site are untouched — that call is yours. Its note now records where the retirement is actually pinned (the spec tombstone + the ADR-0087 conversion entrystack.api.requireAuth, which strips a surviving key, andrest/rest-auth-gate.test.ts) and what an honest dogfood proof would have to do: authorapi: { requireAuth: false }and expect the authoring/boot rejection.Observation, not a miss —
multi-tenantrls-multitenant.dogfood.test.tsgenuinely exercisesmulti-tenant, but the whole suite isdescribe.skipIf(!organizationsAvailable): in the open workspace it does not run at all, and only enterprise/cloud CI (which ships@objectstack/organizations) exercises the row. The marker records what the file proves where it runs, and says so in the file.OS_TEST_MULTI_ORG_ENABLED=1(#4700) is what keeps that skip honest. Flagging it because a green open-core run is not evidence for this row — no change requested.Reverse verification (the required pin)
Fix committed first, then
owd-private'sproofre-pointed atflow-runas.dogfood.test.ts— a file that exists, so the pre-#7976 existence check was perfectly happy with it. Observed output:Both directions fired and both name the row.
flow-runas.dogfood.test.tswas correctly not flagged — its own claim stays reciprocated. Restored withgit checkout HEAD -- …(nevergit stash), tree clean.The mechanism is also pinned as tests, so it cannot go back to sleep: six cases in
authz-conformance.test.ts(#7976 — row ↔ proof attribution is mutual, including a baseline lock that fails if the matrix stops citing proofs) and seven unit cases inconformance-helper.test.ts— one of which pins that attribution is opt-in, i.e. existence alone still passes without it.Verification
pnpm -w typecheck— 127/127 successful.pnpm check:type-check-debt— OK, nothing raised (the only deltas are pre-existing surplus below recorded ceilings).authz-conformance.test.ts+conformance-helper.test.ts— 34 passed. All fivecheckLedgerledgers (authz / expression / search / flow-trigger / validation) + helper — 43 passed.@objectstack/verifytests — 28 passed. ESLint clean on the changed files.origin/mainmerged in (both incoming commits arepackages/spec; this diff touches neither).Generated by Claude Code