docs: import wiki at afc651f75a3a

ila
2026-08-07 16:37:05 +08:00
parent 01e6f8e194
commit f29d5ae7ad
+393
@@ -0,0 +1,393 @@
<!-- docs-wiki-sync:docs/tasks/T-263.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
> 同步来源:[`docs/tasks/T-263.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-263.md) · commit `afc651f75a3a`
---
id: T-263
title: 以页面结构判定支付边界并按页面类型收敛文本规则
phase: 2
deps:
- T-262
status: DOING
created: 2026-07-31
context_ref: 0c2e423
work_branch: null
write_paths:
- docs/tasks/T-263.md
- docs/current-state.md
- android-buyer/app/src/main/**
- android-buyer/app/src/test/**
---
## 问题 / 背景
T-262 采集的四个真机样本证明:**现行「见到支付关键词即停」的判定,在误报和漏报两端
同时失效。**
| 页面 | Activity | 节点数 | 非空文本 | WebView | `order_checkout` | paymentMarkers |
| --- | --- | --- | --- | --- | --- | --- |
| 首页 | `MainFrameActivity` | 227 | 48 | 0 | 未命中 | 无 |
| 我的优惠券 | `NewPageActivity` | 457 | 205 | 2(占满屏) | 未命中 | 无 |
| 商品规格弹层 | `NewPageActivity` | 147–153 | 多 | 0 | 未命中 | **2 条 → 误报** |
| **订单确认/去支付** | `NewPageActivity` | **11** | **0** | **1(占满屏)** | 未命中 | **0 → 漏报** |
### 两端同时失效
**误报**:商品规格弹层上的「使用#微信支付,更换先用后付可0元下单」(促销横幅)和
「提交订单 ¥14.9」(底部 CTA)被子串匹配命中。这是**正常购物页面**,自动化必须能在
上面读取规格,却被拦在第一步,导致连续多次真机任务三轮全败。
**漏报**:真实结算页**一条可见文本都没有**,`order_checkout` 也未命中。现行规则
**完全无法识别它**——之所以从未出事,是因为自动化根本走不到那一步。
### 根本原因:混淆了「在支付页上」与「看得见通往支付的按钮」
商品规格弹层上本来就有结算入口按钮,这是正常的。自动化需要停止的是**已经身处支付
页面**,而不是**看见了支付相关字样**。文本关键词无法区分这两者。
### 决定性证据:WebView 本身不阻断无障碍
优惠券页同为占满屏 WebView,其 **205 条文本全部可读**;结算页的 WebView **一条读不
到**。因此「结算页读不到内容」是该页面特有性质(很可能是支付页的无障碍防护),
而非 WebView 的普遍限制。
据此,「**存在占满屏 WebView 且语义文本数近乎为零**」在四个样本中**唯有结算页命中**。
## 关联需求与交互
- 功能:F-005、F-007
- 用户故事:US-004、US-006
- 交互:不新增采购操作
- 架构/API:沿用 T-262 归因诊断,**后端零改动**
## 行为契约
### 一、总原则:只增强,不削弱
新判据是**追加**,不是替换。三条判据任一成立即判 `PAYMENT_BOUNDARY`:
```
结构判据(新) 或 hasCheckoutRoot(保留) 或 文本判据(收敛后保留)
```
`paymentMarkers` 列表内容与顺序**不得删改**。`hasCheckoutRoot` 规则**不得删改**。
### 二、结构判据(新增主判据)
同时满足以下全部条件时判定为支付边界:
1. 前台包名为拼多多;
2. 存在**占满屏**的 `WebView`(宽高各不小于屏幕的 90%);
3. 可见语义文本数**不超过阈值**(常量定义,初值 `2`,覆盖极少量装饰性文本);
4. **连续 `PAYMENT_STRUCTURE_CONFIRMATIONS` 次快照均满足上述条件**,初值 `3`。
第 4 条是硬要求。任何普通页面在 WebView 尚未渲染时都会短暂呈现「WebView + 零文本」,
单次命中即判定会把**页面加载瞬间**误判为结算页。参照既有 `unknownPageLimit`
(20 次 × 200 毫秒)的模式,不得单次命中即停。
连续计数在页面结构不再满足条件时立即清零,不得跨页面累积。
### 三、文本判据按页面类型收敛
**仅当页面未被正面识别为已知安全页面类型时**,文本关键词命中才强制停止。
- 页面已被分类器正面识别为商品详情、规格弹层、搜索结果、图片搜索结果或首页时,
文本命中**不再强制停止**,但必须完整记录(见第四节)。
- 页面为 `UNKNOWN` 或任何未正面识别的类型时,文本命中**维持现有强制停止行为**。
依据:在已确认的正常购物页面上出现支付字样属预期现象(促销横幅、结算入口按钮);
在无法识别的页面上出现支付字样则是危险信号,必须保守处理。
**登录、验证码、风控三类安全停止的判定与优先级完全不变**,不受本节影响。
### 四、可审计:记录被放行的文本命中
新增诊断计数,追加在 T-262 五个键之后:
```
payStructureHit=<0|1> payStructureStreak=<n> payTextSuppressed=<n>
```
| 键 | 含义 |
| --- | --- |
| `payStructureHit` | 本轮是否由结构判据触发过支付边界 |
| `payStructureStreak` | 结构条件连续命中的最大次数 |
| `payTextSuppressed` | 因页面被正面识别而**未强制停止**的文本命中次数 |
`payTextSuppressed` 是本次变更的安全审计口径:**它记录了在旧规则下会停止、新规则下
放行的次数**,用于事后评估变更影响。若真机上该值异常偏高,说明收敛过度,需回头收紧。
沿用既有脱敏口径:只记计数,不记页面文本或商品信息。
### 五、回归测试必须基于真实样本
必须用 T-262 记录的四个真机样本构造测试夹具,逐一断言判定结果:
| 样本 | 期望 |
| --- | --- |
| 首页(227 节点 / 48 文本 / 0 WebView / 无命中) | 不判支付边界 |
| 优惠券页(457 / 205 / 2 WebView / 无命中) | 不判支付边界 |
| 商品规格弹层(147 / 多文本 / 0 WebView / 2 条命中) | **不判支付边界**(本任务修复目标) |
| 结算页(11 / 0 文本 / 1 占满屏 WebView / 无命中) | **判支付边界**(连续 3 次后) |
另需覆盖:结算页结构仅命中 1 次和 2 次时**不得**判定;`hasCheckoutRoot` 单独命中时
仍判定;`UNKNOWN` 页面文本命中时仍判定;登录/验证码/风控优先级不变。
### 六、既有行为不变
- 不改登录、验证码、风控的检测与优先级。
- 不改 `paymentMarkers` 内容与顺序、`containsAny` 实现、`hasCheckoutRoot` 规则。
- 不改 T-262 五个归因键的顺序、名称与取值,也不改 T-254/T-255/T-257/T-260/T-261
既有诊断键。
- 不改候选识别、签名算法、跨轮去重、SKU 硬约束、价格解析、三轮预算或成功判定式。
- 不改下单 dry-run、订单提交、授权价格策略或支付边界之后的处置逻辑。
## 方案
1. `PinduoduoPageClassifier` 增加结构判据计算(占满屏 WebView + 文本数阈值),
阈值与连续次数以具名常量定义。
2. 连续确认状态由调用方持有并传入,分类器保持无副作用的纯函数特性;结构不满足时清零。
3. 文本判据增加「页面已正面识别」的抑制条件,抑制时记录计数而非停止。
4. 三个新诊断键并入 `CandidateSearchDiagnostics`,追加在 T-262 键之后。
5. 用四个真机样本构造测试夹具并逐一断言,另覆盖连续次数边界与各优先级场景。
## 验收要点
- [ ] 四个真机样本的判定结果与上表逐一相符。
- [ ] 结算页结构连续命中不足 3 次时不判定,达到 3 次后判定。
- [ ] 结构条件中断后连续计数清零,不跨页面累积。
- [ ] `hasCheckoutRoot` 单独命中仍判定支付边界。
- [ ] `UNKNOWN` 页面上文本命中仍强制停止。
- [ ] 登录、验证码、风控的判定与优先级未变,有回归测试。
- [ ] `paymentMarkers` 内容与顺序未删改。
- [ ] 三个新诊断键出现且不含页面文本或商品信息。
- [ ] T-254/T-255/T-257/T-260/T-261/T-262 既有诊断键顺序、名称、取值不变。
- [ ] Android Debug/Release 单测、`lintDebug`、`assembleDebug`、`assembleRelease` 全部通过。
- [ ] 真机验证:同一商品重跑采购任务**不再**被拦在 `pdd_open_app`,能进入候选采集;
并从 Admin 读取 `payTextSuppressed` 确认放行次数在合理范围。
## 边界
- **不删除任何现有判据**:`paymentMarkers`、`hasCheckoutRoot` 全部保留。
- 不改登录、验证码、风控的检测逻辑与优先级。
- 不改支付边界判定成立**之后**的处置逻辑(立即停止、不清理、不点击)。
- 不改 T-261 清理逻辑与 `isSafeForCleanup`。
- 不改 T-256 提交门槛、T-258 后端校验、T-259 安全停止补报、T-260 节奏配置。
- 不改候选识别、价格解析或下单相关任何逻辑。
- 不改后端代码、数据库、API 契约或 Admin 模板。
- 不处理订单列表页可能被自身规则阻挡的问题(见 T-262 附带发现),另行开任务。
## 已知局限
**真实结算页样本只有一个。** 结构判据基于该单一样本设计,可能存在盲区:
- 若某些拼多多版本或路径使用**原生结算页**(有可读文本),结构判据不会命中。此时
仍依赖 `hasCheckoutRoot` 与 `UNKNOWN` 页面上的文本判据兜底。
- `order_checkout` 这个 resourceId 在本次采集的结算页上**并未命中**,其在当前版本
上的有效性存疑,但按「只增强不削弱」原则仍予保留。
因此本任务**不宣称覆盖全部支付页面形态**,只解决已证实的误报并补上已证实的漏报。
后续若发现新的结算页形态,应继续追加判据而非放松现有判据。
## 执行记录
开工后记录修改、命令、结果、环境、决策和 blocker。
### 2026-07-31 实现与自动化验证
环境:WSL 无 Android SDK/JDK,全部构建与单测经
`/mnt/c/Windows/System32/cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat <目标> --no-daemon --console=plain"`
在 Windows 侧执行;未运行 `init.ps1`。
#### 改动文件
- `android-buyer/app/src/main/java/com/roubao/autopilot/workflow/WorkflowModels.kt`:
`PaymentBoundaryAttribution` 追加三个字段——`structureHit`(本次
`PAYMENT_BOUNDARY` 是否由结构判据触发)、`structureConfirmations`(产出本次
归因时的结构连续确认次数)、`textSuppressedCount`(本次判定时因页面被正面
识别而放行的命中文本条数);均为末尾追加、默认值 `false`/`0`/`0`,`init` 增
加对应非负校验。既有五个字段与其含义/顺序未动。
- `android-buyer/app/src/main/java/com/roubao/autopilot/pinduoduo/PinduoduoPageClassifier.kt`:
- 新增具名常量 `PAYMENT_STRUCTURE_TEXT_THRESHOLD = 2`、
`PAYMENT_STRUCTURE_CONFIRMATIONS = 3`、私有 `FULL_SCREEN_WEBVIEW_RATIO = 0.9`。
- `classify()` 追加末尾可选参数 `priorStructureConfirmations: Int = 0`(调用方
持有并传入的连续确认状态,分类器本身仍是无副作用纯函数);新增私有函数
`hasFullScreenWebViewWithFewTexts()`:屏幕范围取当前可见元素包围盒(根容器
通常等于屏幕大小,避免新增屏幕尺寸入参改动既有调用方签名),WebView 宽高
各不小于该包围盒的 90% 且可见非空文本数不超过阈值时判定单次快照满足结构
条件;`structureConfirmations = if (met) prior+1 else 0`,
`structureHit = structureConfirmations >= PAYMENT_STRUCTURE_CONFIRMATIONS`。
- 将原来紧跟在 `visibleTexts` 之后计算的 `page` 判定块整体上移到
`safetyStop` 判定之前(`page` 的计算只依赖 `hasCheckoutRoot`/`normalized`/
`visibleElements` 等既有局部变量,与 `safetyStop` 无关,纯粹重排不改变
`page` 本身的判定逻辑),使文本判据可以引用已确定的 `page` 值。
- `safetyStop` 表达式改为
`structureHit || hasCheckoutRoot || (textMarkerHit && !textMarkerSuppressed)`,
其中 `textMarkerHit = visibleTexts.containsAny(paymentMarkers)`(`containsAny`
实现、`paymentMarkers` 内容与顺序、`hasCheckoutRoot` 规则逐字节未动);
`textMarkerSuppressed = textMarkerHit && page in {PRODUCT_DETAIL,
SPECIFICATION_PANEL, SEARCH_RESULTS, IMAGE_SEARCH_RESULTS, HOME}`。登录/
验证码/风控三类判定仍在同一个 `when` 的前三个分支,优先级和触发逻辑未动;
结构/文本判据只影响 `LoginBlocker.NONE/UNKNOWN` 分支内部。
- `PinduoduoUiSnapshot` 追加两个字段:`structureConfirmations: Int = 0`(本
次调用后的连续确认计数,供调用方在下一次调用时回传)、
`textMarkerSuppressed: Boolean = false`(本次快照是否发生文本命中放行,
与 `PAYMENT_BOUNDARY` 是否触发无关,始终计算,供调用方做会话级审计)。
- `computePaymentBoundaryAttribution()` 追加三个入参并透传进
`PaymentBoundaryAttribution` 的三个新字段,其余标记扫描逻辑未动。
- `android-buyer/app/src/main/java/com/roubao/autopilot/accessibility/BuyerAccessibilityService.kt`:
新增私有实例字段 `pinduoduoStructureConfirmations`;`classifyPinduoduoRoot()`
(全仓库唯一的真机快照来源,其余 `snapshotPinduoduoUi()`、
`clickPinduoduoSearchEntry()` 等数十处调用均经此函数)将该字段作为
`priorStructureConfirmations` 传入 `classify()`,再用返回快照的
`structureConfirmations` 回写该字段,从而让结构判据的连续确认覆盖所有轮询
循环与一次性动作调用;`snapshotPinduoduoUi()` 在前台包名不是拼多多的分支
显式清零该字段,确保离开拼多多前台或结构条件中断时立即清零、不跨页面累积。
- `android-buyer/app/src/main/java/com/roubao/autopilot/pinduoduo/PinduoduoCandidateAutomation.kt`:
新增两个会话内累计字段 `maxStructureConfirmations`、`textSuppressedCount`
(`reset()` 一并清零);新增私有 `observeSnapshot()` 作为本类唯一的快照读取
入口(原有约 11 处 `driver.snapshot()` 全部替换为该函数),每次快照后更新
两个累计字段的最大值/计数,不改变 `driver.snapshot()` 的返回值或任何既有
判定/重试/清理行为;`diagnostics()` 追加
`payStructureStreak = maxStructureConfirmations`、
`payTextSuppressed = textSuppressedCount`。
- `android-buyer/app/src/main/java/com/roubao/autopilot/procurement/CandidateSearchDiagnostics.kt`:
末尾追加 `payStructureHit`/`payStructureStreak`/`payTextSuppressed` 三个字段
(默认值 `0`)及对应 `require` 校验;`withPaymentBoundaryAttribution()` 追加
设置 `payStructureHit`(直接取自本次归因),`payStructureStreak` 改为
`maxOf(既有值, 归因中的 structureConfirmations)`(取大合并,不覆盖候选自动化
自身轮询中已观察到的更高计数);`payTextSuppressed` **不**在此方法中设置,
完全由调用方(`PinduoduoCandidateAutomation.diagnostics()`)持续累积后写入,
避免被本方法在未触发 `PAYMENT_BOUNDARY` 的常见场景下清零或覆盖(原因见下方
「偏离与决策」第 2 条)。既有五个键与 `auditSuffix()` 中它们之前的所有键均
逐字节未动,三个新键追加在 `payExact=` 之后。
- 测试:
- `PinduoduoPageClassifierTest.kt` 新增 9 个用例:基于 T-262 四个真机样本的
首页/优惠券页/规格弹层/结算页判定(规格弹层用例直接复现 T-262 记录的误报
文案,断言不再判定且 `paymentBoundaryAttribution` 保持默认值)、结算页
结构连续命中 1/2 次不判定、第 3 次判定、结构中断后清零再重新计数、
`hasCheckoutRoot` 单独命中且结构计数为 0 时仍判定、`UNKNOWN` 页面文本命中
仍强制停止、登录/验证码/风控在已有结构连续计数时优先级不变。
- `CandidateSearchDiagnosticsTest.kt` 更新 6 处既有精确匹配断言以包含三个新
增尾部默认键,新增 4 个用例覆盖非法计数拒绝、结构命中归因追加、
`payStructureStreak` 取大合并两种方向、`payTextSuppressed` 不被
`withPaymentBoundaryAttribution` 清零。
- `PinduoduoCandidateAutomationTest.kt` 的 `FakeCandidateDriver` 新增
`structureConfirmations`/`textMarkerSuppressed` 两个可选构造参数并透传进
快照;新增一个用例验证候选自动化经 `observeSnapshot()` 累积的
`payStructureStreak`/`payTextSuppressed` 出现在 `diagnostics()` 中,且
`reset()` 后归零。
#### 命令与结果
```
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat testDebugUnitTest --no-daemon --console=plain"
BUILD SUCCESSFUL;Debug 单测 313 项,0 失败、0 错误(较 T-262 的 299 项新增 14 项)。
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat testReleaseUnitTest --no-daemon --console=plain"
BUILD SUCCESSFUL;Release 单测 313 项,0 失败、0 错误。
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat lintDebug --no-daemon --console=plain"
BUILD SUCCESSFUL。
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat assembleDebug --no-daemon --console=plain"
BUILD SUCCESSFUL。
cmd.exe /c "cd /d D:\chengma\cmroubao\android-buyer && gradlew.bat assembleRelease --no-daemon --console=plain"
BUILD SUCCESSFUL。
```
五个目标全部通过。
#### 四个真机样本的断言结果(单测,非真机)
| 样本 | 断言 | 结果 |
| --- | --- | --- |
| 首页(无 WebView、46+2 条文本、无标记命中) | `page=HOME`,`safetyStopReason=null`,`structureConfirmations=0` | 通过 |
| 优惠券页(2 个占满屏 WebView、205 条文本、无标记命中) | `safetyStopReason=null`,`structureConfirmations=0`(文本数量守住占满屏 WebView 不误判) | 通过 |
| 商品规格弹层(0 WebView、命中“提交订单”“微信支付”2 条) | `page=SPECIFICATION_PANEL`,`safetyStopReason=null`(**本任务修复目标**),`paymentBoundaryAttribution` 为默认值,`textMarkerSuppressed=true` | 通过 |
| 结算页(1 个占满屏 WebView、0 条文本) | 连续 1/2 次快照 `safetyStopReason=null`;第 3 次 `safetyStopReason=PAYMENT_BOUNDARY`,`paymentBoundaryAttribution.structureHit=true`、`structureConfirmations=3`、`checkoutRootHit=false`、`markerIndex=-1` | 通过 |
另覆盖:结构条件中断后连续计数清零、`hasCheckoutRoot` 单独命中仍判定、
`UNKNOWN` 页面文本命中仍强制停止、登录/验证码/风控在已有结构连续计数时优先级
不变——均通过。
#### 偏离任务文档之处及理由
1. **结构判据的“屏幕范围”不是新增入参,而是取当前可见元素包围盒。** 任务文档
要求“宽高各不小于屏幕的 90%”,但 `classify()` 现有签名不含屏幕尺寸,
`PinduoduoUiElement` 也不含屏幕尺寸字段。改为在可见元素集合内取
`boundsLeft/Top/Right/Bottom` 的包围盒作为屏幕范围的近似——真机根容器
(`FrameLayout`)的 bounds 本就等于屏幕大小(T-262 结算页 dump 亦如此:
`FrameLayout bounds=[0,0][1080,2376]`),且无障碍节点树采集时根节点必然
被收录(`BuyerAccessibilityService.collectNodes()` 用 BFS 且根节点最先入
队)。这样既满足“宽高各不小于屏幕的 90%”的判定意图,又不改变 `classify()`
/ `PinduoduoUiElement` 对既有调用方的签名,风险更低。
2. **`payTextSuppressed` 的口径改为调用方持续累积,而非像 T-262 五个键那样只
在 `PAYMENT_BOUNDARY` 触发时产出。** 这是本任务中分量最重的一个偏离,
理由:文本判据被放行(`textMarkerSuppressed=true`)几乎总是意味着
`PAYMENT_BOUNDARY` **没有**触发(正是本任务要修复的常见场景——自动化在
规格弹层等页面继续正常浏览)。若沿用 T-262 的“只在触发时产出”模式,
`payTextSuppressed` 在绝大多数真实场景下都会读到默认值 `0`,验收要点
“若真机上该值异常偏高,说明收敛过度”将无法被观察到,审计口径名存实亡。
经推演证实:`hasCheckoutRoot` 恒定强制 `page=ORDER_CONFIRMATION`(不在可
抑制页面集合内),结构判据要求非空文本数 `<=2` 而可抑制的五类页面
(HOME/SEARCH_RESULTS/IMAGE_SEARCH_RESULTS/PRODUCT_DETAIL/
SPECIFICATION_PANEL)现有识别规则均需要 `>=2` 个独立于标记文本之外的
语义文本,二者在现有启发式下几乎互斥——即“归因触发时刻”与“文本判据被
放行”事实上不会同时发生。因此改为:`PinduoduoUiSnapshot` 新增
`textMarkerSuppressed`(与 `structureConfirmations` 一样,每次快照都计算,
不依赖 `PAYMENT_BOUNDARY` 是否触发);`PinduoduoCandidateAutomation` 通过
新增的唯一快照入口 `observeSnapshot()` 在自身轮询过程中持续累积
`textSuppressedCount`(会话内计数,`reset()` 清零),随
`diagnostics()` 一并暴露;`CandidateSearchDiagnostics.withPaymentBoundaryAttribution()`
不再覆盖或清零该键。
**已知局限**:该累积目前只覆盖 `pdd_browse_candidates` 步骤(候选浏览,
规格弹层重复出现的主要场所),不覆盖 `PinduoduoSearchAutomation`/
`PinduoduoImageSearchAutomation` 所在的 `pdd_open_app`/`pdd_enter_query`/
`pdd_submit_query` 等更早步骤(这两个类当前没有诊断累积基础设施,为其新增
等价机制需要更大改动面)。原故障恰好是“在 `pdd_open_app` 单次命中即误判”,
修复后该步骤会正常通过并进入候选浏览,绝大多数后续文本放行会发生在
`pdd_browse_candidates` 内并被正确计数;但如果放行只发生在更早步骤且之后
再未出现,`payTextSuppressed` 会读到偏低的值。这是本次为控制改动面而接受
的范围边界,如需覆盖全流程应另开任务为
`PinduoduoSearchAutomation`/`PinduoduoImageSearchAutomation` 补充等价的
累积与暴露机制。
3. **`payStructureStreak` 由 `withPaymentBoundaryAttribution()` 与
`PinduoduoCandidateAutomation.diagnostics()` 共同产出,取二者较大值,而非
单一来源。** 归因中的 `structureConfirmations` 只反映触发 `PAYMENT_BOUNDARY`
那一刻的读数(可能来自 `pdd_open_app` 阶段,`PinduoduoCandidateAutomation`
完全不知情);候选自动化自身累积的 `maxStructureConfirmations` 只覆盖它
自己轮询到的快照。二者是互补而非互斥的信息源,取大合并可以同时覆盖“结构
判据在更早步骤触发”与“候选浏览阶段观察到接近阈值但未触发”两类场景,且不
丢失任何一方已经观察到的信息。
4. **文本判据可抑制的页面集合按任务文档字面五类严格取值**
(`PRODUCT_DETAIL`、`SPECIFICATION_PANEL`、`SEARCH_RESULTS`、
`IMAGE_SEARCH_RESULTS`、`HOME`),未包含 `SEARCH_RESULTS_OTHER_QUERY`、
`IMAGE_SEARCH`、`IMAGE_SEARCH_RETRY_DIALOG`、`ORDER_LIST` 等其余非
`UNKNOWN` 类型。任务文档原文列举的是“商品详情、规格弹层、搜索结果、图片
搜索结果或首页”,理解为这五个具名类型;其余非正面列举的类型按“任何未正面
识别的页面”对待,维持强制停止,偏保守方向,符合“遇到不确定页面停止”的
业务硬边界。
5. **`PinduoduoOrderDryRunAutomation`/`AndroidPinduoduoOrderDryRunDriver`
(T-217 订单 dry-run)一个字节未改**,但由于二者最终都经同一个
`BuyerAccessibilityService.classifyPinduoduoRoot()` 获取快照,新增的结构
判据在共享的 `classify()` 层面对它们同样生效:若 dry-run 流程在
`expectingOrderConfirmation` 变为 `true` 之前意外连续 3 次观察到“占满屏
WebView + 语义文本 ≤ 2”的结构(现有 `PinduoduoCollapsedCheckoutPolicy`
本就是为识别这一结构而设计,只是原先只在该布尔量被显式置位后才转换为
`PAYMENT_BOUNDARY`),`classify()` 会独立触发 `PAYMENT_BOUNDARY`,比
dry-run 驱动原有的门控逻辑更早/更广地停止。这是共享判据修复误报/漏报后
的必然连带效应,方向上只会让安全停止更早/更确定,不会绕过或削弱既有
dry-run 停止点,且不修改 dry-run 文件本身的任何代码,因此判断为符合
“只增强不削弱”原则、未违反“不改下单 dry-run”的边界,但作为可能影响未来
dry-run 行为的连带效应在此明确记录,供后续如遇异常提前停止时排查参考。
#### 未完成项(需人工真机验证,state 保持 DOING)
- 真机复现同一商品重跑采购任务,确认不再被拦在 `pdd_open_app` 且能进入候选
采集。
- 从 Admin 读取 `payTextSuppressed`,结合上方「偏离与决策」第 2 条的范围边界
确认放行次数在合理范围;若异常偏高需回头收紧文本判据的可抑制页面集合或
结构判据阈值。
- 若条件允许,在真实结算页上验证连续 3 次快照后确实触发 `PAYMENT_BOUNDARY`
且 `payStructureHit=1`。