docs(t217): freeze authorized order dry-run contract

This commit is contained in:
QiuSW
2026-07-28 13:26:28 +08:00
parent 2372ab2280
commit 913107c28d
7 changed files with 168 additions and 5 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ T-205 原子领取/租约状态机、T-206 Android 登录/有限离线、T-207
规格组合/价格核验均已完成。T-208 的原始候选观测、模型评估、确定性推荐、逐候选 规格组合/价格核验均已完成。T-208 的原始候选观测、模型评估、确定性推荐、逐候选
结构化人工理由和修订历史也已完成。T-214 商品持久身份和重新定位指纹、T-215 结构化人工理由和修订历史也已完成。T-214 商品持久身份和重新定位指纹、T-215
Admin 候选确认、不可变待投递授权及 T-216 设备命令可靠投递与确认均已完成;下一项 Admin 候选确认、不可变待投递授权及 T-216 设备命令可靠投递与确认均已完成;下一项
是 T-217 已授权商品重新定位与订单 dry-run。 是正在实现的 T-217 已授权商品重新定位与订单 dry-run。
不得直接把候选链接或列表 ordinal 当成授权。 不得直接把候选链接或列表 ordinal 当成授权。
手机从管理后端领取任务并回传结果,VLM、拼多多自动化和人工确认在 App 本地完成。 手机从管理后端领取任务并回传结果,VLM、拼多多自动化和人工确认在 App 本地完成。
T-206 增加有限离线执行;T-207 已复用 Roubao 端上 OpenAI 兼容适配器并加密本地 Key。 T-206 增加有限离线执行;T-207 已复用 Roubao 端上 OpenAI 兼容适配器并加密本地 Key。
+21
View File
@@ -608,6 +608,27 @@ HTTP 响应不确定时,服务端对 `DELIVERED/ACKNOWLEDGED` 重放相同 com
App 只有在加密状态提交成功后才 ACK。投递/确认与真正页面执行分离,T-216 收到命令 App 只有在加密状态提交成功后才 ACK。投递/确认与真正页面执行分离,T-216 收到命令
后仍不打开拼多多。App 本地旧候选选择不再作为后台任务完成条件。 后仍不打开拼多多。App 本地旧候选选择不再作为后台任务完成条件。
### 已授权订单 dry-run(T-217)
```text
ACKNOWLEDGED command
-> encrypted PendingOrderDryRun
-> idempotent dry-run start: authorization -> EXECUTING
-> reference-image search with bounded candidate/scroll budget
-> exact card/detail anchor + normalized title
-> deterministic color/size + quantity + price/budget verification
-> unique specification confirmation action
-> ORDER_CONFIRMATION read-only verification and evidence
-> encrypted READY
-> idempotent backend ready record
```
旧 ordinal 和第三方 URL 都不参与点击。旧截图 hash 是 Admin 决策证据,不要求新截图
逐像素相等;重新定位至少精确命中一个旧语义签名,并同时满足标题、SKU、价格与唯一性。
App 可以从规格弹层进入确认订单页,但 T-217 的 accessibility action allowlist 不包含
确认订单页的提交、付款、地址或优惠选择。READY 同时存在于手机加密状态与后端审计表,
才允许 T-218 消费。
人工理由使用版本化 allowlist。接受或拒绝至少有一个理由且指定主要理由; 人工理由使用版本化 allowlist。接受或拒绝至少有一个理由且指定主要理由;
`OTHER` 才要求 4-200 字备注。拒绝推荐后改选必须同时产生一条原推荐项负标签和一条 `OTHER` 才要求 4-200 字备注。拒绝推荐后改选必须同时产生一条原推荐项负标签和一条
替代项正标签;全部无匹配时每个曝光候选都有负标签。人工修正追加新 review 并引用 替代项正标签;全部无匹配时每个曝光候选都有负标签。人工修正追加新 review 并引用
+1 -1
View File
@@ -17,7 +17,7 @@
| IX-008 | US-006 | App/管理端错误状态 | 自动失败、取消、重试上传 | 显示结构化原因和恢复动作 | P0 | 已定 | | IX-008 | US-006 | App/管理端错误状态 | 自动失败、取消、重试上传 | 显示结构化原因和恢复动作 | P0 | 已定 |
| IX-009 | US-008 | App 候选理由/管理端决策详情 | 接受、拒绝、改选或修正 | 保存逐候选结构化人工标签 | P1 | T-208 已实现 | | IX-009 | US-008 | App 候选理由/管理端决策详情 | 接受、拒绝、改选或修正 | 保存逐候选结构化人工标签 | P1 | T-208 已实现 |
| IX-010 | US-009 | App 独立执行设置/同步状态 | 配置模式、离线执行或补报 | 授权内独立执行并可审计同步 | P0 | T-206 离线控制已实现,结果补报待 T-207 | | IX-010 | US-009 | App 独立执行设置/同步状态 | 配置模式、离线执行或补报 | 授权内独立执行并可审计同步 | P0 | T-206 离线控制已实现,结果补报待 T-207 |
| IX-011 | US-010 | Admin 候选授权/App 订单执行 | 选择候选并授权、设备领取执行 | 创建一笔可对账的待付款订单并提醒人工付款 | P0 | T-215/T-216 已完成;T-217 待实现 | | IX-011 | US-010 | Admin 候选授权/App 订单执行 | 选择候选并授权、设备领取执行 | 创建一笔可对账的待付款订单并提醒人工付款 | P0 | T-215/T-216 已完成;T-217 进行中 |
## IX-001 管理 Web 登录 ## IX-001 管理 Web 登录
+15
View File
@@ -299,6 +299,21 @@ App 严格校验并写入 Keystore-backed 加密状态后,使用 bearer、clai
command/hash/设备或无效租约返回冲突。ACK 只表示命令已可靠保存,不表示开始操作、 command/hash/设备或无效租约返回冲突。ACK 只表示命令已可靠保存,不表示开始操作、
创建订单或付款。 创建订单或付款。
### `POST /api/v1/tasks/{task_id}/order-dry-runs/start`
T-217 App 在加密保存 dry-run 意图后调用。请求带 BUYER bearer、claim token、
`Idempotency-Key`,body 固定 device、execution、generation、command id/hash。
服务端要求有效租约、任务仍为 `WAITING_CONFIRMATION`、未取消且授权为
`ACKNOWLEDGED`;事务内创建 PREPARING dry-run、把授权置为 `EXECUTING` 并写开始
事件。相同请求重放返回同一记录。
### `POST /api/v1/tasks/{task_id}/order-dry-runs/{command_id}/ready`
App 到达确认订单页并先加密保存 READY 后,提交当前 card/detail 指纹、规范化标题、
已选 SKU、数量、单价、商品总额和受控 evidence asset。服务端重新核对 command、
task/execution/claim、数量、预算和 evidence 归属后,把同一 dry-run 置为 READY 并写
事件;相同请求可重放。该响应不表示订单已提交,且不授权付款。
## 设备与领取 ## 设备与领取
### `POST /api/v1/devices/heartbeat` ### `POST /api/v1/devices/heartbeat`
+4 -3
View File
@@ -5,7 +5,7 @@
## 当前快照 ## 当前快照
- 日期:2026-07-28 - 日期:2026-07-28
- 阶段:T-216 设备下单命令投递、确认与恢复已完成,待开始 T-217 - 阶段:T-217 已授权商品重新定位与订单 dry-run 进行中
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-207、T-209、 - 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 历史 T-208、T-210、T-211、T-212、T-213、T-214 均已纳入 Git 历史
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码 - 生产代码:`android-buyer/` 已接入 Roubao Android 源码
@@ -134,6 +134,7 @@
| `docs/tasks/T-214.md` | DONE | 建立 execution-scoped candidate key 与设备采集指纹 | | `docs/tasks/T-214.md` | DONE | 建立 execution-scoped candidate key 与设备采集指纹 |
| `docs/tasks/T-215.md` | DONE | Admin 按 candidate key 选择并创建不可变待投递授权 | | `docs/tasks/T-215.md` | DONE | Admin 按 candidate key 选择并创建不可变待投递授权 |
| `docs/tasks/T-216.md` | DONE | App 主动拉取并先加密落盘再确认同一条下单命令 | | `docs/tasks/T-216.md` | DONE | App 主动拉取并先加密落盘再确认同一条下单命令 |
| `docs/tasks/T-217.md` | DOING | 重新核对已授权商品并选择 SKU/数量,停在最终提交前 |
| `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 | | `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 |
| `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 | | `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 |
| `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 | | `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 |
@@ -145,8 +146,8 @@
## 任务摘要 ## 任务摘要
- 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-216。 - 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-216。
- 正在进行:无。 - 正在进行:T-217 已授权商品重新定位与订单 dry-run。
- 下一步:依次实现订单 dry-run、单次提交对账和付款提醒。 - 下一步:依次实现单次提交对账和付款提醒。
## 当前可运行内容 ## 当前可运行内容
+2
View File
@@ -47,6 +47,8 @@ Android 导航名称是逻辑目的地,具体 Compose/Fragment 形式待接入
| `POST /api/v1/tasks/{id}/candidates` | 保存最多 5 个候选、模型判断和本地推荐 | | `POST /api/v1/tasks/{id}/candidates` | 保存最多 5 个候选、模型判断和本地推荐 |
| `POST /api/v1/tasks/{id}/commands/next` | 拉取或重放 Admin 已创建的同一条下单命令 | | `POST /api/v1/tasks/{id}/commands/next` | 拉取或重放 Admin 已创建的同一条下单命令 |
| `POST /api/v1/tasks/{id}/commands/{command_id}/ack` | App 加密落盘后幂等确认命令 | | `POST /api/v1/tasks/{id}/commands/{command_id}/ack` | App 加密落盘后幂等确认命令 |
| `POST /api/v1/tasks/{id}/order-dry-runs/start` | 加密保存执行意图后开始已授权商品重新定位 |
| `POST /api/v1/tasks/{id}/order-dry-runs/{command_id}/ready` | 回传确认订单页前的 SKU/数量/金额与证据 |
| `POST /api/v1/tasks/{id}/complete` | 提交人工结果,强制 `order_submitted=false` | | `POST /api/v1/tasks/{id}/complete` | 提交人工结果,强制 `order_submitted=false` |
| `POST /api/v1/tasks/{id}/fail` | 提交结构化失败和受控证据 | | `POST /api/v1/tasks/{id}/fail` | 提交结构化失败和受控证据 |
+124
View File
@@ -0,0 +1,124 @@
---
id: T-217
title: 已授权商品重新定位与订单 dry-run
phase: 2
deps:
- T-216
status: DOING
created: 2026-07-28
context_ref: 2372ab2
work_branch: null
write_paths:
- docs/tasks/T-217.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-216 已把 Admin 选择的 candidate 作为不可变命令可靠保存到手机,但命令不包含可执行
第三方 URL,旧 ordinal 也不是商品身份。App 必须重新使用任务参考图进入拼多多图片
搜索,在变化的结果中重新核对 Admin 授权的 observation,确定性选择原 SKU 和数量,
并停在“确认订单”页的最终提交按钮之前。
本任务是 `ORDER_DRY_RUN`,不是订单提交。它允许进入规格弹层、确认规格并进入订单确认
页,但代码中不提供“提交订单/立即支付/确认支付”动作。只有 T-217 的本地与后端
READY 证据都存在,T-218 才能消费同一授权执行一次提交。
## 冻结合约
### 启动与恢复
1. 前台 execution service 只在本地 `PendingOrderCommand.acknowledged=true`、租约有效、
未取消且 readiness 仍满足时自动启动;不要求采购员再次点击,也不创建第二条命令。
2. App 先生成并加密保存 `PendingOrderDryRun`、start/ready 幂等键和 attempt 状态,再
调用 `POST /api/v1/tasks/{task_id}/order-dry-runs/start`。服务端只允许同一
user/device/task/execution/generation/command hash 的 `ACKNOWLEDGED` 授权进入
`EXECUTING`;响应丢失可安全重放。
3. 进程在 READY 前退出时,不假定拼多多仍停在可信页面。恢复后先识别并安全返回,
再从参考图重新执行有界定位;不从上次坐标或旧 ordinal 继续。
4. T-216 command pull 对同一 `EXECUTING` 授权继续返回相同 command/hash,支持本地
加密状态丢失后的安全恢复,但不得因此创建第二次授权。
### 重新图片搜索与候选唯一定位
1. 每次尝试重新校验本地参考 JPEG 的 SHA-256,使用 T-211 的一次性 MediaStore 图片
重新进入拼多多图片结果;最多检查 10 个卡片、滚动 2 次,预算在动作前扣减且重试
不重置。
2. `observed_ordinal` 只用于审计,不能作为点击索引。每个实际检查项重新采集 card
signature、detail signature、可见标题、规格证据、当前组合价格和新截图哈希。
3. 候选必须至少有一个旧的语义签名(card/detail)精确命中,同时标题规范化一致;
之后颜色与尺码必须按原始 SKU 各自唯一匹配并复核 selected/“已选择”摘要。旧详情/
规格截图 hash 只证明 Admin 当时的依据,不要求新截图逐像素相同。
4. 当前组合价格必须能严格解析为单一人民币分值。有任务总预算时,
`unit_price * quantity <= max_budget`;没有预算时当前单价必须等于授权候选的严格
可解析单价。区间、券后、条件、多价格冲突或价格上涨均停止。
5. 只有一个候选通过全部条件才能继续。零个或多个命中、签名全部变化、标题冲突、
SKU 不唯一、库存/控件禁用或价格不满足时安全停止。VLM 可辅助排序和解释截图,
但不能替代任一确定性条件或发出点击。
### SKU、数量与订单确认页
1. 从已唯一定位的详情页只允许用 T-213 已验证的规格入口进入弹层。颜色、尺码按目标
唯一选择;数量控件必须有可访问的当前数值与唯一加减语义,目标数量 `1..99`,点击
次数有界且每次后读验证。不能用坐标或长按填数量。
2. 规格、数量和价格全部复核后,允许点击规格弹层内唯一的“确定”语义控件进入
`ORDER_CONFIRMATION`。该动作只是形成待提交订单预览,不是平台订单提交。
3. 订单确认页重新读取商品标题/规格摘要/数量/商品金额或应付总额;必须与授权和弹层
证据一致,且总额不超过任务最高商品总预算。地址、优惠券、运费或服务选项只读,
App 不自动修改。
4. 捕获受控订单确认截图后,App 先加密保存 READY 证据,再上传截图并幂等调用
`POST /api/v1/tasks/{task_id}/order-dry-runs/{command_id}/ready`。服务端保存当前
指纹、SKU、数量、价格、金额和 evidence,写 `ORDER_DRY_RUN_READY` 事件。
5. 服务端确认后 UI 显示“订单已核验,等待单次提交”。App 保持前台服务和租约,
不点击确认订单页的提交控件;重启复用同一 READY 记录。
## 数据模型
v10:
- `order_dry_runs`:每个 authorization 唯一一行,保存 PREPARING/READY、命令 hash、
本次 card/detail 指纹、标题、SKU、数量、单价/总额、受控 evidence 和时间;
- `device_order_dry_run_requests`:按 device + operation + idempotency key 保存请求
hash 与结果引用;
- task events 增加 `ORDER_DRY_RUN_STARTED` 和 `ORDER_DRY_RUN_READY`。
存在 dry-run 数据或新事件时 destructive down 必须失败。授权沿用 v8 已有
`ACKNOWLEDGED -> EXECUTING`,T-217 不增加订单已提交或付款状态。
## 验收要点
- [ ] start/ready 仅接受原身份、有效租约、同一 command hash,并可安全重放。
- [ ] 重新定位不按 ordinal/坐标;预算、滚动和候选检查均有硬上限。
- [ ] 至少一个旧语义签名、标题、唯一 SKU 和价格/预算同时满足,且只能唯一命中。
- [ ] 数量设置逐步复核;禁用、重复、缺失和状态未改变均停止。
- [ ] 只允许从规格弹层进入订单确认页,绝不点击订单提交或支付控件。
- [ ] READY 先加密落盘再回传,截图/当前指纹/SKU/数量/金额可审计并可恢复。
- [ ] v10 migration、Go test/race/vet、Android test/Debug/Release 和根验证通过。
- [ ] PKG110 真机 smoke 到订单确认页,确认拼多多订单列表尚无新订单。
## 边界
- 不点击确认订单页最终提交,不创建平台订单;属于 T-218。
- 不读取订单列表、订单号或下单时间;属于 T-218。
- 不提示或执行付款;属于 T-219/采购员人工操作。
- 不用 VLM、OCR、坐标、剪贴板或旧截图 hash 绕过可访问性语义失败。
## 执行记录
- 2026-07-28:T-216 实现提交 `2372ab2` 后领取。冻结自动触发、恢复、重新图片搜索、
多信号唯一定位、SKU/数量/价格复核和最终提交禁区;计划复用 T-211/T-213 已验证的
图片搜索、规格解析和页面分类能力。