Skip to content

check-governed-merges: measure whether a governed-surface PR landing via a merge-queue batch is visible to the audit (#11831 ruling) #11996

Description

@os-support-ai

Chartered in #11831's ruling (maintainer, 2026-08-25, verbatim 「10150 是我合并的,其他接受」; full record = #11831 comment 5404974747). Filed by PM session session_01KWRU3s15AJz7PGW7a7wdCh per 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 reached main through a merge-queue batch. Construct or replay the queue-batch signature (batch merge-commit shape, parent/child pairing, merged_at collapse, merged_by semantics) against the gate — measure, ⛔ do not reason from the gate's source alone.

Disposition rule:

  • Gate sees it → post the discriminating evidence on this card and close; the post-hoc-audit model (2026-08-18 ruling) is confirmed to cover the queue route.
  • Gate is blind → add the recognition so a governed-surface queue landing appears in the audit list. That is audit tightening (a Bug fix); ⛔ never weaken or bypass anything to get there. The fix rides this card.

Lane note for triage: per the lane table, a gate whose SUBJECT is the governed surface itself (check-governed-merges is named there) anchors to domain: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.

Activity

  1. os-litant commented on Aug 27, 2026

    @os-litant
    Collaborator

    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's node scripts/pm/dispatch-gates.mjs --tier scripts/pm/check-governed-merges.mjs (tree 0043c92): "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 touches scripts/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

  2. self-assigned this
    on Aug 27, 2026
  3. os-litant commented on Aug 27, 2026

    @os-litant
    Collaborator

    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/main at dispatch)
    File surface: scripts/pm/check-governed-merges.mjs plus 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

  4. os-litant commented on Aug 27, 2026

    @os-litant
    Collaborator

    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-batch carries 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; main then fast-forwards to the batch tip. Confirmed end-to-end mid-measurement — origin/main moved 0043c9224 -> 4be2e880e while this work was running, i.e. the very commits fetched above from the queue branch became main by 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 GitHub on 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=2
    

    Both governed queue landings are listed, with the right PR numbers and the right file lists. EXIT=2 is the attribution leg only — every channel answered HTTP 403 at this container's proxy, which the gate classifies as an ENVIRONMENT problem and prints as merged_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 entry
    QS-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 --since is set to exactly that second SEES — 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; pullNumberFromSubject is 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_at collapse and merged_by semantics

    • merged_at is display-only. It is read once into attribution.mergedAt and 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 by git log --since, never on merged_at. So a batch collapsing several PRs onto one merge timestamp cannot cost the list an entry.
    • merged_by read os-steve for 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_by names 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 not hotlong, 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-test under 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 0s
    

    Note 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_at 05:25:22Z; #12443 commit 05:00:49Z vs 05:33:08Z). That puts 10 committer-date inversions on main's first-parent chain in the last 3612 pairs, two of them governed, and a --since boundary 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-tree plumbing, ~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

  5. os-litant commented on Aug 27, 2026

    @os-litant
    Collaborator

    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:

    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

  6. os-litant commented on Aug 27, 2026

    @os-litant
    Collaborator

    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-test prints "157 assertions" green including the failed-closed generator row, matching the report; ③ claude/issue-11996-governed-merges-queue-batch carries 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions