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

108 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-208
title: 三闸门提交围栏与结果调和 API
phase: 2
deps: [T-207, T-210]
status: TODO
created: 2026-08-04
vikunja_task_id: 47
context_ref: cd47d0c
work_branch: task/t-208-submission-fence
needs_device: false
needs_human_review: false
write_paths:
- 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
---
<!-- BEGIN VIKUNJA EXPORT id=47 synced=2026-08-04T14:41:55Z sha256=a1109c837b096ecebfbda2200aa3c95c84b31ac2ff8e52404acb95060f148b3b -->
## 问题 / 背景
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 通过。
## 执行记录
(暂无)
<!-- END VIKUNJA EXPORT -->
## 边界
- `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()`,不点击“提交订单”,不编写支付、免密支付、先用后付或扣款能力。