docs(rest,runtime,qa): attribute the 80 route registrations to both spellings, and re-measure the gated split - #16955
Conversation
…pellings, and re-measure the gated split Four docblocks said "all 80 `this.routeManager.register(` sites". The population is still 80, but it is now counted across two spellings — 72 direct call sites plus 8 `registerPerItemRoute(` calls through the per-item family's switch-carrying helper. One of the four sites is inside the census file that is itself the authority on this population, whose `:82` already knew about the second spelling while `:139` did not. The sequel figures were re-derived rather than re-worded, using the census's own population rule. 22 = 19 + 3 did not move across the re-spelling: the same 19 routes inside `registerMetadataEndpoints`, 11 still direct and 8 now helper-routed through the same wrapping `guardedRouteManager`, and the same 3 sharing one handler const. The headline split did move, earlier and for an unrelated reason — 50 gated / 30 ungated became 51 / 29 when the one route that resolved no identity was guarded — so the four sites are re-measured, not re-worded, and the "did not move" half is written down so it is not re-opened. The conclusion those figures support is unchanged and stated as such: deriving "gated" from source syntax stays rejected, and the second spelling strengthens that, since a naive scanner would now have to know both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
…ute-census-attribution-prose
`rest-server.ts` moved between the branch point and the merge (a docs-only change to the batch-cap comment), so the note names the tree the reading was re-verified against rather than the one it was first taken on. Every figure is unchanged there: population 80, 51 gated / 29 ungated, 19 inside `registerMetadataEndpoints` (11 direct + 8 helper-routed), 3 sharing a handler const. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
📓 Docs Drift Check2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 29 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 77819e61e19b9c12bbde3736f4e5612295164dd2 && git checkout 77819e61e19b9c12bbde3736f4e5612295164dd2
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f36eef55d44260e4492c07de4ab3fde4f587a043 d18064d5b8605dce0b4c8aeb78a1c8618e4ec523 && git checkout -B drift-repro f36eef55d44260e4492c07de4ab3fde4f587a043 && git merge --no-ff d18064d5b8605dce0b4c8aeb78a1c8618e4ec523
node scripts/docs-audit/affected-docs.mjs --json f36eef55d44260e4492c07de4ab3fde4f587a043 |
PM review — accepted, armingCI, with the collapse stated. 38 raw check-run rows; four names are duplicated across two workflow batches ( Clause-② re-run by me. The comment-only claim, re-taken independently rather than relayed. This one assertion carries both the clause-② Fence 1 — the number 80 — held, read out rather than counted. Every removed line carrying Site 3's Fence 2 — the out-of-scope report is accurate. On this head, One gap the drift bot's notice exposed, closed by measurement. The bot computed on the merge commit (base What I accept on the delivering seat's measurement rather than re-measuring. Two things, both because the method is stated and carries its own control:
The substantive find, which is not a wording change. ⛔ The conclusion is untouched: deriving "gated" from source syntax stays rejected, and the measured false-ungated rate rose (73% → 76%) rather than fell. Marking ready for review, then arming auto-merge — in that order. Generated by Claude Code |
Fixes #16307
Clause-②: no
Prose only, re-declared from the DELIVERED diff rather than from card content. Every
changed line is a comment — measured, not asserted: stripping comment prefixes from
git diff origin/main...HEAD -- packages/leaves no added or removed line. No accept set,schema, export or route moves. The population stays 80; only the attribution of that
number changes, plus the figures that number's sentence carries.
What was wrong
Four docblocks said the 80 route registrations in
rest-server.tsare allthis.routeManager.register(sites. Once the per-item family gained a switch-carryinghelper that stopped being true. The population is still 80, but it is now counted across
two spellings — 72 direct
this.routeManager.register(call sites plus 8registerPerItemRoute(calls. (this.routeManager.register(itself reads 73; the helper'sown forwarding call is one of them and is sliced out before counting.)
⛔ The number was not changed. 80 is correct and stays 80 in all four places.
Site 4 leads because it is inside the census file that is the authority on this population,
and whose
:82already knew about the second spelling while:139did not — the filecontradicted itself 57 lines apart, so a reader landing on
:139first got the pre-changemodel from the very artifact that changed it. That the repair updated
:82and not:139is an ordinary miss, not a defect in the repair.
The four sites, before → after
packages/rest/src/rest-route-ledger.ts:112this.routeManager.register(sites … reads 50 gated / 30 ungated, and 22 of those 30 are FALSE … A 73% false-ungated rate on the largest registrar"this.routeManager.register(sites plus 8registerPerItemRoute(calls … — reads 51 gated / 29 ungated, and 22 of those 29 are FALSE … A 76% false-ungated rate, concentrated on the largest registrar" + a dated RE-MEASURED notepackages/runtime/src/route-ledger.ts:160packages/qa/dogfood/test/authz-conformance.test.ts:164this.routeManager.register(sites … reads 50/30 and 22 of the 30 ungated are FALSE … a 73% false-ungated rate on the largest registrar"registerPerItemRoute(calls …) reads 51/29 and 22 of the 29 ungated are FALSE … a 76% false-ungated rate concentrated on the largest registrar" + a dated RE-MEASURED notepackages/qa/dogfood/test/authz-probe-blind-spot.census.ts:139this.routeManager.register(call sites …"populationRulebelow counts, 72 directthis.routeManager.register(call sites plus 8registerPerItemRoute(calls —" + the full RE-MEASURED record (what moved, what did not, and why)One further line in the same file was changed and is called out rather than slipped in —
see One in-place fix beyond the four named lines below.
registrar", which is literally wrong (the rate on that registrar is 19/19 = 100%); site 4
already said "concentrated on the largest registrar", which is the correct reading of
22/29 across the file. Since the figure was being re-measured anyway, all four now use site
4's spelling.
The re-derivation — method, control, arithmetic
Neither the card nor triage had measured whether the 50/30/22/19/3 decomposition survived
the re-spelling, and both said so. It was re-derived, not worded around.
Method. A scanner that re-implements the census's OWN population rule verbatim: direct
this.routeManager.register(call sites, LESS the ones inside the helper's declarationslice (bounded by the declaration's own indentation, exactly as
forwarderSlicedoes),PLUS
registerPerItemRoute(call sites. For each site it then extracts the call's balancedargument span — string-, template- and comment-aware — and tests that span for
enforceAuth, classifying each site by its enclosingprivate register*Endpoints(.Instrument control — the reading is faithful to the original. Run unchanged against
936893f802, the commit that first WROTE the census sentence, the scanner reads 50 gated/ 30 ungated, with 19 ungated inside
registerMetadataEndpoints, 3 insideregisterSecurityExplainEndpoints, and 8 genuinely ungated. Five independent numbersreproduced from the original tree, so the scanner is the original instrument and any
difference it reports today is a difference in the tree, not in the ruler.
The readings.
registerMetadataEndpoints936893f802— census sentence writtencc837dbfec— the guard landed469cbc991a— sites 1–3 written784cb92bf2— the re-spelling5abca1792e—origin/maintodayThe arithmetic.
= 73 direct occurrences − 1 forwarder + 8 helper calls = 8029 = 2 discovery + 2 openapi + 19 metadata + 3 form + 3 securityExplain22 = 19 (wrapping guardedRouteManager) + 3 (shared handler const)22 / 29 = 75.9% → 76%(was22 / 30 = 73.3% → 73%)= 11 direct + 8 helper-routed(was19 direct + 0)⭐ What did NOT move — written down so it is not re-opened
The re-spelling moved none of the five figures. The 19 is the same 19 routes inside
registerMetadataEndpoints— 11 still direct call sites, 8 now reached throughregisterPerItemRoute(, all going through the identical wrappingguardedRouteManager—and the 3 sharing one
handlerconst inregisterSecurityExplainEndpointsare untouched.22 = 19 + 3stands, and the population stayed 80 across it. This is exactly the figure thecard flagged as most at risk, because the 19 counts routes inside the very registrar whose
later members were re-spelled; it was checked and it holds.
That sentence is now in the tree at all four sites. 一个被验证过仍然成立的数字,和一个没人敢碰的数字,在文本上长得一样。
What DID move — and it is not the re-spelling
50 gated / 30 ungated became 51 / 29 at
cc837dbfec(2026-08-31T07:53Z), which guardedGET /ui/view/:object/:type— the one route inrest-server.tsthat resolved no identity.The census file already records that same event from the other side, as "
enforceAuth61 -> 64 …
registerUiEndpointswas the ONE route in this file that resolved no identity,and it is now guarded"; it simply never carried the consequence back into blocker 2.
⭐ The timing is the whole story: that guard landed the day after the census paragraph
was first written (2026-08-30 12:33Z) and 4h52m before the sentence was copied into
rest-route-ledger.ts,route-ledger.tsandauthz-conformance.test.ts(all three in469cbc991a, 2026-08-31 12:45Z). The three external copies were transcribed from thecensus rather than re-measured, so they were one off on the day they were written — which
is why four sites carried
50/30in perfect step for a week.⛔ The conclusion is untouched
Deriving "gated" from source syntax stays rejected, and posture stays a declared, reviewed
fact. The measured false-ungated rate did not fall — it rose, from 73% to 76% — so the
argument is if anything stronger, and the second spelling strengthens it independently: a
naive scanner now has to know both spellings before it can read the file even this badly.
⛔ Nothing in this PR may be read as reopening that question.
Changeset —
skip-changeset, measured rather than assumedThe obvious guess was measured wrong on another card today (a source docblock that turned
out to be emitted into
dist), so this was built and searched, not reasoned about.@objectstack/dogfoodisprivate: true— two of the four files can never be published.@objectstack/restand@objectstack/runtimeboth declarefiles: ["dist","README.md","CHANGELOG.md"]and exactly one export,"." → dist/index.*.76% false-ungated→ 0,RE-MEASURED 2026-09-08→ 0, acrossdist/,README.mdandCHANGELOG.mdof both packages.Register OpenAPI 3.1 spec + interactive docs viewer.— a docblock sentence inpackages/rest/src/rest-server.ts— is present inpackages/rest/dist/index.d.ts;Wraps ObjectKernel and provides standard orchestration for:frompackages/runtime/src/runtime.ts:64is present inpackages/runtime/dist/index.d.ts. Bothdist/index.d.tsfiles carry 170 and 100+ docblock openers respectively, so comment stripping is not what produced the zeros.RestRouteLedgerEntry,REST_ROUTE_LEDGER,RouteLedgerEntryandROUTE_LEDGEReach appear 0 times anywhere in eitherdist/. Neither ledger module is re-exported from its package index, so its docblocks have no path to published bytes.⇒ Reading: nothing published moves.
skip-changesetis available and is applied to thisPR. The difference from the other card is structural, not lucky — a docblock ships only if
its module is exported, and these two are not.
Docs drift — re-derived from a clean worktree, with a live control
The tool's zero was not taken as a clean bill.
node scripts/docs-audit/affected-docs.mjs --jsonfrom a clean worktree:"dirty": false,head d18064d5b8,diffBase 5abca1792e→ 0 docs, 2 anchors (RestRouteLedgerEntry,RouteLedgerEntry, both "a top-level interface"), 0 anchorless changes, 0 unanchored rule blocks.content/(441 files) for this change's tokens —routeManager.register,registerPerItemRoute,all 80,50 gated,guardedRouteManager, plusfalse-ungated,RestRouteLedgerEntry,RouteLedgerEntry→ 0 files each.enforceAuth→ 3 files,authz→ 9,route→ 134,/api/v1→ 98. The sweep is live; the zeros are readings, not a dead probe.enforceAuthorauthzwere read for the population or the derivation claim. None carries either; the single regex hit is an unrelated metadata-registry table row.content/docs/releases/is untouched.The three unrelated "80"s — checked and excluded, not re-filed
packages/spec/scripts/liveness/check-liveness.test.ts:413andpackages/spec/scripts/liveness/check-liveness.mts:461are 80deadledger rows;packages/spec/scripts/lib/format-type.ts:182is a third, different 80. All three wereread, none is a route registration, and
git statusshows no change anywhere underpackages/spec/. Recorded here so the next person's grep does not re-file them.One in-place fix beyond the four named lines
packages/qa/dogfood/test/authz-probe-blind-spot.census.ts:187back-references the samederivation as "(73% false-ungated)". Left alone it would have made that file contradict
itself again, 42 lines from the line this PR just repaired — the exact defect the card
exists to remove, freshly re-introduced. Same file (inside the declared surface), same
figure, same derivation, purely mechanical: updated to 76%.
Out of scope — reported, not written
⭐ A fifth carrier of these figures exists and is deliberately NOT edited here:
packages/qa/dogfood/test/authz-conformance.matrix.ts:33-36says "22 of 30 apparentlyungated
register(sites … a 73% false-ungated rate". It spells the callregister(andnever says "80", so neither of the two queries this card was scoped by can see it. It is
outside the declared four-file surface. After this PR it is the only remaining carrier of
22/30 and 73%.
A second, in this card's file but outside its named lines: the census's blocker 1 says "of
the 8 REST route mounts measured to carry no
enforceAuth" — 29 − 22 = 7 today. Notrepaired here because it is not mechanical: the 8 is tied to a ledger-grade decomposition
(3
server-only/ 3public/ 2sdk) that needs its own measurement.Both are filed together as #16954 after a duplicate check.
Verification
node scripts/pm/dispatch-gates.mjs --commandsderived 52 families; all 52 run;--ranreconciles 52 derived, 52 run, 0 UNRUN. After merging currentorigin/mainthe derivation grew to 55; the 3 new changeset families were run and are green.check:dual-build-cjs-loadsprintsPREREQUISITE NOT MET(37 packages have nodist/; it needs a whole-treepnpm build), andcheck:type-check-debt --re-measureprintsPREREQUISITE NOT METthen OOMs under the 4096 MB ceiling this box mandates and refuses to record. Neither can be moved by comments that provably do not reach anydist/.pnpm lint(eslint . --no-inline-config, the whole repo, no narrowing) → exit 0.@objectstack/rest,@objectstack/runtime,@objectstack/dogfood→ exit 0. Coverage proved rather than assumed:tsc --noEmit --listFilesinpackages/qa/dogfoodlists both edited test files in the program.@objectstack/rest184 files / 3057 tests passed;@objectstack/runtime247 files / 3504 tests passed; the two dogfood suites that read these files,authz-probe-blind-spot.test.tsandauthz-conformance.test.ts, 2 files / 80 tests passed. The rest of the dogfood tier is declared to CI — a proven narrowing, not a blind one: the complete consumer set of the edited census module is those two suites pluspackages/rest/src/rest-meta-auth.test.ts, which the full rest suite covers.Generated by Claude Code