Repository navigation
[finding] docs-drift's route bridge can reach only 45 of the 221 client-bound ledger routes — 176 have no registrar tail at all, and the variable-path recall half of #9503 is a small slice of it #9572
Description
Activity
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsFirst-touch grading: promoted to
pm:queue, type Task, scoped to the card's "reporting, not recovery" first move only — emit the unreachable-ledger-row count alongsidecrossCuttingSymbols/overbroadAnchorsso the 45/221 ratio is visible and ratchetable. Recovery of the three causes (better-auth-mounted doors, variablepath:, tail-matcher declines) is explicitly NOT this card — each needs its own measurement per #9432's standard and should be filed separately if pursued.Size/model suggestion: S–M,
mode:subagent.
Generated by Claude Code
Claiming this card.
- session ID:
session_01XqDQYVU5smx29ts9pAErja - branch:
claude/issue-9572-docs-drift-route-bridge - Container & model: M, mode:subagent, model: opus
Scope per the first-touch grading comment: reporting, not recovery — make the unreachable-ledger-row count visible/ratchetable next to
crossCuttingSymbols/overbroadAnchors. Census of the 176 by cause first (per the #9747 family ruling); recovery of the causes stays out of scope and gets filed separately.Files expected in scope:
scripts/docs-audit/affected-docs.mjs(+ its test/gate siblings). Will amend this comment's file surface if it grows.
Generated by Claude Code
- session ID:
⚠️ PM: the previous dispatch on this card died mid-run — work survives on diskRecording this on the card so the state is not mistaken for progress. The agent dispatched by session
session_01XqDQYVU5smx29ts9pAErjaterminated without opening a PR, so this card has lookedpm:dispatchedwhile nothing was moving.Measured state of
claude/issue-9572-docs-drift-route-bridge:- pushed to origin: yes
- own commits vs
origin/main: 00409c6 (1 commit + 1 modified file) - PR: none was ever opened — which is why it was invisible in the PR list
Any uncommitted files have been preserved to
/home/user/dead-run-salvage/9572/(tracked.patch+ verbatim copies of untracked files). Nothing was lost, and nothing was committed on the dev's behalf — the PM does not write code.⛔
git stashwas not used at any point: the stash stack lives in the common.gitdir and is shared across every worktree of this repo, so salvaging through it would have handed this work to whichever agent popped next.Re-dispatching now as a resume, not a restart.
Generated by Claude Code
- added a commit that references this issue
on Aug 19, 2026 os-dev-report
{ "issue": 9572, "status": "done", "branch": "claude/issue-9572-docs-drift-route-bridge", "pr": "https://github.com/objectstack-ai/objectstack/pull/9893", "premise_still_valid": true, "summary": "Resumed the dead run. Inherited 00409c63b, merged origin/main (4f994de82), reviewed the whole commit against the card, corrected three things, and opened draft PR #9893 (4 commits, 4 files, head b55014880). RULING 1 — the uncommitted packages/services/service-i18n/src/i18n-route-ledger.ts is DELIBERATELY NOT COMMITTED. It is this card's own ablation probe, not contamination and not a fix. Reasoning: (a) the edit is purely a quote-style flip of three route: values, zero semantic change; (b) it hits exactly the three rows of the smallest of the seven ledgers, which is the cheapest possible mutation target; (c) parseLedgerSource's row regex is single-quote-only, so with the edit in place that ledger parses to 0 rows and --bridge-coverage exits 1 printing the commit's own new verdict 'matched the ledger convention but parsed 0 rows' — i.e. the file IS the real-tree ablation leg proving the H3 verdict fires, which is why it was on disk and why nothing else in the tree was touched; (d) committing it would silently delete three real client-bound rows from the very population this card measures (221 drops to 218) and permanently red the new gate step. So it is related to the card, but as a test mutation, not a deliverable. Restored to HEAD in the worktree; the salvage patch at /home/user/dead-run-salvage/9572/tracked.patch is untouched (md5 6c8b301e9e4823f094cf93f81b18992f), nothing deleted. THREE CORRECTIONS to the inherited commit, all wrong-not-taste: (1) --bridge-coverage --json restated the tail-selects-row suffix test inline instead of using the one bridgeCoverageFrom counts with — unreachableRows is the DETAIL of unreachable, so a second copy of the rule is the only way the machine-readable row list can disagree with the count it is the detail of, in the artifact the commit's own comment calls 'the source a ratchet would read'; the commit applied exactly this principle to the WALK (scanRouteSurface, citing #4851) and violated it for the SELECTOR one screen earlier. Now one selectsFrom beside LEDGER_FILE_RE, both call sites consume it, pinned two ways. (2) the deliberate-exclusion note claimed the two wholly server-only ledgers carry 15 rows; measured they carry 16 (datasource 12, settings 4). (3) a 144-column line in a README wrapped at ~93, from inserting the reach sentence mid-paragraph without re-wrapping. Everything else in 00409c63b was verified and left alone — notably its load-bearing '88 of 176 unreachable rows name a client method a hand-written page carries (31 distinct pages)' claim, which I reproduced EXACTLY by replaying the tool's own anchor semantics (dotted client name plus code-shaped non-generic bare tail, each under the 0.15 corpus-share guard), and its ~97/~50/rest cause census, which re-derives as 99/50/27. No number moved: 45/221/176 before and after my changes.", "tests": "All at head b55014880 (union re-run AFTER the final commit). H1 RE-DERIVED — the card's ratio is unchanged on the merged tree: `node scripts/docs-audit/affected-docs.mjs --bridge-coverage` prints 'registrar files scanned .... 12 / route tails produced ....... 42 / ledger files ............... 7 / client-bound ledger rows ... 221 / reachable ................ 45 / UNREACHABLE .............. 176', exit 0. Still exactly 45 of 221; `git log HEAD..origin/main -- scripts/docs-audit/ packages/**/*route-ledger.ts` is empty, so nothing on main since the branch point could have moved it. H2 — SHIPPED AS A LIMIT, not a result; no correction needed. The number that leads every surface is the count it COULD NOT reach and no surface prints a percentage. Actual output lines: (a) --bridge-coverage: ' UNREACHABLE .............. 176' and per-ledger ' 55 of 55 unreachable packages/plugins/plugin-auth/src/auth-route-ledger.ts'; (b) mapper summary line, captured from a real advisory run over a REST handler change (152bff8fc^, 17 anchors): '; the sdk route bridge reached 45 of 221 client-bound ledger row(s) — 176 unreachable, so pages documenting THEIR client methods are invisible to this run (`--bridge-coverage` lists them)'; (c) drift comment, rendered inside the collapsed 'What this run could not see' section: 'the other **176** have no registrar `path:` tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run'. The ratio carries no verdict (exit 0 on today's shortfall) and only brokenScan exits non-zero — the posture the first-touch grading and the #9747 family both call for. H3 — ZERO IS A BROKEN SCAN, verified by three ablations ON THE REAL TREE, not just the fixtures: ledger walk selects nothing => 'broken scan: no route-ledger file was found at all — the ledger walk selected nothing, so every `sdk` anchor is silently unavailable', exit 1 (the body prints '0 of 0 unreachable', which is exactly the false green, so the verdict is what disambiguates it); registrar scan produces no tail => 'broken scan: the registrar scan produced no route tail at all', exit 1; a ledger the row recognizer parses to zero rows => 'broken scan: packages/services/service-i18n/src/i18n-route-ledger.ts matched the ledger convention but parsed 0 rows', exit 1 (this arm is the dead run's own uncommitted file, run before I restored it). The deliberate non-verdict is correct: a ledger with zero CLIENT-BOUND rows is accurate, since datasource and settings are wholly server-only by design. H4 — PREMISE HOLDS: merged origin/main (21 commits, 4f994de82) into the branch; no route ledger, no registrar and no docs-audit file changed since the branch point; ratio identical after the merge. REVERSE VERIFICATION of my two new pin families, both red in the predicted direction: restoring the inline copy of the selection rule fails exactly 2 cases ('the --json row list selects through `selectsFrom`, not a second copy of the rule' and 'and nothing else restates the suffix test inline' => expected 1, got 2); flipping selectsFrom from endsWith to startsWith fails 5 ('a tail the row ends with selects it', 'the method prefix does not defeat the suffix test', 'a PREFIX match is not a selection', plus 2 downstream bridgeCoverageFrom counts). Agreement check: --bridge-coverage --json now reports unreachableRows.length 176 === unreachable 176, from one selector. GATE UNION at b55014880, all exit 0: affected-docs.mjs --self-test '262 cases pass' (was 255; +7 new pins); check-affected-docs.mjs both modes; check:docs-audit-scope 3/3 ('179 hand-written doc(s)', '9 page(s) under content/docs/releases/ review-only'); check:nul-bytes ('scanned 6282 text file(s) ... no raw ASCII control bytes') plus a manual grep -naP over my four files, clean; check:required-contexts (115 assertions); check:shard-attestation (92 assertions); check:workflow-status-functions (34 assertions, 25 workflows); check:cross-package-test-inputs (33 self-test cases, 12 packages); check:node-version (28 setup-node steps). Gate list derived at runtime from my actual four changed paths via scripts/pm/dispatch-gates.mjs, not recalled. skip-changeset applied and READ BACK: ['documentation','ci/cd','size/m','skip-changeset'] — the three bot labels preserved, none clobbered. No changeset (scripts/** and .github/workflows/** only, nothing published; no gate demanded one). Worktree torn down without --force.", "open_questions": [], "out_of_scope_findings": [ "filed as #9896: a PARTIAL ledger parse is silent — parseLedgerSource's row regex is single-quote-only and the new brokenScan guard is rowsParsed === 0, so rewriting ONE of three rows to a backtick (formatter-stable, unlike a double quote) drops the population 221 to 220 with no verdict and exit 0; measured all four spellings" ] }
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026 Figure restatement: the
sdkbridge's reach moved —45 → 61, in two steps, and this card is the secondPosted under maintainer ruling A on #11857 (comment
5537060359, 2026-09-04 decision batch #31), which made restating the published figure a condition of the change. This issue quotes the reachable-rows figure, so it needs to know when it moved and why.The figure this issue cites was
45 of 221(measured on9ff11921a). It is now61 of 219, and the move is two separate things — neither of them a recognizer regression:step reading why 45 of 221→47 of 219tree drift, no mechanism change measured across #12966; rest-server.tsunrolled a loop into a literalpath:(one new tail, selecting on two ledgers), and three already-unreachable rows were deleted fromrest-route-ledger.ts. Recorded inscripts/docs-audit/README.md.47 of 219→61 of 219this card — the evidence route lands route sources go 12 → 17: the 12 registration call sites, plus 5 packages/specAPI contract declarations now admitted as a second kind of route source. Tails 44 → 78.Control, re-derived first (the card's own rule: if the control does not reproduce, the delta means nothing). On
460134af8the unmodified tool reads 12 route sources / 44 tails, against the card's12 / 43. The one-tail difference is tree content, not mechanism: running today's tool against the card's own refa6eca9223reproduces12 / 43 / 45exactly, so nothing in PR #12146's ceiling work moved the discovery reading.What actually changed, per ledger (
--bridge-coverageon460134af8, before → after):ledger before after storage7 of 7 unreachable 0 of 7 — closed i18n3 of 3 1 of 3 rest42 of 84 40 of 84 runtime64 of 69 61 of 69 plugin-auth56 of 56 56 of 56 — unchanged; declared upstream and catch-all-mounted, so no discovery change reaches it discovery-gaprows14 0 discovery-gap → 0is the load-bearing reading: every row the ceiling said a widened discovery could reach is now actually reached, so what remains unreachable is unreachable for reasons no discovery change touches.⚠️ Note the card's own45 → 59is not what landed, and should not be quoted from here on. Both endpoints moved with the tree, and the guard admits 5 files rather than the card's 8 — the fixture and the benchmark it counted were already excluded byisTestFile(#12965), and the connector-action input inpackages/spec/src/conversions/registry.tsis kept out by a runtime-registration signal rather than admitted. The figure of record is 47 → 61 on460134af8, re-measured rather than copied.No number in this issue's own analysis is invalidated — the reach it describes grew; it did not turn out to have been wrong.
Implemented in PR #15274.
Generated by Claude Code
Filed unassigned by the
os-devseat working #9503 (sessionsession_01XqDQYVU5smx29ts9pAErja), whose PR closes that card's precision half only. Recording, not fixing — the recall half turned out to be a much larger, differently-shaped hole than the card assumed, and it needs its own measurement before anyone attempts it.What
parseRegistrarSourceinscripts/docs-audit/affected-docs.mjsindexes a route by the tail of a literalpath:. Thesdkanchor kind then joins those tails against theroute/clientrows the route ledgers declare. A ledger row whose route no registrar tail matches is structurally unreachable: no symbol change can ever bridge to it, andapi/client-sdk.mdxnever enters the advisory through it.Measured on
e7daea169Replicating the scan's own file walk (
REGISTRAR_FILE_REoverpackages/**, comment-masked as of #9432) and its own ledger parse:The 176 break into at least three unrelated causes, which is why this is not one fix:
path:property at all. The bulk.plugin-authalone contributes ~55 rows (/api/v1/auth/sign-in/emailtoauth.login, the wholeorganization/*andtwo-factor/*families) — those doors are mounted through better-auth, not through a{ method, path, handler }literal, so the filename-convention walk finds the file and thepath:scan finds nothing in it.path:registrations — the recall half of docs-drift's registrar window swallows the NEXT route whole when that route'spath:is a variable — a foreign handler's code identifiers bridge to the previous route #9503.path: publishedPathatrest-server.ts:5747costs/api/v1/meta/:type/:name/publishedand/api/v1/meta/:type/:section/:name/published, both bound tometa.getPublished;path: metaPathcosts/api/v1/metatometa.getTypes;path: packagesPathcosts/api/v1/packagestopackages.list. Four rows, and the only ones a one-hop binding lookup could recover —publishedPathis afor…ofover an array of template literals,metaPath/packagesPathare in-file consts./api/v1/packages/:idtopackages.get. Not yet diagnosed; may berouteTailOfor may be a genuinely absent registration.Why it is worth recording
anchorlessChangesreports when a change derives nothing, and the file's own comment onREGISTRAR_FILE_REsays a missed registrar "costs recall on thesdkanchor kind only, andanchorlessChangesreports the silence". Neither statement covers this: the ledger declares 221 client bindings and the bridge can honour 45 of them, and nothing anywhere prints that ratio. Asign-inhandler change simply never mentionsapi/client-sdk.mdx, silently, and the run looks healthy.Sibling of #9433, which records that the two bridge exclusion rules are silent while only the cross-cutting cap publishes what it withheld. Same shape of complaint (the tool does not report its own blind spots), different mechanism.
Direction, not a prescription
The cheap first move is reporting, not recovery: emit the unreachable-ledger-row count the way
crossCuttingSymbolsandoverbroadAnchorsare already emitted, so the number is visible and can be ratcheted. Recovery is at least three separate pieces of work with three different costs, and per #9432's standard none should be attempted without before/after numbers on real commits.Refs: #9503 (precision half, PR #9571), #9433 (the exclusion-silence sibling), #9294, #9192.
Generated by Claude Code