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

11 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-208 三闸门提交围栏与结果调和 API 2
T-207
T-210
TODO 2026-08-04 47 cd47d0c task/t-208-submission-fence false false
docs/tasks/T-208.md
admin/migrations/00008_submission_fence.sql
admin/internal/migrations/migrations_test.go
admin/internal/domain/submission.go
admin/internal/domain/submission_test.go
admin/internal/submission/**
admin/internal/taskclaim/**
admin/internal/taskdetail/**
admin/internal/server/submission_fence.go
admin/internal/server/submission_fence_test.go
admin/internal/server/router.go
admin/internal/transport/webui/webui.go
admin/internal/transport/webui/templates/task-detail.html
admin/cmd/server/**
admin/README.md
docs/api.md
docs/04-architecture.md
docs/routes.md
docs/current-state.md

问题 / 背景

T-205 将保存同一 attempt 的 append-only 三闸门事件和执行摘要,T-210 将开放 Gate1/Gate2/Gate3 三种固定证据资产,T-207 只允许围栏前人工恢复。真实点击“提交订单”前仍缺少服务端唯一提交围栏、首次一次性许可、围栏响应不明处理、点击后一次性结果以及同一 submission 的人工调和。T-208 只实现采购服务端围栏与调和;不连接真机、不点击提交订单、更不付款。

关联需求与交互

  • 功能:F-006、F-007、F-011、F-017。
  • 用户故事:US-004、US-005、US-008。
  • 交互:IX-009、IX-011;围栏由后台申请,Web 只显示和调和同一 submission。
  • 依赖:T-207、T-210;显式保留 T-210 作为三证据类型安全门禁。T-207 传递 T-205/T-302,T-210 传递 T-107/T-204/T-302。
  • API:POST /api/v1/purchase-attempts/{aid}/submission-fence、POST /api/v1/order-submissions/{sid}/result、POST /order-submissions/{sid}/reconcile。
  • 后续消费者:T-401 单趟真实提交。

方案

  1. 新增 00008_submission_fence.sql,保存 fence request 幂等事实、唯一 order_submission proof、三个 gate evidence 外键、一次性 result request、append-only 人工调和记录,以及冻结的 device/session/claim generation/claim token hash 归属。authorization 与 attempt 各最多一个 submission;降级遇到新事实原子拒绝。
  2. 设备认证必须在读取 body 前完成;严格 JSON 请求绑定 task/version、authorization、attempt、session、generation、claim token、最后事件序号、规格、两次单价、数量、确认页金额、精确 submit_control_text/match_count 和三个 evidence asset id,不接受未知字段、自由页面文本、XML、路径、selector、坐标或动作脚本。
  3. operation=CREATE_ONCE 才可能创建围栏。事务必须先取得 SQLite 写线性化位置并再次确认设备 ACTIVE,再复用 T-302 的 current claim validator:设备/session/task/authorization/attempt/generation/token 全部一致,claim lease 与 authorization 严格未过期,task/attempt 为 ORDERING,authorization 为 CLAIMED。当前 validator 不得为结果上报放宽。
  4. 围栏事务逐字段比对请求、T-205 append-only 事件、purchase attempt summary 与 T-210 evidence assets。三个资产必须精确为 SKU_PANEL_GATE_1、SKU_PANEL_GATE_2、ORDER_CONFIRM_GATE_3,属于同一 task/attempt/device;服务端只验证 kind/归属/hash 事实,不解析图片内容。
  5. 服务端重新执行三闸门:颜色/尺码精确等于授权;Gate1/Gate2 为规范十进制正金额且完全相等;Gate1 × 授权数量不超 total_price_cap;数量精确相等;Gate3 规格/数量一致且确认页总额不超上限;submit_control_text 精确等于“提交订单”,可点击祖先计数精确为一。任一缺失、歧义、顺序错误或冲突都零围栏、零许可。
  6. 首次 CREATE_ONCE 在一个事务中创建唯一 submission,并把 authorization CLAIMED→FENCED、attempt ORDERING→FENCED,使 task 永久不可重领。只有这次首次明确成功响应可含 click_permitted=true;许可不是数据库中可重取的开关,也不能由业务代码手工构造。
  7. 同 fence_key 同规范 proof 的任何重放都返回同一 submission_id,但必须 click_permitted=false、reconciliation_required=true;同 key 异 proof、不同 key 命中同 authorization/attempt、并发失败或服务重启都绝不再次许可。
  8. operation=LOOKUP_ONLY 只用于围栏响应不明后的同键调和:永远不创建 submission、永远不许可点击。找到原围栏时返回同一 submission 并保持/转入调和;找不到时明确无围栏事实,但恢复路径仍不得重新申请首次点击许可。
  9. 围栏后的 result 不再要求活跃租约,也不得放宽 current claim validator;新增独立 fenced ownership validator,只按 order_submission 冻结的 device/session/generation/token hash 验证原设备归属。它不能创建、续租、释放或转领 claim。
  10. 每个 submission 只接受一条一次性 result 事实。SUBMITTED 转 WAITING_PAYMENT 并永久消费授权;EXTERNAL_PAYMENT_HANDOFF、SECURITY_CHALLENGE、UNKNOWN 转 RECONCILIATION_REQUIRED 并保持围栏。相同 result_key 同载荷稳定重放,不同 key/observation 不得覆盖。没有 retry observation。
  11. 本任务从 result wire 移除旧 evidence_asset_id。首次真实提交后的页面尚未经本项目真机取证,不新增 SUBMISSION_RESULT kind;UNKNOWN 允许无截图。绝不把 Gate3 图冒充结果图,绝不上传外部支付页、支付凭据或完整 XML。后续结果截图必须另立取证和 evidence kind 任务。
  12. 管理调和只处理指定 submission,结论固定为 CONFIRMED_CREATED、CONFIRMED_NOT_CREATED_OR_CANNOT_COMPLETE、STILL_UNKNOWN;记录 reviewer、固定结论、服务端时间和幂等键,不触发设备动作。前两种形成最终人工结论后 authorization 永久 CONSUMED;STILL_UNKNOWN 保持围栏和待调和。
  13. T-206 详情显示同一 submission 的 proof、首次许可事实、result 和调和记录。任何围栏后页面都没有重置、取消、重新授权、重新领取、再次提交或普通重试入口;WAITING_PAYMENT 仍明确系统尚未付款。

验收要点

  • 两设备、同设备、同/异 key 并发中最多一个首次响应拥有 click_permitted=true;事务回滚时零 submission/状态变化。
  • 覆盖首次成功、同键重放、响应截断、服务重启、异载荷/异 key 冲突;所有非首次路径 click_permitted 都为 false。
  • LOOKUP_ONLY 找到/未找到、并发和重启都零创建、零许可;围栏响应不明不通过新 key 或恢复模式获得许可。
  • 覆盖错误/缺失/跨 task/attempt/device 的三类 evidence、事件缺口/乱序、summary 冲突,以及规格、数量、金额、上限、提交文本和 match count 全部负例。
  • 覆盖 current claim validator 的 lease/authorization 边界;静态和运行测试证明 fenced ownership validator 只验证冻结归属,不能被用于领取、续租、围栏创建或释放。
  • 覆盖 fence 与 T-207 reset/cancel 并发;任一 submission 一旦存在,所有 authorization 状态和人工结论下都永久拒绝释放。
  • 覆盖四种 result、同键重放、异 observation 冲突、UNKNOWN 无截图,以及三种人工调和结论;任何结果均不开放第二次点击。
  • Migration 覆盖升级/重开、唯一/FK/trigger、已有安全事实、降级拒绝和 foreign_key_check。
  • 静态检查没有 client/PDD selector、通用点击、submit_order_once、结果截图 kind、支付、免密支付、先用后付或扣款能力。
  • go test ./...、go test -race ./...、go vet ./...、go build ./...、完整 init、上下文、Vikunja export 与 diff-check 通过。

执行记录

2026-08-06T02:58:55Z · ila

2026-08-06T02:58:54.857Z · Codex 2026-08-06 合并式最终提交面板契约:submission-fence 请求使用 gate3_submit_amount、完整 submit_control_text、match_count 与 nearest_clickable_ancestor_unique;不得继续使用 confirm_page_amount/confirm_amount。服务端要求 Gate3 严格等于 Gate2 且不超上限。

边界

  • 2026-08-06 起 Gate3 是合并式最终提交面板唯一结构化最终控件金额;围栏请求/存储必须使用 gate3_submit_amount、完整控件文本、匹配数和最近可点击祖先唯一布尔值。旧 confirm_page_amount / confirm_amount 不得继续消费;T-210 迁移未完成时禁止建立围栏。

  • CREATE_ONCE 建立围栏时必须复用 T-302 的 current claim validator,严格要求当前设备、session、 task、authorization、attempt、generation、claim token、未过期 lease/authorization 与当前状态 全部一致。不得为了让围栏或结果流程跑通而放宽、复活或绕过该 validator。

  • 围栏后的 result 必须使用独立 fenced ownership validator,只验证 order_submission 已冻结的 device/session/generation/token hash 归属。它不得被用于领取、续租、创建第二个围栏、释放、转领, 也不得反向削弱 current claim validator。

  • 只有首次 CREATE_ONCE 原子创建唯一 submission 的明确成功响应可以包含 click_permitted=true。同 key 重放、异 key 冲突、并发输家、服务重启、响应不明恢复及任何 LOOKUP_ONLY 都必须 click_permitted=false;许可不得作为可重取数据库开关或手工构造对象。

  • LOOKUP_ONLY 永远零创建、零许可。围栏响应不明时不得换 key、再次申请首次许可或凭页面猜测点击; 找到原围栏只能调和同一 submission,找不到也只能按围栏前安全失败处置。

  • 围栏事务必须逐字段比对请求、T-205 append-only events/summary 和 T-210 三类 evidence 资产, 并在服务端重算三闸门与精确唯一“提交订单”控件。缺失、歧义、跨 task/attempt/device、价格/规格/ 数量冲突或事件乱序均零围栏、零许可。

  • 本任务不新增 SUBMISSION_RESULT 或其他结果截图 kind,并从 result wire 移除旧 evidence_asset_id。首次真实提交后的页面尚未完成本项目真机取证;UNKNOWN 允许无截图,不得把 Gate3 图片冒充结果图,外部支付页、支付凭据和完整 XML 永不上传。

  • 任一 submission 建立后,authorization、attempt 和 submission 永久不可释放、重置、取消、重新 授权、重新领取或再次点击。result 或人工调和只能收紧到 WAITING_PAYMENT / FAILED / 保持 RECONCILIATION_REQUIRED,绝不能回到围栏前状态。

  • 本任务只实现服务端围栏、一次性结果和同一 submission 调和;不修改 client/PDD 页面判据,不连接 真机,不实现 submit_order_once(),不点击“提交订单”,不编写支付、免密支付、先用后付或扣款能力。