Skip to content

fix(engine): 为终态事件保留原始回合及提交归属 - #97

Merged
asto18089 merged 5 commits into
pinvou3-cleanfrom
codex/engine-terminal-correlation-r14
Oct 12, 2026
Merged

asto18089 merged 5 commits into
pinvou3-cleanfrom
codex/engine-terminal-correlation-r14

Conversation

@zhuowp

@zhuowp zhuowp commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

No-Issue: 本 fork 已禁用 Issues;此问题由 Pinvou/pinvou3#1325 的 A08 审核追踪,本 PR 提供根因、修复及验证,不修改仓库设置、不自动关闭宿主 PR。

A08 宿主审核确认:TurnComplete 缺少生产者捕获的原始归属,宿主无法可靠地区分当前提交、自治回合、启动前拒绝及手动压缩;从 advisory Error 或当前 busy 状态推断会误收口其他回合。本 PR 只修复可复用的底座契约,宿主的实例围栏及接纳/收口改造另行处理。

  • 已启动模型回合在 Completed/Failed/Interrupted 中返回与 TurnStarted 一致的 turn_id 和原 submission_id;自治回合不借用宿主 token。
  • Send/Edit 启动前拒绝返回无 turn_id 的原提交失败终态;保留原有 Edit 和 Send 的 goal 收口路径。
  • 手动 compaction 返回独立 compaction_id,覆盖路由拒绝和取消;自动压缩仍归属模型回合,Purge 不携带这些 ID。
  • wire 投影保留三种归属;新增字段 absent/default 兼容旧数据。迁移仓内构造器/匹配及测试夹具,补充契约文档和回归测试。
  • 修复独立审核确认的 TUI outbox 回归:终态只使用本次事件捕获的 turn_id,启动前拒绝及手动压缩不再借用上一模型回合 ID;显示及远控投影保留原状态。
  • 经明确批准,以独立提交清理 4 处已有 Clippy 报错(UTF-16 分组、冗余闭包、Copy clone),不新增 lint 豁免、不修改安全策略或门禁。

清理、终态契约及独立审核发现的 outbox 修复分别保留为三个签署提交;请采用保留提交历史的 merge 方式。本 PR 仅创建供人工审核,不自动合并、发布或传播到宿主后续波次。

Testing

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or -A clippy::collapsible_if -A clippy::assertions_on_constants
  • cargo test --workspace --all-features --locked(未运行全 workspace/all-features 运行时套件;不将编译门禁冒充运行时覆盖)

本地环境:Windows 原生 gnullvm、Rust 1.98.1/clang,locked/offline 构建。测试进程显式使用现有 Python、pwsh 和同一 gnullvm rustc,以避开 WindowsApps Python 占位符、旧 PowerShell 编码及默认 MSVC 缺 link.exe;没有改变产品逻辑。

原契约候选 28be9569 实际运行:1787 passed / 0 failed / 6 个既有 ignored(默认 features),不重复计算聚焦测试。明细:Engine 564、Runtime Threads 160、Runtime API 165、Remote Control 76、TUI 685、protocol parity 6、child env 22、shell dispatcher 21、web locale receipt 1;protocol 单元 70 + 集成 17。4 处清理只在完整组合候选上验证,未单独构建 cleanup-only tree。

本轮 outbox 修复:最终 TUI 套件 686 passed / 0 failed(已包含 1 项新增回归,覆盖模型完成、未启动失败、手动压缩 3 种情况,不另加计);另外复跑原契约所有权与 wire parity 12 passed / 0 failed。生产编译、格式及官方完整 Clippy 均通过。未将未重跑的原套件计为本轮候选运行。

回归有效性:复审后将 outbox 接线改为从真实 EngineEvent::TurnComplete 捕获专用收据,生产调用不再接收可误传的 app.runtime_turn_id。临时把捕获逻辑改为在缺少事件 turn_id 时回退 stale-turn,测试在 rejected.turn_id.is_none() 断言真实失败(0 passed / 1 failed);恢复修复后通过。composer shell 测试同时断言终态 turn_id 等于已观测的 start id,且 submission_id、compaction_id 均为 None。曾故意去掉终态 submission_id,4 个所有权断言真实失败(非超时/环境失败),恢复后全部通过。独立审核 CODE、INDEX、FIXED 通过。

Checklist

  • Updated docs or comments as needed
  • Added or updated tests where relevant
  • 不新增 layer/module/第二套 turn loop,不增加 prompt/context contributor。
  • 无 UI 布局/交互改动;TUI 除事件匹配迁移外,修正生命周期 outbox 的终态归属并增加回归测试,未做人工交互冒烟。
  • 无 harvested/co-authored credit。

已知边界:Rust 事件构造器新增字段,仓外源码消费者需迁移;wire 新字段保持 optional/default。MSRV 1.88 的 API 可用性已核对,但本地未实际以 1.88 编译。ID 本身不提供 engine-incarnation 安全性,宿主仍须独立围栏及匹配;本 PR 不表示 A08 宿主问题已全部修复。

zhuowp added 3 commits October 9, 2026 23:06
Signed-off-by: z27014 <zhuo.wenpei@h3c.com>
Signed-off-by: z27014 <zhuo.wenpei@h3c.com>
Signed-off-by: z27014 <zhuo.wenpei@h3c.com>
@zhuowp

zhuowp commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

独立审核修复结论:初审确认 1 项 P2,TUI 生命周期 outbox 在未启动失败时会借用上一已完成模型回合 ID,造成错误的 turn.failed 归属。已由与审核者分离的实现角色修复:直接采用本次 TurnComplete 捕获的 turn_id,未启动失败和手动压缩保留 None,不回退到 app.runtime_turn_id。

修复提交:91843c7618cd03a624540bc14ac31deefb1324e9。独立 CODE / INDEX / FIXED 均通过,完整差异及终态消费者未发现其他确认的 P1/P2。原提交保留,不自动合并。

验证:完整 TUI 686 passed / 0 failed;新增 1 项回归覆盖 3 种归属情况,已包含在 686 中。恢复旧逻辑的真实 RED 准确失败于 rejected.turn_id.is_none(),修复版 GREEN 通过;生产 check、fmt、官方完整 Clippy 全部通过。以上为固定本地候选证据,新 head 的远端 CI 尚在运行,不借用旧 head 的 CI 结论。

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

从零重审(7-lane 全新评审)

全新从零重审:7 个独立 lane(引擎全部 12 处 TurnComplete 发射点逐一核验 / wire 协议双向兼容 / TUI outbox 修复 / 全仓消费方普查 / 测试有效性 / 正文声明与夹带审计 / 契约文档 + 父仓影响),叠加本地门禁与 2 组变异实证。分支已基于最新 pinvou3-clean(merge-base = 0bc868f = 当前 tip),无需 rebase。

核心结论:修复真实、已到根因,且顺带治愈了 base 上三个真实缺陷。 12 处发射点逐处核验均符合新契约:started 回合(含中断与启动后失败)回显自己的 TurnStarted.turn_id 与原始 submission_id;启动前拒绝(restricted 动态工具、restricted edit、edit 三分支、route 安装失败)只带被拒 submission_id、无 turn_id;手动压缩六处终端(含路由拒绝与取消)只带 compaction_id;purge 两处全 None;自启(goal 续跑、后台 shell 唤醒、子代理完成续跑)一律 None,不借宿主 token;无任何路径产生双终态;route 失败重排不可观测(被移动的 sync_unstarted_goal_for_terminal_projection 不发事件)。wire 双向兼容成立:serde default + skip_serializing_if、全仓无 deny_unknown_fields、仓内唯一 wire 生产者(protocol_parity)已同步且原来硬编码 turn_id: None 的说谎点被一并修掉。ARCHITECTURE.md 新契约逐条与实现一致(含 purge/自动压缩归属)。本 PR 还实修了 base 三个缺陷:headless exec 在 route 拒绝时永久挂起(exec_agent.rs 只等 Error 循环)、runtime monitor 对 route 拒绝的 send/compact 永不收口、TUI lifecycle outbox 把压缩/拒绝终端记到上一模型回合头上。

无夹带。 三个提交分工与声明一致(cleanup 最老 → 契约 → outbox),全部 DCO 签署;cleanup 提交恰为声明的 4 处。我在 base 上用当前 stable clippy(1.98.0)复跑同口径命令:manual_as_chunks×2、redundant_closure、redundant_clone 四处在 base 全部真实报错——"既有告警"成立(这三个 lint 已从 nursery 转正;CI 绿是因 CI 的 stable 落后于本地,属仓库级工具链偏移,与本 PR 无关)。as_chunks 于 1.88 稳定,与 workspace rust-version = "1.88" 恰好相容。

本地验证:fmt 绿;clippy 仅剩 3 处 base 既有 print_stderr(#63 引入,不属本 PR 范围);cargo test --workspace --all-features 12035 通过 / 4 失败,4 个失败在 base 逐一复现(2 处沙箱 no_new_privs 本机环境失败、2 处全量并行 flake 隔离跑双端通过),与本 PR 无关;5 个新增测试全绿;两个新引擎测试的 FIFO 顺序假设、超时与 env 锁用法均核过,无 flake 风险。对照组变异实证:把 5885 终端的 submission_id 改成 None 后,terminal_correlation_started_failure_matches_its_original_submission 与 forkguard_turn_started_echoes_submission_id_self_starts_stay_none 双双转红——引擎侧钉是真咬的。正因如此,下面 R1 的缝隙才显得突出。

Required

R1(outbox 修复的生产缝隙无测试守护;正文变异声明的覆盖面与实情不符)
第二条修复提交的全部意义在 event_loop.rs:2592 调用点改传事件捕获的 turn_id。我把该调用点变回 app.runtime_turn_id.clone()(解构改 turn_id: _,即完整复原 pre-PR bug)后重跑:turn_end_outbox_uses_the_terminal_events_owner 依然通过——现有套件没有任何测试会红。正文 "临时恢复旧 outbox 回退后,新测试在 rejected.turn_id.is_none() 断言真实失败(0 passed / 1 failed)" 只有把变异做在 helper 函数体内才能复现(helper 测试直接传参,不经过 run_event_loop;我已实证 helper 体内变异确实在该断言红)。也就是说,声称的回归保护没有覆盖被修复的那一行代码。请处理:首选让接线"构造即正确"(例如 helper 直接收事件或由 helper 完成从事件取 id 的全部逻辑,使 helper 测试自然覆盖取值来源),或补一条驱动调用点的最小集成测试;无论采用哪种,请同时修正 Testing 段该句表述,注明变异作用在 helper 层。

R2(composer ! shell 终端归属未钉)
engine.rs:2274 是与 5885 互相独立的发射点,带自己的字面量 submission_id: None。既有测试 forkguard_turn_started_composer_shell_self_start_stays_none 的 TurnComplete 臂是 { status, error, .. }(tests.rs:10446),只盯 status,三个新字段完全漂移自由。请在该臂补三行断言:turn_id == Some(观测到的 start id)、submission_id: None、compaction_id: None,封闭这个独立可漂移点(顺带兑现其注释里 "cannot swap … without failing here" 对终端侧的承诺)。

Minor(不阻塞,供斟酌)

  • M1:新 ARCHITECTURE.md 要求宿主 "must match an owned receipt before closing",但仓内两个内嵌宿主(runtime_threads.rs:9734 monitor、remote_control.rs:2292)都是首终端即收口、不做 receipt 匹配。今天安全(引擎串行、每操作一终端),但文档与仓内实现自相矛盾;建议跟进改造或在文档标注仓内宿主现状。
  • M2:拒绝终态现在会走完整收尾链:turn_end observer hook(observer_hooks.rs:148-167)以派发期 turn id + model_backed=true 报告一个从未启动的回合,hook 消费方无法区分"启动前拒绝"与"派发后失败";另有零 usage 行入 /cache 与成本审计、遥测 Turns 计数与 observe_turn_secs(0)、快照 CompletedCommit + checkpoint 清除、每次拒绝一次余额 fetch、队首消息被 pop。多数无害(且对压缩/purge 家族本就如此),但 hook 元数据一项建议至少披露或修正。
  • M3:outbox 的 kind/status 仍取自 app.runtime_turn_status(同事件在 2177 行先行覆写、2590 行读取,中间约 400 行无写点——今天正确,但不变量纯靠代码位置维持)。建议直接用解构出的 status 枚举计算 kind,使"不可借 app 态"与 turn_id 一样成为局部事实。
  • M4:ARCHITECTURE.md 三处措辞空隙:composer shell 回合类未列(有 turn_id、无 submission_id 的合法形状);"emits a failed terminal with the rejected submission_id" 对 runtime provenance(本就无 token)的拒绝不成立;"自启失败只发 advisory Error、完全无终态"未明说。均建议顺手补一句。
  • M5:commit subject 为中文,与 main 近 200 条英文 + (#NN) 惯例不一致;wire 可见字段变更未进 CHANGELOG(近期实践如此,不算违规,提请知悉)。
  • M6(NIT):exec_agent.rs:885 outbox 终端 turn_id 硬编码 None(事件现在带 Some,可回显);remote_control 自有投影不透传新字段(无 parity 声明,可接受)。

父仓影响(披露,非本 PR 缺陷)

wire 层 additive 安全,TS 前端零引用。但父仓以 path 依赖直编 codewhale-tui:features/assistant/forwarder.rs:986 穷尽 match 与 strict_mode_validation_tests.rs 两处构造在 gitlink 越过本 PR 后会编译失败,需与宿主侧改造(pinvou3#1325 A08)协调落地;gitlink 前移本身是 fork-policy §6.2 常规流程。本 PR 属 CW 上游 PR,无需 fork-guard 登记(已核 fingerprints 不受影响)。

上述 R1/R2 落地后我即可改发 approve;Minors 可自选跟进。

Signed-off-by: z27014 <zhuo.wenpei@h3c.com>
@JensenChen28

Copy link
Copy Markdown
Collaborator

已按复审 Required R1/R2 修复并推送 cef5fdee8:

  • R1:outbox 终态收据现在直接从真实 EngineEvent::TurnComplete 捕获为专用类型,生产调用不再接收可误传的 app.runtime_turn_id;同步移除 status 对 app 态的依赖。反向变异为缺失事件 turn_id 回退 stale-turn 后,回归测试在 rejected.turn_id.is_none() 处按预期失败(0 passed / 1 failed),恢复后 1 passed / 0 failed。PR Testing 已改为准确描述变异位置。
  • R2:composer ! shell 测试已保存 TurnStarted.turn_id,并断言终态 turn_id 相同、submission_id=None、compaction_id=None。聚焦测试 1 passed / 0 failed。

额外验证:cargo +1.98.0 fmt --all -- --check、cargo +1.98.0 check -p codewhale-tui --lib --locked、git diff --check 均通过。请复审。

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh re-review 2026-10-12

Rebase 核对:PR head cef5fdee8 已正好坐在 pinvou3-clean 最新 tip(0bc868ff0)之上,rebase 为空操作、无冲突。本轮为从头完整重审:5 条并行专项评审 lane(engine 归属 / wire 协议 / TUI 事件循环 / 消费者回归 / scope 与测试)+ 主审亲手验证;本地 cargo test -p codewhale-tui -p codewhale-protocol --locked 全绿,CI 全绿。

结论:Approve。 上轮 R1/R2 已验证修复;本轮未发现新的 BLOCKER/MAJOR。

上轮 R1/R2 修复实证

  • R1(outbox 归属):TurnEndLifecycleReceipt 现于分发前从真实 EngineEvent::TurnComplete 捕获(event_loop.rs:1682,2620-2626 消费),生产路径不再接收 app.runtime_turn_id。我独立重放变异(把收据来源改回 app.runtime_turn_id.clone()):turn_end_outbox_uses_the_terminal_events_owner 真实在 rejected.turn_id.is_none() 处红(0 passed / 1 failed),恢复后绿。
  • R2(composer shell 终态断言):forkguard_turn_started_composer_shell_self_start_stays_none 现保存 TurnStarted.turn_id,断言终态同一且 submission_id/compaction_id 均 None。
  • 另独立重放 engine 侧变异(reject_unstarted_submission 丢弃 submission_id):terminal_correlation_preflight_failures_keep_each_submitted_owner 真实在 (None, None) != (Some("sub-restricted-send"), None) 处红。2/2 变异独立复核真实有效。

根因与完整性

根因扎实:base 有 4 条提交生命周期路径只发 advisory Error、无权威终态(base 2946 restricted+dynamic tools、3650 restricted edit、5362 route not ready、6012 compaction route rejection),宿主只能悬挂 busy 或误归属;本 PR 全部补上带归属的终态。仓内 12 处 TurnComplete 构造点逐一核对契约相符(composer shell / started 失败 / 正常终态 / 6 处手动 compaction / 2 处 purge 全 None);自治回合结构上无法继承宿主 token(submission_id 是 per-dispatch 局部量,无存储字段)。宿主侧围栏/收口改造按 A08 边界另行处理,披露清楚。

优雅性

遵循仓内既有范式(TurnStarted.submission_id 的同形 additive 字段 + round-trip 测试);reject_edit_last_turn 复用新 helper 并保留 Edit 的 goal 收口路径;无第二 turn loop、无 prompt contributor。小瑕疵:receipt status 经历 enum→str→kind 双重映射,_ => "turn.ended" 臂运行时不可达(见 MINOR 5)。

走私检查

4 个提交边界干净。cleanup 2fdf2b7d0 4 hunk 逐一等价(as_chunks::<2>() 与 chunks_exact(2) remainder 处理一致;web_search 去 clone 处为 Copy 类型;.any fn 引用等价),无夹带。功能提交无范围外内容。唯一瑕疵:cef5fdee8 标题为"补齐回归覆盖"但携带 TurnEndLifecycleReceipt 生产重构(正文已披露,见 MINOR 2)。

缺陷扫描

仓内全部消费者核实安全:remote_control 有 active_run 早退守卫,且引擎 op 严格串行处理保证拒绝终态只能落在回合之间;runtime_threads monitor 把 base 的"被拒 op 悬挂不结算"变为确定性 Failed 结算(修复 base 挂起);exec 无头模式对 route 拒绝获得正确退出码(base 落 channel-closed 兜底);声音/通知仅 Completed 触发不受影响;wire 无 deny_unknown_fields + serde default 双向兼容。无热路径性能变化(仅终态事件上的 Option clone)。

MINOR(不阻塞)

  1. 宿主迁移点请随父仓 bump wave 明确带走:Event::TurnComplete 字段加宽会使 pinvou3-app 编译失败——pinvou3-app/src-tauri/src/features/assistant/forwarder.rs:986-992(无 .. 的穷尽模式)与 features/assistant/strict_mode_validation_tests.rs:128-135、156-162(字面构造)。正文"仓外源码消费者需迁移"已是概括披露,建议把这几处具体站点补进正文,父仓推进 gitlink 的 PR 同波携带(父仓 CI 会拦,但列明成本最低)。
  2. cef5fdee8 提交信息低估其内容(携带 event_loop.rs:29-62、2617-2628 的生产重构)。无需重写历史,后续读码时留意即可。
  3. crates/tui/src/core/protocol_parity.rs:1213-1215:doc 注释 "The guard is the exhaustive match…" 原本描述 protocol_covers_engine_events,新测试插入后现 rustdoc 挂在 manual_compaction_terminal_keeps_its_operation_id_in_projection 上,与内容不符。
  4. 手动 compaction 的运行中取消路径(engine.rs:6131、6161)没有断言 compaction_id 的测试;已钉的是排队取消(tests.rs:207)与 route 拒绝(tests.rs:139)。
  5. run_event_loop 内 capture(1682)→emit(2617-2627)的接线无集成测试(单测覆盖 capture+helper;正文披露的变异点也是 capture)。未来若出现重新借用 app.runtime_turn_id 的接线回归,无测试会红。
  6. 新拒绝终态现在会触发 TurnEnd observer hook(completed_turn: None、零 usage)、telemetry Turns 计数、并向 /cache 历史推入零用量行(event_loop.rs:2340、2592-2611)。与"TurnComplete=权威终态"语义一致,确认是有意即可。
  7. NIT:ARCHITECTURE.md 新小节夹在 Module Organization 的两个 crate 清单之间,放 Key Design Decisions 更合适;event_loop.rs:31-34 关于保留 runtime_turn_id 的注释理由写"saved-session 与 remote-control 投影",实际终态后读者是 activity/receipt 展示与剪贴板 yank;正文"不新增 lint 豁免"仅对 cleanup 提交成立(新增测试局部 #[allow(clippy::await_holding_lock)] 与 17 处既有兄弟测试一致,合理)。

感谢作者高质量的变异验证披露;本轮独立重放与其声称的红/绿逐项一致。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants