Commit 41ff021
docs(agents): the contract-review carrier is the seat's to hang;
Fixes #18181
## What changed
Three lines across two governed rules-layer files; line counts unchanged
(os-dev.md 403/403, SKILL.md 812/812), every edited line at or under the
120-byte cap, no re-wrap anywhere, and the decision-frame block SKILL.md
:734–:755 untouched (md5 `3327d02c56f8a0eca88569dad2270f32` before and
after).
**`.claude/agents/os-dev.md` :287** — the line the card names,
re-derived on `b3b43b6` (the card body's `:288` had drifted; the filer's
correction comment 5664488072 gives :287):
```diff
-- 认领写 `Clause-②: yes` ⇒ 开 PR 同笔挂 `needs:contract-review`,报告附 `--pair N` 退出码。
+- `needs:contract-review` 归席位,⛔ 不挂不摘不等;报 PR 上有无与 `--pair PR-NUMBER` 退出码作读数。
```
The dev no longer hangs the carrier. It never hangs, removes or waits
for `needs:contract-review`; it reports whether the label is present on
the PR and the `--pair PR-NUMBER` exit code — as a reading, not as a
gate it must clear.
**`.claude/agents/os-dev.md` :301** — the neighbouring `skip-changeset`
label-write rule (mechanism assumption A2, same file surface, same class
— an additive label write the container may refuse):
```diff
-- 被拒 ⇒ 停下报 `blocked` 点名端点与状态码,⛔ 不换 MCP 写道;写后必做对比式读回。
+- 被拒 ⇒ 报端点与状态码、席位代挂,⛔ 不报 `blocked`、不走 MCP;写后必做对比式读回。
```
One rule now covers both carriers: a refused additive label write is
reported with endpoint and status and the seat hangs the label; the dev
never reports `blocked` for it and never switches to an MCP write.
**`.claude/skills/pm-dispatch/SKILL.md` :646** — the one `--pair N`
token (REGION claim: this line only):
```diff
-- `Clause-②: yes` 认领同笔在卡上挂标;PR 开出即读 `check-clause2-carriers --pair N` 为 0 再请审。
+- `Clause-②: yes` 认领同笔卡上挂标;PR 开出读 `check-clause2-carriers --pair PR-NUMBER` 为 0 再请审。
```
`--pair PR-NUMBER` is now the only spelling in the three governed files
(contract-review.md :42 already spelled it so).
## Why
Direction A of the card's grading (comment 5673619664), executed as
ruled: the `needs:contract-review` carrier is the PM seat's to hang — on
the card at the claim and on the PR when the PR appears — which is what
every seat already does; the dev never blocks on a label write; `--pair
PR-NUMBER` becomes the only spelling. Option B (granting the dev
container the label write) is a permissions widening and was ruled not
this card.
The two defects the card and its correction measured: a dev that obeyed
:287 literally was refused the additive label write by its container's
write classifier on two independent objectui runs, while an objectstack
dev could write it — per container, unknowable in advance; and `--pair
N` bound `N` to the nearest referent, the card, while the script takes a
PR number and answers a card number with exit 2 (UNJUDGED), which a
naive capture reads as a red gate.
## Mechanism readings
- **A1** — :287 is the line on `b3b43b6` (108 B; the card's :288 is the
`skills/**` line). Replaced as above. Under the byte cap the `Clause-②:
yes` trigger did not fit, so the rule is now unconditional: on every PR
the dev hangs nothing, removes nothing, waits for nothing, and reports
carrier presence plus the `--pair PR-NUMBER` exit code. That superset is
deliberate — `--pair` also carries the widening-tell row, which only
fires on a `Clause-②: no` claim, so a reading on every PR is worth more
than one on `yes` claims alone.
- **A2** — measured on the text: :301's `被拒 ⇒ 停下报 blocked` is the same
container-refusal class one line over, and nothing about
`skip-changeset` requires the dev to block on it: CI's changeset gate
reads the label, the dev's report says whether it landed, and the seat
can hang it on that report. So one rule covers both carriers and :301
changed as above; :299 (the label is your step, hang it as soon as the
PR opens) and :300 (additive endpoint) stay — the dev still attempts the
write. The live half of the measurement is this PR's own
`skip-changeset` write; its outcome (landed, or the endpoint and status
of the refusal) is in the `os-dev-report` comment on the card, not here,
because the PR body is written before the label write and is not
re-sent.
- **A3** — `git grep -n -- '--pair N\b' -- '.claude/**'
'scripts/pm/**'`: before 6 hits (os-dev.md :287, SKILL.md :646, and 4 in
`scripts/pm/**`); after 4 hits, all outside the claimed surface:
`check-clause2-carriers.mjs` :24 and :490 (docblocks), :4947 (a
self-test title), and `check-widening-tells.mjs` :492 (docblock). In the
three governed files the count is 0. Lit control `--pair PR-NUMBER`: 1
before (contract-review.md :42), 3 after (:42, os-dev.md :287, SKILL.md
:646). The script docblocks bind `N` to a PR number in their own text
(:11 spells `--pair 13910 # ONE PR`), so the ambiguity is not live
there; noted below, not edited.
- **A4** — measured from the sibling checkout on this container: `cd
/home/user/objectui && PM_SWEEP_REPO=objectstack-ai/objectui node
../objectstack/scripts/pm/check-clause2-carriers.mjs --pair 9552` exits
0 with the provenance line `every row below is read from
objectstack-ai/objectui (source: PM_SWEEP_REPO)` and the verdict `PR
#9552 / card #6910 — the clause-② declaration is readable in the fixed
spelling and both carriers agree` (token present, 3 reads served; the
proxy re-exec happened by itself). A control on objectui#9495 (no longer
open) exits 2 — UNJUDGED, with the board still retargeted. So the
invocation works from a sibling checkout. The byte cap leaves no room on
:287 for the invocation (the line stands at 119 B), so the line carries
only the flag spelling; the script header (:20–:24) already spells the
sibling invocation and every run prints the board it read, which is the
remedy text the ruling asks to keep in the script.
## Gates
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths; change set from the merge base):
18 families on `a2cbbef0`, re-derived on the merged head `0c934967`
(merge base `68fea8bc`) — identical list. All 18 run in the foreground
on `0c934967`, exit codes captured by redirect before any pipe, all 0:
`check-closing-keyword-parity` (+ `--self-test`) ·
`check-comment-mask-corpus` · `check-governed-queue-guard --self-test` ·
`check:doc-formula-expressions` (lint pkg) ·
`check:agent-model-declared` · `check:agent-test-spelling` ·
`check:commit-card-trailers` · `check:doc-authoring` ·
`check:driver-memory-census` · `check:nul-bytes` ·
`check:pm-governed-merges` · `check:pm-governed-prose` ·
`check:pm-skill-id-lint` · `check:pm-skill-ratchet` ·
`check:refd-timer-probe` · `check:skill-frame-sync` ·
`check:watch-hint-literal`
Reconciliation on `0c934967`: `✓ dispatch-gates --ran: 18 derived
famil(ies) accounted for — 18 run, 0 NOT-MEASURED (a DERIVED zero — all
18 recorded an exit code and none of them is 3)`.
Verdict lines: `✓ check-skill-line-ratchet: .claude/agents/os-dev.md is
403 lines (ceiling 403; headroom 0)` · `✓ check-skill-line-ratchet:
.claude/skills/pm-dispatch/SKILL.md is 812 lines (ceiling 812; headroom
0)` · `✓ check-agent-model-declared: 1 agent definition(s) under
.claude/agents/ all declare a model` · `✓ check-skill-id-lint: 27
file(s) clean` · `✓ check-skill-frame-sync: the one declared copy of the
decision frame is internally coherent`.
`check:doc-formula-expressions` first answered exit 3 (PREREQUISITE NOT
MET — `@objectstack/formula` and `@objectstack/lint` unbuilt in a fresh
worktree); prerequisite built under the verify lock (`turbo run build
--filter=@objectstack/formula --filter=@objectstack/lint`, VERDICT
command-exit 0), rebuilt again after the merge, then the gate exits 0.
The exit 3 is not a measurement and is not counted as one.
Not run locally, by design: the 11 wide-population families and the
whole-repo `pnpm lint` sweep belong to CI. Control-byte self-scan on
both edited files: no hits.
## Line budget
| file | before | after | ceiling | edited lines (bytes) |
| --- | --- | --- | --- | --- |
| `.claude/agents/os-dev.md` | 403 | 403 | 403 | :287 108→119 · :301
110→114 |
| `.claude/skills/pm-dispatch/SKILL.md` | 812 | 812 | 812 | :646 116→118
|
Paid by density in place; no line merged or split.
## Deviations, declared
1. SKILL.md :646: the token swap alone (`N` → `PR-NUMBER`, +8 B) puts
the line at 124 B, over the 120-byte cap. Two characters were dropped on
the same line (`在`, `即`), content unchanged, landing at 118 B. Still
inside the one-line REGION claim.
2. os-dev.md :287: the `Clause-②: yes` trigger is gone for the byte cap;
the rule is unconditional (see A1). If the seat wants the trigger back,
the price is the `作读数` tail or the `不等` limb.
3. os-dev.md :287 does not spell the objectui invocation (A4); the line
names the flag, the script header names the sibling form.
4. os-dev.md :301 edited under A2 (inside the file surface and the
card's class). objectui's copy and the dispatch-order template are
untouched.
5. `skip-changeset`: nothing published moves — `.claude/**` is on the
fast path (no package `files[]` ships it).
6. `origin/main` merged at `68fea8bc` (one spec commit, nothing on this
surface) before opening; #18242 remains open on SKILL.md :172–:185 —
disjoint from :646.
## Acceptance notes
- **To file (class b, for the seat — dedupe words:
`needs:contract-review`, `PR 创建者`, `同笔挂`, SKILL.md, direction A):**
SKILL.md :645 still reads 「`needs:contract-review`(恒英文)由 PR
创建者随可复审契约增量同笔挂:draft PR,或先到的报告。」 For a dev-created PR the creator is the
dev, which direction A says never hangs it. Outside this card's REGION
claim (:646 only), so not edited here; it sits on the SKILL.md chain the
seat owns.
- noted, not filed: 4 `--pair N` spellings remain in `scripts/pm/**`
docblocks and one self-test title (listed under A3); each is next to
text that binds N to a PR number. 承接者:无.
- noted, not filed: os-dev.md :300's 「可达性按会话探,先探后用」 is redundant with
the new :301 — the write attempt is the probe. Left as is; deleting it
frees bytes, not a line. 承接者:无.
- Observed on this PR after opening: the labels present are reported in
the `os-dev-report` comment with a read-back; under one shared identity
the author field says nothing about who hung what.
## 维护者速读(草稿)
**改了什么**:改了两个受管规则文件里的三行,行数不变。开发 agent
定义(os-dev.md)一行:`needs:contract-review` 这个标签归席位挂,开发 agent
不挂、不摘、不等,只在报告里写 PR 上有没有它、以及 `--pair PR-NUMBER`
的退出码,作读数。另一行:加法写标签被容器拒绝时,报端点与状态码、由席位代挂,不再报 `blocked`、不走 MCP。PM
技能(SKILL.md)一个 token:`--pair N` 改成 `--pair PR-NUMBER`。
**为什么改**:两次独立实测里,开发 agent 的容器拒绝写标签,而规则要求它开 PR 同笔挂——照做就失败、如实报告又像抗命。分诊裁定 A
向:载体由席位挂(认领时挂卡、PR 出现时挂 PR),开发 agent 只报所见。同一行的 `N` 让人绑到卡号,脚本要的是 PR
号,给卡号返回 exit 2(未判),会被当红灯读;`PR-NUMBER` 是唯一拼写。
**风险与代价(含回滚)**:行数与上限不变,每行 ≤120 字节,决策框架块未动。风险是开发 agent 不再自己挂载体,若席位漏挂,条款②
PR 的载体会缺失——但席位侧已经是现行做法,且开发 agent 报告的 `--pair` 读数会暴露缺失。回滚 = revert 本
PR,无其他副作用。
**席位意见**:
**你要做的**:审阅上面三行措辞(尤其 :287 去掉了 `Clause-②: yes` 触发词、改为对每个 PR
都成立);同意即给授权批准,由席位落地;不同意请指出要保留的措辞。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_
---------
Co-authored-by: Claude <noreply@anthropic.com>--pair PR-NUMBER is the only spelling (#18250)1 parent 1297178 commit 41ff021
2 files changed
Lines changed: 4 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
284 | 284 | | |
285 | 285 | | |
286 | 286 | | |
287 | | - | |
| 287 | + | |
288 | 288 | | |
289 | 289 | | |
290 | 290 | | |
| |||
298 | 298 | | |
299 | 299 | | |
300 | 300 | | |
301 | | - | |
| 301 | + | |
302 | 302 | | |
303 | 303 | | |
304 | 304 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
642 | 642 | | |
643 | 643 | | |
644 | 644 | | |
645 | | - | |
646 | | - | |
| 645 | + | |
| 646 | + | |
647 | 647 | | |
648 | 648 | | |
649 | 649 | | |
| |||
0 commit comments