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

68 lines
7.9 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.
---
id: T-302
title: 安装流程整合
phase: 3
deps: [T-301, T-102, T-103]
status: DONE
created: 2026-07-18
issue: null
context_ref: 6575c9ad9b997366514277a925196b2e52f16f75
claim_branch: null
work_branch: agent/codex/T-302
write_paths:
- docs/tasks/T-302.md
- core/application/
- core/installer/
- core/storage/
- docs/api.md
- docs/04-architecture.md
- docs/00-ai-start-here.md
- docs/06-tasks.md
- docs/current-state.md
---
## 问题 / 背景
T-301 只能把网络字节可靠地落为隔离的 `.download`;下载任务元数据、完成路径和文件内容仍可被本地篡改,不能直接作为安装身份或可信包。T-102/T-612/T-613 已分别具备 ZIP 预扫描/安全解压和可恢复的 `staging → current → backup` 切换原型,但尚未把已验签 Catalog、完整包 SHA-256、严格 `app.json` 身份比对、安装记录和健康失败回滚串成一个可调用的 core use case。
本任务关闭这条生产安装链的代码级信任边界。外层 Catalog 的 Ed25519 签名覆盖嵌套 `packages[arch]` 的 `size`、`sha256` 与 `signature` 字段;当前协议没有定义 package `signature` 的独立待签名字节、密钥或轮换域,T-302 不得臆造第二套包级验签。它只接受已经由 `catalog.Client` 验签、严格解析并按目标过滤后的 Catalog 选择,并以该选择中的精确 size/SHA-256 重新验证本地完成文件。
## 方案
1. 在 `core/application/install` 落地 `InstallService`:请求包含已过滤的 `catalog.Entry`、目标 architecture 和 `.download` 路径。服务必须拒绝不可安装 entry、缺失 package、architecture 与 `App.Packages[architecture]` 不一致的选择;不得从 `downloader.Task`、`DownloadCompletedPayload` 或本地 metadata 重新取得 app/version/size/hash。该 application 子包独立于被 Catalog 图标投递依赖的父包,避免反向 import cycle。
2. 在 `core/installer` 增加经过验证的包提取入口。它以一次 `Lstat → open → fstat → Lstat` 得到普通文件句柄,先确认实际长度精确等于 Catalog size、再以**同一文件句柄**计算 SHA-256,并以常量时间比较 Catalog hash;哈希不符时不得构造 `zip.Reader`、读取 `app.json` 或创建 staging。随后仍以同一句柄完成 EOCD/ZIP64/中央目录预扫描和 `zip.Reader` 构造,不能按路径重新打开。
3. 安全读取 ZIP 根目录唯一的 `app.json`(最大 1 MiB,严格 JSON、无未知字段/尾随值),校验 v1 的全部字段与共享 Windows 安全相对路径规则。身份字段必须与 Catalog 选择一致:`id`、`version`、`channel`、`min_os`、`architecture`、`entrypoint`、`requires_admin`;其余 v1 常量和值也必须符合 `schemas/app.schema.json`。只有通过比对后,才复用既有两阶段 ZIP 检查把 `payload/` 解压到全新的 `apps/<id>/staging`。
4. 解压复制时为每个实际 payload 文件计算 size/SHA-256,作为 `ExtractResult` 的已观察文件清单。`InstallService` 将此清单写入 `installed-app.json`,不信任或依赖可选的 `files.json` 作为新的信任根;保留 v1 `files.json` 的协议语义和后续修复功能边界。
5. 每次安装先对 app root 调用既有 `installer.Recover`。提取成功后以已有 `Switcher` 激活 staging;必需的注入 health check 先通过,随后在同一个 switch health 阶段原子写入新 `installed-app.json`,使 health/记录写失败走既有 rollback,保留旧 `current` 与旧记录。成功才清理 backup/journal。返回的阶段化错误和结果要能让后台调用方区分验证、解压、切换/回滚和记录失败;UI 仍只通过 application 事件/结果更新状态,不在 Gio Layout 做 IO。
6. 为上列顺序补齐隔离的单元/集成测试:成功安装、Catalog 选择不一致、非普通/替换的完成文件、size/SHA 不符(确认 ZIP/app.json/staging 未触及)、恶意或不匹配 `app.json`、完整安装记录文件清单、已有版本健康或记录写入失败后的回滚、安装前 transaction recovery。测试包只使用运行时生成的虚构 ZIP/哈希。
## 验收要点
- `InstallService` 的唯一可信输入是已验签/过滤 Catalog 的 entry + architecture;篡改下载 metadata、request/app ID 或 completed 路径不能改变实际验证的 package identity、size 或 SHA-256。
- 普通完成文件的 Catalog size 和 SHA-256 均在同一打开句柄上通过后,才允许 ZIP 预扫描、`app.json` 读取、staging 创建和 payload 写入;所有失败保留 `errors.Is` 可识别的根因。
- `app.json` 严格符合 v1,并与 Catalog 的 ID、版本、通道、最低系统、架构、入口和管理员标记一致;安全路径、未知字段、重复/缺失入口或不匹配一律拒绝。
- 成功链为 `recover → verify → manifest compare → safe staging extract → switch → health → installed-app record → committed cleanup`;记录含每个实际 payload 文件的安全相对路径、字节数和 SHA-256。
- health 或记录写失败时,已有版本的 `current` 与旧 `installed-app.json` 恢复并保持;首次安装不留下可执行 `current`。已有未完成 transaction 先按恢复状态机收敛。
- `go -C core vet ./...`、`go -C core test -count=1 ./...`、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py` 与 `python scripts/validate_harness_governance.py` 全部通过;任务执行记录写明结果。
## 边界(不改什么)
- 不修改 Catalog 签名协议、Schema 或密钥;不为 nested package `signature` 虚构独立验签算法。外层已验签 Catalog 是本任务唯一的密码学身份来源。
- 不做磁盘空间预检、程序占用/退出等待、面向 UI 的完整错误码映射或下载重试策略(T-303/T-401);不强杀或自动启动包内程序。
- 不做 Gio 安装面板、下载完成到安装调用的 UI 编排或物理断电/杀毒软件/文件锁故障注入。T-302 只提供无头 core use case;真实 Windows VM/真机故障注入转入 T-601 发布前环境验证。
- 不改变 `files.json` 的 v1/v1.1 协议或实现修复功能,不引入数据库、第三方包、Gio、Windows API 或 Go 1.21+ API。
## 协作约束
- 当前项目为单 Agent 串行模式;当前 Agent 独占全部 `write_paths`,自行完成设计、安全复核、测试和提交,不启动子 Agent。
- 先提交本任务规格与关联协议/架构/状态文档;领取后将本文件改为 `DOING`,填写当前 HEAD `context_ref` 与 `work_branch: agent/codex/T-302`,再运行基线和实现。
- T-303、T-401 及任何后置任务在本任务 `DONE`、验证与实现提交前不得领取或提前修改。
## 执行记录
- 2026-07-18:正式落成。冻结可信输入、同句柄 size/SHA/ZIP 顺序、严格 app.json 对齐、实际 payload 文件记录、health 内记录写入回滚语义和后续任务边界。
- 2026-07-18:领取任务,基线为 `6575c9ad9b997366514277a925196b2e52f16f75`,工作分支 `agent/codex/T-302`;下一步运行统一初始化/完整基线,再开始实现。
- 2026-07-18:基线通过:`./init.ps1` 完成治理、core 架构/Go 版本闸门、Go 1.20 core vet/test、modern/Win7 test/build 与 Python harness 校验。
- 2026-07-18:实现 `core/application/install.InstallService`、同句柄 `Extractor.ExtractVerifiedFile`、严格且有 1 MiB 上限的 app.json 校验、payload 观测 hash 清单和 `InstalledAppStore.EnsureAppRoot`;Catalog 依赖父 application 包的既有图标投递链会产生 import cycle,因此 use case 放在独立 application 子包,未改变 Gio/UI 边界。
- 2026-07-18:复核成功/size+hash+选择失败/严格 manifest/非普通文件/manifest 上限/transaction recovery/health 与记录写失败回滚;`go -C core vet ./...`、`go -C core test -count=1 ./...` 和 `go -C core test -count=10 ./installer ./application/install` 全部通过。真实 Windows 断电、文件锁/杀毒软件故障注入未在当前环境执行,保留为 T-601 发布前验证。