Repository navigation
Commit b5b1bf7
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
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
351 | 351 | | |
352 | 352 | | |
353 | 353 | | |
354 | | - | |
| 354 | + | |
355 | 355 | | |
356 | 356 | | |
357 | 357 | | |
| |||
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
364 | | - | |
| 364 | + | |
365 | 365 | | |
366 | 366 | | |
367 | 367 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
108 | | - | |
| 108 | + | |
109 | 109 | | |
110 | 110 | | |
111 | 111 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
202 | 202 | | |
203 | 203 | | |
204 | 204 | | |
205 | | - | |
206 | | - | |
| 205 | + | |
207 | 206 | | |
208 | 207 | | |
209 | 208 | | |
| |||
232 | 231 | | |
233 | 232 | | |
234 | 233 | | |
235 | | - | |
| 234 | + | |
| 235 | + | |
236 | 236 | | |
237 | 237 | | |
238 | 238 | | |
| |||
0 commit comments