feat(tooling): gate the route-ledger row census so a silent deletion reds - #17155
Conversation
…reds
`packages/runtime/src/route-ledger.ts` is census-shaped: its value is the
completeness of a list, and nothing in this tree read its row COUNT. An
index-slice edit meant to add two rows removed 105 lines -- route rows plus the
whole `/actions` section -- and exited 0, caught only because an unrelated gate
happened to redden.
Measured before writing anything:
* The file's own suite (route-ledger.conformance.test.ts) is domain-level --
"every registered dispatcher domain has at least one ledger entry" -- which
a shorter list satisfies. Positive control: deleting a whole live-registry
domain's rows DOES redden it, so "no count gate" is a reading and not an
artefact of the search.
* The cross-ledger guards (live-mount parity, client-url-conformance) compare
against the UNION of five ledgers, which absorbs deletions: 26 of
ROUTE_LEDGER's 82 rows have their wire pattern declared by another ledger,
by the `* /mcp/**` wildcard, or via `servedBy` -- including two rows in the
very `/actions` section the incident removed.
* 10 of the 11 `*-route-ledger.ts` files carry a per-route completeness pair
in their own package suite; this one is the only member of the family that
does not. So the gate carries one occurrence, measured rather than assumed.
The count is GENERATED (`--fix` writes the digits, nobody's memory does), the
anchor must resolve to exactly one match or report ROTTED_ANCHOR, and the shape
is check-lockstep-package-count.mjs's. Deliberately a deletion detector and not
a route-granularity cross-check against the dispatcher registry -- that question
is open in #17041 and a census stays correct whichever way it is ruled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
📓 Docs Drift Check
What this run could not see
Coarse fallback — 24 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
…ute-ledger-census-floor
Ablation results — predicted first, and one prediction was wrongTwo legs, each mutating and measuring in one shell, restoring under
Leg A — delete lines 498..586 (the
|
| predicted | observed | |
|---|---|---|
check:route-ledger-census |
RED | RED — reads 82, the array holds 74 — 8 row(s) went missing |
packages/runtime conformance suite |
GREEN | ❌ RED — 2 of 6 failed |
The prediction was wrong, and the reason is worth recording. I had assumed /actions, /mcp and /mcp/skill were legacy-chain prefixes outside the live registry, because LEGACY_CHAIN_PREFIXES pins all three. They are live registry domains as well, so deleting every row of a domain reddens every registered dispatcher domain has at least one ledger entry:
AssertionError: Dispatcher domains with no route-ledger entry: /actions, /mcp/skill, /mcp
AssertionError: Non-dispatch mounts with no route-ledger entry: /apps
So a whole-domain wipe is already caught. That makes leg A a second positive control rather than a demonstration of the gap, and it sharpens what the gap actually is — which is what the conformance suite's own header says it is: "For a multi-route domain, any non-last row can be deleted, or never added, without turning anything red."
Leg B — positive control for item 1: delete one row (GET /.well-known/objectstack, 3 lines)
| predicted | observed | |
|---|---|---|
packages/runtime conformance suite |
RED | RED — Non-dispatch mounts with no route-ledger entry: /.well-known/objectstack |
The suite demonstrably reads this file and demonstrably fires. So "no gate reads its row count" is a measurement, not an artefact of the search.
Leg A2 — the faithful index-slice: 9 lines, 6 rows
Computed, not guessed: the longest contiguous run of rows that is simultaneously domain-preserving (so the domain-level suite is structurally blind to it) and union-absorbed (so the cross-ledger guards are too) and carries no responseSchema / authz name (so the schema-count pin is too). That is lines 487..495 — GET /meta/object/:name/state/:field plus 5 of the 6 /data rows, all of them duplicated by rest-route-ledger.ts. /data keeps 1 row, /meta keeps 8, and the ledger still spells 23 distinct domains.
| predicted | observed | |
|---|---|---|
check:route-ledger-census |
RED | RED — reads 82, the array holds 76 — 6 row(s) went missing |
packages/runtime conformance suite |
GREEN | GREEN — Test Files 1 passed / Tests 6 passed |
client-url-conformance + route-ledger-coverage + route-ledger-response-schema |
GREEN | GREEN — Test Files 3 passed / Tests 8 passed |
Six rows — 7% of the ledger — removed by a nine-line contiguous slice, and every gate in this tree that reads this file stays green except the new one. That is the incident's failure mode reproduced with the luck taken out of it.
Restore
git diff HEAD -> 0 changed path(s) (0 = clean)
blob after restore: 3a66f84341394c4214b270ba7bf54416ce6bfce2
blob at HEAD : 3a66f84341394c4214b270ba7bf54416ce6bfce2
RESTORE VERDICT: byte-identical to HEAD (empty git diff HEAD AND blob equality)
Post-restore, check:route-ledger-census exits 0 and the conformance suite is 6/6 green.
Baselines, for the record
Pristine tree, before any mutation: census exit 0, packages/runtime conformance 6/6, the three client ledger guards 8/8.
Ablation and report generated by Claude Code — session-URL attribution kept here in prose, since a comment's tail footer belongs to the platform.
Generated by Claude Code
Verification — all readings taken at
|
Acceptance-notes addendum — one card filed out of this work, one adjacency notedFiled as #17174 (unassigned, unlabelled): Adjacency, noted only: #17111 reports that Dedup for #17174 was a deterministic repo-scoped scan, not a semantic search: Filed by Claude Code. Generated by Claude Code |
Fixes #16758
packages/runtime/src/route-ledger.tsis census-shaped: its value is the completeness of a list. Nothing in this tree read its row count, so an index-slice edit that meant to add two rows could remove 105 lines — route rows plus the whole/actionssection — and exit 0.This adds
check:route-ledger-census, which holds a generated census sentence aboveROUTE_LEDGERto the number of rows the array actually contains.The four items, measured in order
1. Does any existing gate read this file's row count? No — and here is the positive control.
packages/runtime/src/route-ledger.conformance.test.tsis the gate that demonstrably does read the file, and it does fire: deleting a live-registry domain's rows reddensevery registered dispatcher domain has at least one ledger entry. Every one of its six assertions is set- or predicate-shaped, and every one of them is satisfied by a shorter list — the suite's own header says so, out of the #17038 ablation that #17075 wrote up. Repo-wide,ROUTE_LEDGERis read by six files and none of them reads.lengthagainst any number.2. Is the population knowable statically? Yes. It is the array literal itself. The gate walks it comment- and string-aware and counts one row per
{opening at depth 1 — 82 today, cross-checked against an independent reader (grep -c "route: '"→ 82).3. Which instrument? Census/deletion — deliberately not the router cross-check. Whether this ledger should be checked at ROUTE granularity, given
DomainHandlerRegistry.list()exposes domains and not routes, is the open maintainer decision in #17041. Building that here would answer it silently. A census notices the list got shorter independent of what the routes are, and stays correct whichever way #17041 is ruled.4. Which other census-shaped files share the gap? One — this file. This tree holds 11
*-route-ledger.tsfiles. Ten carry a per-route completeness pair in their own package suite (every mounted route has a ledger entryandevery ledger entry is really mounted, against a live enumeration of that package's registrar), so deleting a row from any of those ten reddens that package's own tests. The eleventh — this one — is domain-level only. The sweep's control: a mechanical census-shape sweep (exported top-level array of ≥20 object rows, non-test) re-findspackages/runtime/src/route-ledger.ts, so the "one" is a reading and not an empty search.Why the cross-ledger guards do not already cover it
route-ledger-live-mount-parity.dogfood.test.tsdoes check per-route completeness — but its direction 2 (every mounted route is ledgered) compares the live mount table against the union of five ledgers, and so doesclient-url-conformance.test.ts. A row whose wire pattern is still produced by another member of that union is invisible when deleted. Measured from source, no boot needed (it is a set difference against the union; if the union still holds the key, the mount is not reported unledgered):26 of
ROUTE_LEDGER's 82 rows are in that state — 23 duplicated byrest-route-ledger.tsori18n-route-ledger.ts(/meta/*,/data/*,/security/*,/discovery,/ui/view/*,/i18n/*), one absorbed by the* /mcp/**wildcard row, and twoservedByspecializations whose own pattern is registered nowhere:POST /actions/global/:actionandPOST /actions/_activation/:object/:action— both in the/actionssection the incident deleted.The count is generated, not hand-typed
--fixwrites the digits; nobody's memory does. The shape isscripts/check-lockstep-package-count.mjs's (#17055), including itsROTTED_ANCHORbehaviour: the anchor must resolve to exactly one match before the number it captured is compared to the truth, so a reworded sentence — or an index slice that takes the comment out along with the rows — reports rather than agrees. The digits in this PR were produced by writing0and running--fix.What that buys a reviewer is the property the incident lacked: a PR that really adds two rows moves the number by two, in the same diff as the rows.
Acceptance — predicted first, then run
Predictions written before the run:
check:route-ledger-censuspackages/runtimeconformance suiteResults are posted below; the ablation mutates and measures in one shell, restores under
trap … EXIT INT TERMwithgit checkout HEAD -- <abspath>, and proves restoration by an emptygit diff HEADand blob equality against theHEADblob hash — never by an exit code.Scope
skip-changeset: this releases nothing — a gate script, its CI step, itspackage.jsonentry, and a generated comment in a source file. No exported symbol, no published-payload key, nopackages/spec/src/**path (Clause-② isnoand staysno). No open PR touches any*route-ledger*file (all 19 open PRs checked by file list), so the hot-file courtesy check is clear.验收备注
LEGACY_CHAIN_PREFIXESandNON_DISPATCH_MOUNT_PREFIXESin the same file are not given census sentences: deleting a member of either already redsroute-ledger.conformance.test.ts(a ledger domain that matches no live prefix, no pinned legacy branch and no pinned non-dispatch mount). Noted, not filed.Generated by Claude Code