Skip to content

Commit b5b1bf7

Browse files
pm-dispatch: an unruled decision behind Blocked-by refuses the dispatch; measure-then-decide sends a reading-only dev (#23414)
Fixes #23379 Clause-②: no Text-only charter change on three protocol faces, under the maintainer's 「同意」 (2026-10-14) on the director seat's proposal, quoted from the card verbatim: 「先裁再派。派发门加一条:卡上有未裁的 `Blocked-by: 决策` 或派发令写「先测量再决定」的,只派「只读读数」的 dev(不建闭包、不跑测试),或根本不派、等裁。」 and 「立卡你决定,不需要我同意」. Second leg of the serial relay behind #23378 (its draft PR #23405 at head `63496bd46c` edits `os-dev.md`, `dispatch-runbook.md` and `AGENTS.md`); this PR is based on `origin/main` `5656c5638a`, not on that branch, and touches none of its hunks (proof below). Draft; the landing tier is whatever `node scripts/pm/check-governed-merges.mjs --pr N` reads for `.claude/**`. ## What changes (text only), before / after ### 1. Dispatch gate — `.claude/skills/pm-dispatch/SKILL.md`, 状态模型 table, the `pm:blocked` row (L108), amended in place Before (159 bytes): ```text | `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游:选择期跳过,#N 关闭时由解锁扫描放回;工已完、PR 被外部门禁卡住的同用本态 | ``` After (295 bytes; the file's widest-row pin is 342, so the row stays inside it, and a table row is structurally exempt from the 120-byte line cap): ```text | `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游:选择期跳过,#N 关闭时由解锁扫描放回;工已完、PR 被外部门禁卡住的同用本态;正文或线程行首 `Blocked-by:` 指向无 `Ruled:` 的 open `needs-user-decision` 卡 ⇒ ⛔ 不派发,挂本态直至其 `Ruled:` | ``` The gate sentence: a line-head `Blocked-by:` in the card's body or thread that names an open `needs-user-decision` card carrying no `Ruled:` means the card is not dispatched and hangs `pm:blocked` until that card carries `Ruled:`. It sits where `pm:blocked` + `Blocked-by:` semantics already live (the selection skip, the unlock scan), beside the `needs-user-decision` row (L106, 永不派发) and the red line at L29. `Ruled:` is the marker `references/lanes/director.md` L12 writes on the decision card's first body line when a ruling is recorded. ### 2. Reading-only dev — `.claude/skills/pm-dispatch/references/dispatch-runbook.md`, 〈派发词构造细则〉 Before (L235, 112 bytes): ```text - 测量先行卡(修复由测量结果有条件授权)⇒ 派发令写明 ⛔ 测量存在之前不写修复。 ``` After (L235 amended in place, 118 bytes; L236 new, 108 bytes): ```text - 测量先行卡(修复由测量结果有条件授权)⇒ 只派只读读数 dev 或不派等裁;建设 dev 待裁。 - 只读读数 dev 不建闭包、不跑测试、不开 PR;只读树与记录,读数贴决策卡或立卡。 ``` A measure-first card (a dispatch whose first act is "measure, then decide") dispatches a 只读读数 (reading-only) dev — no worktree closure, no build, no test run, no PR; it reads the tree and the record and posts the reading on the decision card or files one — or is not dispatched at all and waits for the ruling; the building dev follows the ruling. The old clause 「⛔ 测量存在之前不写修复」 is subsumed: a dev that opens no PR writes no fix. Paid by one deletion in the same file. Deleted L206 (87 bytes): ```text - 内部 PM 副本 = `.claude/skills/pm-dispatch/SKILL.md` 升级与决策 节的四轴框架块。 ``` It restates `.claude/agents/os-dev.md` L355 「框架唯一副本在 pm-dispatch SKILL.md 〈升级与决策〉;派发词逐字粘贴,dev ⛔ 不留副本。」, and the one fact it carried for the PM side (where the block lives) is folded into its neighbour L205, amended in place: 「四轴框架整段现取现粘、逐单随派发词下发。」 becomes 「四轴框架块(SKILL.md 升级与决策 节)整段现取现粘、逐单随派发词下发。」 (95 bytes). ### 3. The mode's one line — `.claude/agents/os-dev.md`, 〈何时停手不写码〉 and the report template New L354 (116 bytes), the sibling of the `needs_decision` line: ```text - 派发词标只读读数 ⇒ 不建闭包、不跑测试、不开 PR;报 `status: "reading"`,读数贴决策卡。 ``` Report template enum (L364), amended in place: ```text "status": "done | rework | blocked | needs_decision", ``` becomes ```text "status": "done | rework | blocked | needs_decision | reading", ``` Paid by one deletion in the same file. Deleted old L354 (84 bytes): ```text - 升级分析的四轴决策框架由派发词携带,PM 从自己那份副本填入。 ``` It restates its two siblings: L355 「…派发词逐字粘贴,dev ⛔ 不留副本。」 (the frame arrives via the dispatch order, from the single copy) and L356 「…派发词没带,停下向 PM 索取…」, and `dispatch-runbook.md` L205 「…逐单随派发词下发」 carries the PM side. ## Line budget (ceilings on `main`: 319 / 241 / 402 — every added line paid in the same file, no ceiling moved) | file | before | after | lines added | lines deleted (named above) | lines amended in place | |:--|--:|--:|--:|:--|:--| | `.claude/skills/pm-dispatch/SKILL.md` | 319 | 319 | 0 | none | L108 row (159 → 295 B) | | `.claude/skills/pm-dispatch/references/dispatch-runbook.md` | 241 | 241 | 1 (L236, 108 B) | L206 (frame-copy pointer) | L205 (95 B), L235 (118 B) | | `.claude/agents/os-dev.md` | 402 | 402 | 1 (L354, 116 B) | old L354 (frame carried by the dispatch order) | L364 enum (65 B) | Every added or amended non-table line is at or under 120 bytes (measured with `Buffer.byteLength`): 116, 65, 95, 118, 108; the SKILL.md row is a table row (exempt class `table` in `scripts/pm/check-skill-line-ratchet.mjs`) at 295 B against the file's row pin of 342 B. No re-wrap funds any line. No issue number enters a skill file; no model identifier enters any file. The maintainer's term 「只读读数」 is the one spelling in all three files; "reading-only" is its English face here. ## Dry run — the new sentences applied to #23267's claim as it stood at comment 6156257682 (2026-10-13T22:09Z, posted 22:10:28Z) What the record shows for the card at that instant: - Label state: `pm:queue` (it had carried `pm:blocked` from 14:09:08Z and moved to `pm:queue` at 20:58:57Z, the unlock scan firing after its `Blocked-by:` target reached the closed state at 20:02:28Z). The claim's own act moved it to `pm:dispatched` at 22:10:22Z. - Its only line-head `Blocked-by:` is `Blocked-by: #23224` (body line 10 today, line 18 before the `Ruled:` first line and its blank were prepended on 2026-10-14). #23224 is a `domain:engine` enhancement card that never carried `needs-user-decision` (its label timeline has zero such events) and was already in the closed state at 22:09Z. - #23266 is named only in a prose heading, 「## Why `Blocked-by: #23266`」, not as a line-head marker; `references/state-machine.md` L24 makes the line-head form canonical and a sentence mention prose. And #23266 was no longer an unruled decision at 22:09Z: its ruling (letter C, 「同意」) is comment 6148183570 at 14:44:15Z and `needs-user-decision` left it at 14:44:56Z — 7.4 hours before the claim, which itself says "under ruling C on #23266". At 22:09Z #23266 was a `pm:dispatched` execution card of that ruling (its body carries no `Ruled:` first line; the ruling lives in its comment, which predates the first-line convention). So the **`Blocked-by:` leg does not refuse** the building dispatch as of 22:09Z: neither card the body names was an open `needs-user-decision` card without `Ruled:`. It would have refused only a dispatch read before 14:44Z. The **measure-then-decide leg does refuse** it. The claim's File surface reads "the dev measures which door is the record's and declares it in the report and PR body … whichever the measurement shows ADR-0138 D3 item 5 names as the administrator's path", so the dispatch's first act was a measurement whose result selected the route, and the card's own body lists the dev's options as (A) a new door, (B) a new spec key, (C) amend the record — a decision shape. Under runbook L235–236 as amended, that dispatch sends a 只读读数 dev (or nobody, waiting for the ruling): the reading goes on the decision card, no closure is built, no suite runs, no PR opens. What happened instead is the card's own measurement: a building dev built the plugin-security closure under the verify lock and ran two pin suites, returned `status: needs_decision` at 22:28:30Z (comment 6156571003), the card entered `needs-user-decision` at 22:31:36Z, the ruling landed at 02:06:29Z (comment 6159279205, `Ruled:` first line written on the body), and the building dev was dispatched at 02:11Z. Acceptance item read against the record: one of the two legs refuses (measure-then-decide), the other does not fire at 22:09Z; the card's premise that the `Blocked-by:` leg would have caught this claim is corrected here rather than forced. ## Boundary proof against PR #23405 (head `63496bd46c`, merge-base `e4ec6ec1a5`) - `git fetch origin pull/23405/head:refs/pm/pr-23405` → `63496bd46c`. - `git merge-tree --write-tree 54dbbd5 refs/pm/pr-23405` → exit 0, tree `73d591a0e40c08d7f06917e8a0a710ec6c5fe871`, no conflict (a read; nothing merged, no `main` merged, no rebase). - #23405's hunks (main-side numbering, from `git diff -U0 e4ec6ec refs/pm/pr-23405`): `os-dev.md` L138, L140–141, L143, L147–148, L158–163 (deleted), inserts after L245 and L278, L370; `dispatch-runbook.md` L220; `AGENTS.md` L584–585. This PR's edits: `os-dev.md` L354 and L364; `dispatch-runbook.md` L205–206 and L235–236; `SKILL.md` L108. `AGENTS.md` untouched. - Also untouched: SKILL.md's 速读 lines (L95, L190–192, #23381's) and the four-axis block L241–261 (md5 of those lines on this head equals the dispatch's reading, see the report comment). - The three target files are byte-identical between `e4ec6ec1a5` and `5656c5638a` (`git diff --stat` on them is empty), so every line number above holds on the current `main`. ## Gates (local subset = the families `node scripts/pm/dispatch-gates.mjs --commands` derives for this diff at `5656c5638a`; exit codes captured before any pipe; the farm — `pnpm lint`, `pnpm test`, full builds — is CI's, read off this PR's check-runs) 22 families derived, 22 run, all exit 0, `--ran` reconciliation: 「✓ dispatch-gates --ran: 22 derived famil(ies) accounted for — 22 run, 0 NOT-MEASURED (a DERIVED zero — all 22 recorded an exit code and none of them is 3)」. - `pnpm check:pm-skill-ratchet` exit 0 — verdict lines: 「SKILL.md is 319 lines (ceiling 319; headroom 0)」, 「SKILL.md: widest table row is 342 bytes (pin 342; headroom 0)」, 「dispatch-runbook.md is 241 lines (ceiling 241; headroom 0)」, 「os-dev.md is 402 lines (ceiling 402; headroom 0)」. - `pnpm check:pm-skill-id-lint` exit 0 · `pnpm check:pm-governed-prose` exit 0 · `pnpm check:skill-frame-sync` exit 0 · `pnpm check:agent-model-declared` exit 0 · `pnpm check:nul-bytes` exit 0. - The other sixteen derived families (`check-closing-keyword-parity` + its self-test, `check-comment-mask-corpus`, `check-governed-queue-guard --self-test`, `check-harness-current --self-test`, `check:agent-test-spelling`, `check:commit-card-trailers`, `check:cross-package-test-inputs`, `check:doc-authoring`, `check:driver-memory-census`, `check:gitlink-declared`, `check:pm-expected-skips`, `check:pm-governed-merges`, `check:refd-timer-probe`, `check:watch-hint-literal`, `@objectstack/lint check:doc-formula-expressions`) all exit 0. - One declared deviation from the dispatch's "no build locally": `check:doc-formula-expressions` first answered exit 3 PREREQUISITE NOT MET (`@objectstack/formula` and `@objectstack/lint` unbuilt — nothing measured), so a targeted two-package build ran under the verify lock (`os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2'` → 「VERDICT command-exit 0 · held the lock 112s · waited 0s」), after which the gate re-ran to exit 0 (「9 @example(s) judged clean … 15 predicate(s) … judged clean」). The build left `git status` clean — no generated artifact moved. - Raw control-byte self-scan of the three files: zero hits. No ablation: text-only change, nothing tested pins it. ## Acceptance notes (observations, not cards) - #23266's ruling is recorded as a comment (6148183570) and its body carries no `Ruled:` first line; the first-line convention (`lanes/director.md` L12) post-dates it. The gate reads `Ruled:` on the decision card; a reader applying it to pre-convention cards should also accept a `## Ruling` comment as the record. Not filed: no live card is affected (that card is in the closed state), and the convention is already the rule. - The card's text names "the dispatch reference" for the gate; the dispatch narrowed the file surface to the three files above, so `references/execution-duties.md` L10 (候选 = …无 `needs-user-decision`…) is unchanged — its filter still reads only the card's own labels; the `Blocked-by:`-against-the-decision-box reading lives in the SKILL.md row, which every seat reads. ## 维护者速读(草稿) - **改了什么**:三份协议文本各加一句,行数不变(319/241/402)。状态模型表的 `pm:blocked` 行多一条:卡上行首 `Blocked-by:` 指向一张还没 `Ruled:` 的 open 决策卡,就不派发,挂 `pm:blocked` 等裁。派发细则里「测量先行卡」改为只派「只读读数」dev(不建闭包、不跑测试、不开 PR,读数贴决策卡或立卡)或干脆不派等裁,建设 dev 待裁后再派。os-dev 契约加一行只读读数模式(报 `status: "reading"`),报告枚举多一个 `reading`。新增的行以删掉两句重复表述支付。 - **为什么改**:你 2026-10-14 「同意」的「先裁再派」。#23267 那种卡决策没裁就派了建设 dev,结果是一次 271 秒的闭包构建、两套 pin 测试和一份八千字的 needs_decision 报告,零代码。门加在派发前:读数由便宜的只读 dev 产出,建设在裁决之后。 - **风险与代价(含回滚)**:纯文本,不碰脚本、门禁与 workflow;唯一风险是口径 —— 只认行首 `Blocked-by:` 与决策卡上的 `Ruled:` 标记,散文里提到决策卡不触发,这是既有正典标记规则的延续。回滚 = revert 这一个 commit,无数据与状态迁移。 - **席位意见**: - **你要做的**:落地档位由 `check-governed-merges --pr` 读(`.claude/**` 按登记是 Tier S,席位按契约复核落地);若它读为 Tier H,需要你的一次批准。PR 保持 draft。 --- _Generated by [Claude Code](https://claude.ai/code/session_013zGKMG1cRedVzWtqGGGZW9)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5656c56 commit b5b1bf7

3 files changed

Lines changed: 6 additions & 6 deletions

File tree

‎.claude/agents/os-dev.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ model: opus
351351
- issue 对塑造公开契约的决定欠规格(spec/Zod schema、API 形状、命名、元数据语义)时:不猜。
352352
- 两种读法通向两种架构时同此;⛔ 不写投机代码。
353353
- 返回 `status: "needs_decision"`,每个问题带选项、真实代价与工作量、推荐,写进 `open_questions`。
354-
- 升级分析的四轴决策框架由派发词携带,PM 从自己那份副本填入。
354+
- 派发词标只读读数 ⇒ 不建闭包、不跑测试、不开 PR;报 `status: "reading"`,读数贴决策卡。
355355
- 框架唯一副本在 pm-dispatch SKILL.md 〈升级与决策〉;派发词逐字粘贴,dev ⛔ 不留副本。
356356
- 每个方案逐轴分析,推荐也按那些轴给理由;派发词没带,停下向 PM 索取,⛔ 不自拟一套轴。
357357
- `main` 碎了、依赖未合并、CI 基础设施故障 ⇒ 报 `blocked` 附证据,重试到排除你的改动。
@@ -361,7 +361,7 @@ model: opus
361361
```json
362362
{
363363
"issue": <n>,
364-
"status": "done | rework | blocked | needs_decision",
364+
"status": "done | rework | blocked | needs_decision | reading",
365365
"branch": "claude/issue-<n>-<slug>",
366366
"pr": "<url or null>",
367367
"session": "session_<id> — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
105105
| `pm:dispatched` | 已派发(派发评论记轮次),恒带 assignee;与摘 `pm:queue` 同一次标签写入成对落地 |
106106
| `needs-user-decision` | 决定待做:永不派发、除代裁通道外永不代答;维护者的收件箱 |
107107
| `pm:on-hold` | 决定已做且答案是暂不做:不派发不催;仅当带机器可读 `Restart-when:` 行才合法 |
108-
| `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游:选择期跳过,#N 关闭时由解锁扫描放回;工已完、PR 被外部门禁卡住的同用本态 |
108+
| `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游:选择期跳过,#N 关闭时由解锁扫描放回;工已完、PR 被外部门禁卡住的同用本态;正文或线程行首 `Blocked-by:` 指向无 `Ruled:` 的 open `needs-user-decision` 卡 ⇒ ⛔ 不派发,挂本态直至其 `Ruled:` |
109109
| `pm:awaiting-maintainer` | 决定已做,只剩一次 GitHub 之外的人工动作:不派发不催;与其它 pm 状态标签互斥;入态恒带行首 `Maintainer-action:` 行(细则见 `references/state-machine.md`),无行即半态 |
110110
| `pm:blocking` | 有 open 下游依赖者(自 `Blocked-by:` 索引推导的缓存,⛔ 不手工挂);进选择全序 |
111111
| `pm:retriage` | 向分诊提问(改判、跨域 PR 指定车道、改路由、拆卡、裁 dev 报告留下的分叉),定车道与改路由限未派发卡,异议评论写明所求;与现行 `pm:*` 并存、⛔ 不摘原标;带本标签的 `pm:queue` 卡跳过派发 |

‎.claude/skills/pm-dispatch/references/dispatch-runbook.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,7 @@
202202
- 点名单当天现取仍会漏,dev 对实际改动路径重取补跑。
203203
- ② 去重或看板读数取一次(查询式加时间戳)整批共用。
204204
- 派发令写:当既有事实用,只复核其后的增量,⛔ 不重跑;dev 侧对价住 os-dev 定义。
205-
- 四轴框架整段现取现粘、逐单随派发词下发。
206-
- 内部 PM 副本 = `.claude/skills/pm-dispatch/SKILL.md` 升级与决策 节的四轴框架块。
205+
- 四轴框架块(SKILL.md 升级与决策 节)整段现取现粘、逐单随派发词下发。
207206
- 该块自黑体句起、至推荐约束句止,整段逐字粘贴,⛔ 不重打、⛔ 不摘句。
208207
- 派发那刻现取,把该副本的 commit 与 md5 记进派发词。
209208
- ⛔ 仓内任何文件不得另立第三份,`check:skill-frame-sync` 全树扫;os-dev 定义没带就停下索取。
@@ -232,7 +231,8 @@
232231
- ⛔ 不凭记忆;并带一句:每一面都必须在 PR 正文有一个结论。
233232
- 结论三选一:已改、本就合规(证据)、明确不在范围(理由)。
234233
- ⛔ 静默略过:评审把没提到的面读作漏掉的面。
235-
- 测量先行卡(修复由测量结果有条件授权)⇒ 派发令写明 ⛔ 测量存在之前不写修复。
234+
- 测量先行卡(修复由测量结果有条件授权)⇒ 只派只读读数 dev 或不派等裁;建设 dev 待裁。
235+
- 只读读数 dev 不建闭包、不跑测试、不开 PR;只读树与记录,读数贴决策卡或立卡。
236236
- 探针做在 dev 要留的分支上,⛔ 不用一次性分支:建得出删不掉,遗留分支被预检读成认领。
237237

238238
## security-object 无判决枚举(座位贴常设 ⑦)的方法

0 commit comments

Comments
 (0)