fix(pm): the mixed-diff diversion on PR #15284 fired and lawfully found nothing to fork — the three report-side readings that made it look like a failure - #15650
Conversation
…ct lift no longer reports itself as a clear that matched nothing (#15406) The merge-queue log for PR #15284 printed, one line under its own `LIFTED skills/objectstack-ui/references/react-blocks.md` note: ✅ CLEAR — the diff touches no governed surface, so this guard has nothing to judge. … ⛔ ZERO review lookups were made: the path test runs first and returns Both sentences are false for that run. The path test MATCHED (the diff's eleventh file is on the `skills/**` surface), and the register's own recompute ran and certified it. Read back from the log, a compliant landing under the 2026-09-01 generated-artifact ruling is indistinguishable from a guard that never saw the file. Report-only: `guardVerdict` now carries the paths the register lifted (default `[]`), and the `clear` rendering picks between the zero-cost clear — kept BYTE-FOR-BYTE on both legs, so the 2026-08-27 pull_request byte-identity constraint is untouched — and a clear reached through a lift, which names the lifted paths and says the recompute ran. No predicate, verdict, exit code or API cost changes. `liftedPathsBetween` derives what was lifted from the row lists on either side of `liftGeneratedExceptions`, not from its prose notes, and is deliberately conservative across rows (the #11084 fence is per-row). Self-test: 133 → 144 cases; new battery replays #15284's real 11-path file list, one commit, PR 15284, zero reviews of any kind. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
… it does not recompute, and --test stops reporting a post-lift zero as a clean read (#15406) Two report-side readings turned a compliant landing into an incident card. 1. `renderTestVerdict`'s head counts `hitPaths`, which is the POST-lift set. On PR #15284 it printed "0 of 11 path(s) hit the register" immediately above the exception line naming the path that hit it. The count keeps its meaning (what is STILL governed) and now says when the register lifted the difference. Byte-identical when nothing was lifted. 2. The sweep classifies with `governedPathsIn` alone and never consults the exception register — deliberately: provenance is a recompute against the tree a commit landed on, and this sweep holds no such tree. The row it rendered for #15284 was therefore indistinguishable from one for a hand-authored governed merge. `registerCell` adds the missing reading: which register row the governed path belongs to, that this sweep does NOT recompute, and that certification is recorded in that landing's queue-guard log. It lifts nothing and suppresses nothing — the row is still listed and still counts as a governed merge — and it repeats the register's own doctrine rather than softening it: a candidate earns the QUESTION, never the answer. Membership is the register's own `generatedExceptionFor`, so no second mechanism is authored (#11705's ruled constraint). Self-test: 263 → 274 assertions, new battery replaying #15284's shape in both directions (all-registered, mixed with hand-authored content, and none). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
|
ACCEPT — PM seat Reviewed against the three-dot diff at Root cause accepted as measured: the queue guard ran on #15284's merge group (job 101026960644) and cleared through a certified
Measured by this seat on the PR head (throwaway worktree, PM checkout's The merges self-test is NOT MEASURED here (its live battery runs the real generator); the dev reports 274 assertions with the live line, and CI's Fences held: two files under The dev's two open questions are recorded here, not decided: the sweep-side recompute (options A/B/C, C shipped) and the ruling-intersection reading both belong to #15406's governance half. Flipping ready + enabling auto-merge; the card stays open ( Generated by Claude Code |
Part of #15406 — the MECHANISM half only. The governance ruling half (whether the merge was
compliant, whether anything is rolled back, whether
os-justinis a principal) is the projectdirector seat's, and nothing here decides or implies it.
Root cause: the diversion evaluated PR #15284 and lawfully found nothing to fork
The queue guard ran on #15284's merge group and printed this, verbatim
(run 33874147578, job 101026960644,
merge_groupongh-readonly-queue/main/pr-15284-dc46c4ec1,head
f502898a49530a1c85e58f3c4d2b340c0e1cb909, 2026-09-04T12:43:16Z):So the leg that owns the outcome is the register, not a broken predicate. #15284's single
skills/**path isskills/objectstack-ui/references/react-blocks.md, which matches thespec-react-blocksrow ofGENERATED_SURFACE_EXCEPTIONS(ruling #11705). Its provenance wasrecomputed on that tree, certified byte-exact, and the path was lifted — after which the diff
held no governed path and the mixed-diff rule had nothing to fire on. That is the ruled
behaviour under 2026-09-01 (「纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧」).
The hunk corroborates it — one table row whose description column is derived from the
grouping.describe()this same PR edited inpackages/spec/src/ui/view.zod.ts, truncated bythe generator:
Reproduced live on today's tree, with #15284's real 11-path file list through the seat-side
predicate — the three tools agree, as #11705's "⛔ do not author a second mechanism" requires:
Why the four existing mixed-diff assertions did not "cover" this: they do cover it. They
assert that a single live governed hit governs the whole PR. #15284's only governed hit was
not live at verdict time — it was lifted — and the self-tests already carried that case too
(
with-the-toolchain-installed-a-PURE-REGENERATION-merge-group-CLEARS-with-zero-approvals-and-zero-api-calls).No assertion was missing. ⇒ No predicate is patched here.
What IS broken, and it is the reporting
Three report-side statements denied the register hit they had just processed, and together they
are why a compliant landing reads back as a mechanism failure:
check-governed-queue-guard.mjs— theclearrendering asserted "the diff touches nogoverned surface" and "the path test runs first and returns" one line under its own LIFTED
note. Both are false for that run: the path test matched, and the recompute ran.
check-governed-merges.mjs—renderTestVerdict's head counts the POST-lift set, so itprinted "0 of 11 path(s) hit the register" directly above the exception line naming the hit.
check-governed-merges.mjs— the sweep classifies withgovernedPathsInalone and neverconsults the register (deliberately: provenance is a recompute against the tree a commit
landed on, and the sweep holds no such tree). The row it renders for a certified regeneration
is therefore indistinguishable from a row for a hand-authored governed merge.
All three fixes are report-only. No predicate, verdict, exit code, API cost or listing changes;
the zero-cost clear keeps its wording byte-for-byte on both legs, so the 2026-08-27
pull_requestbyte-identity constraint is untouched.registerCelllifts nothing and suppressesnothing — the row is still listed and still counts as a governed merge — and it repeats the
register's own doctrine rather than softening it: a candidate earns the QUESTION, never the
answer, and certification is recorded in that landing's queue-guard log.
Timeline facts for #15284 (reported, not judged)
49f19218b—chore(spec): regenerate api-surface / export-origins / references …ready_for_reviewauto_merge_enabledadded_to_merge_queuereact-blocks.mdLIFTEDmergedasf502898a4removed_from_merge_queueReviews: none. The timeline carries zero
reviewedevents of any state — no APPROVED,no CHANGES_REQUESTED, no DISMISSED, by any account. The guard did not need one: with the only
governed path lifted it returned before any review lookup existed (
0 API read(s)).No late push. The
skills/**file entered the diff at49f19218b(11:37:28Z), 65 minutesbefore the enqueue and 66 before the guard ran — so the classic "added after the guard ran"
bypass is ruled out. The guard also ran twice on the PR head (
49f19218b) as thepull_requestearly warning, 11:56:24Z and 12:40:51Z, bothsuccess.Not done here, and why. The dispatch's optional report-only approval column is not
added:
check-governed-merges.mjsreads only/pulls/{n}formerged_by/merged_at(
fetchPullAttribution), so an approval column would be a new API surface, which the briefforbids.
Reproduction case
A new self-test battery in each script replays #15284's exact shape: its real file list (11
paths in
check-governed-queue-guard.mjs, the 4-path core incheck-governed-merges.mjs), onecommit, PR 15284, zero reviews of any kind, an approver set (
GOVERNED_APPROVERS) that doesnot contain the merging account, and a recompute that certifies
react-blocks.md. The queuebattery drives
main()'s own wiring — lift, derive what was lifted, judge — rather than theexisting
endToEndhelper, because the defect lived in the step between those two.Both directions are pinned: with the recompute certifying, the verdict is still
clear/exit 0with
apiCalls === 0; with the recompute refusing (a hand edit to the same path), the identicalfile list still REFUSES with
EXIT_REFUSED_UNAPPROVED. The shape is not an exemption forreferences/react-blocks.md; it is an exemption for a recompute.Ablation
Each fix was reverted on a committed tree and the batteries re-run. Mutation and restore were
both confirmed on disk (injected-marker / removed-text counts, then
git checkout HEAD -- pathverified against the HEAD blob hash, with
git diff HEADempty), and each script carried atrap … EXIT INT TERMrestore.check-governed-queue-guard.mjsconst lifted = verdict.lifted ?? []→const lifted = [](the pre-fix unconditional clear wording)✗ 3 of 144 case(s) failed, exit 1 — including⭐ the-CLEAR-line-does-NOT-claim-the-diff-touched-no-governed-surfaceand⭐ the-CLEAR-line-does-NOT-claim-the-path-test-returned-before-matchingcheck-governed-merges.mjsregisterCellreturns''immediately; the lift clause on the--testhead forced off✗ check-governed-merges --self-test — 7 failure(s), exit 1Restore legs:
RESTORE OK: … == HEAD blob 240297fb1f955c2eefaadb9c12389b763fe331eaandRESTORE OK: … == HEAD blob 081df968d6900cdb636af2570727e03390e73778,git diff HEADempty inboth.
Verification
Derived, not recalled:
node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack(exit 0) — change set read by the tool itself from the merge base
d30ccb9bd, two paths,scripts/pm/check-governed-merges.mjsandscripts/pm/check-governed-queue-guard.mjs. All 32emitted commands were run; every one exited 0. The union was run on the final commit
9832369e9.node scripts/pm/check-governed-queue-guard.mjs --self-test✓ check-governed-queue-guard self-test: 144 cases pass …(was 133)pnpm check:pm-governed-merges(=check-governed-merges.mjs --self-test)✓ check-governed-merges --self-test: 274 assertions …+live: the real generator declared 9 output(s) and certified this tree(was 263)pnpm check:pm-dispatch-gates✓ dispatch-gates self-test: 1445 cases pass. (no case added by this PR)pnpm check:nul-bytesgrep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over both edited files: no match)pnpm check:pm-governed-proseAlso run, because their rosters live under
scripts/orscripts/pm/— the directories thisdiff is in — where
dispatch-gatesscores themsilentand says that silence is evidence inneither direction:
check-published-list-mirrors.mjs(+--self-test),check:console-injection,check:engine-double-contract,check:i18n-stale-fill,check:pm-label-desc-cap— all exit 0.NOT MEASURED, by name:
pnpm check:published-readme-exports— exit 1, but a PREREQUISITE failure, not a finding:every message is
… does not exist. Build first: pnpm build, overpackages/**READMEs thisdiff does not touch. This worktree has no
dist/. It is also a roster gatedispatch-gatesdid not derive for these paths.
check-cross-package-test-inputs --union-into "$RUNNER_TEMP/…",check-shard-attestation --shard ${{ matrix.shard }},check-test-completeness "$RUNNER_TEMP/test-core.log") — no value outside a CI run.dispatch-gatesreports as CI-MEASURED ONLY (it reads the workflow eventpayload).
scripts/pm/**publishesfrom no package, so this PR carries
skip-changeset.pnpm lintwas not run. Narrowed instead, and the narrowing is NOT proved to thethree-part standard, so it is reported as not run rather than as a measurement.
Scope
Two files, both
scripts/pm/**:check-governed-queue-guard.mjsandcheck-governed-merges.mjs.dispatch-gates.mjsis not touched — itsskills/**classification row is correct and was not the root cause.
.github/workflows/**,.claude/**,skills/**,AGENTS.md,CLAUDE.mdanddocs/adr/**are untouched. No changeset(
scripts/pm/**publishes from no package);skip-changesetapplied.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code