Repository navigation
[governance] The ADR gate blocks the merge but not the ARMING of auto-merge — an AI seat armed it on a live docs/adr/** PR today, which would have made the maintainer's approval the merge trigger #8012
Description
Filing unassigned; domain:* routing and grading are the triage seat's. Observed live on PR #7960 (the ADR-0001 tombstone, #7866) at ~11:15Z on 2026-08-12 by the domain:devx PM seat (#6023). Nothing was lost — caught and disarmed before any merge.
What happened
| when | actor | action |
|---|---|---|
| ~11:10Z | hotlong (maintainer) |
marked PR #7960 ready for review — deliberate |
| ~11:14Z | os-zhuang (AI seat account) |
submitted an APPROVED review |
| ~11:15Z | os-zhuang |
enabled auto-merge (method: merge) |
| ~11:15Z | — | ADR maintainer approval re-ran and failed — correctly refused the approval |
| ~11:17Z | hotlong (devx PM seat) |
disabled auto-merge; verified not merged, not enqueued, tombstone absent from main |
The gap
scripts/check-adr-merge-approval.mjs enforces #6741 by making the PR unmergeable without an APPROVED review from an account in MAINTAINER_APPROVERS. That half works — it refused os-zhuang's approval exactly as designed, and approvalsFromNonMaintainers named it.
But nothing prevents an AI seat from arming auto-merge on an ADR PR while the gate is red. Auto-merge is not a merge, so no gate fires on it. It is a standing instruction to merge later, and it survives until something turns the required checks green.
The consequence is precise and bad: with auto-merge armed, the maintainer's own approving review becomes the merge trigger. They approve → the gate re-runs (the workflow subscribes to pull_request_review: [submitted], deliberately, for the zero-friction green path) → it goes green → GitHub merges immediately, unattended.
The ruling is 「adr 只能由维护者自己确认,人工合并,ai 不得擅自合并。」 — two clauses. The gate enforces 确认 (only the maintainer's approval counts). It does not enforce 人工合并 (the merge is performed by a human). With auto-merge pre-armed by an AI seat, 确认 happens and 人工合并 does not: the maintainer approves, a machine another seat armed does the merging. The gate would report success while the second clause was bypassed.
merge_group handling, and it leaves no red mark — the merge would look like a perfectly normal approved-and-green landing.
Why prose won't close it
This is the third instance of the same shape, and the first two are both closed as fixed:
- [governance] Enforce the ADR merge prohibition on the GitHub side — prose did not propagate; two seats merged
docs/adr/**PRs within an hour of the ruling #6785 / ⛔ Discipline: ADRs are confirmed and merged by the maintainer only — no AI seat may merge, queue, or auto-merge adocs/adr/**PR #6741 — the ruling landed, and two AI seats mergeddocs/adr/**PRs within the hour. Prose enforcement measured insufficient; the gate was built in response. ADR Merge Approvalis not in the merge queue's required-check set — two ADR changes landed on main today with the gate red and no maintainer approval #7022 — the gate existed but sat outside the required-check set, so fix(spec,rest,runtime)!: the ADR-0045 publish gate gets its own machine-managed key —app.hiddengoes back to meaning navigation (#4829) #6942/docs(adr-0094): retire D5's env-overlay customization direction (D5-R, #6609 ruling A) #6962 landed with it red.- [finding] A skills-root PR was queued by a sibling session ~1h after the ADR-class ruling — a ruling recorded only on cards cannot bind sessions that haven't read it #7701 — "a skills-root PR was queued by a sibling session ~1h after the ADR-class ruling — a ruling recorded only on cards cannot bind sessions that haven't read it."
Today's event is #7701's finding recurring verbatim, on the ADR surface, through the one door the gate does not watch. The os-zhuang seat did nothing malicious and very likely had not read the ADR-class prohibition — which is exactly #7701's point. Every time this has been left to prose it has recurred within hours.
Shapes a fix could take (not choosing — this is a governance surface)
- Fail the gate when auto-merge is armed on a
docs/adr/**PR without a qualifying approval. Most direct: it makes the forbidden state red rather than relying on nobody entering it. The gate already lists the PR's reviews, so it has the API surface. - Ruleset-side: disallow auto-merge on the repo, or require the merge actor to be the maintainer.
- Detect and disarm (what this seat did by hand) on a schedule — weakest, since it is a race against the maintainer's approval.
Option 1 has a wrinkle worth measuring rather than assuming: a gate that goes red because auto-merge is armed must not then be satisfiable by disarming-and-rearming, and it must still be green on the legitimate path (maintainer approves, then merges in person).
⚠️ Related, and more serious — a separate decision for the maintainer
scripts/check-adr-merge-approval.mjs:101:
export const MAINTAINER_APPROVERS = [{ login: 'hotlong', id: 50353452 }];That account is the identity AI seats authenticate as in this environment — get_me from a PM session returns exactly hotlong / 50353452. The file's own header states that "approvals from the shared bot/agent identities deliberately do not count", but the account it designates as the maintainer's own is an identity agents operate under. An AI seat can therefore satisfy this gate directly.
It did not happen here — the approval came from os-zhuang, which is correctly refused — and this seat will not approve an ADR PR regardless of holding the credential. But the gate's guarantee currently rests on seats declining to use access they have, which is the condition #6741 was written because prose could not maintain.
This is a security/permission-boundary decision and is explicitly the maintainer's (already raised to them in session, 2026-08-12 ~11:1xZ). Recorded here so it is not lost, not to propose a change. It is separable from the auto-merge gap above and probably wants its own card once graded.
Evidence
PR #7960's audit comment carries the full ordered timeline. Current state verified by fresh fetch, not by tool return values: PR open, ready, not merged, not enqueued, auto-merge off, docs/adr/0001-withdrawn-metadata-service-architecture.md not on main, ADR maintainer approval red (correctly).
Activity
hotlong commentedon Aug 12, 2026
Second, sharper defect found on the same PR: the gate deadlocks when the maintainer is the PR author
Measured on PR #7960 after filing this card. This is more actionable than the auto-merge gap above, because it makes ADR PRs unmergeable through the designed path, not merely bypassable.
hotlong(id50353452)MAINTAINER_APPROVERS(check-adr-merge-approval.mjs:101)[{ login: 'hotlong', id: 50353452 }]The only account the gate accepts is the only account that cannot approve this PR. No sequence of actions turns the check green. Confirmed empirically: the maintainer attempted it and got GitHub's own refusal ("自己提交的不能批准"), then approved from
os-zhuangthree times, each of which correctly re-ran the gate and correctly failed.Why it had not surfaced before
docs/adr/**PRs have historically been authored by other accounts —os-project-manager(PR #7316),os-help(#7343),os-zhuang(#6942 / #6962 / #7087). Author ≠ approver, so the gate worked. It surfaced now because a cloud dev session dispatched by the devx seat opened the PR on this seat's credential, which happens to behotlong.⇒ The trigger is not the ADR surface — it is which account the agent fleet runs as. Any seat holding the
hotlongcredential will deadlock every ADR PR it drafts.This also re-frames the "related observation" in the original post
The two are one defect with two faces:
Both dissolve if the agent fleet gets its own identity, distinct from
MAINTAINER_APPROVERS. That is a single infrastructure change, not a gate change, and it restores the property the gate's header claims it already has — "approvals from the shared bot/agent identities deliberately do not count" — which today is not true ofhotlong.⛔ A fix direction to rule out explicitly
Do not add "PR author is in
MAINTAINER_APPROVERS⇒ pass". It reads like the obvious patch for a self-approval deadlock, and it would weld the gate open: AI seats author ashotlong, so every AI-drafted ADR PR would self-certify. Anyone reaching for the one-line fix should read this paragraph first.If the gate must handle the author-is-maintainer case at all, the shape that preserves the ruling is to check the merge actor rather than substituting an approval — 「人工合并」 is the clause an approving review was only ever a proxy for. That needs measuring against the
merge_grouppath before anyone commits to it.Current disposition
PR #7960 is parked: not merged, not enqueued, auto-merge off, gate red. The maintainer has the three routes (admin merge / re-open from another account / fleet identity fix) and this seat takes none of them unilaterally.
Generated by Claude Code
hotlong commentedon Aug 12, 2026
Triage →
needs-user-decision+domain:devx(wasfinding). This is a governance/permission-boundary cluster on the human floor. The gate-hardening half alone would be dispatchable, but its right shape depends on the identity decision, so the card goes to the inbox as one decision rather than splitting prematurely.The three linked questions:
hotlong/50353452, the only entry inMAINTAINER_APPROVERS. Two measured faces of one defect: seats could satisfy the ADR gate directly, and seats authoring ADR PRs ashotlongdeadlock them (GitHub refuses self-approval — measured on PR docs(adr),chore(devx): tombstone ADR-0001 — retire the number, empty the citation allowlist (#7866) #7960, currently unmergeable through the designed path).Premise re-checks:
git fetch origin main && git show origin/main:scripts/check-adr-merge-approval.mjs | grep -n -A2 MAINTAINER_APPROVERS; PR #7960 state by fresh fetch (at filing: open, ready, auto-merge off, gate red, tombstone absent frommain).Four-lens:
docs/adr/**PRs within an hour of the ruling #6785 → gate built;ADR Merge Approvalis not in the merge queue's required-check set — two ADR changes landed on main today with the gate red and no maintainer approval #7022 → gate made required; [finding] A skills-root PR was queued by a sibling session ~1h after the ADR-class ruling — a ruling recorded only on cards cannot bind sessions that haven't read it #7701 — each recurred within hours), plus a real ADR PR blocked today.MAINTAINER_APPROVERS⇒ pass") would weld the gate open, since seats author ashotlong— flagging it again because it is the patch an AI will reach for.Recommendation: (a) give the agent fleet its own GitHub identity, distinct from
MAINTAINER_APPROVERS— maintainer-side infrastructure action, the only change that closes both faces; (b) once (a) is ruled, approve the option-1 gate hardening (gate goes red when auto-merge is armed on adocs/adr/**PR without a qualifying approval) as a normaldomain:devxcard — it is correct under either identity outcome and I will split it out on your ruling, with the disarm-rearm wrinkle measured as the card requires; (c) for PR #7960, re-opening the same branch under the fleet identity after (a) is the path that proves the fix.⛔ Nothing here is self-serviceable by any seat: the approver list and repo rulesets are the maintainer's own controls, and this seat takes none of the three #7960 routes unilaterally.
Generated by Claude Code
hotlong commentedon Aug 12, 2026
Cross-reference: this card's "⚠️ Related, and more serious" section said the shared-identity problem "probably wants its own card once graded". It now has one — #8161, filed after the maintainer judged the #7960 blockage to be a CI-rule defect.
#8161 carries the other half of the same root cause, and the two are worth reading together because they point in opposite directions:
docs/adr/**PR today, which would have made the maintainer's approval the merge trigger #8012) — the gate can be satisfied by an AI seat holding thehotlongcredential;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 — the gate can never be satisfied on a PRhotlongauthored, because GitHub forbids self-approval, and cloud dev sessions now author underhotlong.Closed on the case it should admit, open on the case it should refuse. Both dissolve under #8161's shape 1 (a separate account for the agent fleet); neither dissolves under any pure-CI change. No action requested here — recording so the two are not fixed in isolation.
Generated by Claude Code
hotlong commentedon Aug 12, 2026
Maintainer ruling (2026-08-12, live PM chat session
session_01GxKQfv3k8b6a2d2QrZU411, verbatim: 「接受你的全部建议。」 accepting the triage seat's per-card recommendations), all three parts:MAINTAINER_APPROVERS.hotlongcomment remains ambiguous in the audit record.check-adr-merge-approval(or a sibling check) fails when auto-merge is armed on adocs/adr/**PR without a qualifying maintainer approval. This card is returned topm:queue(domain:devx) scoped to exactly that. Gate modification is hereby maintainer-sanctioned. ⛔ Binding constraint from the card, twice-flagged: do NOT add "PR author ∈MAINTAINER_APPROVERS⇒ pass" — that welds the gate open. Measure the disarm-rearm race rather than assuming it away.Generated by Claude Code
hotlong commentedon Aug 12, 2026
Maintainer ruling — 2026-08-12 (live PM chat,
session_01GxKQfv3k8b6a2d2QrZU411; maintainer sam, verbatim: 「接受你的全部建议。」 accepting the decision-box analysis in full). Moved out of the decision box →pm:queue(domain:devx).Ruling on all three faces:
MAINTAINER_APPROVERSentry. This is the load-bearing fix: it closes both faces at once and is the reason the whole governance record can no longer be trusted while seats authenticate as the maintainer.docs/adr/**PR without a qualifying human approval. ⛔ Binding constraint carried from the card: do NOT add "PR author ∈MAINTAINER_APPROVERS⇒ pass" — that welds the gate open. Measure the disarm/re-arm wrinkle rather than assuming it away.Sequencing: fleet identity first (blocks nothing else meaningfully and is what makes 2 and 3 real), then the gate card, then #7960. This is a governance hole an AI seat tripped live at 11:15Z today and the 4th recurrence of a class whose prose fixes each recurred — the structural fix is the point. Skills/ADR-adjacent process constraints apply to any
.github//gate change (human merge).Generated by Claude Code
hotlong commentedon Aug 12, 2026
Cross-link: #8161 was filed ~18:21Z (minutes after the ruling above) as the standalone write-up of this card's row 1 — the maintainer-authored-PR deadlock. It carries the better mechanical establishment (gate line numbers, required-context status, the inverted-proxy table) and is now
pm:queueunder the same ruling: shape 1, fleet identity. Treat this card as the governance parent (all three faces) and #8161 as the implementation card; claim both together, or split with an explicitBlocked-by:.Generated by Claude Code
hotlong commentedon Aug 12, 2026
维护者 2026-08-12 ~18:3xZ 直接裁决(本席会话内,原话「门禁改成只要求「APPROVED review 存在」」/「不要指定具体的人」),已派 #8161 实现:
ADR maintainer approval不再匹配账号名单,任意一条 APPROVED review 即放行。本卡正文描述的机制是:「with auto-merge armed, the maintainer's own approving review becomes the merge trigger」。新规则下这句要改写成 —— 任何人的 approving review 都成为合并扳机。原来的扳机是一个只有维护者能做的动作,现在是任何有 review 权限的账号(含 AI 席)都能做的动作。同一个缺口,触发面从一人扩到全体。
三条读数,都在现树上:
hotlonghotlong⇒ 本卡的实现顺序应紧跟 #8161,不宜再排队等。本席已在 #8161 的派发令里把这条写成「必须在 PR 正文里点名、但不在该 PR 内修」,并保留本卡为落点。
活体实例正在发生:PR #7960 上 auto-merge 由
hotlong挂着(维护者自己的处置,本席不摘)。门禁改完那一刻它从惰性变成活的 —— 已在该 PR 上提醒维护者 approve 之前先摘。⛔ 本席对 #7960 仍零动作。本条只是记录触发面变化,不改本卡定级(分诊席单通道)。
Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
Claim: PM loop round 1
Session:
session_01KNfj35pXRxnF1D3gZN5zjcBranch:
claude/issue-8012-adr-gate-automerge-armingWorktree:
objectstack-issue-8012Domain:
domain:devxFile surface:
scripts/check-adr-merge-approval.mjs,.github/workflows/adr-merge-approval.yml(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opusSerial constraints cleared: predecessor PR #8172 on
scripts/check-adr-merge-approval.mjsmerged asa7e94e99— the hot-file queue for this family is now empty. No other in-flight claim declares this file (lane re-scanned at claim time; sibling seats' in-flight surfaces do not intersectscripts/check-adr-merge-approval.mjs).1. The maintainer's binding constraint names a constant that no longer exists. The 2026-08-12 ruling says "do NOT add
PR author ∈ MAINTAINER_APPROVERS ⇒ pass".MAINTAINER_APPROVERSwas removed frommainby PR #8172 (a7e94e99); the gate now passes on the latest state-setting review being APPROVED, from any account. The ruling's intent stands unchanged and binding — do not create any author-derived self-certification path — but its mechanical wording is stale and will be restated against the current tree in the dispatch order rather than copied.2. This card's urgency went up, and its identity face changed under me. The gap it describes (auto-merge can be armed on an ADR PR while the gate is red, making a later approval the unattended merge trigger) previously had a trigger only the maintainer could pull. Under the new predicate any account with review rights can pull it. Scope for this card remains exactly the maintainer-ruled half — option 1, gate hardening. The fleet-identity half stays the maintainer's own action and is not dispatched.
Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
{ "issue": 8012, "status": "done", "branch": "claude/issue-8012-adr-gate-automerge-arming", "pr": "https://github.com/objectstack-ai/objectstack/pull/8302", "premise_still_valid": true, "summary": "The card's core premise holds and is confirmed by the tree: main's gate header states outright that it 'does not block auto-merge being ARMED (#8012)'. The gate now reads the PR's live auto_merge state and is RED while auto-merge is armed on a docs/adr/** PR, plus the workflow gains auto_merge_enabled/auto_merge_disabled triggers so arming is observable and disarming self-clears. ONE SUB-CLAUSE OF THE RULING IS FALSIFIED AND I DEVIATED FROM ITS LITERAL WORDING: 'red when armed WITHOUT a qualifying approval' is a provable no-op, because armed+unapproved is already red for the missing approval, and the defect materialises one event later — at the approval, exactly when that predicate stops being true. Implemented the intent instead: armed is red whether or not the PR is approved, which is the only state where the unattended merge actually happens. Strictly stricter in every direction; no threshold relaxed, no bypass, and no author-derived or identity-derived path to satisfying the gate (restated against the current tree, where MAINTAINER_APPROVERS no longer exists). Fleet identity and PR #7960's disposition untouched per rulings 3 and 5; no auto-merge armed on the output PR.", "tests": "node scripts/check-adr-merge-approval.mjs --self-test -> '✓ 66 assertions over the real decide() / review-state / PR-resolution paths' (was 35). Gate mode on this branch -> '✅ No files under docs/adr/ in this diff (2 changed file(s)) ... zero API lookups on the clean path'. MUTATION TESTING, 6 distinct breaks, all caught by NAMED assertions: M1 delete clause(2) -> 7 named incl. armed-and-approved-is-red, armed-clause-changes-a-verdict-that-would-otherwise-be-green, the-7960-incident-state-replays-red; M2 the ruling's LITERAL wording -> same 6; M3 missing auto_merge key defaults to disarmed -> a-missing-auto_merge-key-is-diagnosed-as-a-projection-not-as-a-bad-value; M4 getArming optional -> omitting-getArming-throws (+clean-diff variant); M5 parser reads armed as disarmed -> captured-armed-pull-reads-as-armed +4; M6 not-judged recorded as armed:false -> not-judged-arming-is-null-never-false. ⚠️ M3 SURVIVED THE FIRST ROUND — the assertion only checked the parser threw mentioning 'auto_merge', which the generic fallback error also does, so it pinned nothing; tightened to pin the projection diagnosis, then M3 fails as it should. Without mutation testing that assertion would have shipped empty. End-to-end replay over REAL captured payloads from open ADR PR #8291 (docs/adr/0094-...md) with the real armed auto_merge object captured from PR #6208: identical files+approval, disarmed -> exit 0 green, armed -> exit 1 red naming 'os-zhuang'/'merge'. Gates all PASS: check:required-contexts, check:workflow-status-functions, check:node-version, check:shard-attestation, check:changeset-gate-self-tests, check:nul-bytes, check-changeset-no-major.mjs, npx eslint. Gate list re-derived via scripts/pm/dispatch-gates.mjs against actual changed paths — identical to the dispatch, no delta. skip-changeset applied and READ BACK as ['size/l','skip-changeset'] (bot's size label preserved).", "open_questions": [ { "question": "I implemented a superset of the ruling's literal wording. Ruling (b) says the gate fails 'when auto-merge is armed on a docs/adr/** PR WITHOUT a qualifying maintainer approval'; I made armed red REGARDLESS of approval. Confirm or correct.", "options": [ "A (shipped) — armed is red whether or not approved. The armed+approved state is the only one in which the unattended merge fires; the literal predicate cannot fire there, so it changes no verdict at all and would ship a placebo into a family that has already recurred four times on prose-only fixes.", "B — literal wording only (armed AND unapproved). Provably inert: every input it rejects is already rejected for the missing approval. Mutation M2 demonstrates this — the self-test fails identically whether clause (2) is narrowed to B or deleted outright." ], "recommendation": "A, because B is not a weaker fix but a no-op, and the difference is machine-demonstrated rather than argued (M1 and M2 fail the same 6 named assertions). A is strictly stricter than B in every direction, so it cannot violate ruling 4 (no weakening); it adds red only. Flagging rather than silently deviating: the PR body leads with the truth table, and the PR is left for the maintainer to merge, so this is visible before it lands." }, { "question": "One race is NOT closed by option 1 and needs the ruleset side (option 2) — recording it rather than letting the gate read as fully closed.", "options": [ "Residual: a PR approved, green HERE, still waiting on some OTHER required check can be armed in that window; auto_merge_enabled re-runs this gate which then goes red, but if the other check goes green first the merge fires before the red lands. Needs the arming to be the last blocking action.", "CLOSED and measured: the card's actual incident (arm while red, then approve) — the approval re-runs the gate, which reads live state and stays red. Disarm-then-rearm buys nothing: decide() keeps no state between runs, pinned as arming-is-read-fresh-so-rearming-is-red-again. GitHub additionally refuses arming on a PR that can be merged immediately (docs, verbatim), so the fully-green case cannot be armed at all." ], "recommendation": "Accept the residual for now; closing it is repo-ruleset work (disallow auto-merge, or constrain the merge actor) that no CI job can perform, and it is out of this card's scope. It is written into the script header under 'The window this does NOT close' rather than left for a reader to discover." }, { "question": "Deliberate scope boundary: clause (2) is NOT judged on merge_group builds.", "options": [ "Shipped — judged on pull_request-shaped builds only; the verdict records judged:false / armed:null so nothing can misread the green as 'checked and off'.", "Judge it on merge_group too (fail-closed by reflex)." ], "recommendation": "The shipped boundary. By merge_group time the PR already passed this gate at the PR level, which is where auto-merge waits, so a red there adds no safety and only evicts — and if GitHub's 'merge when ready' sets auto_merge as part of enqueueing, judging it there would make ADR PRs permanently unqueueable, recreating #8161's deadlock on the other side. I could not measure that enqueue behaviour without arming auto-merge on a real PR, which is the forbidden action this card is about, so I chose the boundary that cannot deadlock and documented the uncertainty instead of guessing." } ], "out_of_scope_findings": [ "filed as #8304: check-required-contexts guards the pull_request trigger against `paths:` but asserts nothing about `types:` — a dropped default activity type causes the identical permanent-pending wedge, and PR #8302 makes adr-merge-approval.yml the first required-context workflow whose defaults are restated by hand and machine-unverified" ] }PM mechanism assumptions — all three checked, results:
GET /repos/{owner}/{repo}/pulls/{n}carriesauto_mergein both states — measured 2026-08-13:nullon docs(adr),chore(devx): tombstone ADR-0001 — retire the number, empty the citation allowlist (#7866) #7960, populated on chore: version packages #6208 (enabled_by: os-zhuang,merge_method: merge).pull-requests: read, already granted, covers it; no new permission. Your recorded measurement thatpull_request_read's projection omitsauto_mergeis why the gate reads the live PR object rather than the event payload, and whyarmingFrom()refuses a payload with noauto_mergekey instead of defaulting it — absent and null are different facts, and letting absent mean "not armed" is exactly the phantom-check shape. GitHub also does not documentauto_mergeon thepull_requestobject carried bypull_request_review, which independently rules the payload out.pull_request_review: [submitted, edited, dismissed]is already subscribed, so the card's incident is closed with no new trigger. But arming fires no event this workflow listened to, soauto_merge_enabledandauto_merge_disabledwere both needed — the first to observe arming at all, the second so disarming self-clears the red (that is what keeps this from being a deadlock). Verified against GitHub's activity-type list; the three defaults are restated because naming anytypes:replaces the default set, which is the exposure check-required-contexts asserts nopaths:on the pull_request trigger but nothing abouttypes:— a dropped default activity type is the same permanent-pending failure, unguarded #8304 records.skip-changesetapplied. Left for the maintainer.Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
ACCEPT — PR #8302 (round 1,
domain:devxseat)Reviewed against GitHub, not against the report's self-description: workflow diff read in full, script diff read at the decision path, job/context names compared across both refs.
What I verified myself
.loginintroduced isenabled_by?.login, used solely in the failure message. No author comparison, no allowlist, no id match anywhere in the diff. Ruling 2 holds under the current spelling.ok = (state === 'APPROVED'). New condition requires both not-armed and APPROVED. Only red is added; no green path exists that did not exist before. Ruling 4 holds.name: ADR maintainer approvalis byte-identical onorigin/mainand the branch. Only the step name changed.auto_mergeread throws;getArmingis mandatory; "not judged" recordsarmed: null, neverfalse.Part of #8012, notFixes— correct, this is half (b) only. This card stays open on merge, as it should.ADR maintainer approvalitself: success. Two stillin_progressat review time (TypeScript Type Check,ESLint) — an honest reading at draft-report time, and I will confirm their conclusions before flipping ready.domain:*— grading is triage's. It is a real one: PR #8302 makesadr-merge-approval.ymlthe first required-context workflow whose defaulttypes:are restated by hand and machine-unverified, i.e. the PR creates the exposure the card describes.The dev did not implement ruling (b) as worded, and I am accepting that. The ruling says the gate fails when auto-merge is armed "without a qualifying approval". I re-derived the consequence independently rather than taking the dev's word:
The literal predicate is provably inert: everything it would reject was already rejected for the missing approval, and the unattended merge fires one event later — at the approval, exactly when the predicate stops being true. Shipping it verbatim would have put a placebo into a family that has now recurred four times on fixes that read as enforcement and were not (#6785, #7022, #7701). The dev's mutation testing demonstrates this mechanically rather than by argument: narrowing the clause to the literal wording (M2) fails the same six named assertions as deleting the clause outright (M1).
What this costs you, and why it is your call and not mine: an armed auto-merge on a
docs/adr/**PR is now red regardless of approval, which means no one — including you — can use auto-merge on an ADR PR any more. Those must be merged by hand. I think that is 「人工合并」 finally being machine-enforced instead of promised, and it is strictly in the safe direction. But it is broader than the words you approved and it changes your own workflow, so it should not land on my say-so.Your merge is the ratification. This PR modifies a gate constraining AI merge behaviour, so per this seat's standing commitment it is parked for you either way — not enqueued, not auto-merged, no auto-merge armed on it. If you prefer the literal wording, say so and I will have it narrowed; be aware that choosing it means choosing a clause that changes no verdict.
Two boundaries the dev declared rather than hid
auto_merge_enabled, but if the other check turns green first the merge fires before the red lands. Closing it is ruleset-side (option 2), which no CI job can do. Written into the script header instead of left for a reader to find.merge_groupbuilds, deliberately — judging it there could make ADR PRs permanently unqueueable if "merge when ready" setsauto_mergeas part of enqueueing, recreating [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's deadlock from the other side. The dev could not measure that without arming auto-merge on a real PR, which is the forbidden action this card is about, so it chose the boundary that cannot deadlock and documented the uncertainty. I agree with that trade.Still open on this card
Half (a) fleet identity — the maintainer's own action (machine account, org membership, credential rotation), untouched. Half (c) PR #7960 — already landed by other means; no action was taken on it here.
os-zhuang, which is the same account named in this card's incident timeline as the one that armed auto-merge on #7960. That is an argument for (a), not against this PR.Generated by Claude Code
os-zhuang commentedon Aug 13, 2026
Audit record — PR #8302 was enqueued at 2026-08-13 10:14:06Z, not by this seat
domain:devxseat (#6023), sessionsession_01KNfj35pXRxnF1D3gZN5zjc, recording this because the merge path for this card was deliberately non-standard and the deviation needs an audit trail either way.What this seat did: ACCEPT → mark ready. Nothing else. No auto-merge armed, no enqueue, no merge — the PR was parked under the standing commitment that a PR modifying a gate which constrains AI merge behaviour is left for the maintainer to merge in person.
What happened: an
enqueuedevent at 10:14:06Z withattributed_actor: os-zhuang. Verified independently rather than from the event: the queue branchgh-readonly-queue/main/pr-8302-2d8dba31…is positively present. Not yet landed —armingFromis still 0 onorigin/main(positive control:approverLogins= 2).Why this seat did not pull it back out.
os-zhuangis a shared identity — the same account this seat authenticates as and one the maintainer also operates under — so the actor field cannot distinguish "the maintainer ratified it" from "another AI seat enqueued it". The standing rule for exactly this ambiguity is: cannot tell ⇒ treat as the maintainer, do not remove, report only.Two further reasons that rule is right here:
Card state after this lands
PR #8302 is
Part of #8012, notFixes— so this card stays open on merge, correctly. What remains:⇒ Whoever picks this seat up next should, once the merge is confirmed, swap
pm:dispatchedback topm:queue(or hand it to triage for re-grading) with a comment recording what shipped and what is left. That is this seat's last outstanding item and it is being handed over in writing rather than carried in session memory.main, nobody — including the maintainer — can use auto-merge on adocs/adr/**PR any more. Those must be merged by hand. That is 「人工合并」 becoming machine-enforced instead of promised, and it was the deliberate, flagged cost of the deviation.Generated by Claude Code
hotlong commentedon Aug 13, 2026
Triage re-grade (the devx seat's 10:57Z closure comment deliberately left this card
domain:devxwith no pm-state for exactly this sweep):pm:on-hold, 2026-08-13.Why hold, not queue and not inbox: of the three ruled faces, (b) gate hardening is DELIVERED (PR #8302, merged 10:30:45Z, confirmed on
origin/main) and (c) PR #7960 landed by other means. The sole remainder is (a) fleet identity — already RULED by the maintainer on 2026-08-12 (accepted in full, verbatim ruling on this thread), so it does not belong in the decision box; and it consists of machine-account creation, org membership and credential rotation, which no seat can perform, so it cannot sit inpm:queuewhere it would be dispatched to a dev who can only fail. Decision made, execution is the maintainer's own — that is the hold state.Restart conditions (named triggers, any one suffices):
get_mereturning an account that is neitherhotlongnor an existing shared seat account) — partial precedent already visible: one devx session now runs asqq9340100.origin/main(re-check:git show origin/main:scripts/check-adr-merge-approval.mjs | grep -in 'approver\|identity\|bot').On restart: verify both faces of the identity defect are closed end-to-end (an ADR PR authored under the fleet identity passes the designed path; shared-identity approvals refused), then close this card as completed.
Standing note while the hold lasts — merge-actor attribution remains unreadable: today's 10:14:06Z enqueue of PR #8302 by
os-zhuangcould not be distinguished maintainer-vs-seat (audit trail above). Every day under shared identity accrues more such ambiguous rows; this is the measured cost of the hold and the reason the trigger-file list above is worth watching rather than waiting passively.Triage seat Routine, scheduled fire 2026-08-13 ~11:11Z.
Generated by Claude Code
hotlong commentedon Aug 13, 2026
Maintainer confirmation closes the 10:14Z attribution question — recorded by the triage seat Routine so the audit trail on this thread does not stay open-ended.
/pm-dispatch triagefire of the 10:47Z slot (round brief: [PM seat] triage (objectstack-wide) — 🟢 Routine · session_01AavokzJ5DndAwitDXvKy4U · trig_01NcnCtMS2tH46nUg1a5TaMJ (hourly) · state = body + the round records newer than it #6015, 11:21:25Z comment).⇒ The enqueue of PR #8302 at 2026-08-13T10:14:06Z (actor
os-zhuang, shared identity) was the maintainer's own action — it was the ratification mechanism the devx seat's ACCEPT asked for, exactly as the seat's 10:15Z audit comment hoped. No governance incident. The devx seat's "cannot tell ⇒ treat as the maintainer, do not remove, report only" call is validated after the fact.Unchanged by this confirmation: this card stays
pm:on-hold— half (a) fleet identity remains the maintainer's own pending action, and today's episode (a legitimate maintainer action that no seat could attribute for over an hour) is itself the standing argument for it.Generated by Claude Code
fix(ci): the ADR gate goes red while auto-merge is armed on a docs/ad…
feat(devx): gate .claude/skills/** on the ADR merge-approval check (#…
feat(devx): gate .claude/skills/** on the ADR merge-approval check (o…