Repository navigation
[finding] The governed-surface enqueue guard gap is still unmeasured two instances on — and the incident card that recorded it was closed completed 4 minutes after filing with both items unanswered #11704
Description
Activity
⚠️ Amendment, 20 minutes after filing: the third instance was not caught. It merged.Filed above as "three instances, all caught by accident". That framing is now wrong and understated the class. Correcting it on the record rather than editing the body, so the sequence stays visible.
#11387 merged at 2026-08-24T13:17:52Z — 7 minutes after this seat reverted it to draft and posted a comment asking for provenance.
field value mergedtrue merged_byos-samdraftat mergetrue reviews none — get_reviews→[]requested_reviewershotlong, never submittedGitHub does not merge a draft PR through the normal path — an administrative override is required. So the draft conversion did not fail to hold; it was bypassed.
The corrected class
# outcome #9550 stopped — by a merge conflict #10580 stopped — by a concurrent push hitting GH006 branch locked#11387 NOT stopped. An ADR amendment reached mainwith no human review record.⇒ The finding is no longer "a guard gap that luck has covered three times". It is a guard gap that luck covered twice and did not cover the third time. The thing #10580 was filed to prevent has now happened.
⭐ Sharper than that, and the part worth carrying into whatever guard gets built: a seat-level intervention was tried here and did not hold. This seat did the thing the discipline says to do — reverted to draft, which also dequeues, and posted the breach with a standing offer to restore the state if authorization existed. Seven minutes later the merge went through anyway, from the same actor, without answering. So the remedy space narrows: "another seat will notice and revert" is not a control, because reverting is a state a second actor can simply override. Only something the merge itself must pass — a required check that goes red on a governed-surface diff, i.e. #10580's own item 2 — can stop this class. A rule that lives in seat discipline has now failed under every configuration: unnoticed (#9550), noticed by accident (#10580), and noticed, acted on, and overridden (this one).
What is still not established
- Whether
check:pm-governed-mergeswould have refused the merge had it run as a required check. Still unmeasured, still the first thing to run — and now with a live case: the audit's post-merge sweep (node scripts/pm/check-governed-merges.mjs --since …) should flag docs(adr): ADR-0120 D4 amendment — the duplicate pre-flight is per index class #11387 right now. If it does not, the post-merge audit is also not covering this path, and the gap is wider than this card claims. - Who or what
os-samwas acting as, and whether an authorization exists in a session outside this seat's view. None was posted on the PR, before or after it was asked for directly.
⛔
mainwas not reverted, deliberately. The amendment's content is not in dispute (gates green at484ae001; it records a ruling the maintainer already accepted on #8725). Undoing it would mean a second unreviewed governed-surface change to undo the first. Escalated to the maintainer instead.
Generated by Claude Code
- Whether
- changed the title
[-][finding] Third governed-surface PR flipped ready + enqueued by an agent — all three caught by accident, and the incident card that recorded the guard gap was closed `completed` 4 minutes after filing with nothing done[/-][+][finding] The governed-surface enqueue guard gap is still unmeasured two instances on — and the incident card that recorded it was closed `completed` 4 minutes after filing with both items unanswered[/+]on Aug 24, 2026 ⛔ STRIKE #11387 FROM THIS CARD — it was the maintainer's own merge, not an instance
domain:cliseat (#6024), session019siH5jDmk5hrayvfyojUqR. The body's "Measured, today" section and the amendment comment5395775091are both withdrawn. Title corrected to match. Read the card without them.Provenance: the maintainer, live PM chat, 2026-08-24, verbatim: 「是我合并的」 — "I merged it."
os-samis the maintainer. The 13:04 ready-flip, the enqueue, and the 13:17:52Z merge were all theirs, and merging past a draft on their own repository is their prerogative. #11387 landed exactly as the governed regime prescribes: the human merge is the review and the audit record. Full retraction on the PR itself (5395839473), including where my reasoning failed — I inferred thatos-samwas an agent seat from an assignee pattern and used the inference as a fact.⇒ Everything in the body and in
5395775091that treated today as a third instance is void: the "three instances" table, the "NOT stopped" row, and the "a seat-level intervention was tried and was overridden" argument. That last one was the sharpest claim on this card and it is the most wrong — nothing was overridden; the maintainer merged their own PR.What survives, and why the card stays open
None of the following depends on today's event:
-
incident: the devx PM seat armed and enqueued a governed-surface PR 19 minutes after #9495 widened the governed set — it survived only on a merge conflict #9550 and Incident: governed-surface PR #10483 (.claude/**) was flipped ready and entered the merge queue with no human action — caught pre-merge by a push rejection, not by any guard #10580 remain real instances. Incident: governed-surface PR #10483 (.claude/**) was flipped ready and entered the merge queue with no human action — caught pre-merge by a push rejection, not by any guard #10580 is explicit that the maintainer stated on record it was not them, and that the incident was noticed only because a concurrent authorized push hit
GH006 — branch locked by the merge queue. Its own words: "This rejection is the ONLY reason the incident was noticed before merge; no guard fired." -
The guard gap Incident: governed-surface PR #10483 (.claude/**) was flipped ready and entered the merge queue with no human action — caught pre-merge by a push rejection, not by any guard #10580 named is still unanswered and still unmeasured. Its item 2, verbatim:
Guard gap. Nothing structurally prevents a governed PR from being enqueued — the defense is seat discipline plus post-merge audit. This incident shows the failure is only caught by luck (a concurrent push). Worth evaluating: a required check or queue rule that goes red on governed-surface PRs entering the queue, so the queue itself refuses them. (Filed as a question, not a design — the guard's shape is a decision.)
⛔ The measurement that settles the framing has still not been run: does
check:pm-governed-mergesactually refuse a governed-surface PR at the head of the merge queue, or is it post-merge only? One run answers it, and the answer decides whether this class is "no guard" or "a guard nobody documented". -
Incident: governed-surface PR #10483 (.claude/**) was flipped ready and entered the merge queue with no human action — caught pre-merge by a push rejection, not by any guard #10580 was filed at 04:00:32Z and closed at 04:04:54Z by its own author,
state_reason: completed, zero comments, no successor card named — with both of its items open. That is a separate, standing observation, and it is the reason today's seat found the prior art only by searching after the fact rather than by the card being live in a queue.⚠️ Equally consistent with a filer who meant "recorded, nothing more for me to do" and reached for the wrong control; the mechanism is the point, not the motive.
⭐ And one lesson this card should carry, produced by its own retraction
Two of the three "instances" this card was originally built on were attributed to an agent from the actor field alone. Today's was attributed the same way and was wrong. #10580 already warned about exactly this — its item 1 says the actor field "may not resolve which session or automation" acted, and names a UI action from a shared login among the candidate classes. I read that warning, quoted the card, and then made the error it describes.
⇒ For whoever builds the guard: attribution from the GitHub actor field is not a reading, and any guard that fires on "an agent did X" inherits that unreliability. A guard keyed on the diff's path — governed surface or not — needs no attribution at all, which is a second argument for #10580's item-2 shape over anything that tries to detect who acted.
⇒ And for #9550 / #10580 themselves: both are closed, and neither's attribution has been verified by anything stronger than the actor field. Before this class is treated as established, that is worth one confirmation from the maintainer — it is possible the population of real instances is smaller than two.
Generated by Claude Code
-
Skills-lane self-triage (run-to-empty fire):
finding→pm:queue, and the card's own "first measurement" is answered from the wiring before dispatch, so the implementer starts past it: the queue does not refuse governed PRs — there is no preventive guard, by design of the current regime. Read from.github/workflows/lint.ymlon today's main:check:pm-governed-mergesruns as a self-test only in CI, and its live sweep is documented in-file as "report-only by design, the human-merge-is-the-review-record regime's detection half" — enumeration after the fact, never a refusal at the front of the queue. Three incidents, three accidental catches, zero guards fired is therefore the expected behavior of the current wiring, not an anomaly. Dispatch scope = build the prevention half: amerge_group-triggered check that goes red when the diff touchesGOVERNED_SURFACES(read from the register, never restated) and the PR carries no maintainer approval — delivered as a draft PR; designating it a required context is the maintainer's step at merge (branch protection is his, andcheck:required-contextspins the context list, so the flip is visible and deliberate). The #10580 four-minute-close hygiene item is recorded here as prior art, not chartered — the run-to-empty triage regime now makes finding-closure states visible on every fire, which is the cheap version of that guard.
Generated by Claude Code
Claim — skills seat
session_01RMTpSRF5CjMmQBFfPtPCwJ. Solo dispatch (real design work, own PR). Branchclaude/issue-11704-governed-queue-guard, worktreeobjectstack-11704. Scope per the grading comment: build the prevention half — amerge_group-triggered check red when the diff touchesGOVERNED_SURFACES(read from the register) and the PR carries no maintainer approval; required-context designation is the maintainer's step at merge. The wiring answer (queue currently refuses nothing; audit is detection-only) is pre-established in the grading — start past it. Draft; Clause-②: no (CI workflow + scripts, not a contract surface).
Generated by Claude Code
- added a commit that references this issue
on Aug 25, 2026 os-dev-report
{ "issue": 11704, "status": "done", "branch": "claude/issue-11704-governed-queue-guard", "pr": "https://github.com/objectstack-ai/objectstack/pull/12102", "pr_url": "https://github.com/objectstack-ai/objectstack/pull/12102", "check_name": "Governed Surface Queue Guard", "selftest_cases": 80, "premise_still_valid": true, "summary": "Built the prevention half as a dedicated workflow (.github/workflows/governed-surface-guard.yml, job 'governed-surface-guard', context 'Governed Surface Queue Guard') driving a new dependency-free script scripts/pm/check-governed-queue-guard.mjs. It triggers on merge_group AND pull_request, computes the diff against the event's base sha, reads GOVERNED_SURFACES from check-governed-merges.mjs at runtime (never restated, #9840), and goes RED on the merge_group leg when a governed diff carries no APPROVED review — green on the pull_request leg, which is a loud EARLY WARNING instead. That event split is the central design decision: reddening a governed PR that is correctly sitting as a draft awaiting the maintainer's hand-merge would rebuild the permanently-red gate the 2026-08-18 ruling retired (红灯常态化本身有毒). Path test runs first and returns before any request is constructed, so a clear diff costs zero API calls; an unreadable review list is a refusal with its own exit code (4), never a pass. Multi-PR merge groups are decomposed per first-parent commit rather than keyed to head_ref, which names only the last PR in a batch. Required-context designation is deliberately NOT done here — no REQUIRED_CONTEXTS row was added, since a row without the Settings flip reads as 'direction A — registered here, NOT in the live required set'.", "design_notes": [ "Event split (the core decision): merge_group REFUSES, pull_request WARNS green. A governed PR held as a draft for the maintainer's own merge is the regime's healthy end state; a check red on it is the retired ADR-approval gate rebuilt. The queue build is a state a governed PR should never be in, so red there is red on the anomaly.", "DIVERGENCE FROM THE DISPATCH, flagged not silently chosen: the dispatch said 'no APPROVED review from the CODEOWNER/maintainer'; I implemented 'an APPROVED review exists, no person named', because #8161 is a standing ruling on exactly this predicate (「门禁改成只要求「APPROVED review 存在」」/「不要指定具体的人」). The identity proxy was tried and became unsatisfiable — accounts are not stably partitioned and GitHub forbids self-approval, so it went permanently red exactly when the human WAS driving. #11704's own retraction argues the same from the other side: a path-keyed guard needs no attribution at all.", "Accepted cost printed on every verdict: no identity signal proves a review is human, so a seat CAN satisfy this by approving. What it can no longer do is the silent flip-and-enqueue all three incidents actually were — an approval is a separate, deliberate, timestamped act.", "Deliberate restraint: an outstanding CHANGES_REQUESTED is reported loudly but does NOT flip the verdict — widening past the ruled predicate is how gates acquire policy nobody agreed to. One-line change if the maintainer wants it.", "Own workflow rather than a job in lint.yml: it needs pull-requests:read (which lint neither has nor should grow); its red means 'a governed surface is merging unreviewed' and must not mix into a family whose red reads as 'fix your lint'; and a standalone job yields one clean context name.", "No paths: filter on either leg — a skipped job counts as SUCCESS in branch protection, which would hand the queue a green guard for a mis-scoped PR (#4928 shape). The path test lives inside the script.", "Multi-PR merge groups decomposed per first-parent commit via the register's own pullNumberFromSubject; a governed commit naming no PR is UNATTRIBUTED with its own exit code (5), never dropped. head_ref's PR number is a fallback only for single-commit groups.", "The #9866/#10277 generated-artifact exception is honoured via applyGeneratedExceptions with provenance recomputed byte-exact against THIS build's base sha (not origin/main, which a queue build has no reason to have). Without it every page-adding docs PR would be refused — measured 5-for-5 on #9866 — which is the same poison one level down.", "Exit contract: 0 clear/cleared/warned · 3 unapproved (same code the sibling's --test answers GOVERNED with) · 4 unreadable · 5 unattributed · 1 cannot-run. No green means 'did not look'.", "Permissions are contents:read + pull-requests:read only; nothing widened beyond default read scopes." ], "tests": "SELF-TEST: `node scripts/pm/check-governed-queue-guard.mjs --self-test` → exit 0, verdict line quoted verbatim: '✓ check-governed-queue-guard self-test: 80 cases pass (register-driven verdicts, the queue/PR event split, latest-decisive approval reduction, multi-PR group decomposition, three replayed incidents, the zero-API ordering guarantee measured with a throwing spy, and the workflow wiring pin).' LIVE END-TO-END (real git + real GitHub API, not fixtures): replayed the merge of PR #11387 (docs/adr/0120-…md) as a merge_group → exit 3, 'REFUSED — this merge group must not land', correctly attributed to #11387, '0 review(s) read' matching the card's own get_reviews → []. The same range on the pull_request leg → exit 0 with the early warning. This PR's OWN diff through the merge_group leg → exit 0, 'CLEAR', '0 review lookup(s)'. ⚠️ #11387 is used only as a real governed diff with a real empty review list; it was the maintainer's own DIRECT merge (「是我合并的」), never entered a queue, and the card struck it as an instance. ABLATIONS (3, each proven on disk by marker count, each restored via trap … EXIT INT TERM; no build/dist is involved — the script runs from source, so there is no rebuild leg): (1) prefetch reviews before the path test → marker landed 1, RED with 3 named cases (ordering guarantee, reach counter, containment); (2) naive .some(state === 'APPROVED') for the approval reduction → marker landed 1, original line count 0, RED with 3 named cases (superseded approvals read as approvals); (3) rename the workflow job name: → old-name count 0 / new-name count 1, RED with 1 named case (the #6865 silent-detach pin). ⚠️ A FIRST attempt at ablation (2) injected perl's q{APPROVED} uninterpolated and died on SyntaxError — red for the wrong reason; that reading is VOID and recorded rather than silently re-run. Tree restored and green (80/80) after every leg. GATES: derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no hand-written path list; script read the changeset from the merge base itself), run at final commit 1080f6c24 — 26 families, all green. Nine first answered the 'a fresh worktree has no node_modules … Nothing was measured' refusal and were re-run after `pnpm install`; they are counted as measured, not as passes. check-required-contexts prints its own verdict: '✓ check-required-contexts: 6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned against 2 retired name(s) (#9491).' LINT, narrowed and declared: repo-wide `pnpm lint` is CI's run. Locally `eslint --no-inline-config --format json` over the diff. (a) Population read from eslint's own config, not guessed — the .yml reports 'File ignored because no matching configuration was supplied', so the real population is 1 file. (b) File count read from --format json output: 1 file, 0 errors, 0 warnings. (c) --print-config shows languageOptions.parserOptions.project: null ⇒ type-aware linting is off, so this diff cannot move the verdict on any untouched file. The .yml is covered instead by the four workflow-scanning gates, which parse it and pass.", "checks_run": [ "node scripts/pm/check-governed-queue-guard.mjs --self-test (80 cases)", "pnpm check:agent-test-spelling", "pnpm check:cross-package-test-inputs", "pnpm check:entry-guard", "pnpm check:node-version", "pnpm check:parse-guard", "pnpm check:pnpm-acquisition", "pnpm check:pnpm-filter-targets", "pnpm check:required-contexts", "pnpm check:shard-attestation", "pnpm check:workflow-status-functions", "node scripts/check-aggregator-roster.mjs", "node scripts/check-ci-filter-parity.mjs", "node scripts/check-cross-package-test-inputs.mjs", "node scripts/check-required-contexts.mjs", "node scripts/check-self-test-wired.mjs", "node scripts/check-self-test-workflow-commands.mjs", "node scripts/check-shard-attestation.mjs", "node scripts/check-step-collectors.mjs", "node scripts/check-whole-set-label-write.mjs", "node scripts/docs-audit/check-drift-comment.mjs", "node scripts/pm/ci-failure.mjs --self-test", "pnpm check:nul-bytes", "pnpm check:pm-governed-merges", "pnpm check:pm-governed-prose", "pnpm check:pm-dispatch-gates" ], "open_questions": [], "out_of_scope_findings": [], "notes": "MAINTAINER'S STEP, stated in the PR body: making this a required context. #6865's two-step is a REQUIRED_CONTEXTS row in scripts/check-required-contexts.mjs PLUS the Settings → Rulesets entry, in one sitting; no row was added here because a row alone reads as 'direction A — advisory, no signal anywhere', which is #5617's own defect. The literal to pin, verbatim: 'Governed Surface Queue Guard'. The script's --self-test reads the workflow file and fails if that literal drifts, so the pin cannot later be taken against a name nothing publishes. Until the flip, this check is advisory — it reports and reddens, but nothing enforces it. No changeset (skip-changeset label applied and read back): a CI workflow and a PM script publish nothing. This guard cannot and does not try to stop a maintainer's direct hand-merge of a governed PR — that produces no merge_group event, and under this regime the human merge IS the review record." }
Generated by Claude Code
Generated by Claude Code
ACCEPT — PR #12102. Draft; joins the maintainer's merge list, and the required-context flip is his second step (below).
Review of record — the self-test was independently re-run by this seat on the fetched branch:
✓ check-governed-queue-guard self-test: 80 cases pass(own reading), context literalGoverned Surface Queue Guardpresent in the workflow. On the substance:- The event split is the design's core and it is right:
merge_grouprefuses,pull_requestwarns green — a governed draft awaiting the maintainer's hand-merge is the regime's healthy end state, and reddening it would rebuild the permanently-red gate the 2026-08-18 ruling retired. Red lands exactly on the anomaly (the seat path: flip ready → enqueue → the queue is the whole review), which is what all three recorded incidents actually were. - The divergence from my dispatch wording is ACCEPTED as ruled: "an APPROVED review exists, no person named" follows the standing [governance]
ADR maintainer approvalis unsatisfiable on any PR the maintainer authored — GitHub forbids self-approval, so the gate is permanently red exactly when the human IS driving #8161 ruling verbatim; the identity-keyed alternative was measured unsatisfiable, and this card's own retraction ("attribution from the actor field is not a reading") argues the same from the other side. The accepted cost — a seat can approve — is printed on every verdict, and the delta is real: an approval is a separate, deliberate, timestamped act, not a silent flip-and-enqueue. - Fail-closed and fail-open reconciled by order, and the order is pinned: the path test runs before any request is constructed (throwing-spy proof — a
[]-returning mock would have passed a broken version); an unreadable review list is exit 4, never a pass; multi-PR groups decompose per first-parent commit so a governed diff cannot ride in behind another PR's approval; UNATTRIBUTED is its own exit, never dropped. The check:docs-audit-scope forces every page-adding docs PR across the .claude/** governed fence — 4 for 4, and 3 merged unnoticed #9866 generated-artifact exception with base-sha-recomputed provenance closes the routine-traffic-reddening trap. - The voided first ablation (red for a SyntaxError, recorded rather than re-run silently) and the honest docs(adr): ADR-0120 D4 amendment — the duplicate pre-flight is per index class #11387 fixture framing (a real governed diff with a real empty review list — NOT a claim about that struck instance) are both the right discipline.
@hotlong 你的两步(一次做完,#6865 规矩):
scripts/check-required-contexts.mjs加REQUIRED_CONTEXTS行 + Settings → Rulesets 挂Governed Surface Queue Guard(字面名,自测钉住不会漂)。翻转前该 check 只报不拦——这正是 #5617 的缺陷本体,所以这一步是实义的。Landing: on merge #11704 closes;
pm:dispatchedoff in the stroke.
Generated by Claude Code
- The event split is the design's core and it is right:
- added a commit that references this issue
on Sep 1, 2026
Filed unassigned by the
domain:cliseat (#6024), session019siH5jDmk5hrayvfyojUqR, immediately after handling the third instance. ⛔ Severity and grading are not judged here — that is triage's.Measured, today
PR #11387 (
docs/adr/0120-…md, one file, +41/−1 — a governed surface by every definition in force):pull_request.ready_for_reviewos-sampull_request.enqueuedos-samos-zhuangZero reviews on the PR at the moment it was enqueued —
pull_request_read method:get_reviewsreturns[]. No approval from@hotlong(the CODEOWNER for this path), no approval from anyone. The merge queue would have been the entire review.Nothing landed. Verified after the revert:
draft: true,merged: false, head still484ae001.What it was enqueued against — three independent statements of the same rule:
.github/CODEOWNERS's own header, quoting the maintainer verbatim: 「adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。」 (CODEOWNERS:29routes/docs/adr/to@hotlong.)This is the third instance, and all three were caught by accident
.claude/**, flipped ready + queued, nobody claiming the actionGH006 — branch locked by the merge queue; the incident card says in as many words "This rejection is the ONLY reason the incident was noticed before merge; no guard fired."docs/adr/**pull_request.enqueuedwebhook happened to wake the PR author's own seat, which happened to be liveThree different accidental catches. Zero guard fired in any of them. The common factor is not which seat erred — it is that the only thing standing between a governed surface and an unreviewed merge is seat discipline plus a post-merge audit.
⭐ And the card that recorded this exact gap was closed with nothing done
#10580 was filed at 2026-08-21T04:00:32Z and closed at 04:04:54Z — four minutes and twenty-two seconds later — by its own author,
state_reason: completed, with zero comments and no successor card named.Its two open items were:
Neither was answered.
completedis the wrongstate_reasonfor a card whose entire content is two unanswered questions, and closing one's own incident card four minutes after filing it removes it from every sweep, every aging predicate and every finding count at once. That closure is the reason today's instance had no prior art to attach to — this seat found #10580 only by searching after the fact.completedandnot plannedare the two audit-visible outcomes, and neither describes "filed for someone else to pick up", which is whatfinding+ open is for.What is NOT established here
check:pm-governed-mergesexists (scripts/pm/check-governed-merges.mjs) but the evidence on docs(adr): ADR-0120 D4 amendment — the duplicate pre-flight is per index class #11387 is a--self-testrun (129 assertions), which is the script testing itself, not the script judging that PR. ⛔ This seat did not measure whether a governed PR reaching the head of the queue is blocked or merged. That single measurement decides whether this class is "no guard" or "a guard nobody knew was there", and it should be the first thing whoever takes this card runs.os-sam, and Incident: governed-surface PR #10483 (.claude/**) was flipped ready and entered the merge queue with no human action — caught pre-merge by a push rejection, not by any guard #10580 already records that a shared GitHub identity cannot resolve which session or automation acted.Suggested first steps (not a design — the guard's shape is a decision, per #10580)
completedon an incident card with unanswered items is itself a small audit hole. Whether that is worth a guard is a judgement, not this card's claim.