diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md index 43b27c2..988dc28 100644 --- a/docs/00-ai-start-here.md +++ b/docs/00-ai-start-here.md @@ -59,7 +59,7 @@ T-205 原子领取/租约状态机、T-206 Android 登录/有限离线、T-207 规格组合/价格核验均已完成。T-208 的原始候选观测、模型评估、确定性推荐、逐候选 结构化人工理由和修订历史也已完成。T-214 商品持久身份和重新定位指纹、T-215 Admin 候选确认、不可变待投递授权及 T-216 设备命令可靠投递与确认均已完成;下一项 -是 T-217 已授权商品重新定位与订单 dry-run。 +是正在实现的 T-217 已授权商品重新定位与订单 dry-run。 不得直接把候选链接或列表 ordinal 当成授权。 手机从管理后端领取任务并回传结果,VLM、拼多多自动化和人工确认在 App 本地完成。 T-206 增加有限离线执行;T-207 已复用 Roubao 端上 OpenAI 兼容适配器并加密本地 Key。 diff --git a/docs/04-architecture.md b/docs/04-architecture.md index ed7cdb7..de6e77e 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -608,6 +608,27 @@ HTTP 响应不确定时,服务端对 `DELIVERED/ACKNOWLEDGED` 重放相同 com App 只有在加密状态提交成功后才 ACK。投递/确认与真正页面执行分离,T-216 收到命令 后仍不打开拼多多。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。接受或拒绝至少有一个理由且指定主要理由; `OTHER` 才要求 4-200 字备注。拒绝推荐后改选必须同时产生一条原推荐项负标签和一条 替代项正标签;全部无匹配时每个曝光候选都有负标签。人工修正追加新 review 并引用 diff --git a/docs/08-interaction-checklist.md b/docs/08-interaction-checklist.md index 308d7c4..4bc8147 100644 --- a/docs/08-interaction-checklist.md +++ b/docs/08-interaction-checklist.md @@ -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-216 已完成;T-217 待实现 | +| IX-011 | US-010 | Admin 候选授权/App 订单执行 | 选择候选并授权、设备领取执行 | 创建一笔可对账的待付款订单并提醒人工付款 | P0 | T-215/T-216 已完成;T-217 进行中 | ## IX-001 管理 Web 登录 diff --git a/docs/api.md b/docs/api.md index 09e5c1a..07d823a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -299,6 +299,21 @@ App 严格校验并写入 Keystore-backed 加密状态后,使用 bearer、clai 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` diff --git a/docs/current-state.md b/docs/current-state.md index 7c93ab7..5f764c2 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -5,7 +5,7 @@ ## 当前快照 - 日期: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、 T-208、T-210、T-211、T-212、T-213、T-214 均已纳入 Git 历史 - 生产代码:`android-buyer/` 已接入 Roubao Android 源码 @@ -134,6 +134,7 @@ | `docs/tasks/T-214.md` | DONE | 建立 execution-scoped candidate key 与设备采集指纹 | | `docs/tasks/T-215.md` | DONE | Admin 按 candidate key 选择并创建不可变待投递授权 | | `docs/tasks/T-216.md` | DONE | App 主动拉取并先加密落盘再确认同一条下单命令 | +| `docs/tasks/T-217.md` | DOING | 重新核对已授权商品并选择 SKU/数量,停在最终提交前 | | `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 | | `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 | | `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 | @@ -145,8 +146,8 @@ ## 任务摘要 - 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-216。 -- 正在进行:无。 -- 下一步:依次实现订单 dry-run、单次提交对账和付款提醒。 +- 正在进行:T-217 已授权商品重新定位与订单 dry-run。 +- 下一步:依次实现单次提交对账和付款提醒。 ## 当前可运行内容 diff --git a/docs/routes.md b/docs/routes.md index 044a968..4bea7f6 100644 --- a/docs/routes.md +++ b/docs/routes.md @@ -47,6 +47,8 @@ Android 导航名称是逻辑目的地,具体 Compose/Fragment 形式待接入 | `POST /api/v1/tasks/{id}/candidates` | 保存最多 5 个候选、模型判断和本地推荐 | | `POST /api/v1/tasks/{id}/commands/next` | 拉取或重放 Admin 已创建的同一条下单命令 | | `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}/fail` | 提交结构化失败和受控证据 | diff --git a/docs/tasks/T-217.md b/docs/tasks/T-217.md new file mode 100644 index 0000000..e32da0a --- /dev/null +++ b/docs/tasks/T-217.md @@ -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 已验证的 + 图片搜索、规格解析和页面分类能力。