Repository navigation
fix(engine): 为终态事件保留原始回合及提交归属 - #97
Conversation
Signed-off-by: z27014 <zhuo.wenpei@h3c.com>
Signed-off-by: z27014 <zhuo.wenpei@h3c.com>
Signed-off-by: z27014 <zhuo.wenpei@h3c.com>
|
独立审核修复结论:初审确认 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
left a comment
There was a problem hiding this comment.
从零重审(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:9734monitor、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:885outbox 终端 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>
|
已按复审 Required R1/R2 修复并推送
额外验证: |
asto18089
left a comment
There was a problem hiding this comment.
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(不阻塞)
- 宿主迁移点请随父仓 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 会拦,但列明成本最低)。 cef5fdee8提交信息低估其内容(携带 event_loop.rs:29-62、2617-2628 的生产重构)。无需重写历史,后续读码时留意即可。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上,与内容不符。- 手动 compaction 的运行中取消路径(engine.rs:6131、6161)没有断言
compaction_id的测试;已钉的是排队取消(tests.rs:207)与 route 拒绝(tests.rs:139)。 run_event_loop内 capture(1682)→emit(2617-2627)的接线无集成测试(单测覆盖 capture+helper;正文披露的变异点也是 capture)。未来若出现重新借用app.runtime_turn_id的接线回归,无测试会红。- 新拒绝终态现在会触发 TurnEnd observer hook(
completed_turn: None、零 usage)、telemetry Turns 计数、并向 /cache 历史推入零用量行(event_loop.rs:2340、2592-2611)。与"TurnComplete=权威终态"语义一致,确认是有意即可。 - 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 处既有兄弟测试一致,合理)。
感谢作者高质量的变异验证披露;本轮独立重放与其声称的红/绿逐项一致。
Summary
No-Issue: 本 fork 已禁用 Issues;此问题由 Pinvou/pinvou3#1325 的 A08 审核追踪,本 PR 提供根因、修复及验证,不修改仓库设置、不自动关闭宿主 PR。
A08 宿主审核确认:
TurnComplete缺少生产者捕获的原始归属,宿主无法可靠地区分当前提交、自治回合、启动前拒绝及手动压缩;从 advisoryError或当前 busy 状态推断会误收口其他回合。本 PR 只修复可复用的底座契约,宿主的实例围栏及接纳/收口改造另行处理。TurnStarted一致的turn_id和原submission_id;自治回合不借用宿主 token。compaction_id,覆盖路由拒绝和取消;自动压缩仍归属模型回合,Purge 不携带这些 ID。turn_id,启动前拒绝及手动压缩不再借用上一模型回合 ID;显示及远控投影保留原状态。清理、终态契约及独立审核发现的 outbox 修复分别保留为三个签署提交;请采用保留提交历史的 merge 方式。本 PR 仅创建供人工审核,不自动合并、发布或传播到宿主后续波次。
Testing
cargo fmt --all -- --checkcargo 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_constantscargo 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
已知边界:Rust 事件构造器新增字段,仓外源码消费者需迁移;wire 新字段保持 optional/default。MSRV 1.88 的 API 可用性已核对,但本地未实际以 1.88 编译。ID 本身不提供 engine-incarnation 安全性,宿主仍须独立围栏及匹配;本 PR 不表示 A08 宿主问题已全部修复。