docs(pm-dispatch): the ruling procedure writes a banner onto the card body - #19303
Conversation
… body A ruling that lands as a comment left the body asserting the opposite, and nothing reconciled them — the body is what a taker reads first, so a ruled card kept advertising itself as un-dispatchable. The director charter's shape section now fixes the BODY's first line the way it already fixes the ruling COMMENT's first line: the same stroke that records the ruling writes a `Ruled:` banner and strikes the stale do-not-dispatch assertion, through post-stamped --body whose --ack-through is the thread read. No new label and no new state — both measured instances were in the correct state the whole time. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
…restated clause The banner act now reads as two lines instead of three: the first fixes the body's first line and the no-new-label/state guard, the second carries the strike, the post-stamped route and the failure mode. Both sit under the 120-byte per-line cap the ratchet reads (109 B and 119 B). Retired the duty-one clause whose substance is already carried one line above it: an audit seat that is explicitly 非放行链, with clearance standing with the in-seat review, does not also need to be told not to take the clearance action. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
Patch round — the file is at 73, one line above the ceilingVerdict was REWORK with one item: bring 1. The three added lines are now twoBoth sit under the 120-byte per-line cap the ratchet reads. Described rather than quoted — the landed bytes carry the corpus's angle-bracket placeholders, spelled here as words per the body-sanitizer rule:
2. Retired: one line, with the line that now carries its substance
Both halves land there. 「非放行必要条件」 follows from 「非放行链:放行常设归派发席席内复核」 — clearance being the in-seat review's standing duty is exactly what makes a director ruling not a precondition for it; the same fact is stated a second time in 授权与形态 (「复核放行不在其内,常设归派发席席内」) and a third time in the file that line points at, where the director's ruling is declared 非达档裁决. 「⛔ 不与席内复核抢同一载体的放行动作」 follows from 「本职是审计位,非放行链」 — a seat that is not in the clearance chain takes no clearance action. The remaining fragment, 「裁决仍被尊重」, is the concessive half of the sentence and places no obligation on anyone. Nothing pins the retired text: Augmenting the surviving line was considered and rejected on measurement: it stands at 116 B, and appending 「⛔ 不抢放行动作」 would take it to 141 B, past the cap. 3. NOT retired, and why — the second candidate
The condition for retiring it was that both halves are provably already stated. Only one is.
Deleting it would leave 「收尾 PASS 且工已完 ⇒ 跟到合并为止」 undefined at the exact point where the misreading is "so merge it" — the reading Prime Directive #14 and #15 exist to prevent. Per 「砍掉内容若在别处无家,砍掉就是丢规则」, it stays. 4. Where that leaves the count72 ceiling · 73 lines · shortfall exactly 1 line. The ratchet script was not touched and nothing further was deleted, per the patch order. The gate union was re-run on head Generated by Claude Code |
…its ceiling 基本原则's 呈报批序与写法见 ../decision-analysis.md asserted no rule of its own. Both of its referents survive: the 写法 pointer to the same file is carried by 职责二's 有席内 ACCEPT 者呈一批 ≤5 行 line, and 批序 is that file's own content. The charter is back at its ratchet ceiling with the banner clause in place. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgments
② Semver levelNone — nothing published; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…ly is missing (未回字 = 不在 = 停) (objectstack-ai#19416) Fixes objectstack-ai#19274 Clause-②: no The director seat burned the maintainer's tokens while he was away. Batches 168–178 had drawn no reply since 2026-09-18T15:10Z; the seat went on presenting batches 179–191 — thirteen batches, sixteen cards, each with a full four-axis analysis and a reprinted cumulative awaiting-reply index — and sent an hourly status paragraph besides. The charter had no sentence saying WHEN the next batch may open, no definition of "absent", and no word that the pending list is presented once, so the seat read 「批间新到滚入下批」 as "open the next batch now". The maintainer ruled twice, verbatim (2026-09-20, the director seat's chat, quoted untranslated): > 「你浪费了我很多token,上一批决裁没有回复说我我人不在,你不应该开启下一批决裁。立skills卡」 > 「项目总监由维护者召唤,维护者不在期间什么都不用做。」 The second sentence supersedes the card's rule 3 (a silent patrol still doing three mechanical things) with 3′: while the maintainer is absent the director seat does not run at all. ## What changed Seven in-place line rewrites; net 0 lines in every file — no line added, deleted or re-wrapped — and every touched line at or under the 120-byte cap (`wc -c`, newline excluded): | file:line | before | after | now carries | |---|---|---|---| | `references/lanes/director.md:15` | 118 B | 116 B | the definition 「未回字 = 不在 = 停」 and 3′: absent ⇒ the seat does not run — no patrol, no audit, no snapshot, no chat, no card comment | | `references/lanes/director.md:16` | 110 B | 116 B | the return: the audit and the rehang reset are re-run ONCE first, then exactly one line 「批 #N 待字;箱内另有 K 张新到未呈」 | | `references/lanes/director.md:41` | 77 B | 115 B | receives 「⛔ 不复活已退役的汇总 issue」 from :43 — same section, one line down is now the cadence rule | | `references/lanes/director.md:43` | 92 B | 113 B | 一次只开一批: batch N+1 is composed from the arrivals only after the maintainer has answered batch N (one character counts); keep batching until the inbox is empty | | `references/lanes/director.md:58` | 106 B | 120 B | the 待您清单 and the awaiting-reply index are presented ONCE, with the batch they belong to, ⛔ never reprinted per round | | `SKILL.md:766` | 104 B | 118 B | the director entry gains the pointer 未回字 = 不在 = 停 beside ⛔ 无 Routine/cron | | `references/core-rules.md:147` | 117 B | 119 B | the digest twin of `SKILL.md:766` | The landed lines, verbatim: - director.md:15 — 未回字 = 不在 = 停:维护者不在则本席不运行,⛔ 无巡检、审计、快照、聊天、卡评论。 - director.md:16 — 回来后先补跑审计与重挂复位一次,再只发一行「批 #N 待字;箱内另有 K 张新到未呈」。 - director.md:41 — 决策收件箱 `needs-user-decision` 跨仓勤务,按基本原则批呈;⛔ 不复活已退役的汇总 issue。 - director.md:43 — 一次只开一批:维护者对批 N 回字(一字即可)后,新到才合成批 N+1;续批至清零才收工。 - director.md:58 — 待您清单与待字索引随所属批只呈一次,⛔ 不逐轮重印;座位贴散文降为指针,标签即状态。 - SKILL.md:766 — 项目总监席:维护者在所在仓召唤 `/pm-dispatch director`;⛔ 无 Routine/cron;未回字 = 不在 = 停。 - core-rules.md:147 — 项目总监席由人工召唤,未回字 = 不在 = 停;四职:复核审计、决裁、动作台账、合并审计。 ### Where the card's five rules landed 1. 一次只开一批 — director.md:43 (批 N+1 只在维护者对批 N 回字后开, 一字即可). 2. 未回字 = 维护者不在 — director.md:15, the definition; the same literal is the pointer on SKILL.md:766 and core-rules.md:147. 3′. The seat does not run while absent — director.md:15 (不运行, the list of what stops); the re-run-once-after-return half is director.md:16. Self-wake is NOT restated: the standing ⛔ 无 Routine、无 cron at director.md:7 and SKILL.md:766 already forbids it at every hour, and a second spelling scoped to the absence could be read as a presence-time allowance. 4. The index is presented once, never reprinted; one line on return — director.md:58 and :16. 5. The arrivals are composed into the next batch only after the maintainer answers — director.md:43 (新到才合成批 N+1); ≤5 per batch keeps its two existing homes at director.md:23 and :45. ### The payment: every retired clause and its surviving home Nothing was added, so nothing was retired whole; four clauses left their lines, each with a home named and grepped (`grep -rn` over `.claude/` and `scripts/`; nothing pins the retired text — 0 hits for 停放是设计安全态, 滚入下批, 本文不写模型名 outside these files, and the two SKILL.md hits below are the homes themselves): | retired from | clause | surviving home | |---|---|---| | director.md:15 | 召唤间隔里决策卡与审计窗口停放是设计安全态 | the new :15 itself — 停 IS the parked state, now defined rather than described (PM assumption 3: one rule, not two overlapping ones) | | director.md:15 | 复核放行不在其内,常设归派发席席内 | director.md:28 「本职是审计位,非放行链:放行常设归派发席席内复核」 — verbatim twin | | director.md:16 | 档位由维护者逐场按项目人工定,⛔ 无档位硬门、无免档整理态 | SKILL.md:769 「总监席档位由维护者按项目人工定,每场记入摘要台账;⛔ 无档位硬门、无免档整理态」 — verbatim twin; SKILL.md is read in full by every seat session (the ratchet's own header), and director.md:17 still says the tier is read at session open | | director.md:16 | 本文不写模型名 | AGENTS.md attribution section: no model identifier lands in a PR title or body, a comment, a changeset, a doc or a code comment — and the pre-push hook refuses one in the trailer pair | | director.md:43 | ⛔ 不复活已退役的汇总 issue | moved verbatim to director.md:41, same section (职责二), beside the inbox it is about | | director.md:43 | 批间新到滚入下批,收件箱清零才收工 | the new :43 — 新到才合成批 N+1 (now conditioned on the reply) and 续批至清零才收工 | | SKILL.md:766 | the word 入口 | the entry command `/pm-dispatch director` stays on the line; director.md:14 spells 入口 in full | | core-rules.md:147 | the full duty names (决裁勤务 / 维护者动作台账 / 受管合并审计) | SKILL.md:767 and the four director.md section headings carry the full names; core-rules.md's own header says details live in SKILL.md | 「到合并 = 推进到就绪并点名等待,⛔ 不代维护者合并。」 (director.md:33) was not touched, per the dispatch. ### Why :15 in place rather than a line beside :7 The dispatch suggested the 不在 = 停 rule beside :7. It went into :15 in place instead: :15 is the line PM assumption 3 names as the existing parking rule, in the same 授权与形态 section, and rewriting it is what turns two overlapping rules into one; :7 stays byte-identical, and :68's 「节奏与停放同授权段」 still points at the section where 停 is now defined. Fewer lines move, and the no-self-wake rule keeps exactly two homes (:7 here, :766 in SKILL.md). ## PM mechanism assumptions — measured on this branch 1. **Confirmed.** Branch base `8ddefbc`; the three files are byte-identical between `0862063` and it (`git diff --stat 0862063 8ddefbc -- ` the three paths: empty). `grep -E '未回字|不在 = 停|一次只开一批|只呈一次'` over each of the three files at the base: 0 hits, exit 1, three times. Positive control: `grep -n 'Ruling:'` hits director.md:11, exit 0. 2. **Confirmed, and it shaped the second commit.** The first commit listed 自唤醒 inside :15's stopped-seat list; the second commit removed it — a third spelling of the no-self-wake rule, and one an absent-scoped list could invert. The rule keeps its two homes and :766 now carries the 未回字 = 不在 = 停 pointer beside it. 3. **Confirmed and acted on**, see the payment table: old :15's 停放是设计安全态 became the definition line; :68's pointer is untouched. 4. **Confirmed.** `check:pm-skill-ratchet` prints 72 / 72, 813 / 813, 151 / 151 with `MAX_LINE_BYTES = 120`; `check:pm-skill-id-lint` matches a hash followed by three or more digits, so the 反例's batch numbers live only in this body; `check:pm-governed-prose` passes. ## Verification Gate union derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; the tool takes the change set from the merge base) — 21 commands, byte-identical between the first head and the final head `33d8403`. Run on `33d8403`, every exit captured before any pipe (redirect first, then the status): **21 of 21 exit 0** — `check-closing-keyword-parity` (and `--self-test`), `check-comment-mask-corpus`, `check-governed-queue-guard --self-test`, `check-harness-current --self-test`, `@objectstack/lint check:doc-formula-expressions`, `check:agent-test-spelling`, `check:cross-package-test-inputs`, `check:doc-authoring`, `check:driver-memory-census`, `check:gitlink-declared`, `check:nul-bytes`, `check:pm-expected-skips`, `check:pm-governed-merges`, `check:pm-governed-prose`, `check:pm-half-states`, `check:pm-skill-id-lint`, `check:pm-skill-ratchet`, `check:refd-timer-probe`, `check:skill-frame-sync`, `check:watch-hint-literal`. `--ran` reconciliation with the exit codes recorded: 21 derived, 21 run, 0 NOT-MEASURED — a DERIVED zero, none of the 21 is exit 3. - `check:doc-formula-expressions` first refused with exit 3 PREREQUISITE NOT MET (`@objectstack/formula` and `@objectstack/lint` not built) — recorded as NOT MEASURED, not red; the `@objectstack/lint` closure (spec, formula, sdui-parser, lint) was then built under the shared verify lock (`VERDICT command-exit 0`, 195 s held) and the gate re-run green. - Extra, run on purpose: `check:pm-settings-deny-roster` exit 0 (the derivation marks its roster as living under `.claude/`, where silence is evidence in neither direction). - Ratchet positive control (taken on the first head `5b04dc7`; the file is 72 lines and the ceiling row untouched on both heads): one bullet appended to director.md → 73 lines → `check-skill-line-ratchet` exit 1 with its own verdict line 「is 73 lines; the ratchet ceiling is 72」; restored with `git checkout HEAD -- ` that path, proven by `git hash-object` equal to the HEAD blob, `git diff HEAD` empty and `git status --porcelain` empty; the gate exits 0 again after the restore. - Control-character self-scan (`grep -naP` over the C0 set plus DEL) on the three files: no match. Bare card ids on the added lines: none. - `check:pm-dispatch-gates` (the slow whole-tree family the dispatch named) is running detached on `33d8403` as this body is written; its exit lands in the `os-dev-report` comment on the card — this body is written once. - Governed tier, measured locally: ⛔ GOVERNED — Tier S(席内达档复核落地): every governed path here lies under a Tier S surface, so the OWNING (`check-governed-merges.mjs --test` on the three paths with the diff's 7 / 7 line counts). This PR stays DRAFT; the owning seat lands it on a `## Contract review` record for the current head. No changeset: `.claude/**` publishes nothing from any released package. Label `skip-changeset` applied. ## 维护者速读(草稿) **改了什么** — 总监席章程里改了七行,每个文件一行不增不减:① 定义句「未回字 = 不在 = 停」——您对上一批没回字就等于您不在,不在期间本席不运行:不巡检、不做合并审计、不给决策箱拍快照、不在聊天里发任何东西、不在任何卡上留评论;② 您回来时,本席先把合并审计与重挂复位补跑一次,然后只发一行「批 #N 待字;箱内另有 K 张新到未呈」;③ 一次只开一批:批 N+1 只在您对批 N 回字(一个字也算)之后才由新到的卡合成;④ 待您清单与待字索引跟着所属批只呈一次,不逐轮重印;⑤ SKILL.md 总监席条目和核心条款摘要各带上「未回字 = 不在 = 停」这一句指针。自唤醒不再重复写:章程原有的「⛔ 无 Routine、无 cron」已经禁了它。 **为什么改** — 您的两句原话:「你浪费了我很多token,上一批决裁没有回复说我我人不在,你不应该开启下一批决裁。立skills卡」与「项目总监由维护者召唤,维护者不在期间什么都不用做。」反例:批 168–178 自 2026-09-18 起无一回字,总监席照样开了批 179–191(13 批 16 张),每批带完整四轴与逐批重印的累计索引,外加每小时一段状态散文,还用 send_later 自唤醒 68 次——这些 token 全烧在您不在的时候。旧章程只写「批间新到滚入下批」,没写下一批何时可开、没定义「不在」、没说清单只呈一次。 **风险与代价(含回滚)** — 行数零增长,每行 ≤120 字节,四条被挤出的旧句各有出处可查(上表)。代价:总监席不在场时完全静默,受管合并审计推迟到您回来后补跑一次;这是您第二句话的直接后果。回滚:`git revert` 这两个提交即可,不涉及任何脚本或门禁。 **席位意见** — (留空,席位复核时填) **你要做的** — 无。`.claude/**` 是 Tier S,由席位在复核记录后落地;这份 PR 是草稿,不需要您点击。 ## Acceptance notes Noted, not filed — none is a reproducible defect, a declared-contract violation, or a metadata-authoring trap: - `references/decision-analysis.md:12` already says 「呈完即停等回批」 for the standing batch flow; the director seat did not obey it and the charter now spells the rule for that seat. Whether decision-analysis.md should point at director.md:43 is a question for its next editor. 承接者:无. - `scripts/pm/check-prior-rulings.mjs` header cites `references/lanes/director.md:45` for the grep step that has sat at :47 since PR objectstack-ai#19303 inserted three lines above it — a comment's line number, not a pin (the script reads no line numbers). 承接者:无. - director.md:68 「⛔ 无 cron,节奏与停放同授权段」 now points at a section where 停 is defined while the batch cadence itself lives at :43; left untouched to keep this diff at seven lines. 承接者:无. --- _Generated by [Claude Code](https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #19256
Clause-②: no
A ruling that lands as a COMMENT left the card BODY asserting the opposite, and nothing reconciled the two. The body is what a taker reads first, so a ruled card went on advertising itself as un-dispatchable — measured at 11 and 15 days on two live cards.
The director charter's 授权与形态 section already fixes the first line of the ruling COMMENT. This adds the sibling rule for the first line of the BODY, in the same stroke that records the ruling.
What changed
.claude/skills/pm-dispatch/references/lanes/director.md— three lines, inserted directly after the 「裁决评论首行固定一条」 line. Nothing else in the file moved; no line was deleted or re-wrapped.Their content, described rather than quoted (the landed bytes carry the corpus's usual angle-bracket placeholders; this body spells them as words, per the body-sanitizer rule in AGENTS.md):
Ruled:followed by the ruling comment id, the letter, and the UTC stamp, separated by the middle dot the sibling comment line already uses. Closes with ⛔ 不新增标签与状态 — both measured instances were in the correct state the whole time, so the clause adds no label and no state.post-stamped --body, whose acknowledgement flag names the newest comment, so that acknowledgement IS the thread read the charter's 裁前先读线程 line already requires. No new tool capability: the act is the seat's, spelled in the procedure.Why 授权与形态 and not 职责二
One home, not both. 授权与形态 is the shape section, and the new lines sit beside the rule that fixes the ruling comment's first line — body-first-line and comment-first-line are siblings. It also reaches every path that records a ruling: the 一类自裁 self-ruling that 录裁转工作态 without going to a batch, the 代裁 channel, and the maintainer's 一行回批 alike. Under 职责二 決裁勤务 the same text would read as scoped to the batch-reply path, which is the one path the two measured instances did not take alone.
Measurements taken on this branch
grep -nE 'banner|正文|body'over the charter returns 0 hits, exit 1; the positive controlgrep -n 'Ruling:'hits line 11, exit 0. The ruling procedure had no body-side act.needs-user-decision→pm:queuetransition, with the stale paragraph left in place below it and explicitly marked stale. objectui#7750'supdated_atreads 2026-09-20T05:58Z — the filing round's own remediation of the two instances, not a mechanism. Nothing in.claude/skills/**spells that shape (grep -rn 'CARD IS RULED'over.claude/andscripts/pm/: 0 hits), so the gap the card names is live and this PR is what makes the remediation a rule. The landed clause is a floor: a one-line banner, which the two measured bodies exceed.scripts/pm/post-stamped.mjsspells the flag as anack-throughoption belonging to a--bodyrefresh, refusing a body write while unacknowledged newer comments exist and refusing an id that is not the newest. Re-verified against currentorigin/mainafter the merge below (17 occurrences), since that file moved on main while this branch was open.check:pm-skill-id-lintexits 0: the landed lines cite no card, and the two objectui instances appear only in this PR body.node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/references/lanes/director.md→ GOVERNED, Tier S (.claude/**), 1 of 1 path hitting the register. This PR stays DRAFT; the owning seat lands it on a## Contract reviewrecord for the current head.⛔ One gate is RED, and it cannot be greened from this PR's declared file surface
pnpm check:pm-skill-ratchet→ exit 1, its own verdict line:The arithmetic, in full:
So the two currencies the ratchet accepts are both unavailable here. Deleting content is not one: every line in this charter is a live rule with a reader, and cutting one would be a scope expansion this card does not authorize. Re-wrap is not one either — there is no soft-break slack to reclaim, and buying lines for new content with a re-wrap is forbidden to a dev seat regardless.
That leaves the route the ratchet itself names and the ceiling row's own history records — the row was raised 75 → 77 once before on exactly this file at headroom 0, paid for by a maintainer directive that dictated the lines. That raise is not in this PR's file surface (
scripts/pm/check-skill-line-ratchet.mjsis notdirector.md), and a dev seat does not set it. The lines are landed, the ceiling row is untouched, and the measured count is reported red rather than hidden. To green this PR the ceiling row needs 72 → 75 with the authorizing ruling quoted in the raising PR.Verification
Gate union derived and run on the final head
af00eb6, with each exit code captured before any pipe:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths) → 16 commands, byte-identical to the derivation the dispatch carried.node scripts/pm/dispatch-gates.mjs --ran …→ ✓ 16 derived families accounted for — 16 run, 0 NOT-MEASURED (a DERIVED zero: all 16 recorded an exit code and none is 3).check:pm-skill-ratchetexit 1, above. Green:check-closing-keyword-parity(and its self-test),check-comment-mask-corpus,check-harness-current --self-test,check:doc-formula-expressions,check:agent-test-spelling,check:cross-package-test-inputs,check:doc-authoring,check:driver-memory-census,check:nul-bytes,check:pm-governed-merges,check:pm-skill-id-lint,check:refd-timer-probe,check:skill-frame-sync,check:watch-hint-literal.check:pm-settings-deny-roster→ exit 0, run on purpose: the derivation marks it as keeping its roster under.claude/, where silence is evidence in neither direction.check:nul-bytes:grep -naPfor the C0 set and DEL returns no match.origin/mainwas merged into the branch (it had moved 3 commits, one of themscripts/pm/post-stamped.mjs, which the new clause names);director.mdwas untouched on main and the branch diff is still exactly +3 lines in that one file. Build state was refreshed for the one package the union needs compiled (@objectstack/lintand its closure) rather than the whole tree — a declared narrowing, since the diff is three markdown lines.No changeset:
.claude/**publishes nothing from any released package. Labelskip-changesetapplied.Acceptance notes
Noted, not filed — neither is a reproducible defect, a declared-contract violation, or a metadata-authoring trap:
Ruling:with a batch and item number; the body banner spellsRuled:with the comment id. The asymmetry is intentional — the body must point at a retrievable id, which the charter's 出决策箱须引可取回的裁决 id rule already requires — but the two spellings sitting one line apart will invite a future author to unify them. 承接者:无.Generated by Claude Code