Files
makelore/.project-docs/10-decisions/proposals/20260825-pi-worker-exit-thinking-5e7c2a91__pi-runtime-resilience-hardening.md
T

446 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pi Runtime 鲁棒性加固规范与实施计划
## 0. 文档信息
| 字段 | 值 |
| --- | --- |
| 状态 | PI-R001 已实施并完成验证;PI-R002 保持后续任务 |
| 规划任务 | `20260825-pi-runtime-resilience-plan-4b8e2c71` |
| 当前文档所有者 | `20260825-pi-worker-exit-thinking-5e7c2a91` |
| 规划基线 | `274187e3cfe2ae6cb97f2b4bcb422b3fdf06fbf2` |
| 原始规范 | `20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-spec.md` |
| 原始票据图 | `20260822-pi-runtime-spec-b6e2c9a4__pi-runtime-cutover-tickets.md` |
| 故障诊断 | `20260825-pi-stuck-processing-diagnosis-7c4e91a2` |
| 当前实施任务 | `20260825-pi-worker-exit-thinking-5e7c2a91` |
| 当前实施基线 | `274187e3cfe2ae6cb97f2b4bcb422b3fdf06fbf2` |
| Pi 版本 | `@earendil-works/pi-coding-agent@0.84.2`,保持精确 pin |
| 日期 | 2026-08-25 |
本文是既有 OpenCode → Pi 硬切换规范的事故后补充,不修改原 18 节点 / 31 边 DAG,也不恢复 OpenCode fallback。它把原规范中的 `RUN-004`、`RUN-007`、`RPC-004`、`API-003`、`UX-023`、`UX-026` 和第 18 节故障恢复要求收紧为可执行、可观测、可在最终 Windows 安装包中故障注入的合同。
本次不承诺第三方 Pi 进程“永不退出”。产品承诺是:Pi worker 可以失败,但 Makelore 应用不随之退出;Conversation 不永久锁死;未确认的请求不自动重发;任何 worker 重建都有明确原因并受 generation 隔离。
## 1. 事实、推测与未验证边界
### 1.1 已确认事实
1. 事故发生后 Electron Main 与 Renderer 仍存活,目标 Pi RPC worker 已不存在。
2. 目标 session JSONL 结构有效;最后是 assistant `toolUse(task_state)` 与对应 `toolResult`,没有后续权威 `agent_settled`。
3. `PiWorkerPool.handleInvalidation()` 已将 worker 标为 `crashed`、释放 top-level permit 并发出 `worker.crashed`。
4. `PiConversationRuntime.onPoolEvent()` 对 `worker.crashed` 只投影 `worker.state`,没有调用已有 `failRun()`,因此 worker 已错误而 run 仍为 `running`。
5. `PiConversationRuntime.abort()` 在 worker 已退出时请求失败,当前 catch 会恢复旧的 `running` 状态,造成第二条永久锁死路径。
6. Renderer 的“生成中”“当前对话正在处理”和 Send 禁用均由 run 状态派生;Renderer 是无效 Main 状态的下游表现,不是根因。
7. 复制 session 后,使用最终安装包 Electron executable、Pi 0.84.2、实际 Makelore extension 与受控 loopback Provider 的 11 类故障矩阵均正常 `agent_settled`;普通 HTTP 500、已测的 partial/malformed SSE、正常 `task_state` 和 copied session 不是此次退出的确定性原因。
8. 当前 `PI_RPC_EXITED` 没有携带已有的脱敏 stderr 诊断;Main 日志也不能区分自发退出、RPC 协议失效后强杀和 Main 主动 stop。
9. `set_thinking_level=high` 在当前托管模型形状下可能实际得到 effective `off`,但产品仍持久化请求值;该能力合同错误已确认,但没有证据表明它导致了 worker 退出。
### 1.2 合理推测
- 当前最严重的产品缺陷不是“发生了一个未知退出”,而是生命周期模块未把退出收敛为 Conversation 终态,并且缺少能定位退出发起方的结构化证据。
- 只增加自动重启或固定时长 watchdog 会掩盖状态机缺陷,并可能重复执行已经被 Provider 或 tool 接受的 mutation。
- 保持一 Conversation 一 worker 的隔离模型,比改成单一全局 Pi daemon 更能控制故障半径。
### 1.3 尚未验证
- 本次 worker 消失究竟是一次真实 HTTPS/Undici/Electron-Node 异常、Main 侧 RPC protocol invalidation,还是主动 stop/rebuild 竞态,现有 artifact 无法判定。
- 真实 Provider 的认证、代理、限流、响应差异、真实并发和跨 worker 凭据隔离仍未验证。用户已将其标记为 `Explicitly Waived / Accepted Risk`,`realTurnVerified=false`。
- macOS x64/arm64 与 native non-WSL Linux 的发布证据状态不因本规范改变。
## 2. 目标与非目标
### 2.1 目标
1. Pi worker 异常退出不得导致 Electron Main、Renderer 或整个应用退出。
2. matching generation 的 worker 一旦不可用,任何仍活动的 run 必须有界收敛到 `status=error`、`terminalReason=failed`、`recoverable=true`。
3. Composer 在故障终态恢复编辑/恢复操作,不得继续显示无界“处理中”。
4. crash、abort、`agent_settled`、recover 和 generation replacement 必须按 Conversation + generation 串行且终态单调。
5. accepted 或 uncertain prompt 永不自动重放;recover 复用同一产品 Conversation 和已有 Pi session binding。
6. 每个 Main 主动 stop、protocol invalidation 和 unexpected exit 可明确区分,且诊断不泄露敏感内容。
7. thinking level 只持久化 Pi 实际支持并接受的 effective level;切换本身不得隐式重启正常 worker。
8. 最终 Windows 安装包具备确定性故障注入证明,而不是只依赖 fake child 单测。
### 2.2 非目标
- 不保证操作系统、Electron、Pi、原生依赖或 Provider 永不失败。
- 不新增全局 runtime start/stop/restart UI。
- 不引入 OpenCode fallback、双 runtime、兼容层、feature flag 或 Pi 上游 fork。
- 不添加“长时间无输出就杀 worker”的通用 Provider watchdog。
- 不自动重发 prompt、steer、follow-up、fork、compact 或任何 mutation RPC。
- 不把 loopback/fault injection 写成真实 Provider Pass。
- 不在本票据完成 macOS 或 native non-WSL Linux 发布验收。
## 3. 架构裁决
### 3.1 深模块与 seam
继续由 Main-owned `PiConversationRuntime` 作为产品 Conversation 生命周期的深模块。Renderer、Host route 和 store 只消费 Makelore-owned Snapshot/Patch 与稳定错误;它们不理解 Pi process、RPC、stderr 或 stop reason。
内部职责保持:
- `PiWorkerProcess`:spawn、RPC 字节流、exit/close、协议失效、有限诊断和进程树退出。
- `PiWorkerPool`:Conversation → worker/generation 归属、permit、LRU、active run、replacement 与 crash event。
- `PiConversationRuntime`:产品 run 状态、event projection、terminalization、recover/hydration 与 Host SSE。
- `PiSessionRegistry`:Conversation 与 session binding、模型和 thinking metadata 的持久化。
- extension/subagent/write-lease 模块:generation/run-owned 资源,失败时可由同一生命周期收敛路径取消。
不得新增一个只转发这些方法的浅层“supervisor service”。需要的行为应深化现有三个模块,并以现有 `CodingConversationRuntime` interface 作为外部 test seam。
### 3.2 核心不变量
| ID | 不变量 |
| --- | --- |
| `RES-INV-001` | 同一 Conversation 不允许长期存在 `worker=crashed/error` 且 `run=running/aborting/retrying/compacting`。 |
| `RES-INV-002` | matching generation 的 crash 只能结束目标 Conversation;其他 Conversation 的 Snapshot、run、queue、worker 与 draft 不变化。 |
| `RES-INV-003` | terminal run 单调;较晚到达的 close、abort rejection 或旧 generation event 不得覆盖 completed/aborted/failed。 |
| `RES-INV-004` | `clientRequestId` 已 accepted 或状态 uncertain 时,不得因 recover/SSE reconnect/worker replacement 自动再次执行 mutation。 |
| `RES-INV-005` | 每个 generation replacement 必须有 Main-owned bounded reason;没有 reason 的 replacement 是测试失败。 |
| `RES-INV-006` | running worker 不因 LRU、thinking change、同账号 model change 或普通 diagnostics 被 stop。 |
| `RES-INV-007` | worker crash 后 permit、interaction、extension run、child、process lease 与 write lease 最终全部释放。 |
| `RES-INV-008` | recover 使用原 Conversation/session binding;不截断、覆盖或静默替换旧 JSONL。 |
| `RES-INV-009` | 原始 stderr、Provider body、prompt、tool 内容、Header、Token、账号 ID、完整路径不进入 Renderer 或普通日志。 |
### 3.3 目标状态流
```text
idle/ready -> preparing/queued -> running -> settling -> idle/completed
| |
| +-> late close: keep completed
+-> worker exit/protocol invalidation
-> error/failed/recoverable
-> explicit recover
-> new generation + same session binding
-> hydrate -> idle
```
不存在从 `worker exit` 返回旧 `running` 的边;不存在从 `recover` 自动重发旧 prompt 的边。
## 4. 故障分类与重建政策
### 4.1 进程终止分类
| 分类 | 判断来源 | 产品动作 | 重建政策 |
| --- | --- | --- | --- |
| Main intentional stop | stop 前已记录 reason | 按原业务完成清理;不伪装 crash | 仅由对应显式操作或安全边界继续 |
| RPC protocol invalidation | strict framer/RPC 判定 | 保留 `PI_RPC_PROTOCOL_ERROR`,强制结束目标 generation | 用户 recover;mutation 不重放 |
| Unexpected exit | 无 intentional stop、无既有 protocol invalidation 的 exit/close | 保留 `PI_RPC_EXITED` 内部分类并投影稳定 recoverable runtime error | 用户 recover;mutation 不重放 |
| App shutdown | `app_shutdown` reason | 拒绝新 prompt,3 秒 graceful 后整树 kill | 下次启动按 registry reopen |
| Idle eviction | `idle_eviction` reason,且 run 已 idle | 保留 session binding,不投影 run failure | 下次显式 prepare 可重开 |
| Stale resource rebuild | `stale_resource_rebuild` reason | active run 先 settled;旧 generation 失效 | settled 后一次重建 |
| Recover/dispose/fork replacement | 对应显式 reason | 取消旧 generation 资源并保持产品语义 | 只执行该操作要求的 replacement |
Main 主动 stop reason 使用有限内部类型,至少覆盖:
```text
app_shutdown
idle_eviction
stale_resource_rebuild
recover
dispose
fork_replacement
protocol_invalidation
test_injection
```
实现可使用与当前调用点等价的更精确名称,但禁止自由文本 reason 和缺省 reason。
### 4.2 自动动作边界
- 保留既有只读 RPC 在 generation 不变时最多一次 retry 的合同。
- spawn/ready 失败继续进入明确 recoverable error,不添加后台无限拉起。
- accepted run 的 worker 退出后不自动新建 generation,也不自动重发请求。
- 用户显式 recover 才执行“关闭旧 generation → 清理 → spawn → reopen same binding → hydrate”。
- Provider 的 429/5xx 只使用 Pi 自己的有界 retry;Main 不叠加 retry。
- 不使用无 token/无事件时长作为 worker 死亡判断。合法的模型推理、工具执行和 compaction 可能很长。
## 5. 运行时终态与竞态合同
### 5.1 `worker.crashed`
`PiConversationRuntime` 必须把 crash handling 放入目标 Conversation 的 projection chain:
1. 读取事件携带的 Conversation、generation 和原始 `PiProcessError`。
2. 若 Snapshot 不存在或 generation 已过期,只做脱敏 lifecycle 记录,不改产品状态。
3. 取消该 generation 的 interaction、extension run、child 与其他 run-owned 资源。
4. 若 matching run 仍为活动状态,调用统一 `failRun()`,投影 `status=error`、`terminalReason=failed`、`settledAt` 与稳定 recoverable error。
5. 投影 worker error state。
6. 若 run 已被较早的权威 `agent_settled` 终结,不得改写终态。
### 5.2 `abort()`
- live worker 的正常 abort 继续等待权威 `agent_settled`,不得伪造成功。
- abort request 失败后必须重新读取最新 run 与 matching worker generation。
- run 已 terminal:保留终态并返回/投影对应结果。
- worker 已 crashed/absent 且仍是同一活动 run:收敛到与 crash 相同的 failed/recoverable 终态。
- worker 仍 live、abort RPC 只是可恢复命令错误:可以恢复原活动态并返回错误,但必须证明 worker 仍归该 generation。
- 任何路径都不得把 `error/completed/aborted` 恢复成 `running`。
### 5.3 事件顺序
必须覆盖以下交错:
1. `worker.crashed -> abort rejection`;最终 failed。
2. `abort request -> worker.crashed`;最终 failed,不回到 running。
3. `agent_settled(completed) -> close`;保持 completed。
4. `worker.crashed -> late agent_settled from old generation`;忽略 late event,保持 failed。
5. `recover generation N+1 -> generation N late response/event/interaction`;全部丢弃。
6. 目标 Conversation crash 时另一个 Conversation running/settled;另一个不受影响。
## 6. 资源与会话完整性
### 6.1 必须收敛的资源
| 资源 | 所有者 | crash 后要求 |
| --- | --- | --- |
| top-level running permit | Worker pool | 释放一次,计数不得为负 |
| queued run | Worker pool | 目标项以原始错误拒绝,不重排其他 FIFO |
| process lease | Worker pool/process budget | `finally` 释放 |
| interaction | Interaction store | generation/run scoped cancel |
| extension run binding | Extension host/UI | clear/end,旧 token 无效 |
| subagent dispatch/child permit | Scheduler | parent crash 向下取消并释放 |
| child process | Child opener | 有界 stop/kill,零残留 |
| project write lease | Write lease | holder/waiter 均可取消,其他项目不受影响 |
所有清理动作必须可重复;一个 cleanup step 失败不得阻止后续资源释放。不得用吞掉所有错误来伪装成功,首个失败应进入脱敏诊断。
### 6.2 Session 与请求安全
- worker exit 保留 session JSONL 和 registry binding。
- recover 不创建新的产品 Conversation,不改变稳定 Conversation ID。
- hydrate 从 `get_state/get_entries` 重建 Snapshot,并继续使用 generation fence。
- 已持久化的 assistant/tool 结果可以水合;尚未确认的 prompt 不重发。
- draft、待上传 attachment 和 optimistic user node 按现有 request reconciliation 规则保留。
- session unreadable 继续 fail closed;不得自动截断或覆盖文件。
## 7. Model、thinking 与 revision 合同
### 7.1 Thinking
1. 调用前获取或使用已验证的 `get_available_thinking_levels` 能力。
2. 不支持的 level 必须拒绝,或由明确产品规则选择并展示有效值;不得请求 `high` 却持久化一个 Pi 实际为 `off` 的假状态。
3. `set_thinking_level` 成功后读取 Pi effective state,只有 effective value 可写入 Registry 和 Snapshot。
4. 写 Registry 失败时保持旧产品状态并投影稳定 persistence error。
5. thinking change 不 replacement worker generation。
6. 当 Provider 无法真正关闭 reasoning 时,UI 不得把“关闭思考”表现为可保证抑制 Provider reasoning 的能力。
### 7.2 Model 与资源 revision
- 同 Provider account 的 model change 继续使用 `set_model`,不重建 worker。
- 跨 Provider account 只重建目标 Conversation,并保持现有 credential isolation。
- Provider/resource/credential revision 在 run active 时只标记 stale;settled 后用显式 reason 重建。
- revision rebuild、recover、fork replacement 的 generation reason 必须进入 Main-only lifecycle 记录。
## 8. 可观测性与隐私
### 8.1 Main-only lifecycle 事件
至少记录以下结构化事件:
```text
worker.stop_requested
worker.protocol_invalidated
worker.exit
worker.close
worker.crashed
run.failed
worker.replacement_started
worker.replacement_ready
worker.recovered
```
允许字段:匿名/局部 Conversation 标识、runId 局部标识、generation、stage、stop reason、错误 code、exit code、signal、cold/warm、duration、是否完成各类 cleanup。
禁止字段:prompt、assistant 内容、thinking 内容、tool 名称/参数/结果、文件内容、完整路径、Provider body、Header、Token、credential、账号 ID、完整 request ID。
### 8.2 错误保真
- `PiWorkerProcess` 在 `PI_RPC_EXITED` 中附带已有的 bounded/redacted stderr tail。
- protocol invalidation 保留原 `PI_RPC_PROTOCOL_ERROR`,后续 close 不得覆盖错误类别。
- intentional stop 不能被 close handler 再标成 crash。
- stderr 只进入 Main-owned diagnostic/log,不进入 Renderer DTO。
- 若新安装包再次发生问题,证据必须能直接回答“谁发起 stop、哪类 invalidation、哪个 generation、退出码/信号是什么”。
如结构化证据仍只显示无法解释的原生异常终止,再单独评估 opt-in Windows dump;本票据不先引入 crash-dump 基础设施。
## 9. 用户体验合同
- worker crash 后,目标 Conversation 在 Main 收到 exit/invalidated 后不晚于 2 秒进入 recoverable error。
- Textarea 保持可编辑;Composer 不再显示无界“当前对话正在处理”。
- UI 显示固定中文说明:本地 Agent 已中断,原请求未自动重发。
- 提供目标 Conversation 的 recover 操作;recover 失败保持可再次操作的错误态,不进入永久 recovering。
- 另一个 Conversation 的生成、输入、队列和错误不变化。
- 技术 stderr、exit code、内部路径和 stop reason 不直接展示给普通用户;诊断面板只保留安全结构化状态。
## 10. 实施票据与阶段
本规范新增事故后纠正票据 `PI-R001`,不插入或重写原 18 节点 / 31 边 cutover DAG。
### PI-R001 — Pi worker failure convergence and explainable lifecycle
| 字段 | 值 |
| --- | --- |
| 状态 | 已授权;实施中 |
| 实施任务 | `20260825-pi-worker-exit-thinking-5e7c2a91` |
| 基线 | `274187e3cfe2ae6cb97f2b4bcb422b3fdf06fbf2` |
| 依赖 | 已交付的 Pi hard cutover、Windows proxy/session reopen hotfix、故障诊断任务 |
| 解锁 | 新 Windows 安装包人工复测;后续 `PI-R002` soak/现场归因 |
#### 阶段 A:生命周期终态
- 修改 `electron/coding-runtime/pi/runtime.ts` 的 crash projection 与 abort race。
- 必要时调整 `worker-pool.ts` 事件载荷,但不得扩大公共 Renderer interface。
- 先添加 red-capable 回归,再修改实现。
产物:worker crash/abort/settled 的单调状态机;目标 run 不再永久 active。
#### 阶段 B:退出分类与 stop reason
- 修改 `worker-process.ts`、`process-errors.ts`、`worker-pool.ts`、`telemetry.ts`。
- 显式标注所有 stop 调用点;保留 protocol/exit 原始分类与脱敏 stderr。
- 证明 intentional stop 不被 close 误判,protocol failure 不被 exit 覆盖。
产物:可解释 generation replacement 与下一次现场问题所需的最小证据。
#### 阶段 C:资源收敛
- 覆盖 extension、interaction、subagent、process budget 和 write lease 的 crash cleanup。
- 复用现有 cleanup seam,不新增全局协调器。
- 验证 cleanup 部分失败时仍继续释放其余资源。
产物:目标故障后所有活动/等待计数归零,其他 Conversation 继续运行。
#### 阶段 D:thinking 能力合同
- 修改 `runtime.ts`、`provider-config.ts`、managed catalog/Registry 相关代码。
- 只持久化 effective level,unsupported level 不伪装成功。
- 验证 thinking change 和同账号 model change 不产生 generation replacement。
产物:UI、Registry 与 Pi 实际状态一致。
#### 阶段 E:Host、Renderer 与安装包证明
- 覆盖 `electron/api/routes/coding-conversations.ts`、`src/stores/coding-conversations.ts`、`CodingChatPanel.tsx`、`CodingComposer.tsx`、`CodingConversationHeader.tsx` 的现有 seam。
- 扩展 `release-proof.ts`、fake child、Windows Electron E2E 与 packaged proof。
- 生成新的 Windows x64 NSIS,并返回路径、大小、SHA-256 和 clean candidate commit。
产物:最终安装包中可复现的强制退出、恢复和零残留证明。
### PI-R002 — Soak and incident classification follow-up
`PI-R002` 不与 `PI-R001` 并发实施,也不阻断当前修复包交付。它在 `PI-R001` 安装包完成后执行:
1. 以受控 loopback 连续运行 create/open/prompt/tool/abort/recover,并随机注入目标 worker exit。
2. 在 4 parent + 4 child 压力中注入单点失败,确认隔离和计数收敛。
3. 观察 normal path 的 generation replacement:每次都必须有已知 reason,未知 reason 为失败。
4. 若用户真实环境再次发生退出,读取新的结构化诊断确定 initiating class,再决定是本地修复、上游 Pi issue 还是 Windows/Electron 原生诊断。
真实 Provider 因用户 waiver 不属于 `PI-R002` hard gate;这意味着产品只能宣称本地 runtime failure containment 已验证,不能宣称所有真实 Provider 行为已验证。
## 11. 验收矩阵
| ID | 场景 | 必须结果 |
| --- | --- | --- |
| `RES-ACC-001` | accepted running + unexpected worker exit | ≤2 秒投影 run error/failed/recoverable;Composer 解锁;应用存活 |
| `RES-ACC-002` | worker exit 后点击 abort | 不恢复 running;最终仍为 failed/recoverable |
| `RES-ACC-003` | abort 与 exit 交错 | 终态单调;资源只释放一次 |
| `RES-ACC-004` | `agent_settled` 后 close | completed/aborted 不被改成 failed |
| `RES-ACC-005` | crash 后旧 generation event | 被丢弃,不修改新 Snapshot |
| `RES-ACC-006` | Conversation A crash、B running | B 正常 settled,B generation/run/draft 不变 |
| `RES-ACC-007` | strict JSONL protocol violation | 保留 protocol error,记录 protocol stop reason,目标 worker 被清理 |
| `RES-ACC-008` | deliberate idle eviction/app shutdown/recover | 各自记录正确 reason,不记为 unexpected crash |
| `RES-ACC-009` | crash 时存在 interaction/child/write lease | 全部取消/释放;其他 Conversation/项目不受影响 |
| `RES-ACC-010` | recover existing Conversation | 复用 session binding,hydrate 成功,不自动重发原 prompt |
| `RES-ACC-011` | thinking level unsupported | 拒绝或返回实际 effective level,不持久化假值 |
| `RES-ACC-012` | thinking 与同账号 model change | worker generation 不变化 |
| `RES-ACC-013` | stderr 含 token/header/content 形状 | Main 诊断有界脱敏;Renderer/日志无敏感值 |
| `RES-ACC-014` | final packaged fault injection | 真实 `app.asar` Main + packaged Pi 通过 crash/recover/isolation/cleanup |
| `RES-ACC-015` | 应用退出 | 3 秒 graceful/强杀合同生效,最终本任务 Pi/Electron 残留进程为 0 |
| `RES-ACC-016` | 正常 packaged flow | 无无原因 generation replacement;既有 cold/warm/acceptance 预算不回退 |
## 12. 测试与发布门禁
### 12.1 Focused tests
- `tests/unit/pi-conversation-runtime.test.ts`
- `tests/unit/pi-worker-pool.test.ts`
- `tests/unit/pi-worker-process-real.test.ts`
- `tests/unit/pi-rpc-foundation.test.ts`
- `tests/unit/pi-subagent.test.ts`
- `tests/unit/pi-write-lease.test.ts`
- `tests/unit/coding-conversations-store.test.tsx`
- `tests/unit/coding-chat-panel.test.tsx`
- 对应 Host route、release proof 和 managed model tests
每个行为变更必须先有能在旧实现失败的正确 seam 回归。不得只断言函数“没有抛错”;必须断言最终 Snapshot、generation、资源计数、日志分类和 prompt 未重放。
### 12.2 必跑命令
使用 `packageManager` 固定的 pnpm `10.33.4`:
```text
corepack pnpm install --frozen-lockfile
corepack pnpm run typecheck
corepack pnpm run lint:check
corepack pnpm test
corepack pnpm run build:vite
corepack pnpm run test:electron:windows
corepack pnpm run package:win
corepack pnpm run verify:publish-runtime
corepack pnpm run verify:artifact:win
corepack pnpm run verify:artifact:pi
corepack pnpm run test:pi-subagent:packaged
```
### 12.3 Packaged fault injection
最终 clean candidate 的 Windows 安装包必须覆盖:
1. running 中强制退出真实 packaged Pi worker;Main/Renderer 存活。
2. abort-after-exit;不会回到 running。
3. settled-before-close;completed 不被覆盖。
4. strict framer protocol invalidation;错误分类不被 close 覆盖。
5. deliberate stop;reason 与 crash 分类分离。
6. 目标 recover;原 binding/hydration 保留,原 uncertain prompt 不重发。
7. 两 Conversation 隔离;只杀一个 worker,另一个完成。
8. parent/child/write lease/process budget 清理;所有 active/waiting 计数归零。
9. thinking/model 变更不造成非预期 generation replacement。
10. stderr redaction 与退出后零残留进程。
报告必须绑定:代码 commit、安装器路径、字节数、SHA-256、Electron/Node/Pi 版本、测试样本、`realTurnVerified=false` 和未完成平台边界。
## 13. 提交与合并批次
推荐批次如下,每批都可独立审阅:
1. `test: reproduce Pi worker exit lifecycle lock`:只加入 crash/abort/ordering red tests。
2. `fix: terminalize Pi worker failures`:runtime/pool 最小状态机修复与资源收敛。
3. `feat: classify Pi worker lifecycle exits`:stop reason、错误保真、脱敏 diagnostics。
4. `fix: persist effective Pi thinking level`:capability/effective state 与 no-restart tests。
5. `test: prove packaged Pi failure recovery`:Host/Renderer/E2E/release proof。
6. `docs: record Pi resilience qualification`:任务证据、安装器信息和剩余风险。
合并前必须审阅累计 diff;不得把旧 hotfix worktree、诊断临时脚本、构建输出、安装器、用户数据或敏感日志提交进 Git。
## 14. 阻断与停止条件
出现以下任一情况,实施任务不得宣称完成:
- crash 后仍能得到 worker error + active run 的组合。
- abort-after-exit 仍恢复 running/aborting。
- accepted prompt 在 recover、SSE reconnect 或 worker replacement 中被再次发送。
- cleanup 只在 happy path 归零,fault injection 后有 orphan process/permit/lease。
- protocol error 被后续 close 覆盖成普通 exit。
- 任一 generation replacement 缺少 reason。
- thinking UI/Registry 与 Pi effective state 不一致。
- packaged proof 只使用 repo-source composition 或 fake process,没有穿过最终 `app.asar` Main 与 packaged Pi。
- 最终 Windows artifact/runtime closure、Electron E2E、build 或文档门禁失败。
若完成上述本地合同需要修改 Pi 上游包、引入全局 daemon 或改变真实 Provider waiver,实施者必须停止并回报,不得自行扩大架构方向。
## 15. 风险与发布口径
- `PI-R001` 通过后可以宣称:Windows 最终安装包已验证 Pi worker 故障隔离、Conversation 终态收敛、显式恢复、无自动重放、退出可观测和零残留。
- 不得宣称:Pi 永不崩溃、真实 Provider 永不触发新协议问题、跨平台 release-ready。
- 真实 Provider:`Explicitly Waived / Accepted Risk`,`realTurnVerified=false`,不是 Pass。
- macOS x64/arm64、native non-WSL Linux:状态不变,不是本票据 Pass。
- 新诊断上线后若再现问题,优先使用结构化 exit/stop evidence 定位;不要先增加重启次数、watchdog 或内容日志。