Repository navigation
Commit be1fd31
docs(os-dev, pm-dispatch): a breaking PR runs the full dogfood battery on its final head; the review checklist reads the field before arming (#24917)
Fixes #24890
Clause-②: no
## Summary
A dispatched breaking change — a `!` in the PR title, or a `(narrowing)`
arm on the `Clause-②:` line — now owes the full dogfood battery on its
final head, records the result in a new `dogfood_battery` report field,
and the review checklist reads that field before ready / auto-merge /
queue. The rule is stated once, in `.claude/agents/os-dev.md`, by
extending the clause that already said 「dogfood 只在终 head 跑一次」; the
checklist line reads it. No CI, workflow, gate or script changes; two
governed `.claude/**` files, both at their line ceilings before and
after (404/404, 77/77).
## Where the rule landed, and why there (H2, H5)
- `.claude/agents/os-dev.md` L279 read 「PR 开出后合 main 只因冲突、main 致 CI
红、出队,后只跑 §10 复检;dogfood 只在终 head 跑一次。」 Commit `b8762c089` introduced
that clause together with AGENTS.md §10's "dogfood once, on the final
head"; the parent tree's only other "dogfood" in AGENTS.md is §7's
full-battery enumeration ("`main`'s hourly and push runs carry the full
battery — dogfood, e2e, …"). So the word there names the Dogfood
Regression Gate suite, with no trigger and no command. The clause is
split off L279 and extended in place into three rule lines (trigger ·
command · red disposition), so the rule stays stated once. AGENTS.md §10
itself is Tier H and outside the file surface; it keeps its trigger-less
sentence, and the role file now carries the trigger.
- The report field `dogfood_battery` is added to the JSON template in
the same file (SKILL.md 〈报告契约〉: 「终报 JSON 形状住
`.claude/agents/os-dev.md`,⛔ 不抄第二份」). The report-marker readers in
`scripts/pm/check-half-states.mjs` read only the marker line and
`open_questions`, so an added key is additive.
- The reader is `references/review-checklist.md` 〈测试与门禁证据〉, whose last
block is already the seat's pre-arm read (「翻 ready、挂 auto-merge 或入队前…」,
「每个门禁读数先钉到 PR 当前 head」, 「dev 本地并集钉 head」). The new line sits at its end.
`landing-operations.md` 〈入队与落地〉 decides the 条款② gate and the queue
mechanics, not the dev-evidence read, and its ratchet is 101/101 — no
line goes there, no second home. `execution-duties.md` and
`dispatch-runbook.md` are untouched: the standing clause is
unconditional, and os-dev.md 〈标准条款住在这里〉 says unconditional clauses live
in the role file, not in the dispatch brief.
## The trigger spelling (H1 — a correction to the card)
The card writes the declaration as `Clause-②: yes (narrowing)`; the
dispatch brief's hypothesis names `Clause-②: no (narrowing)`. Both are
legal spellings, and neither is the trigger by itself:
- `scripts/pm/clause2-line.mjs` (the fleet's one reader) closes the
value set at `yes | no` and the arm set at `(widening) | (narrowing)`,
and documents the four combinations: `yes (narrowing)` = "a diff that
widens one surface and narrows another. Both facts are true and both are
read"; `no (narrowing)` = "NOT a widening, but breaking. This is the
whole point of the arm"; `no (widening)` = malformed.
- `scripts/pm/record-recognisers.mjs` reads the narrowing face from "A
`narrowing` arm on ANY of them ⇒ the narrowing face (`yes (narrowing)`
included)".
- AGENTS.md step 3: "`(narrowing)` is BREAKING".
- The tree at head carries both:
`.changeset/24583-7b3-organizationless-counter-retired.md` (`yes
(narrowing)`), `.changeset/24765-team-sessions-related-list.md` and
`.changeset/24813-adopt-orphans-readonly-package.md` (`no (narrowing)`).
So the rule is written against the ARM, value-agnostic: 「`Clause-②:` 带
`(narrowing)` 臂(yes/no 皆算)」. The card's `yes (narrowing)` is one of the
two spellings, not the only one; no third spelling was introduced.
Related observation (no change here): `references/contract-review.md`
L12 spells only `no (narrowing)` for the tier derivation while the
recogniser reads either value — noted under Acceptance notes.
## The command (H4)
- `packages/qa/dogfood/package.json`: `"test": "vitest run"`.
`vitest.config.ts`: `shard: process.env.OS_TEST_SHARD` is the only shard
mechanism; two projects (`shared-showcase`, `isolated`), `OS_CLOUD_URL:
'off'` and `OS_REGISTRY_LOG: 'warn'` set per project inside the config.
- ci.yml `dogfood` job (`Dogfood Regression Gate (n/3)`): builds the
closure with `pnpm turbo run build --filter=@objectstack/dogfood`, then
runs `OS_TEST_SHARD=n/3 pnpm turbo run test
--filter=@objectstack/dogfood` — the same `test` script, sharded only by
that variable — and grades the log with
`scripts/check-test-completeness.mjs`.
- Measured at `a8b44d407` in `packages/qa/dogfood`: `npx vitest list
--filesOnly` prints 287 files; `find -name '*.test.ts'` counts 287 (265
of them `*.dogfood.test.ts`). The plain `pnpm --filter
@objectstack/dogfood test` with no `OS_TEST_SHARD` therefore runs the
whole set — the union of the three CI shards — so the card's command IS
the full battery, provided the closure is built first and nothing shards
or narrows it. The rule says so: 「命令同 CI:按 ① 建闭包,锁下 `pnpm --filter
@objectstack/dogfood test`;⛔ 无 `OS_TEST_SHARD`、无路径」 (① = the
closure-build rule in 〈本地验证范围〉).
- Beyond the ~10-minute foreground cap: L88 is generalised from the one
`check:pm-dispatch-gates` case to 「超前台上限者(`check:pm-dispatch-gates`、全量
dogfood):`nohup … > LOG 2>&1 &` 后 `tail --pid`」. The verify lock still
applies (rule 1 of 〈资源纪律〉 — the battery is a heavy run), so the
backgrounded command is the lock wrapper itself, and the lock's VERDICT
line goes into the report field.
- NOT MEASURED: the battery's local wall time. ci.yml's own comment
reads "the ~7½-minute dogfood suite" (a CI runner, sharded); this PR is
not a `!`/narrowing change, so it owes no run, and a 287-file boot
battery on the shared box was not spent on a reading the rule does not
depend on.
## Paying the ratchets (H3)
Both files sat at zero headroom. Every added line is paid by deleting a
line that states no rule, each named with where its content still lives.
No re-wrap was used as payment; the four added prose lines measure 119 /
120 / 119 / 118 bytes against the 120-byte cap, and the JSON line sits
in a fence, which the cap exempts.
`.claude/agents/os-dev.md` — 404 → 404 (+3 rule lines, +1 JSON template
line, −4; L88 and L279 rewritten in place):
- deleted 「陷阱在 shell 用法里,不可机械化;⛔ 别等机械强制。」 — a meta statement; the file's
own L20 already says it carries only 「钩子无法机械强制的条款」.
- deleted 「⛔ 不把这类结果写进报告当作一次失败的测量。」 — restates L176 「两类跑了却没测到,都读作 NOT
MEASURED,不读作绿也不读作红」 for class ②.
- deleted 「消融本就为证明新门禁能失败时,这份假绿读作门禁没触发,于是有人去弱化一条好门禁。」 — the story behind
the asymmetry; the rule stays in the asymmetry line above it and in the
three leg lines below it (build each leg, prove it reached `dist/`).
- deleted 「例:反向验证方向反转、前提死了、某个产物在此无意义;按模板硬造比留白更糟。」 — an example list;
each case is a rule elsewhere (「报告你实际观察到的方向」, `premise_still_valid:
false`, 「报告模板是工具,不是真相」).
- L88 「测试电池 `pnpm check:pm-dispatch-gates` 亦属 `check:*`:不走锁,…」 →
「超前台上限者(`check:pm-dispatch-gates`、全量 dogfood):…」 — the `check:*` /
no-lock classification is L87's, by name; the nohup + tail form is
generalised rather than stated twice.
`.claude/skills/pm-dispatch/references/review-checklist.md` — 77 → 77
(+1, −1):
- deleted 「这是合并前唯一的人工关口,⛔ 不只信报告;合并后那一读见 `landing-operations.md`。」 — the
rule lives in `execution-duties.md` 〈复核〉 「你是记录在案的复核人:对 GitHub 核验,⛔
不对报告的自述核验」 and the pointer in its preamble 「落地窗口另读
`landing-operations.md`」; the first clause is narrative.
## Verification
Local union, run after the final commit on `bd8baf269` (working tree
clean, `git status --porcelain` empty), every exit code captured before
any pipe:
| family (as `dispatch-gates --commands` printed it) | exit | the gate's
own verdict line |
|:--|:--|:--|
| `node scripts/check-closing-keyword-parity.mjs` | 0 | OK (3 parsers
agree on all 9 keywords …) |
| `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | 40
assertions, 5 mutations each driven to red |
| `node scripts/check-comment-mask-corpus.mjs` | 0 | 9691 files, 0
disagree, 0 unparseable |
| `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0 | 322
cases pass |
| `node scripts/pm/check-harness-current.mjs --self-test` | 0 | all 26
cases passed |
| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` |
0 | 23 record-scoped formula example(s) across 476 files judged clean
(first attempt exit 3, PREREQUISITE NOT MET: `@objectstack/formula` /
`@objectstack/lint` unbuilt — built under the verify lock, VERDICT
command-exit 0, held 2s, then re-run) |
| `pnpm check:agent-model-declared` | 0 | 1 agent definition(s) under
.claude/agents/ all declare a model |
| `pnpm check:agent-test-spelling` | 0 | 0 violations — 599 file(s) · 14
separator(s) JUDGED |
| `pnpm check:commit-card-trailers` | 0 | self-test 81 cases pass |
| `pnpm check:cross-package-test-inputs` | 0 | 31 package(s) read
outside themselves, all declared |
| `pnpm check:doc-authoring` | 0 | 53 published skill files clean; 19144
customer-facing string(s) clean |
| `pnpm check:driver-memory-census` | 0 | every declaration is ledgered,
every ledger entry is live |
| `pnpm check:gitlink-declared` | 0 | OK (11488 index entries — 0
gitlink(s)) |
| `pnpm check:nul-bytes` | 0 | OK (scanned 11479 text file(s) … no raw
ASCII control bytes) |
| `pnpm check:pm-governed-merges` | 0 | self-test 500 assertions |
| `pnpm check:pm-skill-id-lint` | 0 | 87 file(s) clean (pattern
/#[0-9]{3,}/g) |
| `pnpm check:pm-skill-ratchet` | 0 | `.claude/agents/os-dev.md` is 404
lines (ceiling 404; headroom 0) · `review-checklist.md` is 77 lines
(ceiling 77; headroom 0) |
| `pnpm check:refd-timer-probe` | 0 | 9685 source file(s) swept |
| `pnpm check:required-contexts` | 0 | 5 required context name(s) pinned
across 3 workflow(s) |
| `pnpm check:skill-frame-sync` | 0 | the one declared copy of the
decision frame is internally coherent |
| `pnpm check:watch-hint-literal` | 0 | 76 declaration(s) across 4
rostered name(s) |
`node scripts/pm/dispatch-gates.mjs --ran ran.list --repo
objectstack-ai/objectstack` → exit 0: "Run reconciliation — 21 derived,
21 run, 0 NOT-MEASURED, 0 UNRUN."
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` with no paths derived the same 21 commands
the dispatch brief listed; `--ran` reconciliation below.
- No package is touched ⇒ no ① closure build and no ② package
test/typecheck; `test:repo` and every CI family are read from this PR's
check-runs by the seat, not re-run here.
- No test-pinned behaviour changed ⇒ no ablation.
- `skip-changeset` is declared on this PR: `.claude/**` is on the fast
lane, nothing published moves.
## Acceptance notes
- `references/contract-review.md` L12 names only `Clause-②: no
(narrowing)` as the narrowing spelling that lifts the tier, while
`record-recognisers.mjs` lifts it on the arm whatever the value (`yes
(narrowing)` included). Prose narrower than its reader; a governed
reference outside this card's surface. Observation — carrier: none.
- `os-dev.md` 「证据已销毁(评论、卡或 PR 答 404)的复核项记 NOT MEASURED…」 and
`review-checklist.md` 「复核项证据已销毁(…)⇒ 记 NOT MEASURED…」 state one rule on
both sides; each has its own reader (dev / seat), so neither was spent
here. Observation — carrier: none.
- `vitest list --filesOnly` prints 287 under `OS_TEST_SHARD=1/3`, `2/3`
and `3/3` alike: listing is not sharded, only `run` is. Not a defect; a
reason the rule counts files from the plain list.
- ci.yml's "~7½-minute dogfood suite" comment predates the three-shard
job and was not re-measured. Observation — carrier: none.
## 维护者速读(草稿)
**改了什么**:给开发 agent 的角色文件加了一条规则:PR 标题带 `!`、或 changeset 的 `Clause-②:` 行带
`(narrowing)` 臂(yes/no 皆算)的卡,在最终 head 上跑一次全量 dogfood(与 CI
同一条命令、不分片不缩范围),结果写进终报的新字段 `dogfood_battery`;复核清单在翻 ready / 挂 auto-merge
/ 入队前核这个字段。红的处置照卡:是本改动自己的后果(陈旧 fixture)就同 PR
修,否则照实报,永不跳过或收窄。两个文件各删了等量不含规则的文字抵账,行数不变。
**为什么改**:推送 CI 和合并队列只测被改到的包,带 `!` 的破坏性改动会让没被改到的包里的 dogfood
测试在合并后的整点全量跑才变红(18.x 已发生两次,#24780 与
#24879)。维护者问「如何避免类似的问题」并「立卡」;卡上的「Done when」是裁决,本 PR 照它落地。卡上写的触发拼写 `yes
(narrowing)` 只是两种合法拼写之一,规则按「臂」写,两种都算。
**风险与代价(含回滚)**:纯流程文字,不动 CI、workflow、门禁或脚本。代价是带 `!` 或收窄声明的卡多一次锁下的全量
dogfood 运行(CI 分片跑约 7 分半,本地单机预计超过十分钟),其它卡零成本。回滚 = revert 本 PR 的一个
commit,无数据与发布影响。
**席位意见**:
**你要做的**:本 PR 只碰 `.claude/**`,属 Tier S:席位达档复核 PASS
后由席位入队,无需你动手。如对触发条件(按臂算、不按值算)或字段名 `dogfood_battery` 有异议,在卡上回一句即可。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01M82feuetLzrCiBz41BJb7L)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 73a2b52 commit be1fd31
2 files changed
Lines changed: 7 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
| 88 | + | |
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| |||
172 | 172 | | |
173 | 173 | | |
174 | 174 | | |
175 | | - | |
176 | 175 | | |
177 | 176 | | |
178 | 177 | | |
179 | 178 | | |
180 | 179 | | |
181 | | - | |
182 | 180 | | |
183 | 181 | | |
184 | 182 | | |
| |||
249 | 247 | | |
250 | 248 | | |
251 | 249 | | |
252 | | - | |
253 | 250 | | |
254 | 251 | | |
255 | 252 | | |
| |||
276 | 273 | | |
277 | 274 | | |
278 | 275 | | |
279 | | - | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
280 | 280 | | |
281 | 281 | | |
282 | 282 | | |
| |||
370 | 370 | | |
371 | 371 | | |
372 | 372 | | |
| 373 | + | |
373 | 374 | | |
374 | 375 | | |
375 | 376 | | |
| |||
382 | 383 | | |
383 | 384 | | |
384 | 385 | | |
385 | | - | |
386 | 386 | | |
387 | 387 | | |
388 | 388 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
13 | 12 | | |
14 | 13 | | |
15 | 14 | | |
| |||
48 | 47 | | |
49 | 48 | | |
50 | 49 | | |
| 50 | + | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| |||
0 commit comments