Repository navigation
check-governed-merges: measure whether a governed-surface PR landing via a merge-queue batch is visible to the audit (#11831 ruling) #11996
Description
Activity
Claim: PM loop round R1 W1
Session:session_01MnijPVVDakqK2J335JoJtq
Branch:claude/issue-11996-governed-merges-queue-batch
Worktree:objectstack-issue-11996
Domain:domain:skills
File surface:scripts/pm/check-governed-merges.mjs(+ its self-test/pin surface; measurement-first — a recognition fix rides only on a measured blind verdict, per the card's disposition rule) (stop on breach; explain in the report)
Container & model: M (measurement + conditional recognition fix),mode:subagent,model: opus— this dispatch'snode scripts/pm/dispatch-gates.mjs --tier scripts/pm/check-governed-merges.mjs(tree0043c92): "no path-derived mandate … floor sonnet · default opus · ceiling fable"; measurement design is a judgment face, so default opus.
Clause-②: no — audit tooling; the card pre-rules tightening-only; no contract accept/reject behaviour change, no public surface widened.
Serial constraints cleared: no open PR and no in-flight claim touchesscripts/pm/check-governed-merges.mjs(all 11 open PRs walked by head branch at dispatch time; lane in-flight #12220-chain and #12518 are disjoint surfaces). No sibling card's pins assert this script's behaviour.
Generated by Claude Code
Dev claim (subagent under the PM claim above; identity is the branch, since all seats share one GitHub login).
Branch:
claude/issue-11996-governed-merges-queue-batch
Worktree:objectstack-issue-11996
Base:0043c9224(origin/mainat dispatch)
File surface:scripts/pm/check-governed-merges.mjsplus its self-test / pin surface. Measurement-first: no code is written unless the measurement returns a blind verdict, per this card's disposition rule.Assignee left untouched (
os-litant, set by PM).
Generated by Claude Code
Generated by Claude Code
Verdict: the audit SEES a governed-surface PR that lands through a merge-queue batch
Measured, not reasoned from the gate's source. Every batch topology below lists the governed file; zero of six batch topologies are blind. Per this card's disposition rule that confirms the post-hoc-audit model (2026-08-18 ruling) covers the queue route, so no code changed and no PR opened. Closing is left to PM review.
Branch
claude/issue-11996-governed-merges-queue-batchcarries no commits, deliberately.
1. What a queue batch on this repo actually looks like (measured live, not assumed)
A real batch was in flight during the measurement:
$ git ls-remote origin 'refs/heads/gh-readonly-queue/*' 4be2e880e7… refs/heads/gh-readonly-queue/main/pr-12622-cbacc9691c… cbacc9691c… refs/heads/gh-readonly-queue/main/pr-12625-0043c92240… aa5994e17a… refs/heads/gh-readonly-queue/main/pr-12626-4be2e880e7…The branch names pair each entry to the sha it stacks on, and the chain that produces is:
$ git log --graph --format='%h [%p] %s' aa5994e17a --not 0043c92240 * aa5994e1 [4be2e880] feat(spec): Operation Message Catalog gains refusal-situation keys … (#12626) * 4be2e880 [cbacc969] test(cli): make the app-anchored serve e2e prove a BOOT, not just a resolution (#12622) * cbacc969 [0043c922] test(rest): pay down 17 of the test-typecheck ledger's 37 errors (#12625)So the batch signature here is: one single-parent squash commit per PR, stacked on the previous entry, committer
GitHub, subject ending in the trailing(#N)parenthetical;mainthen fast-forwards to the batch tip. Confirmed end-to-end mid-measurement —origin/mainmoved0043c9224 -> 4be2e880ewhile this work was running, i.e. the very commits fetched above from the queue branch becamemainby fast-forward.Corroborating the shape across history, on 3613 first-parent commits of
main:- zero multi-parent commits on the first-parent chain;
- committer is
GitHubon 2001 of 2002 sampled; - 533 of 2001 consecutive pairs land within 60 s of each other — batches, not 533 hand merges.
That matters because it settles which topology is the live one rather than leaving it to inference.
2. Real-history replay: the real sweep over a real batch carrying two governed PRs
A real 5-commit / 24-second batch on 2026-08-26 contained two governed landings. Running the gate unmodified:
$ node scripts/pm/check-governed-merges.mjs --repos objectstack --since 2026-08-26T05:00:26Z governed-merges sweep: 3 governed merge(s) since 2026-08-26T05:00:26.000Z across 1/1 governed repo(s) scanned 75 mainline commit(s); 3 API lookup(s). • objectstack-ai/objectstack PR #12443 — docs(pm-dispatch): repo-class governance split … (#12443) commit ade757fb3 @ 2026-08-26T05:00:49+00:00 surfaces: .claude/** ×2 - .claude/skills/pm-dispatch/SKILL.md - .claude/skills/pm-dispatch/references/decision-analysis.md • objectstack-ai/objectstack PR #12440 — Reconcile os-dev contract and pm-dispatch channel references … (#12440) commit 04600d939 @ 2026-08-26T05:00:27+00:00 surfaces: .claude/** ×3 - .claude/agents/os-dev.md - .claude/skills/pm-dispatch/references/platform-readings.md - .claude/skills/pm-dispatch/references/rest-channel.md EXIT=2Both governed queue landings are listed, with the right PR numbers and the right file lists.
EXIT=2is the attribution leg only — every channel answered HTTP 403 at this container's proxy, which the gate classifies as an ENVIRONMENT problem and prints asmerged_by UNAVAILABLE. It is not a missed entry; the entries are on the list.3. Constructed-signature matrix
Because the live queue only produces one topology, the rest were constructed: throwaway repos carrying each merge-queue batch shape, each landing the same governed edit (
.claude/skills/pm-dispatch/SKILL.md, PR #22222) and differing only in the topology it arrives by, then swept by the real script via--repo-root objectstack=PATH. Attribution was pointed at a dead endpoint so the run measures enumeration, not network.topology what it is result QS-1 squash-batch shape replay of the LIVE signature in section 1 SEES — listed, attributed to the governed PR QS-2 merge-commit batch queue with the merge-commit method: one two-parent merge per PR, ff onto main SEES — listed, attributed to the governed PR QS-3 single-batch-merge main advances by ONE two-parent merge whose second parent is the whole queue chain SEES the file, attributes it to #11111 (the PR named in the merge subject) QS-4 octopus-batch-merge one commit, N parents, governed inside a non-first parent SEES the file, pr: null— listed as its own loud entryQS-5 rebase-batch linear, original author dates preserved, committer dates at landing SEES — listed, attributed to the governed PR QS-6 same-second batch at the boundary whole batch shares one committer second and --sinceis set to exactly that secondSEES — listed, attributed to the governed PR Blind topologies: 0. The governed file reaches the list in all six.
The two collapse shapes deserve their qualification stated rather than buried: in QS-3 and QS-4 the governed file is listed but the PR column is wrong or empty. That does not defeat the audit's contract — the list's question is "does the maintainer recognise every entry", and a governed surface with a
.claude/**file on it is exactly what prompts that recognition;pullNumberFromSubjectis a convenience column, and the header already rules that a mainline commit naming no PR "is listed as its own loud entry". It is also not a live gap: this repo's queue does not produce either shape (section 1). Recording them so a future merge-method change is a known quantity, not a surprise.4.
merged_atcollapse andmerged_bysemanticsmerged_atis display-only. It is read once intoattribution.mergedAtand rendered once in the report line; it is used in no filter, no key and no dedupe. The window is cut on the commit's committer date bygit log --since, never onmerged_at. So a batch collapsing several PRs onto one merge timestamp cannot cost the list an entry.merged_byreados-stevefor both governed queue landings in section 2 — a seat account, not the maintainer's. Per the header's own 2026-08-18 caveat that is not by itself the violation signal:merged_bynames an ACCOUNT, not a PRINCIPAL, and the maintainer also operates the seat accounts. Recording it verbatim as the header instructs. This is the first reading on this repo that is nothotlong, so the header's "if a future reading shows a bot login" note is worth widening to "a login other than the maintainer's" — but no bot login appeared, so no remap is proposed.
5. Baseline
node scripts/pm/check-governed-merges.mjs --self-testunder the shared verify lock:✓ check-governed-merges --self-test: 157 assertions (…) live: no generator toolchain in this environment (the generator's own --check exited 254) — the row failed closed, which is the ruled answer here os-verify-lock: VERDICT command-exit 0 · held the lock 1s · waited 0sNote for the record: this card says "129 assertions green today". The current count is 157 — the register grew between filing and dispatch. Nothing in this card changed it.
6. One thing the measurement turned up that is NOT this card's question
The queue writes an entry's commit at build time and marks the PR merged at land time — measured here as a 25 to 32 minute skew (#12440 commit 05:00:27Z vs
merged_at05:25:22Z; #12443 commit 05:00:49Z vs 05:33:08Z). That puts 10 committer-date inversions onmain's first-parent chain in the last 3612 pairs, two of them governed, and a--sinceboundary landing inside such an inversion drops the governed entry with nothing marking the gap.That is a property of the sweep's window, not of the batch topology this card asks about, and it does not open under the documented default (
--since 24h; the largest measured skew is 1.012% of that window). So it is filed rather than fixed here: #12633, with the evidence and four options.7. Reproducing this
Sections 1, 2, 4 and 6 need no rig — they are
git/gh-free commands against this repo plus two PR reads, and every command is quoted above. Section 3's constructed repos came from a throwaway harness (single file,git commit-treeplumbing, ~250 lines) that was not committed, since this card's disposition rule forbids a code change on a "sees" verdict. If PM wants that matrix to stay covered as a pin, that is a follow-up worth ruling on — say so and it can ride its own card.
Generated by Claude Code
Generated by Claude Code
os-dev-report
{ "issue": 11996, "status": "done", "branch": "claude/issue-11996-governed-merges-queue-batch", "pr": null, "premise_still_valid": true, "summary": "Measured verdict: check-governed-merges.mjs is NOT blind to a governed-surface PR landing through a merge-queue batch. Zero of six batch topologies are blind — the governed file reaches the audit list in all of them. The live signature was measured from real in-flight gh-readonly-queue branches (one squash commit per PR, stacked, main fast-forwards; confirmed when origin/main moved 0043c9224 to 4be2e880e mid-run), and a real sweep over a real 5-commit/24-second batch listed both governed landings it carried (#12443, #12440) with correct PR numbers and file lists. Per the card's disposition rule a 'sees' verdict means evidence on the card and NO code change, so the branch carries zero commits and no PR was opened; closing is left to PM. Two collapse shapes (single-batch merge, octopus) list the governed file but misattribute or drop the PR column — recorded as robustness observations, not live gaps, since this repo's queue produces neither.", "tests": "No code changed, so nothing was built, mutated or ablated — there is no ablation leg to report and no changeset is owed. Gate family for this branch, at final tree 0043c9224 (`git rev-parse --short HEAD`): `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` -> \"this branch changes nothing against 'origin/main' (merge base 0043c9224) — nothing to derive\"; the --repo assertion held against this checkout's origin. Baseline pin, run under the shared lock with OS_VERIFY_LOCK_SLOT=issue-11996: `bash scripts/pm/os-verify-lock.sh -c 'node scripts/pm/check-governed-merges.mjs --self-test'` -> gate's own verdict line \"check-governed-merges --self-test: 157 assertions\" plus \"os-verify-lock: VERDICT command-exit 0 held the lock 1s waited 0s\" (the card's '129 assertions' is stale; the register grew between filing and dispatch, and nothing here changed it). MEASUREMENT RUNS, all foreground: (1) real sweep `node scripts/pm/check-governed-merges.mjs --repos objectstack --since 2026-08-26T05:00:26Z` -> \"3 governed merge(s) ... scanned 75 mainline commit(s)\", listing PR #12443 (.claude/skills/pm-dispatch/SKILL.md + references/decision-analysis.md) and PR #12440 (.claude/agents/os-dev.md + 2 references), EXIT=2 from the attribution leg only (every channel HTTP 403 at this container's proxy, which the gate classifies as ENVIRONMENT and prints as merged_by UNAVAILABLE — not a missed entry). (2) Eight-topology constructed matrix swept by the real script via --repo-root objectstack=PATH, each landing the same governed edit and differing only in topology: QS-1 squash-batch (live signature replay) SEES; QS-2 merge-commit batch SEES; QS-3 single-batch-merge SEES the file, attributes to the wrong PR; QS-4 octopus SEES the file, pr null (loud entry); QS-5 rebase-batch SEES; QS-6 same-second batch at the boundary SEES; blind topologies 0. (3) Boundary probe on real history: --since 2026-08-14T05:55:02Z gives 190 governed entries with 01a7337fc0 absent, --since 2026-08-14T05:44:52Z gives 191 with it present — the finding filed as #12633. Ancestry direction proven, not inferred: `git merge-base --is-ancestor 98eec7e248 01a7337fc0` exits 0. History shape: 3613 first-parent commits, zero multi-parent, 2001/2002 sampled committer GitHub, 533/2001 consecutive pairs within 60s.", "mcp_calls": "10 — 2 issue reads on this card, 2 pull_request_read (#12443/#12440 for merged_at/merged_by), 1 targeted search_issues for dedupe (REST channel switch declared below), 1 issue_write to file #12633, 3 add_issue_comment (dev claim, evidence, this report), 1 get_comments read-back of the evidence comment", "open_questions": [ { "question": "Should the eight-topology constructed-signature matrix be landed as a pin, so a future change to the repo's merge method (or to mainlineCommits/commitPaths) cannot silently make the audit blind to a batch shape it handles today? The card's disposition rule attaches a pin only to the BLIND branch, so on a 'sees' verdict there is no instruction covering this, and the harness was deliberately left uncommitted rather than widening the file surface on my own judgment.", "options": [ "A — land the matrix as a --self-test case set inside check-governed-merges.mjs (each topology built with git commit-tree plumbing in a temp repo). Highest fidelity, but the self-test is currently offline-and-pure and this would give it real git I/O and temp dirs.", "B — land it as a separate scripts/pm test that CI runs, keeping --self-test pure. Cheaper to keep honest, one more gate in the farm.", "C — land nothing. The measurement stands as a dated record on this card; re-measure if the merge method ever changes." ], "recommendation": "B, on a follow-up card of its own. The property worth protecting is real — 'the audit sees a queue batch' is exactly the kind of fact that decays silently when a repo setting changes elsewhere — but it is git-I/O-shaped and does not belong inside a self-test whose whole documented posture is 'offline: pure functions + replay fixtures'. C is defensible too and costs nothing; A trades a documented invariant for coverage and I would not take it without a ruling." } ], "out_of_scope_findings": [ "filed as #12633: the merge queue's build-to-land skew (measured 25-32 min; 10 committer-date inversions in 3612 first-parent pairs, max 874s, two of them governed) can put a governed landing BELOW a round's --since boundary, where it is silently absent from the audit list and the sweep can still exit 0 = clean window; safe under the documented --since 24h default (max skew is 1.012% of that window), exposed at a knife-edge --since-ref boundary. Four options given, B+A recommended." ] }Two container-level notes for PM that no field covers:
- A
git fetch --depth=Nre-shallows the whole shared object store, every sibling worktree included. Fetching the live queue branches with--depth=25collapsed this clone's visible history from 2002 commits to 50. The gate's own horizon guard is what caught it — it refused the sweep with "this clone is shallow and its oldest visible commit on 'origin/main' is 2026-08-26, which sits INSIDE the window" rather than under-reporting into a short list that reads as compliance. Repaired withgit fetch --shallow-since=2026-08-01 origin main(now 3613 first-parent commits) and left deep, since deeper only helps siblings. Worth knowing before anyone reaches for--depthin a shared checkout. - Channel: repo-scoped REST answered HTTP 403 from this container and
ghis absent, so dedupe went through one targeted MCPsearch_issuesinstead, with check-governed-merges: measure whether a governed-surface PR landing via a merge-queue batch is visible to the audit (#11831 ruling) #11996 itself as the same-query control term to prove the channel was live rather than merely quiet. No duplicate of finding(check-governed-merges): the merge queue's build-to-land skew can put a governed landing BELOW a round's --since boundary, where it is silently absent from the audit list #12633 existed among the 19 results.
Probe refs created during the measurement were deleted; the worktree is removed. The branch is left in place with zero commits because both claim comments name it.
Generated by Claude Code
Generated by Claude Code
- A
ACCEPT (reviewer of record: skills seat, session
session_01MnijPVVDakqK2J335JoJtq). Verified against GitHub and the tree, not the report's narrative: ① the real-sweep replay is independently corroborated — this seat's own audit run this round (--since 2026-08-26T01:05:45Z) listed the same two governed queue landings (#12443, #12440) with identical surface lists; ② baseline spot-check re-run on the current tree:--self-testprints "157 assertions" green including the failed-closed generator row, matching the report; ③claude/issue-11996-governed-merges-queue-batchcarries zero commits and no PR — the ruled shape for a "sees" verdict, not a failure; ④ out-of-scope finding #12633 exists and is graded separately this round. Acceptance criterion met: a written verdict (SEES — zero blind topologies of six, live-batch replay + constructed-signature matrix) either way, backed by the runs quoted on this card.Open-question ruling (verification strategy — named seat-discretion class): C, land nothing now. The measurement itself shows the enumeration is robust by construction (even the two collapse shapes list the governed file; only the convenience PR column degrades), the decay scenario requires a maintainer-side merge-method change, and a new git-I/O gate in the farm for a property with zero live pull fails the startup-focus test. The durable record is one header note — topology verdict, dated, with the re-measure trigger (merge-method change, or a refactor of the mainline-commits/commit-paths enumeration) — riding #12633's already-authorized edit of this same script; recorded on that card as a rider.
Deviations: none. Closing per the card's own disposition rule ("sees" ⇒ evidence on the card, no code change).
Generated by Claude Code
Chartered in #11831's ruling (maintainer, 2026-08-25, verbatim 「10150 是我合并的,其他接受」; full record = #11831 comment 5404974747). Filed by PM session
session_01KWRU3s15AJz7PGW7a7wdChper the confirmed checklist. The #10150 instance itself was confirmed a maintainer hand-merge (no violation) — this card is the structural question that outlives it.Content: establish by measurement whether
scripts/pm/check-governed-merges.mjs(129 assertions green today) would flag a governed-surface PR (docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md) that reachedmainthrough a merge-queue batch. Construct or replay the queue-batch signature (batch merge-commit shape, parent/child pairing,merged_atcollapse,merged_bysemantics) against the gate — measure, ⛔ do not reason from the gate's source alone.Disposition rule:
Lane note for triage: per the lane table, a gate whose SUBJECT is the governed surface itself (
check-governed-mergesis named there) anchors todomain:skills— triage confirms the lane.Acceptance criterion: a written verdict either way, backed by the constructed-signature run; recognition added iff blind, with a pin so the signature stays covered.