Skip to content

feat(pm): approve-gated queue path for the governed surface — guard passes an authorized approval pinned to the exact head - #12755

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-12750-approve-gated-queue-guard
Aug 28, 2026
Merged

feat(pm): approve-gated queue path for the governed surface — guard passes an authorized approval pinned to the exact head#12755
os-zhuang merged 1 commit into
mainfrom
claude/issue-12750-approve-gated-queue-guard

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#12750

Approve-gated queue path for the governed surface: the Governed Surface Queue Guard's merge_group leg now PASSES a governed PR iff an APPROVED review by an authorized account is pinned to the PR's exact current head sha; otherwise it refuses exactly as before. The protocol text gains the second legal landing path, the proactive dual review-push, and the agent-no-approve guardrail.

Rulings executed (verbatim, untranslated)

  • 2026-08-27, the question that drove the design: 「需要我人工审查的,如果我真的审查了,并且点了批准,也不能合并吗?还是要等我 bypass吗」
  • 2026-08-27, the authorized set: 「os-zhuang hotlong 批准算数」
  • 2026-08-27, the proactive dual push (ruling increment on the card): 「需要批准的主动推送到这两个账户。」

The guard's new decision table (merge_group leg)

Review state on the governed PRVerdictExit
APPROVED by an account in GOVERNED_APPROVERS, commit_id == current head shaCLEARED0
APPROVED by an authorized account at an OLDER sha (any push after approval)REFUSED, stale — render names both shas3
APPROVED only by an account outside GOVERNED_APPROVERSREFUSED — render says it never counts3
Authorized approval later DISMISSED or superseded by CHANGES_REQUESTEDREFUSED (latest-decisive-per-reviewer)3
No approving review at allREFUSED3
PR head or review list unreadable (non-2xx, no parseable head.sha, missing token)REFUSED — never a pass4
Governed commit attributable to no PRREFUSED (unchanged)5
Nothing governed in the diffCLEAR, zero API calls (unchanged)0
  • GOVERNED_APPROVERS = ['os-zhuang', 'hotlong'] is a frozen constant in the guard script — the single source, the CONTRACT_REVIEW_TIER pattern. The self-test pins the membership to the ruling; every rendering derives its named accounts from the constant; protocol text references the constant and never copies the names.
  • The head read is a new makePullHeadReader — GET on the pull request via the same channel the review read already uses (standard GITHUB_TOKEN REST under the workflow's existing pull-requests: read scope; the workflow file is untouched). Read order per governed PR: head first, then reviews; an unreadable head refuses (exit 4) without ever constructing the review request. Sha comparison is case-insensitive; an empty/unparsable head pins NOTHING (fail closed).
  • pull_request (early-warning) leg byte-identical, including its API surface: no head read, the pre-existing any-approver informational reading, the same rendered lines — pinned by self-test rows (spy counts the head reader's calls: 0; the rendered review lookup(s) header and APPROVED review present, by: line asserted verbatim). Known and accepted consequence, bound by the card's byte-identical constraint: the warned forecast sentence still says "carries an APPROVED review by then" without restating the authorized-pinned narrowing; the queue leg enforces the real predicate regardless.
  • CHANGES_REQUESTED restraint unchanged: another reviewer's outstanding CHANGES_REQUESTED does not flip an authorized pinned approval (a reviewer's own later CHANGES_REQUESTED/DISMISSED does supersede their approval); rendered as informational, with the merge_group note updated to state the pinned predicate.

Protocol text — per-clause rewrites (clause homes located by grep)

.claude/skills/pm-dispatch/SKILL.md — ACCEPT path-fork, four-piece piece ② (the governed-landing rule): now states the two legal ends — 人工直合即审核记录 (fallback, unchanged) or 授权人工批准钉 head ⇒ 队列放行, with the approver set and the predicate single-sourced at the guard constant (GOVERNED_APPROVERS in scripts/pm/check-governed-queue-guard.mjs; APPROVED and commit_id == current head; a push after approval expires it). The flip-ready/enqueue/auto-merge prohibition is retained, now conditioned: 未钉批准 ⛔ 不翻 ready、不入队、不挂 auto-merge.

Same file, four-piece piece ③ (review-request step): the delivering seat now requests review from BOTH os-zhuang and hotlong in the same stroke (2026-08-27 「需要批准的主动推送到这两个账户。」 — a proactive push, never waiting to be discovered). The 2026-08-19 push-channel provenance stays (both rulings quoted). The author-identity-422 fallback is generalized to two accounts: the account matching the PR author gets the assign fallback, the other is review-requested normally. Account names appear here because this clause already named os-zhuang — following the file's existing convention (the dispatch's stated exception); the set's single source remains the guard constant, said in the clause.

Same file, Guardrails, governed-merge bullet: gains the dated fourth ruling line (2026-08-27 second path, pointer to the fork — no re-quote, no copied names) and the hard prohibition: ⛔ agent 席位永不以任一账号对受管面 PR 提交批准 review (the authorized set contains an account agent seats also operate, so the technical control is normative — same class as the never-enqueue rule), and the after-the-fact audit line now reads the APPROVER as well as the merger: agent 批准或不认识的合并 = 违规立案回滚.

references/contract-review.md — 清标即落地, the 三样不变 mention: updated so the pointer-level mention matches the fork's two legal ends (人工直合或授权批准钉 head 入队, single source pointed at the main file) and the chain-side echo of the prohibition: 链永不入队亦不代批. Net 0 lines (48/48).

references/landing-operations.md: untouched — grep found no governed-landing clause there beyond the main-red 人工直合 exception mention, which stays correct (direct merge remains legal).

Cut ledger (zero-headroom ratchets — net 0 per file, surviving homes)

SKILL.md 1005 → 1005 (seam lines 756-765: 10 → 8; Guardrails bullet: 5 → 7). Cuts, each with its surviving home:

  1. 「本条座位纪律是唯一的 merge 前防线(per-PR 事前门已退役,没有机器会替你挡)」 — factually superseded by this very change (the queue guard IS a machine pre-merge line, and now judges the ruled predicate). History and the retired-gate story survive verbatim in the guard script's header, which this clause now points into via the constant.
  2. The MCP draft-bit mechanism detail (3 lines → 1 pointer line): update_pull_request always sending the draft bit, the reviewers-only incident, the dedicated REST endpoint — all survive in full at references/platform-readings.md (the「update_pull_request 不管传不传都发送 draft 位」 entry); SKILL.md keeps the operative rule + pointer.
  3. Measurement provenance compression in piece ③: 「当日实测 draft 可点名审核且推送到达手机」 → 「当日实测推送到达(「推送到了」)」 — the maintainer confirmation quote is retained; only the descriptive prose is tightened. All three 2026-08-19 ruling quotes retained.
  4. Guardrails: 「维护者不认识的条目 = 席位违规,立案回滚」 rewritten in place to add the approver reading (content extended, phrasing tightened; nothing dropped).
  5. contract-review.md: 「低于档位的席清不了标,故落不了地」 (a derivation, not a rule) — the fuse mechanics it derives from live two sections down in the same file (降档保险丝(机读)) and in 复审资格 (「低于档位的席照旧留标等待」).

No re-wrap purchases: freed lines come from the content cuts above; the two long quote-spanning lines in the rewritten seam follow the file's existing convention for such lines (the same shape the untouched neighbors already use).

Verification (all at head b5fb638; exit codes captured before any pipe; verdict lines quoted from the gates themselves)

  • Guard self-test — was 86 cases at base, now 113 (+27 rows: per-authorized-login pinned pass, membership pin to the ruling, stale/no-commit_id/unauthorized/superseded/dismissed/none, case-insensitive sha, unparsable-head fail-closed, verdict-table rows for stale + unauthorized, end-to-end runGuard wiring head-then-reviews, PR-leg zero-head-read byte-identity, unreadable-head exit-4 with reviews never constructed, head-reader unit rows, render rows deriving accounts from the constant): ✓ check-governed-queue-guard self-test: 113 cases pass (register-driven verdicts, the queue/PR event split, latest-decisive approval reduction, the 2026-08-27 authorized-approval-pinned-to-head predicate on the queue leg — pass, stale, unauthorized, dismissed/superseded, none — with the PR leg byte-identical and head-read-free, ...) (exit 0, captured as EXIT=0 before the file was paged).
  • pnpm check:pm-skill-ratchet per-file: ✓ ... SKILL.md is 1005 lines (ceiling 1005; headroom 0) · ✓ ... SKILL.md: widest table row is 765 bytes (pin 765; headroom 0) · ✓ ... contract-review.md is 48 lines (ceiling 48; headroom 0) · ✓ ... landing-operations.md is 80 lines (ceiling 80; headroom 0).
  • ✓ check-skill-id-lint: 23 file(s) clean (pattern /#[0-9]{3,}/g).
  • ✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md) and claim no others.
  • ✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files · ✓ check-skill-frame-freshness: the decision frame in this tree is current with origin/main (fetched just now).
  • Derived union: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — first stderr line: dispatch-gates: gate list derived from the tree of 'objectstack-ai/objectstack' at commit b5fb6383a. All 17 matched families run green in one os-verify-lock invocation (os-verify-lock: VERDICT command-exit 0), including check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:cross-package-test-inputs, check:doc-authoring, check:doc-formula-expressions (after turbo run build --filter=@objectstack/formula and --filter=@objectstack/lint cleared its PREREQUISITE NOT MET — not a red, nothing was measured before the build), check:entry-guard, check:parse-guard, check:pm-governed-merges (self-test 206 assertions), check:pnpm-filter-targets, check-ci-filter-parity, check-cross-package-test-inputs. Convention-triggered by the gate-script edit and run green: node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates (✓ dispatch-gates self-test: 719 cases pass.). Also check-nul-bytes: OK (scanned 7093 text file(s) ...).
  • The derivation printed a STALE-TREE warning: origin/main moved 2 commits past this branch point and the two changed derivation inputs are scripts/vercel-ignore-docs.sh / .selftest.sh — neither is in this diff's surface; the queue's merge-time re-validation covers the range.
  • No external test file pins the guard script (grep over scripts/tests for its name: only the workflow and SKILL.md reference it); its own --self-test is the suite, run above.
  • NOT MEASURED locally, declared: the guard's live merge_group half (a real queue build with the real GITHUB_TOKEN) is CI-only — the end-to-end wiring is exercised in the self-test with injected readers; the workflow invocation is unchanged and untouched.

Acceptance replay (the card's four scenarios)

  1. Governed PR with os-zhuang's approval pinned to head enters the queue and merges — self-test: the-pinned-predicate-is-wired-end-to-end-an-authorized-pinned-approval-CLEARS (runGuard, head read + review read, CLEARED exit 0), plus per-login rows for both constant members.
  2. Same PR after a new push (stale approval) is dequeued red — the-pinned-predicate-is-wired-end-to-end-a-stale-approval-REFUSES (exit 3) and the render row proving the refusal names both shas.
  3. Unauthorized approval is dequeued red — an-unauthorized-account-approval-REFUSES-the-merge-group-with-code-3 and its never-counts render row.
  4. Bypass direct merge still works — structurally unchanged: a direct merge produces no merge_group event and no guard run (stated in the guard header, which this PR keeps); the direct-merge path is named as the fallback in every rewritten clause.

Notes for the reviewer

  • Non-goals honored: governed-surface definition, draft-only delivery, four-piece, audit regime, --test predicate, workflow yml, branch-protection assumptions, labels — all untouched.
  • Deliberate choice, per the dispatch's account-naming rule: the four-piece piece ③ names both accounts (the clause already named os-zhuang — existing file convention); piece ② and the Guardrails reference the constant without names.
  • Found while implementing, outside this card's surface: AGENTS.md Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 carries the bolded sentence "Reviewed + approved + fully green does not override this." and the unconditional never-enqueue wording — both now contradicted by the ruled second path for the authorized-pinned case. AGENTS.md is governed, ratcheted, and outside this card's dispatched file surface, so this PR does not touch it; flagged in the report for PM triage (a follow-up card on the PD feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 paragraph).

Generated by Claude Code


Generated by Claude Code

…uard passes an authorized approval pinned to the exact head
merge_group leg: pass iff an APPROVED review by an account in
GOVERNED_APPROVERS (single source in the guard script) has commit_id equal
to the PR's current head sha; stale/unauthorized/dismissed/superseded never
count; unreadable PR head or review list stays a refusal (exit 4).
pull_request leg byte-identical, head-read-free. Protocol text gains the
second legal landing path and the proactive dual review-push, and the
guardrail: an agent seat never submits an approving review on a
governed-surface PR, under any account.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MnijPVVDakqK2J335JoJtq
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 27, 2026 — with Claude
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Aug 27, 2026
@os-litant
os-litant marked this pull request as ready for review August 28, 2026 05:14
@os-litant
os-litant enabled auto-merge August 28, 2026 05:14
@os-litant
os-litant added this pull request to the merge queueAug 28, 2026
@github-merge-queue
github-merge-queueBot removed this pull request from the merge queue due to failed status checks Aug 28, 2026
@os-zhuang
os-zhuang added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit 93a554dAug 28, 2026
37 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-12750-approve-gated-queue-guard branch August 28, 2026 06:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants

@os-litant@os-zhuang@claude