Agent:采购记录页回填入口与 PDD 订单扫描(阶段一:只写本地) #242

Open
opened 2026-09-08 11:46:41 +08:00 by ila · 6 comments
Owner

2026-09-10 范围变更:拆阶段,本工单收敛为「只写本地」

用户决定把回填拆成两个阶段,本工单只做第一阶段,并且在现有代码基础上更新,不重做。原话:「或者我们拆开来,agent增加回填,把从pdd读取的订单号等数据先写入agent的sqlite数据库。这样改动比较小。」「修改#242,要在现有代码基础上来更新」

变更内容

  1. 本阶段不上传服务端。 扫描结果只写入 Agent 本地 SQLite。原范围第 8 条(调用 #241 端点小批提交)与第 14 条(区分永久/瞬时业务错误重试)移出本工单,留待第二阶段。
  2. 解除对 #241 的前置依赖。 本阶段服务端零改动,#241 是否合并、是否部署都不影响本工单。
  3. 在既有提交 edd1cb1 基础上修改,不推倒重做。 该提交已实现扫描、解析、互斥与防重入、安全点击边界、时序自检,全部保留。经核实 OrderBackfillScanner.kt 不引用任何网络层(无 import network / Upload / http),上传逻辑独立在 OrderBackfillUpload.kt(92 行),因此本次改动主要是把该上传步骤替换为本地写入。

本阶段新增的硬性约束

  1. 本地记录结构必须与 #241 的请求体逐字段对齐:addressSuffix、pddOrderNo、orderSubmittedAt。目的是让第二阶段成为「读表 → POST」,不产生数据结构返工。字段命名与语义不得自行发挥。
  2. 只持久化后缀,不持久化地址全文。 扫描需读取收货地址以提取 _cgNN,但落库只允许存后缀;收件人姓名、手机号、地址全文一律不得写入本地库、不得进日志。
  3. 本地表必须有保留上限(条数或天数),不得无限增长。具体取值作为实现参数记录。
  4. 必须基于 fix/249-local-purchase-diagnostics(659bbc6)重建分支。 理由:现有 #242 分支基于 main(versionCode 72 / 0.9.59),而 Android 修复链已到 80 / 0.9.67,设备上装的就是 0.9.67,在旧基线出包会丢失 #238、#243–#249 全部修复;且两条链在 GoAutoAccessibilityService.kt 与 AgentForegroundService.kt 两个文件上重叠,必须解决冲突。

本阶段的验收重点

本阶段交付的是验证价值而非业务价值:Admin 仍会显示「尚未取得订单号」,积压的无单号真单不会因此减少。真正要确认的是页面识别判据在真机上是否成立——list()、cards()、detail()、expansion() 四个判据至今一次真机都没跑过,其中 list() 要求存在 label 为「全部」且 selected 为 true 的节点,该属性是否被 PDD 置位从未验证。这是整条链路能否成立的前提,也是拆阶段的主要理由。


2026-09-08 评审更新:内部系统精简方案

用户要求“内部系统,简单易用,不要复杂门禁”,并授权更新本工单正文。本版收敛为人工点击回填、输入天数、自动扫描、小批提交、显示结果;不增加产品内审批、二次确认、后台任务系统或复杂配置。保留设备归属、冲突不覆盖、设备互斥和禁止支付/确认收货等必要边界。

本次只更新方案,不代表批准实施、原型确认、迁移或真机操作。代码核验基线 main fdd26af;Android 已发布分支 7ac1355 的互斥锁与历史缓存相关文件与此基线一致。原工单真机现象与积压数量是创建时记录,本轮未重新验证,不作为实时统计。

Gitea MCP 当前不可用,回退项目安全配置提供凭据的 Gitea API;未输出或写入凭据。

来源与目标

2026-09-08 用户提出 Agent 端回填入口的具体形态,原话:

「agent的采购tab的搜索按钮右边增加的回填按钮,点击后弹出窗口里有个天数输入框,默认是2天,右边是个确认按钮,点击确认按钮后,去pdd app获取指定时间范围的订单的订单号和下单时间,更新到本地sqlite数据库和admin」

以及互斥与时间来源的确认:

「同意采集、采购和回填互斥;2,下单时间读页面,读不到回落irreversible_at.」

目标:在 Agent 采购记录页提供人工触发的回填入口,扫描 PDD「我的订单-全部」,读取收货地址含 _cgNN 的订单的订单号与下单时间,更新本地缓存并提交服务端。

前置依赖

依赖 #241(服务端批量回填端点)先行落地,本工单调用该端点。#241 未完成前本工单保持待实施。

当前事实(真机实测,设备 192.168.0.173:36805,PDD 8.23.0)

页面形态与字段位置:

  • 订单列表卡片只有店铺名、商品名、订单状态与操作按钮,无地址、无订单号、无下单时间,必须逐单进详情页。
  • 订单详情页向下滚约一屏可见收货地址(含 _cgNN)与订单编号,二者均为明文可读。
  • 下单时间默认折叠,需点击订单编号下一行的「展开」按钮(实测 clickable=true,位于「商品快照」行右侧)。展开后出现:
订单编号: <脱敏订单号>
支付方式: 微信支付
下单时间: 2026-09-08 10:50:29
拼单时间: 2026-09-08 11:20:35
  • 注意展开后同时出现「下单时间」与「拼单时间」,两者相差约半小时,必须取下单时间。
  • 深链 https://mobile.yangkeduo.com/orders.html 可直接进入 PDD App(落 NewPageActivity),无需经过首页或个人中心。原实测 type=2 命中「打包中」、type=4 命中「评价」,这是分类/Tab 线索,不是列表分页证据;不可用该参数递增实现翻页。进入后必须识别「全部」列表。

现有正则可原样复用,实测均命中且语义正确:

ORDER_NO   = (?:订单编号|订单号)\s*[::]?\s*([A-Za-z0-9-]{6,64})
ORDER_TIME = (?:下单时间|创建时间)\s*[::]?\s*(20\d{2}[-/.年]\d{1,2}[-/.月]\d{1,2}日?\s+\d{1,2}:\d{2}(?::\d{2})?)

ORDER_TIME 只认「下单时间/创建时间」前缀,不会误取「拼单时间」。实施不得改为「抓页面第一个日期」之类的宽松写法。

互斥有现成机制可直接复用:

AgentForegroundService 已有 TaskExecutionMutex,且采集 tab 的「采集当前拼多多商品」按钮就是同一模式的人工触发动作:

private val taskMutex = TaskExecutionMutex()
if (taskMutex.currentTaskId() != null) return MANUAL_BUSY
if (!taskMutex.tryAcquire(CURRENT_PAGE_RESERVATION_ID)) { ... }

回填复用同一把锁,但 tryAcquire 对同一 ID 可再次成功。固定预留 ID 本身不能防止重复启动,必须另有原子防重入/运行标记,并在 finally 释放;不改既有任务锁的全局语义。

UI 位置有对称先例:

TaskHistoryFragment 采集模式下,搜索按钮右侧已有「采集」按钮(contentDescription = "采集当前拼多多商品")。采购模式下同一位置新增「回填」按钮与之对称。

本地缓存已有对应字段:

persistence/TaskHistoryCache.kt 使用 SharedPreferences,已缓存 pddOrderNo 与 orderSubmittedAt,不是 SQLite。SQLite 的 PurchaseTaskStore 承载执行与 outbox,不能与历史缓存混为一谈。按本次精简方案只复用历史缓存,不新建 SQLite 回填表,不篡改原执行/outbox。

积压规模: 服务端当前 21 个 order_result_unknown 任务缺订单号。

范围

  1. 采购记录搜索按钮右侧新增“回填”。点击只弹出天数输入(默认 2)与确认;一次确认即开始,无额外审批或二次确认。首版不显示缺订单号精确总数,不要求 #241 新增计数接口。
  2. 天数允许修改,输入正整数;原建议的“最多 7 天”不是已确认要求,不作为业务门禁。本次扫描开始时冻结时间范围、服务器与设备身份。天数口径已确认为滚动小时:N 天 = 点击确认那一刻往前推 N×24 小时(2026-09-08 用户确认,理由是实现最简单、语义直白、不涉及时区与自然日边界)。比较基准是页面读到的下单时间,不混用任务 createdAt;不新增用户可配置的扫描次数或超时参数。
  3. 服务中使用既有 TaskExecutionMutex 加原子防重入。忙碌时提示稍后操作,不排队、不抢占、不重复启动;扫描期间不领取或恢复驱动 PDD 的其他动作,心跳保持。预留 ID 不作为真实任务号上报;停止、异常及结束均释放锁。不得重构原采购执行流程。
  4. 深链进入并验证 PDD「我的订单-全部」→逐个打开安全订单详情→有界滚动→在订单信息区域按需点击“展开”→读取地址后缀、订单号、下单时间→返回并重新定位列表。不能按固定坐标点卡片或高危控件;页面不符合预期、登录/风控出现时停止并显示原因。
  5. 每个详情独立收集临时字段,支持跨滚动区域读取,但进入下一订单清空临时值;只有同一详情唯一后缀与唯一订单号才形成条目。无后缀直接跳过,相关个人订单数据不缓存、不上传、不入日志。正则可复用,旧采购解析器要求待支付证据的整体逻辑不可直接用于全部订单。
  6. 实施第一步必须先验证列表时序:让 Agent 读取「我的订单-全部」前若干单的下单时间,确认是否按下单时间单调递减。这是「超过指定时间即停止」这条停止规则能否成立的前提——若列表存在待付款置顶、拼单中浮动或按状态分组等非严格倒序情形,遇到第一笔超时订单就停会静默漏单,而回填工具最坏的失败模式正是「以为扫完了其实漏了」。验证通过方可采用「遇超时即停」;验证不通过则必须扫至内部上限并在结果中说明。(注:该验证无法用 adb 完成——「全部」页存在待付款倒计时持续动画,uiautomator dump 取不到 idle 状态,实测四次全部失败;Agent 走无障碍服务不受此限制。)
  7. 下单时间只按“下单时间/创建时间”标签读取,不取拼单时间或页面第一个日期;有后缀和订单号但时间缺失仍提交,由 #241 回落。扫描日期范围不能仅以“碰到第一笔旧订单”认定完成:严格倒序未经验证。内部设置扫描条数/耗时上限、无进展/重复页面终止和去重;缺时间不得无限扫描,触及上限明确显示未完整扫描。具体内部限值作为实现参数记录,不增加用户操作门禁。
  8. 找到结果就小批提交,不等全部扫描结束;按 #241 逐条确认更新/刷新 TaskHistoryCache,包括最终状态、订单号、时间与错误展示,服务端始终为事实源。已成功部分保留;订单冲突显示人工检查,不覆盖;未确认项不写成本地成功。网络结果不明时可幂等重放,断网/进程退出未提交的结果可下次重扫,不新增持久化补偿系统。
  9. 展示已检查、成功、已回填、冲突/失败等数量和停止按钮;取消后停止后续页面操作,已提交结果保留。完成、用户停止、超时/无进展、网络或页面异常分别给出结果,不把扫描不完整显示为全部成功。切换服务器/设备身份须停止扫描,禁止提交到另一环境。

非目标

  • 不修改服务端、数据库或 admin 前端。
  • 不新增任务类型、任务表或任务调度机制;回填是人工触发的一次性操作,不进任务系统。
  • 不实现快递单号采集(无障碍树不暴露该字段,另议)。
  • 不使用 OCR/VLM,不保存原始控件树或整屏截图。
  • 不修改既有采集与采购流程的任何行为。
  • 回填范围严格限定为订单号与下单时间两个字段(2026-09-08 用户确认)。不采集也不上报 PDD 订单的商品、金额、订单状态、物流或其他任何信息。

安全边界(硬性,不可放宽)

扫描过程会经过订单列表与订单详情页,其中存在高危控件。以下必须永久排除为点击目标:确认收货、申请退款、催发货、去支付、立即支付、提交订单。

实测「确认收货」与「查看物流」在同一行、间距仅 240px,误点后果不可逆。允许点击的目标仅限:订单卡片、「展开」、返回。

不执行支付、不创建订单、不修改任何订单状态。整个流程为只读。

方案与设计证据

复用采购/采集现有按钮、对话框和反馈样式,提供可审核的轻量设计,覆盖输入、运行进度/停止、空结果、部分成功、冲突和失败;不新增独立后台页面。按本次仓库规则,独立功能的轻量原型须记录可访问链接与版本,经用户确认后才能写生产代码;本次“更新工单”不视为原型确认,不在本次制作原型。

原始用户提到 SQLite,代码事实为 SharedPreferences 历史缓存;本版按用户要求的简单易用方案明确复用缓存,不追加 SQLite 迁移。

验收

  • 回填入口、默认两天、可修改天数、一次确认、进度与停止均可用,无新增审批流程。
  • 忙碌拒绝、快速重复点击、取消/异常释放锁、扫描中不领取其他任务通过测试,心跳不使用假任务 ID。
  • 同详情跨滚动字段正确关联,下一单不残留;下单时间不误取拼单时间,已支付等详情不被旧待支付判据误拦。
  • 无后缀不留存个人订单数据;冲突不覆盖;遇旧订单不无证据地宣称完成,缺时间仍有界终止。
  • 小批提交前面成功结果保留;重复为已回填;断网、部分失败、取消和不完整扫描有明确反馈,缓存只反映服务端确认。
  • 全程不支付、不创建订单、不确认收货、不退款;只读安全点击失败时停止。
  • Android 单测、assembleDebug 通过;授权后再做真机验证,未执行项明确记录。

文档影响

实施时更新线上 Business-Rules-and-Glossary 与 Architecture-and-Code-Map:人工订单扫描、设备互斥、防重入、安全点击和缓存处理是长期新增行为。与 #241 共用 API 契约,若客户端实施发现契约需调整,先回到 #241 对齐,不暗自扩展。在线回读后同步/检查镜像;本次只更新工单,不修改 Wiki。保留已有无关工作区改动。

待确认

天数口径已确认为滚动 N×24 小时,不再是待确认项。内部扫描条数/耗时限值作为实现参数记录,不要求用户配置技术参数;触及内部上限时结果必须明确显示「未完整扫描」,不得让用户误以为已扫完。原“7 天硬上限”建议已撤回为非既定要求(用户明确要求内部系统不加复杂门禁)。原型及开始实施仍待用户确认,当前不实施。

实施分工

本工单已建单,用户明确要求暂不实施(原话「建工单,先别做」)。实施启动前需用户再次确认,且需 #241 先行完成。

## 2026-09-10 范围变更:拆阶段,本工单收敛为「只写本地」 用户决定把回填拆成两个阶段,本工单只做第一阶段,并且**在现有代码基础上更新,不重做**。原话:「或者我们拆开来,agent增加回填,把从pdd读取的订单号等数据先写入agent的sqlite数据库。这样改动比较小。」「修改#242,要在现有代码基础上来更新」 ### 变更内容 1. **本阶段不上传服务端。** 扫描结果只写入 Agent 本地 SQLite。原范围第 8 条(调用 #241 端点小批提交)与第 14 条(区分永久/瞬时业务错误重试)移出本工单,留待第二阶段。 2. **解除对 #241 的前置依赖。** 本阶段服务端零改动,#241 是否合并、是否部署都不影响本工单。 3. **在既有提交 `edd1cb1` 基础上修改,不推倒重做。** 该提交已实现扫描、解析、互斥与防重入、安全点击边界、时序自检,全部保留。经核实 `OrderBackfillScanner.kt` 不引用任何网络层(无 import network / Upload / http),上传逻辑独立在 `OrderBackfillUpload.kt`(92 行),因此本次改动主要是把该上传步骤替换为本地写入。 ### 本阶段新增的硬性约束 4. **本地记录结构必须与 #241 的请求体逐字段对齐**:`addressSuffix`、`pddOrderNo`、`orderSubmittedAt`。目的是让第二阶段成为「读表 → POST」,不产生数据结构返工。字段命名与语义不得自行发挥。 5. **只持久化后缀,不持久化地址全文。** 扫描需读取收货地址以提取 `_cgNN`,但落库只允许存后缀;收件人姓名、手机号、地址全文一律不得写入本地库、不得进日志。 6. **本地表必须有保留上限**(条数或天数),不得无限增长。具体取值作为实现参数记录。 7. **必须基于 `fix/249-local-purchase-diagnostics`(`659bbc6`)重建分支。** 理由:现有 #242 分支基于 main(versionCode 72 / 0.9.59),而 Android 修复链已到 80 / 0.9.67,设备上装的就是 0.9.67,在旧基线出包会丢失 #238、#243–#249 全部修复;且两条链在 `GoAutoAccessibilityService.kt` 与 `AgentForegroundService.kt` 两个文件上重叠,必须解决冲突。 ### 本阶段的验收重点 本阶段交付的是**验证价值而非业务价值**:Admin 仍会显示「尚未取得订单号」,积压的无单号真单不会因此减少。真正要确认的是**页面识别判据在真机上是否成立**——`list()`、`cards()`、`detail()`、`expansion()` 四个判据至今一次真机都没跑过,其中 `list()` 要求存在 label 为「全部」且 selected 为 true 的节点,该属性是否被 PDD 置位从未验证。这是整条链路能否成立的前提,也是拆阶段的主要理由。 --- ## 2026-09-08 评审更新:内部系统精简方案 用户要求“内部系统,简单易用,不要复杂门禁”,并授权更新本工单正文。本版收敛为人工点击回填、输入天数、自动扫描、小批提交、显示结果;不增加产品内审批、二次确认、后台任务系统或复杂配置。保留设备归属、冲突不覆盖、设备互斥和禁止支付/确认收货等必要边界。 本次只更新方案,不代表批准实施、原型确认、迁移或真机操作。代码核验基线 main `fdd26af`;Android 已发布分支 `7ac1355` 的互斥锁与历史缓存相关文件与此基线一致。原工单真机现象与积压数量是创建时记录,本轮未重新验证,不作为实时统计。 Gitea MCP 当前不可用,回退项目安全配置提供凭据的 Gitea API;未输出或写入凭据。 ## 来源与目标 2026-09-08 用户提出 Agent 端回填入口的具体形态,原话: > 「agent的采购tab的搜索按钮右边增加的回填按钮,点击后弹出窗口里有个天数输入框,默认是2天,右边是个确认按钮,点击确认按钮后,去pdd app获取指定时间范围的订单的订单号和下单时间,更新到本地sqlite数据库和admin」 以及互斥与时间来源的确认: > 「同意采集、采购和回填互斥;2,下单时间读页面,读不到回落irreversible_at.」 目标:在 Agent 采购记录页提供人工触发的回填入口,扫描 PDD「我的订单-全部」,读取收货地址含 `_cgNN` 的订单的订单号与下单时间,更新本地缓存并提交服务端。 ## 前置依赖 **依赖 #241(服务端批量回填端点)先行落地**,本工单调用该端点。#241 未完成前本工单保持待实施。 ## 当前事实(真机实测,设备 192.168.0.173:36805,PDD 8.23.0) **页面形态与字段位置:** - 订单列表卡片只有店铺名、商品名、订单状态与操作按钮,**无地址、无订单号、无下单时间**,必须逐单进详情页。 - 订单详情页向下滚约一屏可见收货地址(含 `_cgNN`)与订单编号,二者均为明文可读。 - **下单时间默认折叠**,需点击订单编号下一行的「展开」按钮(实测 `clickable=true`,位于「商品快照」行右侧)。展开后出现: ``` 订单编号: <脱敏订单号> 支付方式: 微信支付 下单时间: 2026-09-08 10:50:29 拼单时间: 2026-09-08 11:20:35 ``` - 注意展开后同时出现「下单时间」与「拼单时间」,两者相差约半小时,**必须取下单时间**。 - 深链 `https://mobile.yangkeduo.com/orders.html` 可直接进入 PDD App(落 `NewPageActivity`),无需经过首页或个人中心。原实测 `type=2` 命中「打包中」、`type=4` 命中「评价」,这是分类/Tab 线索,不是列表分页证据;不可用该参数递增实现翻页。进入后必须识别「全部」列表。 **现有正则可原样复用**,实测均命中且语义正确: ```kotlin ORDER_NO = (?:订单编号|订单号)\s*[::]?\s*([A-Za-z0-9-]{6,64}) ORDER_TIME = (?:下单时间|创建时间)\s*[::]?\s*(20\d{2}[-/.年]\d{1,2}[-/.月]\d{1,2}日?\s+\d{1,2}:\d{2}(?::\d{2})?) ``` `ORDER_TIME` 只认「下单时间/创建时间」前缀,不会误取「拼单时间」。实施不得改为「抓页面第一个日期」之类的宽松写法。 **互斥有现成机制可直接复用:** `AgentForegroundService` 已有 `TaskExecutionMutex`,且采集 tab 的「采集当前拼多多商品」按钮就是同一模式的人工触发动作: ```kotlin private val taskMutex = TaskExecutionMutex() if (taskMutex.currentTaskId() != null) return MANUAL_BUSY if (!taskMutex.tryAcquire(CURRENT_PAGE_RESERVATION_ID)) { ... } ``` 回填复用同一把锁,但 tryAcquire 对同一 ID 可再次成功。固定预留 ID 本身不能防止重复启动,必须另有原子防重入/运行标记,并在 finally 释放;不改既有任务锁的全局语义。 **UI 位置有对称先例:** `TaskHistoryFragment` 采集模式下,搜索按钮右侧已有「采集」按钮(`contentDescription = "采集当前拼多多商品"`)。采购模式下同一位置新增「回填」按钮与之对称。 **本地缓存已有对应字段:** `persistence/TaskHistoryCache.kt` 使用 SharedPreferences,已缓存 `pddOrderNo` 与 `orderSubmittedAt`,不是 SQLite。SQLite 的 PurchaseTaskStore 承载执行与 outbox,不能与历史缓存混为一谈。按本次精简方案只复用历史缓存,不新建 SQLite 回填表,不篡改原执行/outbox。 **积压规模:** 服务端当前 21 个 `order_result_unknown` 任务缺订单号。 ## 范围 1. 采购记录搜索按钮右侧新增“回填”。点击只弹出天数输入(默认 2)与确认;一次确认即开始,无额外审批或二次确认。首版不显示缺订单号精确总数,不要求 #241 新增计数接口。 2. 天数允许修改,输入正整数;原建议的“最多 7 天”不是已确认要求,不作为业务门禁。本次扫描开始时冻结时间范围、服务器与设备身份。**天数口径已确认为滚动小时:N 天 = 点击确认那一刻往前推 N×24 小时**(2026-09-08 用户确认,理由是实现最简单、语义直白、不涉及时区与自然日边界)。比较基准是页面读到的下单时间,不混用任务 createdAt;不新增用户可配置的扫描次数或超时参数。 3. 服务中使用既有 TaskExecutionMutex 加原子防重入。忙碌时提示稍后操作,不排队、不抢占、不重复启动;扫描期间不领取或恢复驱动 PDD 的其他动作,心跳保持。预留 ID 不作为真实任务号上报;停止、异常及结束均释放锁。不得重构原采购执行流程。 4. 深链进入并验证 PDD「我的订单-全部」→逐个打开安全订单详情→有界滚动→在订单信息区域按需点击“展开”→读取地址后缀、订单号、下单时间→返回并重新定位列表。不能按固定坐标点卡片或高危控件;页面不符合预期、登录/风控出现时停止并显示原因。 5. 每个详情独立收集临时字段,支持跨滚动区域读取,但进入下一订单清空临时值;只有同一详情唯一后缀与唯一订单号才形成条目。无后缀直接跳过,相关个人订单数据不缓存、不上传、不入日志。正则可复用,旧采购解析器要求待支付证据的整体逻辑不可直接用于全部订单。 6. **实施第一步必须先验证列表时序**:让 Agent 读取「我的订单-全部」前若干单的下单时间,确认是否按下单时间单调递减。这是「超过指定时间即停止」这条停止规则能否成立的前提——若列表存在待付款置顶、拼单中浮动或按状态分组等非严格倒序情形,遇到第一笔超时订单就停会**静默漏单**,而回填工具最坏的失败模式正是「以为扫完了其实漏了」。验证通过方可采用「遇超时即停」;验证不通过则必须扫至内部上限并在结果中说明。(注:该验证无法用 adb 完成——「全部」页存在待付款倒计时持续动画,`uiautomator dump` 取不到 idle 状态,实测四次全部失败;Agent 走无障碍服务不受此限制。) 7. 下单时间只按“下单时间/创建时间”标签读取,不取拼单时间或页面第一个日期;有后缀和订单号但时间缺失仍提交,由 #241 回落。扫描日期范围不能仅以“碰到第一笔旧订单”认定完成:严格倒序未经验证。内部设置扫描条数/耗时上限、无进展/重复页面终止和去重;缺时间不得无限扫描,触及上限明确显示未完整扫描。具体内部限值作为实现参数记录,不增加用户操作门禁。 8. 找到结果就小批提交,不等全部扫描结束;按 #241 逐条确认更新/刷新 TaskHistoryCache,包括最终状态、订单号、时间与错误展示,服务端始终为事实源。已成功部分保留;订单冲突显示人工检查,不覆盖;未确认项不写成本地成功。网络结果不明时可幂等重放,断网/进程退出未提交的结果可下次重扫,不新增持久化补偿系统。 9. 展示已检查、成功、已回填、冲突/失败等数量和停止按钮;取消后停止后续页面操作,已提交结果保留。完成、用户停止、超时/无进展、网络或页面异常分别给出结果,不把扫描不完整显示为全部成功。切换服务器/设备身份须停止扫描,禁止提交到另一环境。 ## 非目标 - 不修改服务端、数据库或 admin 前端。 - 不新增任务类型、任务表或任务调度机制;回填是人工触发的一次性操作,不进任务系统。 - 不实现快递单号采集(无障碍树不暴露该字段,另议)。 - 不使用 OCR/VLM,不保存原始控件树或整屏截图。 - 不修改既有采集与采购流程的任何行为。 - **回填范围严格限定为订单号与下单时间两个字段**(2026-09-08 用户确认)。不采集也不上报 PDD 订单的商品、金额、订单状态、物流或其他任何信息。 ## 安全边界(硬性,不可放宽) 扫描过程会经过订单列表与订单详情页,其中存在高危控件。以下必须永久排除为点击目标:**确认收货、申请退款、催发货、去支付、立即支付、提交订单**。 实测「确认收货」与「查看物流」在同一行、间距仅 240px,误点后果不可逆。允许点击的目标仅限:订单卡片、「展开」、返回。 不执行支付、不创建订单、不修改任何订单状态。整个流程为只读。 ## 方案与设计证据 复用采购/采集现有按钮、对话框和反馈样式,提供可审核的轻量设计,覆盖输入、运行进度/停止、空结果、部分成功、冲突和失败;不新增独立后台页面。按本次仓库规则,独立功能的轻量原型须记录可访问链接与版本,经用户确认后才能写生产代码;本次“更新工单”不视为原型确认,不在本次制作原型。 原始用户提到 SQLite,代码事实为 SharedPreferences 历史缓存;本版按用户要求的简单易用方案明确复用缓存,不追加 SQLite 迁移。 ## 验收 - 回填入口、默认两天、可修改天数、一次确认、进度与停止均可用,无新增审批流程。 - 忙碌拒绝、快速重复点击、取消/异常释放锁、扫描中不领取其他任务通过测试,心跳不使用假任务 ID。 - 同详情跨滚动字段正确关联,下一单不残留;下单时间不误取拼单时间,已支付等详情不被旧待支付判据误拦。 - 无后缀不留存个人订单数据;冲突不覆盖;遇旧订单不无证据地宣称完成,缺时间仍有界终止。 - 小批提交前面成功结果保留;重复为已回填;断网、部分失败、取消和不完整扫描有明确反馈,缓存只反映服务端确认。 - 全程不支付、不创建订单、不确认收货、不退款;只读安全点击失败时停止。 - Android 单测、assembleDebug 通过;授权后再做真机验证,未执行项明确记录。 ## 文档影响 实施时更新线上 Business-Rules-and-Glossary 与 Architecture-and-Code-Map:人工订单扫描、设备互斥、防重入、安全点击和缓存处理是长期新增行为。与 #241 共用 API 契约,若客户端实施发现契约需调整,先回到 #241 对齐,不暗自扩展。在线回读后同步/检查镜像;本次只更新工单,不修改 Wiki。保留已有无关工作区改动。 ## 待确认 天数口径已确认为滚动 N×24 小时,不再是待确认项。内部扫描条数/耗时限值作为实现参数记录,不要求用户配置技术参数;触及内部上限时结果必须明确显示「未完整扫描」,不得让用户误以为已扫完。原“7 天硬上限”建议已撤回为非既定要求(用户明确要求内部系统不加复杂门禁)。原型及开始实施仍待用户确认,当前不实施。 ## 实施分工 本工单已建单,**用户明确要求暂不实施**(原话「建工单,先别做」)。实施启动前需用户再次确认,且需 #241 先行完成。
ila self-assigned this 2026-09-08 11:46:41 +08:00
Author
Owner

依赖说明:本工单依赖 #241(服务端批量回填端点)先行落地。#241 完成前保持待实施。

两个工单均已按用户要求建单但不实施,启动实施需用户再次确认。

依赖说明:本工单依赖 #241(服务端批量回填端点)先行落地。#241 完成前保持待实施。 两个工单均已按用户要求建单但**不实施**,启动实施需用户再次确认。
Author
Owner

2026-09-08 用户确认后的调整(Claude 复核 Codex 评审版)

Codex 评审版对本工单的两处代码事实纠错已在基线 main fdd26af 上独立验证,均成立:

  • TaskHistoryCache 使用 getSharedPreferences,不是 SQLite。用户原话提到 SQLite,代码事实为 SharedPreferences;SQLite 的 PurchaseTaskStore 承载执行与 outbox,不可混用。
  • TaskExecutionMutex.tryAcquire 实现为 activeTask.compareAndSet(NO_TASK, taskId) || activeTask.get() == taskId,对同一 ID 可重入。若按原工单用固定预留 ID,用户快速连点两次「回填」将两次返回 true,导致两个扫描并发驱动 PDD。必须另加原子防重入标记,并在 finally 释放。

本次调整

  1. 天数口径已定死:N 天 = 点击确认那一刻往前推 N×24 小时(滚动小时)。理由是实现最简单、语义直白、不涉及时区与自然日边界。不再作为待确认项。
  2. 新增范围第 6 条:实施第一步必须先验证列表时序。用户确认的停止规则是「超过指定时间就停止」,该规则成立的前提是列表按下单时间单调递减。若存在待付款置顶、拼单中浮动或按状态分组等非严格倒序情形,遇第一笔超时订单即停会静默漏单——而回填工具最坏的失败模式正是「以为扫完了其实漏了」。验证通过方可采用「遇超时即停」,否则须扫至内部上限并在结果中说明。
    该验证无法用 adb 完成:「全部」页存在待付款倒计时持续动画,uiautomator dump 取不到 idle 状态,本轮实测四次全部失败;Agent 走无障碍服务不受此限制,故列为实施内的第一步而非独立工单。
  3. 原第 6/7/8 条顺延为 7/8/9。
  4. 非目标新增:回填范围严格限定为订单号与下单时间两个字段,不采集也不上报商品、金额、订单状态、物流等其他信息(用户 2026-09-08 明确确认)。
  5. 待确认段更新:天数口径移出待确认;补充要求「触及内部上限时结果必须显示未完整扫描」。

已撤回的意见

原提「7 天用户可见上限」已撤回,属于用户明确不要的门禁,且评审版内部上限已提供兜底。

本次仅调整工单方案,不实施。实施仍需 #241 先行完成,并经用户确认原型与开工。

## 2026-09-08 用户确认后的调整(Claude 复核 Codex 评审版) Codex 评审版对本工单的两处代码事实纠错已在基线 main `fdd26af` 上独立验证,均成立: - `TaskHistoryCache` 使用 `getSharedPreferences`,不是 SQLite。用户原话提到 SQLite,代码事实为 SharedPreferences;SQLite 的 `PurchaseTaskStore` 承载执行与 outbox,不可混用。 - `TaskExecutionMutex.tryAcquire` 实现为 `activeTask.compareAndSet(NO_TASK, taskId) || activeTask.get() == taskId`,对同一 ID 可重入。若按原工单用固定预留 ID,用户快速连点两次「回填」将两次返回 true,导致两个扫描并发驱动 PDD。必须另加原子防重入标记,并在 finally 释放。 ### 本次调整 1. **天数口径已定死**:N 天 = 点击确认那一刻往前推 N×24 小时(滚动小时)。理由是实现最简单、语义直白、不涉及时区与自然日边界。不再作为待确认项。 2. **新增范围第 6 条:实施第一步必须先验证列表时序**。用户确认的停止规则是「超过指定时间就停止」,该规则成立的前提是列表按下单时间单调递减。若存在待付款置顶、拼单中浮动或按状态分组等非严格倒序情形,遇第一笔超时订单即停会静默漏单——而回填工具最坏的失败模式正是「以为扫完了其实漏了」。验证通过方可采用「遇超时即停」,否则须扫至内部上限并在结果中说明。 该验证无法用 adb 完成:「全部」页存在待付款倒计时持续动画,`uiautomator dump` 取不到 idle 状态,本轮实测四次全部失败;Agent 走无障碍服务不受此限制,故列为实施内的第一步而非独立工单。 3. 原第 6/7/8 条顺延为 7/8/9。 4. 非目标新增:回填范围严格限定为订单号与下单时间两个字段,不采集也不上报商品、金额、订单状态、物流等其他信息(用户 2026-09-08 明确确认)。 5. 待确认段更新:天数口径移出待确认;补充要求「触及内部上限时结果必须显示未完整扫描」。 ### 已撤回的意见 原提「7 天用户可见上限」已撤回,属于用户明确不要的门禁,且评审版内部上限已提供兜底。 本次仅调整工单方案,**不实施**。实施仍需 #241 先行完成,并经用户确认原型与开工。
Author
Owner

2026-09-08 前置依赖 #241 已完成,本工单开工

#241 已实施并通过审核,待验收。分支 feat/241-order-backfill-endpoint,提交 290a17e → 72b8b5d → ca7f768 → 482ba34,基于 fdd26af。尚未合并到 main,因此本工单基于 #241 分支顶端开发。

#241 实际交付的契约(本工单据此对接)

端点:POST /api/agent/v1/purchase-tasks/order-backfill

  • 鉴权:Authorization: Bearer <Device Token>,沿用 RequireAgentHTTPS。无需 Admin JWT、claim、start 或 attempt。
  • 设备号只从认证读取,请求不得包含 deviceId、地址全文、收件人、手机号;未知 JSON 字段会被拒绝。
  • 请求:{requestId, items:[{addressSuffix, pddOrderNo, orderSubmittedAt?}]},每批 1~50 条。
  • addressSuffix 必须是规范形式 _cg<任务号>,服务端用 ParseAddressSuffix 回比校验,_cg07、_cg+7、全角数字等非规范形式一律拒绝。Agent 必须自行从地址中提取该后缀,只上传后缀,不上传地址全文。
  • orderSubmittedAt 为带时区 RFC3339/RFC3339Nano;缺省时服务端回落该任务的 irreversible_at。
  • 响应逐条返回 index、taskId、result、code、status、statusVersion、pddOrderNo、orderSubmittedAt、timeSource、retryable。timeSource 取值 page / irreversible_at / existing_unknown,客户端展示必须区分页面读到的真实值与回落估算值。

业务错误码:PURCHASE_BACKFILL_SUFFIX_INVALID、PURCHASE_TASK_NOT_FOUND、PURCHASE_BACKFILL_DEVICE_MISMATCH、PURCHASE_STATE_CONFLICT、PURCHASE_INVALID_REQUEST、PURCHASE_ORDER_TIME_INVALID、PURCHASE_ORDER_TIME_MISSING、PURCHASE_BACKFILL_ORDER_CONFLICT、PURCHASE_BACKFILL_BATCH_CONFLICT、PURCHASE_BACKFILL_ORDER_ALREADY_USED、INTERNAL_ERROR。

逐条独立事务、逐条独立结果,合法条目不因其他条目失败而回滚;同任务同订单号重复提交返回 already_backfilled。

追加范围

  1. 提交结果必须区分永久性业务错误与瞬时错误。上述冲突类错误码(后缀非法、任务不存在、设备不匹配、状态冲突、订单号冲突、批内冲突、订单号已被占用)是永久性的,重试不会成功:必须停止重试、在界面显示为「需人工检查」并保留证据,不得无限重试。仅网络错误、超时、INTERNAL_ERROR 等瞬时错误才允许有界重试。

记录:相邻问题,本工单不修

android/.../persistence/PurchaseOutboxUploader.kt 的 flush() 对永久性业务错误同样会无限重试(submit 抛出即停在该条,markUploaded 不执行,下次 flush 继续同一条)。该 outbox 服务于采购结果上传,与本工单的回填提交是不同路径,因此不在本工单范围内修改,仅记录。若后续要修,另建工单。

本条记录的目的是:实施本工单时不要复用 PurchaseOutboxUploader 来提交回填结果,避免继承同一缺陷。

## 2026-09-08 前置依赖 #241 已完成,本工单开工 #241 已实施并通过审核,待验收。分支 `feat/241-order-backfill-endpoint`,提交 `290a17e` → `72b8b5d` → `ca7f768` → `482ba34`,基于 `fdd26af`。**尚未合并到 main**,因此本工单基于 #241 分支顶端开发。 ### #241 实际交付的契约(本工单据此对接) 端点:`POST /api/agent/v1/purchase-tasks/order-backfill` - 鉴权:`Authorization: Bearer <Device Token>`,沿用 `RequireAgentHTTPS`。无需 Admin JWT、claim、start 或 attempt。 - 设备号只从认证读取,**请求不得包含 deviceId、地址全文、收件人、手机号**;未知 JSON 字段会被拒绝。 - 请求:`{requestId, items:[{addressSuffix, pddOrderNo, orderSubmittedAt?}]}`,每批 1~50 条。 - `addressSuffix` 必须是规范形式 `_cg<任务号>`,服务端用 `ParseAddressSuffix` 回比校验,`_cg07`、`_cg+7`、全角数字等非规范形式一律拒绝。**Agent 必须自行从地址中提取该后缀,只上传后缀,不上传地址全文。** - `orderSubmittedAt` 为带时区 RFC3339/RFC3339Nano;缺省时服务端回落该任务的 `irreversible_at`。 - 响应逐条返回 `index、taskId、result、code、status、statusVersion、pddOrderNo、orderSubmittedAt、timeSource、retryable`。`timeSource` 取值 `page` / `irreversible_at` / `existing_unknown`,**客户端展示必须区分页面读到的真实值与回落估算值**。 业务错误码:`PURCHASE_BACKFILL_SUFFIX_INVALID`、`PURCHASE_TASK_NOT_FOUND`、`PURCHASE_BACKFILL_DEVICE_MISMATCH`、`PURCHASE_STATE_CONFLICT`、`PURCHASE_INVALID_REQUEST`、`PURCHASE_ORDER_TIME_INVALID`、`PURCHASE_ORDER_TIME_MISSING`、`PURCHASE_BACKFILL_ORDER_CONFLICT`、`PURCHASE_BACKFILL_BATCH_CONFLICT`、`PURCHASE_BACKFILL_ORDER_ALREADY_USED`、`INTERNAL_ERROR`。 逐条独立事务、逐条独立结果,合法条目不因其他条目失败而回滚;同任务同订单号重复提交返回 `already_backfilled`。 ### 追加范围 14. **提交结果必须区分永久性业务错误与瞬时错误**。上述冲突类错误码(后缀非法、任务不存在、设备不匹配、状态冲突、订单号冲突、批内冲突、订单号已被占用)是永久性的,重试不会成功:必须停止重试、在界面显示为「需人工检查」并保留证据,不得无限重试。仅网络错误、超时、`INTERNAL_ERROR` 等瞬时错误才允许有界重试。 ### 记录:相邻问题,本工单不修 `android/.../persistence/PurchaseOutboxUploader.kt` 的 `flush()` 对永久性业务错误同样会无限重试(`submit` 抛出即停在该条,`markUploaded` 不执行,下次 flush 继续同一条)。该 outbox 服务于**采购结果**上传,与本工单的回填提交是不同路径,因此**不在本工单范围内修改**,仅记录。若后续要修,另建工单。 本条记录的目的是:实施本工单时**不要复用 `PurchaseOutboxUploader` 来提交回填结果**,避免继承同一缺陷。
Author
Owner

已完成并推送,待验收。

实现

分支 feat/242-agent-order-backfill(独立 worktree),基于 #241 分支顶端 482ba34(#241 尚未合并到 main)。

  • edd1cb1 feat(android): add read-only order backfill with bounded scanning (#242) —— 11 个 Android 文件。
  • ba0e48b —— Wiki 镜像同步。

审核验证(Claude 独立执行)

  • ./gradlew :app:testDebugUnitTest --rerun-tasks:337 通过,0 失败,0 错误,0 跳过,新增 16 个用例。
  • ./gradlew :app:assembleDebug:BUILD SUCCESSFUL。
  • git diff --check clean;git diff 482ba34 HEAD --name-only 无 server/ 或 web/ 命中。
  • 确认未复用 PurchaseOutboxUploader(全 diff 无该符号),未继承其无限重试缺陷。
  • 主工作区无关改动全程未被触碰(本工单在独立 worktree 完成)。

逐项核对结果

防重入(本工单最高风险项,审核阶段指出 TaskExecutionMutex.tryAcquire 对同一 ID 可重入):
OrderBackfillGuard 用 AtomicBoolean.compareAndSet(false, true) 做原子闸门,再取共享 TaskExecutionMutex,取锁失败时回滚 active,finally 释放。测试 mutex occupied rejects and simultaneous double tap has one winner and finally releases 使用 CountDownLatch + Executors 构造真并发,不是顺序调用。问题已正确解决。

安全点击边界:BackfillPagePolicy.safe() 三重判据——控件自身可点可见有尺寸、子树不含 11 个高危词、且不与屏幕上任何含高危词的可见控件几何重叠。第三条比工单要求更严,直接以坐标重叠排除,覆盖「确认收货与查看物流同行仅隔 240px」的实测风险。另有 validate() 在遇登录/验证码/风控词时立即停止。测试 dangerous nodes ancestors and overlapping actions never become click targets 覆盖。

时序自检:OrderBackfillWindow.observe() 要求连续观察至少 5 单(ORDERING_SAMPLE)、期间时间严格递减且无缺失,才允许按时间提前停止;一旦时间回升或出现缺失即禁用提前停止,改为扫至内部上限并输出「列表非严格倒序,已改为有界扫描,可能未覆盖全部」。源码注释「a sorted prefix cannot prove the unvisited tail is sorted」说明理解了该判据的边界。测试 time reversal and missing times disable early stop、unordered list scans to internal cap and reports incomplete 覆盖。

跨单残留:每单只在「唯一后缀 + 唯一订单号 + 至多一个时间」时才形成条目,BackfillDetailReader.finish() 清空全部累加器。测试 ambiguous and noncanonical suffixes are skipped 覆盖。

下单时间取值:正则原样复用,ORDER_TIME 只认「下单时间/创建时间」前缀,不会误取「拼单时间」。测试 expanded detail reads order time not group time across frames 覆盖跨帧场景。

隐私:后缀正则 _cg[1-9][0-9]*(?![0-9A-Za-z_0-9]) 排除前导零与全角数字;测试 payload contains only suffix order and optional RFC3339 time 断言上传载荷不含地址全文、收件人或手机号;untagged order never becomes retained or uploaded candidate 断言无后缀订单不留存不上传。

错误分类:测试 permanent business errors never retry regardless of server retryable flag —— 永久性业务错误码不重试,且不信任服务端返回的 retryable 标志,比工单要求更保守;瞬时错误最多 3 次并使用稳定 transport request id。

内部上限:200 单 / 10 分钟 / 每详情最多 6 轮滚动 / 连续无进展最多 3 次。

未验证(关键,验收前必须知道)

所有页面识别判据均未经真机验证。 实施方无法连接手机(本工单禁止 adb),list()、cards()、detail()、expansion() 四个判据都是基于工单记录的实测证据推导的,未在设备上跑通。具体风险:

  • list() 要求存在 label == "全部" 且 selected == true 的节点。审核阶段的真机 dump 只确认了「打包中」被选中时该 tab selected=true,未验证「全部」tab 被选中时是否同样置位。若 PDD 不置该属性,扫描会直接不启动。
  • cards() 依赖商品区可点击容器与按钮行不发生几何重叠。审核阶段单次 dump 显示商品区 y=710-974、按钮行 y=1114-1207,确实不重叠,但这是单一样本、单一订单状态。
  • expansion() 要求同时存在「订单编号」节点与唯一的「商品快照」节点,且「展开」位于两者之间。审核阶段 dump 与此一致。

上述判据全部 fail-closed(识别不到即停止并报告未完整扫描),不会误操作,但意味着功能可能在真机上直接跑不起来,真机联调是可用性的前置条件。

一个实际使用上的取舍

ORDERING_SAMPLE = 5 意味着必须连续读到 5 单带时间且严格递减,才允许按天数提前停止。若指定天数内订单不足 5 单,样本凑不齐,扫描会一直进行到内部上限(200 单或 10 分钟)。即「填 2 天」不等于「很快结束」。这是保守设计的代价,不是缺陷,但使用前应知晓。

文档

线上 Business-Rules-and-Glossary 已更新并回读,revision 1eb380d07183157a8430c7ced868479e72fbdfe3,新增人工触发只读扫描、三者互斥与防重入、时序自检与未完整标记、只读安全边界、跳过无后缀订单与隐私边界等长期规则。harness.py sync 与 sync --check 通过。

未执行

  • 未合并到 main,未部署,未安装 APK。分支已推送至 origin/feat/242-agent-order-backfill。
  • 未连接手机、未执行 adb、未领取或重试任何任务、未下单、未支付。
  • 真机验证与原型确认待人工授权后单独进行。

依赖提醒

本分支基于 #241 分支,两者需一并合并;单独合并 #242 会因缺少服务端端点而不可用。

已完成并推送,待验收。 ## 实现 分支 `feat/242-agent-order-backfill`(独立 worktree),基于 #241 分支顶端 `482ba34`(#241 尚未合并到 main)。 - `edd1cb1 feat(android): add read-only order backfill with bounded scanning (#242)` —— 11 个 Android 文件。 - `ba0e48b` —— Wiki 镜像同步。 ## 审核验证(Claude 独立执行) - `./gradlew :app:testDebugUnitTest --rerun-tasks`:**337 通过,0 失败,0 错误,0 跳过**,新增 16 个用例。 - `./gradlew :app:assembleDebug`:BUILD SUCCESSFUL。 - `git diff --check` clean;`git diff 482ba34 HEAD --name-only` 无 `server/` 或 `web/` 命中。 - 确认**未复用 `PurchaseOutboxUploader`**(全 diff 无该符号),未继承其无限重试缺陷。 - 主工作区无关改动全程未被触碰(本工单在独立 worktree 完成)。 ### 逐项核对结果 **防重入**(本工单最高风险项,审核阶段指出 `TaskExecutionMutex.tryAcquire` 对同一 ID 可重入): `OrderBackfillGuard` 用 `AtomicBoolean.compareAndSet(false, true)` 做原子闸门,再取共享 `TaskExecutionMutex`,取锁失败时回滚 `active`,`finally` 释放。测试 `mutex occupied rejects and simultaneous double tap has one winner and finally releases` 使用 `CountDownLatch` + `Executors` 构造真并发,不是顺序调用。问题已正确解决。 **安全点击边界**:`BackfillPagePolicy.safe()` 三重判据——控件自身可点可见有尺寸、子树不含 11 个高危词、**且不与屏幕上任何含高危词的可见控件几何重叠**。第三条比工单要求更严,直接以坐标重叠排除,覆盖「确认收货与查看物流同行仅隔 240px」的实测风险。另有 `validate()` 在遇登录/验证码/风控词时立即停止。测试 `dangerous nodes ancestors and overlapping actions never become click targets` 覆盖。 **时序自检**:`OrderBackfillWindow.observe()` 要求连续观察至少 5 单(`ORDERING_SAMPLE`)、期间时间严格递减且无缺失,才允许按时间提前停止;一旦时间回升或出现缺失即禁用提前停止,改为扫至内部上限并输出「列表非严格倒序,已改为有界扫描,可能未覆盖全部」。源码注释「a sorted prefix cannot prove the unvisited tail is sorted」说明理解了该判据的边界。测试 `time reversal and missing times disable early stop`、`unordered list scans to internal cap and reports incomplete` 覆盖。 **跨单残留**:每单只在「唯一后缀 + 唯一订单号 + 至多一个时间」时才形成条目,`BackfillDetailReader.finish()` 清空全部累加器。测试 `ambiguous and noncanonical suffixes are skipped` 覆盖。 **下单时间取值**:正则原样复用,`ORDER_TIME` 只认「下单时间/创建时间」前缀,不会误取「拼单时间」。测试 `expanded detail reads order time not group time across frames` 覆盖跨帧场景。 **隐私**:后缀正则 `_cg[1-9][0-9]*(?![0-9A-Za-z_0-9])` 排除前导零与全角数字;测试 `payload contains only suffix order and optional RFC3339 time` 断言上传载荷不含地址全文、收件人或手机号;`untagged order never becomes retained or uploaded candidate` 断言无后缀订单不留存不上传。 **错误分类**:测试 `permanent business errors never retry regardless of server retryable flag` —— 永久性业务错误码不重试,且**不信任服务端返回的 retryable 标志**,比工单要求更保守;瞬时错误最多 3 次并使用稳定 transport request id。 **内部上限**:200 单 / 10 分钟 / 每详情最多 6 轮滚动 / 连续无进展最多 3 次。 ## 未验证(关键,验收前必须知道) **所有页面识别判据均未经真机验证。** 实施方无法连接手机(本工单禁止 adb),`list()`、`cards()`、`detail()`、`expansion()` 四个判据都是基于工单记录的实测证据推导的,未在设备上跑通。具体风险: - `list()` 要求存在 `label == "全部"` 且 `selected == true` 的节点。审核阶段的真机 dump 只确认了「打包中」被选中时该 tab `selected=true`,**未验证「全部」tab 被选中时是否同样置位**。若 PDD 不置该属性,扫描会直接不启动。 - `cards()` 依赖商品区可点击容器与按钮行不发生几何重叠。审核阶段单次 dump 显示商品区 y=710-974、按钮行 y=1114-1207,确实不重叠,但这是单一样本、单一订单状态。 - `expansion()` 要求同时存在「订单编号」节点与唯一的「商品快照」节点,且「展开」位于两者之间。审核阶段 dump 与此一致。 上述判据全部 fail-closed(识别不到即停止并报告未完整扫描),不会误操作,但意味着功能可能在真机上直接跑不起来,**真机联调是可用性的前置条件**。 ## 一个实际使用上的取舍 `ORDERING_SAMPLE = 5` 意味着必须连续读到 5 单带时间且严格递减,才允许按天数提前停止。若指定天数内订单不足 5 单,样本凑不齐,扫描会一直进行到内部上限(200 单或 10 分钟)。即「填 2 天」不等于「很快结束」。这是保守设计的代价,不是缺陷,但使用前应知晓。 ## 文档 线上 `Business-Rules-and-Glossary` 已更新并回读,revision `1eb380d07183157a8430c7ced868479e72fbdfe3`,新增人工触发只读扫描、三者互斥与防重入、时序自检与未完整标记、只读安全边界、跳过无后缀订单与隐私边界等长期规则。`harness.py sync` 与 `sync --check` 通过。 ## 未执行 - 未合并到 main,未部署,未安装 APK。分支已推送至 `origin/feat/242-agent-order-backfill`。 - 未连接手机、未执行 adb、未领取或重试任何任务、未下单、未支付。 - 真机验证与原型确认待人工授权后单独进行。 ## 依赖提醒 本分支基于 #241 分支,两者需一并合并;单独合并 #242 会因缺少服务端端点而不可用。
ila changed title from Agent:采购记录页回填入口与 PDD 订单扫描 to Agent:采购记录页回填入口与 PDD 订单扫描(阶段一:只写本地) 2026-09-10 10:48:19 +08:00
Author
Owner

2026-09-10 阶段拆分决策记录

用户确认把回填拆为两阶段,本工单收敛为阶段一「只写本地」,并要求在现有代码基础上更新。

为什么这么拆是合理的

风险最大的部分恰好不依赖服务端。 目前唯一未验证的是页面识别判据(list() / cards() / detail() / expansion()),这四个判据与服务端无关,本地就能验。

而 #241 引入的 PurchaseTask.BeforeSave 全局钩子会改变存量正式采购流程(影响 lifecycle.go 的 order_created 回传、manual.go 的 ResolveUnknown/Cancel、reset.go 共 6 处既有保存点),且只在 SQLite 上验证过,MySQL 行锁行为未验证。拆开正好把这个风险挡在本阶段之外,服务端零改动。

改动量评估(已核实)

  • OrderBackfillScanner.kt 不引用任何网络层,扫描与上传本就解耦。
  • 上传逻辑独立在 OrderBackfillUpload.kt,共 92 行。
  • 因此本阶段主要是把这 92 行的上传替换为本地写入;扫描、解析、互斥防重入、安全点击边界、时序自检等既有实现全部保留。

本阶段明确不解决的事

数据只落在单台设备本地,Admin 依然显示「尚未取得订单号」,已积压的无单号真单不会减少。按 AGENTS.md,服务端才是事实来源,本地库只能算缓存。本阶段交付的是验证价值,不是业务价值。
这一点必须在验收时如实记录,不得把阶段一的完成表述为回填功能已可用。

阶段二(另议,不在本工单)

待 #241 具备安全部署条件后,新增「读本地表 → 调用 #241 端点 → 按逐条结果更新本地状态」,并处理永久性/瞬时业务错误的重试区分。届时若本阶段的本地记录结构与 #241 请求体对齐,该阶段应只是薄薄一层,不需要改动扫描与解析。

## 2026-09-10 阶段拆分决策记录 用户确认把回填拆为两阶段,本工单收敛为阶段一「只写本地」,并要求在现有代码基础上更新。 ### 为什么这么拆是合理的 **风险最大的部分恰好不依赖服务端。** 目前唯一未验证的是页面识别判据(`list()` / `cards()` / `detail()` / `expansion()`),这四个判据与服务端无关,本地就能验。 而 #241 引入的 `PurchaseTask.BeforeSave` 全局钩子会改变**存量正式采购流程**(影响 `lifecycle.go` 的 `order_created` 回传、`manual.go` 的 ResolveUnknown/Cancel、`reset.go` 共 6 处既有保存点),且只在 SQLite 上验证过,MySQL 行锁行为未验证。拆开正好把这个风险挡在本阶段之外,服务端零改动。 ### 改动量评估(已核实) - `OrderBackfillScanner.kt` 不引用任何网络层,扫描与上传本就解耦。 - 上传逻辑独立在 `OrderBackfillUpload.kt`,共 92 行。 - 因此本阶段主要是把这 92 行的上传替换为本地写入;扫描、解析、互斥防重入、安全点击边界、时序自检等既有实现全部保留。 ### 本阶段明确不解决的事 数据只落在单台设备本地,Admin 依然显示「尚未取得订单号」,已积压的无单号真单不会减少。按 AGENTS.md,服务端才是事实来源,本地库只能算缓存。本阶段交付的是验证价值,不是业务价值。 这一点必须在验收时如实记录,不得把阶段一的完成表述为回填功能已可用。 ### 阶段二(另议,不在本工单) 待 #241 具备安全部署条件后,新增「读本地表 → 调用 #241 端点 → 按逐条结果更新本地状态」,并处理永久性/瞬时业务错误的重试区分。届时若本阶段的本地记录结构与 #241 请求体对齐,该阶段应只是薄薄一层,不需要改动扫描与解析。
Author
Owner

??????:??????????????? TaskHistoryCache/SharedPreferences,??? SQLite;??????????????;??????? addressSuffix?pddOrderNo?orderSubmittedAt,???????????;?????????????????????????,????????/??/?????????????;???????????????????????????;???????????????? outbox;???????????????????????????????,???????????,???????????????????#241 ???????????

??????:??????????????? TaskHistoryCache/SharedPreferences,??? SQLite;??????????????;??????? addressSuffix?pddOrderNo?orderSubmittedAt,???????????;?????????????????????????,????????/??/?????????????;???????????????????????????;???????????????? outbox;???????????????????????????????,???????????,???????????????????#241 ???????????
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#242