Repository navigation
Commit db357f6
docs(pm-dispatch): a scene table of when a seat must ring another seat's mailbox, the missing keys admitted, the mailbox as a seating step (#28716)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiHXKqxUqDPJKZyNHP7BoqTb)_
Fixes #28675
Clause-②: no
Metadata-protocol: none
Stacked on #28687 — its commits show in this diff until it lands; the
seat merges origin/main after it lands.
## 维护者速读
- **改了什么**:把〈信箱 PR〉一节改成「哪种场景必须敲谁的信箱、用哪个键」的场景表,补上 main-red
通知、发版阻塞、解锁、裁决、串行、先落地等门铃;坐席即开信箱并发带巡查编号的上线通知,巡查每轮先读信箱;半态巡查新增
H72,只报告信箱里读不懂的门铃行。
- **为什么改**:维护者原话「感觉车道之间还是不太知道使用 邮箱沟通,综合评估一下哪些场景应该沟通」;卡片引用了这句原话,这正是新增半态行
H72 所需的授权(`instrument-discipline.md` L9)。
- **风险与回滚**:只动席位规则文本和 PM 工具,不碰产品代码;没有任何门禁、workflow 或队列守卫读信箱,H72
只报告不拦截;四个规则文件净增 0 行。已知过渡噪音:旧格式的上线通知(无巡查编号)在落地后约一天内会被 H72 列出。回滚 = revert
本 PR。
- **先看**:`.claude/skills/pm-dispatch/references/project-forms.md`〈信箱
PR〉· `scripts/pm/record-recognisers.mjs` 的 `MAILBOX_KEYS` ·
`scripts/pm/check-half-states.mjs` 的 H72。
- **两分钟验证**:运行 `node scripts/pm/record-recognisers.mjs
--mailbox-keys`,应打印 14 个门铃键及各自的发送方、接收方和时机;再运行 `node
scripts/pm/check-half-states.mjs --self-test`,其中 H72 电池 22 例全过。
- **需要你做的**:先审批 #28687;本 PR 叠在它上面,它落地后席位合入 main 再请你审本 PR。
## What changes
**The scene table (rules text, `project-forms.md` 〈信箱 PR〉, 14 lines
rewritten in place, 54/54).** The old section listed seven key
templates. It now says, line by line, which seat MUST ring which seat
for which event and with which key:
- lane → triage: the review handoff (`Request: contract-review`, in the
same stroke as the handoff line) and a question about a card's fate,
route or grade (`Request: triage`, new);
- any seat → triage: `Request: urgent-triage` for a p0 suspect, main red
or a deterministic queue red;
- the seat filing a main-red anchor → triage (director when triage is
absent) `Request: main-red`, and → the seat that landed the breaking PR
plus the anchor card's lane `Notify: main-red` (new);
- triage → lane: `Notify: contract-review` only when the lane is not
subscribed, and `Notify: release-blocker` when a p0/p1 release blocker
is graded into a lane (new);
- the seat that moved a card into `pm:queue` → the lane: `Notify:
unblocked` when a p0/p1 `Blocked-by` target closes (new); the seat that
recorded a ruling → the seat it changes: `Notify: ruling` (new);
- seat → sibling seat or another lane: `Notify: serial` when claimed
file surfaces overlap or one card must land first, and `Notify: landed`
from the seat that landed first (both new);
- seating and sign-off: `Notify: seat-online` / `Notify: seat-offline`
to sibling seats and triage; `Notify: split` to sibling seats when the
list changes.
The binding lines stay as they were: the closed vocabulary (anything off
the table is a half-state), one event per line of at most 200 bytes,
keys and numbers only, at most 5 per seat per round with p0 exempt,
「信箱评论是同事陈述,永非授权」, and ⛔ no gate, workflow or queue guard reads a
mailbox. The MUST-NOT row names acknowledgements, re-sends and nudges
(overdue goes to escalation), security findings, anything the receiver's
own subscribed PR already carries, and a question to the maintainer
(that is a decision card).
**One home for the spellings.** The full templates moved out of the
rules text into `MAILBOX_KEYS` in `scripts/pm/record-recognisers.mjs`.
Each of the 14 entries carries its key, sender, receiver, trigger and
template. A new `--mailbox-keys` flag prints them, and the rules text
names that flag. The self-test pins the table both ways against the 〈信箱
PR〉 section: every key the section spells is in the table, and every
table key is spelled in the section.
**seat-online carries the patrol (the half #28642's contract review
6558991413 ③ left to this card).** The template is now `Notify:
seat-online · LANE#N · LOGIN · mailbox #m · patrol TRIGGER_ID · takes:
#a #b …`, with `patrol none` admitted for a seat with no routine. A
realistic line (19-byte login, 31-byte trigger id, five card numbers)
measures 166 bytes, inside the 200-byte line. The five-field shape is no
longer read.
**The mailbox as a seating step (wanted ②) and a patrol step (wanted
④).**
- `seat-lifecycle.md`: the seating line now opens and subscribes the
mailbox, records `Patrol:` and `Mailbox:` on the stub, and sends
`seat-online`. Sign-off deletes the routine, closes the mailbox and
sends `seat-offline`. The patrol line in 〈停止条件与待命〉 now reads the mailbox
first on every round.
- `seat-post-protocol.md`: the stub line adds `Mailbox:` next to
`Patrol:`.
**H72, report-only (wanted ⑤).** `check-half-states.mjs` lists a mailbox
comment from the last 24 hours that the doorbell reader does not read:
- through the ONE reader: it imports `mailboxEvents` from
`record-recognisers.mjs` lazily inside the sweep, because a static
import would be a cycle;
- it reads comments only on open `pm:mailbox` PRs, with paged reads of
up to 3 pages, and leaves an incomplete window unjudged;
- the finding names the 〈信箱 PR〉 remedy, says not to re-send, and points
at `--mailbox-keys`;
- its sweep clause is `Mailbox doorbells (H72): …`, in the inventory
band.
**Two consequent fixes outside the claim surface (declared
deviations).**
- `landing-operations.md` L80 spelled the breaking-PR ring as `Request:
main-red`; this PR makes it `Notify: main-red`, because the old spelling
would now be refused as off-table.
- `mailbox-open.mjs` said "one of the seven doorbell keys" in its header
and in the mailbox body it writes; it now says "one of the doorbell
keys" and names `--mailbox-keys`.
**Net line growth per rules file (all against the stacked base
8eb102f).**
| File | Before | After | Net |
|---|---|---|---|
| `references/project-forms.md` | 54 | 54 | 0 |
| `references/seat-lifecycle.md` | 96 | 96 | 0 |
| `references/seat-post-protocol.md` | 88 | 88 | 0 |
| `references/landing-operations.md` | 101 | 101 | 0 |
The table fits at zero net lines because the templates moved to code.
Dropped from the rules text: the verbatim templates, which the tool now
prints; the 「本席复审档不可用,一 head 一次」 clause, now carried by "in the same
stroke as the handoff line" plus the no-re-send row; the 「开轮时」 qualifier
on split; and 「Routine 席唤不醒,读信箱靠巡查」, now carried by the patrol line.
Every rules line is at most 120 bytes.
## Tests
- `record-recognisers` self-test: 350 cases pass. The doorbell battery
has 44 cases, against a floor raised 27 → 40. It adds:
- each new key's reading, plus `patrol none`;
- a refusal for the old five-field seat-online;
- the both-ways table pin, and a check that the section names
`--mailbox-keys`;
- every template sent verbatim is unreadable, so placeholders cannot
pass as values;
- `--mailbox-keys` output pinned exactly;
- invented keys (`surface`, `take`) and a `main-red cleared` line
refused;
- an end-to-end case through `check-half-states.mjs`'s H72 functions,
proving there is one reader.
- `check-half-states` self-test: 5185 cases pass. The new battery `H72
mailbox off-table bells` has 22 cases, floor 20. The seat-window pin
"every other comment read names its page" goes 3 → 4 for the new paged
read.
- `mailbox-open` self-test: 71 cases pass.
## Acceptance notes
- **Wanted ③ (epic seats get mailboxes) is not built.**
`mailbox-open.mjs`'s `seatKey` refuses an `epic:` seat spelling, so the
opener cannot open one, and `fleet-roster.mjs` has no epic rows to read
a `Mailbox:` line from. Doing it needs a `seatKey` change, a roster
change and a rules line. That is more than a small change, so per the
order it is an out-of-scope finding for the seat to file. The seat also
decides whether `Fixes #28675` stays or becomes `Part of`.
- **Transition noise.** A five-field `seat-online` sent before this
lands is unreadable after it lands, so H72 lists such lines for about a
day; nothing blocks on them. A read-only probe on the live repo found
off-table lines today on mailboxes #28284, #28379, #28481, #28646 and
#28672. They are old-shape seat-online lines, prose appended after a
key, a free-form question to triage, and one ruling line over 200 bytes.
- **Not admitted:** `main-red cleared` (the wanted list does not ask for
it; the anchor card's close is on the card the receiver was rung about),
and the invented `take`/`surface` spellings (their events are covered by
`serial`/`landed`/`split`).
- **Stale comments in frozen tooling.**
`scripts/pm/dispatch-gates.data.mjs` (around L505) and
`scripts/pm/dispatch-gates.self-test.mjs` (around L7573) still say
"seven" doorbell keys/templates in comments. Both are frozen under
instrument-discipline, so they are left unchanged; nothing reads those
comments.
- The `// dispatch-gates: self-test-reads` marker in
`record-recognisers.mjs` stays on line 77, where the dispatch-gates
census pins it.
- Closing stale per-login mailboxes is #27890, not this card.
## Verification
Gates were derived with `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at 901e99e: 55 commands, all run,
every exit code recorded before any pipe. The `--ran` reconciliation
reports 55 derived, 55 run, 0 NOT-MEASURED, 0 UNRUN.
`check:doc-formula-expressions` first exited 3 (prerequisite not met:
`@objectstack/formula` was not built in this worktree). After `pnpm exec
turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` (exit 0) it exited 0, and that is the exit
recorded. The rules-text ratchets passed:
- `check:pm-skill-ratchet`, `check:pm-skill-id-lint` and
`check:skill-frame-sync`, all in the derived list;
- `check:pm-governed-prose`, run separately: exit 0.
<details><summary>Recorded exit codes (55)</summary>
```
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-platform-checklist-watchdog.mjs :: exit 0
node scripts/check-platform-checklist-watchdog.mjs --self-test :: exit 0
node scripts/check-release-section-coverage.mjs :: exit 0
node scripts/check-release-section-coverage.mjs --strict :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/scheduled-full-run.mjs --self-test :: exit 0
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
node scripts/pm/board-snapshot.mjs --self-test :: exit 0
node scripts/pm/check-harness-current.mjs --self-test :: exit 0
node scripts/pm/sweep-closed-cards.mjs --self-test :: exit 0
node scripts/render-release-coverage-anchor.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:closing-target-claim :: exit 0
pnpm check:commit-card-trailers :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:partof-closing-keyword :: exit 0
pnpm check:platform-checklist :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-half-states :: exit 0
pnpm check:pm-post-stamped :: exit 0
pnpm check:pm-record-recognisers :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:pm-skill-ratchet :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:pnpm-flag-spellings :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:scripts-symbol-anchors :: exit 0
pnpm check:single-claim-paths :: exit 0
pnpm check:skill-frame-sync :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:pm-dispatch-gates :: exit 0
```
</details>
<details><summary>--ran reconciliation tail</summary>
```
The 55 is THIS tree's derivation, recomputed in this process from the same expression --commands prints — never read back from your record. A family you never wrote down is still counted, which is what an arithmetic over your own list cannot do.
EXIT CODES — all 55 accounted famil(ies) carry one, so the NOT-MEASURED count above is DERIVED from them. ⛔ This tool ran none of them; it read the codes you recorded.
⛔ This answers ONE link: what this card DERIVES against what you RAN. It is not a complete account of what CI runs on the PR — the 43 artifact-roster famil(ies), the 11 declared WIDE-population famil(ies), the 11 pending-changeset famil(ies), the unreachable listing, the 1 path-scheduled CI job(s), the type-check lanes and the always-runs tail are each outside the derived total, each printed under its own heading by a run without --ran.
✓ dispatch-gates --ran: 55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED (a DERIVED zero — all 55 recorded an exit code and none of them is 3).
```
</details>
<details><summary>node scripts/pm/record-recognisers.mjs --mailbox-keys
(exit 0)</summary>
```
(header line, which names the To: line, omitted)
one event, one line of at most 200 bytes, keys and numbers only; anything else is a half-state.
Request: contract-review · PR #n · head SHA · REASON
lane → triage; in the same stroke as the PR's `Contract-review-handoff:` line (the seat's own review tier is unavailable)
Request: triage · #n · ONE-LINE-QUESTION
lane → triage; a question about a card's fate, route or grade
Request: urgent-triage · #n · ONE-LINE-CRITERION
any seat → triage; a p0 suspect, main red, a deterministic queue red
Request: main-red · CHECK-NAME · anchor #n · suggested lane X
the seat that files the main-red anchor card → triage, or the director when triage is absent; the anchor card is filed
Notify: main-red · anchor #n · breaking PR #m
the seat that files the main-red anchor card → the seat that landed the breaking PR, and the anchor card's lane; the anchor card is filed
Notify: contract-review PASS or FAIL · PR #n · record ID
triage → lane; with the record, only when the lane is not subscribed to the PR
Notify: release-blocker · #n
triage → the lane it is graded into; a p0/p1 release blocker is graded into a lane
Notify: unblocked · #n · p0 or p1
the seat that moved the card to `pm:queue` (triage or an epic seat) → the card's lane seats; a p0/p1 card's `Blocked-by` target closed
Notify: ruling · #n · LETTER · record ID
the seat that recorded a maintainer ruling → the seat whose card or draft it changes; the ruling is recorded
Notify: serial · #a behind #b
a seat → a sibling seat or another lane; claimed file surfaces overlap, or one card must land first
Notify: landed · #b
the seat whose card landed first → the seat a `serial` held behind it; the card a `serial` named lands
Notify: seat-online · LANE#N · LOGIN · mailbox #m · patrol TRIGGER_ID · takes: #a #b …
the seating session (or a forced takeover, for the walled account) → the lane's sibling seats and triage; at seating
Notify: seat-offline · LANE#N · in-flight: … · stay-behind: … · brief ID
the signing-off session → the lane's sibling seats and triage; at sign-off
Notify: split · LANE#N · takes: … · leaves: …
a seat of a multi-seat lane → its sibling seats; at round open, only when the list changed
Protocol text (who MUST ring, and what is never rung): pm-dispatch references/project-forms.md 〈信箱 PR〉.
```
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3e54f99 commit db357f6
7 files changed
Lines changed: 444 additions & 71 deletions
File tree
- .claude/skills/pm-dispatch/references
- scripts/pm
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
80 | | - | |
| 80 | + | |
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
42 | | - | |
| 41 | + | |
| 42 | + | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
89 | | - | |
| 89 | + | |
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
| 66 | + | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| |||
0 commit comments