Skip to content

Commit be1fd31

Browse files
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

File tree

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ model: opus
8585
- `bash scripts/pm/os-verify-lock.sh -c '<command>'`(或 `-- <argv>`);⛔ 永不手搓 `flock`/lockfile。
8686
- 一次前台调用领全部等待预算,阻塞到拿锁或 99;⛔ 不轮询重试,恒设 `OS_VERIFY_LOCK_SLOT`。
8787
- 它不保证机器空闲:`check:*` 门禁、install、dev server 不走它,与持锁同核并跑。
88-
- 测试电池 `pnpm check:pm-dispatch-gates` 亦属 `check:*`:不走锁,`nohup … > LOG 2>&1 &` 后 `tail --pid`。
88+
- 超前台上限者(`check:pm-dispatch-gates`、全量 dogfood):`nohup … > LOG 2>&1 &` 后 `tail --pid`。
8989
- 锁下墙钟绝对值是共享盒读数;只包命令本身,不包你的阅读与判断。
9090
- 结论读它印的 `VERDICT command-exit` 行,⛔ 不读裸 `$?`;按到达序授予,排队是常态,不是挂死。
9191
2. **压住堆**:重命令前缀 `NODE_OPTIONS=--max-old-space-size=4096`,要抬需给理由。
@@ -172,13 +172,11 @@ model: opus
172172
- `set -o pipefail` 与 `${PIPESTATUS[0]}` 仅当下游读到 EOF 才安全。
173173
- `| head -N` 读满即关读端,生产者吃 SIGPIPE 以 0 退出:是管道改了生产者的退出码。
174174
- 此陷阱不限于门禁:任何 `cmd | head` 之后读退出码都中招(`git grep`、`node` 皆然)。
175-
- 陷阱在 shell 用法里,不可机械化;⛔ 别等机械强制。
176175
- 两类跑了却没测到,都读作 NOT MEASURED,不读作绿也不读作红。
177176
- 证据已销毁(评论、卡或 PR 答 404)的复核项记 NOT MEASURED 并写因,⛔ 不记通过或「无旗」。
178177
- ① 包的 `typecheck` 可能 `exclude` 掉 `**/*.test.ts`;声称它覆盖你的测试前,用 `--listFiles` 数。
179178
- ② `MODULE_NOT_FOUND` 一类的 exit 1 不是红门禁:多半是脚本名或路径敲错,根本没进到门禁体。
180179
- 它与 `exit 99` / queue-timeout / `PREREQUISITE NOT MET` 同类:用真正的 `pnpm check:*` 命令重跑。
181-
- ⛔ 不把这类结果写进报告当作一次失败的测量。
182180

183181
## 标准条款住在这里,不住在你的派发词里
184182

@@ -249,7 +247,6 @@ model: opus
249247
- `packages/qa/dogfood` 是一例,不是定义;读成 dogfood 专属,普通单元套件里的 ablation 会被当真。
250248
- 两个方向不对称:没 build 的修复是会被注意到的假红;没 build 的 ablation 保持绿。
251249
- 那份绿给一条可能永远失败不了的断言背书,对 CI 永久隐形(CI 构建正确,那边永远绿)。
252-
- 消融本就为证明新门禁能失败时,这份假绿读作门禁没触发,于是有人去弱化一条好门禁。
253250
- 每一腿(变异与还原)都是:改动 → 证明它真落到了磁盘 → `pnpm --filter <pkg> build`。
254251
- 落盘走 `node scripts/ablation-replace.mjs`,不走 `-i` 家族:锚点必须命中,写入与还原按磁盘核验。
255252
- 再证明它到达了 `dist/`,才读运行结果:`node scripts/ablation-dist-preflight.mjs <pkg> '<marker>'`。
@@ -276,7 +273,10 @@ model: opus
276273
- 分区 pin `test/vitest-tiers-partition.test.ts` 在某个测试文件两层皆无或皆有时变红。
277274
- 用 `git push -u origin claude/issue-<n>-<slug>` 推上去,网络失败退避重试;pre-push 拒卡片 trailer。
278275
- Draft PR 指向 `main`,正文首行 `Fixes #<n>`;合并不应关卡时用 `Part of #<n>`,并说明留下哪一半。
279-
- PR 开出后合 main 只因冲突、main 致 CI 红、出队,后只跑 §10 复检;dogfood 只在终 head 跑一次。
276+
- PR 开出后合 main 只因冲突、main 致 CI 红、出队,后只跑 §10 复检。
277+
- 全量 dogfood 只在终 head 跑一次,仅当标题带 `!` 或 `Clause-②:` 带 `(narrowing)` 臂(yes/no 皆算)。
278+
- 命令同 CI:按 ① 建闭包,锁下 `pnpm --filter @objectstack/dogfood test`;⛔ 无 `OS_TEST_SHARD`、无路径。
279+
- 红 = 本改动的后果(陈旧 fixture)同 PR 修,余照报;⛔ 永不跳过或收窄;计数记 `dogfood_battery`。
280280
- 认领带 `Clause-②:` 行才照抄进正文行首:`Check Changeset` 读正文不读卡;无则零写,⛔ 不自造。
281281
- ⛔ 永不 `Fixes` 一张还在决策箱的卡:合并会静默关掉它,而收件箱过滤只读 open。
282282
- ⛔ 不写否定式的关单句,它照样关掉点名的卡:解析器无视否定,只匹配关键词 + `#<n>`。
@@ -370,6 +370,7 @@ model: opus
370370
"summary": "what was implemented, 2-4 sentences",
371371
"tests": "commands run + pass/fail evidence (real output excerpts); ablation only where a tested behaviour changed: rebuild + on-disk mutation proof",
372372
"gates": "local subset: each command + exit code captured before any pipe + --ran reconciliation; every family CI runs: the PR's check-runs (name + conclusion) cited, not re-run locally",
373+
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm | <head sha> · the lock's VERDICT line · Test Files N/N · Tests M/M · every failure named",
373374
"mcp_calls": "<n> — MCP GitHub calls with tool names; a write tool in the list = this report is refused",
374375
"api_writes": "<n> — REST proxy writes, each endpoint listed",
375376
"open_questions": [
@@ -382,7 +383,6 @@ model: opus
382383
- `status: "rework"` = 你自知不完整的部分成果,在 `summary` 里说明为什么。
383384
- `premise_still_valid: false` = 核验证伪了前提(规则 6):证据进 `summary`,`pr` null 或只圈存活部分。
384385
- 报告模板是工具,不是真相:某字段的预设与实际发生的对不上时直说。
385-
- 例:反向验证方向反转、前提死了、某个产物在此无意义;按模板硬造比留白更糟。
386386

387387
## 字节与 sanitizer 纪律
388388

‎.claude/skills/pm-dispatch/references/review-checklist.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
- 切 `Part of` 的正文最易再武装自动关闭:否定句、记账句、引号包裹,解析器一律照关。
1010
- 安全拼法 = 卡号旁零动词,关闭安排写进评论。
1111
- 闭合关键词两读:翻 ready 前亲核首行、亲扫全正文,⛔ 关键词永不挨另一张 open 卡编号。
12-
- 这是合并前唯一的人工关口,⛔ 不只信报告;合并后那一读见 `landing-operations.md`。
1312
- `Part of` 收口的卡不会自动关,`pm:dispatched` 必须手工摘,动作要件见落地细则 B。
1413
- ACCEPT 那一刻记进落地待办,⛔ 不留给下次巡检。
1514

@@ -48,6 +47,7 @@
4847
- ⛔ 非当前 head 上的 `cancelled` 零动作、永不重跑:新推送自带全套 run。
4948
- 席位核对门禁证据 = 钉 head 的 check-runs(名+结论);本地并集只证本地子集,⛔ 不替缺席者。
5049
- dev 本地并集钉 head:所抄 `git rev-parse --short HEAD` 不符 PR `head.sha` 或没抄 ⇒ 双向都不入账。
50+
- `!` 或 `(narrowing)`:入队前核 `dogfood_battery` 钉 head、全量、红已修/已报;缺或收窄 ⇒ REWORK。
5151

5252
## 断言与收益
5353

0 commit comments

Comments
 (0)