docs(t216): freeze device command delivery contract
This commit is contained in:
@@ -58,7 +58,7 @@ T-205 原子领取/租约状态机、T-206 Android 登录/有限离线、T-207
|
||||
证据回传、T-211 参考图召回和 SKU 硬匹配、T-212 候选身份映射,以及 T-213 受控
|
||||
规格组合/价格核验均已完成。T-208 的原始候选观测、模型评估、确定性推荐、逐候选
|
||||
结构化人工理由和修订历史也已完成。T-214 商品持久身份和重新定位指纹、T-215
|
||||
Admin 候选确认与不可变待投递授权也已完成;下一步实现 T-216 设备命令投递与确认。
|
||||
Admin 候选确认与不可变待投递授权也已完成;当前实现 T-216 设备命令投递与确认。
|
||||
不得直接把候选链接或列表 ordinal 当成授权。
|
||||
手机从管理后端领取任务并回传结果,VLM、拼多多自动化和人工确认在 App 本地完成。
|
||||
T-206 增加有限离线执行;T-207 已复用 Roubao 端上 OpenAI 兼容适配器并加密本地 Key。
|
||||
|
||||
@@ -589,6 +589,25 @@ Admin task detail
|
||||
追加新版本并把旧记录标记为 `SUPERSEDED`,设备领取后必须走显式停止/撤销协议。
|
||||
浏览器不持有设备 claim token,授权表也不保存拼多多登录、支付或收货凭证。
|
||||
|
||||
### 设备命令投递(T-216)
|
||||
|
||||
Roubao 复用前台服务的 30 秒 heartbeat 主动 pull,不依赖 Android 后台推送:
|
||||
|
||||
```text
|
||||
flush candidate outbox
|
||||
-> task heartbeat keeps lease
|
||||
-> POST commands/next
|
||||
-> backend validates claim + execution + active authorization
|
||||
-> PENDING_DELIVERY -> DELIVERED and returns canonical command
|
||||
-> App validates and saves encrypted PendingOrderCommand
|
||||
-> POST commands/{id}/ack with same hash/idempotency key
|
||||
-> DELIVERED -> ACKNOWLEDGED
|
||||
```
|
||||
|
||||
HTTP 响应不确定时,服务端对 `DELIVERED/ACKNOWLEDGED` 重放相同 command 和 hash;
|
||||
App 只有在加密状态提交成功后才 ACK。投递/确认与真正页面执行分离,T-216 收到命令
|
||||
后仍不打开拼多多。App 本地旧候选选择不再作为后台任务完成条件。
|
||||
|
||||
人工理由使用版本化 allowlist。接受或拒绝至少有一个理由且指定主要理由;
|
||||
`OTHER` 才要求 4-200 字备注。拒绝推荐后改选必须同时产生一条原推荐项负标签和一条
|
||||
替代项正标签;全部无匹配时每个曝光候选都有负标签。人工修正追加新 review 并引用
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
| T-213 | 拼多多候选 SKU 组合与价格核验 | T-212 | 受控选择唯一颜色/尺码并读取组合价;不确认购买,不进入订单或支付 |
|
||||
| T-214 | 建立候选商品持久身份与重新定位指纹 | T-208、T-213 | candidate key 绑定原 observation、卡片/详情/双证据指纹;不伪造平台链接 |
|
||||
| T-215 | Admin 候选确认与一次性下单授权 | T-214 | 非空候选进入等待确认;Admin 按 candidate key 提交逐项理由并生成不可变待投递授权 |
|
||||
| T-216 | 设备下单命令投递、确认与恢复 | T-215 | App 主动拉取;先加密落盘再 ACK;断网/重启重放同一授权且不执行拼多多动作 |
|
||||
| T-217 | 已授权商品重新定位与订单 dry-run | T-216 | 重新核对持久指纹并唯一选择 SKU/数量,停在最终提交前 |
|
||||
| T-218 | 单次订单提交与订单回读 | T-217 | 一次性提交后从订单列表读取订单号/时间并对账,不支付 |
|
||||
| T-219 | Admin 待付款提醒与端到端验收 | T-218 | 展示可核对订单并提醒采购员去拼多多确认付款 |
|
||||
|
||||
## Phase 3:端到端验证
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
| IX-008 | US-006 | App/管理端错误状态 | 自动失败、取消、重试上传 | 显示结构化原因和恢复动作 | P0 | 已定 |
|
||||
| IX-009 | US-008 | App 候选理由/管理端决策详情 | 接受、拒绝、改选或修正 | 保存逐候选结构化人工标签 | P1 | T-208 已实现 |
|
||||
| IX-010 | US-009 | App 独立执行设置/同步状态 | 配置模式、离线执行或补报 | 授权内独立执行并可审计同步 | P0 | T-206 离线控制已实现,结果补报待 T-207 |
|
||||
| IX-011 | US-010 | Admin 候选授权/App 订单执行 | 选择候选并授权、设备领取执行 | 创建一笔可对账的待付款订单并提醒人工付款 | P0 | T-215 至 T-219 |
|
||||
| IX-011 | US-010 | Admin 候选授权/App 订单执行 | 选择候选并授权、设备领取执行 | 创建一笔可对账的待付款订单并提醒人工付款 | P0 | T-215 已完成;T-216 进行中 |
|
||||
|
||||
## IX-001 管理 Web 登录
|
||||
|
||||
|
||||
+20
@@ -279,6 +279,26 @@ Admin `GET /api/v1/tasks/{task_id}` 同时返回当前及历史
|
||||
`order_authorizations`。授权仅允许 T-216 设备命令领取,不代表浏览器可以直接操作
|
||||
手机,也不授权付款。
|
||||
|
||||
### `POST /api/v1/tasks/{task_id}/commands/next`
|
||||
|
||||
T-216 起由原 BUYER/设备主动拉取当前 execution 的下单命令。请求带 bearer、
|
||||
`X-Claim-Token`,body 提交 `device_id`、`execution_id` 和 `claim_generation`。
|
||||
服务端校验有效 claim/租约、`WAITING_CONFIRMATION`、未取消和同一 active
|
||||
authorization。没有命令返回 `204`;首次投递把授权置为 `DELIVERED`,之后对
|
||||
`DELIVERED/ACKNOWLEDGED` 返回同一 schema v1 command。
|
||||
|
||||
命令只含 task/execution/content hash、原始 SKU/数量、candidate key、观测标题/
|
||||
规格/价格和 T-214 四个指纹,不含第三方 URL。`observed_ordinal` 仅是重新定位提示。
|
||||
`command_sha256` 固定 canonical payload,投递重试不得变化。
|
||||
|
||||
### `POST /api/v1/tasks/{task_id}/commands/{command_id}/ack`
|
||||
|
||||
App 严格校验并写入 Keystore-backed 加密状态后,使用 bearer、claim token、
|
||||
`Idempotency-Key` 提交 execution、generation 和 `command_sha256`。成功把
|
||||
`DELIVERED -> ACKNOWLEDGED`;相同 key/body 和同 command/hash 可安全重试,其他
|
||||
command/hash/设备或无效租约返回冲突。ACK 只表示命令已可靠保存,不表示开始操作、
|
||||
创建订单或付款。
|
||||
|
||||
## 设备与领取
|
||||
|
||||
### `POST /api/v1/devices/heartbeat`
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-07-28
|
||||
- 阶段:T-215 Admin 候选确认与一次性下单授权已完成,准备 T-216
|
||||
- 阶段:T-216 设备下单命令投递、确认与恢复进行中
|
||||
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-207、T-209、
|
||||
T-208、T-210、T-211、T-212、T-213、T-214 均已纳入 Git 历史
|
||||
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
|
||||
@@ -128,6 +128,7 @@
|
||||
| `docs/tasks/T-208.md` | DONE | 归一化候选决策数据并增加结构化人工 review |
|
||||
| `docs/tasks/T-214.md` | DONE | 建立 execution-scoped candidate key 与设备采集指纹 |
|
||||
| `docs/tasks/T-215.md` | DONE | Admin 按 candidate key 选择并创建不可变待投递授权 |
|
||||
| `docs/tasks/T-216.md` | DOING | App 主动拉取并先加密落盘再确认同一条下单命令 |
|
||||
| `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 |
|
||||
| `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 |
|
||||
| `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 |
|
||||
@@ -139,7 +140,7 @@
|
||||
## 任务摘要
|
||||
|
||||
- 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-215。
|
||||
- 正在进行:准备 T-216 设备命令投递与确认。
|
||||
- 正在进行:T-216 设备下单命令投递、确认与恢复。
|
||||
- 下一步:依次实现设备命令、订单 dry-run、单次提交对账和
|
||||
付款提醒。
|
||||
|
||||
|
||||
+3
-2
@@ -45,7 +45,8 @@ Android 导航名称是逻辑目的地,具体 Compose/Fragment 形式待接入
|
||||
| `POST /api/v1/tasks/{id}/cancel-ack` | 安全停止后确认 `CANCELED` 并结束 execution |
|
||||
| `POST /api/v1/tasks/{id}/events` | 幂等补报本地执行事件 |
|
||||
| `POST /api/v1/tasks/{id}/candidates` | 保存最多 5 个候选、模型判断和本地推荐 |
|
||||
| `POST /api/v1/tasks/{id}/commands/next` | T-216 起领取 Admin 已创建的设备命令 |
|
||||
| `POST /api/v1/tasks/{id}/commands/next` | 拉取或重放 Admin 已创建的同一条下单命令 |
|
||||
| `POST /api/v1/tasks/{id}/commands/{command_id}/ack` | App 加密落盘后幂等确认命令 |
|
||||
| `POST /api/v1/tasks/{id}/complete` | 提交人工结果,强制 `order_submitted=false` |
|
||||
| `POST /api/v1/tasks/{id}/fail` | 提交结构化失败和受控证据 |
|
||||
|
||||
@@ -59,7 +60,7 @@ T-207 的 events/candidates/complete/fail 接受原设备对授权内已产生
|
||||
## 导航规则
|
||||
|
||||
- App 有活跃任务时启动后优先恢复该任务,不允许直接领取下一条。
|
||||
- `CLAIMED` 进入预览;`RUNNING` 进入执行;`WAITING_CONFIRMATION` 进入候选确认;
|
||||
- `CLAIMED` 进入预览;`RUNNING` 进入执行;`WAITING_CONFIRMATION` 进入等待 Admin;
|
||||
终态进入结果。
|
||||
- 从执行页切换拼多多后,使用前台服务通知返回执行页。
|
||||
- 候选确认页返回不能恢复自动化。
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
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 不确定结果造成命令丢失。
|
||||
Reference in New Issue
Block a user