Files
cmbuyer/docs/tasks/T-308.md
T

7.2 KiB
Raw Blame History

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-308 客户端提交围栏与结果 sink 3
T-208
T-303
T-307
TODO 2026-08-04 49 8600c33 task/t-308-submission-sink false true
docs/tasks/T-308.md
client/src/cmbuyer_client/submissions/**
client/src/cmbuyer_client/core/**
client/src/cmbuyer_client/remote/**
client/src/cmbuyer_client/localstate/**
client/tests/submissions/**
client/tests/core/**
client/tests/remote/**
client/tests/localstate/**

问题 / 背景

T-303 只提供 claim/renew/evidence 的严格 HTTP、本地 DPAPI/SQLite 与恢复底座,T-307 只实现围栏前 events/fail,二者都明确禁止伪造 fence/result。T-208 将冻结服务端 CREATE_ONCE、LOOKUP_ONLY、一次性 result 与同一 submission 调和语义。T-308 补齐客户端提交围栏与结果 sink,并把许可、点击意图和结果恢复事实写入 T-303 的同一 localstate;本任务不连接真机、不接 UI、不执行或暴露任何点击。

关联需求与交互

  • 功能:F-007、F-011、F-017。
  • 用户故事:US-004、US-005、US-008。
  • 交互:IX-011;只实现后台契约和恢复状态,不实现界面。
  • 依赖:T-208、T-303、T-307。
  • 后续消费者:T-400 离线单次提交安全编排。
  • API:CREATE_ONCE / LOOKUP_ONLY submission fence 与一次性 submission result;严格消费 T-208 最终 wire。

方案

  1. 定义窄 SubmissionSink:create_submission_fence、lookup_submission_fence、prepare_click_intent、report_submission_result。proof/result 值对象只含 T-208 固定 UUID、claim 归属、规范十进制金额、正整数、三闸门 AssetRef、精确 submit 文本/计数和固定 observation;不接收自由页面文本、XML、路径、selector、坐标或动作脚本。
  2. HTTP 必须复用 T-303 的精确 loopback、禁代理/重定向、有界请求/响应和严格 JSON 解析。每次方法最多一次传输,不在 transport 或 sink 内隐藏重试。
  3. 只扩展 T-303 的同一 SQLite/localstate,不建立第二个数据库。首次网络前原子保存 fence_key、operation、完整规范 proof、active claim 绑定和预期状态;DPAPI、SQLite 或归属校验失败时零 HTTP。
  4. CREATE_ONCE 结果不明时只允许以原 fence_key、原 proof 调用 LOOKUP_ONLY。不得换 key、再次 CREATE_ONCE 或根据本地页面猜测围栏是否存在;LOOKUP_ONLY 找到或未找到都不得产生可点击许可。
  5. 只有当前进程当前 CREATE_ONCE 调用收到首次、明确、完整且严格核对的 click_permitted=true 响应,并已把 submission 与许可事实 durable 落盘后,才由模块私有工厂返回 opaque SubmissionPermit。响应截断、协议漂移、落盘失败、重放、LOOKUP_ONLY 或 click_permitted=false 均不返回 permit。
  6. SubmissionPermit 不提供公共构造器,不可 pickle/JSON/复制,不写入 SQLite、日志或异常。SQLite 只保存围栏和恢复审计事实;重启后无论数据库记录什么都不能重建可点击 permit,只能进入同一 submission 的调和路径。
  7. prepare_click_intent 必须消费当前进程唯一 permit,在返回一次性 ArmedSubmission 前原子写入“即将发出唯一点击”。同 permit 二次调用、并发调用、active claim 漂移或已有点击意图均拒绝;重启只看到 reconcile-only 事实,不能重新 arm。
  8. result_key、固定 observation 与 submission/attempt/冻结归属在首次 result HTTP 前原子保存。结果不明和重启只能重放原 key、原载荷;明确成功后保存服务端终态。同一 submission 不得创建第二个 observation 或把 UNKNOWN 改写成 SUBMITTED。
  9. 401、403、409、非法 2xx、本地状态错误按 T-303 分类停止;网络、超时、5xx 只表示原幂等 HTTP 可调和,绝不表示 fence、permit、点击或页面动作可重试。
  10. 构造和 import 闭包不包含 pdd、device、uiautomator2、UI、execution、app、通用 click、SubmitOrderPort、submit_order_once 或任何 payment 能力。

验收要点

  • 覆盖首次 CREATE_ONCE、同键重放、LOOKUP_ONLY 找到/未找到、响应截断/漂移、落盘失败、401/403/409/503;只有首次明确且 durable 成功返回 opaque permit。
  • 覆盖并发围栏、并发 prepare_click_intent、同 permit 二次消费、进程重启和 SQLite 恢复;任一非首次路径均无法获得或重建 permit/ArmedSubmission。
  • 证明 fence/result 首次网络前已持久化完整键、载荷与 frozen claim 绑定;不明结果只重放原 HTTP 事实,不产生新 key 或第二 observation。
  • 证明 SQLite/WAL、日志、异常和 DTO 不含明文 token、页面正文、XML、本机路径、selector、坐标或可点击对象。
  • 静态 import/AST 检查证明 pdd/device/UI/app/execution、真机 click、submit_order_once 和任何付款能力不可达。
  • client 全量 unittest、compileall、wheel metadata、完整 init、上下文、Vikunja export 与 diff-check 通过。
  • needs_human_review:人工审阅 opaque permit 的构造边界、首次许可与重启不可重建证明;确认前不得标 DONE。

执行记录

(暂无)

边界

  • 本任务只实现 T-208 已冻结的 CREATE_ONCE、LOOKUP_ONLY 与一次性 result 客户端契约, 并复用 T-303 同一 transport/localstate。不得另建 HTTP 客户端、SQLite、DPAPI 封装或第二份 权威恢复状态;本地与服务端事实冲突时只能停止并调和。
  • 只有当前进程当前 CREATE_ONCE 调用收到首次、明确、完整且已 durable 落盘的 click_permitted=true 响应,才可由模块私有工厂产生 opaque SubmissionPermit。同键重放、 LOOKUP_ONLY、响应不明、解析/落盘失败、并发输家和服务重启均不得产生许可。
  • SubmissionPermit 与一次性 ArmedSubmission 不得公开构造、序列化、复制、写入 SQLite 或从 恢复快照重建。SQLite 只能保存围栏、点击意图与调和事实;进程退出后即使尚不能确认是否点击, 也只能进入 RECONCILE_ONLY,不能重新 arm。
  • fence/result 的 key、完整规范载荷和 frozen claim 绑定必须先原子持久化再发送。HTTP 结果不明 只能重放原 key、原载荷;不得换 key、再次 CREATE_ONCE、覆盖 observation 或把 HTTP 可恢复 错误解释为页面动作可重试。
  • 本任务不得导入或修改 client/src/cmbuyer_client/pdd/**、device/**、ui/**、execution/** 或 app.py,不得连接真机、持有 selector/坐标/节点/点击对象,也不得实现 SubmitOrderPort、 submit_order_once() 或任何通用/具体点击。
  • 本任务不编写支付、免密支付、先用后付或任何扣款能力。外部支付、安全校验与未知结果只能作为 T-208 固定 observation 保存并转调和,不能继续交互、释放围栏、重新授权、重新领取或再次提交。
  • needs_human_review: true:自动验证完成后仍保持 DOING,等待人审 opaque permit 构造边界、 首次许可唯一性和重启不可重建证明;未经明确确认不得标 DONE。