6.1 KiB
6.1 KiB
id, title, phase, deps, status, created, issue, context_ref, claim_branch, work_branch, write_paths
| id | title | phase | deps | status | created | issue | context_ref | claim_branch | work_branch | write_paths | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| T-301 | 可恢复下载队列 | 3 |
|
DONE | 2026-07-16 | null | a52acbf926 |
null | agent/codex/T-301 |
|
问题 / 背景
Phase 2 已能识别软件状态并展示列表/详情,但点击下载后还没有并发调度、暂停/取消/重试、HTTP Range 续传或重启恢复。若下载状态只保存在 goroutine 内存中,盒子退出后任务丢失;若 Range 响应、临时文件和元数据不一致,可能把拼接错误的字节交给 T-302 校验/安装链路。
T-301 是 T-302/T-303 的前置安全边界。它只负责把远端 ZIP 稳定下载为隔离的 .part/完成文件并发布进度事件,不声称包已可信可执行。
方案
- 在
core/downloader定义任务模型、队列命令与状态机;默认最多 2 个 active transfer,其余保持 queued。 - 支持 enqueue、pause、resume、cancel、retry;每个命令按稳定
request_id/app_id定位,重复命令返回确定结果而非产生第二份任务。 - HTTP transport 使用注入接口和标准库实现:
- 已有
.part时发送Range: bytes=<offset>-。 206必须校验 Content-Range 起点;不匹配则拒绝。- 服务端返回
200时安全截断并从 0 重下,不得追加到旧字节。 - 网络中断保留已落盘
.part;取消按策略删除任务临时文件;完成前 flush/sync/close。
- 已有
- 在
core/storage持久化下载任务元数据,采用同目录临时文件 + backup 原子替换;Schema 落到schemas/download-task.schema.json。 - 启动恢复时:
- queued / paused / failed 保持可恢复语义。
- 进程退出时遗留的 downloading 降级为 queued,不假装仍在下载。
- 元数据与
.part实际长度不一致时以磁盘为准或返回稳定损坏错误,不得静默跳过字节。
- application 事件沿用
DownloadStarted/DownloadProgress/DownloadPaused/DownloadCompleted/DownloadFailed;负载使用具体类型,UI 只消费事件,不轮询文件。 - 进度包含 done/total/speed;total 未知时明确表示 unknown,不伪造百分比。事件节流与 clock 注入保证测试确定性。
验收要点
- 并发默认值为 2;表驱动/阻塞 transport 测试证明任意时刻 active transfer 不超过 2,完成/暂停后下一个 queued 自动开始。
- pause 保留
.part和元数据;resume 使用正确 Range;cancel 不再发布进度且按协议清理;failed 可 retry。 - httptest 覆盖:正常 200、正确 206、错误 Content-Range、服务器忽略 Range 返回 200、连接中断后续传、非成功状态。
- 任务元数据严格读写;未知字段、非法状态/ID/URL/路径/计数被拒绝;主文件缺失时可读取中断遗留 backup。
- 新队列实例能从磁盘恢复;遗留 downloading → queued,paused 保持 paused,已完成任务不重复下载。
.part长度、done 与下一次 Range offset 一致;不把未完成文件暴露为完成文件。- application 具体事件负载与 request_id/app_id 匹配;进度 done 单调不回退,暂停/取消后不出现迟到 progress。
cd core && go vet ./... && go test -count=1 ./...、完整双目标闸门与治理校验通过。
边界(不改什么)
- 不做 ZIP SHA-256 / package 签名、app.json 比对、解压、staging 切换或健康检查(T-302)。
- 不做磁盘空间预检查、程序占用、hash_mismatch/zip_corrupt 等完整失败矩阵(T-303)。
- 不实现 Gio DownloadPanel;本任务只交付 core 队列、持久化与事件,后续 UI 通过事件接入。
- 不实现多源下载、P2P、带宽限速、代理设置或动态并发 UI。
- 不引入第三方下载器、数据库或 Go 1.21+ API。
协作约束
- 唯一写入 Agent 独占本任务全部
write_paths,负责实现但不创建额外任务提交。 - 测试设计 Agent 只读输出并发、Range、崩溃恢复和事件时序测试矩阵。
- 安全/架构 Agent 只读审查路径隔离、响应拼接、持久化原子性、迟到事件和 T-302 信任边界。
- coordinator 汇总意见、要求必要修正、执行最终验证、更新本执行记录并创建唯一 T-301 Git 提交。
- T-302/T-303 在本任务 DONE 前不得领取;若后续决定并行编码,先另行落成并提交带后缀子任务及共享接口冻结任务。
执行记录
- 2026-07-16:正式落成多 Agent 协作方式;采用一个写入 Agent + 测试设计/安全架构两个只读 Agent,保持 T-301 → T-302 → T-303 依赖串行。
- 2026-07-16:实现
core/downloader.Queue、严格 HTTPS/Range transport、任务状态/路径协议、pause/resume/cancel/retry、默认并发 2、进度节流与 application 具体事件负载;新增core/storage.DownloadTaskStore、download-task.schema.json和测试数据说明。 - 2026-07-16:根据多轮只读审查修复安全边界:服务器忽略 Range 时先截断并 sync
.part再持久化新 attempt/validator;unknown total 全程受硬上限约束且续传必须证明完整 total;写入句柄身份贯穿 rename 前后;pause 不吞 body close/sync 错误,cancel 则记录非致命关闭错误后仍完成固定清理;Close 不覆盖用户 cancel;known-total 完整 part 在同进程 resume/retry 直接 finalize;observer 失败不反向改变 durable 结果并通过OnObserverError报告。 - 2026-07-16:补齐并发补位、断连续传、restart 写失败窗口、未知 total、HTTP 404/416/500 与畸形响应、进度单调/节流/速度、迟到 generation、事件字段、observer 失败对账、崩溃恢复和文件身份替换回归测试;最终只读测试/安全审查均无 blocker/high。
- 2026-07-16:验证通过:
go -C core vet ./...;go -C core test -count=1 ./...;go -C core test -count=10 ./downloader;./scripts/verify_phase0.ps1。go test -race已尝试,当前环境CGO_ENABLED=0且未安装 gcc,因此 race detector 无法启动;非 race 闸门全部通过。