Files
cmroubao/docs/tasks/T-216.md
T

150 lines
6.4 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-216
title: 设备下单命令投递、确认与恢复
phase: 2
deps:
- T-215
status: DOING
created: 2026-07-28
context_ref: 827afc7
work_branch: null
write_paths:
- docs/tasks/T-216.md
- docs/00-ai-start-here.md
- docs/02-requirements.md
- docs/04-architecture.md
- docs/06-tasks.md
- docs/08-interaction-checklist.md
- docs/api.md
- docs/current-state.md
- docs/routes.md
- backend-api/migrations/**
- backend-api/internal/domain/**
- backend-api/internal/usecase/**
- backend-api/internal/repository/sqlite/**
- backend-api/internal/transport/httpapi/**
- android-buyer/app/src/main/**
- android-buyer/app/src/test/**
---
## 问题 / 背景
T-215 只生成 `PENDING_DELIVERY` 下单授权。Android 仍停留在旧的手机人工选择流程,
也没有可靠方式知道 Admin 已授权哪个 candidate。直接在 Admin 浏览器推送或收到 HTTP
响应后立即执行都无法处理 Android 后台限制、断网、进程重启和响应丢失。
本任务把授权作为同一 execution 的可重放设备命令,由 Roubao 的现有前台服务主动
拉取。App 必须先把完整命令写入 Keystore-backed 加密状态,再确认收妥。T-216 不
执行拼多多页面动作。
## 冻结合约
### 拉取而非服务端推送
1. App 在非终态 execution 的 30 秒 heartbeat 后,以及用户点击“立即同步”时调用
`POST /api/v1/tasks/{task_id}/commands/next`。不增加 WebSocket、FCM 或浏览器到
手机的直连通道。
2. 请求必须携带 BUYER bearer、`X-Claim-Token`,并提交当前 `device_id`、
`execution_id`、`claim_generation`。服务端校验 task、user、device、execution、
generation、token、有效租约、`WAITING_CONFIRMATION` 和未请求取消。
3. 没有命令返回 `204`。当前 active authorization 为
`PENDING_DELIVERY/DELIVERED/ACKNOWLEDGED` 时返回同一命令;一个 execution 不会
返回第二条 active 命令。
### 命令内容与持久身份
命令 `schema_version=1`、`type=CREATE_PENDING_ORDER`,只包含服务端快照和受控
observation,不包含第三方 URL:
```json
{
"id": "authorization-uuid",
"schema_version": 1,
"type": "CREATE_PENDING_ORDER",
"authorization_version": 1,
"task_id": "uuid",
"execution_id": "uuid",
"task_content_sha256": "64-char-hex",
"original_sku": "雾霾蓝 / L",
"quantity": 2,
"candidate": {
"candidate_key": "64-char-hex",
"observed_ordinal": 1,
"title": "实际观测标题",
"sku_text": "雾霾蓝 / L",
"price_text": "39.80",
"card_signature": "64-char-hex",
"detail_signature": "64-char-hex",
"detail_evidence_sha256": "64-char-hex",
"specification_evidence_sha256": "64-char-hex"
},
"command_sha256": "64-char-hex",
"authorization_status": "DELIVERED"
}
```
1. `observed_ordinal` 只用于重新定位提示,不能直接作为点击索引;T-217 必须重新核对
card/detail/SKU/证据。
2. `command_sha256` 是 schema v1 除自身和投递状态外的 canonical JSON SHA-256。
首次投递在事务内写入该 hash、`delivered_at` 和
`ORDER_AUTHORIZATION_DELIVERED` 事件;重投不新增事件。
3. 已 `ACKNOWLEDGED` 但 App 本地状态丢失时仍允许同一身份重新拉取,以便安全恢复;
不允许因此改写 hash 或创建新命令。
### App 先持久化后确认
1. 新增加密 `PendingOrderCommand`,保存完整快照、command hash、ACK 幂等键和确认
状态。先验证 schema/type、task/execution/content hash、数量和所有指纹,再一次
`store.save`;保存失败不得发送 ACK。
2. 保存成功后调用
`POST /api/v1/tasks/{task_id}/commands/{command_id}/ack`,请求包含 execution、
generation 和 command hash,并带 claim token 与 `Idempotency-Key`。
3. ACK 在事务内把 `DELIVERED -> ACKNOWLEDGED`,写 `acknowledged_at`、幂等记录和
`ORDER_AUTHORIZATION_ACKNOWLEDGED` 事件。相同 key/body 或同 command/hash 重试
返回相同结果;hash、归属、状态或身份不匹配返回冲突。
4. HTTP ACK 响应丢失时 App 保留同一 ACK key 重试。只有后端确认后本地
`acknowledged=true`,UI 显示“后台已授权,等待订单核验”;进程重启继续 ACK,
不能重新采集或领取其他任务。
### 旧手机人工确认收口
1. 后台任务非空候选上传成功后,App 进入 `WAITING_ADMIN_CONFIRMATION`,不再显示
本地接受/拒绝候选按钮,也不提交旧的 `HUMAN_REVIEW + COMPLETE`。
2. 空候选仍可按现有安全终态回传;独立本地探针不受后台任务模式影响。
3. 前台服务在等待 Admin 时继续 heartbeat、同步 outbox 和拉取命令;租约到期、
取消、认证失效或命令校验失败时保持安全停止。
## 数据模型
v9:
- `order_authorizations` 增加不可变 `command_sha256` 和投递计数/最后投递时间;
- `device_order_command_ack_requests` 保存 device、幂等键、请求 hash、command 和时间;
- `task_events` 增加 `ORDER_AUTHORIZATION_DELIVERED` 与
`ORDER_AUTHORIZATION_ACKNOWLEDGED`。
存在命令投递/ACK 数据或新事件时 destructive down 必须失败。
## 验收要点
- [ ] 只有原 user/device/execution/generation/token 且租约有效时能拉取命令。
- [ ] 首次拉取原子进入 `DELIVERED`;网络重放返回相同 command/hash 且不重复事件。
- [ ] App 严格校验并先加密持久化,存储失败不 ACK,重启可恢复同一命令。
- [ ] ACK 幂等进入 `ACKNOWLEDGED`,错误 hash/command/设备和并发请求被拒绝。
- [ ] 后台候选流程等待 Admin,不再允许手机本地选择并提前完成任务。
- [ ] App 等待/已授权状态可见,但不打开拼多多、不选择 SKU/数量、不提交订单。
- [ ] v9 migration、Go test/race/vet、Android test/Debug/Release 和根验证通过。
## 边界
- 不重新打开图片搜索或定位 Admin 选择的商品;属于 T-217。
- 不选择 SKU/数量、进入确认订单页或比较最终金额;属于 T-217。
- 不点击确认购买、读取订单号或处理付款;属于 T-218/T-219。
- 不新增 FCM/WebSocket,不把第三方 URL 当命令,不授权支付。
## 执行记录
- 2026-07-28:T-215 实现提交 `827afc7` 后领取。复用现有 30 秒前台 heartbeat 和
Keystore-backed 状态,选择 pull + durable-before-ACK,避免 Android 后台推送和
HTTP 不确定结果造成命令丢失。