docs: import wiki at afc651f75a3a

ila
2026-08-07 16:36:59 +08:00
parent 0499065623
commit 7423bacaea
+126
@@ -0,0 +1,126 @@
<!-- docs-wiki-sync:docs/tasks/T-251.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
> 同步来源:[`docs/tasks/T-251.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-251.md) · commit `afc651f75a3a`
---
id: T-251
title: 修复候选结果页早判与三轮耗尽后执行不终止
phase: 2
deps:
- T-248
- T-250
status: DONE
created: 2026-07-30
context_ref: 9e399e7
work_branch: null
write_paths:
- docs/tasks/T-251.md
- docs/current-state.md
- android-buyer/app/src/main/**
- android-buyer/app/src/test/**
---
## 问题 / 背景
真机任务在 Admin 显示“执行中”,手机停在拼多多图片搜索结果页。现场状态表明三轮候选
搜索均在候选浏览步骤返回 `TARGET_NOT_READY`,本地 attempt gate 已记录三次失败并禁止
再次启动,但 execution 仍继续每 30 秒 heartbeat,服务端因此一直保持 `RUNNING`。
拼多多结果页的标题和排序栏会早于商品卡片稳定出现。现有代码在页面已被识别为结果页后
立即读取候选卡;首次读取为空时会直接消耗滚动和本轮预算,不能区分“卡片仍在加载”、
“规格不匹配”和“商品身份无法解析”。第三轮耗尽后只记录搜索事件,没有写入既有终态
outbox,是服务端状态不能收敛的直接原因。
## 关联需求与交互
- 功能:F-004、F-005、F-006、F-007
- 用户故事:US-003、US-004、US-006
- 交互:沿用候选搜索进度、安全停止和任务详情页面,不新增采购操作
- 架构/API:复用 T-207 的事件/终态 outbox 和 execution `FAIL` 上报,不修改后端契约
## 行为契约
### 候选卡就绪
- 结果页外壳识别成功后,候选浏览必须在当前步骤 timeout 内对候选卡做最长 10 秒的
有界轮询;卡片出现后才允许开始检查商品或消耗结果滚动次数。
- 轮询期间页面变为验证码、登录、风险控制、订单确认或支付页时,继续按既有安全策略
立即停止,不把阻塞页面当作加载中。
- 10 秒内没有任何可安全读取的候选卡时,本轮返回
`CANDIDATE_RESULTS_NOT_READY`;不得以滚动成功伪装成已取得候选。
- 就绪等待包含在 T-250 的候选浏览 100/45 秒 timeout 内,不增加三轮总预算。
### 失败分类
- 页面已出现候选卡,但规格弹层内颜色或尺码不满足任务硬约束时,使用
`CANDIDATE_SKU_NOT_MATCHED`。
- 候选可打开但无法取得稳定 `goods_id`、规范链接或身份指纹时,使用
`CANDIDATE_IDENTITY_UNRESOLVED`。
- 候选卡存在但在本轮检查/滚动上限内没有可保存候选,且不属于以上单一主因时,使用
`CANDIDATE_TARGET_NOT_FOUND`。
- 页面启动阶段原有 `TARGET_NOT_READY` 保留,不再作为候选采集的笼统结果。
### 三轮耗尽终态
- 每轮仍先通过既有 API 记录搜索尝试、轮次、原因和 `exhausted`。
- 第三轮失败且 execution-scoped attempt gate 已耗尽时,App 必须追加一个
`CANDIDATE_SEARCH_EXHAUSTED` 的 `FAIL` 终态 outbox;`step` 固定为
`CANDIDATE_SEARCH`,`order_submitted=false`,并标为可安全重试。
- 终态 outbox 必须复用当前 execution/generation 和幂等保护;后台同步上传成功后清理
当前任务并停止 execution heartbeat,使 Admin 从 `RUNNING` 收敛到 `FAILED`。
- 升级前已经耗尽三轮的本地状态必须被识别;若还没有终态 outbox,App 自动补写一次,
若终态已经在 outbox 中则只补齐本地 `terminalQueued` 标记,不重复生成终态。
- 三轮尚未耗尽时不得写终态;已增量回传的候选继续保留,现有重新执行和下单信号阻断
规则不变。
## 方案
1. 在 `PinduoduoCandidateAutomation` 增加有界候选卡就绪轮询,并记录本轮是否见到卡片、
规格不匹配和身份解析失败,用确定性优先级返回细分失败码。
2. 为三轮搜索结果增加纯 Kotlin 终态策略,只在 gate 确认耗尽时生成固定的失败载荷;
`MainActivity` 先记录轮次事件,再调用 `ProcurementRepository.failExecution`。
3. 复用 `ProcurementExecutionService` 的 outbox flush 和 STOP 路径,不另建心跳开关,也
不从拼多多页面执行任何下单、返回、支付或全局导航动作。
4. 增加自动化单测覆盖延迟卡片、无卡片、SKU 不匹配、身份缺失,以及非终轮/终轮策略。
## 验收要点
- [x] 候选卡延迟出现时不消耗滚动或搜索轮次,并可继续采集。
- [x] 结果页 10 秒无候选卡时返回 `CANDIDATE_RESULTS_NOT_READY`。
- [x] SKU 不匹配和身份缺失分别返回稳定细分失败码。
- [x] 第一、第二轮失败只记录事件;第三轮耗尽追加唯一 `FAIL` outbox。
- [x] 升级前已经耗尽的 execution 可幂等补写终态。
- [x] 终态上传后复用既有同步 STOP 路径清理任务并停止 heartbeat。
- [x] T-248/T-250 的累计候选、第三轮保底、预算、随机停顿和支付边界不变。
- [x] Android 单测、lint、Debug 和 Release 构建通过。
## 边界
- 不改变后端 execution 状态机、候选 API、Admin 选择授权或安全重试规则。
- 不降低候选卡、商品身份、颜色、尺码、数量、价格和订单确认页校验。
- 不把空白/加载页视作默认候选,不在本任务增加第四轮搜索。
- 不启动真实拼多多采购,不进入订单提交或支付。
## 执行记录
- 2026-07-30:真机执行记录显示三轮均在 `pdd_browse_candidates` 失败并耗尽,
`execution_report` 没有终态,服务仍持续 heartbeat;拼多多前台已显示完整图片搜索
结果,确认同时存在结果卡片早判竞态和耗尽后未上报终态两个问题。
- 2026-07-30:候选自动化增加生产默认 50 次、每次 200 毫秒的卡片就绪轮询;就绪前
不滚动。候选终态按无卡片、SKU 不匹配、身份未解析和未找到目标四类稳定返回,启动
阶段的 `TARGET_NOT_READY` 含义不变。
- 2026-07-30:Main 统一通过 `recordAutomaticSearchFailure` 先记录轮次事件,再在第三轮
耗尽时追加 `CANDIDATE_SEARCH_EXHAUSTED` 的可重试 `FAIL`。attempt gate 持久化
`terminalQueued`,自动轮询还能恢复升级前已经三轮耗尽但没有终态的 execution;
repository 既有 terminal outbox、同步后清理和 STOP 行为保持不变。
- 2026-07-30:新增延迟卡片、空结果、SKU 不匹配、身份解析失败、三轮门禁恢复和终态
策略测试。`testDebugUnitTest`、`lintDebug assembleDebug assembleRelease` 与根
`init.ps1` 全部通过;根入口同时通过 Android Debug/Release 单测和 Go 后端全量测试。
- 2026-07-30:本次未覆盖安装 APK、未启动 Roubao 或拼多多,也未触发真实采购;现场
execution 从 `RUNNING` 收敛到 `FAILED` 需在安装新 APK 并启动 Roubao 后复验。
- 2026-07-30:随后通过 USB 将最新 Debug APK 覆盖安装到 PKG110,`adb install -r`
成功;手机 `base.apk` 与本地产物 SHA-256 均为
`19ac6295e033f02693fa539128d2881a7c91d8924c73c7bb1a3f7e96cd2aadac`,版本仍为
`1.4.16 (21)`。覆盖安装后 Roubao 采购无障碍仍在启用列表中。
- 2026-07-30:为触发升级前耗尽状态恢复,仅启动 Roubao 主界面;没有启动拼多多或
下单。随后 ADB 设备连接消失,且本机后端 `8080` TLS 握手超时,因此尚未确认
`terminalQueued`、终态上传或 Admin 状态收敛。