From 2ad7fb974992720ebb09117aa97814570c410cae Mon Sep 17 00:00:00 2001 From: lex Date: Sat, 8 Aug 2026 20:08:40 +0800 Subject: [PATCH] docs: prepare batch B implementation tickets --- .opencode/handoff-batch-b.md | 17 +++--- .../01-q1-escalation-pending-lifecycle.md | 14 +++-- .../issues/02-q2-delivery-gated-retime.md | 14 +++-- .../03-q3-node-deadline-extended-event.md | 16 ++--- .../issues/04-q3-sdk-regen-consumers.md | 14 +++-- .../issues/05-s5-workflow-lock-timeout.md | 14 +++-- .../issues/06-flaky-stdout-pollution.md | 12 ++-- .../issues/07-flaky-sharenext-timing.md | 10 +-- .../issues/08-flaky-workspace-timing.md | 10 +-- .../batch-a/issues/09-promote-dev-to-main.md | 12 ++-- .scratch/batch-b/README.md | 23 +++++++ .scratch/batch-b/abort-path-contracts.md | 53 ++++++++++++++++ .scratch/batch-b/evidence.md | 61 +++++++++++++++++++ .../batch-b/issues/01-u1-fork-rollback.md | 15 +++++ .../issues/02-u2-transport-timeout-abort.md | 15 +++++ .../issues/03-transport-midstream-stall.md | 15 +++++ .../issues/04-f3-subscription-readiness.md | 14 +++++ .../05-f4-type-safe-dag-store-fixtures.md | 14 +++++ .scratch/batch-b/issues/06-o1-lkg-spec.md | 14 +++++ .../batch-b/issues/07-o1-lkg-implement.md | 14 +++++ .../08-s7-recovery-invented-diagnosis.md | 15 +++++ .../issues/09-promote-dev-main-release.md | 12 ++++ .../issues/01-p8-spawn-ready-observation.md | 12 ++++ 23 files changed, 353 insertions(+), 57 deletions(-) create mode 100644 .scratch/batch-b/README.md create mode 100644 .scratch/batch-b/abort-path-contracts.md create mode 100644 .scratch/batch-b/evidence.md create mode 100644 .scratch/batch-b/issues/01-u1-fork-rollback.md create mode 100644 .scratch/batch-b/issues/02-u2-transport-timeout-abort.md create mode 100644 .scratch/batch-b/issues/03-transport-midstream-stall.md create mode 100644 .scratch/batch-b/issues/04-f3-subscription-readiness.md create mode 100644 .scratch/batch-b/issues/05-f4-type-safe-dag-store-fixtures.md create mode 100644 .scratch/batch-b/issues/06-o1-lkg-spec.md create mode 100644 .scratch/batch-b/issues/07-o1-lkg-implement.md create mode 100644 .scratch/batch-b/issues/08-s7-recovery-invented-diagnosis.md create mode 100644 .scratch/batch-b/issues/09-promote-dev-main-release.md create mode 100644 .scratch/batch-c/issues/01-p8-spawn-ready-observation.md diff --git a/.opencode/handoff-batch-b.md b/.opencode/handoff-batch-b.md index 02a8726146..09e58a754b 100644 --- a/.opencode/handoff-batch-b.md +++ b/.opencode/handoff-batch-b.md @@ -3,31 +3,32 @@ > 本会话(批 A 全链路)已极长,批次 B 在新对话执行。引用本文件 + 下述证据路径即可开工。 ## 起点状态(交接时) -- 基线:**dev**(`bee78d7ed` 起算,开工前 `git fetch && git switch dev && git pull`) +- 基线:**dev**(批 B 规划基线 `3e8368f37`;每票开工前重新同步最新 `dev`) - 批次 A 全闭环:Q1-Q6 引擎语义 + 接受期绑定校验 + flaky 根治(豁免清单已清零)+ 技术债清零(PR #185-#189 全合入) - lint 棘轮:4852(CI 口径;本地 = CI − 10 生成物差,本地基线 ≤4842)——**只紧不松** - 台账:.scratch/batch-a/issues/01-11(01-09 完成,10/11 closed) -## 批次 B 四组票(Ask Matt 路由:agent-ready,证据已在案) +## 批次 B 四组票(审计后路由,证据已在案) ### 组 1:U-1 / U-2 + Transport mid-stream-stall(一个 spec 三张票) -三个 abort-path 集成测试,同源同批。先 /to-spec 收敛三票边界,再逐票 /implement。 +三个 abort-path 集成测试,同源同批。规格已收敛到 `.scratch/batch-b/abort-path-contracts.md`,按 01→02→03 逐票 /implement。 ### 组 2:F3 / F4 测试卫生债 小票直接做(无需 grill),每票新上下文 /implement。 ### 组 3:O1 remote config last-known-good 缓存 -小 feature;离线降级的 last-known-good 语义在批 A 前置调研中有上下文(config-offline-degrade 已合入 dev,先读现状再定增量)。 +批 A 已完成“网络/响应体失败时 warn + skip”;剩余增量只有持久化 last-known-good。先完成 `.scratch/batch-b/issues/06-o1-lkg-spec.md` 的小规格,再按 07 实现;不得重新实现离线降级。 ### 组 4:S7 recovery INVENTED 推断 ⚠️ -唯一带 bug 气味的——**必须走 /diagnosing-bugs**:先 tight feedback loop(一条命令红灯复现)再修,禁止先理论后复现。修复以回归测试收口。 +当前已有 ownership-lost 后暂停工作流的缓解,尚无用户态缺陷实证。**必须走 /diagnosing-bugs**:先建立一条确定性、快速、可红灯的复现命令;不能建立反馈回路则记录尝试并停止,不得先改生产代码。若红灯成立,另开修复票与新上下文。 ## 证据路径(不用重新调查,票据直接引用) -- .opencode/promotion-review-round1/*.md(U-1/U-2/F3/F4/O1/S7/P8 全部 finding + 根因记录) -- .opencode/promotion-review-round2.yaml 相关证据(若引用深审轮次) +- `.scratch/batch-b/evidence.md`:已追踪的稳定证据快照,含当前代码路径纠偏与验收边界;新 worktree 只依赖此文件 +- `.opencode/promotion-review-round1/*.md`、`.opencode/.dag-specs/evidence/*.md`:原始本地评审产物,当前未追踪,仅用于复核来源,不作为跨 worktree 前置 +- `.scratch/batch-b/abort-path-contracts.md`:U-1/U-2/mid-stream-stall 的已追踪规格;本地 OpenSpec 原件受 `.gitignore` 约束,不作为跨 worktree 前置 ## 工程纪律(仓库铁律 + 本项目惯例) -- 分支:从 **dev** 切 `feat/**` 或 `fix/**`(AGENTS.md 原文写 main,本项目当前惯例:批 B 基于 dev——dev 领先 main 且为集成层) +- 分支:从最新 **dev** 切票据指定分支;生产 feature 用 `feat/**`,测试/规格债用 `test/**` 或 `docs/**`,均符合 branch-naming ruleset - PR → dev:Typecheck 门禁;push dev 自动触发全量测试(Typecheck + Unit + E2E×2) - 测试从包目录跑(packages/opencode 等),禁根目录;typecheck 用 `bun typecheck` 不用裸 tsc - 每票一个新上下文会话执行(/implement 内含 /tdd),票间清上下文 diff --git a/.scratch/batch-a/issues/01-q1-escalation-pending-lifecycle.md b/.scratch/batch-a/issues/01-q1-escalation-pending-lifecycle.md index a6ac3c5844..62c43fde33 100644 --- a/.scratch/batch-a/issues/01-q1-escalation-pending-lifecycle.md +++ b/.scratch/batch-a/issues/01-q1-escalation-pending-lifecycle.md @@ -6,10 +6,12 @@ **Blocked by:** None — can start immediately -**Status:** ready-for-agent +**Status:** closed(PR #186,merge commit `4ddeaf2fc`) -- [ ] 节点终态转移(completed/failed/aborted)与取消路径清 escalation_pending -- [ ] wake_reported 在清旗路径上不被触碰(两旗正交测试) -- [ ] 已有 NodeStarted/NodeRestarted 清旗点保持不回退 -- [ ] replay/恢复场景下清旗经事件折叠重放一致 -- [ ] dag 测试套件 + typecheck 绿 +**Completion evidence:** 批次 A 实现与测试随 PR #186 合入 `dev`,并随 PR #188 通过 main 全量门禁。 + +- [x] 节点终态转移(completed/failed/aborted)与取消路径清 escalation_pending +- [x] wake_reported 在清旗路径上不被触碰(两旗正交测试) +- [x] 已有 NodeStarted/NodeRestarted 清旗点保持不回退 +- [x] replay/恢复场景下清旗经事件折叠重放一致 +- [x] dag 测试套件 + typecheck 绿 diff --git a/.scratch/batch-a/issues/02-q2-delivery-gated-retime.md b/.scratch/batch-a/issues/02-q2-delivery-gated-retime.md index 70cf200f88..955ae776e6 100644 --- a/.scratch/batch-a/issues/02-q2-delivery-gated-retime.md +++ b/.scratch/batch-a/issues/02-q2-delivery-gated-retime.md @@ -6,10 +6,12 @@ **Blocked by:** None — can start immediately -**Status:** ready-for-agent +**Status:** closed(PR #186,merge commit `4ddeaf2fc`) -- [ ] skip 合取项落在 re-time 唯一发起点,全 re-time 触发路径逐条覆盖(测试枚举,不只抄规格) -- [ ] 初始升级(deadline ⟹ 首次 wake)不受门控影响 -- [ ] 裁决写入后 re-time 能力恢复的测试 -- [ ] watchdog 无状态写(仅提案)的断言保持 -- [ ] dag 测试套件 + typecheck 绿 +**Completion evidence:** 批次 A 实现与测试随 PR #186 合入 `dev`,并随 PR #188 通过 main 全量门禁。 + +- [x] skip 合取项落在 re-time 唯一发起点,全 re-time 触发路径逐条覆盖(测试枚举,不只抄规格) +- [x] 初始升级(deadline ⟹ 首次 wake)不受门控影响 +- [x] 裁决写入后 re-time 能力恢复的测试 +- [x] watchdog 无状态写(仅提案)的断言保持 +- [x] dag 测试套件 + typecheck 绿 diff --git a/.scratch/batch-a/issues/03-q3-node-deadline-extended-event.md b/.scratch/batch-a/issues/03-q3-node-deadline-extended-event.md index 3456af0a29..f09f418f09 100644 --- a/.scratch/batch-a/issues/03-q3-node-deadline-extended-event.md +++ b/.scratch/batch-a/issues/03-q3-node-deadline-extended-event.md @@ -6,11 +6,13 @@ **Blocked by:** 01 — Q1:escalation_pending 裁决旗生命周期闭环(projector 折叠侧写集串行) -**Status:** ready-for-agent +**Status:** closed(PR #186,merge commit `4ddeaf2fc`) -- [ ] Schema 定义 NodeDeadlineExtended + 入 EventManifest.Definitions -- [ ] 命令层执行 guard:拒绝时命令失败并携带 typed 错误,编排器可区分拒绝与成功 -- [ ] 直写 deadline 旧路径废除(无遗留调用方) -- [ ] projector 纯折叠:无事件发布、无返回值契约依赖 -- [ ] 恢复/replay 一致性测试(事件日志重放 ⟺ 活跃态) -- [ ] dag 测试套件 + typecheck 绿 +**Completion evidence:** 批次 A 实现与测试随 PR #186 合入 `dev`,并随 PR #188 通过 main 全量门禁。 + +- [x] Schema 定义 NodeDeadlineExtended + 入 EventManifest.Definitions +- [x] 命令层执行 guard:拒绝时命令失败并携带 typed 错误,编排器可区分拒绝与成功 +- [x] 直写 deadline 旧路径废除(无遗留调用方) +- [x] projector 纯折叠:无事件发布、无返回值契约依赖 +- [x] 恢复/replay 一致性测试(事件日志重放 ⟺ 活跃态) +- [x] dag 测试套件 + typecheck 绿 diff --git a/.scratch/batch-a/issues/04-q3-sdk-regen-consumers.md b/.scratch/batch-a/issues/04-q3-sdk-regen-consumers.md index 694c50366b..b099b39682 100644 --- a/.scratch/batch-a/issues/04-q3-sdk-regen-consumers.md +++ b/.scratch/batch-a/issues/04-q3-sdk-regen-consumers.md @@ -4,10 +4,12 @@ **Blocked by:** 03 — Q3:NodeDeadlineExtended durable 事件 + guard 前移命令层 -**Status:** ready-for-agent +**Status:** closed(PR #186,merge commit `4ddeaf2fc`) -- [ ] SDK 再生脚本执行,生成物提交 -- [ ] 事件联合类型包含 NodeDeadlineExtended,消费方编译绿 -- [ ] `check:generated`(SDK + client)零 diff -- [ ] 涉及响应/事件形状的 httpapi-exercise 场景已更新(如有) -- [ ] 全量单元测试(含 httpapi 契约)绿 +**Completion evidence:** 批次 A 生成物与消费者更新随 PR #186 合入 `dev`,并随 PR #188 通过 main 全量门禁。 + +- [x] SDK 再生脚本执行,生成物提交 +- [x] 事件联合类型包含 NodeDeadlineExtended,消费方编译绿 +- [x] `check:generated`(SDK + client)零 diff +- [x] 涉及响应/事件形状的 httpapi-exercise 场景已更新(如有) +- [x] 全量单元测试(含 httpapi 契约)绿 diff --git a/.scratch/batch-a/issues/05-s5-workflow-lock-timeout.md b/.scratch/batch-a/issues/05-s5-workflow-lock-timeout.md index d0df3f56e4..d568bd49c1 100644 --- a/.scratch/batch-a/issues/05-s5-workflow-lock-timeout.md +++ b/.scratch/batch-a/issues/05-s5-workflow-lock-timeout.md @@ -6,10 +6,12 @@ **Blocked by:** None — can start immediately -**Status:** ready-for-agent +**Status:** closed(PR #186,merge commit `4ddeaf2fc`) -- [ ] 唯一改动点在 withWorkflowLock 包装层(一行 + 常量) -- [ ] 30s 超限产生 TimeoutException,编排器按既有 error_class 分诊规则处置 -- [ ] 无新错误类、无 per-caller 分支的断言 -- [ ] watchdog 自续行为在锁超时后仍正确的测试 -- [ ] dag 测试套件 + typecheck 绿 +**Completion evidence:** 批次 A 实现与测试随 PR #186 合入 `dev`,并随 PR #188 通过 main 全量门禁。 + +- [x] 唯一改动点在 withWorkflowLock 包装层(一行 + 常量) +- [x] 30s 超限产生 TimeoutException,编排器按既有 error_class 分诊规则处置 +- [x] 无新错误类、无 per-caller 分支的断言 +- [x] watchdog 自续行为在锁超时后仍正确的测试 +- [x] dag 测试套件 + typecheck 绿 diff --git a/.scratch/batch-a/issues/06-flaky-stdout-pollution.md b/.scratch/batch-a/issues/06-flaky-stdout-pollution.md index ca83a2c3e2..57abd202ff 100644 --- a/.scratch/batch-a/issues/06-flaky-stdout-pollution.md +++ b/.scratch/batch-a/issues/06-flaky-stdout-pollution.md @@ -6,9 +6,11 @@ **Blocked by:** None — can start immediately -**Status:** ready-for-agent +**Status:** closed(PR #186,merge commit `4ddeaf2fc`) -- [ ] 污染源定位经可复现测试验证(修复前红、修复后绿) -- [ ] run-process 9 项断言不削弱、不删除,本地重复跑(≥5 次)稳定绿 -- [ ] ShareNext 的 stdout 污染分量同步修复(计时问题归 07 票) -- [ ] opencode 包测试套全绿(除豁免清单剩余项) +**Completion evidence:** flaky 根因修复与验证随 PR #186 合入 `dev`,并随 PR #188 通过 main 全量门禁。 + +- [x] 污染源定位经可复现测试验证(修复前红、修复后绿) +- [x] run-process 9 项断言不削弱、不删除,本地重复跑(≥5 次)稳定绿 +- [x] ShareNext 的 stdout 污染分量同步修复(计时问题归 07 票) +- [x] opencode 包测试套全绿(除豁免清单剩余项) diff --git a/.scratch/batch-a/issues/07-flaky-sharenext-timing.md b/.scratch/batch-a/issues/07-flaky-sharenext-timing.md index 173a89dbdd..ed5980f57e 100644 --- a/.scratch/batch-a/issues/07-flaky-sharenext-timing.md +++ b/.scratch/batch-a/issues/07-flaky-sharenext-timing.md @@ -6,8 +6,10 @@ **Blocked by:** 06 — Flaky:stdout 污染族根治(同一测试文件,写集串行) -**Status:** ready-for-agent +**Status:** closed(PR #186,merge commit `4ddeaf2fc`) -- [ ] 修复走信号等待惯用法;若改预算须附 CI 计时证据 -- [ ] 本地重复跑(≥5 次)+ 模拟负载下稳定绿 -- [ ] 无新增 Effect.sleep 等待 forked fiber 的反模式 +**Completion evidence:** flaky 稳定化与验证随 PR #186 合入 `dev`,并随 PR #188 通过 main 全量门禁。 + +- [x] 修复走信号等待惯用法;若改预算须附 CI 计时证据 +- [x] 本地重复跑(≥5 次)+ 模拟负载下稳定绿 +- [x] 无新增 Effect.sleep 等待 forked fiber 的反模式 diff --git a/.scratch/batch-a/issues/08-flaky-workspace-timing.md b/.scratch/batch-a/issues/08-flaky-workspace-timing.md index 0a265c8df4..0402fbbc16 100644 --- a/.scratch/batch-a/issues/08-flaky-workspace-timing.md +++ b/.scratch/batch-a/issues/08-flaky-workspace-timing.md @@ -6,8 +6,10 @@ **Blocked by:** None — can start immediately -**Status:** ready-for-agent +**Status:** closed(PR #186,merge commit `4ddeaf2fc`) -- [ ] 先复现并确认根因(计时 vs 其他),根因记录入票 -- [ ] 修复后本地重复跑(≥5 次)+ 模拟负载下稳定绿 -- [ ] 无新增固定 sleep 反模式 +**Completion evidence:** flaky 稳定化与验证随 PR #186 合入 `dev`,并随 PR #188 通过 main 全量门禁。 + +- [x] 先复现并确认根因(计时 vs 其他),根因记录入票 +- [x] 修复后本地重复跑(≥5 次)+ 模拟负载下稳定绿 +- [x] 无新增固定 sleep 反模式 diff --git a/.scratch/batch-a/issues/09-promote-dev-to-main.md b/.scratch/batch-a/issues/09-promote-dev-to-main.md index d9a6a51793..bca0d7dada 100644 --- a/.scratch/batch-a/issues/09-promote-dev-to-main.md +++ b/.scratch/batch-a/issues/09-promote-dev-to-main.md @@ -4,9 +4,11 @@ **Blocked by:** 01、02、03、04、05、06、07、08 全部合入 dev -**Status:** ready-for-agent +**Status:** closed(PR #188,merge commit `e837dcbfa`) -- [ ] dev 最新 push 的 CI 四项检查全绿(Typecheck、Unit、E2E linux、E2E windows) -- [ ] 豁免清单清零或逐项重新裁决留档 -- [ ] PR 描述附批次 A 交付清单(Q1/Q2/Q3/S5 + flaky 根因修复)与两轮深审 PASS 证据链接 -- [ ] 合并后 main 可手动 release-fork +**Completion evidence:** dev→main 晋级 PR #188 的 Typecheck、Unit Tests (linux)、E2E Tests (linux/windows) 全部通过并合入。 + +- [x] dev 最新 push 的 CI 四项检查全绿(Typecheck、Unit、E2E linux、E2E windows) +- [x] 豁免清单清零或逐项重新裁决留档 +- [x] PR 描述附批次 A 交付清单(Q1/Q2/Q3/S5 + flaky 根因修复)与两轮深审 PASS 证据链接 +- [x] 合并后 main 可手动 release-fork diff --git a/.scratch/batch-b/README.md b/.scratch/batch-b/README.md new file mode 100644 index 0000000000..7a94dbb54e --- /dev/null +++ b/.scratch/batch-b/README.md @@ -0,0 +1,23 @@ +# 批次 B 执行台账 + +**规划基线:** `dev@3e8368f37`(PR #190) + +**当前状态:** 规格与票据已就绪;实现代码尚未开始。 + +## 审计结论 + +- 批次 A 已经 PR #188 晋级 `main`;残余修复 PR #189 与交接 PR #190 已进入 `dev`。 +- U-1/U-2/mid-stream-stall 的 OpenSpec 已完成并通过校验;仓库规定 `/openspec/` local-only,跨 worktree 使用已追踪镜像 `.scratch/batch-b/abort-path-contracts.md`。 +- 原始 `.opencode/promotion-review-round1/` 与 `.opencode/.dag-specs/evidence/` 是未追踪本地文件;跨 worktree 统一引用 `.scratch/batch-b/evidence.md`。 +- O1 不是直接实现票:离线降级已完成,剩余 LKG 的持久化、键、失效和安全边界需先写小规格。 +- S7 只有静态 bug 气味;先诊断,不能复现就无代码收口。P8 维持批 C 的观测候选,不阻塞批 B。 + +## 串行顺序 + +1. 01 U-1 → 02 U-2 → 03 mid-stream-stall;02/03 写同一测试文件,禁止并行。 +2. 04 F3 → 05 F4;两票只清测试债,不顺带改运行时。 +3. 06 O1 规格 → 07 O1 实现。 +4. 08 S7 诊断;只有红灯成立才新建独立修复票。 +5. 批 B 全部处置 + 批 C P8 观测记录关闭后,执行 09 的 dev→main 晋级与 release-fork。 + +每票从最新 `dev` 创建符合仓库规则的分支,单独 PR → `dev`,单独新任务执行。票据完成时将状态改为 `closed`,附 PR、merge commit 与验证命令。 diff --git a/.scratch/batch-b/abort-path-contracts.md b/.scratch/batch-b/abort-path-contracts.md new file mode 100644 index 0000000000..eaaa7dde92 --- /dev/null +++ b/.scratch/batch-b/abort-path-contracts.md @@ -0,0 +1,53 @@ +# 批次 B:abort-path integrity 规格 + +**Status:** accepted +**Applies to:** 01 U-1、02 U-2、03 Transport mid-stream-stall +**Local OpenSpec source:** `openspec/changes/batch-b-abort-path-contracts/`(仓库规定 local-only;`openspec validate --changes` 已通过) + +## 目标 + +三个边界目前只有实现/注释声明,没有真实失败路径证据:Session fork 嵌套发布失败时的批次回滚、HTTP timeout 对真实连接的取消传播、合法首帧后的逐帧间隔 timeout。本规格只补集成测试;红灯暴露违约时,才允许做对应契约所需的最小生产修复。 + +## Requirement 1:fork 复制批次原子回滚 + +系统 SHALL 在 `Session.fork` 的消息/part 复制批次中保持原子性:任一嵌套 durable event 发布失败时,同一外层事务内此前写入的复制事件及其投影全部回滚。 + +### Scenario:部分复制后嵌套发布失败 + +- WHEN fork 已创建目标 session,至少一个 message/part 发布完成,随后嵌套发布失败 +- THEN fork 调用失败,目标 session 不存在本批复制出的 message/part projections 与 durable copy events +- THEN 源 session 的 message/part 保持不变 +- THEN 复制事务外已经提交的目标 Session Created 记录可以保留 + +**设计裁决:** 必须扩展 `packages/opencode/test/session/fork-batch.test.ts` 的真实 SQLite fixture;adapter-only savepoint 测试不足以证明 EventV2/projector 共用外层连接。 + +## Requirement 2:provider timeout 取消真实 transport + +系统 MUST 在 provider HTTP timeout 到期时,以现有 Transport/Timeout 结束 LLM stream,并取消仍在进行的真实 HTTP response stream,使 provider 端观察到连接或响应体取消。 + +### Scenario:真实 provider response 超时后仍保持打开 + +- WHEN loopback provider 已接收请求并返回超过 timeout 仍保持打开的 response stream +- THEN 客户端在有界时间内以现有 `LLMError` Transport/Timeout 失败 +- THEN provider 在有界时间内观察到 response cancellation 或等价 request abort + +**设计裁决:** 使用真实 `Bun.serve` loopback 与生产 fetch-backed client。以 response cancellation 为确定性主信号,request abort 可作补充;内存 HttpClient 和显式 Fiber interrupt 都不能替代本场景。 + +## Requirement 3:timeout 约束每个帧间隔 + +系统 SHALL 将 stream timeout 作为相邻数据帧之间的最大间隔,而不是只覆盖 response headers 或首帧等待。 + +### Scenario:合法首帧后永久停顿 + +- WHEN provider 在 timeout 内发出至少一个合法 SSE frame,随后不关闭且不再发送数据 +- THEN timeout 前到达的 frame 已交付消费者 +- THEN 停顿超过 timeout 后,stream 以现有 Transport/Timeout 失败 + +**设计裁决:** 使用 fence 证明首帧已交付,再用 TestClock 越过下一帧间隔;本场景验证 stream timing,不重复真实 socket 取消测试。 + +## 非目标与顺序 + +- 不要求把 fork session creation 与复制批次合并为同一事务。 +- 不改变 timeout 默认值、错误词汇、retry policy 或 provider protocol。 +- 01 → 02 → 03 串行落地;02/03 都修改 `packages/llm/test/transport-timeout.test.ts`。 +- 若 Bun 在目标 CI 平台无法提供可重复的服务端取消信号,02 停在诊断结论,不能退化为重复断言 Timeout 错误。 diff --git a/.scratch/batch-b/evidence.md b/.scratch/batch-b/evidence.md new file mode 100644 index 0000000000..b2cfb8887e --- /dev/null +++ b/.scratch/batch-b/evidence.md @@ -0,0 +1,61 @@ +# 批次 B 证据快照 + +**代码基线:** `dev@3e8368f37` +**用途:** 给每票的新任务/worktree 提供稳定证据;无需重新调查原始评审。 +**原始来源:** 本地未追踪的 `.opencode/promotion-review-round1/*.md` 与 `.opencode/.dag-specs/evidence/*.md`。 + +## 组 1:abort-path 契约 + +### U-1 — Session fork 嵌套事务回滚 + +- 原始 finding 引用的 `packages/core/src/session.ts` 已过期;当前实现位于 `packages/opencode/src/session/session.ts` 的 `Session.fork`。 +- fork 的消息/part 复制由一个外层 `db.transaction` 包裹,嵌套 `events.publish` 会进入 Effect-Drizzle SQLite savepoint。 +- `packages/effect-drizzle-sqlite/src/effect-sqlite/session.ts` 已实现嵌套事务的 savepoint/rollback。 +- `packages/opencode/test/session/fork-batch.test.ts` 已验证成功路径与事务/savepoint 数量,缺口仅是“部分发布成功后失败”的整体回滚。 +- 目标契约、测试边界与非目标见 `.scratch/batch-b/abort-path-contracts.md`。 + +### U-2 — timeout 传播到底层 HTTP 取消 + +- `packages/llm/src/route/transport/http.ts` 对请求执行使用 `Effect.timeout`,对响应 stream 使用 `Stream.timeoutOrElse`。 +- `packages/llm/test/transport-timeout.test.ts` 已覆盖 headers 挂起、body 从不发帧、正常完成、默认 timeout 与选项合并,但使用内存 HTTP client,不能证明真实 socket/response body 被取消。 +- `packages/opencode/test/session/llm.test.ts` 已覆盖显式 Fiber interrupt 导致 provider response body 取消;本票必须验证“timeout 驱动”的取消,不能复制该场景。 +- 验收必须使用真实 loopback `Bun.serve` + 生产 fetch-backed client,并以服务端可观察的 response cancellation 为主信号。 + +### Transport mid-stream-stall + +- 当前 timeout suite 没有“合法首帧已交付,随后永久停顿”的场景。 +- 本票验证逐帧间隔 timeout;可使用 TestClock,不承担真实 socket 取消证明。 +- U-2 与本票都修改 `packages/llm/test/transport-timeout.test.ts`,必须先 02 后 03。 + +## 组 2:测试卫生债 + +### F3 — 固定订阅 settle sleep + +- `packages/opencode/test/goal/e2e-loop.test.ts` 定义 `SUBSCRIPTION_SETTLE_MS = 200`,共有 8 个固定 `Effect.sleep` 等待点。 +- `GoalLoop` 初始化主要读取实例状态并 fork 事件订阅;票据应以可观察 readiness/fence 或最小调度让步替代墙钟等待。 +- 验收要求旧的固定 settle sleep 全部消失,并重复运行目标测试;不把生产行为修改当作默认方案。 + +### F4 — DagStore 双重断言 + +- `packages/opencode/test/dag/dag-timeout-escalation-fixes.test.ts` 有两处 `as unknown as DagStore.Interface`,原始证据只记录了第一处。 +- 两处都需改为类型安全的 `Layer.mock`/fixture factory;不得只清一处。 + +## 组 3/4 与批 C + +### O1 — remote config last-known-good + +- PR #182/#189 前的现状已变化:`packages/opencode/src/config/config.ts` 在 remote transport/body 失败时会 warn + skip;HTML 登录页/auth 与 schema decode 仍硬失败。 +- 剩余需求仅是持久化 LKG。实施前需裁决:缓存内容、稳定键、原子写与权限、何种失败允许回退、损坏缓存行为、TTL。 +- 安全下限:缓存键不得含 header/token;LKG 不得掩盖 auth/decode 错误;损坏缓存只能 warn + skip。 + +### S7 — recovery INVENTED 推断 + +- `packages/opencode/src/dag/runtime/recovery.ts` 的 session checker 从最后一条 assistant finish 推断 active/terminal;tool-calls、unknown 或无 finish 会落入 active/unknown,并可能在 reconcile 中写入 `exec_failed`。 +- `packages/opencode/src/dag/runtime/loop.ts` 在 `ownershipLost` 后会暂停 workflow,现有测试已覆盖该缓解;目前没有已复现的用户态缺陷。 +- 只能按 `/diagnosing-bugs` 先建红灯反馈回路。若“durable transcript 已语义完成却被判 active 并写失败”无法稳定复现,结论应是无修复,不得凭静态推断改代码。 + +### P8 — spawnReady 复杂度 + +- `packages/opencode/src/dag/runtime/loop.ts` 的 `spawnReady` 对 ready 节点反复在全节点数组中 `.find`,静态复杂度为 `O(ready × nodes)`。 +- 原始性能评审明确标记“>50 节点的实际调度开销未实测”;当前无用户痛点或 benchmark。 +- 批 C 只记录观测结论;没有 trace/benchmark 证明影响时,以 no-code 关闭,不阻塞最终晋级。 diff --git a/.scratch/batch-b/issues/01-u1-fork-rollback.md b/.scratch/batch-b/issues/01-u1-fork-rollback.md new file mode 100644 index 0000000000..fa0ae38611 --- /dev/null +++ b/.scratch/batch-b/issues/01-u1-fork-rollback.md @@ -0,0 +1,15 @@ +# 01 — U-1:Session fork 中途失败整体回滚 + +**What to build:** 在真实 SQLite 的 `Session.fork` 集成测试中注入确定性中途失败,证明一个嵌套 durable publication 失败会回滚同一复制批次中已写入的消息/part 事件及投影。 + +**Spec:** `.scratch/batch-b/abort-path-contracts.md` 的 Requirement 1 +**Evidence:** `.scratch/batch-b/evidence.md#u-1--session-fork-嵌套事务回滚` +**Branch:** `test/fork-rollback` +**Blocked by:** None +**Status:** ready-for-agent + +- [ ] 复用 `packages/opencode/test/session/fork-batch.test.ts` 的真实 SQLite fixture;不写 adapter-only 替代测试 +- [ ] 至少一个 message/part 发布完成后再确定性失败,旧实现若违约时测试能红 +- [ ] 目标 session 无复制出的 durable events 与 projections,源 session 不变;Session Created 可保留 +- [ ] 若红灯暴露生产缺陷,只做本契约所需的最小修复 +- [ ] 在 `packages/opencode` 运行目标测试与 `bun typecheck`,结果附入票据 diff --git a/.scratch/batch-b/issues/02-u2-transport-timeout-abort.md b/.scratch/batch-b/issues/02-u2-transport-timeout-abort.md new file mode 100644 index 0000000000..5983901932 --- /dev/null +++ b/.scratch/batch-b/issues/02-u2-transport-timeout-abort.md @@ -0,0 +1,15 @@ +# 02 — U-2:HTTP timeout 传播到真实 response 取消 + +**What to build:** 使用 loopback `Bun.serve` 与生产 fetch-backed HTTP client,证明 provider timeout 不只返回 Transport/Timeout,还会取消仍打开的真实响应流。 + +**Spec:** `.scratch/batch-b/abort-path-contracts.md` 的 Requirement 2 +**Evidence:** `.scratch/batch-b/evidence.md#u-2--timeout-传播到底层-http-取消` +**Branch:** `test/transport-abort` +**Blocked by:** 01(同一 OpenSpec 串行落地) +**Status:** blocked + +- [ ] fixture 提供“请求已接收”与“response 已取消”的有界 fence,禁止用固定 sleep 猜时序 +- [ ] timeout 后断言现有 `LLMError` Transport/Timeout 形状 +- [ ] 服务端确定性观察到 response stream cancellation;request abort 只作补充信号 +- [ ] 不用内存 HttpClient 或显式 Fiber interrupt 重复现有覆盖 +- [ ] 在 `packages/llm` 连续运行目标测试至少 3 次并运行 `bun typecheck` diff --git a/.scratch/batch-b/issues/03-transport-midstream-stall.md b/.scratch/batch-b/issues/03-transport-midstream-stall.md new file mode 100644 index 0000000000..ebc3f10eee --- /dev/null +++ b/.scratch/batch-b/issues/03-transport-midstream-stall.md @@ -0,0 +1,15 @@ +# 03 — Transport:合法首帧后的 stall 触发逐帧超时 + +**What to build:** 增加“合法 SSE 首帧已交付,连接随后永久停顿”的测试,固定 `Stream.timeoutOrElse` 是相邻帧间隔上界的契约。 + +**Spec:** `.scratch/batch-b/abort-path-contracts.md` 的 Requirement 3 +**Evidence:** `.scratch/batch-b/evidence.md#transport-mid-stream-stall` +**Branch:** `test/midstream-timeout` +**Blocked by:** 02(共同修改 `packages/llm/test/transport-timeout.test.ts`) +**Status:** blocked + +- [ ] 用 fence 证明 timeout 前合法首帧已经交付给消费者 +- [ ] 用 TestClock 越过下一帧间隔,随后得到现有 Transport/Timeout +- [ ] 不引入真实墙钟 sleep,不改变 timeout 默认值与错误词汇 +- [ ] 完整 `transport-timeout.test.ts` 覆盖保持绿色 +- [ ] 在 `packages/llm` 运行目标测试与 `bun typecheck` diff --git a/.scratch/batch-b/issues/04-f3-subscription-readiness.md b/.scratch/batch-b/issues/04-f3-subscription-readiness.md new file mode 100644 index 0000000000..4ac2da220e --- /dev/null +++ b/.scratch/batch-b/issues/04-f3-subscription-readiness.md @@ -0,0 +1,14 @@ +# 04 — F3:用确定性 readiness 替代订阅 settle sleep + +**What to build:** 清除 `packages/opencode/test/goal/e2e-loop.test.ts` 的 `SUBSCRIPTION_SETTLE_MS = 200` 与 8 个固定 settle sleeps,用可观察 readiness/fence 或最小调度让步同步 GoalLoop 订阅就绪。 + +**Evidence:** `.scratch/batch-b/evidence.md#f3--固定订阅-settle-sleep` +**Branch:** `test/goal-readiness` +**Blocked by:** 03(批次串行;代码写集独立) +**Status:** blocked + +- [ ] 先证明每个 sleep 等待的具体事件/状态,不用另一个超时数值替换 200ms +- [ ] 8 个固定 settle sleeps 全部删除或由同一确定性同步机制取代 +- [ ] 默认不改生产行为;确需生产 readiness 信号时先在票内写明边界 +- [ ] 目标测试连续运行至少 5 次稳定绿色 +- [ ] 在 `packages/opencode` 运行目标测试与 `bun typecheck` diff --git a/.scratch/batch-b/issues/05-f4-type-safe-dag-store-fixtures.md b/.scratch/batch-b/issues/05-f4-type-safe-dag-store-fixtures.md new file mode 100644 index 0000000000..fed1d221b7 --- /dev/null +++ b/.scratch/batch-b/issues/05-f4-type-safe-dag-store-fixtures.md @@ -0,0 +1,14 @@ +# 05 — F4:移除 DagStore fixture 的双重类型断言 + +**What to build:** 清除 `packages/opencode/test/dag/dag-timeout-escalation-fixes.test.ts` 中两处 `as unknown as DagStore.Interface`,改用类型安全的 `Layer.mock` 或测试 fixture factory。 + +**Evidence:** `.scratch/batch-b/evidence.md#f4--dagstore-双重断言` +**Branch:** `test/dag-store-fixtures` +**Blocked by:** 04(批次串行;代码写集独立) +**Status:** blocked + +- [ ] 两处双重断言都消失,不能只修原评审记录的第一处 +- [ ] fixture 缺少/签名漂移的方法能在 typecheck 时暴露 +- [ ] 不复制 DagStore 生产逻辑到测试 +- [ ] timeout escalation 目标测试行为与断言不削弱 +- [ ] 在 `packages/opencode` 运行目标测试与 `bun typecheck` diff --git a/.scratch/batch-b/issues/06-o1-lkg-spec.md b/.scratch/batch-b/issues/06-o1-lkg-spec.md new file mode 100644 index 0000000000..b2410cb777 --- /dev/null +++ b/.scratch/batch-b/issues/06-o1-lkg-spec.md @@ -0,0 +1,14 @@ +# 06 — O1:remote config LKG 小规格 + +**What to build:** 只为 remote config 的持久化 last-known-good 增量产出一份小规格;不修改生产代码。OpenSpec 原件在 local-only `/openspec/` 生成并校验,同时把可执行镜像写入 `.scratch/batch-b/config-lkg-spec.md`,再更新 07 的具体文件、验收与分支边界。 + +**Evidence:** `.scratch/batch-b/evidence.md#o1--remote-config-last-known-good` +**Branch:** `docs/config-lkg-spec` +**Blocked by:** 05(批次串行) +**Status:** blocked + +- [ ] 定义缓存内容与写入时机:只缓存已验证结构,明确环境替换前后边界 +- [ ] 定义稳定 cache key、原子写、文件权限;key/内容不得泄露 header/token +- [ ] 仅 transport/body 失败允许回退;auth/HTML login/schema decode 不得被 LKG 掩盖 +- [ ] 定义损坏缓存、空缓存与 TTL/不过期策略 +- [ ] `openspec validate --changes` 通过,已追踪镜像与原件一致,07 获得可执行验收标准 diff --git a/.scratch/batch-b/issues/07-o1-lkg-implement.md b/.scratch/batch-b/issues/07-o1-lkg-implement.md new file mode 100644 index 0000000000..d8c81a913d --- /dev/null +++ b/.scratch/batch-b/issues/07-o1-lkg-implement.md @@ -0,0 +1,14 @@ +# 07 — O1:实现 remote config last-known-good 缓存 + +**What to build:** 按 06 产出的已校验 OpenSpec 实现 LKG,并扩展现有 `packages/opencode/test/config/wellknown-offline.test.ts`;不得重新实现已存在的 warn + skip 离线降级。 + +**Evidence:** `.scratch/batch-b/evidence.md#o1--remote-config-last-known-good` +**Branch:** `feat/config-lkg` +**Blocked by:** 06 规格通过校验并补齐本票验收 +**Status:** blocked + +- [ ] 本票开工前把 06 的 OpenSpec requirement/scenarios 链接写入此处 +- [ ] 在线成功后产生可复用 LKG,随后 transport/body 失败按规格回退 +- [ ] auth/HTML login/decode 失败仍保持硬失败 +- [ ] 损坏缓存不崩溃、不覆盖错误类别,且日志不含凭据 +- [ ] 在 `packages/opencode` 运行目标测试与 `bun typecheck` diff --git a/.scratch/batch-b/issues/08-s7-recovery-invented-diagnosis.md b/.scratch/batch-b/issues/08-s7-recovery-invented-diagnosis.md new file mode 100644 index 0000000000..8508c80cda --- /dev/null +++ b/.scratch/batch-b/issues/08-s7-recovery-invented-diagnosis.md @@ -0,0 +1,15 @@ +# 08 — S7:诊断 recovery INVENTED 推断 + +**What to build:** 仅诊断“已语义完成的 durable transcript 被 recovery 判为 active/ownershipLost 并写入 `exec_failed`”是否可复现。当前票不预设存在缺陷,也不授权先改生产代码。 + +**Method:** `/diagnosing-bugs` +**Evidence:** `.scratch/batch-b/evidence.md#s7--recovery-invented-推断` +**Branch:** `test/recovery-diagnosis` +**Blocked by:** 07(批次串行) +**Status:** blocked + +- [ ] 第一项产出是一条确定性、快速、可由 agent 重复运行且能红灯的命令;在此之前不写理论/修复 +- [ ] 症状必须包含“durable transcript 语义完成”与“reconcile 实际写 failed”,不能只单测 helper 返回 active +- [ ] 红灯成立后才列 3–5 个可证伪假设、最小化复现并另开独立修复票 +- [ ] 无法建立反馈回路时记录尝试和阻塞原因,以 no-fix 关闭 +- [ ] 不把既有 ownershipLost → workflow pause 缓解误报为未覆盖 diff --git a/.scratch/batch-b/issues/09-promote-dev-main-release.md b/.scratch/batch-b/issues/09-promote-dev-main-release.md new file mode 100644 index 0000000000..a7bce5d9fa --- /dev/null +++ b/.scratch/batch-b/issues/09-promote-dev-main-release.md @@ -0,0 +1,12 @@ +# 09 — 收束:批 B+C 后 dev → main → release-fork + +**What to build:** 批 B 所有票关闭、批 C P8 完成观测处置后,确认最新 `dev` 全量 CI 绿色,发 dev→main 晋级 PR;四项门禁全绿后合并并手动运行 release-fork。 + +**Blocked by:** 01–08 全部关闭;`.scratch/batch-c/issues/01-p8-spawn-ready-observation.md` 已处置 +**Status:** blocked + +- [ ] 最新 dev push 的 Typecheck、Unit Tests (linux)、E2E Tests (linux/windows) 全绿 +- [ ] 每票 PR/merge commit/验证命令已回填,S7 若确认缺陷则其独立修复票也关闭 +- [ ] dev→main PR 描述列出批 B 交付与批 C no-code/benchmark 裁决 +- [ ] 四项 main PR 门禁全绿后合并 +- [ ] release-fork 从 main 成功产出正式版,再执行用户确认过的分支/worktree 清理 diff --git a/.scratch/batch-c/issues/01-p8-spawn-ready-observation.md b/.scratch/batch-c/issues/01-p8-spawn-ready-observation.md new file mode 100644 index 0000000000..4f5313f378 --- /dev/null +++ b/.scratch/batch-c/issues/01-p8-spawn-ready-observation.md @@ -0,0 +1,12 @@ +# 01 — P8:spawnReady 复杂度观测处置 + +**What to build:** 记录 `spawnReady O(ready × nodes)` 是否有实际性能证据。没有 trace/benchmark/用户痛点时,以 no-code 关闭;不得仅凭静态复杂度实施缓存或索引改造。 + +**Evidence:** `.scratch/batch-b/evidence.md#p8--spawnready-复杂度` +**Blocked by:** None +**Status:** deferred-nonblocking + +- [ ] 搜集已有生产 trace、benchmark 或明确用户场景,不为本票新造大规模优化工程 +- [ ] 无量化证据:记录“当前不做”与重开阈值,状态改 closed-no-code +- [ ] 有量化证据:另开 `/improve-codebase-architecture` 设计票,写明基线与目标 +- [ ] 本观测票本身不改 `spawnReady`