文档:核实删调用身份守卫的前置判据,收敛第 5 步口径

- ADR §9 记录核实结论:取消兜底本来就同时释放占用、补写 turn.completed 并踢队列,删守卫不需要新增无条件终态;连接死亡的失败事实由 app-server 连接层落地
- 实施计划第 5 步改写:60 秒启动窗口闸门改挂 Thread Manager 的占用登记年龄,读者改读 direct_active_turn_id_at,TurnAlreadyRunning 一并删
- decision-log 追加 2026-09-30 条目,记录核实过程、决策与影响范围
This commit is contained in:
2026-09-30 10:30:16 +08:00
parent 25f3e41623
commit 41c03d7199
3 changed files with 47 additions and 13 deletions
@@ -85,9 +85,15 @@ Thread Manager 在一个回合收口**之后**原子地做:取队首 → 登
- CLI 一退,`DirectTurnError::TurnAlreadyRunning` 的两个生产点(调用身份守卫、占用登记)都没有调用方,
它连同前端"同一轮消息仍在处理中"文案、专属分支与测试一起删。
- `DirectTaonierActiveInvocationGuard` 的**身份**与 Thread Manager 的 `active_turn.turn_id` 是同一件事的两份记录
(GUI 路径下同源字符串),而 09-23 ADR 立的是"同一件事只许有一处真相"。CLI 退役后它的硬阻塞消失:第二步让那五个读者
改读 Thread Manager 的活动回合身份,守卫连同它的测试与 60 秒卡死兜底一起删。删之前必须确认取消路径的无条件终态
仍然解得开"回合卡死"(那次兜底来自真实事故 `d833ca9d3`)。
(GUI 路径下同源字符串),而 09-23 ADR 立的是"同一件事只许有一处真相"。CLI 退役后它的硬阻塞消失:五个读者
(`direct_execution` / `direct_tool_bridge` / `direct_validation` / `direct_project_context` / 付费美术重生成)
改读 Thread Manager 的活动回合身份,守卫连同它的测试一起删,不再有第二份进程内记录。
- 删守卫的前置判据**已核实(2026-09-30)**,不需要新增"取消路径补无条件终态":`d833ca9d3` 的兜底路径本来就
同时做三件事——释放占用、往事件流补一条 `turn.completed{aborted}`(否则前端会永远停在运行中)、调
`complete_direct_thread_turn` 解除占用。删掉的只有 `DirectTaonierActiveInvocationGuard` 那张进程内表;
唯一独有的东西是 60 秒启动窗口闸门,改挂在 Thread Manager 的占用登记年龄上
(`direct_stale_turn_for_release` 的 `NeverReachedExecutor` 分支)。连接死亡那条失败事实由 app-server
连接层自己落地(`ab970b9fd`),与守卫无关。
## 备选方案与取舍
@@ -9614,3 +9614,24 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
失去"真实二进制驱动执行层生产验证"这条手工路径。
- 验证方式(设计稿,尚未实施):`docs/adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md`、
`docs/technical/【实施计划】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md`。
## 2026-09-30 守卫清理前置判据核实:取消兜底不需要新增无条件终态,60 秒闸门改挂占用登记年龄
- 背景:按 09-24 ADR §9 的计划,删 `DirectTaonierActiveInvocationGuard` 前必须确认"回合任务 park / 泄漏时,
取消仍能清空占用并让队列继续放行"。查 git 历史(`d833ca9d3` / `ab970b9fd`)与当前实现后确认:
`cancel_direct_codex_turn_at` 的兜底路径本来就同时做三件事——释放占用、往事件流补一条
`turn.completed{aborted}`(否则前端会永远停在运行中)、调 `complete_direct_thread_turn` 解除占用并踢队列。
所以删守卫**不需要**新增"取消路径补无条件终态",那是当时就有的东西,不是守卫带来的。
- 决策:守卫只在两件事上是独有的——(1) 它是"这一轮是谁"的第二份进程内记录(与 Thread Manager 的
`active_turn.turn_id` 同源),CLI 退役后已无第二个入口,按"同一件事只许有一处真相"删;
(2) 它的 60 秒启动窗口闸门(防"刚放行、还在本地准备的回合被终止误伤")保留,改挂在 Thread Manager
的占用登记年龄上:`direct_stale_turn_for_release(root, expected, reason)` 只在
`NeverReachedExecutor` 时校验年龄(`DIRECT_STALE_TURN_RELEASE_MIN_AGE_MS`),返回身份;释放本身仍由
既有的 `complete_direct_thread_turn` + `kick_direct_queue_dispatch` 完成(天然幂等)。
- 决策:连接死亡的失败事实由 app-server 连接层自己落地(`ab970b9fd`),与守卫无关,删除守卫不动那条链路。
- 影响范围:`agent/direct_runtime/mod.rs`(删守卫表 / 只读探测,改为 `direct_active_turn_id_at`)、
`agent/direct_thread_manager.rs`(`DirectTurnIdentity` + `read_direct_turn_identity`)、
`agent/codex_app_server/mod.rs`(兜底目标改 `DirectStaleTurnReleaseReason`)、五个身份读者与相关测试、
删 `DirectTurnError::TurnAlreadyRunning` 与前端对应分支 / 生成绑定。
- 验证:`cargo test --bin genarrative-ai-game-creator-shell -- agent::direct`;`npx vitest run tests/appSurface.test.ts`;
`npm --workspace apps/ai-game-creator-shell run typecheck`;`npm run check:encoding`;`git diff --check`。
@@ -2,7 +2,7 @@
更新时间:`2026-09-24`
状态:**实施中**(第 0–3 步已落地;第 4、5 步待做)
状态:**实施中**(第 0–4 步已落地;第 5 步进行中)
设计口径见 [`【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24`](../adr/【ADR】DirectProject命令入队化与待发消息队列归宿主-2026-09-24.md)。
本文件只排实施顺序、不变式与验收,不重复设计理由。
@@ -16,7 +16,7 @@
| 第 2 步 队列归 Thread Manager | 已落地 | `StoredEvent.pending` 产物字段 + `enqueue_pending_turn` / `remove_pending_turn` / `claim_pending_turn`;放行在 `agent/direct_turn_dispatch.rs`(`kick_direct_queue_dispatch` + `DirectTurnReservation`) |
| 第 3 步 前端收口 | 已落地 | 见下面「第 3 步的落地细则」 |
| 第 4 步 CLI 与夹具退役 | 已落地 | 删 `CliCommand::DirectCodexChat`(变体 / `project_path_mut` / 解析 / 派发)、`run_direct_game_creator_turn_at` 一对包装函数与夹具脚本;顺带删掉只剩测试在用的 `direct_turn_error_boundary_text`(判据只剩 `direct_turn_enqueue_failure` 一处),三条边界测试改打 `direct_turn_enqueue_failure(...).message`。**`TurnAlreadyRunning` 挪到第 5 步**(它最后一个生产点在调用身份守卫里) |
| 第 5 步 守卫清理 | 待做 | |
| 第 5 步 守卫清理 | 进行中 | 前置判据已核实;改读 Thread Manager 活动回合身份、删调用身份守卫与其测试,并删 `TurnAlreadyRunning` |
## 第 0 步:词表切换(与代码同批,不单独提交)
@@ -155,14 +155,21 @@ kick 幂等(并发两次只认领一次);队首在放行后被移除、rem
## 第 5 步:守卫清理(CLI 退役之后)
- 五个身份读者(`agent/direct_execution.rs`、`agent/direct_tool_bridge.rs`、`agent/direct_runtime/mod.rs` 的付费美术重生成、
`agent/direct_validation.rs`、`agent/direct_project_context.rs`)改读 Thread Manager 的活动回合身份
(新增一个按项目路径取 `active_turn.turn_id` 的只读入口)。
- 删 `DirectTaonierActiveInvocationGuard` 与 `release_stale_direct_taonier_active_invocation` 及其测试
(`direct_runtime/mod.rs` 的守卫用例、`direct_project_context.rs`、`direct_tool_bridge.rs`、`direct_tools_mcp.rs`、
`user_input.rs`、`codex_app_server/mod.rs` 的集成用例),取消路径改为直接走无条件的 `complete_direct_thread_turn("aborted")`。
- 前置判据:补一条测试证明"回合任务被 park / 泄漏时,取消仍能清空占用并让队列继续放行"——那次 60 秒兜底来自真实事故
(`d833ca9d3`),删它必须有等价保证。
> 前置判据**已核实(2026-09-30)**:`d833ca9d3` 的取消兜底本来就同时做三件事——释放占用、往事件流补一条
> `turn.completed{aborted}`(否则前端永远停在运行中)、解除 `complete_direct_thread_turn` 占用;所以删守卫
> **不需要**新增任何"取消路径补无条件终态"。连接死亡那条失败事实由 app-server 连接层自己落地(`ab970b9fd`),
> 也与守卫无关。守卫唯一独有的东西是 60 秒启动窗口闸门,改挂在 Thread Manager 的占用登记年龄上。
- 五个身份读者(`agent/direct_execution.rs`、`agent/direct_tool_bridge.rs`、`agent/direct_validation.rs`、
`agent/direct_project_context.rs`、`agent/direct_runtime/mod.rs` 的付费美术重生成)改读
`direct_active_turn_id_at`(Thread Manager 的活动回合,只读、不改占用)。
- 删 `DirectTaonierActiveInvocationGuard`、`DirectActiveTurnView`、`read_direct_taonier_active_invocation_at` 与
`release_stale_direct_taonier_active_invocation` 及它们的测试;`codex_app_server` 的兜底目标改
`DirectStaleTurnReleaseReason`,释放本身仍由既有的 `complete_direct_thread_turn` + `kick_direct_queue_dispatch` 完成。
- 删 `DirectTurnError::TurnAlreadyRunning`(最后一个生产点在守卫里)与前端"同一轮消息仍在处理中"文案、专属分支、
生成绑定与 `tests/appSurface/project-conversation.suite.ts` 的对应断言。
- 前置判据测试:`cancel_direct_codex_turn_at` 在"回合任务泄漏、app-server 侧没有可中断句柄"时仍能解除占用,
并把队首放行出去(`codex_app_server` 的兜底用例 + `direct_stale_turn_for_release` 的单测)。
## 验收与证据