采购同任务重试规则哈希不一致:任务长期假待执行 #159

Closed
opened 2026-08-29 17:27:27 +08:00 by ila · 3 comments
Owner

原始需求摘要

用户于 2026-08-29 发现 CG-27 第一次采购失败后,在 Agent 点击“重试采购”,任务主状态变成“待执行”但不再进入 PDD。用户确认重试采购应复用当前采购任务,只新增执行 attempt,不创建新采购任务,并要求新建缺陷工单修复。

当前事实与根因

  • CG-27 第 1 次 attempt 已失败;同任务重试创建第 2 次 pending attempt,符合 #155 的同任务重试语义。
  • Agent 可以领取任务,任务 lease_version 持续增加,但 Start 前 attempt 始终没有 start_request_id。
  • 第 2 次 attempt 的 rule_snapshot_hash 与数据库中 purchase_task.rule_snapshot 的实际哈希不一致。
  • Reset 对保存前的 DefaultLiveRule() 原始字节计算哈希;MySQL JSON 列保存后会规范化表示;Start 对数据库读回值重新计算哈希,因此返回“待执行 attempt 与当前任务不一致”。
  • Agent 只暂时显示 Start 错误,随后被心跳覆盖;服务端任务保留 pending 租约,形成“领取 → Start 失败 → 租约到期 → 再领取”的假待执行循环。
  • Gitea MCP 当前会话不可用,按仓库规则回退 Gitea API 创建和回写工单;凭据仅从 Git 安全配置读取,不输出、不落盘。

目标

  1. 失败采购任务重试继续复用原 purchase_task,只新增 attempt。
  2. 重试 attempt 的规则快照哈希必须基于数据库持久化后读回的规则快照,保证 MySQL JSON 规范化后与 Start 一致。
  3. Claim → Start 对重试 attempt 可正常进入 running。
  4. 若 pending attempt 与任务规则快照仍不一致,必须把该 attempt 和任务收敛为可见失败并释放租约/运行占位,禁止长期假 pending 与自动重复领取。
  5. 保留旧 attempt 历史,不创建新任务,不修改商品、规格、数量、价格和地址快照。
  6. CG-27 存量状态修复作为本工单的高风险实施步骤,代码完成后必须再次取得用户明确授权才执行。

非目标

  • 不创建订单、不执行支付、不触发真机正式采购。
  • 不改变 Admin 批量重试或替换商品后继续采购的“新建任务”语义。
  • 不增加数据库表、字段或迁移。
  • 不修改 Android/UI/API 请求字段。

依赖与并行

  • 关联 #155(同任务重试采购)。
  • 与 #158 售罄识别无代码依赖;CG-27 尚未进入 PDD,根因发生在服务端 Start 前。
  • 服务端代码与测试可先实施;CG-27 数据修复必须等待单独授权。

实施方案

  • Reset 保存任务规则后,通过同一事务重新读取持久化任务,再以读回的 RuleSnapshot 计算 attempt 哈希。
  • 抽取统一的规则快照哈希函数供 Reset 与 Start 使用,避免计算口径漂移。
  • Start 检测 pending attempt 的设备或规则哈希不一致时,在事务内把 attempt 标记为 failed、写入稳定错误码/限长信息,同时把任务置为 failed、清理 lease、claim、device/account run slot;提交失败事实后返回可识别错误,不能因返回错误导致事务回滚。
  • 增加 Reset/Start 回归测试,验证同一 task ID、attempt 递增、哈希一致、正常启动及异常收敛。
  • 存量 CG-27 在授权后标记第 2 次 attempt 技术失败、主任务 failed 并释放租约;之后由用户再次点击重试生成第 3 次 attempt。

验收标准

  • 同任务失败重试不创建新的 purchase_task。
  • 重试后的 pending attempt 哈希等于持久化任务规则快照哈希。
  • Claim → Start 成功后任务和最新 attempt 均为 running。
  • 人为制造哈希不一致时,任务与 attempt 均落成 failed,错误码可见,租约与运行占位清空;再次 Next 不再返回该失败任务。
  • 原失败 attempt 保留不变。
  • 服务端相关单测与构建通过。

风险与安全

  • 采购为高风险域;测试只使用事务/测试数据库,不触发设备或 PDD。
  • CG-27 修复后再次点击重试可能进入正式采购并创建订单,必须由用户主动操作;永久禁止支付。
  • CG-27 数据状态修改必须再次获得明确授权。

设计证据

恢复既有服务端状态机行为,无 UI/页面结构变化,按规则免原型。

文档影响

预期无长期文档影响:同任务重试与失败可见性规则已在 Wiki 中记录,本单恢复已确认行为。若实施导致长期契约变化,再更新对应 Wiki。

## 原始需求摘要 用户于 2026-08-29 发现 CG-27 第一次采购失败后,在 Agent 点击“重试采购”,任务主状态变成“待执行”但不再进入 PDD。用户确认重试采购应复用当前采购任务,只新增执行 attempt,不创建新采购任务,并要求新建缺陷工单修复。 ## 当前事实与根因 - CG-27 第 1 次 attempt 已失败;同任务重试创建第 2 次 pending attempt,符合 #155 的同任务重试语义。 - Agent 可以领取任务,任务 `lease_version` 持续增加,但 `Start` 前 attempt 始终没有 `start_request_id`。 - 第 2 次 attempt 的 `rule_snapshot_hash` 与数据库中 `purchase_task.rule_snapshot` 的实际哈希不一致。 - `Reset` 对保存前的 `DefaultLiveRule()` 原始字节计算哈希;MySQL JSON 列保存后会规范化表示;`Start` 对数据库读回值重新计算哈希,因此返回“待执行 attempt 与当前任务不一致”。 - Agent 只暂时显示 Start 错误,随后被心跳覆盖;服务端任务保留 pending 租约,形成“领取 → Start 失败 → 租约到期 → 再领取”的假待执行循环。 - Gitea MCP 当前会话不可用,按仓库规则回退 Gitea API 创建和回写工单;凭据仅从 Git 安全配置读取,不输出、不落盘。 ## 目标 1. 失败采购任务重试继续复用原 `purchase_task`,只新增 attempt。 2. 重试 attempt 的规则快照哈希必须基于数据库持久化后读回的规则快照,保证 MySQL JSON 规范化后与 `Start` 一致。 3. `Claim → Start` 对重试 attempt 可正常进入 running。 4. 若 pending attempt 与任务规则快照仍不一致,必须把该 attempt 和任务收敛为可见失败并释放租约/运行占位,禁止长期假 pending 与自动重复领取。 5. 保留旧 attempt 历史,不创建新任务,不修改商品、规格、数量、价格和地址快照。 6. CG-27 存量状态修复作为本工单的高风险实施步骤,代码完成后必须再次取得用户明确授权才执行。 ## 非目标 - 不创建订单、不执行支付、不触发真机正式采购。 - 不改变 Admin 批量重试或替换商品后继续采购的“新建任务”语义。 - 不增加数据库表、字段或迁移。 - 不修改 Android/UI/API 请求字段。 ## 依赖与并行 - 关联 #155(同任务重试采购)。 - 与 #158 售罄识别无代码依赖;CG-27 尚未进入 PDD,根因发生在服务端 Start 前。 - 服务端代码与测试可先实施;CG-27 数据修复必须等待单独授权。 ## 实施方案 - `Reset` 保存任务规则后,通过同一事务重新读取持久化任务,再以读回的 `RuleSnapshot` 计算 attempt 哈希。 - 抽取统一的规则快照哈希函数供 `Reset` 与 `Start` 使用,避免计算口径漂移。 - `Start` 检测 pending attempt 的设备或规则哈希不一致时,在事务内把 attempt 标记为 failed、写入稳定错误码/限长信息,同时把任务置为 failed、清理 lease、claim、device/account run slot;提交失败事实后返回可识别错误,不能因返回错误导致事务回滚。 - 增加 Reset/Start 回归测试,验证同一 task ID、attempt 递增、哈希一致、正常启动及异常收敛。 - 存量 CG-27 在授权后标记第 2 次 attempt 技术失败、主任务 failed 并释放租约;之后由用户再次点击重试生成第 3 次 attempt。 ## 验收标准 - 同任务失败重试不创建新的 `purchase_task`。 - 重试后的 pending attempt 哈希等于持久化任务规则快照哈希。 - `Claim → Start` 成功后任务和最新 attempt 均为 running。 - 人为制造哈希不一致时,任务与 attempt 均落成 failed,错误码可见,租约与运行占位清空;再次 Next 不再返回该失败任务。 - 原失败 attempt 保留不变。 - 服务端相关单测与构建通过。 ## 风险与安全 - 采购为高风险域;测试只使用事务/测试数据库,不触发设备或 PDD。 - CG-27 修复后再次点击重试可能进入正式采购并创建订单,必须由用户主动操作;永久禁止支付。 - CG-27 数据状态修改必须再次获得明确授权。 ## 设计证据 恢复既有服务端状态机行为,无 UI/页面结构变化,按规则免原型。 ## 文档影响 预期无长期文档影响:同任务重试与失败可见性规则已在 Wiki 中记录,本单恢复已确认行为。若实施导致长期契约变化,再更新对应 Wiki。
Author
Owner

实施完成,等待高风险步骤授权

代码修复已完成并推送 main,提交:8afbdbc。

实现

  • Reset 保存最新正式采购规则后,在同一事务重新读取数据库持久化后的 purchase_task,再按读回的 RuleSnapshot 生成 pending attempt 哈希;不再对 MySQL JSON 规范化前的原始字节计算哈希。
  • Reset 与 Start 复用同一规则快照哈希函数,避免计算口径漂移。
  • Start 若发现 pending attempt 的设备或规则哈希与任务不一致,会在同一事务把 attempt 与任务标记为 failed,写入既有 PURCHASE_STATE_CONFLICT 和明确错误信息,并清理 lease、claim、device/account run slot;事务提交后再向 Agent 返回错误,避免错误返回导致失败事实回滚。
  • 同任务重试语义不变:继续复用原 purchase_task,只新增 attempt,不修改商品、规格、数量、价格和地址快照。

验证

  • go test ./app/goauto/purchase -run 'TestPurchase(Reset|Start)' -count=1:通过。
  • go test ./app/goauto/purchase -count=1:通过。
  • .\scripts\verify.ps1 -Component server:通过(go test ./... 与 go build)。
  • python dev_scripts/harness.py check --strict:通过。
  • 新增回归覆盖:重试 attempt 哈希使用持久化快照;Claim → Start 正常进入 running;人为制造哈希不一致时任务/attempt 明确失败、租约和占位清空、Next 不再重复派发。

文档影响

无长期文档影响:未新增 API 字段或错误码,使用既有 PURCHASE_STATE_CONFLICT;同任务重试与失败可见性规则未变化,因此按规则跳过 Wiki 更新与同步。

尚未执行

  • 未重启/发布 Admin API。
  • 未修改 CG-27 的生产数据。当前 CG-27 第 2 次 attempt 仍可能被旧服务重复领取并保持假 pending。
  • 未触发真机正式采购、未创建订单、未支付。

下一步需用户明确授权:重启 Admin API,并把 CG-27 第 2 次 attempt 与主任务安全收敛为失败、清理租约/运行占位。修复后由用户自行点击“重试采购”生成同任务第 3 次 attempt;该操作可能进入正式采购并创建订单。

Gitea MCP 当前会话不可用,本工单创建与回写回退 Gitea API;凭据未输出、未落盘。

## 实施完成,等待高风险步骤授权 代码修复已完成并推送 `main`,提交:`8afbdbc`。 ### 实现 - `Reset` 保存最新正式采购规则后,在同一事务重新读取数据库持久化后的 `purchase_task`,再按读回的 `RuleSnapshot` 生成 pending attempt 哈希;不再对 MySQL JSON 规范化前的原始字节计算哈希。 - `Reset` 与 `Start` 复用同一规则快照哈希函数,避免计算口径漂移。 - `Start` 若发现 pending attempt 的设备或规则哈希与任务不一致,会在同一事务把 attempt 与任务标记为 `failed`,写入既有 `PURCHASE_STATE_CONFLICT` 和明确错误信息,并清理 lease、claim、device/account run slot;事务提交后再向 Agent 返回错误,避免错误返回导致失败事实回滚。 - 同任务重试语义不变:继续复用原 `purchase_task`,只新增 attempt,不修改商品、规格、数量、价格和地址快照。 ### 验证 - `go test ./app/goauto/purchase -run 'TestPurchase(Reset|Start)' -count=1`:通过。 - `go test ./app/goauto/purchase -count=1`:通过。 - `.\scripts\verify.ps1 -Component server`:通过(`go test ./...` 与 `go build`)。 - `python dev_scripts/harness.py check --strict`:通过。 - 新增回归覆盖:重试 attempt 哈希使用持久化快照;`Claim → Start` 正常进入 running;人为制造哈希不一致时任务/attempt 明确失败、租约和占位清空、`Next` 不再重复派发。 ### 文档影响 无长期文档影响:未新增 API 字段或错误码,使用既有 `PURCHASE_STATE_CONFLICT`;同任务重试与失败可见性规则未变化,因此按规则跳过 Wiki 更新与同步。 ### 尚未执行 - 未重启/发布 Admin API。 - 未修改 CG-27 的生产数据。当前 CG-27 第 2 次 attempt 仍可能被旧服务重复领取并保持假 pending。 - 未触发真机正式采购、未创建订单、未支付。 下一步需用户明确授权:重启 Admin API,并把 CG-27 第 2 次 attempt 与主任务安全收敛为失败、清理租约/运行占位。修复后由用户自行点击“重试采购”生成同任务第 3 次 attempt;该操作可能进入正式采购并创建订单。 Gitea MCP 当前会话不可用,本工单创建与回写回退 Gitea API;凭据未输出、未落盘。
Author
Owner

已执行授权的重启与 CG-27 状态修复

用户已明确授权:重启 Admin API,并将 CG-27 第 2 次 attempt 和主任务安全收敛为失败、清理租约及运行占位。

执行前门禁

  • CG-27 主任务:pending;第 2 次 attempt:pending,尚无 start_request_id / result_request_id。
  • irreversible_at、order_submit_request_id、pdd_order_no、order_submitted_at 均为空,确认未进入创建订单不可逆边界。
  • 暂停 Admin API 并确认 8000 端口无监听后执行数据修复,避免 Agent 并发领取。

数据修复

使用带完整前置条件的单条 MySQL 多表更新,原子修改 purchase_task.id=27 与 purchase_task_attempt.id=28, attempt_number=2;受影响行数为 2:

  • 主任务与第 2 次 attempt 均置为 failed。
  • 写入既有错误码 PURCHASE_STATE_CONFLICT 与“采购规则快照校验失败,请重新重试任务”。
  • 清空 lease_expires_at、claim_request_id、active_slot、device_run_slot、account_run_slot。
  • 第 1 次失败 attempt 完整保留;没有创建新采购任务,没有修改商品/规格/数量/价格/地址快照。

服务验证

  • 使用提交 8afbdbc 启动 goauto-admin-api。
  • Supervisor:Running。
  • 实际服务端口由本机配置解析为 8010;curl --noproxy '*' http://127.0.0.1:8010/api/v1/health 返回 HTTP 200。本机设置了 HTTP 代理,未绕过代理时的“响应提前结束”不是 API 故障。
  • API 重启后再次回读:CG-27 保持 failed,第 2 次 attempt 保持 failed,租约、领取标记与三个占位均为 cleared,未被 Agent 再次派发。

后续验收

请用户在 Agent 刷新 CG-27 详情,确认状态为“采购失败”且可以点击“重试采购”。再次点击会在同一 CG-27 下创建第 3 次 attempt,并可能进入正式采购、修改地址及创建待付款订单;必须由用户主动操作,系统永久禁止支付。

## 已执行授权的重启与 CG-27 状态修复 用户已明确授权:重启 Admin API,并将 CG-27 第 2 次 attempt 和主任务安全收敛为失败、清理租约及运行占位。 ### 执行前门禁 - CG-27 主任务:`pending`;第 2 次 attempt:`pending`,尚无 `start_request_id` / `result_request_id`。 - `irreversible_at`、`order_submit_request_id`、`pdd_order_no`、`order_submitted_at` 均为空,确认未进入创建订单不可逆边界。 - 暂停 Admin API 并确认 8000 端口无监听后执行数据修复,避免 Agent 并发领取。 ### 数据修复 使用带完整前置条件的单条 MySQL 多表更新,原子修改 `purchase_task.id=27` 与 `purchase_task_attempt.id=28, attempt_number=2`;受影响行数为 2: - 主任务与第 2 次 attempt 均置为 `failed`。 - 写入既有错误码 `PURCHASE_STATE_CONFLICT` 与“采购规则快照校验失败,请重新重试任务”。 - 清空 `lease_expires_at`、`claim_request_id`、`active_slot`、`device_run_slot`、`account_run_slot`。 - 第 1 次失败 attempt 完整保留;没有创建新采购任务,没有修改商品/规格/数量/价格/地址快照。 ### 服务验证 - 使用提交 `8afbdbc` 启动 `goauto-admin-api`。 - Supervisor:`Running`。 - 实际服务端口由本机配置解析为 8010;`curl --noproxy '*' http://127.0.0.1:8010/api/v1/health` 返回 HTTP 200。本机设置了 HTTP 代理,未绕过代理时的“响应提前结束”不是 API 故障。 - API 重启后再次回读:CG-27 保持 `failed`,第 2 次 attempt 保持 `failed`,租约、领取标记与三个占位均为 cleared,未被 Agent 再次派发。 ### 后续验收 请用户在 Agent 刷新 CG-27 详情,确认状态为“采购失败”且可以点击“重试采购”。再次点击会在同一 CG-27 下创建第 3 次 attempt,并可能进入正式采购、修改地址及创建待付款订单;必须由用户主动操作,系统永久禁止支付。
Author
Owner

用户于 2026-08-29 明确确认本工单通过验收。验收结论已记录,现关闭工单。没有新的长期事实变化,本次不重复同步 Wiki。

用户于 2026-08-29 明确确认本工单通过验收。验收结论已记录,现关闭工单。没有新的长期事实变化,本次不重复同步 Wiki。
ila closed this issue 2026-08-29 20:44:45 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#159