Files
soft_quay/docs/tasks/T-615.md
T

60 lines
5.1 KiB
Markdown
Raw Normal View History

---
id: T-615
title: 保留安装输出 I/O 根因并统一磁盘满诊断
phase: 3
deps: [T-303]
status: TODO
created: 2026-07-19
issue: null
context_ref: null
claim_branch: null
work_branch: null
write_paths:
- docs/tasks/T-615.md
- core/installer/
- core/application/install/
- docs/api.md
- docs/04-architecture.md
- docs/00-ai-start-here.md
- docs/06-tasks.md
- docs/current-state.md
---
## 问题 / 背景
T-303 的磁盘预检只能 fail-fast,不能消除“查询后被其他进程占满”的窗口。当前 `Extractor.extractPlan` 将 `io.Copy` 的所有失败包成 `ErrArchiveCorrupt`,并以 `%v` 丢弃原始 error chain;staging 写入的 ENOSPC 因而可能被错误映射为 `zip_corrupt`。此外,staging 清理使用忽略错误的 `os.RemoveAll`:旧 `current` 因尚未进入 switch 而安全,但未清理的 staging 会让后续安装缺少明确的恢复语义。
这不是包信任边界绕过,但违反 T-303 的稳定失败码与可由 `errors.Is` 识别根因的诊断契约。`docs/review/phase3-review.md` 的交叉复核将其裁定为 P1;必须在 T-401 装配真实运行状态/启动能力之前关闭。
## 方案
1. 在 `core/installer` 区分 ZIP 输入(打开、读取、CRC/关闭)失败与 staging 输出(目录/文件创建、写入、sync、close)失败。前者继续归入 `ErrArchiveCorrupt`;后者使用稳定的 staging-output sentinel,并保留底层 OS 错误的 `errors.Is` 链。不得把目标写入错误伪装为“read ZIP”。
2. 将 extraction 失败后的 staging 清理由“忽略 `RemoveAll` 错误”改为受控、可观察的 managed staging 清理。若主失败与清理失败并存,返回能同时被 `errors.Is` 识别的错误;下一次 `Recover` 必须只在已验证 app layout 内收敛残留 staging,或 fail closed 并给出确定的恢复错误,不能静默覆盖 `current`。
3. 在 `core/application/install` 为平台提供的、可测试的 `StorageFailureClassifier` 注入边界;它只判断已保留的底层 I/O 错误是否为磁盘满,core 不 import Windows API。`InstallServiceConfig` 必须显式要求该依赖;可识别的预检、write、sync、close 磁盘满均映射为 `disk_full`,其他 staging 输出 I/O 仍映射 `install_failed`,ZIP/CRC 输入错误仍映射 `zip_corrupt`。
4. 同步 `docs/api.md`/架构中的失败语义,并以内部可注入的 file-operation/failure seam 补齐测试:输入 CRC 损坏、staging write/sync/close 的 ENOSPC、普通输出 I/O、清理失败和后续恢复。测试必须证明原始根因保留、错误码准确、已有 `current`/`installed-app.json` 不变、首次安装不产生可执行 `current`。
## 验收要点
- ZIP/CRC 输入失败仍有 `ErrArchiveCorrupt` 和 `zip_corrupt`;staging 输出写入失败不携带该 sentinel,且原始输出错误可由 `errors.Is` 识别。
- fake `StorageFailureClassifier` 证明预检、write、sync、close 任一可识别 ENOSPC 都返回 `FailureCodeDiskFull`;无法识别的输出 I/O 返回 `FailureCodeInstallFailed`,不泄漏原始错误给 UI 合约。
- 清理失败不被吞掉;主 extraction 错误和 cleanup 错误均可由 `errors.Is` 检测。后续 `Recover` 只处理已验证的 managed staging,不能影响 `current`、`data/` 或 `licenses/`。
- 更新失败时旧 `current` 与旧 `installed-app.json` 字节保持不变;首次失败不留下 executable `current`。所有新增构造路径都必须显式提供 disk、target-state 与 storage-failure classifier,不能静默降级。
- `go -C core vet ./...`、`go -C core test -count=1 ./...`、`go -C core test -count=10 ./installer ./application/install`、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py` 与 `python scripts/validate_harness_governance.py` 全部通过;执行记录写明结果。
## 边界(不改什么)
- 不修改 Catalog、package、app manifest、installed-app 或下载任务 Schema,不改变 T-302 的同句柄验证、staging/switch/rollback 顺序。
- 不实现 Toolhelp、运行状态复查、等待退出、强杀、启动程序、Gio 安装编排或下载完成文件消费;O1 进入 T-401,O3 留给后续下载→安装生产编排任务。
- 不在 core import Windows API、Gio、SQLite 或第三方依赖;不以当前 OS errno 常量取代平台分类接口。
- 不做物理断电、杀毒软件或文件锁真机故障注入(T-601)。
## 协作约束
- 当前项目为单 Agent 串行模式;当前 Agent 独占全部 `write_paths`,自行完成设计、安全复核、测试和提交,不启动子 Agent。
- 先提交本任务规格及路线图/架构/状态文档;领取后将本文件改为 `DOING`,填写当前 HEAD `context_ref` 与 `work_branch: agent/codex/T-615`,再运行基线和实现。
- T-401 仅在本任务 `DONE`、验证和实现提交后再正式落成;其规格必须消费 T-303 的 `TargetStateChecker`,并实现切换临界区的最后一次运行状态复查。
## 执行记录
- 2026-07-19:正式落成。根据 Phase 3 交叉复核冻结输出 I/O/ZIP 输入的错误分界、可注入磁盘满分类、清理失败恢复语义及 T-401/O1 与后续编排/O3 的边界。