2026-07-19 21:42:23 +08:00
|
|
|
|
---
|
|
|
|
|
|
id: T-403
|
|
|
|
|
|
title: SoftBox 自更新助手与健康确认恢复
|
|
|
|
|
|
phase: 4
|
|
|
|
|
|
deps: [T-402]
|
2026-07-19 22:00:38 +08:00
|
|
|
|
status: DONE
|
2026-07-19 21:42:23 +08:00
|
|
|
|
created: 2026-07-19
|
|
|
|
|
|
issue: null
|
2026-07-19 22:00:38 +08:00
|
|
|
|
context_ref: 2900deba4f6ea2e442555e52044118d62983fd74
|
2026-07-19 21:42:23 +08:00
|
|
|
|
claim_branch: null
|
2026-07-19 22:00:38 +08:00
|
|
|
|
work_branch: agent/codex/T-403
|
2026-07-19 21:42:23 +08:00
|
|
|
|
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 传入可执行参数或任意移动路径。
|
2026-07-19 22:00:38 +08:00
|
|
|
|
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。
|
2026-07-19 21:42:23 +08:00
|
|
|
|
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 错误与参数固定性。
|
2026-07-19 22:00:38 +08:00
|
|
|
|
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 真机限制。
|
2026-07-19 21:42:23 +08:00
|
|
|
|
|
|
|
|
|
|
## 验收要点
|
|
|
|
|
|
|
|
|
|
|
|
- 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 22:00:38 +08:00
|
|
|
|
- 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`。
|