Repository navigation
Commit 8a608d8
docs(pm-dispatch): review seats take contract reviews first, the lane next, never triage (#29967)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBUyArWn6eHL33NNgvdpkwiW)_
Fixes #29242
## 维护者速读
- **改了什么:** 新增复核席章程 `lanes/review.md`(21 行):`review#N` 席位坐在能达
`CONTRACT_REVIEW_TIER` 的其他账号上,专职契约复审;车道的 `Request: contract-review`
门铃改敲复核席信箱,无复核席在座时敲车道兄弟席;互斥沿用既有 `Contract-review-claim:` 行,同 head
先贴者持有;复核席不复核本账号派发或实现的 PR,不 merge、不
ready、不派发、不定级。`contract-review.md`(60/60)、`project-forms.md`(59/59)、`lanes/triage.md`(20/20)、`SKILL.md`
车道表(319/319)各在上限内改词指向它。脚本侧:`review`
成为可签名、可开信箱的席位词;`check-owed-reviews.mjs` 先点名复核席(坐在本 PR
派发或实现账号上的复核席回避,不点名),车道只在无复核席可接或多等一轮无认领后接;`fleet-roster.mjs` 把复核席列成行,在办 =
其活认领持有的 PR。
- **为什么改:** 您的原话「契约复审我想分拆到其他账户,怎么处理。」与裁定「选
B,立卡」。此前所有契约复审都在分诊一个账号一个会话上跑,写预算和模型额度互相挤占。
- **风险与回滚:** 文本三处零增行、不抬上限,新文件一行新棘轮行(21);脚本改动全是纯函数加自测,不加门禁、不加标签、不加信箱键。回滚
= revert 本 PR;已坐席的复核席只需撤座位贴。
- **先看:** ① `lanes/review.md` 全文;② `contract-review.md` 第 5、23
行(复核席先接,车道次之,⛔ 非分诊);③ `check-owed-reviews.mjs` 的 `takerOf` /
`reviewSeatsOf`;④ `fleet-roster.mjs` 的 `reviewClaims`。
- **两分钟验证:** `node scripts/pm/check-owed-reviews.mjs --self-test`(预期 `84
cases pass in 10 batteries`)、`node scripts/pm/fleet-roster.mjs --fixture
scripts/pm/fleet-roster.fixture.json --now 2026-10-23T14:00:00Z`(表中出现
`review` 行,在办 `objectstack#25433`)、`pnpm check:pm-skill-ratchet`。
- **需要你做的:** 一处与卡面不同,请知悉(见下 Deviations):回退席不是分诊而是车道兄弟席,依据您 11-03
的话「目前各车道都有fable,应该不需要你契约复审」。无其他动作;席位落地。
Clause-②: no
Metadata-protocol: none
## What changes
**New `references/lanes/review.md` (21 lines, new ratchet row at 21,
every line ≤ 120 bytes).** The card's points 1–6 only, pointing at
`contract-review.md` for the review itself: the seat (`review#N`,
`review#1` first, one seat post each, any account that reaches
`CONTRACT_REVIEW_TIER`; session effort `high`, review subagents at the
review tier, a declaration-face-only review at `TIER_DEFAULT`; one
`pm:mailbox` PR per seat through `mailbox-open.mjs --seat review#N`);
the routing (lanes ring `Request: contract-review` at any review seat's
mailbox, a free one takes it; with none seated the bell goes to a
sibling lane seat, ⛔ never triage; mutual exclusion is the existing
`Contract-review-claim:` line, the earlier claim on a head holds,
between review seats too, the later withdraws and drops its output;
`check-owed-reviews.mjs` names a review seat first and the lane only
when none is seated or a further round after (b) passed with no claim;
the delta re-review stays with the original reviewer, and a review seat
off duty with a stale record hands the delta to the next review seat,
named in the hand-off line); independence and bounds (⛔ never a PR its
own account dispatched or implemented, records keep
`Implemented-by:`/`Reviewed-by:`; ⛔ no merge, ready, dispatch, grading
or human-floor action; security findings per `security-findings.md`).
**`references/contract-review.md` (60/60, net 0 lines, no raise).** The
〈复核归属与资格〉 heading now points at `lanes/review.md`; line 23 folds the
routing into the existing owed-review sentence (「复核席先接,车道次之,⛔ 非分诊」, 119
bytes); the seat word is `复核席` throughout (the older `复审席` spelling
retired on four lines). The claim template line and the 2-hour lapse are
untouched (pinned by `record-recognisers.mjs`).
**`references/project-forms.md` (59/59, net 0 lines).** The seat-word
line names `复核 = review.md` beside 总监 and 分诊 (115 bytes); the `Request:
contract-review` row's taker reads `复核席`.
**`references/lanes/triage.md` (20/20, net 0 lines).** Edited only after
#29940 merged and `origin/main` was merged into this branch (the serial
constraint): the 不推 line now reads 「契约复核(归复核席,`review.md`)」 in place of
「PASS/FAIL 归车道」 (117 bytes), so the triage charter points at the review
seat and never calls itself the fallback.
**`scripts/pm/check-skill-line-ratchet.mjs`.** One added `CEILINGS` row
for the new lane file at its landed count (21, headroom 0) with the
card's verbatim ruling quoted in the row comment, and its
`MAX_TABLE_ROW_BYTES` pin (0). No existing row moves.
**`.claude/skills/pm-dispatch/SKILL.md` (319/319, net 0 lines; patch
round 1, S1).** The lane table's `security` row now reads `security` ·
`review` and ends 「复核席 `review#N` 见 `references/lanes/review.md`」 (333
bytes, within the 341-byte row pin), so the charter has an entry point
from the skill itself; the skills token ratchet stays within its ceiling
on the merged head.
**`scripts/pm/mailbox-open.mjs` and
`scripts/pm/check-half-states.mjs`.** `review` joins `SEAT_BARE_LANES`
and the signature alternation `SIGNATURE_LANE_SOURCE` (the two lists
`fleet-roster.mjs` pins equal), so `review#N` can sign a comment, open a
mailbox (`mailbox/LOGIN/review-N`) and post a claim; `domain:review`
stays refused.
**`scripts/pm/record-recognisers.mjs`.** `contractReviewClaims` reads a
claim's side off its signature: `triage#N` is the legacy `triage` side
(still yields), `review#N` is the `review` side, everything else is
`lane`. Between review and lane there is no precedence: the earliest
live claim holds, which is what keeps two review seats off one head.
**`scripts/pm/check-owed-reviews.mjs`.** `reviewSeatsOf(seats)` reads
the roster for seated review seats; the row carries `reviewSeat` (seated
/ stood-down / absent / unknown) and `reviewSeats`; `takerOf` names `a
review seat (review#1, …) first` while one is seated, adds `or the LANE
seat (a further round waited, no claim)` after `LANE_JOINS_AFTER_ROUNDS`
(2) × `TRIAGE_ROUND_MINUTES`, and names `the LANE seat (no review seat
seated)` while none is. A hand-off still names its seat whatever the
roster says. Patch round 1 (S2): `ownAccountsOf(pull)` reads the PR's
own accounts — the implementing account from the body's
`claude-projects-attribution` login and the dispatching account from the
signer of each `Claim:` comment on the delivered card — and
`reviewSeatsOf` bars a review seat seated on either (`lanes/review.md`
独立性); a barred seat is never named, and when every seated review seat is
barred the state is `barred` and the lane's own seat takes at once, no
further round. The printed row shows `review seat STATE SEATS (barred
SEATS)`. Patch round 2 (S4): when the delivered card's comments could
not be read, `ownAccountsOf` returns null and the state is `unjudged` —
the seated review seats are named only with the caveat 「unless on this
PR's own account (card comments unread)」, the lane's own seat beside
them, so no seat the rule may bar is printed as free.
**`scripts/pm/fleet-roster.mjs` and its fixture.** A `review` seat post
is a row like the lanes; its in-flight is `reviewClaims`: the open pulls
whose CURRENT head a live `Contract-review-claim:` signed `review#N`
holds, read off the signed-comment read the roster already makes (the
claim lapses in 2 h, the read starts `STALE_HOURS` = 6 h back). Review
rows never share (`= seat N`) and hold no card. The fixture gains a
review seat post, its mailbox pull, head shas on two pulls and three
review-signed claims (one live, one expired, one on a moved head); the
rendered roster shows `| objectstack | review | 1 | 🟢 os-zhuang |
objectstack#25433 | … |`.
**Not added:** no gate, no label, no mailbox key (the existing `Request:
contract-review` key carries the routing), no ceiling raise, no
changeset (`.claude/**` and `scripts/pm/**` publish nothing).
## Deviations from the card, declared
The card's rule 2 and 3 and its acceptance line say triage is the
fallback reviewer (「With no review seat seated, the bell goes to triage,
as today」, 「falls back to triage」), and `lanes/triage.md` was to read
「triage becomes the fallback reviewer」. This PR builds the fallback as
the lane's own seat (a sibling lane seat), never triage, for two
reasons: (1) `origin/main` already says so — `contract-review.md`
「车道或复审席接,⛔ 非分诊」, `triage-duties.md` 「⛔ 分诊不再审」, and
`check-owed-reviews.mjs`' taker never named triage at the branch point —
so building triage back in would reverse a landed rule; (2) the
maintainer's later word on 2026-11-03, verbatim:
「目前各车道都有fable,应该不需要你契约复审」. The self-test covers the no-review-seat case
as 「the lane's own seat takes」 and pins that the row never names triage.
Agreed with the skills seat before building.
## Tests
- `node scripts/pm/check-owed-reviews.mjs --self-test` — 84 cases in 10
batteries, new battery `review-seat first …` (16): seated review seat
first, the lane joins after a further round, no review seat ⇒ the lane's
own seat (the card's acceptance case, never triage), stood-down ≠
seated, lane gone, roster unread, hand-off, a review seat's live claim ⇒
HELD, the printed row; S2: own accounts read from the attribution line
and the card claim's signer, the only review seat on the implementing
account ⇒ barred and the lane takes at once, a review seat on the
dispatching account barred while a third-account seat still takes first,
the lane gone with the only seat barred, a seat with no account never
barred; S4: an unread card thread ⇒ `unjudged` with the caveat, a
readable empty thread or no card still judges the bar.
- `node scripts/pm/fleet-roster.mjs --self-test` — 110 cases in 9
batteries: the review row (lane, seat, account, in-flight = the live
claim's pull, mailbox, last activity), only the live claim on the
current head counts of three, a lane-signed or unsigned claim is
nothing, the lane-word pin against `SEAT_BARE_LANES`, the rendered row.
- `node scripts/pm/record-recognisers.mjs --self-test` — 366 cases:
review side holds when first, lane first ⇒ lane holds, two review seats
⇒ earliest holds.
- `node scripts/pm/mailbox-open.mjs --self-test` — 83 cases in 10
batteries (`review#2` opens `review-2`; `domain:review#1` refused); S3
fixed the 「The same three / four words」 comment above `SEAT_LANE_KINDS`.
- `node scripts/pm/check-half-states.mjs --self-test` — 5222 cases on
the merged head (the `review` alternation beside main's H74).
- `pnpm check:pm-skill-ratchet` — review.md 21/21, contract-review.md
60/60, project-forms.md 59/59, triage.md 20/20, SKILL.md 319/319 (widest
row 341, pin 341), widest prose line ≤ 120 bytes; `node
scripts/check-skills-token-ratchet.mjs` — exit 0 (that ratchet walks
`skills/` only, so `.claude/skills/pm-dispatch/SKILL.md` is outside it;
the binding gates on SKILL.md are the line ratchet and the row pin);
`pnpm check:pm-skill-id-lint` clean; `npx eslint` on the edited scripts
exit 0.
## Acceptance notes
- `lanes/review.md` exists with points 1–6; `contract-review.md`
(heading and line 23), `project-forms.md` (seat-word line) and
`lanes/triage.md` (the 不推 line) point at it.
- `check-owed-reviews.mjs` prints the review-seat-first routing; its
self-test covers no review seat seated (⇒ the lane's own seat, see
Deviations).
- `fleet-roster.mjs` lists review seats, in-flight = claimed heads.
- Line budget: contract-review.md 60/60 · project-forms.md 59/59 ·
lanes/triage.md 20/20 · lanes/review.md 21/21 (new row) · SKILL.md
319/319. No raise.
- `Writes:` `git push` ×4 (WIP head 618cdf4, review head 9da0cd8,
patch round 1 head 6e66dd4, patch round 2 head f850ac1), relay
`pr_create` ×1 (draft, `labels: ["skip-changeset"]`, no assignees),
`post-stamped.mjs --comment` ×1 for the `os-dev-report`. `mcp_calls`:
GitHub MCP reads only (`issue_read`, `pull_request_read`); no MCP write.
## Verification
Final head `f850ac1b` (the change, the merge of `origin/main` at
`82f60ebb` which carried #29940, the triage.md commit, a second merge of
`origin/main` at `3b8c724c`, patch round 1 — contract review r0's three
should-fixes — and patch round 2 — r1's S4; both merges were
conflict-free). On the review head `9da0cd8c`, derived with `node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on that head (11 paths), 55 commands run
serially in the worktree with each exit code captured before any pipe,
plus the five self-tests and the eslint run above, then reconciled: `✓
dispatch-gates --ran: 55 derived famil(ies) accounted for — 54 run, 1
NOT-MEASURED (1 DERIVED from a recorded exit 3)`.
- Patch round 2 (check-owed-reviews.mjs only) re-ran its self-test (84
cases), fleet-roster's (110) and `npx eslint` on `f850ac1b` — exit 0.
Patch round 1 (SKILL.md, check-owed-reviews.mjs, mailbox-open.mjs)
re-ran the gates that diff can move on `6e66dd44`: `pnpm
check:pm-skill-ratchet`, `node scripts/check-skills-token-ratchet.mjs`,
`pnpm check:pm-skill-id-lint`, `pnpm check:pm-half-states`, the
self-tests of check-owed-reviews (82), fleet-roster (110),
record-recognisers (366), mailbox-open (83) and `npx eslint` on the two
scripts — all exit 0.
- 54 of 55 derived commands exit 0 on `9da0cd8c`, among them `pnpm
check:pm-skill-ratchet`, `pnpm check:pm-skill-id-lint` (110 files
clean), `pnpm check:pm-half-states` (5222 cases), `pnpm
check:pm-record-recognisers`, `pnpm check:pm-dispatch-gates`, `pnpm
check:pm-governed-merges`, `pnpm check:pm-post-stamped`, `pnpm
check:skill-frame-sync`, `pnpm check:single-claim-paths`, `pnpm
check:scripts-symbol-anchors`, `pnpm check:ratchet-remedy-authority`,
`node scripts/check-self-test-wired.mjs`, `node
scripts/check-release-section-coverage.mjs --strict`.
- `pnpm --filter @objectstack/lint run check:doc-formula-expressions ::
exit 3` — PREREQUISITE NOT MET (the lint package's built output is
absent in a fresh worktree; `--ran` classifies the recorded exit 3 as
NOT MEASURED). CI builds it; read the PR's `Lint & Repo Gates` check-run
for the verdict.
- Not run locally, by the derivation's own account: the artifact-roster,
wide-population and pending-changeset families, the Type Check lanes and
the test-core shards — CI's runs on this PR are the readings for those.
No package test or typecheck is owed: the diff touches no package.
Repeat the two-minute check: `node scripts/pm/check-owed-reviews.mjs
--self-test` → `✓ check-owed-reviews self-test: 84 cases pass in 10
batteries`; `node scripts/pm/fleet-roster.mjs --fixture
scripts/pm/fleet-roster.fixture.json --now 2026-10-23T14:00:00Z` → a
`review` row with `objectstack#25433` in flight.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzhXV3ABCbTkU1iRRn7
<!-- signed: domain:skills#3 · os-support-ai ·
session_01WGufzhd2Ad9ANGTyAMW7pH -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 04cbdf7 commit 8a608d8
12 files changed
Lines changed: 282 additions & 66 deletions
File tree
- .claude/skills/pm-dispatch
- references
- lanes
- scripts/pm
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
165 | | - | |
| 165 | + | |
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
| 5 | + | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | | - | |
26 | | - | |
| 25 | + | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | | - | |
| 40 | + | |
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1255 | 1255 | | |
1256 | 1256 | | |
1257 | 1257 | | |
1258 | | - | |
| 1258 | + | |
1259 | 1259 | | |
1260 | 1260 | | |
1261 | 1261 | | |
| |||
1279 | 1279 | | |
1280 | 1280 | | |
1281 | 1281 | | |
1282 | | - | |
| 1282 | + | |
1283 | 1283 | | |
1284 | 1284 | | |
1285 | 1285 | | |
| |||
0 commit comments