From 325d089273d95f1e937b1e8fc2a485f620a01c00 Mon Sep 17 00:00:00 2001 From: ila Date: Fri, 7 Aug 2026 16:37:12 +0800 Subject: [PATCH] docs: import wiki at afc651f75a3a --- T-274.-.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 T-274.-.md diff --git a/T-274.-.md b/T-274.-.md new file mode 100644 index 0000000..9472cbf --- /dev/null +++ b/T-274.-.md @@ -0,0 +1,92 @@ + +> 同步来源:[`docs/tasks/T-274.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-274.md) · commit `afc651f75a3a` + +--- +id: T-274 +title: 修复重复候选签名导致商品节点无法打开 +phase: 2 +deps: + - T-255 + - T-271 +status: DONE +created: 2026-08-01 +context_ref: c8c4914 +work_branch: null +write_paths: + - docs/tasks/T-274.md + - docs/current-state.md + - docs/04-architecture.md + - docs/05-coding-rules.md + - android-buyer/app/src/main/** + - android-buyer/app/src/test/** +--- + +## 问题 / 背景 + +真机任务三轮都在 `pdd_browse_candidates` 首次打开候选时返回 +`TRANSIENT_AUTOMATION`。每轮诊断均为 `cards=4 distinct=2 attempts=1 scrolls=0`,且 +规格、身份和 VLM 计数全部为 0。 + +代码确认读取阶段按标题语义生成 `signature`,打开阶段重新扫描后使用 +`filter(signature).singleOrNull()` 选择节点。4 张卡片只有 2 个签名时,重复签名使 +`singleOrNull()` 返回空,未执行 `ACTION_CLICK`。此外自动化用相同 signature 记录已尝试 +候选,即使打开成功,同标题但不同商品的后续卡片也会被跳过。 + +## 行为契约 + +1. 候选读取除语义 signature 外,为每张已接受卡片生成仅限当前结果视口使用的 locator: + recycler child index、接受顺序、容器 bounds、点击目标相对 path,以及对完整语义、节点 + class/resource/path 的 SHA-256 指纹。locator 不包含原始文本,也不进入证据、outbox、 + Admin 或持久化进度。 +2. 打开候选时重新读取安全结果页,先同时复核 signature、完整内容指纹和原容器位置; + 原位置变化时只允许通过唯一内容指纹和高比例 bounds 重叠重定位。零命中或多命中均 + 失败关闭,不能凭标题任取第一个节点。 +3. bounds 只用于验证同一节点,动作仍对唯一、可见、enabled 的无障碍节点执行一次 + `ACTION_CLICK`;禁止坐标点击、VLM 选择或保留 `AccessibilityNodeInfo` 跨快照引用。 +4. 本轮尝试去重使用不含位置的完整卡片 attempt key;语义 signature 继续用于观测和证据。 + 同标题但完整卡片不同的商品可分别尝试,同一完整卡片在滚动后不能反复尝试。 +5. 打开结果必须区分 `PAGE_CHANGED/LOCATOR_MISSING/LOCATOR_NOT_FOUND/LOCATOR_AMBIGUOUS/ + ACTION_REJECTED`;重复 signature 被 locator 成功消歧单独计数。所有结果只映射既有安全 + 失败边界,不把失败伪装成 SKU、身份或 VLM 错误。 +6. 初次候选就绪至少需要连续两次非空读取产生相同 locator stability keys,避免使用仍在 + 渲染的临时结构;等待受既有 `candidateReadyPollLimit` 和轮次总预算约束。 + +## 方案 + +1. 扩展 `PinduoduoCandidateCard` 的可空临时 locator,并增加纯 Kotlin locator 生成、 + 重定位和 bounds 重叠策略。 +2. `BuyerAccessibilityService.candidateNodes()` 在同次树遍历中计算容器/点击目标 path 和 + 指纹;打开接口返回结构化 outcome,不再以 signature 的 `singleOrNull()` 点击。 +3. `PinduoduoCandidateAutomation` 使用卡片 attempt key 去重、等待两帧稳定,并将结构化 + outcome 聚合到追加诊断键,外层失败码继续保持兼容。 +4. 用纯策略测试覆盖 `cards=4 distinct=2`、位置稳定、位置变化唯一重定位、重复定位、 + 失配和 bounds 不重叠;用自动化 fake 覆盖每种打开 outcome 和同标题不同卡片。 + +## 验收要点 + +- [x] 4 张卡片只有 2 个语义 signature 时,指定卡片可由 locator 唯一打开。 +- [x] 同标题但完整语义不同的两张卡片可在同一轮分别尝试,不因 signature 去重跳过。 +- [x] locator 缺失、失配、多命中、页面变化和 `ACTION_CLICK=false` 分别形成诊断计数。 +- [x] 页面重排或指纹变化时不按标题任点,也不使用坐标、VLM 或旧节点引用兜底。 +- [x] 旧 fake/fixture 可通过显式 locator 构造迁移,候选证据/API/数据库契约不变。 +- [x] Android Debug/Release 单测、lint 和两种 APK 构建通过。 + +## 边界 + +- 不重新启用图片加载硬过滤,不修改八项候选容器判定。 +- 不改变三轮数量、浏览预算、SKU/价格硬约束、goods_id 或分享链接流程。 +- 不修复订单 dry-run 的独立候选选择契约,不增加 VLM 调用或下单权限。 + +## 执行记录 + +- 2026-08-01:根据真机三轮完全相同的 `cards=4 distinct=2 attempts=1` 诊断创建任务, + 状态 `TODO`。直接失败点为 `openCandidate=false`;重复 signature 已被诊断证明,现有代码 + 尚不能区分重复签名、节点不存在和 `ACTION_CLICK=false`。 +- 2026-08-01:完成临时 locator、纯 Kotlin 重定位策略、两帧稳定读取和完整内容 attempt + key。无障碍服务重新扫描后只点击唯一节点;重复 signature 可成功消歧,位置变化仅允许 + 唯一指纹加 80% bounds 重叠。外层失败码保持兼容,事件追加 `openPageChanged`、 + `openLocatorMissing`、`openLocatorNotFound`、`openLocatorAmbiguous`、 + `openActionRejected` 和 `openDisambiguated` 六个非秘密计数。 +- 2026-08-01:Debug/Release 各 457 项单测、`lintDebug`、`assembleDebug`、 + `assembleRelease` 全部通过,状态改为 `DONE`。未修改容器八项判定、三轮预算、VLM、 + evidence/API/数据库或订单 dry-run;真实失败任务仍需安装本 APK 后复跑确认真机计数。