Skip to content

docs(rest,runtime,qa): attribute the 80 route registrations to both spellings, and re-measure the gated split - #16955

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-16307-route-census-attribution-prose
Sep 8, 2026
Merged

docs(rest,runtime,qa): attribute the 80 route registrations to both spellings, and re-measure the gated split#16955
os-project-manager merged 3 commits into
mainfrom
claude/issue-16307-route-census-attribution-prose

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

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.ts are all
this.routeManager.register( sites. Once the per-item family gained a switch-carrying
helper 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 8
registerPerItemRoute( calls. (this.routeManager.register( itself reads 73; the helper's
own 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 :82 already knew about the second spelling while :139 did not — the file
contradicted itself 57 lines apart, so a reader landing on :139 first got the pre-change
model from the very artifact that changed it. That the repair updated :82 and not :139
is an ordinary miss, not a defect in the repair.

The four sites, before → after

# site before after
1 packages/rest/src/rest-route-ledger.ts:112 "scanning all 80 this.routeManager.register( sites … reads 50 gated / 30 ungated, and 22 of those 30 are FALSE … A 73% false-ungated rate on the largest registrar" "scanning all 80 registration sites … — TWO spellings, 72 direct this.routeManager.register( sites plus 8 registerPerItemRoute( 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 note
2 packages/runtime/src/route-ledger.ts:160 identical sentence (the twin docblock) identical replacement — the two docblocks are still byte-identical after the change (md5 compared)
3 packages/qa/dogfood/test/authz-conformance.test.ts:164 "scanning all 80 this.routeManager.register( sites … reads 50/30 and 22 of the 30 ungated are FALSE … a 73% false-ungated rate on the largest registrar" "scanning all 80 registration sites … (TWO spellings: 72 direct … plus 8 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 note
4 packages/qa/dogfood/test/authz-probe-blind-spot.census.ts:139 "Scanning each of the 80 this.routeManager.register( call sites …" "Scanning each of the 80 registration sites … — the SAME two spellings the populationRule below counts, 72 direct this.routeManager.register( call sites plus 8 registerPerItemRoute( 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.

⚠️ Wording fidelity: sites 1–3 read "a 73% false-ungated rate on the largest
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 declaration
slice (bounded by the declaration's own indentation, exactly as forwarderSlice does),
PLUS registerPerItemRoute( call sites. For each site it then extracts the call's balanced
argument span — string-, template- and comment-aware — and tests that span for
enforceAuth, classifying each site by its enclosing private 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 inside
registerSecurityExplainEndpoints, and 8 genuinely ungated. Five independent numbers
reproduced 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.

commit date population direct + helper gated / ungated inside registerMetadataEndpoints shared handler const
936893f802 — census sentence written 2026-08-30 12:33Z 80 80 + 0 50 / 30 19 3
cc837dbfec — the guard landed 2026-08-31 07:53Z 80 80 + 0 51 / 29 19 3
469cbc991a — sites 1–3 written 2026-08-31 12:45Z 80 80 + 0 51 / 29 19 3
784cb92bf2 — the re-spelling 2026-09-06 13:04Z 80 72 + 8 51 / 29 19 3
5abca1792eorigin/main today 2026-09-08 80 72 + 8 51 / 29 19 3

The arithmetic.

  • population = 73 direct occurrences − 1 forwarder + 8 helper calls = 80
  • ungated 29 = 2 discovery + 2 openapi + 19 metadata + 3 form + 3 securityExplain
  • FALSE 22 = 19 (wrapping guardedRouteManager) + 3 (shared handler const)
  • rate 22 / 29 = 75.9% → 76% (was 22 / 30 = 73.3% → 73%)
  • the 19 = 11 direct + 8 helper-routed (was 19 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 through
registerPerItemRoute(, all going through the identical wrapping guardedRouteManager
and the 3 sharing one handler const in registerSecurityExplainEndpoints are untouched.
22 = 19 + 3 stands, and the population stayed 80 across it. This is exactly the figure the
card 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 guarded
GET /ui/view/:object/:type — the one route in rest-server.ts that resolved no identity.
The census file already records that same event from the other side, as "enforceAuth
61 -> 64 … registerUiEndpoints was 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.ts and authz-conformance.test.ts (all three in
469cbc991a, 2026-08-31 12:45Z). The three external copies were transcribed from the
census rather than re-measured, so they were one off on the day they were written — which
is why four sites carried 50/30 in 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 assumed

The 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/dogfood is private: true — two of the four files can never be published.
  • @objectstack/rest and @objectstack/runtime both declare files: ["dist","README.md","CHANGELOG.md"] and exactly one export, "." → dist/index.*.
  • Both closures were built, then every shipped byte was searched for the new prose: 76% false-ungated0, RE-MEASURED 2026-09-080, across dist/, README.md and CHANGELOG.md of both packages.
  • Positive control (the search fires, and source docblocks in these packages DO reach published bytes): Register OpenAPI 3.1 spec + interactive docs viewer. — a docblock sentence in packages/rest/src/rest-server.ts — is present in packages/rest/dist/index.d.ts; Wraps ObjectKernel and provides standard orchestration for: from packages/runtime/src/runtime.ts:64 is present in packages/runtime/dist/index.d.ts. Both dist/index.d.ts files carry 170 and 100+ docblock openers respectively, so comment stripping is not what produced the zeros.
  • Structural corroboration: RestRouteLedgerEntry, REST_ROUTE_LEDGER, RouteLedgerEntry and ROUTE_LEDGER each appear 0 times anywhere in either dist/. Neither ledger module is re-exported from its package index, so its docblocks have no path to published bytes.

Reading: nothing published moves. skip-changeset is available and is applied to this
PR. 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 --json from a clean worktree: "dirty": false, head d18064d5b8, diffBase 5abca1792e0 docs, 2 anchors (RestRouteLedgerEntry, RouteLedgerEntry, both "a top-level interface"), 0 anchorless changes, 0 unanchored rule blocks.
  • Hand sweep of content/ (441 files) for this change's tokens — routeManager.register, registerPerItemRoute, all 80, 50 gated, guardedRouteManager, plus false-ungated, RestRouteLedgerEntry, RouteLedgerEntry0 files each.
  • Live positive controls in the same sweep: enforceAuth → 3 files, authz → 9, route → 134, /api/v1 → 98. The sweep is live; the zeros are readings, not a dead probe.
  • The semantic half — a page can state a rule by its inputs and share no identifier with the emitter — so the 11 pages mentioning enforceAuth or authz were 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:413 and
packages/spec/scripts/liveness/check-liveness.mts:461 are 80 dead ledger rows;
packages/spec/scripts/lib/format-type.ts:182 is a third, different 80. All three were
read, none is a route registration, and git status shows no change anywhere under
packages/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:187 back-references the same
derivation 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-36 says "22 of 30 apparently
ungated register( sites … a 73% false-ungated rate". It spells the call register( and
never 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. Not
repaired here because it is not mechanical: the 8 is tied to a ledger-grade decomposition
(3 server-only / 3 public / 2 sdk) that needs its own measurement.

Both are filed together as #16954 after a duplicate check.

Verification

  • Gate familiesnode scripts/pm/dispatch-gates.mjs --commands derived 52 families; all 52 run; --ran reconciles 52 derived, 52 run, 0 UNRUN. After merging current origin/main the derivation grew to 55; the 3 new changeset families were run and are green.
  • Two of the 52 returned NOT MEASURED rather than a verdict, both declared to CI: check:dual-build-cjs-loads prints PREREQUISITE NOT MET (37 packages have no dist/; it needs a whole-tree pnpm build), and check:type-check-debt --re-measure prints PREREQUISITE NOT MET then OOMs under the 4096 MB ceiling this box mandates and refuses to record. Neither can be moved by comments that provably do not reach any dist/.
  • Lintpnpm lint (eslint . --no-inline-config, the whole repo, no narrowing) → exit 0.
  • Typecheck@objectstack/rest, @objectstack/runtime, @objectstack/dogfood → exit 0. Coverage proved rather than assumed: tsc --noEmit --listFiles in packages/qa/dogfood lists both edited test files in the program.
  • Tests@objectstack/rest 184 files / 3057 tests passed; @objectstack/runtime 247 files / 3504 tests passed; the two dogfood suites that read these files, authz-probe-blind-spot.test.ts and authz-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 plus packages/rest/src/rest-meta-auth.test.ts, which the full rest suite covers.
  • Exit codes were captured before any pipe throughout.

Generated by Claude Code

…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
`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
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026
@github-actions github-actions Bot added the size/m label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 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 — 29 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 f36eef55d44260e4492c07de4ab3fde4f587a043packageMentionDocs.

Which tree this was computed on

This run read content/docs from 77819e61e19b9c12bbde3736f4e5612295164dd2 — the merge of head d18064d5b8605dce0b4c8aeb78a1c8618e4ec523 into base f36eef55d44260e4492c07de4ab3fde4f587a043, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Copy link
Copy Markdown
Collaborator Author

PM review — accepted, arming

CI, with the collapse stated. 38 raw check-run rows; four names are duplicated across two workflow batches (Check PR Size, Packed-tarball smoke (opt-in), Auto Label, Check Changeset), so the latest-per-name collapse is 38 → 34. ⚠️ Recording something the collapse would otherwise hide: within each duplicated pair the two rows carry different conclusions — Check Changeset is success in run 34267198897 and skipped in run 34267226665 — so latest-per-name resolves to the skipped row. Under both the raw and the collapsed reading the red count is zero, so the arming decision does not turn on which one you take. All rows terminal.

Clause-② re-run by me. --pair 16955exit 0: readable in the fixed spelling, both carriers agree, no widening tell. Standing caveat recorded as the tool prints it — a tell is not a proof and its absence is not one either.

The comment-only claim, re-taken independently rather than relayed. This one assertion carries both the clause-② no and the skip-changeset, so it was not accepted on the PR's word. Stripping *, //, /* and */ prefixes from git diff -U0 <merge-base> <head> -- packages/ leaves zero added or removed lines. ⛔ A zero with no control beside it is not a reading, so the same pipeline was run against a diff known to change code (#16951, which deletes an export const): it printed export const SCAFFOLD_TSX_RANGE = '^4.21.0';. The instrument fires, so the zero here is a reading.

Fence 1 — the number 80 — held, read out rather than counted. Every removed line carrying 80 is matched by an added line that still carries 80; only the attribution changes (this.routeManager.register( sites → registration sites). No line in the diff turns 80 into another number.

Site 3's 80 sits on an unchanged context line. In authz-conformance.test.ts the repair lands on the line after the one carrying all 80. That is the wrap that made a single scoping query undercount this card, visible in the diff — a concrete instance of why a hit count is not a scope.

Fence 2 — the out-of-scope report is accurate. On this head, authz-conformance.matrix.ts:34-35 is the only remaining carrier of 22 of 30 and 73% anywhere under packages/. It spells the call register( and never says 80, so neither query this card was scoped by could reach it. Filed as #16954 with a duplicate check that names its own control word.

One gap the drift bot's notice exposed, closed by measurement. The bot computed on the merge commit (base f36eef55d4); the re-derivation and hand sweep here were taken on base 5abca1792e. Different trees, so the sweep could in principle have missed a content/ page added in between. Measured: exactly one page differs across that range — content/docs/references/automation/control-flow.mdx — and it carries zero of this change's ten tokens (false-ungated, 73%, 76%, 22 of, routeManager, registerPerItemRoute, all 80, 50 gated, guardedRouteManager, enforceAuth), against a positive control firing on that same page (flow 28, condition 14, the 63). The tree difference is immaterial to this card.

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. 50/30 → 51/29 moved at cc837dbfec, one day after the census paragraph was written and 4h52m before the sentence was copied into the other three files. The three external copies were transcribed rather than re-measured, which is why four sites carried the same wrong split in step for a week. The 22 = 19 + 3 decomposition — the figure the dispatch flagged as most at risk, since the 19 counts routes inside the very registrar that was re-spelled — did not move, and that is now written down at all four sites so it is not re-derived again.

⛔ 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

@os-project-manager
os-project-manager marked this pull request as ready for review September 8, 2026 19:34
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 07c56b5 Sep 8, 2026
40 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-16307-route-census-attribution-prose branch September 8, 2026 20:00
os-project-manager pushed a commit that referenced this pull request Sep 9, 2026
… and split the two moves inside one sentence

Five sites in the tree read 76% false-ungated; two still read the pre-repair
figures, and one file carried both — `authz-probe-blind-spot.census.ts:129`
said 8 while `:147`, eighteen lines down, said 76%. Which number a reader got
depended on which line they reached first.

Carrier 1 (`authz-conformance.matrix.ts`) is arithmetic: 22 of 30 -> 22 of 29,
73% -> 76%. The five existing 76% sites are the authority; nothing new was
measured for it.

Carrier 2 is not arithmetic, which is why #16955 left it alone: the 8 carried a
ledger-grade decomposition (3 server-only / 3 public / 2 sdk) and 29 - 22 = 7
does not say how the four numbers redistribute. Measured rather than guessed,
with an AST instrument over `rest-server.ts` — the two registration spellings
the census's own population rule counts, gatedness read from the call
expression's syntax tree so comments cannot answer for code. It reproduces the
recorded 80 / 51 gated / 29 ungated and all four pinned controls at
`5abca1792e`, the commit the census anchors its own re-measurement to, and
reads identically at `f6b7c53db7` despite 78 lines of drift in that file. The
7 mounts grade 3 `server-only` / 3 `public` / 1 `sdk` — the arithmetic-looking
answer, arrived at by measurement.

A naive text scan of the same sites reads 59/21 instead: its paren matcher runs
away on an apostrophe in a comment, spanning one site from line 5244 to 10360.
That is the failure mode this file already warns about for its forwarder slice,
and it is why the reading is taken from a parser.

The same sentence also carried `sdk` 84 / `server-only` 7 / `public` 3, which
moved for an unrelated reason — #14503 took three REST package rows out of the
ledger (94 -> 91), a re-measurement this file's own probe row already records.
Both figures are corrected and the two causes are now named apart, because a
reader had no way to tell which of them moved for which reason.

The population is still 80, deriving authorization from source syntax stays
rejected, and "the single route in this whole population ever measured
unguarded" is preserved verbatim — guarding the route did not make it false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants