Skip to content

pm-dispatch(mailbox): one mailbox PR per seat with a closed doorbell vocabulary; seat-online/offline notices; alternate pick for multi-seat lanes - #27143

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-27077-mailbox-per-seat-doorbell
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-27077-mailbox-per-seat-doorbell

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #27077
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:信箱 PR 从「每项目一个」改成「每席一个」(pm:mailbox+pinned,正文首行 To: LOGIN),信箱上只允许七种一行式门铃(3 种 Request、4 种 Notify),配四条防刷屏硬规则;坐席发 seat-online、收班发 seat-offline;多席车道按优先序交替取卡;record-recognisers.mjs 能读这七键,mailbox-open.mjs 开信箱时带 pinned 并把 To: 写在正文首行。
  • 为什么改:只有 PR 评论能唤醒订阅的会话,issue 上写什么都没人醒;之前跨席请求全靠对方下次巡查来「拉」(PR feat(adr-0087): retire a deleted published package whole (PACKAGE#*, D9 amendment) #26766 的契约复审等了 9 小时)。改成每席一个信箱后,席位之间可以互相按门铃「推」,而且铃声只带七种键与编号,不会演变成 AI 互聊。
  • 风险与回滚:纯协议文本与两个 PM 脚本,不碰产品包、门禁、workflow、队列守卫;回滚 = revert 本 PR。已测得 pinned 标签在本仓并不存在(404),首次开信箱时需要看标签是否落上(见 Acceptance notes)。
  • 先看:① references/project-forms.md 〈信箱 PR〉(七键与四规则,每行 ≤120 字节);② scripts/pm/record-recognisers.mjs 的 readMailboxLine(拒收面:表外键、第二行、超 200 字节);③ scripts/pm/mailbox-open.mjs 的 PR_LABELS 与 mailboxBody。
  • 两分钟验证:node scripts/pm/record-recognisers.mjs --self-test 预期末行 ✓ record-recognisers self-test: 254 cases pass (…);pnpm check:pm-skill-ratchet 预期四个受管文件各自 headroom 0 且绿。
  • 需要你做的:无,席位落地(Tier S:席位契约复核 PASS 后入队)。

What changes

One PR, six files, the four ruled sections A–E of the card:

  • A. One mailbox PR per seat — references/project-forms.md 〈信箱 PR〉: one draft PR per seat on mailbox/LOGIN, labels pm:mailbox + pinned, body first line To: LOGIN, opened at seating by mailbox-open.mjs and subscribed by the seated session, the seat post's stub pointer line carries its number, never merged, closed at hand-over or account switch with the successor opening a new one.

  • B. The closed doorbell vocabulary — the same section: the first line of a mailbox comment is one of the seven keys (three Request:, four Notify:), each key one line with its from → to and when; the four hard rules: one event one line ≤200 bytes keys-and-numbers only, anything outside the table is a half-state; no acknowledgements (the action is the receipt); no re-send and no nagging (the existing escalation rules take over); ≤5 per seat per round, p0 exempt, every send counts against the write pace; a mailbox comment is a colleague's statement, never an authorisation.

  • C. Alternate pick for multi-seat lanes — references/execution-duties.md 〈候选与批次〉, one line beside the take order: seat 1 takes the odd positions, seat 2 the even ones over the lane's total order; split is sent only to break the alternation; the claim timestamp stays the tie-breaker. Hot-file serial is untouched (its rule stands at L26 of the same section).

  • D. Online / offline — references/seat-lifecycle.md 〈座位贴协议〉, two lines: seating sends seat-online, the 收班简报 act sends seat-offline; a forced takeover's newcomer sends seat-online for the walled account (which can write nothing); a Routine seat cannot be woken, so its patrol reads the mailbox (the card's P4, said in the text). SKILL.md L74 is corrected to the maintainer's form: seats including triage are resident; an account out of tokens is replaced by one that reopens the seat and its mailbox; the triage seat's default tier and 「裁决归总监席」 stay.

  • E. Recogniser — scripts/pm/record-recognisers.mjs: MAILBOX_KEYS (the seven templates, spelled exactly as the protocol text prints them), readMailboxLine(body) (pure: the first line in, a read / refused / unreadable reading out — kind, key, numbers, head sha, login, lists) and mailboxEvents(rows) (each comment stamped with its id, created_at and author, plus the half-states by comment id). Exported beside contractReviewHandoff; no CLI flag; nothing fetched. ⛔ No gate, workflow or queue guard reads a mailbox — the battery pins that a doorbell comment beside a ## Contract review record leaves the locator's reading untouched.

  • Tooling — scripts/pm/mailbox-open.mjs: sends pinned with pm:mailbox and skip-changeset; writes To: LOGIN as the body's first line (the protocol text has said 正文首行 To: since the mailbox was born, while the tool wrote Mailbox-of: — one spelling now, the ruled one) and reads it back for refusal ①; the body names the seven-key rule; the close comment says hand-over or account switch. Refusal ① (one mailbox per login per repo) is unchanged: measured on the seat posts, a login holds one seat at a time and a lane's second seat is a second login, so per-login already equals per-seat — the card's open question answered by measurement, no seat name needed.

  • Companion registration (outside the claim's declared file surface — the gate's own remedy, named here as the bounded in-place rule requires) — the doorbell battery reads project-forms.md to pin the templates, which is a population claim on a governed file, and check:pm-dispatch-gates reds a new governed read until it is classified. Three mechanical edits, each the shape the gate prints: the self-test-reads declaration line in record-recognisers.mjs (line 77); the declared: true row for it in GOVERNED_READ_FLOOR (scripts/pm/dispatch-gates.data.mjs, with a docblock paragraph); and the ninth line of the live-declaration roster in scripts/pm/dispatch-gates.self-test.mjs (eight → nine). Measured both ways: before, check:pm-dispatch-gates failed 2 of 1935 (unpinned: record-recognisers.mjs::project-forms.md and check:pm-record-recognisers … (silent)); after, 1935 pass, and dispatch-gates --commands … project-forms.md alone derives pnpm check:pm-record-recognisers, keyed declared self-test read by scripts/pm/record-recognisers.mjs. No open PR touches either dispatch-gates file (file scan of the 32 open PRs, 2026-10-28).

Angle-bracket placeholders are spelled LOGIN / N in this body (the platform strips tag-shaped fragments); the files keep the corpus's mailbox/<login> spelling.

Line budget (every ceilinged file stays at its ceiling; check:pm-skill-ratchet green, headroom 0 on all four)

file before after ceiling
.claude/skills/pm-dispatch/SKILL.md 319 319 319
.claude/skills/pm-dispatch/references/project-forms.md 54 54 54
.claude/skills/pm-dispatch/references/seat-lifecycle.md 96 96 96
.claude/skills/pm-dispatch/references/execution-duties.md 188 188 188

Every new line is paid by deleting a line whose content has a home elsewhere — never by re-wrap. Prose lines are ≤120 bytes (wc -c), measured per line below.

project-forms.md — 〈信箱 PR〉 grows from 6 lines (heading, blank, 4 bullets) to 16 (heading, blank, 14 bullets): +10, paid by 10 deletions elsewhere in the file. The three old bullets of the section are rewritten in place (their facts survive in the new lines); the fourth (pm:mailbox PR 不欠 CI…, 67 B) is kept verbatim.

deleted line (bytes) home of its content
L9 车道席:认领、派发、PR、跨账号复审;总监席:决策箱与决裁批、发版、日报、总表。 (112 B) SKILL.md 入口与角色 table (lane and director rows) + lanes/director.md 职责一–四 headings
L10 分诊席:只写标签与审计评论;分诊与总监可召进任一项目,⛔ 不绑定项目。 (99 B) lanes/triage.md L4 (「由维护者一句召唤到任一项目…只写标签与审计评论」) + SKILL.md L55 (director: 任一车队项目可召)
L11 唯一不变量在账号不在项目:批准账号永不跑席位(SKILL.md 全体座位的不变量);本文不另加。 (120 B) SKILL.md 全体座位的不变量 L93 (「批准账号永不跑席位或作其关联用户」) — a self-declared restatement
L26 一张也没有 ⇒ 项目闲置;每席恰一条席位线程、恰一车道,一席一贴。 (93 B) same file L30 (每席开一条席位线程) + seat-post-protocol.md L9 (一席一贴) + coordinator.md §一 (每条车道开一个席位线程)
L29 协调者每次开场把指令行与座位贴对照:逐席按 seat-post-protocol.md 坐席或接管。 (106 B) coordinator.md §一 L10–L13 (lanes declared by the instruction line, seat the empty post, takeover per project-forms)
L32 总监席只被维护者召唤与 PR 订阅唤醒;摘要与收件箱扫描在召唤内做;见 lanes/director.md。 (118 B) lanes/director.md 授权与形态 (L7 人工召唤, L24 纯召唤制 ⛔ 无任何 routine, L12 入口); same file L31 keeps 「总监席不建」
L33 并发上限是维护者在该项目定的姿态;⛔ 不是协议常量,本文不写数字。 (95 B) coordinator.md §三 L23 (卡线程上限全项目一个数,由维护者按项目言明)
L38 撞墙账号写不出任何东西,接管永不等它的注记或简报。 (75 B) seat-lifecycle.md L29 (强制接管令…⛔ 不等原认领者注记) + execution-duties.md L83 (认领人不可达 ⇒ 接管…⛔ 不判死活); the section heading 「接管不等撞墙账号」 keeps the thesis
L39 跨账号 Release: 点名旧会话与出处三件;新 Claim: 带 Resume-from: BRANCH@SHA,新 dev 续旧分支。 (120 B) execution-duties.md 认领 ①–④ (L84–L86), which L40's pointer 「四件形状见 execution-duties.md 认领节」 already names
L54 读自座位贴、标签、assignee 与 PR 查询;生成视图 ⛔ 不手改(cross-repo-coordination.md 规则 5)。 (120 B) lanes/director.md L82 (车队总表随本摘要重生成…一席一行,⛔ 不手改) + SKILL.md L92 (状态只经标签、assignee、正文行与座位贴读写)

New 〈信箱 PR〉 lines, bytes: 107 · 115 · (67 kept) · 115 · 119 · 118 · 120 · 119 · 118 · 116 · 119 · 115 · 118 · 118. The seven key lines carry the card's spellings verbatim inside the code span; the from → to and when after the colon are compressed to fit the cap, and the seat-online / seat-offline rows carry only from → to because their when lives in seat-lifecycle.md (section D).

seat-lifecycle.md — +2 (115 B, 101 B) after the 收班 line of 〈座位贴协议〉, paid by: L47 接管/移交 = 改正文 + 审计评论(只作审计);收尾清单等细则见 references/seat-post-protocol.md。 (119 B) → home seat-post-protocol.md L27 and L33 (回收 / 当场 = 改正文 + 审计评论) and its 交接收尾清单 section; L48 epic 委托不入座位贴体系;新 packages/spec 工作恒归 spec 座位。 (82 B) → home same file L61 (新立 spec/公共契约 sub-issue 照旧转 spec 座位队列) + cross-repo-coordination.md (协议恒归 domain:spec).

execution-duties.md — +1 (115 B) after the 全序 lines of 〈候选与批次〉, paid by L37 文件面不相交只保证文本可合并;跨文件语义耦合由队列 CI 逮住,⛔ 不读作不可能冲突。 (117 B) → home AGENTS.md Multi-agent discipline §10 (「A clean merge is not a working merge … that second CI round is where joint breakage surfaces」).

SKILL.md — L74 replaced in place, 114 B → 116 B: 分诊席跑直连会话、默认判断档;直连会话按召唤跑,⛔ 无自醒不常驻;裁决归总监席。 → 分诊席与车道席常驻,断粮即换账号重开该席与信箱;分诊席默认判断档;裁决归总监席。. The line sits outside the regions check:pm-governed-prose (路径分叉) and check:skill-frame-sync (升级与决策) anchor; both green.

Tests

  • node scripts/pm/record-recognisers.mjs --self-test → exit 0, ✓ record-recognisers self-test: 254 cases pass (233 before + the new battery of 21; SELF_TEST_BATTERY_FLOOR 13 → 14). The battery reads one example of each of the seven keys (kind, numbers, head sha, login, lists, verdict), pins every template verbatim in project-forms.md and that the text lists exactly seven, tolerates the platform's appended footer and key decoration, and refuses: free text, an acknowledgement (收到,谢谢!), an empty comment, an eighth key of either kind, a lowercase kind, a line over 200 bytes, a second line of content, and a known key whose line does not spell its template (no head, 6-hex head, bad login, missing mailbox, missing lane, non-numeric record). mailboxEvents carries comment id / time / author and lists the half-states by comment id.
  • node scripts/pm/mailbox-open.mjs --self-test → exit 0, ✓ mailbox-open self-test: 45 cases in 8 batteries pass (two new pins: pinned is read off stale.yml's exempt-pr-labels and pm:mailbox is not in it; the body opens with To: and names the closed vocabulary; the open-plan battery floor 8 → 9).
  • node scripts/pm/check-governed-queue-guard.mjs --self-test → exit 0, 401 cases (it loads the recogniser through the real lazy import).
  • No ablation: no tested product behaviour changed (scripts and protocol text — ⛔ 不消融). No package build or pnpm test owed: the diff touches no package (turbo ls has no root package, so this is 「不在图上」, not 「动不了」).

Acceptance notes

  • pinned does not exist as a label on objectstack-ai/objectstack — GET /repos/objectstack-ai/objectstack/labels/pinned → 404 on 2026-10-28, and so does work-in-progress; of stale.yml's three exempt-pr-labels only security exists (200). The text makes pinned mandatory and the tool sends it, as ruled; whether the relay's labels leg (a POST to the pull's issue-labels path) creates a missing label is a platform reading the first mailbox open takes, not one this PR asserts (a write). scripts/pm/ensure-pm-labels.sh is where a label the fleet relies on is created — out of this card's file surface, left for the seat (carrier: seat). Dedupe words: pinned, exempt-pr-labels, stale.yml, work-in-progress.
  • The half-state patrol has no mailbox row. The text says 「表外即半态」 (as ruled); scripts/pm/check-half-states.mjs has no H-row reading a mailbox, and E forbids a gate reading one. The reading a patrol takes is mailboxEvents(rows).halfStates; wiring it into the report-only patrol is a follow-up, not a merge condition.
  • To: vs Mailbox-of: — the tool's Mailbox-of: body line is retired for the protocol's To: spelling: 0 pm:mailbox PRs are open (measured 2026-10-28), so there is no stored instance to migrate and no dual spelling to keep (创业阶段不渐进).
  • PR pm-dispatch: governed is an approval tier, the lane follows the subject — rule text to skills, workflow wiring to devx #27082 and docs(pm-dispatch): a card that touches cloud goes whole to the cloud PM #26749 both edit SKILL.md (L161/L162/L183 and L166) — region-disjoint from L74; neither had landed at 07d18e69 (this branch merged origin/main at that sha before opening; the merge brought nothing on this file surface and did not move the lockfile).
  • Not filed, noted: the routine line 首次坐席自建本项目的 routine… (project-forms L31) was kept because its 「分诊席加每小时裸卡轮」 clause has no other home; the 总表 section pays only one line because its objectstack 置顶 issue「车队总表」 fact has no other home.

Verification

Local subset = the derivation node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the final head (no paths passed; change set from the merge base 07d18e69: 8 paths, 0 uncommitted), 41 families, every command run in the foreground with its exit captured before any pipe and reconciled with --ran (ran-file lines carry :: exit N). Every family CI runs rides the PR's check-runs (Lint & Repo Gates carries the check:* set); they are not re-run here beyond that subset and read in_progress at report time.

Head d82ca533. --ran verdict: ✓ dispatch-gates --ran: 41 derived famil(ies) accounted for — 40 run, 1 NOT-MEASURED (0 DERIVED from a recorded exit 3, 1 claimed)..

41 derived families on head d82ca53 — command · exit (captured before any pipe) · wall
command exit wall
node scripts/check-closing-keyword-parity.mjs 0 3s
node scripts/check-closing-keyword-parity.mjs --self-test 0 7s
node scripts/check-comment-mask-corpus.mjs 0 200s
node scripts/check-declaration-mirrors.mjs 0 0s
node scripts/check-declaration-mirrors.mjs --self-test 0 0s
node scripts/check-self-test-wired.mjs 0 2s
node scripts/check-self-test-wired.mjs --self-test 0 5s
node scripts/check-self-test-workflow-commands.mjs 0 158s
node scripts/check-self-test-workflow-commands.mjs --self-test 0 4s
node scripts/check-whole-set-label-write.mjs 0 2s
node scripts/check-whole-set-label-write.mjs --self-test 0 0s
node scripts/ci/plan-test-core-shards.mjs --self-test 0 0s
node scripts/pm/bare-root-worklist.mjs --self-test 0 33s
node scripts/pm/check-governed-queue-guard.mjs --self-test 0 1s
node scripts/pm/check-harness-current.mjs --self-test 0 7s
pnpm --filter @objectstack/lint run check:doc-formula-expressions 3 (PREREQUISITE NOT MET: @objectstack/lint/formula unbuilt — nothing measured, NOT MEASURED; CI builds them) 1s
pnpm check:agent-test-spelling 0 4s
pnpm check:bash32-floor 0 2s
pnpm check:ci-filter-parity 0 2s
pnpm check:cli-command-ids 0 17s
pnpm check:cross-package-test-inputs 0 40s
pnpm check:doc-authoring 0 7s
pnpm check:driver-memory-census 0 6s
pnpm check:entry-guard 0 45s
pnpm check:gitlink-declared 0 1s
pnpm check:nul-bytes 0 5s
pnpm check:parse-guard 0 3s
pnpm check:pm-dispatch-gates 0 416s
pnpm check:pm-expected-skips 0 6s
pnpm check:pm-governed-merges 0 10s
pnpm check:pm-governed-prose 0 1s
pnpm check:pm-record-recognisers 0 5s
pnpm check:pm-skill-id-lint 0 1s
pnpm check:pm-skill-ratchet 0 1s
pnpm check:pnpm-filter-targets 0 3s
pnpm check:pnpm-flag-spellings 0 7s
pnpm check:ratchet-remedy-authority 0 5s
pnpm check:refd-timer-probe 0 25s
pnpm check:scripts-symbol-anchors 0 9s
pnpm check:skill-frame-sync 0 1s
pnpm check:watch-hint-literal 0 7s

Local-only readings (CI runs their --self-test only): node scripts/check-skill-frame-freshness.mjs --no-fetch → exit 0 (0/1 framework files are byte-identical to the ref (wording differences are not drift) — the frame itself is unchanged; L74 is outside it). node scripts/pm/mailbox-open.mjs --self-test → exit 0, 45 cases (not a derived family: no check: script names it; run because the diff edits it). node scripts/pm/check-governed-queue-guard.mjs --self-test → exit 0, 401 cases.

No package is touched, so there is no build closure (①), no package test (②) and no changeset: .claude/** and scripts/pm/** publish nothing (skip-changeset declared on the create). No dogfood: no ! in the title and no (narrowing) arm.


Generated by Claude Code

…rbell vocabulary, seat-online/offline notices, alternate pick for multi-seat lanes

project-forms.md 〈信箱 PR〉 carries the per-seat shape (pinned + pm:mailbox, body first line To: LOGIN,
opened at seating and closed at hand-over or account switch), the seven first-line keys and the four
anti-chatter rules; seat-lifecycle.md 〈座位贴协议〉 the seat-online / seat-offline acts; execution-duties.md
〈候选与批次〉 the alternate pick; SKILL.md 入口与角色 the maintainer's form of the seats (resident, an
account out of tokens is replaced by one that reopens the seat and its mailbox). Every ceilinged file
stays at its ceiling: new lines are paid by deleting lines whose content has a home elsewhere.

record-recognisers.mjs reads the seven keys (readMailboxLine, mailboxEvents, MAILBOX_KEYS) beside the
records it already reads, with a 21-case battery that pins each template verbatim in the protocol text;
mailbox-open.mjs sends pinned, writes To: LOGIN as the body's first line and names the closed vocabulary.
No gate, workflow or queue guard reads a mailbox.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg
…d and register it in the dispatch-gates census

The doorbell battery reads project-forms.md to pin the seven key templates verbatim — a population claim
on a governed file. check:pm-dispatch-gates reds a new governed read until it is classified: the read is
declared (self-test-reads marker in record-recognisers.mjs), GOVERNED_READ_FLOOR carries its row
(declared: true) and the live-declaration roster of the self-test gains its ninth line. Measured: a change
set holding project-forms.md alone now derives pnpm check:pm-record-recognisers, keyed on the declaration.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d82ca533ad474071d25e6ba22d9cf0fa6c695879
Local-runs: probe — the seat ran the branch's own self-tests from a git archive copy of the head (no checkout, no edit, no network) to confirm the dev's counts: record-recognisers.mjs --self-test 254 cases, mailbox-open.mjs --self-test 45 cases in 8 batteries, check-governed-queue-guard.mjs --self-test 401 cases, all exit 0; the ratchet and id-lint were not runnable from the archive (no .git, no skills/) and are read off the head's check-runs instead
Scope: .claude/skills/pm-dispatch/SKILL.md (L74), references/project-forms.md (〈信箱 PR〉 and the ten paid lines), references/seat-lifecycle.md (two lines), references/execution-duties.md (one line in, one out), scripts/pm/record-recognisers.mjs, scripts/pm/mailbox-open.mjs, scripts/pm/dispatch-gates.data.mjs, scripts/pm/dispatch-gates.self-test.mjs + .github/workflows/stale.yml (exempt-pr-labels, read), card #27077 sections A–E and triage's first grade 6461033102

Read at 2026-10-28T08:18Z by the domain:skills#2 seat on the fetched head (merge-base 07d18e69): eight files, +331/−46, 377 changed lines ≤ 3000. Tier S (.claude/** + non-gate scripts/pm/**; check-governed-merges --pr 27143 lists the four .claude paths and no Tier H path). The model tier for this surface is the ceiling tier (dispatch-gates --tier: MANDATORY by SKILL.md), and this review is served at CONTRACT_REVIEW_TIER.

① Derived judgments

  • Sections A–E of the card land where the claim said, in place: 〈信箱 PR〉 is one mailbox PR per seat (pm:mailbox+pinned, first line To: <login>, opened at seating by mailbox-open.mjs, closed at hand-over or account switch with the successor opening a new one); the seven first-line keys are spelled as the card prints them (three Request:, four Notify:), each with from → to and when; the four anti-chatter rules are there (one event one line ≤200 bytes keys-and-numbers; no acknowledgements or re-sends; ≤5 per seat per round with p0 exempt, counted against the write pace; a mailbox comment is a colleague's statement, never an authorisation, pointing at untrusted-text.md); E's prohibition (⛔ no gate, workflow or queue guard reads a mailbox) is the section's last line. Triage's ⚠️ (keep the vocabulary CLOSED) holds: seven keys in the text, seven in MAILBOX_KEYS, and the reader refuses an eighth by construction.
  • SKILL.md L74 now reads 「分诊席与车道席常驻,断粮即换账号重开该席与信箱;分诊席默认判断档;裁决归总监席。」 (116 B) — the maintainer's own words 「分诊席 和每个车道通常是长期开着的,只有账户没token了,会切换账户重开」 in the protocol's register; 裁决归总监席 kept; the retired clause (summoned-not-resident) contradicted those words and is gone. Outside the governed-prose and frame-sync regions (the frame at L242–262 is byte-identical; check:skill-frame-sync green in the dev's run).
  • Line budget, measured on the head: SKILL.md 319/319, project-forms 54/54, seat-lifecycle 96/96, execution-duties 188/188 (headroom 0 all four, as before); every new prose line ≤120 bytes (wc -c on the fourteen 〈信箱 PR〉 lines: 107 … 120); the ten paid lines in project-forms, two in seat-lifecycle and one in execution-duties each name a home, and the seat spot-read six of those homes on the head (SKILL.md L93, lanes/triage.md L4, coordinator.md L23, lanes/director.md L82, seat-post-protocol.md L27/L33, execution-duties.md L83–86) — present. No re-wrap used as currency: each paid line is a deletion of content with a home.
  • record-recognisers.mjs: MAILBOX_KEYS (template + one pure reader per key), readMailboxLine (first line only; refuses a second line, a key outside the table, >200 bytes), mailboxEvents (rows → events + halfStates), exported beside contractReviewHandoff; a 21-case doorbell battery (seven accepts, refusals, and a verbatim pin of each template against 〈信箱 PR〉), SELF_TEST_BATTERY_FLOOR 13 → 14. The template pin reads a governed file in a self-test, which check:pm-dispatch-gates requires to be DECLARED — hence the three companion edits below. Correct, and the right shape (text and reader cannot drift apart silently).
  • mailbox-open.mjs: pinned joins PR_LABELS (STALE_EXEMPT_LABEL, read off stale.yml's exempt-pr-labels by a new pin; pm:mailbox is confirmed NOT exempt), the body opens with To: <login> (the protocol's spelling since the mailbox's birth; the tool's Mailbox-of: dialect retired, no stored instance carries it — 0 pm:mailbox PRs open, read in this act), the body names the closed vocabulary; refusal ① unchanged because per-login = per-seat (a login holds one seat at a time — the seat's own roster agrees). The open-plan battery 8 → 9, two new pins. Correct.

② Semver level

  • None owed: protocol text and PM scripts; no package touched. Clause-②: no, Metadata-protocol: none on the PR body match the claim; no Skills-docs: line owed. skip-changeset correct.

③ Boundary flags

  • File surface breached by three mechanical companion edits, accepted under the bounded in-place rule (the gate's own remedy for this diff; form pinned by the gate's failure text; no open PR holds either file — the dev's scan of 32 open PRs, and the seat's hot-file queue names none; the family check:pm-dispatch-gates was already derived for the card): the self-test-reads declaration line in record-recognisers.mjs (L77), the fifth GOVERNED_READ_FLOOR row (declared: true) in dispatch-gates.data.mjs with its docblock, and the ninth live-declaration roster row in dispatch-gates.self-test.mjs (eight → nine, the length pin moved with it). Measured by the dev: 2 of 1935 red before, 1935 pass after. The seat amends the claim's file surface by this record.
  • The pinned label does not exist on objectstack-ai/objectstack (GET /labels/pinned → 404 in this act; work-in-progress 404; security 200), while stale.yml's exempt-pr-labels names all three and this PR's text makes pinned mandatory on a mailbox. Whether the relay's labels leg creates a missing label is a platform reading the first mailbox open takes; the designed place to create a label the fleet relies on is scripts/pm/ensure-pm-labels.sh. Class b (the exemption is dead for two of its three names until the labels exist) — filed by the seat as a separate finding card, see the ACCEPT; not widened into this PR.
  • check-half-states.mjs gained no row: the card's 「表外即半态」 is the reading mailboxEvents(rows).halfStates returns; E forbids a gate reading a mailbox, and the half-state patrol is report-only, so adding its row is a later, separate decision — noted, not owed here.
  • check:doc-formula-expressions NOT MEASURED locally (two packages unbuilt in the dev's worktree; the gate's subject is unrelated); CI's Lint lane builds and runs it — read off the head's check-runs in the ACCEPT.
  • Hot files: pm-dispatch: governed is an approval tier, the lane follows the subject — rule text to skills, workflow wiring to devx #27082 (SKILL.md L161/L162/L183) and docs(pm-dispatch): a card that touches cloud goes whole to the cloud PM #26749 (SKILL.md L166, execution-duties L175) are region-disjoint from L74 / execution-duties L37 and L54 — confirmed on the head after the dev's merge of origin/main 07d18e69; post-stamped: every post's first line is a signature <lane>#<n> · <account> · <session>, and a post without one is refused #27113 and pm-dispatch(contract-review): the lane reviews its own PRs; triage takes only a review nobody is doing, and one claim line per head keeps two reviewers off the same head #27062 share record-recognisers.mjs and are serialized behind this PR on the seat post (r70).
  • Author objectstack-fleet[bot] through the relay; mcp_calls: 0; the PR is draft, no flip by anyone yet.

④ Design judgment

  • Against the maintainer's words (「车道之间可以互通」 but 「避免 ai 互聊信息太多爆炸…只沟通必要的信息」, seats long-lived and re-opened on an account switch): the design gives every seat a doorbell that wakes a session (a PR comment), restricts what can be rung to seven typed one-liners, forbids acknowledgements and re-sends, caps the rate, and keeps the mailbox out of every gate and ledger — the push channel the maintainer asked for with the chatter structurally impossible rather than discouraged. The reader lives in the one recogniser module the fleet already uses and pins the text, so the vocabulary cannot widen in code without failing a self-test. Against the four-axis frame: focus (closed set, no eighth key), AI-error (refusal over tolerance), long-run (one protocol file, one reader). Holds. Residual: the first mailbox open is the end-to-end measurement of the labels leg with a non-existent pinned; the filed card removes that unknown before any seat relies on the stale exemption.

Implemented-by: claude/issue-27077-mailbox-per-seat-doorbell
Reviewed-by: session_01QSeKYmhU5KEj1nukYhp1Tg

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 — 席位终稿 · PR #27143 · head d82ca533 · domain:skills#2 · 2026-10-28T08:22Z

  • 改了什么:信箱 PR 从「每项目一个」改为「每席一个」(pm:mailbox+pinned,正文首行 To: <login>,坐席即开、交接或换账号关旧开新);信箱评论首行只许七种门铃键(3 种 Request:、4 种 Notify:),配四条防刷屏硬规则(一事一行 ≤200 字节只写键与编号;无收到/谢谢/催;每席每轮 ≤5 条、p0 豁免;信箱评论永非授权);坐席发 seat-online、收班发 seat-offline;多席车道按全序交替取卡;SKILL.md L74 改成你说的形态(席位常驻、断粮换账号重开);record-recognisers.mjs 读七键并把每个模板逐字钉在协议文本上;mailbox-open.mjs 开信箱带 pinned、首行写 To:。
  • 为什么改:你的裁决(「车道之间可以互通」「只沟通必要的信息」「分诊席和每个车道通常是长期开着的」「同意 信箱改每席一张」):只有 PR 评论能唤醒订阅会话,席位之间需要能「推」,但铃声必须是封闭的七种一行式,结构上防 AI 互聊。
  • 风险与回滚:纯协议文本 + 两个 PM 脚本,不碰产品包、门禁、workflow、队列守卫;四个受管文件行数上限不变(319/54/96/188,headroom 0),付行均为有家可归的删行(PR 正文逐行列出家)。回滚 = revert。一处已知缺口:pinned 标签本仓不存在(404),首次开信箱是第一次实测 —— 已立 ensure-pm-labels.sh: create the pinned label that stale.yml exempts and the per-seat mailbox now requires (404 today; work-in-progress 404 too) #27151 让 ensure-pm-labels.sh 建它。
  • 先看:① references/project-forms.md 〈信箱 PR〉的七键与四规则;② SKILL.md L74 新句;③ record-recognisers.mjs 的 readMailboxLine 拒收面。
  • 两分钟验证:node scripts/pm/record-recognisers.mjs --self-test 预期 254 cases pass;node scripts/pm/mailbox-open.mjs --self-test 预期 45 cases in 8 batteries pass;pnpm check:pm-skill-ratchet 四文件各 headroom 0 绿。
  • 需要你做的:无 —— Tier S,席位契约复核 PASS(6464123012)后由席位翻 ready + 挂 auto-merge 入队;若本会话分类器再次拒绝该落地笔(如 pm-dispatch: governed is an approval tier, the lane follows the subject — rule text to skills, workflow wiring to devx #27082),席位停手并报你。
  • 席位意见:ACCEPT。三处越面(声明行、GOVERNED_READ_FLOOR 第五行、自测花名册第九行)是门禁对本 diff 的自身处方,席位以本记录修正认领的文件面;dev 对 mailbox-open.mjs 的处理(拒收①不改、To: 取代工具方言)实测有据。

席位核对 — ACCEPT · domain:skills#2 · session_01QSeKYmhU5KEj1nukYhp1Tg · 2026-10-28T08:22Z

  1. 形态:draft → main,首行 Fixes #27077,Clause-②: no、Metadata-protocol: none 行首在;八个文件 +331/−46(377 行 ≤ 3000);assignee os-justin;skip-changeset 正确;documentation、size/m 为他方 bot 所加,不动。
  2. 前提实测:席位在取回的 head(merge-base 07d18e69)上核 —— 四受管文件 319/54/96/188;〈信箱 PR〉十四行各 ≤120 字节;十三条付行之家抽读六处皆在;L74 新句 116 B 含「裁决归总监席」;七键文本与 MAILBOX_KEYS 一一对应;stale.yml exempt-pr-labels 含 pinned 不含 pm:mailbox;0 个 pm:mailbox PR 开着(Mailbox-of: 方言无存量)。
  3. 门禁 check-runs(head d82ca533,本笔读数 2026-10-28T08:22Z):45 check-runs,26 success · 19 skipped · 0 in progress · 0 failure(mergeable_state: clean draft)。dev 本地派生 41 族:40 run exit 0、1 NOT-MEASURED(check:doc-formula-expressions,前置包未建,CI 已跑);席位从归档副本复跑三项自测 254/45/401 皆 exit 0。
  4. 接受的偏差:① 文件面越界三处机械伴随改动(record-recognisers.mjs L77 声明行、dispatch-gates.data.mjs 第五行 declared: true、dispatch-gates.self-test.mjs 花名册八→九),门禁自身处方、无其它 open PR 持有这两个文件;② mailbox-open.mjs 为 A 的裁定形态而改(非为拒收①);③ check-half-states.mjs 未加行(E 禁门禁读信箱,半态读数由 mailboxEvents().halfStates 提供);④ seat-online/offline 的「何时」落在 seat-lifecycle.md 两行而非表内(120 字节上限)。
  5. 范围外去向:filed ensure-pm-labels.sh: create the pinned label that stale.yml exempts and the per-seat mailbox now requires (404 today; work-in-progress 404 too) #27151(pinned 标签 404,ensure-pm-labels.sh 建之;finding+tooling,待分诊定级);Acceptance notes:pm-dispatch(contract-review): the lane reviews its own PRs; triage takes only a review nobody is doing, and one claim line per head keeps two reviewers off the same head #27062/pm-dispatch(p0): a lane's p0 card is dispatched at once — no batch, depth-wait or concurrency cap holds it, and lower-priority dev work pauses to make room; main or merge-queue red is graded p0 at first touch #27047 与本 PR 共用 record-recognisers.mjs,后落者先合 origin/main(已在座位贴 r70 热文件队列)。Skills-docs: 不欠(kind none)。
  6. 落地条款:Tier S;契约复核 PASS 评论 6464123012(head d82ca533);全绿即席位经中继 pr_ready + automerge_enable 落地(分类器若拒即停并报维护者,记卡);MERGED 后关卡摘 pm:dispatched,随后派发 post-stamped: every post's first line is a signature <lane>#<n> · <account> · <session>, and a post without one is refused #27113。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 12:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 28, 2026 12:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit d63f24a Oct 28, 2026
46 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-27077-mailbox-per-seat-doorbell branch October 28, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants