Client:安全应用 Admin 规格解析结果并继续采购 #257

Open
opened 2026-08-17 16:30:24 +08:00 by ila · 4 comments
Owner

基本信息

  • 类型:需求
  • 父级大工单:#96(关联 Client Epic #1)
  • 所属 MVP / 版本:#253 采购运行时 AI 规格纠偏闭环
  • 阶段:4. Client 持久化解析并安全继续采购
  • 依赖:#254、#255、#256

要解决什么

#256 只能形成真机候选观察。要在同一次采购中安全继续,Client 还需要通过专用 Gateway 幂等请求 Admin、持久化请求和解析结果、处理网络/AI失败,并在页面可能已经变化的情况下重新读取、精确选择返回候选。该流程发生在真实采购中,不能只在内存里临时替换任务尺码,也不能绕过任务原有安全门禁。

做什么 / 不做什么

  • 做:
    • 扩展 AdminGateway、HTTP 和 Mock,实现 #254 的一次性规格解析命令。
    • 在本地 SQLite 保存解析请求、幂等键、候选哈希、解析记录 ID、结果来源、置信度和最终原始规格;发送前先落库。
    • 在后台设备工作线程中等待有上限的 Admin 响应,不阻塞 Qt 主线程。
    • 只有 Admin 返回 matched 且字段、哈希、候选编号均通过本地校验时才准备继续。
    • 继续前重新读取商品和规格面板,核对商品 ID、已选颜色和候选快照;使用返回的原始候选文字精确点击并验证选中。
    • 后续数量、价格、地址、不可逆标记、单次提交和核单全部复用现有流程。
    • 最终成功或失败结果记录解析 ID和实际采用规格,便于 Admin 审计;未知字段保持向前兼容。
  • 不做:
    • 不在本地运行大模型,不把 Admin API Key 下发 Client。
    • 不使用模型生成文字、模糊坐标或已经变化的候选继续点击。
    • 不无限重试网络或 AI,不在解析失败后自动重新采购。
    • 不在已有不可逆标记的运行中请求解析或重新执行采购;仍然只能核对订单。
    • 不自动付款。

怎么做

  1. 在 Gateway 抽象增加 resolve_purchase_spec(...),HTTP 和 Mock 使用完全相同 DTO、幂等及错误语义;更新双方契约测试。
  2. 增加最小 SQLite migration/Repository 保存解析请求和响应,唯一键覆盖任务、尝试和候选哈希;不得把请求当作普通结果 Outbox,因为当前流程需要响应才能继续,但发送前仍必须持久化。
  3. PurchaseTaskService 在收到 #256 的结构化观察后进入 purchase_resolve_options 步骤,确认本次运行尚无不可���标记,再调用 Gateway。
  4. 对网络超时、认证失败、幂等冲突、uncertain/rejected/failed 和响应字段异常统一安全结束,并通过现有失败 Outbox 上报明确错误码。
  5. 对 matched 响应先持久化,再重新读取页面和候选;候选哈希不同、商品/颜色变化或候选不可点击时停止。
  6. 精确点击并确认后,把本次运行的有效目标规格切换为已审计解析结果;最终确认页仍核对实际规格、数量和订单总价上限。
  7. 崩溃恢复:不可逆前显式重新执行时,只在真机候选哈希相同的情况下复用已持久化结果;不可逆后永久拒绝采购 Adapter,只进入只读核单。

预计修改文件:

  • client/src/admin_gateway.py
  • client/src/http_admin_gateway.py
  • client/src/mock_admin_gateway.py
  • client/src/purchase_task_service.py
  • client/src/task_repository.py
  • client/src/task_models.py
  • client/src/pdd_u2_purchase_adapter.py
  • 相邻 Gateway、Repository、Service 和崩溃恢复测试
  • docs/client/03-data-model.md
  • docs/client/06-quality-security.md
  • 完成后归档 docs/task/<工单号>-client安全应用运行时规格解析.md

验收标准

  • 解析请求发送前已持久化;同一任务、尝试和候选哈希重试使用同一幂等键和内容。
  • HTTP 与 Mock 对 matched、不确定、拒绝、失败、超时、认证和幂等冲突行为一致。
  • Qt 主线程不执行 Admin 请求、uiautomator2、等待或 SQLite 长操作。
  • 只有 matched 且候选编号、原始文字和快照哈希均有效时才继续。
  • 继续前重新读取并核对商品、颜色和候选;候选变化、安全点击缺失或选中未确认时停止。
  • 成功选择后继续执行既有数量、订单总价上限、地址、不可逆标记、单次提交和只读核单门禁。
  • 网络或 AI 失败不循环请求、不自动重新采购,并通过现有失败 Outbox 可追溯上报。
  • 进入过不可逆阶段的任务永久不会因规格解析重新调用采购 Adapter。
  • 最终结果包含解析 ID 和实际规格,但不包含 API Key、原始 XML 或个人数据。
  • Python 3.10 单元测试、契约测试、SQLite 迁移和离屏 UI 冒烟通过。

怎么验证

从 client/ 执行:

C:/Python310/python.exe -m pytest -q
C:/Python310/python.exe -m py_compile src/admin_gateway.py src/http_admin_gateway.py src/purchase_task_service.py src/task_repository.py
$env:QT_QPA_PLATFORM='offscreen'
C:/Python310/python.exe -c "from src.ui_main import MainWindow; from PyQt5.QtWidgets import QApplication; app=QApplication([]); w=MainWindow(); print('OK'); app.quit()"
Remove-Item Env:QT_QPA_PLATFORM

使用 Mock 覆盖全部响应与崩溃点;使用脱敏 XML 验证页面变化。真实设备执行留给 #258,不作为普通自动测试。

风险和回退

  • 风险:等待 AI 时页面发生变化。响应返回后必须从头重新读取并验证候选哈希,不使用缓存坐标。
  • 风险:崩溃后重复请求或重复下单。请求幂等且本地持久化;不可逆标记规则保持最高优先级。
  • 风险:把解析结果误写回原任务。只保存执行期有效目标和审计引用,任务原始 payload 永久保留。
  • 回退:关闭 Gateway 调用和继续分支,恢复规格不匹配即失败;本地解析记录保留,不清理任务或不可逆历史。

2026-08-18 本地联调缺陷:规格解析超时预算不一致

已确认根因

  • Client 的 HttpAdminGateway 让登记、领取、结果提交和规格解析共用默认 3 秒超时。
  • Admin AI 服务商允许等待 30 秒;规格解析 Handler 把 Client 请求上下文直接传给 AI。
  • Client 先在 3 秒关闭连接后,Admin AI 请求随父上下文取消并持久化 failed,Client 只看到 ADMIN_TIMEOUT。这不是 PDD 页面或 Android 设备超时。
  • 现场任务停在 purchase_resolve_options,没有进入不可逆阶段,不会造成重复订单。

用户确认的修复���案

  1. Client 普通 Admin 请求继续使用现有短超时;只有 resolve_purchase_spec 使用独立的 60 秒等待上限。
  2. Admin 运行时采购规格解析中的 AI 调用使用独立的 30 秒等待上限;若服务商配置更短,以更短值为准。
  3. Admin 必须先于 Client 超时返回 matched / uncertain / rejected / failed;Client 超时后仍保持安全失败,不自动循环请求或重新下单。
  4. 不增加页面配置项,不改变登记、领取、结果提交、不可逆标记、单次提交和只读核单门禁。
  5. 同步更新 Client 事实契约和 Admin 接口说明;补充 HTTP/Mock、上下文截止时间和超时安全回归。

补充验收标准

  • Client 规格解析 HTTP 调用的等待上限为 60 秒,其他 Admin 调用仍使用原请求超时。
  • Admin 运行时规格解析 AI 上下文最多 30 秒,且继承更早的请求取消。
  • Admin 在 30 秒内形成安全结论,Client 不会在 Admin 正常预算内提前报 ADMIN_TIMEOUT。
  • 超时和连接取消均不进入不可逆阶段,不自动重复调用采购提交。
  • Python 3.10 Gateway/采购回���、Go 1.23.0 Service/Handler/契约回归通过。
## 基本信息 - 类型:需求 - 父级大工单:#96(关联 Client Epic #1) - 所属 MVP / 版本:#253 采购运行时 AI 规格纠偏闭环 - 阶段:4. Client 持久化解析并安全继续采购 - 依赖:#254、#255、#256 ## 要解决什么 #256 只能形成真机候选观察。要在同一次采购中安全继续,Client 还需要通过专用 Gateway 幂等请求 Admin、持久化请求和解析结果、处理网络/AI失败,并在页面可能已经变化的情况下重新读取、精确选择返回候选。该流程发生在真实采购中,不能只在内存里临时替换任务尺码,也不能绕过任务原有安全门禁。 ## 做什么 / 不做什么 - 做: - 扩展 `AdminGateway`、HTTP 和 Mock,实现 #254 的一次性规格解析命令。 - 在本地 SQLite 保存解析请求、幂等键、候选哈希、解析记录 ID、结果来源、置信度和最终原始规格;发送前先落库。 - 在后台设备工作线程中等待有上限的 Admin 响应,不阻塞 Qt 主线程。 - 只有 Admin 返回 `matched` 且字段、哈希、候选编号均通过本地校验时才准备继续。 - 继续前重新读取商品和规格面板,核对商品 ID、已选颜色和候选快照;使用返回的原始候选文字精确点击并验证选中。 - 后续数量、价格、地址、不可逆标记、单次提交和核单全部复用现有流程。 - 最终成功或失败结果记录解析 ID和实际采用规格,便于 Admin 审计;未知字段保持向前兼容。 - 不做: - 不在本地运行大模型,不把 Admin API Key 下发 Client。 - 不使用模型生成文字、模糊坐标或已经变化的候选继续点击。 - 不无限重试网络或 AI,不在解析失败后自动重新采购。 - 不在已有不可逆标记的运行中请求解析或重新执行采购;仍然只能核对订单。 - 不自动付款。 ## 怎么做 1. 在 Gateway 抽象增加 `resolve_purchase_spec(...)`,HTTP 和 Mock 使用完全相同 DTO、幂等及错误语义;更新双方契约测试。 2. 增加最小 SQLite migration/Repository 保存解析请求和响应,唯一键覆盖任务、尝试和候选哈希;不得把请求当作普通结果 Outbox,因为当前流程需要响应才能继续,但发送前仍必须持久化。 3. `PurchaseTaskService` 在收到 #256 的结构化观察后进入 `purchase_resolve_options` 步骤,确认本次运行尚无不可���标记,再调用 Gateway。 4. 对网络超时、认证失败、幂等冲突、`uncertain/rejected/failed` 和响应字段异常统一安全结束,并通过现有失败 Outbox 上报明确错误码。 5. 对 `matched` 响应先持久化,再重新读取页面和候选;候选哈希不同、商品/颜色变化或候选不可点击时停止。 6. 精确点击并确认后,把本次运行的有效目标规格切换为已审计解析结果;最终确认页仍核对实际规格、数量和订单总价上限。 7. 崩溃恢复:不可逆前显式重新执行时,只在真机候选哈希相同的情况下复用已持久化结果;不可逆后永久拒绝采购 Adapter,只进入只读核单。 预计修改文件: - `client/src/admin_gateway.py` - `client/src/http_admin_gateway.py` - `client/src/mock_admin_gateway.py` - `client/src/purchase_task_service.py` - `client/src/task_repository.py` - `client/src/task_models.py` - `client/src/pdd_u2_purchase_adapter.py` - 相邻 Gateway、Repository、Service 和崩溃恢复测试 - `docs/client/03-data-model.md` - `docs/client/06-quality-security.md` - 完成后归档 `docs/task/<工单号>-client安全应用运行时规格解析.md` ## 验收标准 - [ ] 解析请求发送前已持久化;同一任务、尝试和候选哈希重试使用同一幂等键和内容。 - [ ] HTTP 与 Mock 对 matched、不确定、拒绝、失败、超时、认证和幂等冲突行为一致。 - [ ] Qt 主线程不执行 Admin 请求、uiautomator2、等待或 SQLite 长操作。 - [ ] 只有 matched 且候选编号、原始文字和快照哈希均有效时才继续。 - [ ] 继续前重新读取并核对商品、颜色和候选;候选变化、安全点击缺失或选中未确认时停止。 - [ ] 成功选择后继续执行既有数量、订单总价上限、地址、不可逆标记、单次提交和只读核单门禁。 - [ ] 网络或 AI 失败不循环请求、不自动重新采购,并通过现有失败 Outbox 可追溯上报。 - [ ] 进入过不可逆阶段的任务永久不会因规格解析重新调用采购 Adapter。 - [ ] 最终结果包含解析 ID 和实际规格,但不包含 API Key、原始 XML 或个人数据。 - [ ] Python 3.10 单元测试、契约测试、SQLite 迁移和离屏 UI 冒烟通过。 ## 怎么验证 从 `client/` 执行: ```powershell C:/Python310/python.exe -m pytest -q C:/Python310/python.exe -m py_compile src/admin_gateway.py src/http_admin_gateway.py src/purchase_task_service.py src/task_repository.py $env:QT_QPA_PLATFORM='offscreen' C:/Python310/python.exe -c "from src.ui_main import MainWindow; from PyQt5.QtWidgets import QApplication; app=QApplication([]); w=MainWindow(); print('OK'); app.quit()" Remove-Item Env:QT_QPA_PLATFORM ``` 使用 Mock 覆盖全部响应与崩溃点;使用脱敏 XML 验证页面变化。真实设备执行留给 #258,不作为普通自动测试。 ## 风险和回退 - 风险:等待 AI 时页面发生变化。响应返回后必须从头重新读取并验证候选哈希,不使用缓存坐标。 - 风险:崩溃后重复请求或重复下单。请求幂等且本地持久化;不可逆标记规则保持最高优先级。 - 风险:把解析结果误写回原任务。只保存执行期有效目标和审计引用,任务原始 payload 永久保留。 - 回退:关闭 Gateway 调用和继续分支,恢复规格不匹配即失败;本地解析记录保留,不清理任务或不可逆历史。 ## 2026-08-18 本地联调缺陷:规格解析超时预算不一致 ### 已确认根因 - Client 的 `HttpAdminGateway` 让登记、领取、结果提交和规格解析共用默认 3 秒超时。 - Admin AI 服务商允许等待 30 秒;规格解析 Handler 把 Client 请求上下文直接传给 AI。 - Client 先在 3 秒关闭连接后,Admin AI 请求随父上下文取消并持久化 `failed`,Client 只看到 `ADMIN_TIMEOUT`。这不是 PDD 页面或 Android 设备超时。 - 现场任务停在 `purchase_resolve_options`,没有进入不可逆阶段,不会造成重复订单。 ### 用户确认的修复���案 1. Client 普通 Admin 请求继续使用现有短超时;只有 `resolve_purchase_spec` 使用独立的 **60 秒等待上限**。 2. Admin 运行时采购规格解析中的 AI 调用使用独立的 **30 秒等待上限**;若服务商配置更短,以更短值为准。 3. Admin 必须先于 Client 超时返回 `matched / uncertain / rejected / failed`;Client 超时后仍保持安全失败,不自动循环请求或重新下单。 4. 不增加页面配置项,不改变登记、领取、结果提交、不可逆标记、单次提交和只读核单门禁。 5. 同步更新 Client 事实契约和 Admin 接口说明;补充 HTTP/Mock、上下文截止时间和超时安全回归。 ### 补充验收标准 - [ ] Client 规格解析 HTTP 调用的等待上限为 60 秒,其他 Admin 调用仍使用原请求超时。 - [ ] Admin 运行时规格解析 AI 上下文最多 30 秒,且继承更早的请求取消。 - [ ] Admin 在 30 秒内形成安全结论,Client 不会在 Admin 正常预算内提前报 `ADMIN_TIMEOUT`。 - [ ] 超时和连接取消均不进入不可逆阶段,不自动重复调用采购提交。 - [ ] Python 3.10 Gateway/采购回���、Go 1.23.0 Service/Handler/契约回归通过。
Author
Owner

开始实施。已确认依赖 #254、#255、#256 均已有实现提交。按工单分五层推进:先统一 Gateway DTO/HTTP/Mock,再新增独立 SQLite 解析记录和幂等复用,然后由 PurchaseTaskService 捕获 #256 的结构化观察、发送前落库、单次调用 Admin,最后让 U2 Adapter 重新遍历并核对快照后精确点击原始候选。既有数量、价格、地址、不可逆标记、单次下单和核单门禁保持原路径;不会查询任务状态、无限重试、自动付款或把原始 XML/凭据写入数据库。

开始实施。已确认依赖 #254、#255、#256 均已有实现提交。按工单分五层推进:先统一 Gateway DTO/HTTP/Mock,再新增独立 SQLite 解析记录和幂等复用,然后由 `PurchaseTaskService` 捕获 #256 的结构化观察、发送前落库、单次调用 Admin,最后让 U2 Adapter 重新遍历并核对快照后精确点击原始候选。既有数量、价格、地址、不可逆标记、单次下单和核单门禁保持原路径;不会查询任务状态、无限重试、自动付款或把原始 XML/凭据写入数据库。
Author
Owner

#257 已完成实现并进入待验收。

实现结果

  • Gateway、HTTP 与 Mock 已接入一次性规格解析命令,严格校验响应和候选白名单。
  • SQLite schema 升级到 v6;解析请求在网络发送前落库,响应在继续操作前落库,并保护确定性幂等与冲突。
  • PurchaseTaskService 仅在 #256 的结构化观察出现时调用一次 Admin;非 matched、网络错误或非法响应均安全失败,不自动重试采购。
  • matched 后重新读取并核对商品、颜色和完整候选快照,只按 Admin 返回的页面原文精确选择。
  • 原有数量、订单总价、地址、不可逆标记、单次提交与只读核单门禁保持不变;结果保留原请求规格、实际采用规格和 resolution_id。

验证

  • 定向测试:162 passed。
  • Client 全量:492 passed,2 failed。两项失败均为工作区现有版本号 0.1.2 与旧测试固定期望 0.1.0 不一致,与 #257 无关。
  • Python 编译检查:通过。
  • PyQt5 离屏 MainWindow 冒烟:通过。
  • 真机与真实 Admin 端到端验证未执行,按 #258 处理。
  • 另有既有测试布局问题:不限定目录的 pytest -q 会收集 test/ 与 tools/ 同名脚本产生冲突;本次全量使用 pytest test -q。

提交与归档

  • 实现提交:afc1cdf719a86ebdf1f1bc65371c620c9e0c3964
  • 归档提交:e5ac327d357934b72115171e6ee8de32f7cedaec
  • 归档:docs/task/257-client安全应用运行时规格解析.md

工单保持打开,等待用户验收;验收通过后再关闭并同步父工单。

#257 已完成实现并进入待验收。 ## 实现结果 - Gateway、HTTP 与 Mock 已接入一次性规格解析命令,严格校验响应和候选白名单。 - SQLite schema 升级到 v6;解析请求在网络发送前落库,响应在继续操作前落库,并保护确定性幂等与冲突。 - PurchaseTaskService 仅在 #256 的结构化观察出现时调用一次 Admin;非 matched、网络错误或非法响应均安全失败,不自动重试采购。 - matched 后重新读取并核对商品、颜色和完整候选快照,只按 Admin 返回的页面原文精确选择。 - 原有数量、订单总价、地址、不可逆标记、单次提交与只读核单门禁保持不变;结果保留原请求规格、实际采用规格和 resolution_id。 ## 验证 - 定向测试:162 passed。 - Client 全量:492 passed,2 failed。两项失败均为工作区现有版本号 0.1.2 与旧测试固定期望 0.1.0 不一致,与 #257 无关。 - Python 编译检查:通过。 - PyQt5 离屏 MainWindow 冒烟:通过。 - 真机与真实 Admin 端到端验证未执行,按 #258 处理。 - 另有既有测试布局问题:不限定目录的 `pytest -q` 会收集 `test/` 与 `tools/` 同名脚本产生冲突;本次全量使用 `pytest test -q`。 ## 提交与归档 - 实现提交:`afc1cdf719a86ebdf1f1bc65371c620c9e0c3964` - 归档提交:`e5ac327d357934b72115171e6ee8de32f7cedaec` - 归档:`docs/task/257-client安全应用运行时规格解析.md` 工单保持打开,等待用户验收;验收通过后再关闭并同步父工单。
Author
Owner

#258 联合契约审查发现 #257 的遗漏:MockAdminGateway._validate_spec_observation 尚未完整执行权威契约 §7.1 的字段长度/控制字符、observed_at 时区、请求体 64 KiB,以及 selected_color / target_size 必须来自领取任务原规格的校验;真实 Admin 会拒绝这些请求,Mock 却可能接受。

该修复仍属于本工单“HTTP 与 Mock 使用完全相同 DTO、幂等及错误语义”的原验收范围,不改变接口或采购结果。将补齐 Mock 校验和回归测试,单独提交引用 #257,然后返回 #258 的共享契约向量与端到端验收。

#258 联合契约审查发现 #257 的遗漏:`MockAdminGateway._validate_spec_observation` 尚未完整执行权威契约 §7.1 的字段长度/控制字符、`observed_at` 时区、请求体 64 KiB,以及 `selected_color / target_size` 必须来自领取任务原规格的校验;真实 Admin 会拒绝这些请求,Mock 却可能接受。 该修复仍属于本工单“HTTP 与 Mock 使用完全相同 DTO、幂等及错误语义”的原验收范围,不改变接口或采购结果。将补齐 Mock 校验和回归测试,单独提交引用 #257,然后返回 #258 的共享契约向量与端到端验收。
Author
Owner

2026-08-18 实施完成:对齐跨端超时预算

根因与修复方案已补入本工单正文并完成实施。

  • Client 仅对 resolve_purchase_spec 使用 60 秒等待上限,其他 Admin 请求继续使用原短超时。
  • Admin 仅对采购运行时 AI 使用 30 秒上限;服务商配置更短时以更短值为准,并继承更早取消。
  • 超时继续安全失败,不增加轮询,不改变候选顺序,不绕过采购安全门禁。
  • 已同步 Client/Admin 接口契约。

提交:6345b00(实现)、02be709(归档)。

验证:Admin 的格式、全量测试、构建和 go vet 全部通过;Client 定向 19 项通过,全量 500 项通过、2 项既有版本号断言失败,排除既有失败后 480 项通过;PyQt5 离屏 UI 冒烟通过。

未执行真实 Admin + Android 真机完整联调,也未触发下单或付款。重测前需运行最新 Admin/Client,并确认 AI 服务商连接测试成功且处于启用状态。

## 2026-08-18 实施完成:对齐跨端超时预算 根因与修复方案已补入本工单正文并完成实施。 - Client 仅对 `resolve_purchase_spec` 使用 60 秒等待上限,其他 Admin 请求继续使用原短超时。 - Admin 仅对采购运行时 AI 使用 30 秒上限;服务商配置更短时以更短值为准,并继承更早取消。 - 超时继续安全失败,不增加轮询,不改变候选顺序,不绕过采购安全门禁。 - 已同步 Client/Admin 接口契约。 提交:`6345b00`(实现)、`02be709`(归档)。 验证:Admin 的格式、全量测试、构建和 `go vet` 全部通过;Client 定向 19 项通过,全量 500 项通过、2 项既有版本号断言失败,排除既有失败后 480 项通过;PyQt5 离屏 UI 冒烟通过。 未执行真实 Admin + Android 真机完整联调,也未触发下单或付款。重测前需运行最新 Admin/Client,并确认 AI 服务商连接测试成功且处于启用状态。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: chengma/cmautobuy#257