Skip to content

Commit 115bf92

Browse files
pm: the Notify: security doorbell reads a private repository's card as OWNER/REPO#N (#31107)
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} --> _Requested by **Irene** · [project thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67x3ZJ3vUAa9xu4fhtKsK3Vp6)_ Fixes #31103 Clause-②: no Metadata-protocol: none `dispatch-gates --tier` reads no path-derived model mandate for this surface; any mandate above this thread's model would be waived by the maintainer's standing order in this project (2026-11-03). The dispatching seat runs the contract review. ## 维护者速读 - 改了什么:`record-recognisers.mjs` 的 `Notify: security` 门铃新增一种读法 `Notify: security · OWNER/REPO#N`(私有仓里那张普通 `security` 卡,读出仓库和卡号),裸 `#n` 仍只表示分诊挂标的公开卡;pm-dispatch 三处参考行把私有仓门铃写成 `OWNER/REPO#N`。 - 为什么改:PR #31102 让私有仓的安全发现建普通 `security` 卡并以裸 `#N` 敲 `security#1`,该号不指明仓库,安全席分不清私有仓卡和误公开卡,也打不开卡(#31102 契约复审 S2)。 - 风险与回滚:只改 pm 工具的读法与三行文字;新读法只带卡引用,不带机制、路径、对象名或发现编号;三个文件行数不变、每行 ≤120 B;回滚即 revert。 - 先看:`scripts/pm/record-recognisers.mjs` 的 `REPO_CARD_REF` 与 `'notify:security'` 读者,及新增两条自测。 - 两分钟验证:`node scripts/pm/record-recognisers.mjs --self-test`,预期退出 0,`368 cases pass`。 - 需要你做的:无。Tier 由 `check-governed-merges` 读出,本分支读为 Tier S(`.claude/**`),席位契约复审后落地。 ## What changes ### Grammar, before and after | | Before | After | |---|---|---| | Template | `Notify: security · GHSA-ID or #n` | `Notify: security · GHSA-ID or OWNER/REPO#N or #n` | | `GHSA-xxxx-xxxx-xxxx` | `{ advisory, numbers: [] }` | unchanged | | `#n` (triage's labelled public card) | `{ card, numbers: [n] }` | unchanged | | `OWNER/REPO#N` (private repository's ordinary `security` issue) | unreadable | `{ repo, card, numbers: [] }` | `when` gains one branch: "or the finding is on a PRIVATE repository and the seat filed an ordinary `security` issue there: that card as `OWNER/REPO#N` ALONE". `from` and `to` are unchanged. - **Shape reused, not invented.** `REPO_CARD_REF = /^([A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*)#([1-9][0-9]*)$/` is the repository-qualified card reference `check-half-states.mjs` already reads (`Restart-when: closed <owner/repo>#N` at L3385, `re-check owner/repo#N` at L7008): two segments, each led by an alphanumeric. It is mirrored, not imported, because that module is not a recogniser and the regex is not exported there. The looser `REPO_SHAPE` (`mailbox-open.mjs`, `issue-create.mjs`) admits a leading `.`, so it was not used. - **`numbers` stays empty** for the qualified form, as it does for the advisory id: `numbers` lists numbers on this board, and a private repository's `#N` is not one. - Still one field: a qualified ref beside an advisory id, a public `#n`, a path or free text is unreadable. ### Self-test cases and counts Two pins added to the battery `⭐ the mailbox doorbell …`, floor raised **42 → 44** (by exactly the two pins added, recorded beside the 41 → 42 note). Self-test total 366 → **368** cases, exit 0. 1. ⭐ `Notify: security · objectstack-ai/cloud#412` under `To: security#1` reads `repo: 'objectstack-ai/cloud'`, `card: 412`, no `advisory`, `numbers` empty; `my.org-1/a_b.c-d#7` reads its repository; the bare `Notify: security · #29761` still reads `card: 29761` with no `repo` and `numbers: [29761]`. 2. ⛔ unreadable (14 lines): no slash (`#412`), three segments, empty owner, empty repo, owner led by `.`, `#0`, space before `#`, trailing letters, the template `OWNER/REPO#N`, and the qualified ref beside a GHSA id (either order), beside `#29761`, beside a path, beside free text. The existing pins still hold: the bare `#n` pin (L2869 on main), the "template sent verbatim is not an event" pin (the new template stays unreadable), and the both-ways key-name pin against `project-forms.md` 〈信箱 PR〉. ### Line readings (`.claude/**`, in place, net 0 lines) ```diff --- a/.claude/skills/pm-dispatch/references/security-findings.md (L63, 105 → 115 B) -- 席位两件事:照第 3 节建私卡;敲 `Notify: security · GHSA-ID`(私有仓 `#N`),只带编号。 +- 席位两件事:照第 3 节建私卡;敲 `Notify: security · GHSA-ID`(私有仓 `OWNER/REPO#N`),只带编号。 --- a/.claude/skills/pm-dispatch/references/project-forms.md (L48, 104 → 119 B) -- 持发现的席→`security#1`:私卡建成或误公开卡挂标即 `Notify: security`,只带编号。 +- 持发现席→`security#1`:私卡建成或误公开卡挂标即 `Notify: security`,只带编号或 `OWNER/REPO#N`。 --- a/.claude/skills/pm-dispatch/references/lanes/security.md (L16, 117 → 118 B) -- 接收 ①:发现者的一次 `Notify: security · GHSA-ID`(私有仓 `#N`)门铃,私卡已由其经中继建好。 +- 接收 ①:发现者 `Notify: security · GHSA-ID`(私有仓 `OWNER/REPO#N`)门铃,私卡已由其经中继建好。 ``` Bytes are measured without the newline. Line counts are unchanged: `security-findings.md` 65/65, `project-forms.md` 59/59, `lanes/security.md` 53/53. No ceiling was raised and no line was re-wrapped or merged. To fit 120 B, L48 drops `的` from `持发现的席`, and L16 drops `的一次`; the single-ring rule keeps its home in `project-forms.md` L51 (no re-sends on any key). `lanes/security.md` L49 (`Notify: security · #N`) is triage's public-card hand-over, so it is left alone. ### Other consumers No other `scripts/pm` reader uses the parsed fields. `check-half-states.mjs` H72 reads only each event's `state` (read or not) through `mailboxEvents`. `check-skill-line-ratchet.mjs` names the row only in a comment. Nothing else imports `readMailboxLine` or reads `.card`, `.repo` or `.advisory` from it. ## Why PR #31102 (#30114) made a security finding on a private repository an ordinary `security` issue on that repository, and its holder rang `security#1` with a bare `#N`. The reader only read a bare `#n` as triage's labelled public card, so the security seat could not tell which repository the card was on, or tell it from a mis-disclosed public card except by the sender. This is should-fix S2 of the contract review of PR #31102 (comment 6704473385). ## Verification <details><summary>Local gates on 6c698a1: 38 derived, 38 run with exit 0, 0 NOT MEASURED (`dispatch-gates --ran`)</summary> - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 38 commands for the 4 changed paths. Each was run with its exit code captured before any pipe. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` exited 3 (NOT MEASURED) on the first run because nothing was built. After `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` (exit 0) it exited 0. The other 37 exited 0 on the first run, including `check:pm-record-recognisers`, `check:pm-skill-ratchet`, `check:pm-dispatch-gates`, `check:pm-governed-merges`, `check:doc-authoring` and `check:skill-frame-sync`. - `--ran` printed: `38 derived famil(ies) accounted for — 38 run, 0 NOT-MEASURED (a DERIVED zero — all 38 recorded an exit code and none of them is 3)`. </details> - `node scripts/pm/record-recognisers.mjs --self-test`: exit 0, `368 cases pass` (366 on `main`). - `node scripts/pm/check-skill-line-ratchet.mjs`: exit 0. - `node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-u4w7zq`: GOVERNED, Tier S (`.claude/**`); `scripts/pm/record-recognisers.mjs` is not on the register; 32 changed lines (+25 / −7), under 3000. - `node scripts/pm/dispatch-gates.mjs --tier`: no path-derived model mandate. - `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD`: exit 0 before the push. `skip-changeset`: nothing published. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (No schema touched.) - [x] 2. Every name I added uses the casing for its kind. (`REPO_CARD_REF`, beside `ADVISORY_ID_REF`.) - [x] 3. No formatter ran. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. Nothing publishes (`scripts/pm/**`, `.claude/**`), so no changeset (`skip-changeset`). Both declaration lines are filled in above. - [x] 6. `packages/spec` not touched; nothing to regenerate. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. (The 维护者速读 section and the quoted skill lines are Chinese, per the lane template.) - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01J8VVAjdHfu9CsJ7tGW4TiM <!-- signed: domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 --> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3299873 commit 115bf92

4 files changed

Lines changed: 25 additions & 7 deletions

File tree

‎.claude/skills/pm-dispatch/references/lanes/security.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
## 接收与台账
1515

16-
- 接收 ①:发现者的一次 `Notify: security · GHSA-ID`(私有仓 `#N`)门铃,私卡已由其经中继建好。
16+
- 接收 ①:发现者 `Notify: security · GHSA-ID`(私有仓 `OWNER/REPO#N`)门铃,私卡已由其经中继建好。
1717
- 接收 ②:无门铃的 `triage` 公告(外报或中继实测):经 `advisory_read` 读,座位贴记 `received`。
1818
- 判定:发现 ⇒ 中继 `advisory_state` 置 `draft` 走派修;非发现 ⇒ 置 `closed`,台账记非发现。
1919
- 中继拒或本席空缺照第 3、9 节。

‎.claude/skills/pm-dispatch/references/project-forms.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
- 移卡入队者→车道:p0/p1 `Blocked-by` 关闭 `Notify: unblocked`;记裁决席→被改席 `Notify: ruling`。
4646
- 席→兄弟席或他车道:认领文件面相交或须先落 `Notify: serial`,先落者落地 `Notify: landed`。
4747
- 上下线→兄弟席与分诊 `Notify: seat-online`、`Notify: seat-offline`;清单变→兄弟席 `Notify: split`。
48-
- 持发现的席→`security#1`:私卡建成或误公开卡挂标即 `Notify: security`,只带编号。
48+
- 持发现席→`security#1`:私卡建成或误公开卡挂标即 `Notify: security`,只带编号或 `OWNER/REPO#N`。
4949
- 分诊→认领席:认领卡或 open PR 闲置 ≥3h `Request: progress`;反向随记录 `Notify: progress`。
5050
- 记录 = 项上写下一步与 ETA 的评论、§C 停车或释放;同项 60 分钟一敲,答 ETA 到期前不敲。
5151
- ⛔ 不敲:回执、重发;收方订阅 PR 已载者;停车或待决策卡的项的进度;问维护者走决策卡。

‎.claude/skills/pm-dispatch/references/security-findings.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@
6060

6161
- 每份安全发现从接收到发布归 `security#1` 席(章程 `lanes/security.md`),发现者只报不修。
6262
- ⛔ 不扩本 PR 或分支,不自开修复分支,不在同一面顺手加固;dev 只走第 5 节上报。
63-
- 席位两件事:照第 3 节建私卡;敲 `Notify: security · GHSA-ID`(私有仓 `#N`),只带编号。
63+
- 席位两件事:照第 3 节建私卡;敲 `Notify: security · GHSA-ID`(私有仓 `OWNER/REPO#N`),只带编号。
6464
- 中继拒(第 3 节)⇒ 机制在会话里交 `security#1` 席;席空缺 ⇒ 交维护者;发布清单归维护者。
6565
- 任务绕不开它 ⇒ 报 `blocked`,只写「a security item is with the owner seat」;席位停卡,⛔ 不绕行。

‎scripts/pm/record-recognisers.mjs‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,7 @@ export const MAILBOX_KEYS = Object.freeze([
13041304
{ id: 'notify:seat-online', kind: 'Notify', key: 'seat-online', from: 'the seating session (or a forced takeover, for the walled account)', to: 'the lane\'s sibling seats and triage', when: 'at seating', template: 'Notify: seat-online · LANE#N · LOGIN · mailbox #m · patrol TRIGGER_ID · takes: #a #b …' },
13051305
{ id: 'notify:seat-offline', kind: 'Notify', key: 'seat-offline', from: 'the signing-off session', to: 'the lane\'s sibling seats and triage', when: 'at sign-off', template: 'Notify: seat-offline · LANE#N · in-flight: … · stay-behind: … · brief ID' },
13061306
{ id: 'notify:split', kind: 'Notify', key: 'split', from: 'a seat of a multi-seat lane', to: 'its sibling seats', when: 'at round open, only when the list changed', template: 'Notify: split · LANE#N · takes: … · leaves: …' },
1307-
{ id: 'notify:security', kind: 'Notify', key: 'security', from: 'the seat holding the finding (a dev\'s or a QA runner\'s reaches its PM session privately first); triage, for a public card it labelled `security`', to: 'security#1', when: 'the seat\'s relay `advisory_create` draft landed: the opaque advisory id ALONE; or triage labelled a public card `security`: its number ALONE — never a mechanism, a path or an object name', template: 'Notify: security · GHSA-ID or #n' },
1307+
{ id: 'notify:security', kind: 'Notify', key: 'security', from: 'the seat holding the finding (a dev\'s or a QA runner\'s reaches its PM session privately first); triage, for a public card it labelled `security`', to: 'security#1', when: 'the seat\'s relay `advisory_create` draft landed: the opaque advisory id ALONE; or the finding is on a PRIVATE repository and the seat filed an ordinary `security` issue there: that card as `OWNER/REPO#N` ALONE; or triage labelled a public card `security`: its number ALONE — never a mechanism, a path or an object name', template: 'Notify: security · GHSA-ID or OWNER/REPO#N or #n' },
13081308
{ id: 'request:progress', kind: 'Request', key: 'progress', from: 'triage', to: 'the claiming seat (or the online sibling an off-shift seat\'s items went to)', when: 'a claimed card or an open PR whose last PROGRESS (its head commit, any non-triage comment, a label change by its seat — never `updated_at`) is 3 h old: at most one per item per 60 min; none while an answered ETA stands, or while the item is parked (`Blocked-by:` / `Resume-from:`) or waits on a decision card; two unanswered ⇒ the round record lists it and the seat reads as possibly down', template: 'Request: progress · #n · idle since TIME' },
13091309
{ id: 'notify:progress', kind: 'Notify', key: 'progress', from: 'the claiming seat', to: 'triage', when: 'answering a progress knock — the record is a comment on #n giving the next act and an ETA, the park (`Blocked-by:` / `Resume-from:`), or the release', template: 'Notify: progress · #n · record ID' },
13101310
]);
@@ -1341,11 +1341,20 @@ const RULING_LETTER_REF = /^[^\s#`]{1,12}$/u;
13411341
* `GHSA-xxxx-xxxx-xxxx` — a repository security advisory's opaque id, the ONE field `Notify: security` carries from
13421342
* the seat holding a finding (`security-findings.md`: the id alone, never a mechanism, a path or an object name);
13431343
* triage's hand-over of a public card it labelled `security` carries that card's `#n` alone instead (`NUMBER_REF`,
1344-
* `lanes/security.md`). Never both on one line. MIRRORED from the relay's
1344+
* `lanes/security.md`), and a finding on a PRIVATE repository rings its ordinary `security` issue there as
1345+
* `OWNER/REPO#N` alone (`REPO_CARD_REF`). Never two on one line. MIRRORED from the relay's
13451346
* `GHSA_ID_SHAPE` (`fleet-write/ops.mjs`, its reading of the id an advisory write lands) — not imported, so the
13461347
* patrol graph never loads the relay; the self-test holds the two equal.
13471348
*/
13481349
const ADVISORY_ID_REF = /^GHSA(-[0-9a-z]{4}){3}$/;
1350+
/**
1351+
* `OWNER/REPO#N` — a card on ANOTHER repository, named with its repository: the private-repository ring of
1352+
* `Notify: security` (`security-findings.md`: a finding on a private repository is an ordinary `security` issue
1353+
* there, and a bare `#N` would name this board's card instead). The qualifier is the repository-qualified card
1354+
* reference `check-half-states.mjs` reads (`Restart-when: closed <owner/repo>#N`, `re-check owner/repo#N`): two
1355+
* segments, each led by an alphanumeric, then `._-`. MIRRORED, not imported (that module is not a recogniser).
1356+
*/
1357+
const REPO_CARD_REF = /^([A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*)#([1-9][0-9]*)$/;
13491358
/**
13501359
* `patrol TRIGGER_ID` on `Notify: seat-online` — the seat's patrol routine, the value its post's `Patrol:` line
13511360
* records (`trig_…`), or `patrol none` for a seat with no patrol by rule (the director seat). The template's own
@@ -1465,7 +1474,12 @@ const MAILBOX_FIELD_READERS = Object.freeze({
14651474
// A public card triage labelled `security` (`lanes/security.md`): its number is already public and names no
14661475
// mechanism, so it rides the doorbell as the one field — the card, never the card AND an id.
14671476
const card = NUMBER_REF.exec(p[1]);
1468-
return card ? { card: Number(card[1]), numbers: [Number(card[1])] } : null;
1477+
if (card) return { card: Number(card[1]), numbers: [Number(card[1])] };
1478+
// A private repository's ordinary `security` issue (`security-findings.md`): the card WITH its repository, so the
1479+
// seat can open it and never mistakes it for a public card here. Its number is not a number on THIS board, so
1480+
// `numbers` stays empty, as the advisory's does.
1481+
const qualified = REPO_CARD_REF.exec(p[1]);
1482+
return qualified ? { repo: qualified[1], card: Number(qualified[2]), numbers: [] } : null;
14691483
},
14701484
'notify:contract-review': (p) => {
14711485
if (p.length !== 3) return null;
@@ -2264,7 +2278,9 @@ const SELF_TEST_BATTERIES = Object.freeze({
22642278
// keys that stay refused, and the two end-to-end pins through check-half-states H72 (31 → 44 cases).
22652279
// Raised 40 → 41 with the `Notify: security · #n` reading (a labelled public card handed to the security seat).
22662280
// Raised 41 → 42 with the `Notify: main-green` reading (the knock that resumes a PR held for a main red).
2267-
'⭐ the mailbox doorbell: `To: <seat>`, then one key of the table read — seat, kind, numbers, sha, login, time — and anything else refused': 42,
2281+
// Raised 42 → 44 with the `Notify: security · OWNER/REPO#N` reading (a private repository's ordinary `security`
2282+
// issue) and its unreadable near misses: two pins, two cases.
2283+
'⭐ the mailbox doorbell: `To: <seat>`, then one key of the table read — seat, kind, numbers, sha, login, time — and anything else refused': 44,
22682284
'⭐ the signature line: the reader reads LANE#N · ACCOUNT · SESSION exactly, and one leading signature changes no first-line reading': 22,
22692285
'⭐ the `Contract-review-claim:` line — session, FULL head, time; lapses on a moved head or after two hours; the earliest live claim holds': 24,
22702286
'⭐ the `Contract-review-hold:` line — session, head, reason, until; a new head clears it; at 24 hours it reads overdue and still holds': 23,
@@ -2867,6 +2883,8 @@ export async function selfTest() {
28672883
t('⭐ `Notify: security` under `To: security#1` reads to the security seat (a bare lane `seatKey` spells) with the advisory\'s opaque id and nothing else — it names no card, so `numbers` is empty', (() => { const r = readMailboxLine(DB.sec); return r.state === 'read' && r.to === 'security#1' && r.id === 'notify:security' && r.kind === 'Notify' && r.key === 'security' && r.advisory === 'GHSA-7q4h-9x2m-5c8v' && r.numbers.length === 0 && readMailboxLine(MSG('Notify: security · GHSA-7q4h-9x2m-5c8v', 'security')).to === 'security#1'; })());
28682884
t('⛔ `Notify: security` carrying free text, a second field, an upper-case or short id, or no id at all is unreadable — nothing but the opaque id, or a labelled card\'s number alone, rides the doorbell', ['Notify: security · see the advisory', 'Notify: security · GHSA-7q4h-9x2m-5c8v · packages/x/src/door.ts', 'Notify: security · GHSA-7q4h-9x2m-5c8v · see the advisory', 'Notify: security · GHSA-7Q4H-9X2M-5C8V', 'Notify: security · GHSA-7q4h-9x2m', 'Notify: security'].every((l) => readMailboxLine(MSG(l)).state === 'unreadable'));
28692885
t('⭐ `Notify: security · #n` — triage handing a public card it labelled `security` to the seat — reads the card ALONE; the card beside an id, a second field, a PR, `#0` and the bare template stay unreadable', (() => { const r = readMailboxLine(MSG('Notify: security · #29761', 'security#1')); return r.state === 'read' && r.to === 'security#1' && r.id === 'notify:security' && r.card === 29761 && r.advisory === undefined && r.numbers.join() === '29761' && ['Notify: security · #29761 · GHSA-7q4h-9x2m-5c8v', 'Notify: security · #29761 · see the card', 'Notify: security · PR #29761', 'Notify: security · #0', 'Notify: security · GHSA-ID or #n'].every((l) => readMailboxLine(MSG(l)).state === 'unreadable'); })());
2886+
t('⭐ `Notify: security · OWNER/REPO#N` — a private repository\'s ordinary `security` issue — reads the repository AND the card, `numbers` empty (not a number on this board); the bare `#n` still reads the public card, with no repository', (() => { const r = readMailboxLine(MSG('Notify: security · objectstack-ai/cloud#412', 'security#1')); const bare = readMailboxLine(MSG('Notify: security · #29761')); return r.state === 'read' && r.to === 'security#1' && r.id === 'notify:security' && r.repo === 'objectstack-ai/cloud' && r.card === 412 && r.advisory === undefined && r.numbers.length === 0 && readMailboxLine(MSG('Notify: security · my.org-1/a_b.c-d#7')).repo === 'my.org-1/a_b.c-d' && bare.state === 'read' && bare.card === 29761 && bare.repo === undefined && bare.numbers.join() === '29761'; })());
2887+
t('⛔ a malformed qualifier, or the qualified card beside an id, a public card, a path or free text, is unreadable — the private ring carries the card reference ALONE', ['Notify: security · objectstack-ai#412', 'Notify: security · objectstack-ai/cloud/x#412', 'Notify: security · /cloud#412', 'Notify: security · objectstack-ai/#412', 'Notify: security · .x/cloud#412', 'Notify: security · objectstack-ai/cloud#0', 'Notify: security · objectstack-ai/cloud #412', 'Notify: security · objectstack-ai/cloud#412a', 'Notify: security · OWNER/REPO#N', 'Notify: security · objectstack-ai/cloud#412 · GHSA-7q4h-9x2m-5c8v', 'Notify: security · GHSA-7q4h-9x2m-5c8v · objectstack-ai/cloud#412', 'Notify: security · objectstack-ai/cloud#412 · #29761', 'Notify: security · objectstack-ai/cloud#412 · packages/x/src/door.ts', 'Notify: security · objectstack-ai/cloud#412 · see the card'].every((l) => readMailboxLine(MSG(l)).state === 'unreadable'));
28702888
t('⭐ `Request: progress` reads the item (a card or a PR, bare `#n`) and the time its last progress was read — with seconds too; the placeholder `TIME`, a duration, a date without a time, a local time and `PR #n` stay unreadable', (() => { const r = readMailboxLine(DB.rp); const sec = readMailboxLine(DB.rp.replace('T00:06Z', 'T00:06:30Z')); return r.state === 'read' && r.id === 'request:progress' && r.kind === 'Request' && r.key === 'progress' && r.to === 'domain:skills#1' && r.item === 29420 && r.since === '2026-11-03T00:06Z' && r.numbers.join() === '29420' && sec.state === 'read' && sec.since === '2026-11-03T00:06:30Z' && ['Request: progress · #29420 · idle since TIME', 'Request: progress · #29420 · idle since 3h', 'Request: progress · #29420 · idle since 2026-11-03', 'Request: progress · #29420 · idle since 2026-11-03T00:06', 'Request: progress · #29420 · idle since 2026-11-03T00:06+08:00', 'Request: progress · PR #29420 · idle since 2026-11-03T00:06Z', 'Request: progress · #29420', 'Request: progress · #29420 · idle since 2026-11-03T00:06Z · please answer'].every((l) => { const u = readMailboxLine(MSG(l)); return u.state === 'unreadable' && /does not spell/.test(u.reason); }); })());
28712889
t('⭐ `Notify: progress` reads the item and the record id (the comment on it with the next act and an ETA, the park, or the release) — `record none`, a sentence and a second field stay unreadable', (() => { const r = readMailboxLine(DB.np); return r.state === 'read' && r.id === 'notify:progress' && r.kind === 'Notify' && r.key === 'progress' && r.to === 'triage#1' && r.item === 29420 && r.record === 6599000001 && r.numbers.join() === '29420' && readMailboxLine(MSG('Notify: progress · #29420 · record #6599000001', 'triage')).record === 6599000001 && ['Notify: progress · #29420 · record none', 'Notify: progress · #29420 · record ID', 'Notify: progress · #29420 · ETA 11:00Z', 'Notify: progress · #29420', 'Notify: progress · #29420 · record 6599000001 · ETA 11:00Z'].every((l) => { const u = readMailboxLine(MSG(l, 'triage')); return u.state === 'unreadable' && /does not spell/.test(u.reason); }); })());
28722890
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'); })());

0 commit comments

Comments
 (0)