Uh oh!
There was an error while loading. Please reload this page.
fix(docs-audit): measure the bridge cause split on the advisory path and render it in the drift comment - #12146
Conversation
…and render it `bridgeCoverageFrom` derives three causes for an unreachable bridge row (`discovery-gap` / `no-in-repo-registrar` / `undecided`), but only against a ceiling passed as its optional third argument. Only the `--bridge-coverage` CLI arm passed one; the PHASE 2 advisory path did not, so on that path every cause read `unmeasured` and the three counts were `null` — and the docs-drift PR comment, the surface a human actually reads, rendered all 177 unreachable rows as ONE population when the census says there are three. Both hops, because either alone is the half-wired state: a ceiling nobody renders is cost paid for no reader, and a render branch with no ceiling prints `unmeasured` in a nicer shape. - the ceiling is extracted to `ceilingTailsFrom` and built in ONE place, so the two arms that now publish these three buckets cannot compute them over two populations; - the advisory arm passes it; - `docs-drift-check.yml` renders the split, with the parts READ off the same object as the total and the breakdown withheld — as a stated verdict — on numbers that do not partition it; - `--self-test` pins `causes` at BOTH ends (#9433), pins the partition guard, and pins that the renderer derives no cause count of its own. Measured on f5a7f9c, 7 warm runs per arm: advisory run 0.652s -> 1.998s (+1.35s), 1950 source files walked, 1106 past the `path` prefilter, 82-tail ceiling. Same order as the ~1.4s recorded on 589758d, so the cost that could have flipped this decision has not moved. Part of #11867 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
… own reason Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Uh oh!
There was an error while loading. Please reload this page.
yinlianghui
commented
Aug 25, 2026
Dequeued by a failure that is not this PR's — no fix will be pushed here
What actually failedRead from the merge-queue branch's job log, not inferred: Verified on
The ledger is shrink-only and judged exactly, so a stale entry is a failure. Why this PR is not the cause, stated so it is checkableThis diff is ⛔ No changes pushed here. Patching a base-branch failure on a bystander PR would widen this diff for a defect it did not cause, and would leave Already filed — twice — and being fixed⛔ I did not file a third card. The duplicate scan found two independent filings ahead of mine:
Sibling PRs from this seat, same cause#12154, #12164 and #12171 are in or waiting on the queue with the same inherited failure — What happens nextThis PR waits for ⭐ Worth recording for whoever reads this thread later: the only thing separating "my PR is broken" from "main is broken" was opening the job log.#12180 notes the same trap — it surfaced there on a PR whose entire diff was inside comments. A seat that trusted "red ⇒ mine" would have spent a cycle hunting a type error in a docs-drift renderer. Generated by Claude Code |
Fixes#11867
bridgeCoverageFrom(ledgers, tails, maximalTails)derives three causes for an unreachable bridge row —discovery-gap/no-in-repo-registrar/undecided— but only against a ceiling passed as its optional third argument. Only the--bridge-coverageCLI arm passed one. The PHASE 2 advisory path did not, so on that path every ledger's cause readunmeasuredand the three counts werenull, and the docs-drift PR comment — the surface a human actually reads — rendered all 177 unreachable rows as ONE population when the census says there are three.Both hops, or neither. Either alone is the half-wired state: a ceiling nobody renders is cost paid for no reader, and a render branch with no ceiling prints
unmeasuredin a nicer shape.The cost, re-measured on this tree — the one input that could have reversed this
The card measured ~1093 files masked / ~1.4s on
589758d22. Re-measured here onf5a7f9c88, 7 warm runs of each arm, paired (thebeforearm isgit show origin/main:…run as a probe beside the modified file, so both arms read the same tree):Ceiling population, instrumented directly: 1950
packages/**source files walked, 1106 pastmaximalTailsFrom'spathprefilter, yielding the same 82-tail ceiling--bridge-coveragebuilds. The isolated census costs ~1.37–1.61s on both arms.So: 1106 files / ~1.4s today against 1093 / ~1.4s on
589758d22— the file count drifted by +13 and the time did not move. Not materially worse, so this proceeded rather than coming back as a number. The cost is paid only on a run that already carried a bridgeable symbol and already walked this same file list, against a CI job measured in minutes. The measurement and the two arms that reproduce it are recorded at the call site, with a note to re-take the decision — not absorb the cost — if that number ever grows.The breakdown, re-derived
14 / 56 / 107 against 177 was measured at
589758d22. Onf5a7f9c88it is unchanged:remediable 14 · structural 56 · undecided 107, partitioning 177.clientRowshas moved 221 → 222 andreachableis still 45, so the total moved by one row while the split did not. No drift to report.The judgment call: three-way split, not a single "N are structural" clause
The card left this open and it is decided here, on the numbers.
A single "and 56 of these are structural" clause is shorter but, at this distribution, actively misleading: it implies the remaining 121 are remediable, and they are not — only 14 are. 107 are
undecided, which means "no in-repo declaration, on a ledger that has other in-repo registrars", i.e. absence and an unreadable spelling are not distinguishable there. Collapsingundecidedinto "not structural" reads as "remediable" and reproduces the exact conflation the split exists to end — the one that aimed #11178 at widening a recognizer that was never the constraint. The dominant bucket is the unknown one, and a two-way rendering has nowhere honest to put it.So all three render, in the same order and under the same names
--bridge-coverageprints, as one bullet in the existing fold rather than three — the total and its parts cannot then drift apart onto separate conditions. Wording was tightened once after reading the rendered bytes; the bullet is ~750 chars, ~300 of which is the pre-existing sentence.The breakdown cannot disagree with the headline
The workflow's own rule at that spot — "Same names, one derivation: the headline and this line must never be able to disagree about which files went unseen" — binds here:
bridge.causesbesidebridge.unreachable), computed once insidebridgeCoverageFromand published whole. Nothing is recomputed in the renderer.undecidedoff by one renders⛔ Its cause census is BROKEN — 14 + 56 + 106 is not the 177 it claims to break down, so the split is withheld.measured: falsekeeps its honest arm for a genuinely ceiling-less run — it reports that WHY was not measured, in words, rather than three nulls or three zeroes. Also verified behaviourally.One derivation for the ceiling itself
Two arms now publish these three buckets, so the ceiling construction is extracted to
ceilingTailsFromand built in exactly one place — two spellings of the population they are computed over is how two surfaces start disagreeing about one repo.--bridge-coveragewas rewired onto it; it is not a second copy.Pins, at both ends (#9433)
affected-docs.mjs --self-testalready pinned thebridgeCoveragekey at both ends;causesnow gets the same treatment, and here both halves had been separately broken. Five new pins:affected-docs.mjscausesdocs-drift-check.ymldocs-drift-check.ymlbridge.ledgersdocs-drift-check.ymlaffected-docs.mjsThe three workflow pins read the block-scalar with full-line
//comments stripped first. That is not incidental: the negative pin failed on its own rationale on the first run, because the block namesbridge.ledgersin prose precisely to forbid deriving from it. A raw-text pin there tests the comment, not the code.Reverse-verified, each mutation confirmed on disk before the reading was taken (marker present/absent grep, not an editor exit code), each restored and re-run green afterwards, under an
EXIT INT TERMtrap so a mid-mutation kill cannot leave the tree mutated:causesrender branchbridge.ledgersBaseline and all five restores: exit 0.
Verification
At the final commit
961081924(the same commitdispatch-gates.mjsderived its list from), exit codes captured before any pipe:affected-docs.mjs --self-test— 457 cases passcheck-drift-comment.mjs— 56 cases pass across 5 fixture diff(s)check-affected-docs.mjs— exit 0node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack→ 25 matched families, pluscheck:nul-bytes— all exit 0, run underscripts/pm/os-verify-lock.sh.One family,
scripts/pm/check-governed-queue-guard.mjs, exits 1 in a local shell for a reason unrelated to this diff: it readsGITHUB_EVENT_PATH"and nothing else" and refuses to exit 0 when it cannot look. Given a real event payload for this branch it exits 0 —✅ CLEAR — the diff touches no governed surface.No changeset: root
scripts/+ workflow only, no published package source changes, so this carries theskip-changesetlabel instead.Generated by Claude Code