From b0e16fd5d37bda300539ead89df6c339fc740ec1 Mon Sep 17 00:00:00 2001 From: ila Date: Fri, 7 Aug 2026 16:36:59 +0800 Subject: [PATCH] docs: import wiki at afc651f75a3a --- T-252.-.md | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 T-252.-.md diff --git a/T-252.-.md b/T-252.-.md new file mode 100644 index 0000000..8d73e7a --- /dev/null +++ b/T-252.-.md @@ -0,0 +1,156 @@ + +> 同步来源:[`docs/tasks/T-252.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-252.md) · commit `afc651f75a3a` + +--- +id: T-252 +title: 修复 Android 16 商品链接读取并回传身份未解析候选 +phase: 2 +deps: + - T-246 + - T-251 +status: DONE +created: 2026-07-30 +context_ref: 920886c +work_branch: null +write_paths: + - docs/tasks/T-252.md + - docs/current-state.md + - android-buyer/app/src/main/** + - android-buyer/app/src/test/** + - android-buyer/app/src/androidTest/** + - android-buyer/app/build.gradle.kts +--- + +## 问题 / 背景 + +任务 `09240616-5d87-4ab1-a37a-57949725f8b2` 最终以 +`CANDIDATE_IDENTITY_UNRESOLVED` 失败。人工在同一张参考图的拼多多结果中可以找到 +符合黑色 L 码的商品,但 Admin 没有候选。 + +真机 Android 16/ColorOS 系统日志证明:拼多多在详情页成功写入“复制链接”后, +系统拒绝后台 Roubao 读取剪贴板,理由为应用不在前台。当前候选流程只有取得唯一 +`goods_id` 和规范 PDD URL 后才保存证据,因此已经打开详情、核验规格的商品也被直接 +丢弃,Admin 无法看到标题、规格、价格、图片或失败阶段。重复执行三轮图片搜索不能 +改变系统剪贴板权限,只增加时间成本。 + +本任务修复商品身份主链路,并保留 fail-closed 边界:只有身份完整的候选可进入 Admin +选择和后续下单;身份未解析的匹配商品只作为不可下单观察回传。 + +## 现场证据 + +- PDD 8.17 详情页“分享”以唯一可点击语义暴露,分享面板“复制链接”也可唯一定位, + 因此不是按钮文案或控件改版。 +- 系统日志先记录拼多多前台写剪贴板成功,约三秒后记录 + `Denying clipboard access to com.roubao.autopilot, application is not in focus`。 +- 当前 Activity/Window 和详情页无障碍文本不暴露 `goods_id`;页面 OCR 也没有权威 + 商品编号,不能用 VLM 或视觉猜测替代。 +- 本地 attempt gate 为三轮、`terminal_queued=true`,候选证据缓存为空,说明候选在 + Android 保存/回传前被拒绝;后端没有丢失已上传数据。 +- 后端候选契约允许 `goods_id/product_url` 同时为空并只保存 observation;创建下单 + 授权时会拒绝没有 `candidate_platform_products` 的候选,可复用该安全边界。 + +## 关联需求与交互 + +- 功能:F-004、F-005、F-006、F-007 +- 用户故事:US-003、US-004、US-006 +- Android:图片搜索、规格硬匹配、候选证据、自动轮次和终态 outbox +- Admin:沿用候选观察展示;身份未解析项显示为空链接且任务最终为 `FAILED` +- 架构/API:复用现有候选批次允许空平台身份的契约,不新增后端迁移或第三方代理 + +## 行为契约 + +### 前台剪贴板中继 + +- 拼多多点击“复制链接”成功后,Roubao 启动内部、`exported=false`、不进入最近任务的 + 透明中继 Activity;只有该 Activity 获得窗口焦点后才能读取剪贴板。 +- 中继请求必须绑定一次性 request ID 和本次复制开始时间,最长等待 3 秒;剪贴板必须 + 是复制开始后写入、只有一项、长度不超过 2048,并且恰好包含一个允许的 HTTPS PDD + URL。 +- 中继只在内存中返回文本,不写日志、不持久化、不上传原始分享文案;完成、超时或取消 + 后立即关闭并回到原拼多多详情页。 +- 中继启动失败、没有获得焦点或焦点后仍不能读取时返回稳定的剪贴板身份失败,不回退到 + 后台直接读取。 + +### 身份解析结果 + +- 商品身份解析返回显式结果,而不是以空 capture 混合所有失败: + `SHARE_ACTION_UNAVAILABLE`、`COPY_LINK_UNAVAILABLE`、 + `CLIPBOARD_RELAY_UNAVAILABLE`、`CLIPBOARD_ACCESS_DENIED`、 + `SHARED_LINK_FORMAT_INVALID`、`SHARED_LINK_NETWORK_FAILED`、 + `IDENTITY_CONFLICT`。 +- 详情文本和最多两个视口 OCR 仍优先;只有两者没有唯一结果时才使用分享链接。 +- 短链仍只允许 `p.pinduoduo.com` 和 `mobile.yangkeduo.com`,最多三次受控重定向、 + 6 秒总网络预算和 64 KiB 响应,不放宽 SSRF 边界。 +- VLM 不得生成或猜测 `goods_id`。 + +### 身份未解析观察 + +- 规格硬匹配已经通过但身份解析失败时,保存标题、规格摘要、价格、card/detail 指纹、 + 详情截图、规格截图、搜索轮次/排名和身份失败码;`goods_id/product_url` 同时为空。 +- 每个 execution 最多保留两个身份未解析观察,跨轮持久保存;它们不计入“两个可采购 + 候选”目标,不触发搜索成功,也不占用 resolved goods ID 去重。 +- 身份完整候选继续按原逻辑增量回传。若最终失败,App 必须在 `FAIL` 终态前上传 + “已解析候选前缀 + 最多两个未解析观察”的 partial candidate snapshot;未解析项使用 + 无 match metadata 的诊断观察形式,批次最多五项。 +- 任务最终保持 `FAILED`,Admin 可查看观察和证据但不能选择或创建下单授权。若搜索最终 + 成功,完成批次只包含身份完整候选,不把诊断项混入可选集合。 + +### 重试与终态 + +- `CLIPBOARD_RELAY_UNAVAILABLE` 和 `CLIPBOARD_ACCESS_DENIED` 是设备级系统故障; + 首轮确认后立即写 `CANDIDATE_IDENTITY_INFRASTRUCTURE_FAILED` 终态,不重复三轮图片 + 搜索。 +- 分享按钮缺失、复制按钮缺失、短链格式/网络失败或单个商品身份冲突仍可在当前轮继续 + 检查其他商品,并受 T-250/T-251 原有轮次和时间预算限制。 +- 终态顺序固定为 evidence、partial candidates、失败事件、`FAIL`;上传成功后停止 + execution heartbeat。任何路径都不得进入购物车、订单提交或支付。 + +## 方案 + +1. 新增透明 `PinduoduoClipboardRelayActivity` 和进程内 broker,以窗口焦点、时间戳、 + 单项剪贴板和 PDD URL allowlist 验证复制结果。 +2. 将候选 driver 的身份解析改为 sealed result,并让短链 resolver 区分格式、网络和 + 无法解析;workflow 将原因映射为稳定失败码。 +3. 扩展候选证据和 progress store 支持空平台身份及身份失败码,resolved/diagnostic + 分开计数、去重和排序;终态前才附加 diagnostic observation。 +4. 扩展 terminal policy,使确定性的剪贴板基础设施故障可以在未耗尽三轮时关闭 gate + 并排队终态。 +5. 增加纯 Kotlin 单测、Android 构建验证,并在 PKG110 上验证复制、前台读取、短链 + 解析、返回详情页和 Admin 候选结果。 + +## 验收要点 + +- [x] Android 16/ColorOS 上前台中继可读取新复制的 PDD 白名单链接,不再使用后台 + accessibility service 读取剪贴板。 +- [x] 中继读取结果交给受限短链解析器生成唯一 `goods_id` 和规范商品 URL,完成后 + 自动关闭并恢复前一个 Activity。 +- [x] 两个身份完整候选仍按 resolved 数量增量回传并进入 Admin 等待确认。 +- [x] 身份失败时最多保留两个不可下单观察及其标题、SKU、价格和两张证据图,并在 + 终态前作为无采集元数据的 partial snapshot 回传。 +- [x] 未解析观察不计入成功数量,空平台身份沿用后端禁止创建下单授权的边界。 +- [x] 确定性剪贴板基础设施故障首轮进入终态,不重复三轮图片搜索。 +- [x] 原始剪贴板文本不落盘、不入日志、不上传。 +- [x] Android 单测、lint、Debug/Release 构建、API 36 真机测试和根 `init.ps1` + 通过。 + +## 边界 + +- 不让后台、VLM 或 Admin 猜测商品 ID,不以标题/图片相似度替代平台身份。 +- 不放宽 PDD host、URL、重定向、响应大小和商品 ID 格式限制。 +- 不自动接受身份未解析观察,不创建授权命令,不进入下单或支付。 +- 不改变 T-249 的重试安全边界和 T-251 的三轮耗尽终态。 + +## 执行记录 + +- 2026-07-30:完成真机只读诊断;打开当前结果第一项、确认“分享/复制链接”语义并 + 手工复制一次后恢复图片搜索结果页,未选择 SKU、加入购物车、提交订单或支付。 +- 2026-07-30:新增 `exported=false` 透明前台中继和一次性 broker;用剪贴板时间戳、 + 单项、2048 字符和 PDD HTTPS allowlist 拒绝陈旧或外部内容,删除 accessibility + service 的后台剪贴板入口。 +- 2026-07-30:身份解析改为七类显式结果;候选进度 schema v2 将 resolved 与最多 + 两个 diagnostic 分开计数、去重和持久化,终态前把 resolved 前缀和 diagnostic + 统一转为不可推荐的无采集元数据快照,再排队失败事件和 `FAIL`。 +- 2026-07-30:Android 单测 232 项通过,lint、Debug/Release 构建通过;PKG110 + Android 16/API 36 仪器测试 `1/1` 通过,证明焦点 Activity 能读取新写入 PDD URL + 并返回前一 Activity。安装 `1.4.17 (22)` 后已复核采购无障碍、悬浮窗、通知和 + Shizuku 授权处于启用状态;未进入购物车、提交或支付。