diff --git a/.claude/skills/pm-dispatch/references/contract-review.md b/.claude/skills/pm-dispatch/references/contract-review.md index 7ac8ed545ae..bbb84a52067 100644 --- a/.claude/skills/pm-dispatch/references/contract-review.md +++ b/.claude/skills/pm-dispatch/references/contract-review.md @@ -18,14 +18,14 @@ - 已发布面以包的 `exports` 映射为准:从入口类型图不可达、且 `exports` 不可寻址的 `.d.ts` 是 出货字节,⛔ 不是已发布接受集,改它不触发 `Clause-②: yes`;legacy node10 深引用在契约之外。 - 四面皆不碰 ⇒ CI 加席位自读 ⛔ 不起第二个 agent;谁跑 = 派发席,达档者席内审。 -- 派发席达档调用被配额拒(HTTP 429)⇒ ⛔ 不降档审;PR 落交接行、留等复核标记,交分诊席。 -- `Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (, ) · head ` -- 分诊只接无人在审的欠审 PR(draft 亦算):现 head 无记录、无活认领,且 (a)(b)(c) 之一成立。 -- (a) 交接行点名现 head 或 PR 带等复核标记,同轮接;(b) 现 head 已等满一轮分诊(约 60 分钟); -- (c) 车道席已收班或不在总表;欠审清单 = `node scripts/pm/check-owed-reviews.mjs`,只读。 -- 车道或分诊起审前读 PR:现 head 有记录或活认领 ⇒ ⛔ 不起;否则先落认领行,一 head 一行: +- 派发席席内达档审;被配额拒(HTTP 429)⇒ ⛔ 不降档,留标记落交接行,点复审席,无则兄弟席: +- `Contract-review-handoff: #N> — CONTRACT_REVIEW_TIER unavailable (, ) · head ` +- 欠审 PR(含 draft)现 head 无记录、无活认领且 (a)(b)(c) 之一 ⇒ 车道或复审席接,⛔ 非分诊。 +- (a) 交接行点名现 head 或带等复核标记,同轮由所点席接;(b) 现 head 已等满一轮(约 60 分钟); +- (c) 车道收班或离总表:欠审入交班简报待下席或复审席;清单 `scripts/pm/check-owed-reviews.mjs`。 +- 车道或复审席起审前读 PR:现 head 有记录或活认领 ⇒ ⛔ 不起;否则先落认领行,一 head 一行: - `Contract-review-claim: · head · ` -- 认领随 head 移动或贴后 2 小时无记录失效;同 head 双认 ⇒ 车道胜,分诊回帖撤回、弃产出。 +- 认领随 head 移动或贴后 2 小时无记录失效;双认 ⇒ 先贴者持有,后者回帖撤回、弃产出。 - 车道暂缓本 head 送审落 `Contract-review-hold: · head · · until ` - 新 head 即清;点名现 head 时 (b) 不起;条件满足由持有车道挂等复核标记或落交接行。 - 逾 24 小时条件未满的 hold 列入分诊轮次记录(`check-owed-reviews.mjs` 标出)。 @@ -50,7 +50,7 @@ 而已挂 auto-merge ⇒ 派发席读到即照 AGENTS.md disarm 句撤挂,FAIL 记录点名欠撤挂;重挂照 ①。 - ② PR check 全绿,⛔ 非 required 子集;例外:merge-base 同签名的红不计、按设计而红见 SKILL.md。 - 签名 = 失败步 + 首错行,读 base check runs 的 API ⛔ 不凭口述;主干红止血立单不变。 -- 落地时仍欠复核的 PR 在落地 head 复审,车道席或分诊按 (a)(b)(c) 接;FAIL ⛔ 不修已关 PR。 +- 落地时仍欠复核的 PR 在落地 head 复审,车道席或复审席按 (a)(b)(c) 接;FAIL ⛔ 不修已关 PR。 - 每条 must-fix 引该记录立新卡、按发现定级;回退仅限主干红(p0,main-red 约定①)或安全边界。 ## 等复核标记 `needs:contract-review` diff --git a/.claude/skills/pm-dispatch/references/project-forms.md b/.claude/skills/pm-dispatch/references/project-forms.md index ba7898af012..a0d43d00958 100644 --- a/.claude/skills/pm-dispatch/references/project-forms.md +++ b/.claude/skills/pm-dispatch/references/project-forms.md @@ -37,10 +37,10 @@ - 正文首行 `Mailbox-of: <席位> · `;坐席即开(`mailbox-open.mjs`)并订阅,存根记 `Mailbox:`。 - 永不合并,不欠 CI,落地与台账查询排除它;移交或换账号关旧开新;评论首行 `To: <席位>`。 - 次行恒为一键,下列事件必敲;一事一行 ≤200 字节,只写键与编号;表外即半态。 -- 车道→分诊:复审交接行同笔 `Request: contract-review`;问卡去留、路由或定级 `Request: triage`。 +- 车道→复审席或兄弟席同交接行 `Request: contract-review`;反向随记录 `Notify: contract-review`。 - 任一席→分诊:p0 嫌疑、main 红、确定性队列红 `Request: urgent-triage`。 - 立锚卡者→分诊(缺则总监)`Request: main-red`,→破坏 PR 的席与锚卡车道 `Notify: main-red`。 -- 分诊→车道:未订阅者随记录 `Notify: contract-review`;p0/p1 发版阻塞定入 `Notify: release-blocker`。 +- 车道→分诊问卡去留路由定级 `Request: triage`;反向 p0/p1 发版阻塞定入 `Notify: release-blocker`。 - 移卡入队者→车道:p0/p1 `Blocked-by` 关闭 `Notify: unblocked`;记裁决席→被改席 `Notify: ruling`。 - 席→兄弟席或他车道:认领文件面相交或须先落 `Notify: serial`,先落者落地 `Notify: landed`。 - 上下线→兄弟席与分诊 `Notify: seat-online`、`Notify: seat-offline`;清单变→兄弟席 `Notify: split`。 diff --git a/.claude/skills/pm-dispatch/references/triage-duties.md b/.claude/skills/pm-dispatch/references/triage-duties.md index 0ab79fd080f..ce64b8ddddc 100644 --- a/.claude/skills/pm-dispatch/references/triage-duties.md +++ b/.claude/skills/pm-dispatch/references/triage-duties.md @@ -1,17 +1,14 @@ # 分诊座位职责 -- 本文与技能他处点名分诊之条(复审、摘 `pm:retriage`、约定③ 等)即分诊座位的全部职责。 +- 本文与他处点名分诊之条(定级复审、摘 `pm:retriage`、约定③ 等)即分诊座位的全部职责。 - fire 开局只按名加载互斥检查所需工具,`ToolSearch` 用 `select:`;判定本轮有活才加载其余。 - ⛔ 分诊 fresh session 开局不做泛关键词 ToolSearch;可验判据:空转轮 ~4 万 token 以内。 - 两级盘点:小时轮以 `since` 窗口读增量,锚 = 座位贴上一份收班简报的时间戳。 - 查重缓存住席内:新会话全量拉,其后 `since` 增量;⛔ 不建分支、不共享、不依赖备份。 - 每日一 fire 跑两仓全量对账并归集日频职责;选层按 fire 时刻 ⛔ 不用计数器,简报写明层。 - 每条枚举比对返回数与 `totalCount`,不等 ⇒ 报 `sweep INCOMPLETE` 点名缺口,⛔ 永不报干净。 -- 复审每轮先跑 `check-owed-reviews.mjs`,照 contract-review.md 接;配额拒 ⇒ PR 留标,⛔ 不降档审。 -- 每轮比对自审记录 `Head-sha` 与现 head,越过即过期,纯重生成/只动散文面例外(判已提交树)。 -- 自审 FAIL 过期 ⇒ 同轮达档重审新 head,逐条复查 must-fix,delta commit 当改动审。 -- 自审 PASS 过期 ⇒ PR 记一条 PASS 不再管辖、欠 delta 复审,归本席或新交接行。 -- 轮次记录逐张自审 PR 写 `认领者 · 记录 head→现 head→状态`(现行/过期已重审/过期已记)。 +- 轮次记录按车道计欠审逾一轮的 head 数并点名车道(`check-owed-reviews.mjs`),只报 ⛔ 不接审。 +- 分诊旧记录过期:delta 复审归车道或复审席,照 contract-review.md;⛔ 分诊不再审。 - Backlog sweep 是常设职责,每 fire 扫任一析取命中的卡。 - 析取 ①:全裸(无 `pm:*`、无 `needs-user-decision`、无 `domain:*`)。 - 析取 ②:有 `pm:queue` 无 `domain:*`(仅多车道仓);析取 ③:有 `domain:*` 无 pm-state。 diff --git a/scripts/pm/check-owed-reviews.mjs b/scripts/pm/check-owed-reviews.mjs index c157b55ea8a..4199f5f06d7 100644 --- a/scripts/pm/check-owed-reviews.mjs +++ b/scripts/pm/check-owed-reviews.mjs @@ -12,9 +12,10 @@ * ## Why it is a file in the repository * * The rule text (the pm-dispatch skill's `references/contract-review.md`, - * 〈复核归属与资格〉) says the lane reviews its own pull requests and the triage - * seat takes a contract review only when nobody is doing it; the triage duties - * run this list first in their review step. Before it existed the list was a + * 〈复核归属与资格〉) says the dispatching seat reviews its own pull requests in + * its own seat, and a hand-off or an owed head goes to a review seat or a + * sibling seat of the lane — ⛔ never to triage, which takes no contract review + * and only counts the owed heads per lane in its round record. Before it existed the list was a * session-local scratch script: lost when a container was replaced, rebuilt * without drafts and without the hand-off lines, and two Tier H drafts waited * about five rounds with a hand-off naming their head. A list every seat can @@ -37,10 +38,11 @@ * names an earlier head: the delta re-review stays with that * reviewer), `none`, or `unjudged` (the read could not be made). * hand-off the newest `Contract-review-handoff:` line, as `contractReviewHandoff` - * reads it: `triage` (it names the current head), `stale`, `absent`, - * `unreadable`. + * reads it: `handed` (it names the current head, and the seat it hands + * to: `review#N` or `domain:#N`), `stale`, `absent`, `unreadable` + * (a line still naming `triage` is unreadable: it hands to nobody). * claim the `Contract-review-claim:` lines: `live` (who holds the head, - * the lane winning a tie), `lapsed`, `none`. + * the earliest live claim), `lapsed`, `none`. * hold the `Contract-review-hold:` lines, as `contractReviewHold` reads * them, per head: `live` (a readable hold names the current head: * trigger (b) does not fire, and its age is printed), `stale` (the @@ -60,10 +62,13 @@ * own wait, and a `HOLD` beside `HELD` is one letter from a misread. * trigger the first that holds: (a) a hand-off line naming the current * head, or the waiting label — taken the same round; (b) the head - * has waited a full triage round (`TRIAGE_ROUND_MINUTES`) and no + * has waited a full round (`TRIAGE_ROUND_MINUTES`) and no * live hold names it; (c) the lane's seat has stood down or is * absent from the seat roster. A live hold holds (b) alone: the * lane that holds ends it with (a), and a lane gone is (c). + * taker on a TAKE row, who takes it: the seat the hand-off names; else + * the lane seat or a review seat; with the lane seat gone (c), a + * review seat or the lane's next seat. ⛔ Never triage. * * ## Report-only — the exit is 0 on every completed or incomplete read * @@ -101,7 +106,7 @@ export const EXIT_USAGE = 2; export const DEFAULT_REPO = 'objectstack-ai/objectstack'; /** The waiting label the dispatching seat hangs at ACCEPT (`references/contract-review.md` 〈等复核标记〉). */ export const WAITING_LABEL = 'needs:contract-review'; -/** One full triage round — the rule's 「一轮分诊(约 60 分钟)」, trigger (b). */ +/** One full round — the rule's 「一轮(约 60 分钟)」, trigger (b). The name is kept: importers read it. */ export const TRIAGE_ROUND_MINUTES = 60; /** The seat posts' label (`seat-post-protocol.md`); `parseSeat` reads each one. */ export const SEAT_LABEL = 'pm:seat'; @@ -249,7 +254,7 @@ export function owedReviewRow(pull, { now, seats, repo = DEFAULT_REPO }) { const hold = contractReviewHold(pull.comments, head, { now }); const lane = laneSeatOf(pull.cardLabels, seats); const triggers = []; - if (handoff.state === 'triage') triggers.push('(a) hand-off'); + if (handoff.state === 'handed') triggers.push('(a) hand-off'); if (owed.limbs.includes('label')) triggers.push('(a) label'); // (b) alone yields to a live hold: the lane said "not this head yet", and only (a) or (c) overrides that. const roundWaited = waited !== null && waited >= TRIAGE_ROUND_MINUTES ? `(b) waited ${waited} min` : null; @@ -269,7 +274,7 @@ export function owedReviewRow(pull, { now, seats, repo = DEFAULT_REPO }) { head, waited, record, - handoff: { state: handoff.state, id: handoff.id ?? null }, + handoff: { state: handoff.state, to: handoff.state === 'handed' ? handoff.to : null, id: handoff.id ?? null }, claim: { state: claim.state, holder: claim.holder ?? null, tie: claim.tie ?? false, unreadable: claim.unreadable ?? [] }, hold: { state: hold.state, @@ -289,10 +294,24 @@ export function owedReviewRow(pull, { now, seats, repo = DEFAULT_REPO }) { laneSeat: lane.seat, status, trigger: status === 'TAKE' || status === 'WAIT' ? (triggers[0] ?? 'none yet') : null, + taker: takerOf(status, handoff, lane), gaps: Array.isArray(pull.gaps) ? pull.gaps : [], }; } +/** + * Who takes a TAKE row (null on every other status). Pure. The rule text: the seat a hand-off on this + * head names; otherwise the lane's seat or a review seat; a lane whose seat is gone waits for a review + * seat or its next seat. ⛔ Never triage — it takes no contract review. + */ +export function takerOf(status, handoff, lane) { + if (status !== 'TAKE') return null; + if (handoff?.state === 'handed' && typeof handoff.to === 'string') return handoff.to; + const seat = lane?.lane ? `the ${lane.lane} seat` : 'the lane seat'; + if (lane?.seat === 'stood-down' || lane?.seat === 'absent') return `a review seat, or ${seat} next on duty`; + return `${seat} or a review seat`; +} + const STATUS_ORDER = Object.freeze(['TAKE', 'WAIT', 'HELD', 'DONE']); /** Every owing row, TAKE first, then by PR number. Pure. */ @@ -313,18 +332,18 @@ export function renderRow(row) { const holder = row.claim.holder; const claim = holder === null ? row.claim.state - : `live ${holder.session} (${holder.side}${row.claim.tie ? ', lane wins the tie' : ''}, ${holder.id}, ${holder.time})`; + : `live ${holder.session} (${holder.side}${row.claim.tie ? ', a triage claim yields' : ''}, ${holder.id}, ${holder.time})`; const parts = [ `${row.status} #${row.pr} ${row.draft ? 'draft' : 'ready'}`, `face ${row.face.join('+')}`, `head ${short(row.head)} (${row.waited === null ? '? min' : `${row.waited} min`})`, `record ${record}`, - `hand-off ${row.handoff.state}${row.handoff.id === null ? '' : ` (${row.handoff.id})`}`, + `hand-off ${row.handoff.state}${row.handoff.to ? ` ${row.handoff.to}` : ''}${row.handoff.id === null ? '' : ` (${row.handoff.id})`}`, `claim ${claim}`, `hold ${holdText(row.hold)}`, `lane ${row.lane ?? '?'} ${row.laneSeat}`, ]; - const tail = row.trigger === null ? '' : ` → ${row.trigger}`; + const tail = row.trigger === null ? '' : ` → ${row.trigger}${row.taker ? ` · take: ${row.taker}` : ''}`; const listed = row.hold.listed ? ` ⏳ HOLD ${holdAge(row.hold)} — list it in the triage round record` : ''; const gaps = row.gaps.length > 0 ? ` ⚠️ UNREAD: ${row.gaps.join('; ')}` : ''; return `${parts.join(' · ')}${tail}${listed}${gaps}`; @@ -436,7 +455,8 @@ const USAGE = [ ' node scripts/pm/check-owed-reviews.mjs --self-test | --help', '', ' Lists the open pull requests, drafts included, whose face owes a contract review, with the record,', - ' hand-off and claim on the current head and the trigger that lets the triage seat take it.', + ' hand-off and claim on the current head, the trigger, and who takes it: the seat the hand-off', + ' names, the lane seat or a review seat — never triage.', ` --repo the repository whose pulls are read (default ${DEFAULT_REPO}); --board, where the seat posts live`, ' --now the clock (default: now); --json prints the rows as JSON', ' Report-only: reads through `gh api`, writes nothing, exits 0 on every read; 2 on a usage error.', @@ -472,8 +492,8 @@ const SELF_TEST_BATTERIES = Object.freeze({ 'arguments: modes and every refusal': 6, 'the owed face: paths, the Clause-② line, the label — drafts included, nothing else owes': 9, 'the record: on this head (by whom, as stated), on an earlier head, none, unjudged': 8, - 'the triggers: (a) hand-off or label the same round, (b) a full round, (c) the lane seat': 8, - 'a live claim holds the head and a record settles it; a lapsed claim holds nothing': 5, + 'the triggers: (a) hand-off or label the same round, (b) a full round, (c) the lane seat': 10, + 'a live claim holds the head and a record settles it; a lapsed claim holds nothing': 6, 'the live instance: two Tier H drafts with a hand-off on the current head list as TAKE under (a)': 4, 'the live read: every read through one reader; a failed read is INCOMPLETE, never clean': 6, 'the report: one line per owing PR, TAKE first, exit 0': 5, @@ -500,7 +520,8 @@ export function selfTest() { const HEAD2 = 'ff079cc066b82261e0827f995c83d075302f6798'; const TRIAGE = 'session_01AavokzJ5DndAwitDXvKy4U'; const LANE = 'session_01JQwxbZeBv2y91towxasSUT'; - const handoff = (head, at) => ({ id: 6463525633, created_at: at, body: `## Seat note · contract review owed\n\nContract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, ${at.slice(0, 16)}Z) · head \`${head}\`` }); + const REVIEWER = 'session_01ReviewSeatFixture7Qx'; + const handoff = (head, at, to = 'review#1') => ({ id: 6463525633, created_at: at, body: `## Seat note · contract review owed\n\nContract-review-handoff: ${to} — CONTRACT_REVIEW_TIER unavailable (ceiling model out for the fleet since 2026-10-27, seat runs at TIER_DEFAULT, ${at.slice(0, 16)}Z) · head \`${head}\`` }); const claimRow = (session, head, at, sig) => ({ id: 6468788431, created_at: at, body: `${sig}\n\nContract-review-claim: \`${session}\` · head \`${head}\` · ${at.slice(0, 16)}Z\n\nTaking the review.` }); const recordRow = (head, verdict = 'PASS', id = 6469007202) => ({ id, created_at: '2026-10-28T12:29:43Z', body: `## Contract review\n\nServed-tier: \`CONTRACT_REVIEW_TIER\`\nHead-sha: \`${head}\`\nLocal-runs: none\n\nReviewed-by: \`${TRIAGE}\`\n\n**VERDICT: ${verdict}**` }); const pull = (o = {}) => ({ @@ -559,18 +580,21 @@ export function selfTest() { battery('the triggers: (a) hand-off or label the same round, (b) a full round, (c) the lane seat'); const fresh = '2026-10-28T12:30:00Z'; - t('⭐ (a) a hand-off naming the current head ⇒ TAKE the same round, even 30 minutes in', (() => { const r = row({ headCommittedAt: fresh, comments: [handoff(HEAD, '2026-10-28T12:40:00Z')] }); return r.status === 'TAKE' && r.trigger === '(a) hand-off' && r.handoff.state === 'triage'; })()); - t('⭐ (a) the waiting label ⇒ TAKE the same round', (() => { const r = row({ headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }); return r.status === 'TAKE' && r.trigger === '(a) label'; })()); + t('⭐ (a) a hand-off naming the current head ⇒ TAKE the same round, even 30 minutes in', (() => { const r = row({ headCommittedAt: fresh, comments: [handoff(HEAD, '2026-10-28T12:40:00Z')] }); return r.status === 'TAKE' && r.trigger === '(a) hand-off' && r.handoff.state === 'handed' && r.handoff.to === 'review#1' && r.taker === 'review#1'; })()); + t('⭐ (a) the waiting label ⇒ TAKE the same round, taken by the lane seat or a review seat', (() => { const r = row({ headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }); return r.status === 'TAKE' && r.trigger === '(a) label' && r.taker === 'the domain:ui seat or a review seat'; })()); + t('⭐ a hand-off to a sibling lane seat names that seat as the taker — the fallback while no review seat is seated', (() => { const r = row({ headCommittedAt: fresh, comments: [handoff(HEAD, '2026-10-28T12:40:00Z', 'domain:ui#2')] }); return r.status === 'TAKE' && r.taker === 'domain:ui#2' && renderRow(r).includes('hand-off handed domain:ui#2 (6463525633)') && renderRow(r).includes('→ (a) hand-off · take: domain:ui#2'); })()); + t('⛔ a hand-off line still naming `triage` hands to nobody: unreadable, no (a) from it, and the taker is never triage', (() => { const r = row({ headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }], comments: [handoff(HEAD, '2026-10-28T12:40:00Z', 'triage')] }); return r.handoff.state === 'unreadable' && r.trigger === '(a) label' && r.taker === 'the domain:ui seat or a review seat' && !/take: [^·]*triage/.test(renderRow(r)); })()); t('a hand-off on an EARLIER head is no (a) — stale', (() => { const r = row({ headCommittedAt: fresh, comments: [handoff(HEAD2, '2026-10-28T12:40:00Z')] }); return r.handoff.state === 'stale' && r.status === 'WAIT'; })()); - t('⭐ (b) the head has waited a full triage round with no record and no claim ⇒ TAKE', (() => { const r = row({ headCommittedAt: '2026-10-28T11:59:00Z' }); return r.status === 'TAKE' && r.trigger === '(b) waited 61 min'; })()); + t('⭐ (b) the head has waited a full triage round with no record and no claim ⇒ TAKE', (() => { const r = row({ headCommittedAt: '2026-10-28T11:59:00Z' }); return r.status === 'TAKE' && r.trigger === '(b) waited 61 min' && r.taker === 'the domain:ui seat or a review seat'; })()); t('under a full round, nothing else holding ⇒ WAIT (the lane\'s own), trigger none yet', (() => { const r = row({ headCommittedAt: '2026-10-28T12:01:00Z' }); return r.status === 'WAIT' && r.trigger === 'none yet' && r.waited === 59; })()); - t('⭐ (c) the lane seat stood down, or absent from the roster ⇒ TAKE', row({ headCommittedAt: fresh }, [{ lane: 'domain:ui', status: 'paused' }]).trigger === '(c) lane seat stood-down' && row({ headCommittedAt: fresh }, [{ lane: 'domain:skills', status: 'seated' }]).trigger === '(c) lane seat absent'); + t('⭐ (c) the lane seat stood down, or absent from the roster ⇒ TAKE, by a review seat or the lane\'s next seat', row({ headCommittedAt: fresh }, [{ lane: 'domain:ui', status: 'paused' }]).trigger === '(c) lane seat stood-down' && row({ headCommittedAt: fresh }, [{ lane: 'domain:skills', status: 'seated' }]).trigger === '(c) lane seat absent' && row({ headCommittedAt: fresh }, [{ lane: 'domain:ui', status: 'paused' }]).taker === 'a review seat, or the domain:ui seat next on duty'); t('an unread roster or a card with no lane judges no (c)', row({ headCommittedAt: fresh }, null).laneSeat === 'unknown' && row({ headCommittedAt: fresh, cardLabels: [] }).laneSeat === 'unknown'); t('an unknown commit time judges no (b); a PR opened after its head commit waits from its opening', row({ headCommittedAt: null }).waited === null && row({ headCommittedAt: null }).status === 'WAIT' && row({ headCommittedAt: '2026-10-28T07:30:00Z', created_at: '2026-10-28T12:30:00Z' }).waited === 30); battery('a live claim holds the head and a record settles it; a lapsed claim holds nothing'); t('⭐ a live claim on the current head ⇒ HELD, naming the holder — whatever trigger holds', (() => { const r = row({ labels: [{ name: WAITING_LABEL }], comments: [claimRow(TRIAGE, HEAD, '2026-10-28T12:18:56Z', `triage#1 · os-zhuang · ${TRIAGE}`)] }); return r.status === 'HELD' && r.claim.holder.session === TRIAGE && r.trigger === null; })()); - t('⭐ the lane wins a tie: triage first, lane later ⇒ the lane holds', (() => { const r = row({ comments: [claimRow(TRIAGE, HEAD, '2026-10-28T12:18:56Z', `triage#1 · os-zhuang · ${TRIAGE}`), { ...claimRow(LANE, HEAD, '2026-10-28T12:20:00Z', `domain:ui#1 · os-bill · ${LANE}`), id: 2 }] }); return r.status === 'HELD' && r.claim.holder.session === LANE && r.claim.tie === true; })()); + t('⭐ a legacy `triage#N`-signed claim yields: triage first (posted before triage stopped reviewing), lane later ⇒ the lane holds', (() => { const r = row({ comments: [claimRow(TRIAGE, HEAD, '2026-10-28T12:18:56Z', `triage#1 · os-zhuang · ${TRIAGE}`), { ...claimRow(LANE, HEAD, '2026-10-28T12:20:00Z', `domain:ui#1 · os-bill · ${LANE}`), id: 2 }] }); return r.status === 'HELD' && r.claim.holder.session === LANE && r.claim.tie === true; })()); + t('⭐ two live claims ⇒ the EARLIEST holds, no tie: a review seat first, the lane later ⇒ the review seat holds, and the row prints no tie', (() => { const r = row({ comments: [claimRow(REVIEWER, HEAD, '2026-10-28T12:18:56Z', `review#1 · os-zhuang · ${REVIEWER}`), { ...claimRow(LANE, HEAD, '2026-10-28T12:20:00Z', `domain:ui#1 · os-bill · ${LANE}`), id: 2 }] }); return r.status === 'HELD' && r.claim.holder.session === REVIEWER && r.claim.tie === false && !renderRow(r).includes('yields'); })()); t('a record on the head settles it even beside a live claim ⇒ DONE', row({ comments: [claimRow(TRIAGE, HEAD, '2026-10-28T12:18:56Z', `triage#1 · os-zhuang · ${TRIAGE}`), recordRow(HEAD)] }).status === 'DONE'); t('⛔ a claim two hours old with no record holds nothing ⇒ the trigger reads again', (() => { const r = row({ comments: [claimRow(TRIAGE, HEAD, '2026-10-28T10:30:00Z', `triage#1 · os-zhuang · ${TRIAGE}`)] }); return r.claim.state === 'lapsed' && r.status === 'TAKE'; })()); t('⛔ a claim on an earlier head holds nothing — the head moved', row({ comments: [claimRow(TRIAGE, HEAD2, '2026-10-28T12:50:00Z', `triage#1 · os-zhuang · ${TRIAGE}`)] }).claim.state === 'none'); @@ -582,10 +606,10 @@ export function selfTest() { const ci = pull({ number: 27136, comments: [handoff(HEAD, '2026-10-28T07:43:43Z')], labels: [{ name: WAITING_LABEL }] }); const ai = pull({ number: 27042, head: { sha: HEAD2, ref: 'claude/project-thread-26wk5s' }, files: ['ui/packages/plugin-ai/package.json', 'pnpm-lock.yaml'], body: 'Clause-②: no (narrowing)\nMetadata-protocol: narrowed', headCommittedAt: '2026-10-28T07:34:00Z', labels: [{ name: WAITING_LABEL }], comments: [{ ...handoff('7040d92f78f504b1ef037d8451106c0be452f375', '2026-10-28T03:10:38Z'), id: 6459640213 }, { ...handoff(HEAD2, '2026-10-28T12:14:25Z'), id: 6468695894 }] }); const live = owedReviewRows({ repo: DEFAULT_REPO, pulls: [ci, ai], seats: SEATED }, { now: Date.parse('2026-10-28T12:15:00Z') }); - t('⭐ both list, both TAKE, both under (a) by their hand-off line — drafts, the case the rebuilt scan skipped', live.length === 2 && live.every((r) => r.status === 'TAKE' && r.trigger === '(a) hand-off' && r.draft === true)); + t('⭐ both list, both TAKE, both under (a) by their hand-off line — drafts, the case the rebuilt scan skipped — each taken by the review seat the line names', live.length === 2 && live.every((r) => r.status === 'TAKE' && r.trigger === '(a) hand-off' && r.draft === true && r.taker === 'review#1')); t('the workflow draft owes on the rules face, the retirement on the narrowing face and the label', live.find((r) => r.pr === 27136).face.join('+') === 'rules+label' && live.find((r) => r.pr === 27042).face.join('+') === 'narrowing+label'); t('the renewed hand-off governs: the newest line names the current head', live.find((r) => r.pr === 27042).handoff.id === 6468695894); - t('once the triage seat posts its claim, both read HELD and nobody else starts', owedReviewRows({ repo: DEFAULT_REPO, pulls: [{ ...ci, comments: [...ci.comments, claimRow(TRIAGE, HEAD, '2026-10-28T12:18:56Z', `triage#1 · os-zhuang · ${TRIAGE}`)] }], seats: SEATED }, { now: Date.parse('2026-10-28T12:20:00Z') })[0].status === 'HELD'); + t('once the review seat posts its claim, the row reads HELD, with no taker, and nobody else starts', (() => { const r = owedReviewRows({ repo: DEFAULT_REPO, pulls: [{ ...ci, comments: [...ci.comments, claimRow(REVIEWER, HEAD, '2026-10-28T12:18:56Z', `review#1 · os-zhuang · ${REVIEWER}`)] }], seats: SEATED }, { now: Date.parse('2026-10-28T12:20:00Z') })[0]; return r.status === 'HELD' && r.claim.holder.session === REVIEWER && r.taker === null; })()); battery('the live read: every read through one reader; a failed read is INCOMPLETE, never clean'); const paths = []; @@ -622,7 +646,7 @@ export function selfTest() { battery('the report: one line per owing PR, TAKE first, exit 0'); const printed = []; const code = main(['--repo', DEFAULT_REPO, '--now', '2026-10-28T13:00:00Z'], { get: fake(routes), log: (l) => printed.push(l), error: (l) => printed.push(l) }); - t('⭐ exit 0, and the one owing PR prints as one TAKE line under (a)', code === EXIT_OK && printed.filter((l) => /^(TAKE|WAIT|HELD|DONE) {2}#/.test(l)).length === 1 && printed.some((l) => l.startsWith('TAKE #27136 draft') && l.includes('→ (a) hand-off'))); + t('⭐ exit 0, and the one owing PR prints as one TAKE line under (a)', code === EXIT_OK && printed.filter((l) => /^(TAKE|WAIT|HELD|DONE) {2}#/.test(l)).length === 1 && printed.some((l) => l.startsWith('TAKE #27136 draft') && l.includes('→ (a) hand-off · take: review#1')) && !printed.some((l) => /take: [^·]*triage/.test(l))); t('the header counts what was read and what is owed', printed[0].includes('2 open PR(s) read (1 draft)') && printed[0].includes('1 owe a contract review — 1 to take')); t('rows sort TAKE, WAIT, HELD, DONE, then by number', owedReviewRows({ pulls: [pull({ number: 3, comments: [recordRow(HEAD)] }), pull({ number: 2, headCommittedAt: '2026-10-28T12:59:00Z' }), pull({ number: 1, labels: [{ name: WAITING_LABEL }] })], seats: SEATED }, { now: NOW }).map((r) => `${r.status}${r.pr}`).join() === 'TAKE1,WAIT2,DONE3'); const jsonOut = []; @@ -676,7 +700,7 @@ export function selfTest() { console.log( `✓ check-owed-reviews self-test: ${cases.length} cases pass in ${declared.length} batteries (the owed face from paths, ` + 'the Clause-② line and the label, drafts included; the record on this head, an earlier head, none, unjudged; ' - + 'the (a) / (b) / (c) triggers; claims holding and lapsing, the lane winning a tie; the two live drafts as TAKE; ' + + 'the (a) / (b) / (c) triggers and the taker — the named seat, the lane seat or a review seat, never triage; claims holding and lapsing, the earliest live claim holding; the two live drafts as TAKE; ' + 'every read through one reader with failures as gaps; the report, exit 0; a lane\'s live hold holding (b) alone, ' + 'cleared by a new head, an unreadable hold line voiding nothing and printed, listed at 24 hours and still WAIT).', ); diff --git a/scripts/pm/record-recognisers.mjs b/scripts/pm/record-recognisers.mjs index 4092d00fd41..709540fedcb 100644 --- a/scripts/pm/record-recognisers.mjs +++ b/scripts/pm/record-recognisers.mjs @@ -815,20 +815,22 @@ export function localRunsStand(reading) { /** * The `Contract-review-handoff:` key line -- the ONE way a dispatching seat - * hands its contract review to the triage seat, read with `Reviewed-by:`'s own - * key discipline. + * hands its contract review to a review seat or a sibling seat of its lane, + * read with `Reviewed-by:`'s own key discipline. * * ⭐ The rule it makes machine-findable (the skill's contract-review reference, - * 〈复核归属与资格〉, and the triage duties' round step): the seat that - * dispatched the work runs the review at the contract-review tier. ONLY when - * that tier's call is quota-refused does the seat hand the review over -- it - * never reviews at a lower tier -- and the hand-off is one PR comment carrying - * this line beside the existing `needs:contract-review` label: - * - * Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429, 2026-10-27T13:00Z) · head 1b03a9c3 - * - * Three facts, each required: the TARGET (`triage`, the closed set of one), the - * TIER that was unavailable -- the constant's NAME, exactly as `Served-tier:` + * 〈复核归属与资格〉): the seat that dispatched the work runs the review in + * its own seat at the contract-review tier. ONLY when that tier's call is + * quota-refused does the seat hand the review over -- it never reviews at a + * lower tier -- and the hand-off is one PR comment carrying this line beside + * the existing `needs:contract-review` label: + * + * Contract-review-handoff: review#1 — CONTRACT_REVIEW_TIER unavailable (HTTP 429, 2026-10-27T13:00Z) · head 1b03a9c3 + * + * Three facts, each required: the TARGET (a review seat `review#N`, or a + * sibling seat of the lane `domain:#N` while no review seat is seated -- + * the closed set of two shapes; ⛔ never `triage`, which takes no contract + * review, so a line naming it is unreadable), the TIER that was unavailable -- the constant's NAME, exactly as `Served-tier:` * spells it, ⛔ never a model id -- with its reason and UTC time in the * parentheses, and the HEAD the hand-off is for, 7 to 40 hex. A line missing * any of them is started-and-unreadable: a hand-off nobody can date, or one @@ -836,15 +838,23 @@ export function localRunsStand(reading) { * * ⛔ No gate reads it. The queue guard keeps reading the ONE `## Contract * review` record; this line decides only WHO renders that record, and the - * triage seat's record is the same shape, `Reviewed-by:` naming the triage - * seat. `contractReviewHandoff` below is the reading a seat or patrol takes. + * named seat's record is the same shape, `Reviewed-by:` naming that seat. + * `contractReviewHandoff` below is the reading a seat or patrol takes. */ export const CONTRACT_REVIEW_HANDOFF_KEY = 'Contract-review-handoff'; +/** + * The line exactly as the rule text prints it, placeholders included. This file's self-test pins it + * VERBATIM in `references/contract-review.md`, so the text and `HANDOFF_TARGET` cannot drift apart. + */ +export const CONTRACT_REVIEW_HANDOFF_TEMPLATE = `${CONTRACT_REVIEW_HANDOFF_KEY}: #N> — CONTRACT_REVIEW_TIER unavailable (, ) · head `; const CONTRACT_REVIEW_HANDOFF_LINE = keyLineRegex(CONTRACT_REVIEW_HANDOFF_KEY); /** The same key as a MARKER over a whole comment body -- `latestMarkedComment`'s convention (multiline, `g`-less). */ const CONTRACT_REVIEW_HANDOFF_MARKER = new RegExp(CONTRACT_REVIEW_HANDOFF_LINE.source, 'm'); -/** `triage`, a separator (an em dash, a hyphen pair, a hyphen or a colon), then the rest. */ -const HANDOFF_TARGET = /^triage\b[ \t]*(?:—|--|-|:)?[ \t]*(.*)$/i; +/** + * The target -- `review#N` or `domain:#N`, the seat number required -- a separator (an em dash, a + * hyphen pair, a hyphen or a colon), then the rest. `triage` and every other seat word match nothing. + */ +const HANDOFF_TARGET = /^(review#[1-9][0-9]*|domain:[a-z][a-z0-9._-]*#[1-9][0-9]*)(?![0-9])[ \t]*(?:—|--|-|:)?[ \t]*(.*)$/i; /** `(reason, UTC time)` then ` · head SHA` -- the time is the LAST comma-separated item in the parentheses. */ const HANDOFF_TAIL = /^\(([^()]*?)[ \t]*,[ \t]*([^,()]+)\)[ \t]*·[ \t]*head[ \t]+`?([0-9a-fA-F]{7,40})`?[ \t]*$/; @@ -853,7 +863,8 @@ const HANDOFF_TAIL = /^\(([^()]*?)[ \t]*,[ \t]*([^,()]+)\)[ \t]*·[ \t]*head[ \t * * @returns {{ state: 'missing' } * | { state: 'unreadable', line: string } - * | { state: 'read', to: 'triage', tier: string, reason: string, at: string, head: string }} + * | { state: 'read', to: string, tier: string, reason: string, at: string, head: string }} + * `to` is the target seat, lower-cased (`review#1`, `domain:ui#2`). */ export function readContractReviewHandoff(text) { for (const line of String(text ?? '').split(/\r?\n/)) { @@ -864,14 +875,14 @@ export function readContractReviewHandoff(text) { if (!target) return unreadable; // The tier is read by NAME, through the one token reader `Served-tier:` uses, so a model id // written here is refused for the same reason it is refused there. - const named = stripValueDecoration(target[1]); + const named = stripValueDecoration(target[2]); const tier = TIER_TOKEN.exec(named); if (!tier || tier[1] !== CONTRACT_REVIEW_TIER_NAME) return unreadable; const rest = stripValueDecoration(named.slice(tier[0].length)); const tail = /^unavailable\b[ \t]*(.*)$/i.exec(rest); const parts = tail ? HANDOFF_TAIL.exec(tail[1].trim()) : null; if (!parts || parts[1].trim() === '') return unreadable; - return { state: 'read', to: 'triage', tier: tier[1], reason: parts[1].trim(), at: parts[2].trim(), head: parts[3] }; + return { state: 'read', to: target[1].toLowerCase(), tier: tier[1], reason: parts[1].trim(), at: parts[2].trim(), head: parts[3] }; } return { state: 'missing' }; } @@ -880,10 +891,11 @@ export function readContractReviewHandoff(text) { * Whose review this pull request's contract review is, read off its comment thread: * * absent no hand-off line on any comment -- the dispatching seat's own review; a - * `needs:contract-review` label alone changes nothing here, and triage does not touch it - * triage the NEWEST hand-off line names the current head -- the triage seat renders the record + * `needs:contract-review` label alone changes nothing here + * handed the NEWEST hand-off line names the current head -- the seat it names (`to`, a review + * seat or a sibling lane seat) renders the record * stale the newest hand-off line names an earlier head -- the head moved past the hand-off, so - * it needs a fresh line or the seat's own review; triage does not act on it + * it needs a fresh line or the seat's own review; the named seat does not act on it * unreadable the thread or the head could not be read, or the newest hand-off line cannot be graded * * Newest governs, chosen by `latestMarkedComment` -- the one resolution this file's locator uses -- @@ -907,7 +919,7 @@ export function contractReviewHandoff(rows, headSha) { if (line.state !== 'read') return { state: 'unreadable', gaps: [`the newest hand-off line (comment ${stamp.id ?? '?'}) cannot be read: ${line.line ?? 'no line'}`], ...stamp }; // PREFIX either way, as `regenChainToHead` meets two spellings of one commit: the line may abbreviate the head. const current = shaMeets(head, line.head.toLowerCase()); - return { state: current ? 'triage' : 'stale', head: line.head, reason: line.reason, handedAt: line.at, ...stamp }; + return { state: current ? 'handed' : 'stale', to: line.to, head: line.head, reason: line.reason, handedAt: line.at, ...stamp }; } // --------------------------------------------------------------------------- @@ -916,11 +928,11 @@ export function contractReviewHandoff(rows, headSha) { /** * The `Contract-review-claim:` key line -- who has STARTED the contract review - * of one head, posted by that reviewer before it starts, lane seat or triage. + * of one head, posted by that reviewer before it starts, lane seat or review seat. * * ⭐ The rule it makes machine-findable (the skill's contract-review reference, - * 〈复核归属与资格〉): the lane reviews its own; the triage seat takes a review - * only when nobody is doing it. Before starting, every reviewer reads the PR: + * 〈复核归属与资格〉): the lane reviews its own; a review seat, or a sibling + * lane seat, takes one only when it is handed over or nobody is doing it. Before starting, every reviewer reads the PR: * a record or a LIVE claim on the current head means do not start. Otherwise it * posts ONE comment carrying this line first, then reviews: * @@ -939,10 +951,11 @@ export function contractReviewHandoff(rows, headSha) { * {@link CONTRACT_REVIEW_CLAIM_LAPSE_HOURS} hours after it was posted -- the * reviewer died -- after which the next reviewer may claim. A record on the * head settles the review whatever any claim says; that is the caller's read - * (`locateReviewOfRecord`), never this one's. When a lane claim and a triage - * claim are both live on one head the LANE wins: triage withdraws by replying + * (`locateReviewOfRecord`), never this one's. When two claims are both live on + * one head the EARLIEST posted holds: the later claimant withdraws by replying * once and discards its subagent's work (`contractReviewClaims` names the - * holder). + * holder; a legacy `triage#N`-signed claim is the one claim that yields to a + * later one, as described there). * * ⛔ No gate reads it. The queue guard keeps reading the ONE `## Contract * review` record; this line is coordination between reviewers, never a merge @@ -1014,16 +1027,17 @@ export function contractReviewClaimLapse(claim, { headSha, now, postedAt } = {}) /** * Who holds this pull request's current head, read off its comment thread: * - * live a live claim names the current head -- `holder` is the claim to respect: the LANE's - * when a lane claim and a triage claim are both live (`tie` true), else the earliest posted + * live a live claim names the current head -- `holder` is the claim to respect: the earliest + * posted, except that a legacy `triage#N` claim yields to any other live claim (`tie` true) * lapsed claims named the current head, and every one has expired * none no claim names the current head (claims on earlier heads lapsed when it moved) * unreadable the thread, the head or the clock could not be read * - * A claim's SIDE is read off its comment's signature line (`readSignatureLine`): the triage seat - * signs `triage#N · …`, and every other claim -- a lane seat's, a cross-account reviewer's, an - * unsigned one -- is the `lane` side, which is the side the tie rule protects. Unreadable claim - * lines are returned by comment id and hold nothing. + * A claim's SIDE is read off its comment's signature line (`readSignatureLine`): a claim signed + * `triage#N · …` -- posted before triage stopped taking contract reviews -- is the `triage` side, + * and every other claim -- a lane seat's, a review seat's, a sibling lane seat's, an unsigned one + * -- is the `lane` side, among which the earliest posted holds. Unreadable claim lines are + * returned by comment id and hold nothing. * * @param {{ id?: number, body?: string, created_at?: string }[]} rows the PR's comment rows * @param {string} headSha the pull request's current head, full @@ -1258,12 +1272,12 @@ export function contractReviewHold(rows, headSha, { now } = {}) { * or a patrol takes; nothing here changes an exit. */ export const MAILBOX_KEYS = Object.freeze([ - { id: 'request:contract-review', kind: 'Request', key: 'contract-review', from: 'lane', to: 'triage', when: 'in the same stroke as the PR\'s `Contract-review-handoff:` line (the seat\'s own review tier is unavailable)', template: 'Request: contract-review · PR #n · head SHA · REASON' }, + { id: 'request:contract-review', kind: 'Request', key: 'contract-review', from: 'lane', to: 'a review seat (`review#N`), or a sibling seat of the lane while none is seated', when: 'in the same stroke as the PR\'s `Contract-review-handoff:` line (the seat\'s own review tier is unavailable)', template: 'Request: contract-review · PR #n · head SHA · REASON' }, { id: 'request:triage', kind: 'Request', key: 'triage', from: 'lane', to: 'triage', when: 'a question about a card\'s fate, route or grade', template: 'Request: triage · #n · ONE-LINE-QUESTION' }, { id: 'request:urgent-triage', kind: 'Request', key: 'urgent-triage', from: 'any seat', to: 'triage', when: 'a p0 suspect, main red, a deterministic queue red', template: 'Request: urgent-triage · #n · ONE-LINE-CRITERION' }, { id: 'request:main-red', kind: 'Request', key: 'main-red', from: 'the seat that files the main-red anchor card', to: 'triage, or the director when triage is absent', when: 'the anchor card is filed', template: 'Request: main-red · CHECK-NAME · anchor #n · suggested lane X' }, { id: 'notify:main-red', kind: 'Notify', key: 'main-red', from: 'the seat that files the main-red anchor card', to: 'the seat that landed the breaking PR, and the anchor card\'s lane', when: 'the anchor card is filed', template: 'Notify: main-red · anchor #n · breaking PR #m or none' }, - { id: 'notify:contract-review', kind: 'Notify', key: 'contract-review', from: 'triage', to: 'lane', when: 'with the record, only when the lane is not subscribed to the PR', template: 'Notify: contract-review PASS or FAIL · PR #n · record ID' }, + { id: 'notify:contract-review', kind: 'Notify', key: 'contract-review', from: 'the review seat or sibling lane seat that rendered the record', to: 'lane', when: 'with the record, only when the lane is not subscribed to the PR', template: 'Notify: contract-review PASS or FAIL · PR #n · record ID' }, { id: 'notify:release-blocker', kind: 'Notify', key: 'release-blocker', from: 'triage', to: 'the lane it is graded into', when: 'a p0/p1 release blocker is graded into a lane', template: 'Notify: release-blocker · #n' }, { id: 'notify:unblocked', kind: 'Notify', key: 'unblocked', from: 'the seat that moved the card to `pm:queue` (triage or an epic seat)', to: 'the card\'s lane seats', when: 'a p0/p1 card\'s `Blocked-by` target closed', template: 'Notify: unblocked · #n · p0 or p1' }, { id: 'notify:ruling', kind: 'Notify', key: 'ruling', from: 'the seat that recorded a maintainer ruling', to: 'the seat whose card or draft it changes', when: 'the ruling is recorded', template: 'Notify: ruling · #n · LETTER · record ID' }, @@ -2181,14 +2195,14 @@ const SELF_TEST_BATTERIES = Object.freeze({ '⭐ B′ with C: a PROSE-ONLY head move keeps the record, decided on the COMMITTED trees like a regeneration': 6, '⭐ letter C: the at-tier review of a mixed diff is SCOPED — `hits` plus what they claim, on the `Scope:` line': 18, '⭐ letter B: a declared NARROWING is a face of its own at CONTRACT_REVIEW_TIER — keyed on the `Clause-②:` line, exact': 17, - '⭐ the `Contract-review-handoff:` line — names the current head ⇒ triage\'s review; head moved ⇒ stale; absent ⇒ the seat\'s own': 13, + '⭐ the `Contract-review-handoff:` line — names the current head ⇒ the named review or sibling seat\'s review; head moved ⇒ stale; absent ⇒ the seat\'s own; `triage` unreadable': 17, // Raised 27 → 40 with the scenario table's seven admitted keys and the `seat-online` patrol field: one pin // per new key reading, the patrol and `patrol none` readings, the both-ways table pin that replaced the // verbatim-template pin, the placeholder-is-no-event pin, the `--mailbox-keys` printout, the invented // keys that stay refused, and the two end-to-end pins through check-half-states H72 (31 → 44 cases). '⭐ the mailbox doorbell: `To: `, then one key of the table read — seat, kind, numbers, sha, login, time — and anything else refused': 40, '⭐ the signature line: the reader reads LANE#N · ACCOUNT · SESSION exactly, and one leading signature changes no first-line reading': 22, - '⭐ the `Contract-review-claim:` line — session, FULL head, time; lapses on a moved head or after two hours; the lane wins a tie': 21, + '⭐ the `Contract-review-claim:` line — session, FULL head, time; lapses on a moved head or after two hours; the earliest live claim holds': 21, '⭐ the `Contract-review-hold:` line — session, head, reason, until; a new head clears it; at 24 hours it reads overdue and still holds': 23, }); @@ -2611,23 +2625,28 @@ export async function selfTest() { // -- the `Contract-review-handoff:` line -------------------------------------- // // The maintainer's rule: a seat with a working contract-review tier reviews - // its own dispatches; only an exhausted tier hands the review to triage, and - // triage must be able to TELL that case apart from a label alone. The line is - // the telling; these cases pin the three readings a seat or patrol takes. - battery('⭐ the `Contract-review-handoff:` line — names the current head ⇒ triage\'s review; head moved ⇒ stale; absent ⇒ the seat\'s own'); + // its own dispatches; only an exhausted tier hands the review to a review + // seat, or a sibling seat of the lane while none is seated -- never to + // triage, which takes no contract review. The line is the telling; these + // cases pin the readings a seat or patrol takes. + battery('⭐ the `Contract-review-handoff:` line — names the current head ⇒ the named review or sibling seat\'s review; head moved ⇒ stale; absent ⇒ the seat\'s own; `triage` unreadable'); const HO_HEAD = '1b03a9c36be249502c1160b1f262f835f7ccaf02'; - const HO_LINE = (head = HO_HEAD, reason = 'HTTP 429', at = '2026-10-27T13:00Z') => `${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER_NAME} unavailable (${reason}, ${at}) · head ${head}`; + const HO_LINE = (head = HO_HEAD, reason = 'HTTP 429', at = '2026-10-27T13:00Z') => `${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 — ${CONTRACT_REVIEW_TIER_NAME} unavailable (${reason}, ${at}) · head ${head}`; const HO_ROW = (line, id = 4401, at = '2026-10-27T13:01:00Z') => ({ id, created_at: at, body: `Seat note — the review tier was refused this round.\n\n${line}\n\nLabel kept.` }); - t('⭐ the line names the CURRENT head ⇒ the review is triage\'s, with the reason, the time and the comment that handed it off', (() => { const r = contractReviewHandoff([HO_ROW(HO_LINE())], HO_HEAD); return r.state === 'triage' && r.head === HO_HEAD && r.reason === 'HTTP 429' && r.handedAt === '2026-10-27T13:00Z' && r.id === 4401; })()); + t('⭐ the line names the CURRENT head ⇒ handed, to the seat it names, with the reason, the time and the comment that handed it off', (() => { const r = contractReviewHandoff([HO_ROW(HO_LINE())], HO_HEAD); return r.state === 'handed' && r.to === 'review#1' && r.head === HO_HEAD && r.reason === 'HTTP 429' && r.handedAt === '2026-10-27T13:00Z' && r.id === 4401; })()); t('⭐ the head MOVED past the line ⇒ stale, naming the head the hand-off was for — a fresh line or the seat\'s own review', (() => { const r = contractReviewHandoff([HO_ROW(HO_LINE())], 'c260fddc'.padEnd(40, '0')); return r.state === 'stale' && r.head === HO_HEAD; })()); t('⭐ NO hand-off line on any comment ⇒ absent, the seat\'s own review — a record, a seat note, a label mention change nothing', (() => { const r = contractReviewHandoff([RECORD_ON_9AF9, { id: 4402, created_at: '2026-10-27T13:02:00Z', body: 'ACCEPT — `needs:contract-review` stays until the seat reviews.' }], HO_HEAD); return r.state === 'absent' && r.read === 2; })()); - t('the head is matched by PREFIX, as a record\'s span is: an abbreviated head on the line names the full head, and the reverse', contractReviewHandoff([HO_ROW(HO_LINE(HO_HEAD.slice(0, 7)))], HO_HEAD).state === 'triage' && contractReviewHandoff([HO_ROW(HO_LINE(HO_HEAD))], HO_HEAD.slice(0, 8)).state === 'triage'); - t('NEWEST governs — an older line on an old head under a newer one on the current head reads triage\'s, and the reverse reads stale', contractReviewHandoff([HO_ROW(HO_LINE('deadbeef1'), 4403, '2026-10-27T12:00:00Z'), HO_ROW(HO_LINE(), 4404, '2026-10-27T13:30:00Z')], HO_HEAD).state === 'triage' && contractReviewHandoff([HO_ROW(HO_LINE(), 4403, '2026-10-27T12:00:00Z'), HO_ROW(HO_LINE('deadbeef1'), 4404, '2026-10-27T13:30:00Z')], HO_HEAD).state === 'stale'); - t('decoration reads: a bulleted bolded key, a backticked tier name, a backticked head, a blockquoted line', (() => { const dec = `> - **${CONTRACT_REVIEW_HANDOFF_KEY}:** triage — \`${CONTRACT_REVIEW_TIER_NAME}\` unavailable (quota exhausted, 2026-10-27T13:00Z) · head \`${HO_HEAD.slice(0, 12)}\``; const r = readContractReviewHandoff(dec); return r.state === 'read' && r.head === HO_HEAD.slice(0, 12) && r.reason === 'quota exhausted' && contractReviewHandoff([HO_ROW(dec)], HO_HEAD).state === 'triage'; })()); - t('the separator is any of the four spellings, and the reason may itself carry a comma — the time is the LAST item in the parentheses', ['—', '--', '-', ':'].every((sep) => readContractReviewHandoff(`${CONTRACT_REVIEW_HANDOFF_KEY}: triage ${sep} ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, quota exhausted, 2026-10-27T13:00Z) · head ${HO_HEAD}`).reason === 'HTTP 429, quota exhausted')); - t('⛔ a line with no reason, no time or no head is started-and-unreadable, never a reading — the ambiguity the line exists to remove', [`${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER_NAME} unavailable (, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z)`, `${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head 1b03a`, `${CONTRACT_REVIEW_HANDOFF_KEY}:`].every((l) => readContractReviewHandoff(l).state === 'unreadable')); - t('⛔ the target is the closed set of one and the tier is the constant\'s NAME — another seat, the default tier or a model id is unreadable', [`${CONTRACT_REVIEW_HANDOFF_KEY}: director — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${TIER_DEFAULT_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`].every((l) => readContractReviewHandoff(l).state === 'unreadable')); - t('an unreadable NEWEST line makes the thread unreadable, naming the comment — never absent, never stale', (() => { const r = contractReviewHandoff([HO_ROW(`${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429) · head ${HO_HEAD}`, 4405)], HO_HEAD); return r.state === 'unreadable' && r.gaps.length === 1 && r.gaps[0].includes('4405'); })()); + t('the head is matched by PREFIX, as a record\'s span is: an abbreviated head on the line names the full head, and the reverse', contractReviewHandoff([HO_ROW(HO_LINE(HO_HEAD.slice(0, 7)))], HO_HEAD).state === 'handed' && contractReviewHandoff([HO_ROW(HO_LINE(HO_HEAD))], HO_HEAD.slice(0, 8)).state === 'handed'); + t('NEWEST governs — an older line on an old head under a newer one on the current head reads handed, and the reverse reads stale', contractReviewHandoff([HO_ROW(HO_LINE('deadbeef1'), 4403, '2026-10-27T12:00:00Z'), HO_ROW(HO_LINE(), 4404, '2026-10-27T13:30:00Z')], HO_HEAD).state === 'handed' && contractReviewHandoff([HO_ROW(HO_LINE(), 4403, '2026-10-27T12:00:00Z'), HO_ROW(HO_LINE('deadbeef1'), 4404, '2026-10-27T13:30:00Z')], HO_HEAD).state === 'stale'); + t('decoration reads: a bulleted bolded key, a backticked tier name, a backticked head, a blockquoted line', (() => { const dec = `> - **${CONTRACT_REVIEW_HANDOFF_KEY}:** review#1 — \`${CONTRACT_REVIEW_TIER_NAME}\` unavailable (quota exhausted, 2026-10-27T13:00Z) · head \`${HO_HEAD.slice(0, 12)}\``; const r = readContractReviewHandoff(dec); return r.state === 'read' && r.head === HO_HEAD.slice(0, 12) && r.reason === 'quota exhausted' && contractReviewHandoff([HO_ROW(dec)], HO_HEAD).state === 'handed'; })()); + t('the separator is any of the four spellings, and the reason may itself carry a comma — the time is the LAST item in the parentheses', ['—', '--', '-', ':'].every((sep) => readContractReviewHandoff(`${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 ${sep} ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, quota exhausted, 2026-10-27T13:00Z) · head ${HO_HEAD}`).reason === 'HTTP 429, quota exhausted')); + t('⛔ a line with no reason, no time or no head is started-and-unreadable, never a reading — the ambiguity the line exists to remove', [`${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 — ${CONTRACT_REVIEW_TIER_NAME} unavailable (, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z)`, `${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head 1b03a`, `${CONTRACT_REVIEW_HANDOFF_KEY}:`].every((l) => readContractReviewHandoff(l).state === 'unreadable')); + t('⛔ the target is the closed set of two shapes and the tier is the constant\'s NAME — `triage`, `director`, a seat with no number, the default tier or a model id is unreadable', [`${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: director — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: review — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: domain:ui — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 — ${TIER_DEFAULT_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`, `${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 — ${CONTRACT_REVIEW_TIER} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`].every((l) => readContractReviewHandoff(l).state === 'unreadable')); + t('⭐ the line is spelled VERBATIM, in a code span, in the rule text (`references/contract-review.md`) — the template and `HANDOFF_TARGET` cannot drift apart', (() => { const rules = readFileSync(new URL('../../.claude/skills/pm-dispatch/references/contract-review.md', import.meta.url), 'utf8'); return rules.includes(`\`${CONTRACT_REVIEW_HANDOFF_TEMPLATE}\``) && CONTRACT_REVIEW_HANDOFF_TEMPLATE.startsWith(`${CONTRACT_REVIEW_HANDOFF_KEY}: `); })()); + t('⛔ the TEMPLATE sent verbatim is unreadable — a placeholder names no seat and no head', readContractReviewHandoff(CONTRACT_REVIEW_HANDOFF_TEMPLATE).state === 'unreadable' && contractReviewHandoff([HO_ROW(CONTRACT_REVIEW_HANDOFF_TEMPLATE, 4407)], HO_HEAD).state === 'unreadable'); + t('⭐ a sibling seat of the lane is a target while no review seat is seated: `domain:#N` reads, lower-cased, and hands the head to it', (() => { const line = `${CONTRACT_REVIEW_HANDOFF_KEY}: Domain:UI#2 — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`; const r = contractReviewHandoff([HO_ROW(line)], HO_HEAD); return readContractReviewHandoff(line).to === 'domain:ui#2' && r.state === 'handed' && r.to === 'domain:ui#2' && readContractReviewHandoff(`${CONTRACT_REVIEW_HANDOFF_KEY}: review#12 — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`).to === 'review#12'; })()); + t('⛔ a line still naming `triage` hands the review to NOBODY: newest on the thread it is unreadable, never handed — triage takes no contract review', (() => { const r = contractReviewHandoff([HO_ROW(`${CONTRACT_REVIEW_HANDOFF_KEY}: triage — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429, 2026-10-27T13:00Z) · head ${HO_HEAD}`, 4406)], HO_HEAD); return r.state === 'unreadable' && r.gaps[0].includes('4406'); })()); + t('an unreadable NEWEST line makes the thread unreadable, naming the comment — never absent, never stale', (() => { const r = contractReviewHandoff([HO_ROW(`${CONTRACT_REVIEW_HANDOFF_KEY}: review#1 — ${CONTRACT_REVIEW_TIER_NAME} unavailable (HTTP 429) · head ${HO_HEAD}`, 4405)], HO_HEAD); return r.state === 'unreadable' && r.gaps.length === 1 && r.gaps[0].includes('4405'); })()); t('a thread that is not an array, or a head too short to match, is a read that could not be made', contractReviewHandoff(null, HO_HEAD).state === 'unreadable' && contractReviewHandoff([], 'abc').state === 'unreadable' && contractReviewHandoff([], HO_HEAD).state === 'absent'); t('the key is ONE regex, built by the one key-line convention, and prose naming it mid-sentence is not the line', CONTRACT_REVIEW_HANDOFF_LINE.source === keyLineRegex(CONTRACT_REVIEW_HANDOFF_KEY).source && readContractReviewHandoff(`the ${CONTRACT_REVIEW_HANDOFF_KEY}: line goes on the PR`).state === 'missing' && contractReviewHandoff([HO_ROW(`the ${CONTRACT_REVIEW_HANDOFF_KEY}: line goes on the PR`)], HO_HEAD).state === 'absent'); t('⛔ no gate reads it: the queue guard\'s record reading is untouched — a hand-off line beside a record on this head still reads FOUND', (() => { const r = locateReviewOfRecord(pairOf({ prComments: [RECORD_ON_9AF9, HO_ROW(HO_LINE(HEAD_9AF9))] })); return r.state === 'found' && r.id === 3301; })()); @@ -2639,7 +2658,7 @@ export async function selfTest() { // head. These cases read the line as the triage seat really posted it, with // and without the signature above it, refuse every shape that names no head // or no time, and pin the lapse arithmetic and the lane-wins tie. - battery('⭐ the `Contract-review-claim:` line — session, FULL head, time; lapses on a moved head or after two hours; the lane wins a tie'); + battery('⭐ the `Contract-review-claim:` line — session, FULL head, time; lapses on a moved head or after two hours; the earliest live claim holds'); const CL_HEAD = '3beee64fe77741d07f035f1b80eeaa07e7280ab4'; const CL_SESSION = 'session_01AavokzJ5DndAwitDXvKy4U'; const CL_LANE_SESSION = 'session_01JQwxbZeBv2y91towxasSUT'; @@ -2664,7 +2683,7 @@ export async function selfTest() { t('⭐ lapse: the current head under two hours ⇒ live; two hours or more ⇒ expired; another head ⇒ head-moved', (() => { const c = readContractReviewClaim(CL_LINE()); const at = c.at; return contractReviewClaimLapse(c, { headSha: CL_HEAD, now: at + 119 * 60000 }) === 'live' && contractReviewClaimLapse(c, { headSha: CL_HEAD, now: at + 120 * 60000 }) === 'expired' && contractReviewClaimLapse(c, { headSha: 'f'.repeat(40), now: at }) === 'head-moved' && CONTRACT_REVIEW_CLAIM_LAPSE_HOURS === 2; })()); t('the lapse is timed from when the comment was POSTED when that stamp is held, else from the line\'s own time', (() => { const c = readContractReviewClaim(CL_LINE()); const posted = c.at + 90 * 60000; return contractReviewClaimLapse(c, { headSha: CL_HEAD, now: c.at + 150 * 60000, postedAt: posted }) === 'live' && contractReviewClaimLapse(c, { headSha: CL_HEAD, now: c.at + 150 * 60000 }) === 'expired'; })()); t('⭐ the thread: a live claim on the current head holds it — named by session, comment and side', (() => { const r = contractReviewClaims([CL_ROW(CL_BODY())], CL_HEAD, { now: CL_NOW }); return r.state === 'live' && r.holder.session === CL_SESSION && r.holder.id === 6468788431 && r.holder.side === 'triage' && r.tie === false; })()); - t('⭐ the LANE wins a tie: a triage claim posted FIRST and a lane claim posted later, both live ⇒ the lane holds, tie named', (() => { const r = contractReviewClaims([CL_ROW(CL_BODY(), 1, '2026-10-28T12:18:56Z'), CL_ROW(CL_BODY(CL_LANE_SIG, CL_LINE(CL_LANE_SESSION, CL_HEAD, '2026-10-28T12:20Z')), 2, '2026-10-28T12:20:10Z')], CL_HEAD, { now: CL_NOW }); return r.state === 'live' && r.tie === true && r.holder.side === 'lane' && r.holder.session === CL_LANE_SESSION; })()); + t('⭐ a legacy `triage#N` claim YIELDS: a triage claim posted FIRST and a lane claim posted later, both live ⇒ the later claim holds, tie named', (() => { const r = contractReviewClaims([CL_ROW(CL_BODY(), 1, '2026-10-28T12:18:56Z'), CL_ROW(CL_BODY(CL_LANE_SIG, CL_LINE(CL_LANE_SESSION, CL_HEAD, '2026-10-28T12:20Z')), 2, '2026-10-28T12:20:10Z')], CL_HEAD, { now: CL_NOW }); return r.state === 'live' && r.tie === true && r.holder.side === 'lane' && r.holder.session === CL_LANE_SESSION; })()); t('two claims on one side: the EARLIEST posted holds, and no tie is declared', (() => { const r = contractReviewClaims([CL_ROW(CL_BODY(CL_LANE_SIG, CL_LINE(CL_LANE_SESSION)), 2, '2026-10-28T12:30:00Z'), CL_ROW(CL_BODY(null, CL_LINE(CL_SESSION)), 1, '2026-10-28T12:10:00Z')], CL_HEAD, { now: CL_NOW }); return r.holder.id === 1 && r.tie === false; })()); t('an UNSIGNED claim is the lane side — only a `triage#N` signature makes a claim triage\'s', contractReviewClaims([CL_ROW(CL_BODY(null))], CL_HEAD, { now: CL_NOW }).holder.side === 'lane'); t('⭐ the head MOVED past every claim ⇒ none; every claim on this head expired ⇒ lapsed; nothing holds either way', (() => { const moved = contractReviewClaims([CL_ROW(CL_BODY())], 'a'.repeat(40), { now: CL_NOW }); const old = contractReviewClaims([CL_ROW(CL_BODY())], CL_HEAD, { now: Date.parse('2026-10-28T14:19:00Z') }); return moved.state === 'none' && moved.holder === null && old.state === 'lapsed' && old.holder === null && old.claims[0].lapse === 'expired'; })()); @@ -2739,7 +2758,7 @@ export async function selfTest() { // A message: the addressee line, then the key line. const MSG = (line, to = 'domain:skills#2') => `To: ${to}\n${line}`; const DB = { - cr: MSG(`Request: contract-review · PR #26766 · head ${HO_HEAD} · ${CONTRACT_REVIEW_TIER_NAME} refused, HTTP 429`, 'triage'), + cr: MSG(`Request: contract-review · PR #26766 · head ${HO_HEAD} · ${CONTRACT_REVIEW_TIER_NAME} refused, HTTP 429`), ut: MSG('Request: urgent-triage · #27047 · main red on the merge group, deterministic', 'triage'), mr: MSG('Request: main-red · Test Core · anchor #27090 · suggested lane server', 'triage'), nc: MSG('Notify: contract-review PASS · PR #26766 · record 6450011111'), @@ -2772,7 +2791,7 @@ export async function selfTest() { t('⭐ every key of the table is named, as `Kind: key` in a code span, in the protocol text (`references/project-forms.md` 〈信箱 PR〉), and that section names NO other key — the table in both directions — and points at `--mailbox-keys` for the full spellings', (() => { const forms = readFileSync(new URL('../../.claude/skills/pm-dispatch/references/project-forms.md', import.meta.url), 'utf8'); const at = forms.indexOf('\n## 信箱 PR\n'); const section = at < 0 ? '' : forms.slice(at + 1).split(/\n## /)[0]; const named = new Set([...section.matchAll(/`(Request|Notify): ([a-z-]+)`/g)].map((m) => `${m[1]}: ${m[2]}`)); const table = new Set(MAILBOX_KEYS.map((k) => `${k.kind}: ${k.key}`)); return section !== '' && named.size === table.size && [...table].every((k) => named.has(k)) && section.includes('--mailbox-keys'); })()); t('⭐ every template, sent VERBATIM, is not an event — a placeholder (`#n`, `SHA`, `LANE#N`, `p0 or p1`, `TRIGGER_ID`, `ID`) never reads as one', MAILBOX_KEYS.every((k) => readMailboxLine(MSG(k.template)).state === 'unreadable')); t('⭐ `--mailbox-keys` prints exactly `mailboxKeyLines()` — every template, with its row under it — and exits 0', (() => { const r = faceCli(['--mailbox-keys']); return r.status === 0 && r.stdout === `${mailboxKeyLines().join('\n')}\n` && MAILBOX_KEYS.every((k) => r.stdout.includes(`${k.template}\n ${k.from} → ${k.to}; ${k.when}\n`)) && usageLines().join('\n').includes('--mailbox-keys'); })()); - t('decoration reads: a bulleted bolded key and a blockquoted message are the same event', (() => { const a = readMailboxLine(MSG(`- **Request:** contract-review · PR #26766 · head ${HO_HEAD} · quota exhausted`, 'triage')); const b = readMailboxLine(DB.sp.split('\n').map((l) => `> ${l}`).join('\n')); return a.state === 'read' && a.pr === 26766 && b.state === 'read' && b.id === 'notify:split' && b.to === 'domain:skills#1'; })()); + t('decoration reads: a bulleted bolded key and a blockquoted message are the same event', (() => { const a = readMailboxLine(MSG(`- **Request:** contract-review · PR #26766 · head ${HO_HEAD} · quota exhausted`)); const b = readMailboxLine(DB.sp.split('\n').map((l) => `> ${l}`).join('\n')); return a.state === 'read' && a.pr === 26766 && b.state === 'read' && b.id === 'notify:split' && b.to === 'domain:skills#1'; })()); t('the attribution block the platform APPENDS (bare or session-URL footer under a rule line) is not a second line', ['\n\n---\n_Generated by [Claude Code](https://claude.ai/code)_', `\n\n---\n_Generated by [Claude Code](https://claude.ai/code/${RECORD_SESSION})_`].every((tail) => readMailboxLine(`${DB.on}${tail}`).state === 'read')); t('⛔ free text, an acknowledgement and an empty comment are REFUSED, naming the line — the half-state the text names', (() => { const a = readMailboxLine(MSG('Seat note: picking up the review now, will report on the card.')); const b = readMailboxLine(MSG('收到,谢谢!')); const c = readMailboxLine(''); const bare = readMailboxLine('收到,谢谢!'); return a.state === 'refused' && /not a `Request:` or `Notify:` key line/.test(a.reason) && typeof a.line === 'string' && b.state === 'refused' && c.state === 'refused' && bare.state === 'refused'; })()); t('⛔ a key OUTSIDE the table is refused, whatever its kind — the vocabulary is closed — and the kinds are case-sensitive', ['Request: coffee · #27077', 'Notify: ack · #27077', 'Ack: #27077', 'request: urgent-triage · #27047 · x', 'Request: contract-review-done · PR #1', 'Notify: unblocked-soon · #1 · p1'].every((l) => readMailboxLine(MSG(l)).state === 'refused')); @@ -2862,7 +2881,7 @@ export async function selfTest() { const report = 'os-dev-report\n```json\n{"issue": 1}\n```'; t('`os-dev-report`: the marker reads with and without, and the newest report is found on a signed thread', hs.OS_DEV_REPORT_MARKER.test(report) && hs.OS_DEV_REPORT_MARKER.test(signed(report)) && hs.latestMarkedComment([{ body: signed(report), created_at: '2026-10-28T09:00:00Z' }], hs.OS_DEV_REPORT_MARKER) !== null); t('⛔ …while a marker on a LATER line, below anything but one signature, is still no report', hs.OS_DEV_REPORT_MARKER.test(`${SIG}\n## PM review\n\nos-dev-report\n`) === false && hs.OS_DEV_REPORT_MARKER.test(`${SIG}\n${SIG}\nos-dev-report\n`) === false); - t('`Contract-review-handoff:` reads the same newest-line verdict on a signed thread', (() => { const a = contractReviewHandoff([HO_ROW(HO_LINE())], HO_HEAD); const b = contractReviewHandoff([{ ...HO_ROW(HO_LINE()), body: signed(HO_ROW(HO_LINE()).body) }], HO_HEAD); return a.state === 'triage' && J(a) === J(b); })()); + t('`Contract-review-handoff:` reads the same newest-line verdict on a signed thread', (() => { const a = contractReviewHandoff([HO_ROW(HO_LINE())], HO_HEAD); const b = contractReviewHandoff([{ ...HO_ROW(HO_LINE()), body: signed(HO_ROW(HO_LINE()).body) }], HO_HEAD); return a.state === 'handed' && J(a) === J(b); })()); const claim = 'Claim: PM loop round 1 · 2026-10-28T12:44Z\nSession: `session_01QSeKYmhU5KEj1nukYhp1Tg`\nSeat: `domain:skills#2`\nBranch: `claude/issue-1-x`'; t('claim lines: the `Claim:` marker and the seat number read the same signed', hs.markerMatches(hs.CLAIM_COMMENT_MARKER, signed(claim)) && same(hs.claimSeatNumber, claim) && hs.claimSeatNumber(signed(claim)) === 2); t('release lines: the `Release:` marker reads the same signed', hs.markerMatches(hs.RELEASE_COMMENT_MARKER, 'Release: session_x · reason · back to pm:queue') && hs.markerMatches(hs.RELEASE_COMMENT_MARKER, signed('Release: session_x · reason · back to pm:queue'))); @@ -3119,7 +3138,7 @@ export async function selfTest() { + 'unreadable refused; the reviewer brief printed by `--brief-template` with its read-only shape; the mailbox ' + 'doorbell — `To: ` then each of the table\'s keys read, the table named both ways in the protocol text and printed by `--mailbox-keys`, and a key with no `To:`, a `To:` naming a login, free text, an acknowledgement, ' + 'a key outside the table, a second line and an over-long line refused; the review claim line — read signed and unsigned, ' - + 'spelled verbatim in the rule text, lapsing on a moved head or after two hours, the lane winning a tie; the review hold ' + + 'spelled verbatim in the rule text, lapsing on a moved head or after two hours, the earliest live claim holding; the review hold ' + 'line — read signed and unsigned, spelled verbatim in the rule text, per head and cleared only by a new head, an unreadable ' + 'line listed and voiding nothing, its 24-hour age a reading ' + 'that still holds; and the ONE '