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
{{ message }}
Repository navigation
Commit 4feccc2
Browse filesBrowse the repository at this point in the historyBrowse files
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)
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 c660b17 -> 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 (c660b17) and git diff HEAD is
empty`.
- **Ablation B**: anchor `/docs/adr/ @os-zhuang @hotlong` → `/docs/adr/
@hotlong` in `.github/CODEOWNERS`; `blob 15d3fff -> 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
(15d3fff) 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](https://claude.ai/code/session_01M82feuetLzrCiBz41BJb7L)_
Co-authored-by: Claude <noreply@anthropic.com>
problems.push(`Tier H row \`${surface.id}\` (${surface.glob}) has no CODEOWNERS line at \`${pattern}\` — a pull touching it summons nobody when opened ready`);
687
+
continue;
688
+
}
689
+
if(!sameOwners(row.owners))problems.push(`CODEOWNERS line ${row.line} \`${pattern}\` names ${row.owners.join(' ')||'nobody'}, not every GOVERNED_APPROVERS account (${wanted.join(' ')}) — a Tier H pull then waits on an approver who is never asked`);
if(tierS)problems.push(`CODEOWNERS line ${row.line} routes \`${row.pattern}\`, a Tier S row (\`${tierS.id}\`) that lands on a seat's review of record — a code-owner request there asks the maintainer for a landing that is not his`);
695
+
elseif(!sameOwners(row.owners))problems.push(`CODEOWNERS line ${row.line} \`${row.pattern}\` (an enforcement-chain row) names ${row.owners.join(' ')||'nobody'}, not every GOVERNED_APPROVERS account (${wanted.join(' ')})`);
696
+
}
697
+
if(!/GOVERNED_APPROVERS/.test(String(text??'')))problems.push('the file never names `GOVERNED_APPROVERS` — its header must say which constant the owner list mirrors');
0 commit comments