Skip to content

feat(tooling): gate the route-ledger row census so a silent deletion reds - #17155

Merged
os-trump merged 2 commits into
mainfrom
claude/issue-16758-route-ledger-census-floor
Sep 9, 2026
Merged

feat(tooling): gate the route-ledger row census so a silent deletion reds#17155
os-trump merged 2 commits into
mainfrom
claude/issue-16758-route-ledger-census-floor

Conversation

@os-trump

@os-trump os-trump commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16758

packages/runtime/src/route-ledger.ts is 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 /actions section — and exit 0.

This adds check:route-ledger-census, which holds a generated census sentence above ROUTE_LEDGER to 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.ts is the gate that demonstrably does read the file, and it does fire: deleting a live-registry domain's rows reddens every 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_LEDGER is read by six files and none of them reads .length against 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.ts files. Ten carry a per-route completeness pair in their own package suite (every mounted route has a ledger entry and every 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-finds packages/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.ts does 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 does client-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 by rest-route-ledger.ts or i18n-route-ledger.ts (/meta/*, /data/*, /security/*, /discovery, /ui/view/*, /i18n/*), one absorbed by the * /mcp/** wildcard row, and two servedBy specializations whose own pattern is registered nowhere: POST /actions/global/:action and POST /actions/_activation/:object/:actionboth in the /actions section the incident deleted.

The count is generated, not hand-typed

--fix writes the digits; nobody's memory does. The shape is scripts/check-lockstep-package-count.mjs's (#17055), including its ROTTED_ANCHOR behaviour: 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 writing 0 and 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:

leg check:route-ledger-census packages/runtime conformance suite
delete a block of rows (the incident) RED — STALE, names both numbers GREEN — this is the gap
positive control: delete a live-registry domain's rows RED RED — proves the suite reads the file
restore GREEN GREEN

Results are posted below; the ablation mutates and measures in one shell, restores under trap … EXIT INT TERM with git checkout HEAD -- <abspath>, and proves restoration by an empty git diff HEAD and blob equality against the HEAD blob hash — never by an exit code.

Scope

skip-changeset: this releases nothing — a gate script, its CI step, its package.json entry, and a generated comment in a source file. No exported symbol, no published-payload key, no packages/spec/src/** path (Clause-② is no and stays no). 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_PREFIXES and NON_DISPATCH_MOUNT_PREFIXES in the same file are not given census sentences: deleting a member of either already reds route-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.
  • The 26-row union-absorption reading above is a property of the cross-ledger guards' design, not a defect in them — they were built to answer a different question. Noted, not filed.

Generated by Claude Code

…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
@os-trump os-trump added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026 — with Claude
@github-actions github-actions Bot added size/m ci/cd dependencies Pull requests that update a dependency file labels Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/runtime/src/route-ledger.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/runtime/src/route-ledger.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 24 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 5d12b16e7909d301dea6b2aa39f504f23d2a2ab5packageMentionDocs.

os-trump commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Ablation results — predicted first, and one prediction was wrong

Two legs, each mutating and measuring in one shell, restoring under trap 'git checkout HEAD -- $LEDGER' EXIT INT TERM. Restoration is proven by an empty git diff HEAD and blob equality against git rev-parse HEAD:packages/runtime/src/route-ledger.ts — never by an exit code, and an empty hash would have been read as failure. Every mutation is proven to have reached disk by an occurrence count taken before and after, not by the editor's exit code.

HEAD blob throughout: 3a66f84341394c4214b270ba7bf54416ce6bfce2.

Leg A — delete lines 498..586 (the /mcp + /actions sections, 89 lines, 8 rows)

predicted observed
check:route-ledger-census RED REDreads 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 REDNon-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 REDreads 82, the array holds 76 — 6 row(s) went missing
packages/runtime conformance suite GREEN GREENTest Files 1 passed / Tests 6 passed
client-url-conformance + route-ledger-coverage + route-ledger-response-schema GREEN GREENTest 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

os-trump commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Verification — all readings taken at ccd5ebd12 (the branch tip, origin/main merged in)

Derived gate batch. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 91 families from this tree at ccd5ebd12 for the 4-path change set. All 91 were run and reconciled:

✓ dispatch-gates --ran: 91 derived famil(ies) accounted for — 91 run, 0 NOT-MEASURED.

90 green. One is NOT MEASURED and is reported as such, never as a pass:

pnpm check:dual-build-cjs-loads  -> exit 3
PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/.
Run `pnpm build` first. ⛔ This is NOT a pass: nothing was measured.

Its own self-test passed (93 cases); the real scan needs a full pnpm build of the workspace, which this container's shared box could not be given without evicting other seats. CI's Build Core job runs it against real dist. Declared, not hidden.

The first derivation ran against a tree two commits behind origin/main and said so (⚠️ STALE TREE — scripts/check-dispatcher-error-vocabulary.mjs); origin/main was merged in and the derivation re-taken. Same 91 families, no stale warning.

Wiring, confirmed by the tree's own gates rather than by reading the diff:

  • check-self-test-wired — 196 scripts CI runs that ship a --self-test have it run by CI; the new gate's workflow step chains --self-test, so it is one of them.
  • check-declared-population-live — 237 of 287 families declare a path population and every one reaches this tree's 8075 tracked files; the new gate's declared path (packages/runtime/src/route-ledger.ts) is live, not a dead literal.
  • check-ratchet-remedy-authority — 231 scripts swept; the new gate lands in the 210 that hand out no ratchet-expanding remedy (its remedy regenerates, it does not widen a shrink-only registry), so it needs no control-corpus entry.
  • dispatch-gates itself now names pnpm check:route-ledger-census on any card touching packages/runtime/src/route-ledger.ts — the gate is discoverable by the dispatch derivation, not only by CI.

Repo-wide lintpnpm lint (eslint . --no-inline-config), the whole scan, not a narrowed one: 6434 files, 0 errors, 0 warnings, exit 0, 68s under the shared verify lock. No narrowing to justify.

Affected package@objectstack/runtime: typecheck clean, and the full suite 249 test files / 3516 tests, all passing.

packages/client's three ledger guards were run as part of the ablation (8/8 green both pristine and mutated) — that is the reading that shows the deletion is invisible to them, not a coverage claim about the client package.

Every heavy run went through scripts/pm/os-verify-lock.sh with a stable slot; verdicts were read from its VERDICT command-exit line, never from a bare $?.

Verification driven by Claude Code.


Generated by Claude Code

os-trump commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Acceptance-notes addendum — one card filed out of this work, one adjacency noted

Filed as #17174 (unassigned, unlabelled): route-ledger.ts's LEGACY_CHAIN_PREFIXES docstring says its members are "branches not yet lifted into the DomainHandlerRegistry" and that the conformance test "asserts this list matches the ledger's domain set minus the live registry prefixes". Leg A of this PR's ablation measured /actions, /mcp and /mcp/skill coming back out of registryPrefixes() — they are lifted and still pinned — and the test in fact asserts a weaker three-way disjunction that a both-live-and-pinned domain satisfies. Not repaired here: the fix is a decision (drop the lifted prefixes, or rewrite the docstring to the disjunction and say why a lifted prefix stays pinned), and it is a different list in the same file. This PR touches neither.

Adjacency, noted only: #17111 reports that authz-conformance.matrix.ts states this ledger holds "94 rows / 19 families" — a hand-typed count of the very array this PR now censuses (82 rows / 21 domains at ccd5ebd12). That prose restatement is #17111's card and is deliberately not touched here; when it is taken, OCCURRENCES in scripts/check-route-ledger-census.mjs is the row it belongs in — a second restatement gets a row, never a second script.

Dedup for #17174 was a deterministic repo-scoped scan, not a semantic search: GET /repos/objectstack-ai/objectstack/issues?state=open&per_page=100&page=N over 7 pages, the last returning 13 rows (closed number line), 598 open issues, grepped locally. LEGACY_CHAIN_PREFIXES, DomainHandlerRegistry and "legacy chain / if-chain" each returned zero, against a control term ("route-ledger") returning 12 — so the zero is a reading. The /search/* endpoints answer 403 for this session, which is why the list channel was used.

Filed by Claude Code.


Generated by Claude Code

@os-trump
os-trump marked this pull request as ready for review September 9, 2026 15:12
@os-trump
os-trump enabled auto-merge September 9, 2026 15:12
@os-trump
os-trump added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 6e9bee6 Sep 9, 2026
36 checks passed
@os-trump
os-trump deleted the claude/issue-16758-route-ledger-census-floor branch September 9, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants