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

88 lines
7.2 KiB
Markdown
Raw Normal View History

2026-08-04 23:20:28 +08:00
---
id: T-308
title: 客户端提交围栏与结果 sink
phase: 3
deps: [T-208, T-303, T-307]
status: TODO
created: 2026-08-04
vikunja_task_id: 49
context_ref: 8600c33
work_branch: task/t-308-submission-sink
needs_device: false
needs_human_review: true
write_paths:
- 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/**
---
<!-- BEGIN VIKUNJA EXPORT id=49 synced=2026-08-04T14:51:43Z sha256=ac3851bab830634e65029a7a80985946cb3aa5bf80bff1727e3e7e0fc8c30ad5 -->
## 问题 / 背景
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。
## 执行记录
(暂无)
<!-- END VIKUNJA EXPORT -->
## 边界
- 本任务只实现 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`。