@@ -779,6 +779,45 @@ the issue's files today, and tell the dev to verify against `origin/main`
779779rather than any working tree (Operational notes 4) — the dev's worktree is cut
780780from ` origin/main ` once and never refreshes itself.
781781
782+ ** In-flight overlap needs intercepting too — same-day churn only covers the
783+ dispatch instant.** The paragraph above handles "main moved before takeoff";
784+ main lands ~ 18 merges a day, so it moves ** after** takeoff just as often.
785+ #5322 's agent launched at 23:17Z and #5335 merged 32 minutes into that flight
786+ (` merged_at ` 2026-08-04T23:49:44Z) — the same two compilers, two of the same
787+ four cells. The PM's routine check read `git log
788+ origin/main`, spotted the overlap and sent an immediate SendMessage warning; the
789+ agent narrowed its scope twice and dropped its own design in favour of a minimal
790+ diff replayed inside the other PR's structure. Rule: ** every round, when you
791+ read ` git log origin/main ` , intersect each newly-landed PR against every
792+ in-flight dispatch's declared file surface** — on any intersection warn at once,
793+ with four instructions:「合 main 后重跑测试矩阵、读对方 diff 重划边界、只补它没覆盖
794+ 的部分、** 被完全覆盖就停下回报,⛔ 不要硬造 diff** 」. One round late is one rework.
795+
796+ This is Operational notes 8's sister paragraph: notes 8 is the PM re-checking
797+ main ** by symptom before enqueuing its own** shared-infrastructure fix; this one
798+ is the PM re-checking it ** on behalf of someone else's in-flight agent** . Same
799+ fact that main keeps moving, two different victims — and only the PM can see the
800+ second one, because the flying agent has no view of ` origin/main ` moving under it.
801+
802+ ** A ruling that flips public semantics ships with a whole-repo pin sweep — say so
803+ in the dispatch prompt.** When a maintainer ruling changes a public semantic
804+ (#5322 reclassified the empty combinator from * refused* to the ** boolean identity
805+ element** ), pins of the old position live ** outside** the package being changed:
806+ the consumer layers each hold a copy — REST envelope tests, objectql, runtime.
807+ #5365 's first lap flipped only the service-analytics layer and the copy in
808+ ` packages/rest/src/analytics-filter-refusal-envelope.test.ts ` went red in CI
809+ (` expected 200 to be 400 ` ); a second lap cleared it. Two lines belong in the
810+ prompt:
811+
812+ - ** Flip them all in one pass** :「grep 错误码 / 错误消息(如 ` INVALID_FILTER ` )
813+ ** 全仓扫描** 同语义 pin,一轮翻完,不要只改本包」;
814+ - ** A flipped pin must keep bearing load** : assert the ** substance** of the new
815+ semantics (the reduced row count, the translated message, the status code) —
816+ not "old assertion deleted". Refusal assertions for shapes that are * genuinely*
817+ invalid stay ** verbatim** : the guarded surface may never shrink. The two lines
818+ are one rule — drop the second and "flip the whole repo" degrades into "delete
819+ the whole repo's assertions", which is green and worthless.
820+
782821** Issue 正文是线索,不是规格 —— and the dispatch wording is what makes an
783822honest "the premise is dead" cheap to return.** Step 1's stale-premise check
784823is the PM's sample; the dev's verification is the real thing, so the prompt
@@ -867,6 +906,30 @@ to `mode:subagent`). Per issue:
867906do not fabricate a pending agent's result. A dev that dies or returns
868907malformed output counts as ` status: "blocked" ` with its raw output attached.
869908
909+ ** A stalled subagent is this half's most common failure, and it never
910+ self-heals.** When a dev stops mid-task reasoning that "a background watcher will
911+ wake me", ** that watcher never fires** — a completion notification is itself the
912+ statement that no live subtask remains. Four agents stalled 6 times across the
913+ 2026-08-04/05 night, every one recovered by hand, ~ 1.5–2 h lost. Three rules:
914+
915+ - ** State the execution posture in the dispatch/relay prompt** for any long
916+ verification pipeline:「** 前台(阻塞)同步执行全部步骤,中途不停止、不把构建/
917+ 测试挂到后台等唤醒** 」.
918+ - ** A completion notification carrying a MID-TASK state IS the stall signal** —
919+ "build still in progress", "I'll resume when…". SendMessage it back
920+ immediately with that posture line attached; ⛔ do not wait out any silence
921+ threshold (the cloud-mode ~ 2 h below): a threshold is for * no* answer, not for
922+ an answer that says the agent stopped.
923+ - ** A third stall means unreliable** — re-dispatch a fresh agent onto that
924+ branch under "Handing off an interrupted dev" in step 5 (worktree already exists,
925+ read every existing commit first, re-run the verification in full, claim and
926+ assignee untouched).
927+
928+ The ** producer-side** half of this rule lives in ` .claude/agents/os-dev.md ` 's
929+ resource discipline — fixing it at the producer beats patching it at the PM
930+ (Prime Directive #12 's instinct, applied to agent protocol); these three are the
931+ backstop, not the primary fix.
932+
870933** Cloud mode:** there is no direct return channel — collect through GitHub.
871934Arm a ` send_later ` check-in (~ 15 min); on each wake, sweep the dispatched
872935issues for ` <!-- os-dev-report --> ` comments and linked PRs, then re-arm
@@ -887,6 +950,13 @@ against the report's own claims:
887950- Test evidence in the report shows the actual commands and passing output,
888951 not a bare "tests pass".
889952- The diff plausibly satisfies the issue's acceptance criteria.
953+ - ** A ruling-implementation PR: were the old position's pins flipped repo-wide?**
954+ Fetch the changed-file list, then grep the consumer layers (REST envelope
955+ tests, objectql, runtime) for the ruling's error code / message and confirm no
956+ copy of the old position survives — ** and** that refusal assertions for
957+ genuinely invalid shapes are still there (step 5's two lines). #5365 slipped
958+ through exactly this review layer and was caught by CI instead: CI does catch
959+ it, at the price of one extra lap.
890960- ** Did the dev verify the issue's premise?** The report's
891961 ` premise_still_valid ` field makes the answer explicit — a ` false ` there
892962 reopens triage rather than failing review. A report that falsifies the
@@ -960,20 +1030,85 @@ content/docs/references/**
9601030
96110311 . ` git merge origin/main ` (⛔ 禁 rebase / force-push,AGENTS.md §3)
96210322 . ` git checkout origin/main -- <上述生成物> `
963- 3 . ** 整体重新生成** (⛔ 绝不做文本合并)
1033+ 3 . ** 先 commit 掉这次 merge,再整体重新生成** (⛔ 绝不做文本合并;⛔ 也绝不在 MERGE
1034+ 状态下跑 ` gen:schema ` —— 那会静默倒退锚点,见下一段与 #5370 )
96410354 . 断言** 所有兄弟单的条目都还在** —— #4878 落地时是四条 step17 条目并存
9651036
9661037一个比「条目还在」更硬的旁证:去查** 上一单的实现体** 是否完好(#4878 合并后核
9671038` conversions/registry.ts ` 里 #4391 的 D2 conversion 仍有 16 处命中)。条目是索引,
9681039实现体才是被吞的重灾区。驱动本身另有缺陷(把绝对路径写死进最后跑过 ` pnpm install `
9691040的那个 worktree),见 #4868 。
9701041
1042+ ** 锚点(` authorable-surface.base.json ` )的断言措辞 —— 写错会教唆 dev 手改锚点。**
1043+ #5304 把 #4650 删除闸门的基线固化成树内锚点之后,接力模板曾用** 错误断言**
1044+ 「baseRev == merge-base」派发,后棒实测证伪并纠正。正确措辞(2026-08-05 夜后八棒全部
1045+ 沿用,零误报):
1046+
1047+ > 断言 ` pnpm --filter @objectstack/spec check:authorable-surface ` ** 绿** 即可。锚点
1048+ > authenticity 的定义是两件事:` baseRev ` 是 ` origin/main ` 的** 祖先** ,且它记录的 keys
1049+ > 与** 该 commit** 的 ` authorable-surface.json ` 逐行一致(` verifyCommittedSurfaceBase `
1050+ > 就查这两条)。` baseRev ` ** 允许滞后** —— ` gen:schema ` 只在 keys 真的漂移时才重写它
1051+ > (在 ` main ` 上 merge base 就是 HEAD,该文件** 必然** 落后自己的 surface 一个 PR),
1052+ > 滞后只打一行 ` ℹ️ ` ,不是错误。⛔ 禁止为了凑「相等」手改锚点文件 —— 那正是 #4650
1053+ > 堵住的攻击本身;⛔ 不得要求 ` baseRev == merge-base ` ,那个等式不是任何门的判据。
1054+
1055+ 配套两个新陷阱(都已立单,派发词** 引用** 即可,⛔ 不要在接力单里顺手实现它们):
1056+
1057+ - ** #5370 : merge 未 commit 就跑 ` gen:schema ` ,会把锚点静默倒退回旧 merge-base。**
1058+ MERGE 状态下 HEAD 仍是合并前的分支 tip,` resolveSurfaceBase() ` 解出的是分支的** 旧**
1059+ 分叉点;倒退后的锚点** 依然 authentic** (旧 rev 也是 origin/main 的祖先、keys 也对得
1060+ 上),于是** 全部门放行** ,一次已合并的锚点推进被静默撤销。这就是四步序里「先 commit
1061+ merge,再整体重生成」的由来。
1062+ - ** #5371 :` gen:schema ` 的 ` rmSync ` 顺手抹掉 ` gen:openapi ` 的产物。** 之后跑
1063+ ` @objectstack/rest ` 会拿到 5 条 ` expected 503 to be 200 ` 的** 假红** (` openapi.json `
1064+ 被清场,不是 rest 坏了)。派发词里直接给解法:`pnpm --filter @objectstack/spec
1065+ gen: openapi ` 补回,或重跑一次完整 spec build。
1066+
9711067** B. 跟到 MERGED 为止,不是跟到「已入队」为止。** 「auto-merge 已挂上」不是终点,
9721068维护者对此有过明确纠正。每轮同时读** 队列分支** 与 ` origin/main ` (Operational notes
97310691);红了先分签名,再在「原样重投 / 推新提交 / 重新诊断」三者里选(notes 2 与 5)。
9741070落地之后** 再核一次落地判据本身** —— 队列的合并同样走 os-regen 驱动,A 里那个静默
9751071吞并在队列合并这一步一样能发生。
9761072
1073+ ** 依赖前棒才能转绿的 PR: draft 停放 + 一份精确的预期红清单。** 串行链里后棒常常先行
1074+ 实现(#5365 的四条进一致性表依赖 #5323 的 mongodb 归约才成立)。这种 PR ** 停在
1075+ draft** ,PR body 写两样东西:** 精确的预期红清单** (逐条列失败测试名 + 报错签名)与
1076+ ** 解除条件** (「依赖 PR #N 合入」)。每个 CI-failure webhook 到达时** 与该清单比对**
1077+ —— 签名匹配则静默跳过,** 出现新签名才是真问题** :#5365 的 REST 层红
1078+ (` expected 200 to be 400 ` )正是靠这个比对被一眼认成新问题,而不是被当成已知的等待态
1079+ 忽略过去。依赖合入后走「最后一轮同步 → 红清零 → 转 ready → 入队」。
1080+
1081+ 这是 Operational notes 2「先认签名,再决定重投」在** 故意红** 上的对偶: notes 2 管的是
1082+ flaky 的偶然红,这一条管的是自己设计出来的红 —— 两者的失效方式完全相同,** 把一个没
1083+ 预料到的签名当成预料之中的** 。所以清单必须写到签名级别,「几条测试会红」不够用。
1084+
1085+ #### 串行接力 —— 同碰生成物的多个 PR,一次只放行一个
1086+
1087+ 第 3 步的 batch 模型假设** 同批 file-disjoint 并行** ;当「多个已实现的 PR 全碰
1088+ ` packages/spec ` 生成物」时该假设不成立,唯一可行形态是** 串行接力** :一次只放行一个,
1089+ 每合并一个就向下一棒发接力指令。2026-08-04/05 夜 spec 车道以这个形态连落 10 个 PR
1090+ (#5304 → #5306 → #5308 → #5318 → #5319 → #5321 → #5314 → #5312 → #5323 → #5365 ),
1091+ 下面三条是那一夜靠现场即兴、事后固化的纪律。
1092+
1093+ ** 1. 每一棒都是一整圈,` auto-merge ` 由 PM 挂、dev 永不碰。** 单棒循环: merge main +
1094+ 上面 A 的四步重建 + 全套验证 + 兄弟单断言复核 → ** PM 复核回报** → 转 ready → 挂
1095+ auto-merge / 入队。ready 与 auto-merge 的顺序不可反 —— 转回 draft 会同时掉 auto-merge
1096+ 与队列成员资格(Operational notes 1),而接力循环里** 每一棒都要重走这一次** ,不是整条
1097+ 链只走一次。ACCEPT 那一条只写了单 PR 的流程;链上每一棒逐棒照它执行。
1098+
1099+ ** 2. 相邻两棒同碰一个文件时,交接的是语义,不是文本。** 前棒在回报里写明它对共享文件
1100+ 改动的** 性质** (改名 / 提取变量 / 增补断言,** 而非纯追加** ),PM ** 原样转告** 下一棒,
1101+ 并要求「两个 PR 的意图** 叠加** ,⛔ 禁止机械取一边」。实例:#5318 与 #5319 同动
1102+ ` packages/spec/src/system/metadata-form-zod-reconciliation.test.ts ` ,#5319 逐行号核过
1103+ #5318 新增的十项元素、并实测两者的交互 —— 没有 #5319 的 preprocess 修复,#5318 的新
1104+ 断言在 view 上是空转的。取一边会「各自绿、合起来错」,即 AGENTS.md §10「clean merge
1105+ 不等于 working merge」落在同一份测试文件上的形态。
1106+
1107+ ** 3. 两棒散文互锁,分工由 PM 指派。** 允许前棒给后棒留占位交接(「本段由 #N 在其同步轮
1108+ 翻正 / 删除」):#5323 的族 1 段落 ↔ #5365 、#5335 的「#5322 is its own ruling」pin 块
1109+ 都是这个形状。PM 必须在** 两侧** 的接力指令里写明谁动、谁不动 —— 否则要么两边都动
1110+ (冲突),要么两边都不动(留下一段过期散文),而这两种结果** 在 CI 上都是绿的** 。
1111+
9771112### 8. Escalate uncertainties to the maintainer
9781113
9791114** First, apply the escalation bar — most things that FEEL like decisions are
0 commit comments