1
T-216
ila edited this page 2026-08-07 16:36:34 +08:00
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.

同步来源:docs/tasks/T-216.md · commit afc651f75a3a


id: T-216 title: 设备下单命令投递、确认与恢复 phase: 2 deps:

  • T-215 status: DONE 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:

{
  "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 不确定结果造成命令丢失。
  • 2026-07-28:新增 v9、命令 pull/ACK API、同一授权重放和投递/确认审计事件。 HTTP 集成覆盖错误 hash、响应丢失后的相同 key 重试、ACK 后恢复拉取、事件不重复 及带数据禁止降级。
  • 2026-07-28:Android 1.4.13 (18) 增加命令 canonical hash 校验、加密持久化、 固定 ACK key、重启恢复和等待/已授权 UI。后台任务有候选后不再出现手机接受/ 拒绝入口,也禁止再次采集;空候选仍保留安全终态。
  • 2026-07-28:Go/Kotlin 固定哈希向量一致;Android 测试证明存储失败时 ACK 调用数 为 0。go test ./...、go test -race ./...、go vet ./...、Android test、 Debug/Release assemble 和根 .\init.ps1 全部通过。APK 已构建,未在本任务中 覆盖安装或执行拼多多页面动作。