Skip to content

feat(pm): the relay requests review from every GOVERNED_APPROVERS account on a Tier H pull; CODEOWNERS routes every Tier H surface to both approvers - #25064

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-25046-tier-h-review-request
Oct 21, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-25046-tier-h-review-request

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #25046
Clause-②: no

维护者速读(草稿)

改了什么

  • .github/CODEOWNERS:六个 Tier H 受管面(docs/adr/、skills/、AGENTS.md、CLAUDE.md、docs/NORTH-STAR.md、.github/workflows/)与三条执法链行(本文件、check-governed-merges.mjs、check-governed-queue-guard.mjs)全部路由到 @os-zhuang @hotlong;文件头写明 owner 列表镜像 GOVERNED_APPROVERS 常量。
  • 中继 fleet-write:pr_create(开 draft PR)与 pr_ready(转正式)落地后,执行器读 PR 完整文件清单、按注册表判级;命中 Tier H 就向 GOVERNED_APPROVERS 里尚未被请审、指派或已评审的每个账户发 review request;PR 作者本人是批准账户时改为 assign(GitHub 对"请审作者"回 422)。Tier S、未受管、已覆盖的 PR 零写并在 run 摘要说明;文件清单读不到或被截断、写入被拒,都是 FAILED action,绝不静默跳过。
  • check-governed-queue-guard.mjs --self-test 新增 12 条用例:读磁盘上的 CODEOWNERS,与注册表 Tier H 行、GOVERNED_APPROVERS 常量比对,漂移即红。不新增门禁、check-run 或巡检行。
  • pm-dispatch SKILL.md 第 ③ 条与 rest-channel.md 改为指向该 op(各净 0 行)。

为什么改

  • 席位开的 Tier H PR 是 draft,且一直 draft 到获批;GitHub 的 CODEOWNERS 只在 PR 开为 ready 或转 ready 时才请审(本仓实测:PR 24859 的 draft 创建于 11:19:43,hotlong 的请审 11:22:14 才到,紧跟 ready_for_review 一秒之后)。所以 draft 阶段没有任何机制通知批准人;原 CODEOWNERS 又只写了 docs/adr/ 到 @hotlong,os-zhuang 从未被叫到。「应该是skills有问题,申请我批准的应该推送给我,我看不到任何提示」—— 请审现在是一个 op,不再是一句散文。

风险与代价(含回滚)

  • 每次中继开 PR 或转正式多 1 到 3 次读(文件清单分页、PR、reviews);Tier H 命中才有最多 2 次写,走原有写节流。
  • 文件清单读不到时该 action 判 FAILED(PR 已开着,run 摘要点名"未发请审"),dev 的 dispatch 以 exit 5 收场 —— 这是刻意的响亮失败;代价是偶发平台抖动会把一次开 PR 记成失败,席位按行手补即可。
  • CODEOWNERS 每一行都要求 owner 集合恰等于常量:以后改 GOVERNED_APPROVERS 必须同一 PR 改 CODEOWNERS,否则守卫自检红。
  • 回滚:revert 本 PR 即可;无数据、无 workflow、无发布面变动。

席位意见

(留空)

你要做的

  • 批准本 PR(Tier H:.github/CODEOWNERS 自身行;AGENTS.md 所列受管面的路由规则由本文件承载)。获批后席位落地。

What this PR does

A seat-driven Tier H pull is opened draft and stays draft until the approval, and GitHub requests a code owner only at ready_for_review. CODEOWNERS listed docs/adr/ to @hotlong alone, and SKILL.md's "request review from both approvers" was prose no op performed. Three things change:

  1. .github/CODEOWNERS routes every Tier H register row (/docs/adr/, /skills/, /AGENTS.md, /CLAUDE.md, /docs/NORTH-STAR.md, /.github/workflows/) and the enforcement chain (itself, check-governed-merges.mjs, check-governed-queue-guard.mjs) to @os-zhuang @hotlong; its header names GOVERNED_APPROVERS as the list it mirrors. GET /repos/objectstack-ai/objectstack/codeowners/errors?ref=claude/issue-25046-tier-h-review-request answers {"errors":[]} (read 2026-10-21 after the push), so both rows are live — a code owner without write access would be an error there.
  2. The request is an op. fleet-write/ops.mjs marks pr_create and pr_ready with governedReview: true and spells the two conditional writes in governedReviewRequests. After the row's own requests land, execute.mjs reads the pull's whole file list (every page; a walk past the endpoint's 30-page ceiling is a FAILED action, never "nothing governed"), classifies through the register (governedPathsIn + landingTierOf, scoped to payload.repo by repoIdForSlug), and on a Tier H hit requests review from every GOVERNED_APPROVERS account not already requested, assigned or holding a submitted review — the union the H43 patrol reads as covered. The pull's own author is never requested (GitHub answers 422); a missing approver who is the author is assigned instead. Each answer is judged like the assignees leg: a 2xx lacking a login sent is a FAILED action naming it. Tier S (.claude/** alone), ungoverned and covered pulls send nothing and the summary row says which.
  3. The mirror is pinned inside an existing self-test. check-governed-queue-guard.mjs gains codeownersMirrorVerdict, and its --self-test reads .github/CODEOWNERS from disk: a Tier H row with no line, any line whose owner set differs from the constant, or a routed Tier S row reds it. No new gate, check-run or patrol row.

Landing site for the op: the relay executor (candidate b). Of the three homes the card named, only the executor knows the pull number at the right moment (the create's answer), holds the token, and runs on the board's main checkout where the register lives, so the Tier H decision is the board's single source rather than a seat checkout that may be behind. The seat client (a) would have to compute the diff from a compare and could not name a number that does not exist yet; the landing helper (c) does not exist as a script (the landing stroke is an actions file) and its moment — after the approval — is too late for Tier H.

Measured — the five hypotheses

  • H1 (CODEOWNERS premise): held. origin/main 63085c380 had three rows, all @hotlong. The Tier H row set is derived from GOVERNED_SURFACES (governedSurfacesFor('objectstack'), tier H) and spelled as root-anchored prefix/exact patterns; the self-test pins the on-disk file against that derivation and against GOVERNED_APPROVERS, so the two stay in step by a pin, not by hand. The merges self-test's classification of .github/CODEOWNERS as a near-miss (not a governed row) is unchanged.
  • H2 (home of the op): the executor. See above. Both governed scripts import only Node built-ins and sibling modules, so the runner (no pnpm install) loads them; validate.mjs already imported check-governed-merges.mjs on the runner.
  • H3 (trigger moment): both pr_create and pr_ready. The timeline of PR 24859 shows the draft create at 2026-10-20T11:19:43Z with no review_requested, then ready_for_review at 11:22:13Z and CODEOWNERS' review_requested hotlong at 11:22:14Z. So the draft open is the moment the platform is silent, and the op must fire there. It also fires at pr_ready, because the file list may have grown a Tier H path since the open and the follow-up is idempotent (zero writes when covered), so the second firing costs one read.
  • H4 (author identity): both branches pinned with fixtures. A objectstack-fleet[bot]-authored pull requests both approvers; a pull authored by an approver requests the other and assigns the author.
  • H5 (idempotence): the API's duplicate behaviour is never exercised. The plan subtracts the pull's pending requested_reviewers, its assignees and the authors of its submitted reviews (GET …/pulls/{n}/reviews, any state but PENDING) before sending, so a re-run, a pr_ready after a pr_create, or the platform's own CODEOWNERS request landing first costs zero writes. No throwaway PR was opened.

Tests

  • node scripts/pm/fleet-write/execute.mjs --self-test → ✓ fleet-write/execute self-test: 167 cases pass across 18 batteries (new battery of 29 cases; floor 17 → 18).
  • node scripts/pm/fleet-write/validate.mjs --self-test → ✓ fleet-write/validate self-test: 160 cases pass across 11 batteries (the refused-family pin now covers the two follow-up endpoints; the alsoSpends pin names three rows).
  • node scripts/pm/fleet-write/dispatch.mjs --self-test → ✓ fleet-write/dispatch self-test: 235 cases pass across 21 batteries (unchanged file).
  • node scripts/pm/check-governed-queue-guard.mjs --self-test → ✓ check-governed-queue-guard self-test: 334 cases pass (new battery of 12 cases; floor 26 → 27).
  • Ablation A (the change committed first, then scripts/ablation-replace.mjs): anchor followed = await requestGovernedReview(…) → followed = { ok: true }; tool evidence anchor x1 -> x0, blob c660b170c9e0 -> 58e7fdeed4fd; the executor self-test went red 17 of 167 case(s) failed, including ⭐ pr_create on a Tier H diff: … ONE requested_reviewers POST naming BOTH approvers; restore verified blob == HEAD (c660b170c9e0) and git diff HEAD is empty.
  • Ablation B: anchor /docs/adr/ @os-zhuang @hotlong → /docs/adr/ @hotlong in .github/CODEOWNERS; blob 15d3fffd682e -> f223af300219; the guard self-test went red 2 of 334 case(s) failed (the-file-on-disk-mirrors-the-register-and-the-approver-list: CODEOWNERS line 42 /docs/adr/ names @hotlong, not every GOVERNED_APPROVERS account); restore verified blob == HEAD (15d3fffd682e) and git diff HEAD is empty.

Gates (local subset, at head 91669b139)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; changeset from the merge base) derived 60 families on the actual diff — the dispatch lead's 58 plus pnpm check:closing-target-claim and pnpm check:ratchet-remedy-authority. Every one was run with the capture idiom and reconciled: ✓ dispatch-gates --ran: 60 derived famil(ies) accounted for — 59 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3). The NOT MEASURED one is pnpm --filter @objectstack/lint run check:doc-formula-expressions (exit 3, PREREQUISITE NOT MET: it wants a @objectstack/formula + @objectstack/lint build; this diff touches no package, so no build closure was made — CI runs it). Among the 59 green: check:pm-fleet-write-{dispatch,execute,validate}, check:pm-governed-merges, check:pm-governed-prose, check:pm-skill-ratchet (SKILL.md 319/319, rest-channel.md 82/82, every edited line under the 120-byte line budget), check:pm-skill-id-lint, check:skill-frame-sync, check:pm-dispatch-gates (fast tier, 1913 cases, 328.7 s), check:pm-write-pace, check:doc-authoring, check:required-contexts, node scripts/check-self-test-wired.mjs and its --self-test, node scripts/pm/check-governed-queue-guard.mjs --self-test. The families CI runs on the PR are read from its check-runs by the seat; none was re-run locally beyond this subset.

Acceptance notes

  • AGENTS.md Prime Directive 14 says "CODEOWNERS routes review requests for docs/adr/ only, so nothing summons the maintainer on the other Tier H surfaces". After this PR that sentence is stale. AGENTS.md is outside this card's file surface (Tier H, its own line ratchet), so it is not edited here; a one-line rewrite is the follow-up. check:pm-governed-prose does not pin that sentence (verified green here).
  • The pr_ready + automerge_enable landing stroke now performs one extra read (the readied pull's files) and, on a Tier H pull, up to two paced writes; strokePacing does not count them (conditional, at most two per pull), so they ride rule ①'s gap — documented in the ops.mjs header.
  • A relay pr_create whose file list cannot be read is now a FAILED action (exit 5 for the dev's dispatch) with the pull already open; the row and the log say what was not sent. This is the loud direction on purpose: silence here is the defect.
  • This PR's own pr_create goes through the relay executor on main, which predates this change, so this pull requests nobody by itself; the seat requests review after its at-tier contract review, per the dispatch order.
  • check-governed-merges.mjs --self-test keeps pinning .github/CODEOWNERS as a near-miss that is NOT a governed row; CODEOWNERS routes its own edits instead. Not changed, and I do not think it is wrong: the file is an enforcer, and the register is the audit's surface list.

Out-of-scope findings

  • AGENTS.md Prime Directive 14's CODEOWNERS sentence (above): an agent-read rule that becomes false, not a runtime contract — noted, not filed; carrier: the domain:skills seat, as a one-line follow-up on the Tier H file. Dedupe words: CODEOWNERS, docs/adr only, Prime Directive 14, summons.

Generated by Claude Code

…ount on a Tier H pull, and CODEOWNERS routes every Tier H surface to both

A seat-driven Tier H pull is opened draft and stays draft until the approval, and GitHub
requests a code owner only at ready_for_review (measured on this repository's own Tier H
pulls: the CODEOWNERS request arrived one second after the ready flip, never at the draft
create). CODEOWNERS listed docs/adr/ -> @hotlong alone, and SKILL.md's "request review from
both approvers" was prose no op performed. So an approver waited on a pull nobody told him
about.

- fleet-write/ops.mjs: pr_create and pr_ready carry governedReview; governedReviewRequests
  spells the two conditional writes (requested_reviewers, issue-assignees); pr_ready spends
  issues for the assignee fallback.
- fleet-write/execute.mjs: after a governedReview row lands, read the pull's whole file list
  (every page, a cap that reds as a truncated subset), classify through the register
  (governedPathsIn + landingTierOf, scoped to payload.repo), and on a Tier H hit request
  every GOVERNED_APPROVERS account not already requested, assigned or holding a submitted
  review; the pull's own author is assigned instead (a request naming it is a 422). Tier S,
  ungoverned and covered pulls send nothing and say so; an unreadable list, pull or review
  list, a truncated walk or a refused write is a FAILED action, never a quiet skip.
  Self-test: a new battery of 29 cases; fixtures of the pr ops now declare the files read.
- fleet-write/validate.mjs: pins the two rows, the two endpoints against the refused families,
  and the three alsoSpends rows.
- .github/CODEOWNERS: every Tier H register row and the enforcement chain route to
  @os-zhuang @hotlong; the header names GOVERNED_APPROVERS as the list it mirrors.
- check-governed-queue-guard.mjs: codeownersMirrorVerdict reads the file and the self-test
  reds when a Tier H row is missing, an owner set drifts from the constant, or a Tier S row
  is routed (a pin inside an existing self-test, no new gate).
- pm-dispatch SKILL.md step 3 and rest-channel.md point at the op (net 0 lines each).

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M82feuetLzrCiBz41BJb7L
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 21, 2026
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation labels Oct 21, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 91669b139bd4fbba7459e7cedd57a20a9e676d56
Local-runs: none
Scope: .claude/skills/pm-dispatch/SKILL.md, .claude/skills/pm-dispatch/references/rest-channel.md + scripts/pm/fleet-write/ops.mjs, scripts/pm/fleet-write/execute.mjs, scripts/pm/fleet-write/validate.mjs, .github/CODEOWNERS, scripts/pm/check-governed-queue-guard.mjs — read in the cited code: scripts/pm/check-governed-merges.mjs (the register, governedPathsIn, landingTierOf, pullPathsFrom, pullTruncationReason), scripts/pm/fleet-write/dispatch.mjs (strokePacing), scripts/pm/write-pace.mjs (the gaps), .github/workflows/fleet-write.yml (the mint), .github/workflows/governed-surface-guard.yml, scripts/pm/check-half-states.mjs (H43)

Inputs: card #25046 (body, the Claim 6308718151, the os-dev report 6309589418); PR #25064 body, file list (7 files, +525/−44) and the three-dot diff at the head; the head's check-runs; rules/branches/main, rulesets, branches/main/protection and codeowners/errors?ref=HEAD by REST. The head was verified: a fetch of the branch resolves to the PR's head.sha. Nothing built, run or re-run; the dev's local gate and ablation readings are reported as the dev's, not re-measured.

① Derived judgments

A. The relay's governed-review follow-up (ops.mjs, execute.mjs, validate.mjs)

  • Single source — RIGHT. governedReviewPlan imports repoIdForSlug, governedPathsIn and landingTierOf from the register and GOVERNED_APPROVERS from the queue guard; no list is restated in code, and the battery injects another list (approvers: ['someone-else']) to prove it. The one restated list is prose in the ops.mjs header (the six Tier H globs), a comment nothing pins: documentation drift only.
  • Mixed diff — RIGHT. landingTierOf answers S only when EVERY matched row is S, so one Tier H path beside any number of .claude/** or ungoverned paths is H (pinned: .claude/agents/os-dev.md + docs/NORTH-STAR.md → H, two sends).
  • Tier S-only, ungoverned, or a repo outside GOVERNED_REPOS — RIGHT, zero writes. matched.length === 0 is mapped to tier null BEFORE landingTierOf (whose empty-slice answer is H, fail-closed, the wrong answer for "send"); on anything but H the executor writes one summary row and never reads the pull or its reviews.
  • Covered subtraction — RIGHT, with one bound. Coverage is requested_reviewers ∪ assignees ∪ submitted reviews (state ≠ PENDING), case-insensitive: the H43 union verbatim. Bound: reviews are read as ONE page of 100 with no walk, so past 100 reviews an approver who reviewed can be re-requested; the failure direction is one extra idempotent request, never a missed one. DISMISSED counts as covered, as H43 counts it.
  • Author identity — RIGHT. The pull's user.login is never put in toRequest; an uncovered approver who IS the author goes to toAssign (additive POST issues/{n}/assignees) while the other is requested; both branches pinned with fixtures.
  • Pagination and truncation — RIGHT. readPullFiles walks per_page=100 pages in order until a short page (a sound completeness proof for offset paging), caps at 30 pages (the endpoint's 3,000-file ceiling) and reports a walk that fills the cap as a FAILED action, never "nothing governed"; renames count by their old path through pullPathsFrom. Note: the register already carries pullTruncationReason (completeness by changed_files); the new walk proves the same question differently and does not reuse it. Sound; one duplicated question.
  • Permissions — RIGHT, within existing grants. requested_reviewers is pull-requests: write, both rows' own permission; the assignee fallback is issues: write, now alsoSpends on both rows; both are literal mint inputs on every stroke in the unchanged fleet-write.yml, and the validate pin lists exactly pr_create, pr_ready, automerge_enable. The three reads are covered by the same grants. No token widening.
  • Loud failure — RIGHT. An unreadable or truncated file list, an unreadable pull or review list, a refused write, or a 2xx lacking a login sent each sets stoppedAt: a FAILED row naming what was not sent, exit 5, later actions not attempted; a create answering without a number is FAILED with a not sent row.
  • Existing strokes — RIGHT on writes, one named cost. pr_ready + automerge_enable on a Tier S or ungoverned pull: ZERO extra writes (pinned in the landing-stroke case: the file list GET sits between the mutation and the arm), one extra GET. The cost: that GET is a new failure point on the landing stroke; a 5xx on it stops the stroke before automerge_enable, exit 5, the pull left ready and unarmed. The dev named this; the seat repairs by re-sending.
  • Write pacing — the dev's description is accurate. strokePacing counts only spec.requests() (rule ⑤, the 30 s batch gap); the follow-up's at most two writes per pull go through rest() → paceWrite and ride rule ① (3 s minimum gap) and rule ② (40 writes per hour), unbudgeted by fits. Worst case a batched stroke of K Tier H creates adds up to 2K writes at 3 s each, small against the job budget, but they DO spend the hourly ceiling.
  • A correction to the PR body's H5 ("the API's duplicate behaviour is never exercised"): at pr_ready the platform's own CODEOWNERS request lands about one second after the mutation (the dev's own reading of PR 24859), so the executor's pull read can precede it and send a request naming the same logins; GitHub answers 201 with each reviewer listed once. Harmless and idempotent, but reachable.

B. .github/CODEOWNERS

  • Rows — RIGHT. The six Tier H rows /docs/adr/, /skills/, /AGENTS.md, /CLAUDE.md, /docs/NORTH-STAR.md, /.github/workflows/ are exactly codeownersPatternFor over the register's Tier H rows for this repository: root-anchored, a trailing slash where the register uses a prefix, an exact file where it uses exact; no Tier S row; three chain rows; every row @os-zhuang @hotlong. codeowners/errors?ref=91669b1 answers an empty list, read by this review at the head sha.
  • Effect under the live rules — routing only; it blocks nothing. GET rules/branches/main (ruleset 12119582, active) shows pull_request with required_approving_review_count: 0 and require_code_owner_review: false, and required_status_checks as the five contexts. A CODEOWNERS row summons; it never gates. Classic branch protection: NOT MEASURED (branches/main/protection answers 403 to this token).
  • Streams now summoned, none blocked: Dependabot's monthly github-actions PRs (.github/dependabot.yml) are opened ready on .github/workflows/** and now request both approvers (Tier H by the register already; before, nobody was asked). Every ready PR touching check-governed-merges.mjs or check-governed-queue-guard.mjs now asks both approvers; the latter is a lane file edited for each new self-test battery, and its edit rate is NOT MEASURED (git-history.mjs count refuses on this shallow clone).

C. The mirror pin (codeownersMirrorVerdict, inside check-governed-queue-guard.mjs --self-test)

  • Placement — RIGHT. A 12-case battery in the existing self-test, floor 26 → 27, run by the existing Governed Surface Queue Guard check (its workflow runs --self-test first) and by the lint lane. No new script, workflow, check-run or patrol row: a case inside an existing self-test, as the card requires.
  • Direction — RIGHT. It reads .github/CODEOWNERS from disk and reds on a Tier H row with no line, any line whose owner set differs from the constant (fewer OR other, exact set equality), a line at a Tier S row's pattern, and a header that never names GOVERNED_APPROVERS; a different approvers list reds the on-disk file, so the list is read, not restated.
  • Gap, low. The Tier S direction is caught only at the register's exact spelling (/.claude/): a line such as /.claude/skills/ or a bare * is judged as a chain row and passes on its owner set alone. The compensating control is that CODEOWNERS is itself routed to both approvers.

D. The rules text (SKILL.md ③, rest-channel.md) — net 0 lines each (319 → 319, 82 → 82); every edited line within the 120-byte budget (105, 119, 118, 69; 120).

  • Survives: the request as an op at pr_create and pr_ready to every GOVERNED_APPROVERS account (line 198); the author-identity 422 and the assign fallback (line 199); the draft-bit-free REST path, now as the seat's hand-fill route (line 200); ready/draft via the relay op, the MCP fallback refused, endpoints in rest-channel (line 201); the requested_reviewers endpoint in rest-channel, now annotated as the relay's for Tier H.
  • Relocated, flagged in ③: old line 200 made the ROUND REPORT name that the assign fallback was taken. New line 200 attaches the naming to the seat's hand-fill only; the op's assign is recorded in the run-summary row the seat is told to read. The rule's function (the fallback is named where the seat reads) survives; the round report no longer carries the author-identity case. One net-0 reword of line 200 restores that trail if the seat wants it.

② Semver level

skip-changeset is RIGHT: the diff touches .github/CODEOWNERS, scripts/pm/** and .claude/**, no file of any released package; Check Changeset is success. Clause-②: no on the PR body is RIGHT: no public contract widens or narrows; no changeset is owed and no (narrowing) arm applies.

Check-runs on the head, read 2026-10-21T06:36Z: 30 success, 13 skipped by the paths filter (Build Core, Build Docs, Console Pin Gate, the dogfood shards, the opt-in harnesses), ONE in progress: Test Core (1/6), so Test Core is NOT MEASURED at this reading. Required contexts: TypeScript Type Check success; Lint & Repo Gates success (carries check:pm-fleet-write-{validate,execute,dispatch}, check:pm-governed-merges, check:pm-governed-prose, the skill ratchet); Governed Surface Queue Guard success (the run that executed the new mirror battery); Build Core skipped by filter; Test Core five shards success, one in progress. The landing waits on that shard; this verdict does not depend on it.

③ Boundary flags

  1. Dev deviation, the pin's home (the queue guard's self-test rather than beside a relay file): ACCEPTED. It is the one existing self-test that already reads both the register and the constant.
  2. Dev deviation, check:doc-formula-expressions NOT MEASURED locally (exit 3, no build closure): ACCEPTED; Lint & Repo Gates success on the head covers it.
  3. Dev deviation, the first commit's model-named trailer refused by pre-push and amended before any publish: ACCEPTED as reporting; no published history was rewritten.
  4. Dev deviation, check:pm-dispatch-gates run under nohup with its exit captured by a wrapper: ACCEPTED; the same family is green on the head in CI.
  5. open_questions 1, the third chain row /scripts/pm/check-governed-queue-guard.mjs: ANSWERED, keep (A). The file holds GOVERNED_APPROVERS, the list every row mirrors, so an edit to it changes who counts as an approver. Cost named in ①B: both approvers are asked on every ready PR touching a lane file. Reversible in one line; the maintainer's approval on this PR is the ruling on it.
  6. open_questions 2, AGENTS.md Prime Directive 14's sentence "CODEOWNERS routes review requests for docs/adr/ only, so nothing summons the maintainer on the other Tier H surfaces": ESCALATED to the seat as a one-line follow-up Tier H PR on AGENTS.md (A). Folding it in would breach the claim's file surface; check:pm-governed-prose does not pin that sentence. The seat carries it so the stale sentence does not outlive this landing by long.
  7. open_questions 3, an unreadable file list after pr_create as a FAILED action: ANSWERED, keep (A). Absence must be loud; the pull is open, the row names what was not sent, the seat re-sends. A retried pr_create on the same head is a 422 from GitHub, so no duplicate pull results.
  8. New, low: the executor now performs a hand-written GET …/pulls/{n}/reviews, a path inside REFUSED_PATH_FAMILIES, which the validate pin covers only over the TABLE's spelled requests. A read cannot submit a review and the token's write surface is unchanged, but "refused by construction" now has one executor path outside the pin's reach. Follow-up: a send-time refusal of write verbs against REFUSED_PATH_FAMILIES inside rest(), or a pin that this read is GET-only.
  9. New, low: the mirror pin's Tier S direction is spelling-exact (①C).
  10. New, low: the round report no longer names the op's author-identity assign (①D); the run-summary row does.
  11. New, low: the reviews read is one page; the files walk restates pullTruncationReason's question (①A).
  12. Landing path: by the register this diff is Tier S (.claude/** is its only register row; .github/CODEOWNERS and scripts/pm/** are not rows, and the merges self-test keeps CODEOWNERS a near-miss), so this record is the review of record for the .claude/** face; the seat's Claim takes the stricter Tier H path on CODEOWNERS' own header, and the maintainer's approval is then the landing's record for the whole PR. Either way every check must be green first, and Test Core (1/6) was not yet at this reading.

Implemented-by: claude/issue-25046-tier-h-review-request
Reviewed-by: session_01M82feuetLzrCiBz41BJb7L

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:

  • .github/CODEOWNERS:六个 Tier H 受管面(docs/adr/、skills/、AGENTS.md、CLAUDE.md、docs/NORTH-STAR.md、.github/workflows/)和三条执法链文件(本文件、check-governed-merges.mjs、存放批准人名单常量的 check-governed-queue-guard.mjs),全部指给 @os-zhuang @hotlong。原来只有 docs/adr/ 一行指给 hotlong。
  • 中继(fleet-write):席位开 draft PR(pr_create)或转正式(pr_ready)之后,执行器读这张 PR 的完整文件清单,按受管注册表判级。命中 Tier H,就向批准人名单里还没被请审、没被指派、也没评审过的每个人发请审;PR 作者本人在名单里时改为指派给他(GitHub 不允许请审作者)。只改 Tier S 或非受管文件时不写任何东西。文件清单读不全时判为失败,不会悄悄跳过。
  • 自测:守卫自测里新加 12 条用例,读磁盘上的 CODEOWNERS,和注册表、批准人常量比对,对不上就红。不新增门禁、检查或巡检行。
  • 规则文本:SKILL.md 第 ③ 条和 rest-channel 改为指向这个 op,行数净 0。

为什么改:你说「申请我批准的应该推送给我,我看不到任何提示」,原因有两个:

现在请审由工具执行,不再靠席位记得去做。

风险与代价(含回滚):

  • 每次中继开 PR 或转正式,多 1 到 3 次读。只有命中 Tier H 才写,最多 2 次,走原有的写节流。
  • 文件清单读不到时这一步判失败(PR 已经开着,运行摘要会写明请审没发出),席位手补即可。这是刻意的「响亮失败」。
  • 以后改批准人名单,必须在同一个 PR 里改 CODEOWNERS,否则自测会红。
  • 复核读了仓库的分支规则集:main 没有开「必须由 Code Owner 审」,所以 CODEOWNERS 只负责叫人,不会拦合并。副作用是 Dependabot 改 .github/workflows/** 的 PR,今后也会叫到你们两位。
  • 回滚:revert 这一个提交。不涉及数据、workflow 或发布面。

席位意见:建议批准。

  • 达档契约复核(评论 6309771962)判 PASS。逐条确认:判级出自注册表这一个来源,混合 diff 只要有一条 Tier H 路径就算 Tier H,已请审的人不会重复写,作者兜底正确,失败是响亮的,token 权限没有放宽。
  • 按注册表,这张 PR 只算 Tier S(CODEOWNERS 不在注册表里)。但 CODEOWNERS 的文件头写明,改它就是治理变更。所以席位按更严的路径走,等你的批准。
  • 复核把一件事升级到了席位:AGENTS.md 第 14 条里「CODEOWNERS 只为 docs/adr/ 请审」这句话,本 PR 落地后就不对了。AGENTS.md 是 Tier H,席位另立一张一行修正卡,等本 PR 合并后再派。

你要做的:在 PR #25064 上点 Approve,席位随后落地。

席位核对

ACCEPT · domain:skills seat 1 (seat post #7623) · session_01M82feuetLzrCiBz41BJb7L · head 91669b139bd4fbba7459e7cedd57a20a9e676d56 · 2026-10-21T06:41Z

  1. Form. The PR is a draft against main, and the body's first line is Fixes #25046. Assignee huangyiirene equals the card's. Files, read with the PR's file list: .github/CODEOWNERS, scripts/pm/fleet-write/{ops,execute,validate}.mjs, scripts/pm/check-governed-queue-guard.mjs, .claude/skills/pm-dispatch/SKILL.md and .claude/skills/pm-dispatch/references/rest-channel.md (+525/−44). check-governed-merges.mjs --pr 25064: Tier S (.claude/** ×2), 569 changed lines, under the 3000-line ceiling. skip-changeset is right: nothing published.
  2. Premises measured. The seat read the rules-text and ops.mjs / validate.mjs hunks itself. The review of record read the executor.
    • SKILL.md ③ and the rest-channel line are rewritten in place at net 0 (check:pm-skill-ratchet 319/319 and 82/82). The author-identity fallback survives as an op step.
    • GOVERNED_REVIEW_OPS is derived from the table's governedReview flag, and validate.mjs pins it to exactly pr_create and pr_ready. Their two conditional writes are also inside the refused-family pin.
    • codeowners/errors on the branch reads empty (the dev, and the review at this head).
  3. Gate check-runs, pinned to this head (2026-10-21T06:40Z): 45 runs, 32 success, 13 skipped, 0 failures, 0 in progress. Test Core (all six shards), Lint & Repo Gates, TypeScript Type Check and Governed Surface Queue Guard are success, as are the three claim guards. check-expected-skips.mjs --pr 25064, from a checkout pinned to origin/main 688052e14: OK — 13 skipped check-run(s), every one in main's roster (exit 0). The contract review of record is comment 6309771962 (Served-tier: CONTRACT_REVIEW_TIER, this head, Local-runs: none, PASS), rendered by an isolated subagent at that tier and adopted by this seat. Its one NOT MEASURED item, Test Core (1/6) in progress at its read, has since concluded success.
  4. Accepted deviations and the dev's open questions.
    • The op lives in the relay executor (candidate b): only it holds the pull number at the create's answer and the board's own register.
    • The mirror pin sits in check-governed-queue-guard.mjs --self-test, an existing self-test that owns GOVERNED_APPROVERS. That file is outside the claim's literal surface, but the dispatch allowed a pin only inside an existing self-test.
    • Open question 1, the third chain row: keep. That file holds the approver list the mirror pins.
    • Open question 3, an unreadable file list is a FAILED action: keep. Silence is the defect this card closes.
    • Open question 2, AGENTS.md Prime Directive 14's sentence "CODEOWNERS routes review requests for docs/adr/ only" turns false on landing. It is filed as a one-line Tier H follow-up card with Blocked-by: #25046, so it is not folded in here.
    • The first commit's model trailer was amended before any push; no published history was rewritten.
  5. Carried, not filed (the review's low items; each goes to the seat post's carried pointers for its file's next edit):
    • The executor's hand-written GET …/pulls/{n}/reviews sits in a refused path family that the validate pin covers only over spelled requests. It is a read, with no write-surface change (review 19).
    • The mirror's Tier S direction matches /.claude/ by exact spelling (review 14).
    • The round-report trail for the op's own assignee fallback now lives in the run-summary row (review 15).
  6. Landing. The stricter path declared on the Claim applies. This PR stays draft, review is requested from both authorised approvers, and the card moves to needs-user-decision. On an authorised APPROVED review the seat clears the label, flips the PR ready, arms auto-merge once mergeable_state settles, follows it to MERGED and closes out pm-dispatch: a Tier H PR never reaches os-zhuang — CODEOWNERS routes docs/adr/ to @hotlong only, and SKILL.md ③ "request review from both approvers" is prose no op performs (3 of 4 open Tier H PRs requested hotlong alone) #25046.

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 21, 2026 08:36
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 21, 2026
Merged via the queue into main with commit 4feccc2 Oct 21, 2026
46 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-25046-tier-h-review-request branch October 21, 2026 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants