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

70 lines
8.8 KiB
Markdown
Raw Permalink 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-403
title: SoftBox 自更新助手与健康确认恢复
phase: 4
deps: [T-402]
status: DONE
created: 2026-07-19
issue: null
context_ref: 2900deba4f6ea2e442555e52044118d62983fd74
claim_branch: null
work_branch: agent/codex/T-403
write_paths:
- docs/tasks/T-403.md
- core/updater/
- app-modern/platform/windows/
- app-win7/platform/windows/
- app-modern/cmd/softbox/
- app-win7/cmd/softbox/
- app-modern/cmd/softboxupdater/
- app-win7/cmd/softboxupdater/
- scripts/verify_phase0.ps1
- scripts/verify_phase0.sh
- docs/api.md
- docs/03-tech-stack.md
- docs/04-architecture.md
- docs/00-ai-start-here.md
- docs/current-state.md
---
## 问题 / 背景
Phase 4 的子软件更新已能在用户关闭子软件后安全委托既有安装事务,但 SoftBox 主程序自身不能在运行中替换 EXE。当前架构/API 只描述了 `SoftBoxUpdater.exe --pid --staging --target` 的目标形状,仓库没有独立 updater、主进程退出等待、受限自更新目录布局、健康确认或中断恢复。因此下载到 staging 的新版无法安全激活,也不能在新版未正常启动时可靠保留旧版恢复路径。
自更新更新的是盒子 `app/`,不是 `apps/<id>/`。若助手接受任意 target/staging、跟随 symlink,或把“子进程已创建”误当成健康,可能覆盖 `data/`、`licenses/` 或把不可用新版永久设为 current。当前没有可信自更新包下载/签名消费源,本任务只能提供独立、受约束的本地切换与恢复机制,不能伪称在线自更新已经接通。
## 方案
1. 新建 Go 1.20 兼容的 `core/updater`,固定布局为 `<root>/app`(target)、`<root>/staging/<request-id>`(已由可信外层准备的新目录)、`<root>/backups/<request-id>`、`self-update-transaction.json` 与健康确认文件。请求只接受正主进程 PID、safe request ID、绝对且严格满足该布局的 staging/target;逐项 `Lstat` 拒绝 symlink、非目录、交叉 root、预存 backup/journal 异常,绝不接受 UI/CLI 传入可执行参数或任意移动路径。
2. updater 先等待原主 PID 自然退出,才恢复上次未完成 transaction;取消、超时和等待错误均不触碰 target。退出后按 journal 栅栏执行 `prepared → target_backed_up → staging_activated → launched → committed`:只可把 old `app` 改名为本 request 的 backup、把同 root staging 改名为 `app`;每步用临时 JSON + 原子替换并同步目录。任何启动前/启动失败路径尝试恢复 old app;无法恢复时保留可验证 journal/backup,下一次 updater 可恢复,绝不删除 data/licenses。
3. 新版只能以已验证绝对 `<root>/app/SoftBox.exe`、固定工作目录和内部固定 `--softbox-update-health <request-id>` 参数启动。`SoftBox.exe` 在 Gio Layout 之前(非 UI goroutine I/O)从自己的可执行路径推导同一 root,校验 request ID 和 health locator 后原子写入仅含 request ID 的健康确认;不得接受路径、URL 或任意命令。助手等待匹配确认后才 committed/清理 backup;超时、错误或错误 request ID 均返回稳定失败,保留恢复材料。新版已运行但未确认且 Windows 拒绝 rollback 时 fail closed 留 journal,不强杀;后续受控 updater 在主 PID 退出后恢复。
4. 两端 `platform/windows` 增加一致的主 PID 自然退出和固定内部健康启动边界。Windows 以 `OpenProcess(SYNCHRONIZE)` + 可取消的短 `WaitForSingleObject` 轮询实现,不将无权限/不存在 PID 当作“已退出”;启动不经 shell/PATH、只传固定 health flag。非 Windows stub 明确返回不支持。clock/process/launcher seam 覆盖已退出、正常退出、取消、超时、OpenProcess/wait 错误与参数固定性。
5. 两端新增无 Gio 的 `cmd/softboxupdater`,严格解析 `--pid`、`--staging`、`--target`;request ID 不作为 CLI 参数,而是由受信任预备器创建的规范 `<root>/staging/<request-id>` 目录名确定并被助手重新校验,装配 core/platform 后以非 0 退出失败;主 cmd 只识别内部 health flag 并继续正常启动,不增加用户可传的任意执行入口。验证脚本同时构建主 EXE 和 Updater EXE;文档定义内部健康文件、恢复语义、真实下载/签名未装配事实与 T-601 真机限制。
## 验收要点
- core updater 拒绝非法 PID/request ID、相对/跨 root/不匹配布局、symlink/非目录、缺 staging、已有冲突 backup/journal;失败不改 target、staging、data 或 licenses。成功路径等待原 PID 退出,正确 rename/sync/journal,固定启动新 app,收到匹配 health request ID 后才 committed 并清理 backup/journal。
- PID 等待的取消、超时、OpenProcess/Wait 错误、launcher 失败、health 超时/错误 ID、每个 rename/journal 故障和崩溃注入均有稳定错误;可恢复时 old app 恢复且可启动,无法立即恢复时 journal/backup 保留供下次 `Recover`,不强杀新/旧进程。
- 新主程序只响应固定内部 health flag,从自身 `app/SoftBox.exe` 推导 root 并原子写入最小确认,不在 Gio Layout I/O;不匹配 flag/request/layout 不写文件。双端 `SoftBoxUpdater.exe` CLI 拒绝多余/无效参数,使用平台 PID 等待和固定启动调用。
- core 无 Windows/Gio/第三方依赖;双端平台 public contract 与 non-Windows stub 一致;Windows amd64 交叉编译主程序和 Updater,脚本/CI 闸门覆盖两个 EXE。真实 Windows 文件锁、杀毒/断电以及 UAC/签名发布行为仍由 T-601/T-603 验证。
- `go -C core vet ./...`、`go -C core test -count=1 ./...`、`go -C core test -count=10 ./updater`、两个主程序和 Updater 的 Windows amd64 构建、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py`、`python scripts/validate_harness_governance.py` 全部通过;执行记录如实写明未装配可信自更新包下载/签名来源。
## 边界(不改什么)
- 不实现自更新 Catalog/package 下载、Ed25519 签名、SHA-256 校验、版本检查或自动触发;staging 只可由后续可信外层提供,不能把任意下载文件解压/执行。
- 不触碰 `apps/<id>/` 子软件安装事务、`data/`、`licenses/`、子软件更新、授权策略或 allow-all 注入;不实现 updater 自更新、MSI、服务、注册表、shell script、强杀、命名管道或 UI 进度窗口。
- 不把 Windows API/Gio/SQLite 引入 core,不升级 Go/Gio/依赖,不开放 `SoftBox.exe` 的用户自定义 path/args/command;内部 health flag 仅由 updater 固定生成。
- 不把单元测试等同于断电、文件锁、杀毒和 Windows VM/真机结论;这些保留 T-601/T-603。
## 协作约束
- 当前项目为单 Agent 串行模式;当前 Agent 独占全部 `write_paths`,自行完成规格、实现、审查、测试和提交,不启动子 Agent。
- 先提交本任务规格和项目快照;领取后将本文件改为 `DOING`,填写当前 HEAD 的 `context_ref` 与 `work_branch: agent/codex/T-403`,再重新记录基线和实现。
- 本任务完成后停止;后续 Phase 5 许可证任务仍需按路线图另行正式落成,不能因自更新程序存在而假定下载、签名或发布流水线已完成。
## 执行记录
- 2026-07-19:正式落成。以 T-402 完成后的 Phase 4 依赖为基线,冻结独立 updater、受限根目录、PID 自然退出、健康确认 journal 和可恢复 rollback 的最小自更新协议;明确可信 self-update 下载/签名、真机故障和发布签名继续后置。
- 2026-07-19:领取任务,基于 `2900deba4f6ea2e442555e52044118d62983fd74` 在 `agent/codex/T-403` 执行;先重跑基线,再实现独立 updater 与健康确认。
- 2026-07-19:完成。新增 Go 1.20 `core/updater`,固定 `<root>/app`、`staging/<request-id>`、`backups/<request-id>`、原子 transaction/health 文件与可恢复受限 rename;旧 PID 必须自然退出,健康确认必须来自自身 `app/SoftBox.exe` 且匹配已激活 transaction。双端 `platform/windows` 增加 `OpenProcess(SYNCHRONIZE)`/可取消等待、固定无 shell health 启动和目录 durability,non-Windows 明确 fail closed;两个无 Gio `cmd/softboxupdater` 严格处理 CLI,主 cmd 在 Gio Layout 前处理内部 health flag。规格实现前修正了“先恢复再等待 PID”会在旧主程序仍运行时改目录,以及“助手生成 ID”无法对应预备 staging 的矛盾:安全顺序为先等待再恢复,request ID 仅从受信任的规范 staging 目录名派生并复验。可信 self-update 下载/签名、版本选择和 UI 触发未装配。验证通过:`go -C core vet ./...`、`go -C core test -count=1 ./...`、`go -C core test -count=10 ./updater`、两端全包测试与 Windows amd64 的主程序/Updater 构建、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py`、`python scripts/validate_harness_governance.py`。