You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(pm): an authorised APPROVED review satisfies the queue guard on any commit (2026-09-04 ruling) (#15203)
* fix(pm): an authorised APPROVED review satisfies the queue guard on any commit
The maintainer ruled on 2026-09-04, in the live PM chat, while a governed PR
that an authorised approver had approved three times kept falling out of the
merge queue (verbatim, untranslated):
你的门禁有问题,只需要有人工批准记录就行,不需要卡最新的提交。
So the `merge_group` leg of `check-governed-queue-guard.mjs` now passes a
governed PR iff an account in `GOVERNED_APPROVERS` holds a latest-decisive
APPROVED review — on ANY commit, with `commit_id` unread by the decision. This
supersedes the sha-pin half of the 2026-08-27 predicate and nothing else: that
ruling's approver set stands, DISMISSED and superseded approvals still never
count, an unauthorised APPROVED still never counts, and an empty or unreadable
review list still fails closed.
The accepted cost is stated out loud in the header rather than left to be
discovered: a push after an approval is no longer re-reviewed by this gate. It
is the same shape of cost the 2026-08-12 ruling on the retired per-PR gate
accepted when it dropped the identity proxy.
`staleApprovers` is kept as a PRINTED READING and nothing more — authorised
approvals given on a commit that is no longer the head, which are also in
`approvers` because they count. A cleared queue log therefore names the commit
each approval was given on, so the accepted cost is visible at the moment it is
paid. The PR head is still read for that line but decides nothing, so an
unreadable head is no longer a refusal: a read that decides nothing may not
block a landing. The review list, which IS the predicate's input, still refuses.
The `pull_request` early-warning leg, the path test, the exit codes, the
check-run name, `GOVERNED_APPROVERS` and the generated-surface register are all
untouched, and the early-warning leg's rendered output stays byte-identical.
Self-test: 133 cases pass. The cases that pinned the sha pin now pin the ruling
in both directions — an authorised approval on an OLDER commit passes, while
dismissed, superseded, unauthorised and empty still refuse.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
* fix(agents,pm): carry the 2026-09-04 unpinned-approval ruling into the governed surfaces
The guard half landed in the previous commit; this is the half that lives in
governed files, and the two only work together. Without it the CI job "Claude
hook guard self-tests" is red on main, because the seat-side enqueue hook's
self-test pinned the sha pin the maintainer retired.
`.claude/hooks/guard-governed-enqueue.selftest.sh` — the fixture that was
`governed-stale` is now `governed-approved-on-an-earlier-commit`, and the case
that asserted it BLOCKS asserts it ALLOWS. It is flipped rather than deleted on
purpose: the hook holds no predicate of its own, so that direction arrives
entirely through the imported predicate, and flipping the guard back would flip
this case red. Adds `expect_lacks`, because a retired RULE leaves its sentence
behind in the text a seat reads long after the predicate stopped enforcing it,
and only an assertion that a phrase is ABSENT can catch that.
`.claude/hooks/guard-governed-enqueue.sh` — the blocking message no longer tells
a seat that a push unpins the approval; it quotes the ruling instead. The
`approvalsOnEarlierCommits` line is dropped from the rendered detail, because
since the ruling that bucket can only be non-empty when the verdict is
`approved`, and the detail string is only ever printed on the blocking path.
Both renames land here, with their only importer, in one commit:
`pinnedApprovalVerdict` becomes `authorizedApprovalVerdict` and `staleApprovers`
becomes `approvalsOnEarlierCommits`. A bucket still named for a refusal is one a
later reader re-derives a refusal from. Renaming either without this hook in the
same diff would have turned the hook into a silent fail-open: it catches the
failure and ALLOWS, one warning line on stderr, every governed enqueue through.
`AGENTS.md` and `.claude/skills/pm-dispatch/SKILL.md` — the two prose sites that
stated the retired pin as normative now state the ruled predicate, with the
ruling quoted verbatim and the accepted cost named once. The skill rewrite is
line-for-line so the line ratchet is unmoved. The sentence that enumerates the
governed surfaces is untouched.
Hook self-test: 50 passed, 0 failed. Guard self-test: 133 cases pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
* style(agents,pm): re-wrap the rewritten governed-landing paragraphs to the 120-byte line budget
`check:pm-skill-ratchet` prices a per-session token read by counting lines, so a
long line is an unmetered tax. The rewrite in the previous commit put AGENTS.md
L313 at 136B and two SKILL.md lines at 121B and 164B.
Re-wrapped at legal break points with the content otherwise unchanged, and the
verbatim ruling kept whole on one line in both files — a soft line break inside
「」 renders as a space, and a quotation with a space in it is a quotation that
was edited. AGENTS.md is 1161 lines against its 1162 ceiling; the pm-dispatch
skill is 971 against 1005.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
---------
Co-authored-by: Claude <noreply@anthropic.com>
"$(jq -nc --arg c "$HEAD_SHA"'[{state:"APPROVED",user:{login:"os-zhuang"},commit_id:$c},{state:"DISMISSED",user:{login:"os-zhuang"},commit_id:$c}]')")"
0 commit comments