7.6 KiB
7.6 KiB
id, title, phase, deps, status, created, vikunja_task_id, context_ref, work_branch, needs_device, needs_human_review, write_paths
| id | title | phase | deps | status | created | vikunja_task_id | context_ref | work_branch | needs_device | needs_human_review | write_paths | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| T-303 | 客户端 HTTP 任务源、证据 sink 与可恢复本地状态 | 3 |
|
TODO | 2026-08-04 | 36 | bd4c855 |
task/t-303-http-localstate | false | false |
|
问题 / 背景
T-302 完成后,采购工具需要真实、安全、可恢复地领取/续租任务;T-204 已提供受控截图上传接口。但 client 当前只有真机能力和最小窗口,没有 HTTP 抽象、设备凭据存储或断网/重启幂等状态。T-303 建立 HttpTaskSource、窄 HttpEvidenceSink 与 Windows 本地恢复底座;事件/fail/fence/result 尚分别依赖 T-205/T-208,本任务不伪造完整 HttpResultSink 或占位请求。
关联需求与交互
- 功能:F-005、F-007、F-013。
- 用户故事:US-003、US-004、US-007。
- 依赖:T-002、T-204、T-302;T-301/T-203 已由 T-302 传递满足。
- 后续消费者:T-304 定时轮询/配置 UI、T-306 真截图接入;完整结果 sink 在 T-205/T-208 后组合。
- 本任务不修改桌面 UI;只提供核心契约、HTTP 传输和可恢复本地状态。
方案
- 定义不依赖 HTTP/UI/PDD 的 TaskSource、EvidenceSink、任务/授权/attempt/租约值对象;金额保持规范十进制字符串,UUID/时间/整数/布尔严格区分。不要创建运行时
NotImplementedError的假 ResultSink。 - HTTP 只接受精确
http://127.0.0.1:8080,使用标准库直连,不使用系统代理、不跟随重定向。每个方法只发一次请求,不在传输层隐藏重试;设置连接/读取超时和请求/响应上限。Bearer、device id、Content-Type 均由调用边界唯一生成。 - 严格实现 T-302 最终 wire:claim/renew 的字段、4 KiB JSON 上限、200/204、固定 400/409/413/415、空 401/503;2xx 仍严格校验 Content-Type、UTF-8、重复 key、未知/缺失字段、UUIDv4、UTC
ZRFC3339、64 位小写 token、金额和整数,超限或 schema 漂移视为结果不明而非成功。 %LOCALAPPDATA%/cmbuyer/state/client-state.sqlite3以事务和synchronous=FULL保存配置、polling session、pending claim/renew/evidence 请求和 active claim。设备 token 与 claim token 只以 Windows 当前用户 DPAPI 密文 BLOB 保存;生产非 Windows 或 DPAPI/SQLite 异常失败闭合,测试注入 fake protector。数据库/WAL、日志、异常均不得出现明文 token。- Windows named mutex 保证同一 client 配置只有一个执行进程;服务端的单设备 claim 不能替代本机单实例。配置存在 pending/active claim 时禁止切换 service/device 身份。
- 领取前先原子持久化 session_id+claim_request_id 再发请求。网络/503/截断/非法 2xx 后只保留并重放同一请求;明确 EMPTY 后下一轮才生成新 key。成功响应先 DPAPI 加密 token,再与完整快照单事务落库;响应与落库间崩溃可凭原 key 从服务端恢复同一 token。active 或未知 claim 结果存在时禁止领取第二条。
- 续租前持久化 renew_request_id、session、attempt、generation、token 与 expected expiration;结果不明只重放完全相同载荷。成功后原子更新租约,不轮换 token、不递增 generation;过期、CAS/归属/幂等冲突不得换 key 续租、重新领取或自动转领。
- EvidenceSink 只接收调用方显式传入的单个 PNG 与 T-204 元数据。首次调用前持久化 upload_key、hash 和文件身份;断网只重放同一 key/字节/元数据。文件缺失或 hash/大小变化停止,不枚举目录、不换截图、不上传 XML/manifest/本机路径/外部支付页,也不自行增加 claim token/session 字段。T-306 才把真实流程截图接入。
- 停止/关闭语义冻结给 T-304:只阻止下一次新领取;已发 claim 必须处理或保留恢复,返回任务必须落 active;pending/active claim、renew 和证据不 release/abandon,不生成新 session/key,不自动恢复真机点击。租约过期交 T-207 人工处理。
- 错误分类:本地状态/DPAPI 失败零 HTTP;401 停止并等凭据修复;403/协议型 4xx 停止;网络/超时/5xx 只允许同幂等键重放并由 T-304 计数;409 按固定 code 转人工且不换 key;
retryable绝不表示页面点击、围栏或提交可重试。
验收要点
- 覆盖 claim 成功/EMPTY/结果不明同 key 重放、进程重启恢复、响应漂移拒绝及 active claim 阻止第二次领取。
- 覆盖 renew CAS、同 key 重放、乱序/过期/冲突不复活,generation/token 不变化。
- 覆盖 DPAPI round-trip、损坏密文、SQLite/WAL 无明文 token、pending/active 时禁止换身份和本机双进程唯一。
- 覆盖精确 loopback URL、禁代理/重定向、严格 header/JSON/响应大小/RFC3339/UUID/金额/整数。
- 覆盖 evidence 显式 PNG、hash/大小、首次/重放、文件变化拒绝和绝不枚举/上传 XML/path。
- 覆盖停止发生在 claim 前/中/成功后均不丢 claim、不释放、不再领取;日志不泄露 Authorization/token。
- 静态检查 core/remote/localstate 不导入 PDD 点击、数量、确认页、event/fence/submit/payment 能力。
- client 全量 unittest、compileall、wheel metadata、根目录完整 init、上下文校验与 diff-check 通过。
执行记录
(暂无)
边界
- MVP 设备 Bearer 只允许发往精确
http://127.0.0.1:8080;不得接受用户信息、远程 host、其他 端口、重定向、系统代理或明文局域网地址。每个 transport 方法最多发送一次请求,重试决策必须 留给持有原幂等键的上层恢复流程。 - 设备 token 与 claim token 只能以当前 Windows 用户范围 DPAPI 密文进入本地 SQLite;不得以明文 进入数据库/WAL、日志、异常、Git、Vikunja 或测试 fixture。DPAPI、SQLite、密文或状态校验失败时 必须在零 HTTP 下失败闭合;生产非 Windows 不得降级为明文存储。
- Claim/renew/evidence 的幂等键和完整载荷必须先持久化再发请求。超时、断网、503、截断或非法 2xx 只能重放同一键同一载荷;不得生成新 key、领取第二条、续租复活、替换截图或覆盖本地 active claim。
- 停止轮询、Esc、关闭窗口和进程退出都不 release/abandon 当前 claim,也不自动恢复真机页面动作。 已发 claim 的未知结果和 active claim 必须跨重启保留;租约过期交 T-207 人工处理,不自动转领。
- EvidenceSink 只传调用方显式给出的单个 PNG 和 T-204 固定字段;不得枚举目录、读取或上传 XML、 manifest、本机路径、外部支付页或支付凭据,不得自行向 multipart 增加 claim token/session 字段。
- 本任务不修改
app.py或实现配置/轮询 UI,不连接真机,不导入 PDD 点击能力。T-304 负责桌面交互, T-306 负责把真实截图接入 sink。 - 不伪造尚不存在的 event/fail/fence/result HTTP,不提供假
HttpResultSink,不实现或引用click_permitted、提交订单点击、支付、免密支付、先用后付或任何扣款控件代码。