docs: import wiki at afc651f75a3a

ila
2026-08-07 16:37:07 +08:00
parent bc0202ab83
commit 23abc5f797
+205
@@ -0,0 +1,205 @@
<!-- docs-wiki-sync:docs/tasks/T-265.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
> 同步来源:[`docs/tasks/T-265.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-265.md) · commit `afc651f75a3a`
---
id: T-265
title: 恢复购买入口作为唯一规格入口并依赖订单确认页识别安全回退
phase: 2
deps:
- T-264
status: DOING
created: 2026-07-31
context_ref: d856c0b
work_branch: null
write_paths:
- docs/tasks/T-265.md
- docs/current-state.md
- android-buyer/app/src/main/**
- android-buyer/app/src/test/**
---
## 问题 / 背景
T-264 移除「免拼购买」兜底点击后,真机三轮**每一个候选都无法打开规格面板**:
| 轮次 | attempts | specOpenFailed | specEntryAbsent | reason |
| --- | --- | --- | --- | --- |
| 1 | 2 | 2 | **2** | `CANDIDATE_SPECIFICATION_UNAVAILABLE` |
| 2 | 4 | 4 | **4** | `CANDIDATE_SPECIFICATION_UNAVAILABLE` |
| 3 | 3 | 3 | **3** | `CANDIDATE_SPECIFICATION_UNAVAILABLE` |
`specEntryAbsent` 恒等于 `attempts`,即**覆盖损失 100%**。
### 真机取证:详情页根本没有独立的规格入口
对一个真实服装商品详情页 dump(190 节点),**全部可见文本中没有任何**
「选择规格」「请选择规格」「颜色分类」「颜色款式」「选择颜色」「选择尺码」,也没有
以「请选择:」或「已选:」开头的文本。整页只有标题、价格、优惠券、拼单信息、店铺、
分享/收藏/客服,以及底部的:
```
'已优惠6元 免拼购买'
```
**当前拼多多版本的商品详情页不提供独立的规格入口——「免拼购买」就是唯一入口**,点击
后才弹出规格面板供选择颜色和尺码。
### T-264 的判断错误
T-264 依据纸巾案例(点「免拼购买」直接进入订单确认页)推断该兜底本身危险,予以移除。
但真实情况是:
| 商品 | 点「免拼购买」后 |
| --- | --- |
| 正常服装(有颜色/尺码) | 弹出规格面板,流程正常 |
| 纸巾(无可选规格)+ 促销 | 直接进入订单确认页 |
**问题不在于点击该入口,而在于点击后落到订单确认页时无法识别、无法退出。**
后者已由 T-264 的原生订单确认页识别修复。
`openPinduoduoSpecifications()` 的既有安全入口匹配(`SAFE_ENTRY_TEXTS` /
`SAFE_ENTRY_PREFIXES`)**仍然保留**,只是当前版本页面不命中;未来版本若恢复独立入口,
该路径将优先生效。
## 关联需求与交互
- 功能:F-005、F-007
- 用户故事:US-004、US-006
- 交互:不新增采购操作
- 架构/API:沿用既有诊断机制,**后端零改动**
## 行为契约
### 一、恢复购买入口,但仅作为次优先路径
`openPinduoduoSpecifications()` 恢复「免拼购买」入口点击,**顺序不变**:
1. 优先匹配 `SAFE_ENTRY_TEXTS` / `SAFE_ENTRY_PREFIXES` 的安全入口;
2. 仅当第 1 步找不到唯一目标时,才使用「免拼购买」入口;
3. 两者都找不到时返回 `ENTRY_ABSENT`,维持 T-264 的跳过行为。
**除「免拼购买」外,不得新增任何其他购买、下单、拼单、支付语义控件作为入口。**
### 二、落到订单确认页即判定该候选无可选规格
点击入口后 `awaitSpecificationEntry()` 返回 `ORDER_CONFIRMATION` 时:
1. 调用既有 `returnFromOrderConfirmation()` 退出(沿用其现有实现与有界次数);
2. **将该候选判定为「无可选规格」,跳过并继续下一个候选**,不得对同一候选重试点击;
3. 退出失败时维持既有 `Blocked(SafetyStopReason.PAYMENT_BOUNDARY)` 行为。
依据:任务的 SKU 硬约束要求颜色与尺码,而点击规格入口却直接进入下单流程,说明该商品
不存在可选规格,不可能满足硬约束。
### 三、订单确认页上零点击
**这是硬边界。** 在订单确认页上,除既有 `returnFromOrderConfirmation()` 的返回动作外,
**不得点击任何控件**,尤其不得点击提交订单、立即支付、免密支付、先用后付或任何扣款
语义控件。必须有单测证明该路径不调用任何此类动作。
### 四、诊断可见
追加计数,置于既有键之后:
```
specEntryViaPurchase=<n> specLandedOnCheckout=<n>
```
| 键 | 含义 |
| --- | --- |
| `specEntryViaPurchase` | 通过「免拼购买」入口打开规格的次数 |
| `specLandedOnCheckout` | 点击入口后落到订单确认页并被跳过的候选数 |
`specLandedOnCheckout` 是本次恢复的安全审计口径:**它直接度量自动化到达订单确认页的
频率**。若真机上该值偏高,说明大量候选无可选规格,应在更早阶段过滤(如品类判断),
而非依赖事后回退。
### 五、既有安全能力一律不放松
- 不改 T-264 的原生订单确认页识别与 `hasOrderConfirmation` 任一判据。
- 不改 T-263 结构判据、`hasCheckoutRoot`、`paymentMarkers` 及其抑制名单。
- 不改登录、验证码、风控的检测逻辑与优先级。
- 不改 `returnFromOrderConfirmation()` 的既有实现与有界次数。
- 不改 T-261 清理逻辑与 `isSafeForCleanup`。
- 不改 T-256/T-258/T-259/T-260 的成果。
- 既有诊断键顺序、名称、取值逐字节不变,新键追加在末尾。
## 方案
1. `openPinduoduoSpecifications()` 恢复「免拼购买」为第二优先入口,返回值区分
经由哪条入口打开,`ENTRY_ABSENT` 语义不变。
2. `PinduoduoCandidateAutomation` 在 `ORDER_CONFIRMATION` 分支将候选计为
`specLandedOnCheckout` 并跳过,不重试。
3. 两个新计数并入诊断快照与 message 尾部。
4. 单测覆盖:安全入口存在时优先使用且不点购买入口;安全入口缺失时使用购买入口;
两者皆无时返回 `ENTRY_ABSENT`;落到订单确认页时只调用返回动作并跳过候选;
订单确认页路径不调用任何提交/支付语义动作;退出失败仍返回
`Blocked(PAYMENT_BOUNDARY)`。
## 验收要点
- [x] 安全入口存在时优先使用,不点击购买入口,有单测证明。
- [x] 安全入口缺失时使用「免拼购买」入口,能打开规格面板。
- [x] 两者皆无时返回 `ENTRY_ABSENT`,行为与 T-264 一致。
- [x] 落到订单确认页时调用既有返回动作、跳过该候选、不重试,有单测证明。
- [x] 订单确认页路径不调用任何提交订单/支付/扣款语义动作,有单测证明。
- [x] 退出订单确认页失败时仍返回 `Blocked(PAYMENT_BOUNDARY)`。
- [x] `specEntryViaPurchase` 与 `specLandedOnCheckout` 计数正确并出现在诊断 message。
- [x] T-263/T-264 的支付边界与订单确认页判据未被削弱,有回归测试。
- [x] 登录、验证码、风控判定与优先级未变。
- [x] 既有诊断键顺序、名称、取值不变。
- [x] Android Debug/Release 单测、`lintDebug`、`assembleDebug`、`assembleRelease` 全部通过。
- [ ] 真机验证:重跑采购任务能打开规格面板并进入 SKU 匹配;从 Admin 读取
`specEntryViaPurchase` 与 `specLandedOnCheckout` 记入执行记录。
## 边界
- 除「免拼购买」外不新增任何购买语义控件作为入口。
- 订单确认页上除既有返回动作外零点击,绝不提交订单或支付。
- 不改 T-261/T-262/T-263/T-264 的任何安全判据与清理逻辑。
- 不改候选卡片识别、签名算法、跨轮去重、价格解析或三轮预算。
- 不改后端代码、数据库、API 契约或 Admin 模板。
- **不在本任务实现品类过滤**。若 `specLandedOnCheckout` 偏高,应另开任务在候选筛选
阶段引入品类判断(可考虑接入既有候选评估 VLM),而非在此处堆规则。
## 附带记录(供后续价格任务参考)
同一次商品详情页取证显示价格文本形态为:
```
'券后¥15.98' 前缀「券后」
'券后 ¥ 15.98' 含空格
'¥' '15.98' 货币符号与数字被拆成两个独立节点
```
与 T-257 观测到的 `pricePrefix` 占比吻合,并新增一个此前未知的因素:**价格被拆成多个
节点**,这是 `PRICE_PATTERN.matchEntire` 失败的另一独立原因。价格解析任务需同时处理
前缀与节点拆分两种情况。
该页面同时显示 `# 先用后付 # 支持0元下单,确认收货后再付款`,说明先用后付在正常服装
商品上同样存在,**不能以其存在与否作为跳过依据**;下单路径排除信用支付需另行设计。
## 执行记录
- 2026-07-31:恢复唯一「免拼购买」次优先入口。入口选择先按
`SAFE_ENTRY_TEXTS`/`SAFE_ENTRY_PREFIXES` 得到唯一安全目标;仅无唯一安全目标时
才接受唯一且文本以「免拼购买」结尾的目标,其它购买、下单、拼单和支付文本仍拒绝。
- 2026-07-31:`PinduoduoSpecificationOpenOutcome` 区分安全入口与购买入口;
候选循环对购买入口和落到订单确认页分别累计 `specEntryViaPurchase`/
`specLandedOnCheckout`。订单确认页继续只调用既有
`returnFromOrderConfirmation()`;成功返回后跳过当前候选,失败保持
`Blocked(PAYMENT_BOUNDARY)`。
- 2026-07-31:两个诊断键追加在既有 `payPage` 之后;旧键顺序、名称和格式不变。
单测按动作发生页面证明 `ORDER_CONFIRMATION` 上唯一动作是返回,返回到商品详情后
才执行回搜索结果页;同一候选不重试,下一候选可继续进入规格匹配。
- 2026-07-31:Debug/Release 各 336 项 App 单测零失败,`lintDebug`、
`assembleDebug`、`assembleRelease` 均通过;根 `init.ps1` 以及全部 Go 后端测试
通过。
- 2026-07-31:`adb devices -l` 无已连接设备,因此不能执行最后一项真机采购任务
重跑和 Admin 诊断读取。任务保持 `DOING`,不伪造
`specEntryViaPurchase`/`specLandedOnCheckout` 真机结果。
- 2026-07-31:设备重新连接后,通过 USB 在 OnePlus PKG110 Android 16/API 36
覆盖安装 T-265 Debug APK;项目未在本任务修改版本号,设备仍显示
`1.4.18 (23)`,包更新时间为 `2026-07-31 17:20:18`。`MainActivity` 正常启动,
采购无障碍、悬浮窗、通知和 Shizuku 权限均保持启用。采购任务重跑和 Admin 两个
诊断计数仍待验证。