# R9 任务报告：post-task 去重串线取消事故

## 结论

本次事故不是 AgentRun 自动控制器执行 sibling task reconcile，也不是后端按投影自动取消。

事故由两段 UniDesk 受控流程缺陷串联产生：

1. Artificer Target create 的 idempotency key 未绑定最终 prompt/payload，且显式 `--idempotency-key` 可以绕过 canonical identity；
2. 外部 post-task 收尾操作按一次 dispatch 归组，并用相同 reason 人为取消三个不同 logical operation。

引入提交为 `43124d01`。该提交建立 Target 只读预检、完整位置上下文和自动 dispatch，整体方向符合当前规格；本次属于局部幂等实现缺陷，不满足整 PR 方向错误的语义回滚条件，因此没有回滚整个提交。

## 离线证据

三个被误取消 task：

- `qt_d214e48f4b0d4d6e899428e2855121ac`：payload hash 为 `e7c8bb1016fad9a2e27fb69b20c9c5c069fe13fb1632edf8c604c3a5ad79c3c3`；
- `qt_7c12fa9735634934a64986a1d45cfc44`：payload hash 为 `b48d75f1d717ff36d640f67b28a44c1f016183b677680e727c3ac27a1aa1e206`；
- `qt_45e692244cbd42a9aa28cc9706f4a28f`：payload hash 为 `bccec985937e047e95b1fed23da15927f60f14d3ac36c58ac7101329487572b6`。

三者在 2026-07-15 09:19:03–09:19:42 UTC 进入 cancelled，failure message 完全相同：

`duplicate create task from one post-task dispatch; preserve qt_c94a19c6bd824551bcb7b22a51c266af`

被保留的 `qt_c94a19c6bd824551bcb7b22a51c266af` payload hash 为 `7904461ada875b99d79c4a98a0d82ba7b75bffa3e4c2524ebcb1f3e2ac2cc15f`，属于 Issue #1888。该 task 随后在 2026-07-15 09:22:16 UTC 以“请求 #2170 却复用旧 #1888 input”原因取消，证明 create 幂等复用已经先发生串线。

仓库源码与 Git 历史中不存在上述 duplicate reason，也不存在按 post-task dispatch 自动扫描和取消 sibling task 的 controller。AgentRun session 事件还证明 #1888 session 自身只取消了它刚误创建且尚无 attempt 的嵌套副本，没有执行本次三项取消。

## 修复

`scripts/src/agentrun/target-task.ts` 现在生成版本化 canonical logical-operation fingerprint，绑定：

- tenant/project；
- Aipod；
- MDTODO；
- Target；
- repository/ref；
- 绝对 targetWorkspace；
- 已核验源码提交；
- 最终 prompt 的 SHA-256 payload hash。

同一 operation 重放生成相同 key；任一 scope 或 payload 变化都会生成不同 key。

Target create 收到显式 `--idempotency-key` 时，只接受与 canonical key 完全一致的值。不同 payload 复用旧 key 会在 render 和 queue mutation 前返回：

- stage：`idempotency-key-conflict`；
- code：`target-task-idempotency-key-conflict`；
- reason：`provided-key-does-not-match-canonical-logical-operation`；
- canonical fingerprint；
- payload hash；
- provided key 的不可逆 fingerprint；
- `mutation=false` 与 `valuesPrinted=false`。

task metadata、dry-run 和 mutation 摘要保留 logical-operation evidence，供事件和 OTel 投影读取，不打印 prompt 或 Secret。

`unidesk-subagent` skill 同步明确：

- 禁止按 dispatch、时间窗口、session、标题或列表邻近关系判定重复；
- identity 不全、投影陈旧或终态不确定时只 warning；
- post-task cleanup 不得取消 sibling task；
- 取消只用于用户明确授权目标或同一 task/attempt 的受控终止。

## 验证

Target 原入口轻量验证：

```text
git diff --check
bun test scripts/src/agentrun.test.ts --test-name-pattern 'Artificer Target'
```

结果：4 个测试通过，0 失败，72 个断言。fixture 覆盖 R8、R7.1、R7.2 与 #1888 四个不同 logical operation，并验证：

- 四个 key 全部不同；
- 同一请求重放 key 稳定；
- prompt 变化会改变 key；
- 显式旧 key 与新 payload 冲突时返回 typed evidence，且不披露原 key。

## OTel 证据边界

事故时间窗的 Tempo 搜索没有找到 queue task cancel HTTP route、task ID 或 cancel reason span。当前 AgentRun durable attempt terminal fact 可证明取消结果和 reason，但不能从 OTel 单独恢复取消调用者。

调查 trace `01fb780dc5184363fea5f8ef859c9031` 仅覆盖本次 R9 只读调查命令，不作为事故调用者证据。修复后的 logical-operation evidence 已进入 task metadata；PR 自动交付后应从新的正常 create/replay 原入口确认对应 span/event 投影。

本任务没有新增锁、租约、第二状态库、数据库迁移或运行面围栏；没有取消任何现存 task，没有执行 runtime patch、人工 CI/CD 或 PR merge。

关联：[UniDesk #2176](https://github.com/pikasTech/unidesk/issues/2176)，MDTODO `R9`。
