Add Phase 3 review cross-check ruling
Harness governance / validate (push) Has been cancelled
Phase 0 build gate / verify (push) Has been cancelled

Adjudicate Codex's review correction: concede three overstatements in
the original review (M3 loop not actually complete - InstallService has
no production assembly and T-401 launch is unbuilt; scope 'no security
defect' to T-302/T-303; O2 mischaracterized - ENOSPC during io.Copy is
misclassified as zip_corrupt with a broken error chain, escalate to P1;
O1 fix was flawed - recheck IsRunning explicitly instead of inferring
app_running from rename failure). Add finding: disk-full surfaces at
write/sync/close with three different codes, so the O2 fix must span the
whole write->sync->close sequence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-19 20:06:55 +08:00
co-authored by Claude Fable 5
parent 1ad00f8ef2
commit 09f56478d3
+95
View File
@@ -91,3 +91,98 @@ Phase 3 可作为启动/更新/授权阶段的可信安装基线。安装整合
4. **[低优先]** O4/O5:统一 package stage 命名;预留模型随 backup 策略复核。
> 声明:本报告为静态审计与攻击路径推演,逐项核了实现(含 `verifyPackageSHA256` 单句柄链、`io.MultiWriter` 每文件哈希、`preExtractCheck` 强制性、`failureCodeFor` 映射);因 WSL 无 Go 未执行测试,双 workspace 闸门通过为 Codex 自述。O1 的 Windows 文件锁语义需真机验证。
## Codex 复核修正(2026-07-19)
本节保留上文 Claude Code 的静态审计结论,并根据当前代码、任务边界和路线图作出补充/修正;没有修改 T-301~T-303 的实现。复核时额外执行了 `go -C core test -count=1 ./installer ./application/install`,两个包均通过。
### 修正后的总体判断
上文对 T-302/T-303 的核心信任边界判断成立:已验签 Catalog selection、同一普通文件句柄上的 size/SHA-256、ZIP/app manifest 验证、安全 staging、switch/rollback 以及 staging 前环境预检的顺序正确,未发现从未验证下载字节到执行/切换的直接绕过路径。
但“`M3(清单→列表→下载→安装→启动)核心闭环成立`”不成立。路线图把 M3 定义为 Phase 2~4 前半的闭环,而 T-401 的 Toolhelp 进程检测、启动前检查、WorkingDirectory 与实际启动尚未落成;当前 `NewInstallService`/`TargetStateChecker` 只在测试中构造或调用,尚无命令层生产装配。因此准确表述应为:**T-302/T-303 的无头 core 可信安装边界已完成;端到端安装编排和启动闭环尚未完成。**
同理,“Phase 3 未发现安全缺陷”应限定为“本轮深审的 T-302/T-303 未发现新的信任边界绕过”。本报告明确未重新深审 T-301,不能据此替代对整个 Phase 3 的完整安全结论。
### O1 · 接受,并转入 T-401 的切换临界区契约
`TargetStateChecker.IsRunning` 目前只在解压前执行;解压完成到 `Switcher` 的 `current → backup` 之间存在时间窗口,用户可在此期间启动旧版本。该观察成立,优先级应为 P1 可靠性整改。
处理方式不应把所有 rename 失败一概映射为 `app_running`:权限、杀毒软件、目录损坏等错误会产生错误归因。应在 `current → backup` 的紧邻前增加可注入的最后一次运行状态检查;只有该检查明确返回“仍在运行”时才返回 `app_running`。T-401 落成时应显式消费 T-303 的 `TargetStateChecker` 契约,并补充“解压期间启动旧版”的测试;Windows 文件锁/映像节语义仍需 T-601 真机/VM 验证。
### O2 · 提升为 P1:中途磁盘写满会误报且丢失根因
上文正确指出磁盘预检只是 fail-fast 建议,不能消除“查询后被其他进程占满”的窗口;`docs/api.md` 已经写明预检不能替代写入、同步、切换和回滚阶段的 fail-closed I/O 处理。
但现状比“中途 ENOSPC 变成通用 switch 失败”更严重:`core/installer/extractor.go` 的 `io.Copy` 失败统一返回 `ErrArchiveCorrupt`,并把原始 `copyErr` 作为 `%v` 格式化文本而非 `%w` 错误链。若目标文件写入返回 ENOSPC,调用方会得到 `zip_corrupt`,且不能用 `errors.Is` 识别原始 I/O 根因;这与 T-303 对稳定错误码和可识别根因的目标不符。
整改应同时做到:区分 ZIP 输入/CRC 读取失败与 staging 输出写入失败、保留原始 error chain、把可识别的“磁盘已满”归一为 `disk_full`。跨 Windows/非 Windows 的磁盘满判定不得在 core 直接依赖 Windows API;应由明确的、可测试的接口或平台适配提供分类。`os.RemoveAll(destinationRoot)` 当前是忽略错误的尽力清理,旧版本安全来自尚未进入 switch,而不是清理本身保证原子性;清理失败的恢复语义也应在整改中明确。
### O3 · 接受,但属于后续编排/生命周期任务
已完成 `.download` 的生命周期目前没有生产消费者:下载任务注释只说明 completed 文件“等待后续 workflow consume/remove”,`InstallService` 只接收候选路径且不应删除任意外部路径。应在正式安装编排中定义安全消费协议:仅安装成功后,由下载队列或以 `request_id` 派生路径的编排层删除完成文件和任务元数据;安装失败保留文件用于重试/诊断。该项不应通过让 `InstallService` 直接删除 `DownloadPath` 来解决。
### O4 · 不建议作为独立整改项
`PackageStageVerify` 覆盖 ZIP 的可信性/安全验证,而 `PackageStagePreflight` 专指验证完成后的环境 hook;二者语义可区分,当前错误码也依赖根因而非 stage。命名略有认知成本,但不影响安全、行为或用户可见码。除非后续观测系统需要更细粒度指标,否则不应为此单独改动稳定 stage API。
### O5 · 接受,维持为低优先级架构假设
当前 app root 内的 `current → backup` 与 `staging → current` 都是同卷 rename;旧 current 与完成下载包已占用的空间会反映在预检时的可用空间中,额外需求为新 payload 加 64 MiB 是合理的 v1 启发式。若未来改成跨卷复制、保留多份 backup 或改变下载/安装卷布局,再重新评估该模型。
### 建议的后续顺序
1. 在下一次正式整改前,先落成任务规格,关闭 O2 的输出 I/O 错误保留与 `disk_full` 分类问题。
2. 落成 T-401 时,把运行状态适配、启动协议和 O1 的切换临界区复查作为同一契约实现,并将 T-303 明确列为其依赖/接口前提。
3. 在下载完成到安装的生产编排任务中,定义 O3 的 completed 文件消费和失败保留规则。
## 交叉复核裁定(定稿)
> 复核视角:Claude 全栈开发工程师,对 Codex 复核修正逐条核验后裁定。
> 核验方式:grep `NewInstallService` 生产调用者;读 `extractor.go` 第 203-235 行 io.Copy/fsync/close 的错误包装与码映射;读 `06-tasks.md` M3 定义;因 WSL 无 Go,未执行测试。
> 日期:2026-07-19
### 承认原审核错误
本轮 Codex 复核比原审核更准。原审核有两处 overstate 与一处有缺陷建议,应更正:
- **"M3 核心闭环成立" 撤回——原审核错。** `NewInstallService` 无任何生产调用者(仅定义/测试);M3 路线图定义含"启动(Phase 2-4 前半)",而 T-401(Toolhelp 进程检测 + 启动)未落成。准确表述:**T-302/T-303 的无头 core 可信安装边界已完成;端到端编排与启动闭环未完成。**
- **"Phase 3 未发现安全缺陷" 应限定为 T-302/T-303。** 本轮未重新深审 T-301,不能替代整个 Phase 3 的安全结论。
- **原 O2 定性错误(位置 + 严重性)。** ENOSPC 发生在**解压阶段的 `io.Copy` 写**,不是"通用 switch 失败";它被包成 `ErrArchiveCorrupt` + `%v` → 映射为 `zip_corrupt`(误导为"包坏了")且断了 error chain。这直接违反 T-303 "可识别根因"目标。O2 升 P1 成立。
- **原 O1 建议有缺陷,收回。** "把占用导致的 rename 失败映射到 `app_running`" 会错误归因(权限/杀软/目录损坏也会致 rename 失败);正确做法是 switch 紧邻前**显式复查 `IsRunning`**,只有其返回运行才报 `app_running`。
### 已确认的事实(可作为后续任务前提)
| 项 | 结论 | 证据 |
| --- | --- | --- |
| InstallService 无生产装配 | 属实(M3 撤回) | `NewInstallService` 仅 `service.go:129` 定义,无非测试调用者 |
| M3 含"启动"且未完成 | 属实 | `06-tasks.md:114` M3=「…安装→启动」Phase 2-4 前半;T-401 未落成 |
| ENOSPC 误分类为 zip_corrupt | 属实(O2 升级) | `extractor.go:210` io.Copy 失败包 `ErrArchiveCorrupt`+`%v`;`failureCodeFor` → `zip_corrupt`;消息误标 "read" |
| 磁盘满有三种落点三种码(本轮新增) | 属实 | write→`zip_corrupt`(210);`syncFileWithFence`→默认 `install_failed`(230-231);`Close`→默认 `install_failed`(235)。ENOSPC 常延迟到 fsync/close 暴露 |
| RemoveAll 忽略错误 | 属实 | 失败清理 `_ = os.RemoveAll(...)`;旧版本安全来自"尚未进 switch"而非清理原子性 |
### 采纳 Codex 的修正与 sharpen
- **接受** M3 表述撤回、"无安全缺陷"限定到 T-302/T-303。
- **接受** O1 精化:switch 紧邻前可注入的最后一次 `IsRunning` 复查;**不**把 rename 失败一概映射为 `app_running`;与启动协议一并在 T-401 落成,列 T-303 为接口前提;Windows 映像节/文件锁语义留 T-601 真机验证。
- **接受** O2 升 P1:区分 ZIP 读/解压失败与 staging 写失败、保留原始 error chain(`%w`)、可识别"磁盘满"归一 `disk_full`;分类判定经接口/平台适配注入,不在 core 直接依赖 Windows API。
- **接受** O3 精化(定义安全消费协议:仅成功后由下载队列/编排层按 request_id 派生路径删除,失败保留;不让 InstallService 删任意外部路径)、O4 降级(verify=可信性验证 / preflight=环境 hook,语义可区分,不改稳定 stage API)、O5 维持低优先。
### 新增(对 O2 的补强):磁盘满整改需覆盖 write→sync→close 全序列
O2 的修复**不能只改 `io.Copy` 那一行**。ENOSPC 在 Linux 常因延迟分配推迟到 `write`/`fsync`/`close` 任一处暴露,当前三处给出 `zip_corrupt`/`install_failed`/`install_failed` 三种码。整改需:
- 用平台感知 ENOSPC 判定(POSIX `syscall.ENOSPC` / Windows `ERROR_DISK_FULL`,经注入接口,不进 core)在 write/sync/close 三处统一归一 `disk_full`。
- 把 `output` 包一层"记录写错误的 writer"以区分写失败 vs 读/解压失败(`io.Copy` 本身不告知哪侧失败)。
- 明确清理失败(`RemoveAll` 报错)的恢复语义。
### 最终处理顺序(定稿)
1. **[可靠性 · P1]** O1:switch 紧邻前显式复查 `IsRunning`(收窄 TOCTOU),随 T-401 启动协议同契约落成,列 T-303 为前提;真机文件锁验证归 T-601。
2. **[错误可诊断性 · P1]** O2:write→sync→close 全序列统一 ENOSPC→`disk_full`,保留 error chain,区分读/写失败,分类经平台适配注入;明确清理失败恢复语义。
3. **[生命周期]** O3:正式安装编排定义 completed 下载文件的安全消费与失败保留协议。
4. **[低优先]** O4/O5:stage 命名维持现状;预留模型随 backup 策略复核。
5. **[文档]** 修正 `current-state.md` / 相关文档中 M3 表述:无头 core 安装边界已完成,端到端编排与启动闭环待 T-401。
> 裁定:本轮 Codex 复核成立且优于原审核——撤回 M3 闭环 overstate、纠正 O2 定性(位置+严重性)、收回 O1 有缺陷建议、限定"无安全缺陷"范围。安全边界总结论(T-302/T-303 无绕过)双方一致,不需回滚;新增"磁盘满三落点"补强并入处理顺序第 2 步。