Skip to content

feat(eval): 完成统一评测平台与 GAIA 官方适配器 - #305

Merged
JensenChen28 merged 29 commits into
mainfrom
codex/cli-gaia-benchmark
Aug 24, 2026
Merged

feat(eval): 完成统一评测平台与 GAIA 官方适配器#305
JensenChen28 merged 29 commits into
mainfrom
codex/cli-gaia-benchmark

Conversation

@JensenChen28

@JensenChen28 JensenChen28 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

背景

项目需要一套可复用的本地 benchmark 平台,并以固定版本的 GAIA validation Level 1 作为首个官方适配器,同时保证私有题目、附件、预测和提交文件不会进入公开运行产物或越过受限工具边界。

变更

  • 新增独立的 pinvou-cli workspace:benchmark core、Smoke 适配器、GAIA 适配器、产品 backend 与 typed CLI。
  • 实现固定 revision/digest/schema 的 GAIA 数据集获取、离线验证、运行、恢复、官方评分和 JSONL submission。
  • 增加私有预测持久化与恢复后评分:Windows DPAPI,Unix 私有权限,公开 outcome 仅保存 opaque handle。
  • 将产品 headless runtime 接入评测专用逐轮工具策略,包括只读 File action 投影、ShellPolicy::ReadOnly 和最终分发复检。
  • GAIA 输出使用 gaia-final/v1 契约;缺失最终答案时记录 missing_final_answer,完整运行评分后才发布 report.mdscore.json
  • 使用进程级执行锁、可恢复 JSONL 和计划对账,避免并发重复执行、损坏尾记录及规划阶段中断后漏跑任务。
  • CodeWhale PR fix: 统一侧边栏定时任务记录样式 #15 已合并为 d127aed113529dc93754d044b9f352e9746f6b83,并发布为不可变标签 pinvou-v0.9.5-r8;父仓 gitlink、fork guard 与中英文 fork 文档均已对齐。

隔离边界

  • pinvou-cli 是独立 workspace,不加入桌面 Tauri workspace。
  • 桌面默认 feature 不启用 benchmark-hooks;主工程发布 workflow 不包含 CLI 制品。
  • 普通 GUI 会话删除、任务完成通知、会话 timing 和启动恢复语义保持现状;评测运行时只通过显式 feature 和 headless bridge 接入。
  • 按当前范围,不新增或启用 CLI CI / 发布链路;仓库内 benchmark jobs 保持硬关闭,不进入主工程 PR 或 Merge Queue 门禁。

验证

  • cargo test --manifest-path pinvou-cli/Cargo.toml --locked --all-features -p agent-backend-api -p benchmark-core -p adapter-smoke -p adapter-gaia:153 个测试通过。
  • cargo test --manifest-path pinvou-cli/Cargo.toml --locked -p pinvou-cli --no-default-features:22 个测试通过。
  • cargo test -p codewhale-tui --lib --locked forkguard_ -- --test-threads=1:34 个测试通过。
  • python3 -m unittest scripts.tests.test_ci_gate_policy scripts.tests.test_benchmark_isolation_policy scripts.tests.test_headless_boot_contract:22 个测试通过。
  • ./scripts/verify-public-submodule.sh./scripts/fork-guard.sh --fastpython3 scripts/architecture-guard.py、格式和 diff 检查:通过。

已知限制

  • 未执行真实 gated Hugging Face 下载;下载完整性由固定 revision、远端 metadata、LFS/Git blob digest 与流式预算契约测试覆盖。
  • Windows 原生附件执行保持 fail closed;含附件任务在执行前返回 attachments_platform_security_unsupported,当前不声明 Windows GAIA 端到端实跑支持。
  • 当前 head 的完整 product-backend --all-features --locked 冷编译在本机因磁盘空间耗尽未完成;锁文件解析、core/adapter/无 product-backend 测试及 CodeWhale 安全回归均已完成。

@JensenChen28
JensenChen28 requested review from asto18089, cacdcaecawae and h3c-hexin and removed request for asto18089 and cacdcaecawae August 17, 2026 09:13
Comment thread pinvou-cli/crates/adapter-gaia/src/fetch/windows_private_acl.rs Fixed
@JensenChen28

Copy link
Copy Markdown
Collaborator Author

已同步 CodeWhale PR #15 的评审修复并更新 gitlink 到 169c24cc5,父仓提交为 46504fa9

父仓 GAIA 受限轮次不再安装 shell-backed ToolCallBefore Hook;fork inventory/guard 已补充排队控制操作、Hook 默认关闭、日志与 audit 脱敏指纹,以及通用 seam 的上游与移除计划。

本地 scripts/fork-guard.sh --fastscripts/architecture-guard.pygit diff --check 均通过。

@JensenChen28
JensenChen28 marked this pull request as ready for review August 18, 2026 01:12
Comment thread pinvou-cli/crates/adapter-gaia/src/fetch/windows_private_acl.rs Fixed
Comment thread pinvou-cli/crates/adapter-gaia/src/fetch/windows_private_acl.rs Fixed
@JensenChen28
JensenChen28 force-pushed the codex/cli-gaia-benchmark branch from d5a0723 to 865323a Compare August 18, 2026 03:29

@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.

评审结论:Request changes

感谢这个庞大而认真的 PR。平台的骨架质量很高——私有预测的 opaque handle + 四元绑定 + DPAPI/Unix 权限复检、fetch/verify 全链路 fail-closed(固定 revision/size/SHA-256、LFS 与 git blob 双轨校验、附件硬编码信任锚)、scorer 对官方 question_scorer 的逐项忠实移植、错误码不泄路径与内容,这些都做得扎实。但本次审阅(已将 PR rebase 到最新 main 并实跑全部测试)发现:PR 没有真正实现它声称的根因——GAIA 端到端评测在当前形态下跑不通,另有多处阻断级缺陷与范围污染。以下问题按严重度排列,均有文件:行号证据。

一、根因不成立(阻断)

1. GAIA 逐轮工具白名单与 v0.9.5 底座目录完全脱节,评测轮实际零工具

eval_tool_policy.rs 的两个 profile 使用 v0.9.3 之前的 per-action 旧名(read_file/list_dir/grep_files/file_search/web_search/fetch_url),而底座 @169c24cc 的注册面只有 canonical 家族名(File/Web,registry.rs:717-720、925-930;底座钉板测试 registry.rs:2453-2476 明确断言旧名不注册、不出现在模型目录)。链路后果:

  • 目录过滤 tool_matches_any_rule(tool_catalog.rs:509-527)只做小写精确名/* 前缀匹配,无别名→家族映射 → 模型目录被过滤到空(offline profile 全灭,public-web 仅剩条件注册的 image_analyze);
  • 模型若调用 canonical File/Web,exact 门禁(tool_execution.rs:390-398,ExactToolDispatchPolicy::allows 为原始名逐字相等,ops.rs:60-62)直接拒绝;
  • 模型若伪造 read_file,底座未注册该执行体,fail-closed 到 "no execution path"。

两条路都死:GAIA 产品运行退化为无工具纯文本回答,带附件题目连附件都读不了。PR 自己的断言测试(eval_tool_policy.rs:151-195 断言 read_file 等在产品目录恰好出现一次)与底座钉板测试正面矛盾——即便编译通过也必败。需要按 canonical 家族名 + action 粒度重新设计白名单(可能涉及 CodeWhale PR #15 的 exact 门禁语义支持 action 级解析),这不是局部修补能完成的。

2. 评分闭环缺少官方语义:无 FINAL ANSWER 指令注入与提取

全仓 grep FINAL ANSWER 零命中。prompt 是 parquet 原始 Question(private_inputs.rs:42-45),持久化的 prediction 是最后一轮 assistant 全文拼接(headless_bridge.rs:882),scorer 拿全文与 ground truth 做归一化等值比较(scorer.rs:98)。设计文档(docs/superpowers/specs/2026-08-13-pinvou-benchmark-platform-design.md §13)自己要求了 FINAL ANSWER 提取与 missing_final_answer 处理,实现缺位。真实运行几乎逐题判错,submission 的 model_answer 是整段对话文本,不具备声称的官方可比性。OutputContract("gaia-final/v1") 是纯标签,无消费点。

二、编译/测试断裂(阻断,均已本地复现)

  1. SessionStore::load_skill_bindings 已被 main 的 #285 移除,PR 仍调用(headless_bridge.rs:991eval_cli.rs:73)→ benchmark-hooksdev-tools feature 编译失败(E0599),pinvou-cli 默认构建(主链路)同样断裂。
  2. cargo test --lib 三处编译错误,主 CI 门禁必挂:engine.rs:2652 base_op() 缺新 gitlink 的 turn_tool_security 字段(E0063);eval_tool_policy.rs:87 import 已不存在的 deepseek_tui::tools::pinvou3_blocklist(E0432);:92 import 私有模块 worker_profile(E0603)。
  3. forwarder.rs:834:904 每轮双重 finish:第一次 finish_turn_with_usage 已 pop 队首,第二次 finish_turn_with_observation 常规轮 no-op——PR 核心观测数据(tool_calls/catalog 摘要)永不落盘;排队双轮场景还会 pop 错下一轮,写出错配的假 assistant_done,前端重读时间线即错乱(GUI 回归)。应删掉旧调用只留 observation 版(注意保持"先落时间线再发 chat:done"的顺序),并补排队双轮测试。
  4. submission.rs:202-233 validate_parent 逐级拒绝 symlink 祖先:macOS 的 /var(temp_dir 祖先)是 symlink,导致 $TMPDIR 下写 submission 永远 gaia_submission_target_unsafe,cargo test -p adapter-gaia 在 macOS 两红(已实测复现)。真正防覆写靠 create_new + hard_link,祖先检查收益低、代价是 macOS 不可用,建议只校验叶子父目录。
  5. tests/headless_bridge_contract.rs:276-285 断言 attachments_runtime_unsupported,但 RecordingRuntime 走 trait 默认实现 bail unsupported_tool_policy,错误映射(headless_bridge.rs:829-843)不覆盖该串 → 非 Windows 必挂。
  6. GAIA 测试自身缺陷(已在本地修复验证):contract_tests.rs/dataset_contract.rsTempSnapshot 用纳秒时间戳命名,macOS 粗粒度时钟下同 tick 撞名,并行测试互相 remove_dir_all,约 1/3 概率随机红;fetch_contract.rs 临时目录未设 0700,Unix 默认 umask 下必触发 fetch 的私有权限契约(ImportFailed)。修复各一行量级,建议作者采纳。

三、CI 门禁缺口(阻断——上述断裂全部因此漏网)

  1. 全仓没有任何 CI job 编译/测试 pinvou-cli workspace(约 3 万行),benchmark-hooks feature 后的 headless_bridge.rs(1558 行)+ headless_bridge_contract.rs(806 行)零编译零运行;adapter-gaia 覆盖最重的 dataset_contract 还挂在 required-features=["test-support"] 下,裸 cargo test 会静默跳过。至少需要:pinvou-cli 的 fmt+clippy+cargo test --all-features 门禁、app 侧 --features benchmark-hooks 的编译与契约测试门禁。
  2. verify-public-submodule.sh --allow-registered-candidatepull_requestmerge_group 都放行未打不可变标签的 gitlink,而 fast-gate 的 job 级条件不含 push——严格标签校验在 CI 中已成死代码,main 的可复现性将依赖 CodeWhale 侧一个可变分支不被 force-push/GC。要么 merge_group 去掉 flag,要么在 fork-modifications.md 写明候选提升为不可变标签(r8)的后续责任与时限。另:fork-guard.sh:98-99 两条新指纹指向的测试(restricted_turn_hooks_require_explicit_host_opt_inrestricted_tool_audit_redacts_private_sentinel)不带 forkguard_ 前缀,完整 guard 的 layer-2 过滤器永远选不中它们——恰是"封闭 Hook/日志旁路"的核心回归,需上游改名或扩展过滤器。

四、底座(CodeWhale PR #15 候选,169c24cc)安全缺口

  1. Op::ContinueGoal 处理器不安装 per-op 策略(engine.rs:2146-2207 vs SendMessage 的 :2099-2101):若 exact 白名单含 update_goal,goal 自调度续轮将以完全无限制状态运行(MCP/子 agent/hook/未脱敏日志全部恢复)。当前 GAIA profile 不含 update_goal,属潜在缺口,但底座边界设计不应依赖白名单内容巧合;建议受限态拒绝续轮或继承策略,并补三处续轮 gate 的 forkguard 测试。
  2. Op::ReloadMcp(engine.rs:2701)无 control_plane_restricted 检查,受限轮期间排队的 MCP reload 会真实拉起外部进程,与"排队控制操作继承受限"的声称不符。
  3. 受限轮审计事件整体替换为 {"event":"restricted_tool_event","details":"redacted"}(tool_execution.rs:186),连非私有的 tool_name 都丢弃,受限轮无法审计"哪个工具跑了几次",建议保留工具名、脱敏锁定在 input/output/路径。

五、范围污染与重复建设

  1. 夹带需拆分或删除:根目录 PROGRESS.md(235 行个人开发 checkpoint,含本机用户名路径与内部分支名,性质同 AGENTS.md §0 的 .codex-memory.md,不应入库);docs/superpowers/ 17 份 agent 工具过程文档(约 4900 行,main 无此目录惯例,含 sub-skill 指令与 checkbox 状态);pinvou-cli/target/.rustc_info.jsonCACHEDIR.TAG 两个构建产物(需删除并在 .gitignorepinvou-cli/target/)。
  2. 同一 PR 内两套 smoke 评测栈并存:features/assistant/eval/(app 侧)与 pinvou-cli/crates/adapter-smoke(CLI 侧)是同 5 条 case、rules、product_score、judge、markdown 报告的完整重写。设计文档 §5.1 明确要求迁移验证后删除旧栈、不得双栈演进,PR 却两套都留。请按设计做出取舍。
  3. pinvou3-app/src-tauri/Cargo.lock 全树刷新(~200 个包版本漂移、36 移除、22 新增)远超"新增 optional path 依赖"所需,请基于 main 的 lock 最小化再生或显式说明。
  4. GAIA run 不落 report.md/score.json,pinvou benchmark report 对 GAIA 恒返回 report_not_available,与 docs/gaia-benchmark.md:88 的声称矛盾——闭环最后一环缺失。

六、应改(不阻断但需回应)

  • benchmark-core:JSONL 追加为两次写、读取不容忍残缺行,崩溃在最需要恢复的场景下让 run 永久不可 resume(store.rs:440-460);单任务私有预测持久化失败中止整个 run(service.rs:244);plain resume() 把 durable run 静默降级为 ephemeral(service.rs:123-137);record_outcome 16KB 行上限 vs 无界 tool_observations。
  • fetch 获取锁(fetch.rs:2026-2039)无陈旧恢复,崩溃后永久卡死且错误码误导为 gaia_verify_failed;下载路径附件信任 HF 自报 digest,未用代码里已硬编码的 11 条官方 digest 交叉校验,与"固定 revision/digest"主张有落差。
  • run_eval_smoke 无 feature 门控注册进生产 invoke handler(lib.rs:923),单次 IPC 即以 Yolo 全权限跑真实模型;delete_then_forget 改变既有删除语义、engine_support.rs 通知默认值翻转、GUI 会话 sidecar 新增 tool 记录——这些借本 PR 落地的行为变更应在 PR 说明中显式声明。
  • 评测会话(标题"临时评测")未从 GUI 会话列表过滤,崩溃残留的 eval_* 会话会把私密题目带进用户历史(sessions/store.rs:189 只过滤 sched-)。
  • CLI 手写 json_escape + 字符串拼 JSON(cli/src/lib.rs:803-809 等),不转义控制字符,建议改用已是依赖的 serde_json;cli_contract.rs 的安全负向测试被 cfg(not(feature = "product-backend")) 门控,默认配置永不运行。
  • docs/gaia-native-turn-tool-policy.md 状态仍标"拟实施"且候选名单含已被移除的 retrieve_tool_result;docs/fork-modifications.md:171 的表述与候选 gitlink 状态不符;scorer 固定 revision 在 platform-design §3.2(9f133d71…)与代码(1349a179…)之间不一致。
  • run_with_staged_attachments(无策略变体)先把私有附件拷进 execution root 再无条件 bail,失败路径有副作用且是死代码(headless_bridge.rs:203-221)。
  • Windows 上 GAIA 带附件任务全部 fail-closed 不可跑,发布说明应点名此限制。

总结

这件事对产品有真实意义(模型/底座回归与诚实自评平台),隐私工程与 fetch/verify 链路的质量高于一般水平;但按当前交付形态价值未兑现:核心链路(工具面、评分语义)不成立、CI 看不见新代码、范围混入过程产物。建议按上文分组拆分处理:先修编译与 CI 门禁(二、三),再重做工具白名单与 FINAL ANSWER 闭环(一),范围清理(五)独立提交。底座侧缺口(四)请随 CodeWhale PR #15 一并处理。修改面较大,评审方不代改;如需逐项讨论的原始证据链可继续回复本评论。

@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.

评审更新(针对新版 02efcece,已 rebase 到最新 main 9da6b87c 重验)

作者新推的 3 个修复提交(865323ac/b29f54bd/02efcece)方向正确,上一轮最致命的问题已解。我逐条复核了上轮评论的 17 项,结论:5 项已修复、1 项部分修复、10 项未动,另有 1 个本轮新发现的 macOS 阻断缺陷,以及 1 处我上轮的错误指控需要更正。维持 request changes。

一、已修复(逐条验证过)

  1. GAIA 工具白名单命名(原评论一.1,阻断)——已修复。两个 profile 改用 canonical 家族名 File/Web/image_analyze(eval_tool_policy.rs:4-6)。我沿运行时链路验证到底:底座 agent 注册面注册的工具字面名就是 File(registry.rs:717-720,FileTool::new("File"))、Web(:925-930)与 image_analyze(vision/tools.rs:158);ExactToolDispatchPolicy::allows(ops.rs:60-62)逐字匹配 → 目录过滤与 exact 门禁两条路现在都通。新增的 test_headless_boot_contract.py 家族名契约测试会被 CI 的 unittest discover(pr-check.yml:316)自动收集,接线正确。
  2. load_skill_bindings 退役调用(原二.3)——已修复。两处调用点删除,并有 boot 契约测试兜底。
  3. cargo test --lib 三处编译错误(原二.4)——已修复turn_tool_security: Nonepinvou3_blocklist/worker_profile 两个坏 import 都已处理;cargo check --lib --tests 实测零 error。
  4. Cargo.lock 全树漂移(原五.16)——已修复。新版 lock 相对 main 仅 +12/-2(新增 agent-backend-api),是最小化再生。
  5. 排队控制操作旁路(原四.11 的大部分)——已修复。底座新提交 169c24ccRunShellCommand/SpawnSubAgent 加了 control_plane_restricted 锁存拒绝(engine.rs:2223/2264),并补了 forkguard_queued_control_op_keeps_restricted_turn_authority 回归测试,文档与指纹同步到位。
  6. Windows ACL 解析改为字节拷贝校验(865323ac),消除了原始指针 ACE 遍历,顺带答了 CodeQL。

二、部分修复,残留需作者决策

  • 工具粒度:家族名修复恢复了功能,但底座 agent 注册面的 File全量读写 FileTool(registry.rs:1155with_file_tools()),GAIA offline/public-web 白名单放行的 File 因此包含 write/delete/move 等写动作,与 PR 自己声明的只读意图(原 per-action 名单只有 read/list/grep/search)不符。精确门禁目前只支持整工具名,action 级收窄需要底座支持(可随 CodeWhale PR #15 一并设计)。请明确决策:接受家族级粒度并在文档中声明,或等 action 级能力。
  • forkguard 指纹前缀(原三.10 后半):新增的两条 T2 指纹已带 forkguard_ 前缀,但我指出的 restricted_turn_hooks_require_explicit_host_opt_inrestricted_tool_audit_redacts_private_sentinel(fork-guard.sh:98-99)仍无前缀,完整 guard 的 layer-2 过滤器依然选不中这两条核心回归。

三、仍未修复(与上轮证据一致,均已在新版复核)

  1. FINAL ANSWER 闭环缺失(原一.2,阻断):全仓仍零命中;prediction 仍是末轮全文拼接,OutputContract("gaia-final/v1") 仍是无消费点的标签,scorer 仍拿全文做等值比较。官方可比性仍不成立。
  2. forwarder 双重 finish(原二.5,阻断):forwarder.rs:834/904 原样未动,观测数据(tool_calls/catalog 摘要)仍永不落盘,排队双轮仍会 pop 错。
  3. validate_parent 拒绝 symlink 祖先(原二.6):submission.rs:202-233 未动,macOS $TMPDIR 下写 submission 恒失败的问题仍在。
  4. headless_bridge_contract.rs:276-285 断言(原二.7):RecordingRuntime 仍未实现 run_with_staged_attachments_and_policy,错误映射(headless_bridge.rs:825-840)仍不覆盖 unsupported_tool_policy → 非 Windows 必挂的逻辑未变。
  5. GAIA 测试自身 flake(原二.8):TempSnapshot 纳秒命名并行撞名仍在(src/contract_tests.rs:34tests/dataset_contract.rs:93,本轮实测并行 2 红、串行全绿);fetch_contract.rs 临时目录 0700 加固未加(我在 TMPDIR 为 0755 权限目录的环境下实测 3 红)。修复各一行量级,建议采纳。
  6. CI 对 pinvou-cli 零覆盖(原三.1,阻断):仍无任何 workflow 引用 pinvou-cli;benchmark-hooks 后的 headless_bridge.rs 与其契约测试仍零编译零运行。正因为此,下面第四节的新缺陷才能漏网。
  7. --allow-registered-candidate 在 merge_group 放行(原三.10 前半):pr-check.yml:294 未动,严格标签校验仍是死代码。
  8. 范围污染(原五.14):PROGRESS.mddocs/superpowers/(17 份过程文档)仍在树内。
  9. 双 smoke 栈(原五.15):features/assistant/eval/pinvou-cli/crates/adapter-smoke 仍并存。
  10. GAIA 不落 report.md/score.json(原五.17):仍无实现,与 docs/gaia-benchmark.md:88 矛盾仍在。
  11. 底座残留缺口(原四.11/12/13):Op::ContinueGoal 处理器仍不检查 control_plane_restricted 也不安装 per-op 策略(engine.rs:2146 起);Op::ReloadMcp(:2701)仍无受限检查;受限审计仍整体 redacted 丢工具名(tool_execution.rs:189-197)。请随 CodeWhale PR #15 处理。
  12. 上轮第六部分(非阻断项)均未动,抽查确认:run_eval_smoke 仍无 feature 门控注册进生产 invoke handler(lib.rs:923);会话列表仍只过滤 sched-(store.rs:189),eval_* 残留会话仍会进用户历史;CLI 仍手写 json_escape

四、本轮新发现(macOS 阻断)

_EMBED_INFO_PLIST 重复符号,PR 主链路测试在 macOS 无法链接。 pinvou3-tauri 一个 crate 内现有三处 tauri::generate_context!():lib.rs:928(无条件,既有)+ eval_cli.rs:64(dev-tools,PR 新增)+ headless_bridge.rs:983(benchmark-hooks,PR 新增)。embed_plistembed_info_plist_bytes! 宏以 #[no_mangle] static _EMBED_INFO_PLIST 故意让重复嵌入成为编译错误。实测(用 PR 自带 lockfile,tauri-build 2.6.1):cargo test -p pinvou-cli(默认 features 经 pinvou-product-backend 启用 benchmark-hooks → 两处宏展开)链接失败,0 个测试能跑;cargo test --test headless_bridge_contract --features benchmark-hooks 同样被堵。Linux/Windows 不受影响(embed_plist 是 macOS 专属依赖),所以 CI 是绿的——这正是缺 macOS/CLI 门禁的代价。建议把 headless/eval 入口改为复用 lib.rs 的单一 context 构造,或把 plist 嵌入收敛到最终 bin。

五、对我上轮评论的更正

上轮五.14 中"pinvou-cli/target/.rustc_info.jsonCACHEDIR.TAG 两个构建产物入库"的说法是错的:我逐一核对了 PR 各版本(be0b06ac/46504fa9/d5a07234/02efcece)的 tree,pinvou-cli/target/ 从未被跟踪——那是我本地测试时不小心 git add -A 进自己验证分支的污染。向作者致歉,该子项作废(.gitignorepinvou-cli/target/ 仍是值得做的防御性动作)。

总结

三个修复提交质量好、都打在点上,工具链路的根因已真正打通(粒度问题见二);但评分语义闭环(三.1)、观测落盘(三.2)、CI 门禁(三.6)这些阻断项未动,又新增一个 macOS 链接阻断(四),范围清理也未开始。修改面仍然较大,评审方继续不代改。我本地的 rebase+验证分支(基于 9da6b87c,含 lockfile 再生与一处 rebase 解法:main 已含 tempfile = "3",rebase 重放会产生重复键,需删 PR 侧那行)可随时推给作者参考。

@JensenChen28

Copy link
Copy Markdown
Collaborator Author

评审修复总结(针对第二轮评审,02efceceea461cc6

逐条对应上轮评审清单,本轮新增 6 个提交 + CodeWhale PR #15 候选链 2 个新提交(169c24cc21e5f661a)。

三、上轮未修复项 → 已全部处理

  1. FINAL ANSWER 闭环(原三.1)——已实现全链路:题目 prompt 注入 FINAL ANSWER: <answer> 格式指令(private_inputs.rs);headless 侧按 OutputContract("gaia-final/v1") 从末轮输出提取最后一个非空标记(headless_bridge.rs extract_final_answer/project_private_output),缺失或为空以 missing_final_answer 终态计为失败(runner/service 错误链路);scorer 不再拿全文比对。契约测试覆盖提取语义(最后一个标记、空值拒绝)。
  2. forwarder 双重 finish(原三.2)——已收敛为单次 finish_turn_with_observation 收尾,位置保持在 emit_chat_terminal 之前(先落权威时间线再发 chat:done);删除旧 usage 版调用。补排队双轮回归测试钉住"一次收尾只消费一个活跃轮"(timing.rs::queued_second_turn_survives_single_observation_finish)。
  3. validate_parent symlink 祖先(原三.3)——改为只校验叶子父目录,防覆写仍由 create_new + hard_link 承担;补 symlink 祖先可达/叶子 symlink 拒绝的 Unix 测试。
  4. 契约测试断言对齐(原三.4)——ProductRuntimePort trait 默认实现统一 bail attachments_runtime_unsupported,与断言一致;同时删除了无策略 run_with_staged_attachments(拷贝后无条件 bail 的死代码)。
  5. GAIA 测试 flake(原三.5)——TempSnapshot 改进程内原子计数(同进程并行线程在 macOS 粗时钟下不再撞名);fetch_contract.rs 夹具目录补 0700。
  6. CI 覆盖(原三.6)——新增 benchmark-test 必需门禁 job:cargo test --all-features + --no-default-features(覆盖被门控的安全负向测试)+ --features benchmark-hooks --test headless_bridge_contract,接入路径过滤与 required-gate;test_ci_gate_policy.py 补断言。
  7. --allow-registered-candidate(原三.7)——已从 workflow 移除,pull_request/merge_group 一律严格校验不可变标签。
  8. 范围污染(原三.8)——PROGRESS.mddocs/superpowers/ 17 份过程文档已删除(约 5900 行);.gitignorepinvou-cli/target/
  9. 双 smoke 栈(原三.9)——app 侧旧栈(runner/cases/mock/report/markdown 报告/eval_cli/eval_smoke bin/run_eval_smoke 命令)全部删除;features/assistant/eval 收敛为仅存活的模型身份类型(engine_pool/product_runtime 仍消费),smoke 评测统一由 pinvou-cli/crates/adapter-smoke 承接。
  10. GAIA report.md/score.json(原三.10)——benchmark score gaia 现发布双产物(幂等,重复评分冲突检测);report 命令评分后可用,docs/gaia-benchmark.md 已对齐。
  11. 底座残留缺口(原三.11)——随 CodeWhale PR fix: 统一侧边栏定时任务记录样式 #15 新提交 21e5f661a 处理:Op::ContinueGoalOp::ReloadMcpcontrol_plane_restricted 锁存拒绝;受限审计改为保留 event/tool_name 非私有身份字段、仅脱敏输入/输出/路径;均补 forkguard 回归(forkguard_queued_goal_continuation_and_mcp_reload_keeps_restricted_turn_authority)。
  12. 上轮第六部分抽查项——run_eval_smoke 已随旧栈删除;会话列表同时过滤 eval_ 前缀(含测试);CLI json_escape 委托 serde_json 覆盖控制字符。

二、上轮部分修复项 → 已闭环

  • 工具粒度:走 action 级收窄路线——底座新增 with_read_only_dispatch():受限轮把模型可见 File schema 投影为只读 action(read/list/search_name/search_content),审批前与最终分发前双重拒绝写动作(forkguard_restricted_agent_uses_read_only_file_schemaforkguard_read_only_turn_rejects_write_action_at_final_dispatch)。docs/gaia-native-turn-tool-policy.md 已更新为"已实施"并声明粒度语义。
  • forkguard 指纹前缀:两条旧测试已在底座重命名补 forkguard_ 前缀,fork-guard.sh:98-99 指纹同步;本轮全部新增指纹均带前缀,layer-2 过滤器可选中。

四、macOS _EMBED_INFO_PLIST → 已修复

generate_context!() 收敛为 lib.rs 单一展开点 build_tauri_context(),GUI 与 headless 宿主共用;eval_cli.rs/eval_smoke bin 随旧栈删除。crate 内不再有重复宏展开。

本轮额外修复

  • 合并主线时冲突解决丢失了 EnginePool::send_user_messagePinvou3Bridge::with_session_model(此前分支实际编译不过),已恢复。
  • headless_bridge 迁入 features/assistant/product_runtime/ 并通过架构守卫(stage_file_in_workspace 走 lib 根 re-export,消除 features→app 反向依赖)。

验证

  • cargo test --manifest-path pinvou-cli/Cargo.toml --all-features --offline:22 套件全绿。
  • cargo test --manifest-path pinvou-cli/Cargo.toml --no-default-features --offline:全绿。
  • CodeWhale forkguard 全量 31 条:全绿(含 5 条本轮新增)。
  • python scripts/architecture-guard.pybash scripts/fork-guard.sh --fastscripts/tests/ 17 条:全过。
  • cargo fmt --check(三个 workspace):干净。

未处理项(非阻断,请评审确认是否可留待后续)

  • benchmark-core:JSONL 追加两次写/残缺行容忍、单任务预测失败中止 run、plain resume() 降级、16KB 行上限——建议另开 issue 单独处理。
  • fetch 获取锁陈旧恢复、附件 HF 自报 digest 与官方 11 条 digest 交叉校验——同上。

合并前置条件(严格标签校验的顺序说明)

CI 已按评审意见启用严格公开标签校验,因此合并本 PR 前需完成:① 合并 CodeWhale PR #15(候选链现 head 21e5f661a);② 按 fork 发布流程打不可变标签 pinvou-v0.9.5-r8;③ 回本 PR 将 gitlink 与 PINVOU_CODEWHALE_TAG 对齐 r8。此前公开底座 gitlink 校验会按预期红。该顺序已登记在 docs/fork-modifications.md 候选说明中。

@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.

审阅结论:Request Changes

感谢这份高质量的评测平台工作——隐私边界、digest 信任链和 fork 登记经逐项核验是真实可信的。但存在 2 个代码 BLOCKER(均已在本机复现)和多项 MAJOR,当前修订无法合并;合并还依赖 CodeWhale 侧流程收尾。以下按严重级列出,均附文件:行号。

审阅基础:已将 PR 净差异 rebase 到最新 origin/main(c65fa498,零内容冲突,GitHub MERGEABLE 属实),并在本地完成验证:pinvou-cli 22 套件 160 测试全绿、architecture-guard.py / fork-guard.sh --fast / cargo fmt 全绿、默认 feature 的 pinvou3-app cargo check --lib --tests 通过。标注【已复现】的条目为本机亲手验证,其余为深读代码所得。


BLOCKER

B1【已复现】benchmark run smoke 在真实产品后端上必然全部任务失败

  • pinvou-cli/crates/adapter-smoke/src/lib.rs:58pinvou-cli/crates/cli/src/lib.rs:977 硬编码工具策略 pinvou-product/v1
  • 但产品侧 pinvou3-app/src-tauri/src/features/assistant/product_runtime/eval_tool_policy.rs:61-69resolve_eval_policy 只注册了 pinvou-gaia-public-web/v1pinvou-gaia-offline/v1
  • 链路:PrepareRequest.tool_policyheadless_bridge.rs:683-689 逐 id 解析 → 未注册即 unsupported_tool_policyrunner.rs:175-176 折叠为 backend_prepare_failed

结果:默认 product-backend feature 构建下,smoke 的每个任务 prepare 即失败,无法完成一次成功运行。CI 的两个测试档(--all-features / --no-default-features)都不经真实 headless 宿主执行 smoke,故门禁拦不住。修复方向:smoke 改用已注册的 GAIA 策略 id,或注册 pinvou-product/v1

B2【已复现】benchmark-hooks feature 开启时,806 行 headless 契约测试编译失败

cargo test --manifest-path pinvou3-app/src-tauri/Cargo.toml \
  --features benchmark-hooks --test headless_bridge_contract

E0407: method run_with_staged_attachments is not a member of trait ProductRuntimePortE0599AttachmentAwareRuntime 上无此方法)。该 feature 默认关闭、CI 不开,PR 声称的 pinvou-cli --all-features 验证覆盖不到这个面——即这份契约测试从未被编译通过过。trait 定义与 impl 需要同步修正,且建议把 cargo check --features benchmark-hooks 纳入门禁(见 M8)。

B3(流程阻塞,非代码缺陷)合并前置链未完成

pinvou-v0.9.5-r8 标签不存在、CodeWhale PR #15 未合并、scripts/verify-public-submodule.sh:7PINVOU_CODEWHALE_TAG 仍为 r7。merge queue 严格模式会硬挡(fail-closed 有效,已验证脚本逻辑)。已知并需注意的约束:r8 必须以 fast-forward 方式精确指向 21e5f661——若 #15 经 GitHub merge/squash 产生新提交,则 gitlink 既非 r7 head 也非候选、commit 计数失配,fork-guard.sh:24-35 将在 main 上永久红灯,父仓两脚本 + fork-modifications.md 三处都要同步改。


MAJOR

评分语义与答案提取(直接压低准确率/可信度)

  • M1 货币符号只剥 $ % ,,漏 € £adapter-gaia/src/scorer.rs:114-115。GAIA 有大量欧元/英镑题,€1,250 vs 1250 会被系统性判错。官方 normalize_number 同时剥 €£
  • M2 FINAL ANSWER 空尾标记跨边界回退headless_bridge.rs:605-610rsplit(MARKER).filter(!empty).next()——最后标记尾部为空时会取到更左侧的正文,如 "…Paris\nFINAL ANSWER:" 会把答题过程全文当答案送进 scorer,而非按契约报 missing_final_answer。docs 声称「提取最后一个非空标记」,实现是「最后一个非空段」。
  • M3 标记匹配过脆MARKER 大小写敏感、冒号严格、不截断行尾。Final Answer: 42(常见 casing)、**FINAL ANSWER: 42**、标记后带解释文本等真实模型输出变体都会失败或污染答案。建议行边界 + 容错解析。
  • M4 「官方评分」声明与证据不符:scorer 是未与官方 Python scorer 逐题交叉验证的 Rust 重实现(docs/gaia-benchmark.md:105 自己承认),存在可枚举分歧(M1、列表分支顺序敏感且元素不去标点、INFINITY 哨兵 vs Python 有限哨兵)。但 golden 测试全部以自身实现为 oracle 并冠名 official_*tests/gaia_contract.rs:50-96)。既然 scorer revision 已 pin,请补一次真实 Python scorer 交叉验证,或把 PR 正文与命名降级为「自有重实现」。
  • M5 一票否决的闭环设计:任何一题 Timeout/missing_final_answer → 整 run unofficial_partial 且禁止导出 submission(submission.rs:150-154scorer.rs:103-107)。46+ 题、600s 上限的 web-agent 运行几乎必然有失败,PR 声称的「评分闭环」在真实运行中大概率终止于 partial。建议允许带缺失导出(官方口径按错误计)。

benchmark-core 持久化与恢复(与「恢复」卖点冲突)

  • M6 并发 resume 同一 run 无互斥RunStore 的锁是进程内 per-instance Arc<Mutex<()>>store.rs:27),无 run 级 lockfile。两个进程(或同进程两次调用)resume 同一 run → 重复 outcome 行 → scorer.rs:69 行数不等 → 静默 partial(0,0),无任何错误提示指向并发。
  • M7 单行损坏砖死整个 runstore.rs:467-480 任何 JSONL 行解析失败即 invalid_persisted_record,无 torn-tail(末行截断可丢弃)与中部损坏的区分,resume 永远失败。同类:create 在 mkdir 与 manifest 落盘之间失败会永久占用 run_id(store.rs:231-243);schema_version 写了但读侧从不校验(store.rs:31event.rs:21)。

隐私与平台残留(整体是强项,仅两处缺口)

  • M8 崩溃遗留 acquisition 锁永久阻塞 fetchfetch.rs:2026-2039 锁文件仅由 Drop 移除,SIGKILL/断电后 benchmark fetch gaia 永久 gaia_verify_failed(错误码还误导)。nonce 与文件身份已记录却未用于陈旧锁检测。
  • M9 Windows 预测目录无 ACL 强制:数据集侧有完整 DACL 机制,预测 blob 目录在非 Unix 下 create_dir 后既不设置也不校验 ACL(private_prediction.rs:434-437、497-503),且 envelope 明文 integrity_digest 放大为离线字典验证通道。

CI 覆盖与配置

  • M10 ~19k 行新代码默认零门禁:pinvou-cli 六 crate + headless_bridge.rs(1584 行)+ 契约测试只在 ci:full-benchmark 标签触发,默认 PR/merge queue/main 一律不编译不测试;11668 行新依赖不进任何 cargo-deny。8 条新增 forkguard 安全行为测试在 main 上从不执行。benchmark* 四个 path-filter outputs 无消费者(死配置,制造「有分层覆盖」错觉)。建议至少把 cargo check/test --manifest-path pinvou-cli/Cargo.toml(或 benchmark job 的 merge_group 分支)纳入 required gate——B2 正是这个缺口的直接后果。
  • M11 CLI 无诊断通道cli/src/lib.rs 多处把完整错误链折叠为 smoke_run_failed/gaia_run_failed 机器码且无 --verbose/RUST_LOG 逃生口,GAIA fetch 失败时无法区分网络/磁盘/token/校验错误。防泄漏与可诊断不是二选一,分级错误码即可。
  • M12 工具观察白名单与策略工具名不一致benchmark-core/src/runner.rs:36-54 只认产品内部名(web_search 等),GAIA 策略允许的 File/Web/image_analyze 全被改写为 [redacted-tool]——GAIA run 的工具观察全部失真,且底座 crate 反向硬编码上层工具目录属分层倒置。

行为变更声明

  • M13 未声明的 GUI 行为变更应拆分:普通(非 scheduled)聊天会话的 token 总量持久化为新增行为——engine.rs:1162-1170(spawn 捕获 base)、forwarder.rs:722-767(终态累加 + 失败改判 Failed)、store.rs:571-597persist_chat_token_total)。实现质量尚可,但这是面向全体 GUI 用户、与评测平台无依赖关系的变更,PR 正文只声明了另外三个。建议拆成独立 PR 单独评审与声明。
  • 已声明的三个行为变更本身实现正确:delete_then_forget 不会产生数据孤儿(会话列表来自磁盘扫描、错误保留上抛、NotFound 幂等);通知默认不发对 GUI 零影响(lib.rs 无条件 manage NotificationState,已核实);timing 老 sidecar 兼容路径完整。eval_ 前缀会话不进用户列表 + benchmark-hooks feature 门控 + 显式 None 传参的三重隔离也验证成立。

正面确认(均已核验)

  • fork 登记真实:gitlink 21e5f661 经 gh api 全链溯源(CodeWhale PR #15 head、r7 公开基线之上纯前向 4 提交、13 commit 计数吻合);8 条新 T2 指纹在该提交上全部存在;候选白名单双脚本硬编码 + 策略测试锁定,无静默绕过通道。
  • 隐私边界经得起逐路径追踪:events/predictions/report/submission 四类公开产物只含 task_id、聚合计数与随机 handle,有 sentinel 级测试锁定;digest 信任链端到端(URL pin + 双哈希流式校验 + 发布后重验 + 目录白名单);token 仅环境变量传入、无隐式回退、Debug 全脱敏。
  • 卫生干净:82 文件全部可归入声称范围,无关混入≈0;无敏感信息/个人路径/过程文档残留;21 个提交 DCO 合规。所谓「退役 app 侧旧 smoke 栈」实为本 PR 开发过程中自建自删,main 上从未存在——建议把 features/assistant/eval/mod.rs:3-6 的「已统一实现并删除」注释改为不误导评审者的表述。

合并前条件(建议)

  1. 修复 B1、B2(B2 修复后请实际跑通 --features benchmark-hooks 契约测试);
  2. 完成评分正确性最小集:M1 + M2 + M3(M4/M5 至少把 PR 正文措辞与 docs 的保守表述对齐);
  3. M6/M7 二选一:修复,或在 docs 明确标注当前单进程独占、无 torn-tail 容忍的已知限制;
  4. M13 拆分为独立 PR;
  5. M10 补最小门禁(cargo check + 契约测试进 merge queue);
  6. 走完 CodeWhale #15 合并 + r8 标签(精确指向 21e5f661)+ gitlink/TAG bump。

修改面较大,本轮只评审不代修改。总体而言这是一件意义真实、安全工程质量高于平均的研发基建,期待修复后合入。

@JensenChen28
JensenChen28 enabled auto-merge August 20, 2026 08:03
@JensenChen28
JensenChen28 disabled auto-merge August 20, 2026 08:04
@h3c-hexin

Copy link
Copy Markdown
Contributor

已推送评审修复,当前 head:d9c89dc65。

本轮修复:

  • 补齐 pinvou-product/v1 和 Linux headless trait 契约,修复空 FINAL ANSWER 误取分析前缀。
  • RunStore 增加跨进程执行锁、JSONL 崩溃尾部恢复、schema 校验和重复 outcome 拒绝。
  • GAIA fetch 改用可崩溃恢复的系统锁,并返回 gaia_fetch_in_progress。
  • Windows 私有预测复用受保护 DACL,完整性摘要改为绑定 DPAPI 受保护内容,不再直接散列明文。
  • 对齐 File、Web、image_analyze 工具观测名称。
  • 新增 required benchmark-contract Linux 最小门禁;CodeWhale gitlink及候选校验对齐 PR fix: 统一侧边栏定时任务记录样式 #15 当前 head。

实际验证:

  • Benchmark 核心、GAIA、Smoke、backend 与 CLI 契约测试通过。
  • headless_bridge_contract 16/16,最终答案 2/2,策略 3/3。
  • Windows benchmark-core 含测试代码交叉编译通过。
  • architecture guard、fork guard --fast、公开 submodule 候选校验、CI 策略测试、fmt、DCO 校验通过。

未在本机验证:

  • Windows DACL/DPAPI 原生运行时;等待本次 PR 的 Windows CI 给出最终结果。

@h3c-hexin

Copy link
Copy Markdown
Contributor

复评结论(基于 current head d9c89dc654eff5083ec8f231f32349ba76fcc008):当前仍建议暂不合入。required-gate 已绿,但还有 3 个阻塞问题未被现有门禁覆盖。

阻塞问题

  1. benchmark-hooks 特性下存在真实单测失败。

    run_with_staged_attachments_and_policy 的默认实现返回 attachments_runtime_unsupported,但 policy_defaults_fail_closed_without_calling_legacy_runtime 仍断言 unsupported_tool_policy。实际执行:

    cargo test --manifest-path pinvou3-app/src-tauri/Cargo.toml \
      --features benchmark-hooks --lib \
      features::assistant::product_runtime::headless_bridge:: -- --test-threads=1

    结果为 10 项中 9 通过、1 失败。当前必需的 benchmark-contract 只执行 cargo check --features benchmark-hooks --test headless_bridge_contract,不会编译 lib 的 cfg(test);真正执行测试的 benchmark-test 又需要手工标签且不参与 required-gate,所以现有绿灯没有覆盖该失败。

  2. 未完成运行在评分后再 resume,会导致最终评分产物冲突。

    score_gaia 不取得 run 执行锁,也允许基于部分 outcomes 发布 unofficial_partial;之后 resume 补完任务并再次评分时,既有 score.json / report.md 与新结果不同,publish_gaia_score_artifacts 会返回 gaia_score_artifact_conflict。可触发序列是:run 被中断 → score → resume → score。建议让评分与执行共享一致快照或锁,禁止对仍可恢复的 run 发布固定产物,或按 outcome digest 安全替换、版本化产物,并补该序列的回归测试。

  3. 任务规划阶段崩溃后,resume 会静默漏跑任务。

    plan_tasks 逐任务追加 Planned 事件;若中途崩溃,JSONL 仍合法但任务集合不完整。resume_adapter 虽重新生成完整 plan,却没有补齐缺失的 Planned 事件,而 execute 只运行 recovered runnable 集合中的任务。最终可能报告 remaining=0,但部分数据集从未执行。建议原子发布任务计划,或在 resume 时严格核对并补齐确定性的计划集合。

仍需处理

  • 普通 GUI 聊天的 lifetime token 持久化仍混在本 PR,覆盖所有普通会话;元数据保存失败还会把已完成 turn 改成 Failed。这不是评测平台自身闭环,建议拆分;若保留,需要在 PR 正文明确行为变化并补普通聊天回归测试。
  • CodeWhale fix: 统一侧边栏定时任务记录样式 #15 仍为 OPEN,pinvou-v0.9.5-r8 尚不存在。父仓 gitlink 当前指向候选 a647ed866,但公开校验脚本仍以 r7 为正式标签;合入前需要完成底座 PR、发布不可变 r8,并同步父仓 gitlink 与校验常量。

已确认

  • pinvou-product/v1 注册、run/resume 跨进程锁、JSONL 尾部修复、GAIA 获取锁、Windows 私有预测保护、工具观察名称及 CodeWhale 受限续轮边界已有对应修复。
  • 本地通过 benchmark 核心/适配器 150 项测试、architecture-guard.pyfork-guard.sh --fast、CI 策略测试 17 项及 git diff --check
  • 更正上次意见:官方 GAIA scorer 的数值归一化只移除 $% 和逗号,并不移除 / £;当前 Rust 实现与固定版本官方 scorer 一致,因此撤回该项。

@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.

第四轮复审结论:Request Changes(已 rebase 到最新 main 重验)

审阅基础:PR head d9c89dc65 已 rebase 到 origin/maina7c23943,含 #300/#184/#275/#321/#324,冲突已全部解决)。rebase 后本地验证:pinvou-cli 22 套件 167 测试全绿、cargo check --lib --tests 通过、cargo fmt --check 干净、architecture-guard.py / fork-guard.sh --fast 全绿。

先说结论:平台的主体链路经逐项深审成立——三策略 id 注册、canonical 工具面 + exact 门禁 + 只读 File 投影的多重防线(底座侧未发现越权路径)、FINAL ANSWER 闭环、隐私产物的 sentinel 边界、fetch/verify 信任链、fork 登记拓扑。d9c89dc65 新增的执行锁/torn-tail 修复/duplicate outcome 拒绝/ProductV1 注册也都真实有效。

@h3c-hexin 今早提出的 3 个阻塞问题在本 head 中全部未修,其中阻塞 1 我在 rebase 后的树上实测复现。另有多项本轮新发现。以下按严重度列出,均附文件:行号(行号以 rebase 后树为准,与 PR head 可能有个位数偏移)。


一、h3c-hexin 三阻塞:全部未修复

B1【实测复现】benchmark-hooks 真实单测失败仍未修

rebase 后实跑:

cargo test --features benchmark-hooks --lib features::assistant::product_runtime::headless_bridge:: -- --test-threads=1
test ...::policy_defaults_fail_closed_without_calling_legacy_runtime ... FAILED
  left:  "attachments_runtime_unsupported"
  right: "unsupported_tool_policy"
test result: FAILED. 9 passed; 1 failed

ProductRuntimePort::run_with_staged_attachments_and_policy 的 trait 默认实现 bail attachments_runtime_unsupportedheadless_bridge.rs:85),测试在 :1140 断言 unsupported_tool_policy。d9c89dc65 只修了 Linux trait 签名编译(AttachmentAwareRuntime 统一到新方法),没有触碰这处断言错配。

更深层的问题:这条失败在任何 CI job 里都看不见——cargo test --lib --features benchmark-hooks 在全部 workflow 中从未执行(benchmark-contract 只 cargo check --test headless_bridge_contract,不编译 lib 的 cfg(test);rust-test 是默认 features;benchmark-test 需 ci:full-benchmark 标签且也只跑 --test)。建议把它纳入 benchmark-contract(见 D5)。

修复是一行级:断言改为 attachments_runtime_unsupported,或默认实现统一到 unsupported_tool_policy——取决于作者想表达的语义(我倾向后者:对 legacy runtime 而言"不支持策略化运行"比"不支持附件"更准确)。

B2 score → resume → 再 score 的产物永久冲突:未修

  • score_gaiacli/src/lib.rs:749-753)与 open_gaia_run 全程不取执行锁,也不检查 run 是否仍有未完成任务;部分完成的 run 可评分并写死 score.json/report.md(partial)。
  • 之后 resume 补完再 score,产物字节必然不同 → gaia_score_artifact_conflict:832-837:844-860),且无任何清除/覆盖途径——该 run 的评分工件永久无法再发布。
  • 无该序列的回归测试(现有 gaia_score_publishes_machine_and_markdown_artifacts 只覆盖一次性成功发布)。

建议三选一:score 与执行共享锁并拒绝可恢复 run;或允许基于 outcome digest 的安全替换;或至少提供 --force 重发布并补测试。

B3 plan 阶段崩溃后 resume 静默漏跑:未修

plan_tasks 仍逐任务追加 Planned 事件(store.rs:321-327,非原子);中途崩溃后 JSONL 合法但任务集不完整(repair_torn_jsonl_tail 只修无换行的末行)。resume_adapter 重新生成完整 plan 但不补缺失的 Planned 事件service.rs:163-165 不调 plan_tasks);execute 只跑 recovered runnable 集(:212-216),最终可报 remaining=0 而部分任务从未执行。无回归测试。


二、评分正确性(直接影响"官方可比"主张)

M1【高,待交叉验证】与官方 scorer 的两处可翻转分歧

  • gt 含逗号数字:官方 question_scorer 对 ground_truth 走 parse float(str.replace(",",""))(数字分支);本实现 parse_python_float 不剥逗号(scorer.rs:119-129),gt "1,000" 落入列表分支(split 成 ["1","000"])→ 模型答 "1000" 被判错。normalize_number(模型侧)剥逗号而 gt 侧不剥,是不对称的。
  • 列表比较有序 vs 无序:本实现 zip + 长度相等(scorer.rs:36-48);官方流传实现是 set(...) == set(...)。乱序答案、重复项场景结论相反。现有 golden(gaia_contract.rs:60-68)在两种语义下同值,不构成判别

建议:补"gt 为含逗号数字"与"乱序列表"两类 golden,或直接落一个对 pinned revision 的 Python 差分测试。M4 的"官方评分"措辞在此之前应保持 docs 现有的保守表述。

M2【中】FINAL ANSWER 提取取到文本末尾

extract_final_answerheadless_bridge.rs:600-609)取最后一个非空标记段直到文本结束:"FINAL ANSWER: 42\n\nHope this helps!" 会把闲聊算进答案;**FINAL ANSWER: 42*** 残段在数字题上不被 normalize_number 剥掉 → parse 失败 → INFINITY 哨兵 → 判错。建议按行/段落截断 + 剥首尾 markdown 残段,并补粗体/代码块/尾随文本用例。

M3【中-高,工作流】单题失误永久报废整个 run 的导出资格

失败任务已有 outcome 记录,recover() 的 runnable 集不含它(store.rs:451-454)→ resume 永不重试;叠加 submission 的一票否决(submission.rs:150-154 任何非 Completed 即拒),任何一题 Timeout/missing_final_answer 都使 run 永久失去 submission 资格,只能换 run_id 全量重跑。docs 未提示该不可恢复性。建议:允许 resume 重试失败任务,或 submission 按官方口径"错误计错"导出,至少在 docs 写明。


三、隐私与清理残留

M4【中-高】非 Windows 下 integrity_digest 是无盐明文哈希

DPAPI 绑定密文的修复只覆盖 Windows。Unix 侧 protect 是恒等函数(private_prediction.rs:638-646),blob 内 digest = SHA256(域标签||binding||明文),而 binding 明文存于同一 envelope、handle 公开在 predictions.jsonl——拿到 blob 即可对 GAIA Level 1 常见短答案做离线字典恢复,对不可猜测答案也是验证预言机。建议:macOS 用 Keychain 派生密钥对称加密,或至少在 docs/gaia-benchmark.md Privacy 节明示该边界。

M5【中】下载路径附件未闭环到硬编码官方 digest

OFFICIAL_LEVEL1_ATTACHMENTS(11 条 path+size+digest 锚)只在 import 路径强制(fetch.rs:771-786capture_trusted_import_attachments);下载路径的附件期望 digest 取自 HF ?blobs=true 自报值(parse_hf_metadatadownload_one:906-936),全程不引用锚点。低成本修法:download_snapshot 完成后对 11 条 spec 复用同一校验函数交叉复核一次。

M6【低-中】崩溃残留 sessions/eval_* 与 staging 目录无启动清扫

eval 会话列表过滤正确(store.rs:189-193,有测试),但 SIGKILL/断电残留的 sessions/eval_*(含私有题目与附件明文)永久滞留磁盘,boot_inner 无清扫;下载中断的 .pinvou-gaia-tmp-* staging(最大 ~256MB/次)也无 GC;retention 把 eval_ 残留计入普通聊天配额(retention.rs:63 只跳过 sched-)。建议 headless 宿主启动时清扫前任遗留。


四、CI 盲区(B1 漏网的直接原因)

  • D1cargo test --lib --features benchmark-hooks 全仓库任何 job 从未执行——headless_bridge 的 12 个 lib 单测(含 B1 失败用例)不编译不运行。建议在 benchmark-contract 增加这一步。
  • D2:pinvou-cli workspace 无 fmt / clippy / cargo-deny 门禁(约 3.5 万行新代码 + parquet/arrow/hf-hub/ureq 等新依赖不在任何 deny 图内)。
  • D3pinvou-product-backend(唯一把 pinvou3-lib 以 benchmark-hooks 拉进 cli 依赖图的胶水层)在 required 门禁中从不编译;其默认 feature 形态的 pinvou-cli 测试也不跑(required 只跑 --no-default-features 的 cli 测试)。
  • D4:MQ 阶段 benchmark-test 永不运行(限定 pull_request 事件),Windows 专属 ACL/DPAPI 代码在 MQ 无覆盖。
  • D5(既有缺口,非本 PR 引入,但影响本 PR):windows-rust-test 不在 required-gate 汇总。

五、范围与卫生

  • M13 维持:GUI 聊天 lifetime token 持久化(engine.rs spawn 捕获 + forwarder 终态累加/失败改判 Failed + persist_chat_token_total)仍混在本 PR,与评测平台无耦合,应拆分为独立 PR 单独评审声明。
  • Cargo.lock 陈旧回潮:PR 分叉点早于 #286 的 device_query→x11 收敛,pinvou3-app/src-tauri/Cargo.lockdevice_query + windows 0.48 链(约 12 个传递依赖)+ agent-client-protocol 系列降级属于基线漂移,请基于最新 main 的 lock 最小化再生。
  • docs/fork-modifications.md / .en.md 在 PR head 中的候选登记段与 gitlink 一致,无冲突标记问题(我在 rebase 时已核对)。

六、底座收尾(流程,好消息 + 顺序敏感)

CodeWhale PR #15 已于今天 10:38 squash merge(d127aed11,单 parent = r7 基线 a36e6cd5树与候选 a647ed866 完全一致,tree 均为 0da1d356d)。底座 5 个提交的受限轮安全机制深审未发现越权路径,11 条新增 forkguard 测试钉住关键行为。

合并本 PR 前的收尾顺序(顺序敏感):

  1. 先在公开 CodeWhale 仓库打不可变标签 pinvou-v0.9.5-r8 指向 d127aed11(不要指向 a647ed866——squash 后两者同树但历史不同,标签应标记已合并的公开历史);
  2. 本 PR gitlink 切到 d127aed11,同步 fork-guard.sh(PUBLISHED_HEAD=d127aed11、PUBLISHED_COMMITS=10、移除候选登记)与 verify-public-submodule.sh(PINVOU_CODEWHALE_TAG=r8);
  3. 不先打 r8 直接入 merge queue 会因严格标签校验失败(当前 --allow-registered-candidate 只在 pull_request 阶段放行候选 a647ed866)。

合并前条件(建议)

  1. 修复 B1(一行级)+ B2 + B3,并把 cargo test --lib --features benchmark-hooks 纳入 benchmark-contract(D1);
  2. M1 补两类 golden 或 Python 差分;M2/M3 至少补测试与 docs 说明;
  3. M4 在 docs 明示 Unix 边界或落地加密;M5 补交叉校验;
  4. M13 拆分 + Cargo.lock 再生;
  5. 完成 r8 标签 → gitlink → 脚本三步收尾。

修改面较大,本轮只评审不代修改。这份平台的工程质量整体是高的,前三轮提出的阻断项也已经修掉了绝大部分——把上面这些收口后,期待合入。

@h3c-hexin

Copy link
Copy Markdown
Contributor

当前代码独立评审(基于 head d9c89dc

结论:当前不建议进入 Merge Queue,建议先修复下面列出的阻断项。

这次没有沿用过去的评审结论,而是重新读取了当前 PR head、当前 origin/main、82 个文件的实际 diff,以及 CodeWhale 子模块从 a36e6cda647ed866 的完整差异。

当前 PR 实际是什么

它不是“桌面端新增一个 GAIA 页面”,而是一套新的、独立的 Rust CLI 评测系统:

  • 新增 pinvou-cli 六个 crate:评测核心、CLI、真实产品后端、GAIA、Smoke、后端 API。
  • GAIA 已实现 validation Level 1 的固定数据版本、下载/校验、真实 Agent 执行、恢复、评分和 submission。
  • 原有 Smoke 评测迁移进统一适配器;BFCL、WorkBuddy 目前只是 planned 注册项。
  • 通过无窗口 Tauri + EnginePool 调用当前配置的真实模型。
  • CodeWhale 新增进程内逐轮安全策略,GAIA 轮次只允许 FileWebimage_analyze,并禁止写操作、MCP、子代理、Hook 等旁路。
  • 数据落在 ~/.pinvou3/benchmarks~/.pinvou3/eval/runs

这部分不是占位代码:GAIA 数据、评分、私有预测和工具安全边界实现得比较扎实。

确定的问题

1. 合并队列当前必然失败

PR 阶段允许 gitlink 指向候选提交 a647ed866,但 Merge Queue 会严格执行不可变标签检查:

  • .github/workflows/pr-check.yml:316-324 只在 pull_request 事件传 --allow-registered-candidate
  • scripts/verify-public-submodule.sh:52-66 在严格模式比较不可变标签和父仓 gitlink;
  • 当前脚本仍验证 pinvou-v0.9.5-r7 -> a36e6cd...,父仓 gitlink 则是 a647ed866...
  • 远端当前不存在 pinvou-v0.9.5-r8

本地实际执行严格校验结果:

错误:pinvou-v0.9.5-r7 解引用为 a36e6cd533024cfe5724bae21875aea42b2ed87a,父仓 gitlink 为 a647ed866c9adaac687dc1b892935537adcdaaf5
exit 1

CodeWhale #15 已合并为 d127aed,而且 d127aeda647ed866 的 Git tree 完全相同。因此底座代码已经落地,但仍需发布 r8,并将父仓 gitlink、PINVOU_CODEWHALE_TAG、fork 登记和指纹一起对齐。

2. [P1] 功能没有进入任何发行包,安装用户实际上用不了

pinvou 是独立 workspace 中的新二进制,仅定义在 pinvou-cli/crates/cli/Cargo.toml:22-24。桌面应用默认 feature 仍只有 local-embedbenchmark-hooks 是可选 feature(pinvou3-app/src-tauri/Cargo.toml:29-33)。

现有 release-packages.yml 仍只构建和上传:

  • Linux:Tauri .deb
  • Windows:NSIS .exe
  • macOS:Tauri .dmg

工作流没有构建、复制或打包 pinvou CLI,也没有将其配置为 Tauri sidecar。仓库 README、安装脚本和发布配置中也没有 CLI 安装入口,但 docs/gaia-benchmark.md 直接写“Pinvou Agent 内置”并要求用户执行 pinvou benchmark ...

影响:PR 合并后,从官网安装桌面应用的用户仍找不到这个命令,只能自行从源码构建。这不构成社区版用户可使用的完整闭环。

3. [P1] 在运行未完成时评分,会永久卡住之后的最终评分

pinvou-cli/crates/cli/src/lib.rs:749-753score_gaia() 接受任意当前 run,包括 partial/仍在运行的 run,然后立即调用 publish_gaia_score_artifacts()

publish_gaia_score_artifacts() 将结果写入固定名称 score.jsonreport.mdlib.rs:816-850 对已有但内容不同的文件直接返回 gaia_score_artifact_conflict,不允许更新。

因此下面是完全合法、但会卡死最终报告的操作序列:

run 尚未结束 → score 得到 unofficial_partial
→ run 继续完成,或稍后 resume
→ 再次 score
→ gaia_score_artifact_conflict

最终正式报告无法生成,旧 partial 报告会一直保留。建议禁止活动/可恢复 run 发布固定报告,或者引入版本化报告及受控替换机制。

4. [P2] 规划阶段中断后,resume 会永久漏掉尚未写入的任务

pinvou-cli/crates/benchmark-core/src/store.rs:321-326plan_tasks() 是逐题追加 Planned 事件,不是原子计划记录。

如果 53 题写到一半时进程退出或磁盘写入失败:

  • resume_adapter() 会重新生成完整 adapter plan(service.rs:164-166);
  • execute() 只运行 recover().runnable_task_ids() 中已经持久化为 Planned 的任务(service.rs:204-216);
  • 从未写入 Planned 的后半部分不会补登记,也永远不会执行。

结果是同一个 run 永远只能保持 partial,resume 无法兑现恢复完整计划的语义。建议原子持久化整个计划,或者在 resume 时将重新生成的计划与持久状态进行完整核对和补齐。

5. [P2] 文档承诺“显式 purge”,但产品不存在 purge 功能

docs/gaia-benchmark.md:80-83 写明私有预测“持久化直到显式 purge”,核心策略也命名为 DurableUntilPurge

pinvou-cli/crates/cli/src/lib.rs:140-166 的命令枚举只有:

  • list / fetch / verify / run;
  • status / resume;
  • score / report / submission。

仓库中没有 benchmark purge/delete 命令,也没有其他评测运行数据删除入口。

影响:候选答案、报告、运行记录和 gated 数据快照长期留在 ~/.pinvou3,用户只能自行识别目录并手工删除。应补充窄范围、安全、可验证的 purge 生命周期,并同步文档和测试。

6. [P2] Submission 的符号链接安全承诺与实现相反

docs/gaia-benchmark.md:133-138 声称“目标路径或其祖先存在符号链接/reparse point 时返回 gaia_submission_target_unsafe”。

pinvou-cli/crates/adapter-gaia/src/submission.rs:202-228validate_parent() 明确只拒绝叶子父目录,并主动接受带符号链接的祖先;对应 Unix 测试也要求 symlink ancestor 必须成功。

当前 no-clobber 设计能防止覆盖已有文件,但不能兑现“祖先不可重定向”的路径绑定承诺。至少应修正文档;如果祖先不可重定向确实是安全边界,则应基于 canonical parent 重建最终目标路径,避免校验路径和实际 hard-link 目标路径不同。

7. CI 对核心交付路径存在 required-check 缺口

.github/workflows/pr-check.yml:1085-1100 的 required benchmark-contract

  • 只测试 agent-backend-apibenchmark-coreadapter-smokeadapter-gaia
  • CLI 只用 --no-default-features 测试,排除了 product-backend
  • headless integration test 只执行 cargo check --test,没有执行测试。

真正的 pinvou-cli --all-features 和 headless 行为测试在 benchmark-test job 中,但该 job 需要 ci:full-benchmark 标签,不参与 required-gate;当前 head 的该 job 是 skipped。Linux rust-test 当前同样 skipped。

本地补跑后当前代码能通过,因此这不是当前编译失败;但以后 product-backend 或 headless 生命周期发生回归时,required checks 仍可能全部绿灯。

对现有功能的实际影响

现有区域 当前 PR 带来的变化
桌面 UI 没有评测入口,普通安装用户看不到 GAIA 功能。
普通聊天 开始把整个会话生命周期 token 总数持久化;token 持久化失败时,会把本来完成的聊天回合改报失败(forwarder.rs:648-749)。
会话删除 即使磁盘删除失败,也会清除该会话的运行时 lifecycle、Shell task 和评测模型状态(engine_pool.rs:242-249)。
会话历史 所有 eval_ 前缀会话都从普通历史中隐藏,防止崩溃残留泄露题目;同前缀的非评测会话也会被隐藏(features/sessions/store.rs:180-195)。
通知 缺少 NotificationState 时从“尝试通知”改成“不通知”,避免无窗口宿主 panic;正常 GUI 已管理该状态,预期不受影响。
CodeWhale 公共 engine/turn loop 有 10 文件、约 +1370/-202 的改动。普通 GUI 仍传 turn_tool_security: None,设计上保留原行为;但这是共享底座状态机,回归面明显大于独立 CLI。
网络与隐私 只有显式 fetch 才访问 Hugging Face,不自动上传结果;Windows 含附件的 GAIA 端到端运行被显式拒绝。

附件代码从 app/commands/attachments.rs 迁移到 features/assistant/attachments.rs,并由 lib.rs 保留 re-export,预期只是模块边界调整,不改变普通附件行为。

本轮验证结果

  • PR current head:d9c89dc654eff5083ec8f231f32349ba76fcc008
  • 当前 origin/maina7c239433d6e5a7ff5c7c5ebbade1caedc9794fa
  • PR 和 main 各自比共同基线前进 23 个提交;git merge-tree 无文本冲突,但尚无当前组合提交的完整 CI。
  • 115 个 benchmark/GAIA/core 测试全部通过。
  • 16 个 headless bridge 契约测试全部通过。
  • pinvou-cli --all-features 产品后端编译通过。
  • architecture guard、fork guard、PR 候选 gitlink 校验通过。
  • Merge Queue 使用的严格 gitlink 校验确定失败。
  • 当前 GitHub required checks 全绿,但 benchmark-test 和 Linux rust-test 都是 skipped。

尚未验证:真实 Hugging Face gated 下载、真实模型完整 53 题执行、官方 Python scorer 逐题交叉验证、真实发行包装载,以及当前 main + PR 组合提交的最终 Merge Queue 矩阵。

综上,这个 PR 的核心架构方向和多数安全实现是成立的,但目前仍存在发行闭环、评分状态机、恢复状态机、数据清理生命周期和 Merge Queue gitlink/tag 对齐等明确缺口,修复前不建议合并。

@h3c-hexin

Copy link
Copy Markdown
Contributor

复评补充(基于 current head 599d1fe9caa7fe6b407ad1625bd4361bc9089bb0,并按“CLI 独立发布、不进入桌面安装包”的既定边界):

结论:默认桌面运行时隔离方向已基本成立,但当前仍建议暂不合入。 此前“CLI 未进入桌面安装包”的意见撤回;桌面默认 feature 不启用 benchmark-hooks、现有桌面 release 不包含 CLI,符合独立发布设计。新 head 也已修复 PRODUCT_V1 重复定义,当前 windows-rust-test 已通过。

仍需阻断合入的问题

  1. 独立 CLI 的自动契约门禁被整体关闭。

    benchmark-contract 由按 needs.changes.outputs.benchmark 自动触发,改成只有 PR 人工添加 ci:full-benchmark 标签才运行,并且条件显式排除了 merge_group。当前 head 的 benchmark-contractbenchmark-test 均为 SKIPPED,但 required-gateskipped 当作成功,已经显示绿色。

    这意味着 pinvou-cli/** 或共享 headless runtime 发生变化时,默认 PR 和 Merge Queue 都没有验证 CLI/product-backend/headless 契约。建议拆为:

    • cli-contractpinvou-cli/** 变化时自动运行 core、adapter 和 CLI 无 product-backend 测试;
    • benchmark-hooks-contract:共享 headless/runtime 路径变化时自动运行 feature-gated lib/integration 测试;
    • benchmark-full:保留标签触发的完整诊断矩阵。

    前两项进入 required-gate,普通主工程 PR 因路径不命中而跳过,既不拖累主工程,也不会让独立 CLI 裸奔。

  2. partial score → resume → final score 仍会发生固定产物冲突。

    score_gaia()允许读取未完成 run,并立即写固定的 score.json / report.md。resume 补完任务后再次评分,文件内容变化,publish_gaia_score_artifacts() 会返回 gaia_score_artifact_conflict

    可复现状态序列仍是:

    run 中断 → score 生成 partial 固定产物 → resume 完成 → 再 score → conflict
    

    建议禁止可恢复 run 发布固定最终产物,或按 run revision/outcome digest 原子更新或版本化评分产物,并补完整状态机回归。

  3. 任务计划逐条落盘,规划阶段崩溃后 resume 仍会漏题。

    RunStore::plan_tasks()逐题追加 Planned;中途崩溃会留下合法但不完整的计划。resume_adapter()虽然重新生成完整 plan,execute()却只运行 recover().runnable_task_ids() 中已落盘的任务,缺失任务不会补登记,可能得到 remaining=0 但实际从未执行完整数据集。

    建议原子发布完整计划,或在 resume 时依据固定 plan digest 严格核对并补齐缺失任务。

  4. 评分可信度的 CLI 契约与文档不一致。

    文档已明确完整状态应为 complete_pinned_rust_port,且尚未证明与官方 Python scorer 逐题等价;实际 CLI 仍输出 official_compatible_local,registry 描述仍是“官方兼容本地评分”。独立发布前应统一为文档中的保守契约,避免对外宣称未经验证的等价性。

外部前置与描述同步

  • CodeWhale PR fix: 统一侧边栏定时任务记录样式 #15 已合并为 d127aed113529dc93754d044b9f352e9746f6b83,其 tree 与原候选 a647ed866 相同;但远端当前仍没有 pinvou-v0.9.5-r8
  • 父仓 gitlink 仍指向候选 a647ed866,严格校验仍锁定 r7;Merge Queue 前仍需发布不可变 r8,并同步 gitlink、PINVOU_CODEWHALE_TAG、fork 登记和指纹。
  • PR 正文仍写 CodeWhale fix: 统一侧边栏定时任务记录样式 #15 未合并、gitlink 为 21e5f...,并保留已经被隔离提交撤销/限定为评测专用的 GUI 行为变化,需要按 current head 更新。
  • 当前仓库尚无独立 CLI 发布 workflow。如果计划在后续 PR 完成,这不阻塞“代码隔离”本身,但本 PR 现阶段只能称为源码构建入口,不能宣称已完成独立发布链路。

本轮验证

  • 隔离策略测试:5/5;CI 策略测试:14/14;git diff --check 通过。
  • CLI 无 product-backend:21/21。
  • benchmark core、GAIA、Smoke、backend:126/126。
  • current head 的 Windows 全目标检查及完整 windows-rust-test 已通过。
  • 未取得 current head 的全特性 benchmark/headless 自动验证证据:两个相关 CI job 均被默认跳过。

综上:主工程默认运行时隔离基本达到目标;合入前仍应恢复按路径自动运行的最小 CLI 门禁,并修复评分恢复与计划恢复两个状态机问题。

@h3c-hexin

Copy link
Copy Markdown
Contributor

基于 current head 599d1fe 重新按实际代码复评。本轮按明确目标判断:CLI 独立发布,不进入桌面安装包,并且默认桌面行为不受影响;以下结论不沿用历史评审意见。

结论

隔离方向基本成立:当前桌面默认 feature、默认依赖图和 release-packages 打包流程均未纳入 CLI,benchmark-hooks 也已改为可选 feature;普通桌面会话不会注入 benchmark 的逐轮安全限制。此前把 benchmark 行为带入 GUI 的通知、删除、计时等改动,大部分也已恢复或收进 feature gate。

但当前还不能批准合并。剩余问题不是“CLI 是否进入桌面包”,而是独立 CLI 自身存在两个可复现的持久化/恢复正确性问题,CLI 门禁默认不运行,另外默认 GUI 路径仍残留一次无条件 session 读取。独立发布链路和 CodeWhale r8 前置也尚未闭环。

这份 PR 当前实际做什么

  • 新增独立的 pinvou-cli workspace,用于 GAIA Validation Level 1 的固定数据获取、校验、运行、恢复、评分、报告和 submission 生成。
  • benchmark-hooks 复用真实 Pinvou 产品 runtime,而不是另造一套模型/工具执行循环。
  • CodeWhale 增加逐轮工具限制能力,使 benchmark 可以限制工具集、禁用动态 tool search。
  • 将 benchmark 私有数据、预测结果和运行记录持久化到 run 目录,支持中断恢复。
  • CLI 的产品后端仍链接 pinvou3-tauri,因此它是“单独产物/单独发布”,但不是脱离桌面 Rust runtime 与系统依赖的纯独立实现。

已确认通过的隔离边界

  • pinvou3-tauri 默认 feature 仍只有 local-embed,benchmark-hooks 为可选 feature。
  • 默认 Cargo dependency graph 中没有 agent-backend-api、pinvou-cli、pinvou-product-backend。
  • eval 相关模块和入口均受 feature gate 约束。
  • .github/workflows/release-packages.yml 当前不构建、不上传 CLI。
  • 普通聊天路径的 turn_tool_security 为 None,不会继承 benchmark 限制。
  • GUI 原有通知、删除和主要计时语义已恢复;CodeWhale fork guard 的 34 项检查通过。

阻塞问题

1. [P1] CLI 唯一的 CI 验证是 label-only,当前 PR 实际被跳过

.github/workflows/pr-check.yml:1033-1037 的 benchmark-contract 和对应 benchmark-test 只在 pull_request 且带 ci:full-benchmark 标签时运行;它们不覆盖 merge_group/main。required-gate 在 1237-1238 又把 skipped 当作可接受结果。

当前 PR 没有该标签,因此 current head 上 benchmark-contract 和 benchmark-test 均为 SKIPPED,而 required-gate 可以成功。常规 Rust jobs 只验证桌面 app,不能替代 pinvou-cli workspace 的编译与测试。也就是说,这个新增独立发布物可以在完全没有被 CI 编译/测试的情况下进入 Merge Queue。

建议把便宜且确定性的 pinvou-cli core/adapter/no-default 检查设为按 pinvou-cli/** 路径触发的必跑门禁,并覆盖 pull_request 与 merge_group;重型 Tauri、Windows、CodeWhale 集成仍可保留为 opt-in/full benchmark。

2. [P1] 运行未完成时生成的 partial score 会锁死最终结果

pinvou-cli/crates/cli/src/lib.rs:749-753 允许对未完成 run 评分,并把结果标为 unofficial_partial;随后仍会写入固定的 score.json 和 report.md。运行恢复并完成后,如果最终分数与 partial 内容不同,lib.rs:844-850 会返回 gaia_score_artifact_conflict,而不会更新最终产物。

可复现顺序是:

  1. run 尚未完成时执行 score;
  2. 得到 partial score.json/report.md;
  3. resume 补完剩余任务;
  4. 再执行最终 score;
  5. 因固定产物与新结果不同而失败。

这使“可恢复运行”和“允许 partial score”两个能力组合后无法产出最终官方结果。应在 run 完成前拒绝发布固定评分产物,或者将 partial/final 分离,并对最终产物提供安全、原子且有版本约束的更新规则。

3. [P1] 任务计划不是原子持久化,崩溃后 resume 可能永久漏题

pinvou-cli/crates/benchmark-core/src/service.rs:93-97 先创建 RunStore,再执行 adapter.prepare,之后才逐条写 Planned 事件;RunStore::plan_tasks(store.rs:321-325)也是逐条 append。

因此在 prepare 失败、进程崩溃或写入中断时,磁盘上可能留下空计划或前缀计划。resume_adapter(service.rs:164-166)不会重新准备和校验完整任务集合;store.recover(store.rs:422-454)只能恢复已经持久化的 Planned ID。未写入的题目会从 run 的世界里永久消失,甚至可能把零任务/部分任务运行报告为“剩余为零”。

建议把完整任务计划作为一个原子快照发布,或在 manifest 中绑定 task set/digest;resume 时必须校验计划完整性,并选择修复或明确拒绝恢复,不能把前缀计划当成合法全集。

4. [P2] 默认 GUI 路径仍残留一次无条件 session 读取

pinvou3-app/src-tauri/src/features/assistant/engine.rs:1164-1169 现在无论是否 scheduled session 都会调用 store.load 获取 persisted_total_tokens。主线原行为只在 scheduled 场景读取。

虽然本 PR 已移除普通 GUI 的 benchmark token 持久化,但这次无条件读取仍给默认桌面路径增加了磁盘 I/O 和新的失败点,不符合“默认主工程不受影响”的目标。这里应恢复为仅 scheduled 路径读取。

合并/发布前置

这些不是上述状态机 bug 的替代项,但仍需要在合并或声明“可独立发布”前闭环:

  • CodeWhale fix: 统一侧边栏定时任务记录样式 #15 已合并,但远端还没有 pinvou-v0.9.5-r8;父仓 gitlink 当前指向候选 a647ed866,scripts/verify-public-submodule.sh:7 仍固定 r7。进入 Merge Queue 前需要完成 r8 tag,并同步 gitlink、验证脚本和登记。
  • docs/fork-modifications.md:23 仍写着 CodeWhale PR fix: 统一侧边栏定时任务记录样式 #15 未合并,已与实际状态不一致。
  • 当前没有独立 CLI release workflow/artifact,文档提供的是源码构建。如果独立发布由后续 PR 完成,应把它明确列为 release dependency;否则本 PR 尚未交付“单独发布”闭环。
  • pinvou-product-backend 直接链接 pinvou3-tauri;Linux 产物链接 GTK/WebKit/Soup,并启动无窗口 Tauri event loop。发布前需要干净机器依赖/安装 smoke,以及无显示环境 smoke。现有 integration tests mock 了 ProductRuntimePort,没有执行真实 run_headless_host。
  • PR 正文已落后于 current head:仍描述普通删除/通知/GUI 计时语义被修改,仍写 CodeWhale 当前为 21e5f661a 且 fix: 统一侧边栏定时任务记录样式 #15 未合并;这些内容应按当前代码更新,避免后续合并与发布判断引用过期事实。

已公开但仍需保留说明的能力边界

  • 尚未做官方 Python scorer 的逐题结果一致性验证。
  • Windows GAIA attachment E2E 仍不支持。
  • 没有 purge 命令,敏感 run 数据需要按目录手工清理。
  • 尚无真实 gated Hugging Face 下载/模型端到端证据,也没有已发布的 CLI artifact。

本轮实际验证

599d1fe 上完成/确认:

  • benchmark isolation 与 CI policy 测试;
  • cargo fmt;
  • pinvou-cli core/adapter 定向测试 150 项;
  • pinvou-cli no-default 测试 21 项;
  • CodeWhale fork guard 34 项;
  • headless feature unit tests 11 项;
  • headless integration contract cargo check;
  • architecture guard、父仓 fork guard、diff check;
  • 与当前 main 的 merge-tree 无文本冲突;
  • 默认 Cargo tree 中不存在 CLI/benchmark 产品后端依赖。

最终判断:桌面默认入口和桌面发行包的隔离已经基本通过,但 CLI 正确性、默认 CI 门禁及独立发布闭环尚未通过。建议先修复上述 4 项,并完成 r8/发布前置,再进入批准阶段。

@JensenChen28

Copy link
Copy Markdown
Collaborator Author

已基于最新 4 点在 b09dd161 完成处理,请按 current head 复审。

  1. Benchmark CI:不采纳自动开启建议,按本 PR 的隔离目标硬关闭。 .github/workflows/pr-check.ymlbenchmark-contractbenchmark-test 均改为字面量 if: ${{ false }},不再接受 PR label、路径变化、push 或 merge_group 事件开启;test_ci_gate_policy.py 已锁定该约束,避免后续通过 ci:full-benchmark 等标签重新开启。评测验证只在隔离的本地环境执行,不进入主工程 PR/MQ 门禁。
  2. partial score → resume → final score 冲突:已修复。 未完成报告仍可在 CLI 中查看,但不再发布固定 score.json / report.md;固定产物仅在 complete report 时生成,并补充了 partial 不落盘回归测试。
  3. 规划阶段中断后 resume 漏题:已修复。 resume 使用重新生成的固定计划核对已持久化 Planned 集合,补写缺失任务;若磁盘存在固定计划之外的任务则 fail-closed 为 resume_plan_mismatch。已覆盖“补齐前缀计划”和“拒绝意外任务”测试。
  4. 默认 GUI 无条件 session 读取:已修复。 store.load(session_id) 已恢复为仅 scheduled session 路径调用,普通 GUI 会话不新增该磁盘 I/O/失败点。

验证结果:benchmark-core 49 项通过,CLI partial-score 定向测试通过,Python CI/隔离策略 53 项通过,Rust format、diff check 与 architecture guard 通过。

当前没有未解决的行级 review thread;以上为顶层评论的逐项处理说明。请基于 b09dd161ca0fd83ee52e302f58ecad66e7961ee6 复审。

c24894 added 6 commits August 21, 2026 16:08
Signed-off-by: c24894 <chen.zhusen@h3c.com>
Signed-off-by: c24894 <chen.zhusen@h3c.com>
Signed-off-by: c24894 <chen.zhusen@h3c.com>
Signed-off-by: c24894 <chen.zhusen@h3c.com>
Signed-off-by: c24894 <chen.zhusen@h3c.com>
Signed-off-by: c24894 <chen.zhusen@h3c.com>
Signed-off-by: c24894 <chen.zhusen@h3c.com>
@JensenChen28
JensenChen28 force-pushed the codex/cli-gaia-benchmark branch from b09dd16 to 384c1b8 Compare August 21, 2026 08:09
@h3c-hexin

Copy link
Copy Markdown
Contributor

基于 current head 384c1b8 继续复评。本次 force-push 已把最新 main 纳入分支,GitHub 当前恢复为 MERGEABLE;以下结论只针对该 current head。

结论

桌面隔离目标继续成立:默认桌面 feature、默认 Cargo 依赖图和桌面 release-packages 均不包含 pinvou-cli/benchmark product backend,普通会话也不安装 benchmark 逐轮安全策略。本次主线对齐后没有发现新的默认桌面行为回归。

但仍不能批准合并/发布。当前有两个独立 CLI 正确性/可发布性阻塞项,以及 CI、r8 和文档闭环问题。

已修复

  • GAIA partial score 不再写固定的 score.json/report.md,恢复完成后不会再被旧 partial 产物锁死。
  • 默认 GUI 的 session token 读取已恢复为仅 scheduled session 执行。
  • GAIA 的 resume_adapter 已重新生成固定计划并调用 reconcile_planned_tasks,可补齐规划阶段崩溃留下的前缀计划。
  • 与最新 main 的 docs/fork-modifications.md 文本冲突已经解决。

仍然阻塞

1. [P1] Smoke 的通用恢复路径仍会永久漏 case

pinvou-cli/crates/benchmark-core/src/service.rs:66-69 的通用 run 仍逐条写 Planned 事件;如果进程在中途崩溃,磁盘只留下前缀计划。

新增的计划对账只接入 resume_adapter(service.rs:142-166)。Smoke CLI 在 pinvou-cli/crates/cli/src/lib.rs:1075-1097 走的是通用 service.resume,而 service.rs:125-139 直接调用 execute,没有 reconcile_planned_tasks。

因此 Smoke 的可复现路径仍是:计划写到一半崩溃 -> resume -> 只执行已经落盘的前缀 case -> 未落盘 case 永久消失。建议在通用 resume 同样执行计划对账,并增加 service/CLI 调用链级回归测试;当前新增测试只直接覆盖 RunStore helper。

2. [P1] 主线对齐后 pinvou-cli/Cargo.lock 已失配,独立 locked 构建失败

384c1b8 上执行 cargo test --manifest-path pinvou-cli/Cargo.toml --all-features 时,Cargo 自动改写了 pinvou-cli/Cargo.lock,包括:

  • pinvou3-tauri / pinvou-knowledge:0.8.4 -> 0.8.5;
  • agent-client-protocol:1.3.0 -> 2.0.0;
  • agent-client-protocol-schema:1.4.0 -> 1.5.0;
  • 同步移除 device_query 旧依赖并更新 Windows/X11 依赖图。

恢复提交中的 lockfile 后,实际执行:

cargo test --manifest-path pinvou-cli/Cargo.toml --all-features --locked --no-run

会直接失败:

cannot update the lock file .../pinvou-cli/Cargo.lock because --locked was passed

CLI 既然要单独发布,必须提交与 current main/path dependencies 一致的 lockfile,并把 --locked 构建作为独立发布门禁。否则构建结果依赖现场重新解析,无法复现,正式发布通常也会失败。

3. [P1/发布门禁] 主工程 benchmark jobs 被硬关闭,但没有独立 CLI workflow 承接

.github/workflows/pr-check.yml:1034-1039 和 1115-1119 把 benchmark-contract、benchmark-test 固定为 if: false;当前 head 上两项确实均为 SKIPPED,而 required-gate 接受 skipped 并成功。

硬关闭主工程 benchmark jobs 符合“不影响桌面主工程 CI”的方向,但仓库没有独立 CLI check/release workflow,也没有 CLI artifact。建议把 CLI 编译、核心测试、locked build 和发布 smoke 放到独立 workflow,按 pinvou-cli/** 或 CLI release 事件触发,不接入桌面主工程 required-gate。当前保留两段永久关闭的 workflow 代码没有验证价值。

r8 与文档前置

  • CodeWhale fix: 统一侧边栏定时任务记录样式 #15 已合并,pinvou3-clean 当前是 merge commit d127aed113529dc93754d044b9f352e9746f6b83。
  • 远端仍不存在 pinvou-v0.9.5-r8。
  • 父仓 gitlink 仍指向 PR head a647ed866,scripts/verify-public-submodule.sh 仍锁定 r7。
  • docs/fork-modifications.md:23 和 PR 正文仍写 fix: 统一侧边栏定时任务记录样式 #15 尚未合并/旧 SHA。
  • docs/gaia-benchmark.md:100 仍笼统声称 score 会写 report.md/score.json;现在 partial 只输出不可比结果,不生成固定产物。
  • PR 正文仍声明普通桌面删除、通知和 timing 行为发生变化,但 current head 已恢复或隔离这些行为,应按实际 diff 更新。

进入 Merge Queue 前仍需发布不可变 r8,并对齐 gitlink、验证脚本、fork 登记及 PR 正文。若 CLI 发布由后续 PR 完成,也应把独立 workflow/artifact 明确列为 release dependency。

本轮验证

  • current head 384c1b8 上 CLI all-features 共 170 项测试通过,但该次运行依赖 Cargo 自动更新失配的 CLI lockfile。
  • 恢复提交版 lockfile 后,CLI all-features --locked 编译确认失败。
  • CI/isolation/headless policy 22 项通过。
  • architecture guard、fork guard --fast、git show --check 通过。
  • 默认桌面 Cargo graph 未出现 CLI/benchmark crates。
  • GitHub required-gate 已成功,但 benchmark-contract/benchmark-test 均为 SKIPPED;复评时 windows-rust-test 仍在运行。

最终判断:默认桌面入口和桌面发行包隔离通过;独立 CLI 的 Smoke 恢复、lockfile、独立质量门禁和 r8/发布闭环未通过。建议修完上述项后再批准。

c24894 and others added 2 commits August 21, 2026 16:50
Signed-off-by: c24894 <chen.zhusen@h3c.com>
将父仓 gitlink 与公开校验切换到已合并的 r8 不可变标签,并同步中英文 fork 清单、维护策略和升级状态。

移除仅用于 PR 候选期的 gitlink 放行;不启用 benchmark CI 或 CLI 发布链路。

Signed-off-by: hexin <372726039@qq.com>
asto18089
asto18089 previously approved these changes Aug 21, 2026

@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.

第五轮复审结论:Approve(独立重审,基于 head 5f8c0f5d)

本轮将 PR 完整 rebase 到最新 origin/main(c75f2fb2)解决全部冲突并逐项重验后,又对作者最新的 r8 对齐提交做了完整核验。结论:平台成立、根因完整交付、无阻断级缺陷,建议合并

一、根因交付链路逐段核实(成立)

  • 端到端接线真实:pinvou-clibenchmark-coreadapter-gaiaagent-backend-api(opaque handle / zeroizing secret)→ pinvou-product-backend(14 行薄桥,path 依赖 app 的 benchmark-hooks feature)→ headless_bridgeEnginePool → CodeWhale 受限逐轮安全。cargo test --all-features 170+ 测试全绿。
  • 隐私边界成立:私有预测 handle 为 256-bit 随机(private_prediction.rs:168),AES-GCM/DPAPI 加密绑定 (run_id, task_id, prediction_type, handle) 四元组(binding_bytes),Unix 侧 0600/0700 权限在读取路径复检(private_prediction.rs:471/491);公开 outcome 只存 opaque handle。
  • 受限工具边界到底:CodeWhale tool_execution.rs:399-421 在最终分发前执行 exact allowlist + is_read_only_for 双重门禁;File 工具覆写为 action 级判断(file_tool.rs:303,仅 read/list/search_name/search_content),MCP 工具不在 eval 白名单内天然被 exact 门禁拒绝。core(benchmark-core)零 GAIA 泄漏(仅测试字符串),第二适配器(adapter-smoke)证明可复用性是真实的。
  • scorer 忠实移植:数字(parse_python_float + Unicode 数字折叠 + 下划线规则)/列表(逗号分号逐元素)/字符串(去空白+小写+去标点)三分支对齐官方语义,且文档诚实声明"未做逐题 parity 验证"。

二、隔离边界(上轮的核心争议)——已彻底成立

逐块核实所有 app 侧 diff:GUI 默认构建(--lib --tests 编译验证通过)行为变更实际为零——

  • engine_pool/eval 模型快照、send_eval_user_messagecreate_empty_with_ideval_ 会话前缀过滤(store.rs:194)全部 cfg(feature="benchmark-hooks") 门控;
  • maybe_notify_task_completed 的默认翻转仅 benchmark-hooks 构建生效,默认构建保持 unwrap_or(true) 原语义(engine_support.rs);
  • timing/forwarder 的 observation 收尾与 tool_calls/tool_failures/catalog 摘要字段仅对 eval_observation_enabled 会话生效,普通会话收尾仍是 usage 版;
  • delete_eval_session 的失败即 forget + late sweep 仅作用于 eval 会话,delete_chat_session 与 main 逐行一致;
  • attachments 为纯搬移(facade 留在 app/commands,实现在 features),web access-policy 无 eval 入口,发布链路无 CLI 制品。

新版 PR 正文的"隔离边界"一节与代码完全一致。

三、r8 对齐——已验证为真实

pinvou-v0.9.5-r8 标签已发布并解引用到 d127aed1,与 CodeWhale PR #15 squash 合并结果及本 PR gitlink 三者一致(候选链 head a647ed866 与合并树零差异,已逐字节验证);候选期放行逻辑已从 verify-public-submodule.sh 移除,merge queue 严格标签校验现已实际可过。fork-guard(34 条 CodeWhale 指纹 + 65 项 --fast)、architecture-guard、三个 CI 策略测试全绿。

四、验证复现

作者声明的全部命令逐一复现:--locked --all-features 四 crate 子集 153 通过、--no-default-features 22 通过、CodeWhale forkguard_ 34 通过、策略测试 22 通过、verify-public-submodule.sh 通过。锁文件与当前 main 依赖图一致(device_query 退役已反映),--locked 不再触发重解析。

五、遗留观察(均非阻断,记录备查)

  1. PR 正文三处显式声明与代码不符 已随正文重写解决。
  2. CodeQL 的 3 条 "Access of invalid pointer" 告警指向已不存在的旧路径(adapter-gaia/src/fetch/windows_private_acl.rs),实现已移入 benchmark-core 并以 RAII(LocalFree in Drop)+空指针检查重写,最新 CodeQL 全过——陈旧告警,无需处理。
  3. EnginePool::send_user_message#[allow(dead_code)] 是正确的:消费方 product_runtime::submit 非 eval 分支在 benchmark-hooks 门控内(mod.rs:247),默认构建下确需该标注。
  4. 已知限制(HF gated 下载未实跑、Windows 附件 fail-closed、product-backend 全量冷编译因磁盘未完成)均已诚实声明;其中第 3 项建议合并后在有富余磁盘的隔离环境补一次完整 --all-features --locked 冷编译冒烟。

感谢两位作者的持续响应,五轮迭代后的最终形态质量很高。

Signed-off-by: c24894 <chen.zhusen@h3c.com>
@JensenChen28
JensenChen28 added this pull request to the merge queue Aug 24, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 24, 2026
@JensenChen28
JensenChen28 added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit 1e1d7b7 Aug 24, 2026
30 checks passed
@JensenChen28
JensenChen28 deleted the codex/cli-gaia-benchmark branch August 24, 2026 08:46
asto18089 added a commit that referenced this pull request Aug 24, 2026
Rebase conflict in timing.rs: #305 introduced finish_turn_internal
with ActiveTurnTiming entries and benchmark-hook observation params,
but popped the queue front (FIFO). The tail-pop semantics (terminal
attributed to the newest queued turn, stale unsubmitted-cancel
residue cleared without a terminal, queue cleared whole) is ported
into finish_turn_internal; the five observation accessors
(record_first_event, tool counters, milestone) now read the queue
back to match, so observations land on the live turn.

#305's queued_second_turn_survives_single_observation_finish locked
"one finish consumes exactly one turn" with FIFO attribution as a
side effect; the attribution assertions are adapted to the tail-pop
semantics (terminal lands on the newest turn, stale first gets no
terminal, emptied-queue finish is a no-op) while keeping the
double-finish protection intent.

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit that referenced this pull request Aug 24, 2026
Rebase conflict in timing.rs: #305 introduced finish_turn_internal
with ActiveTurnTiming entries and benchmark-hook observation params,
but popped the queue front (FIFO). The tail-pop semantics (terminal
attributed to the newest queued turn, stale unsubmitted-cancel
residue cleared without a terminal, queue cleared whole) is ported
into finish_turn_internal; the five observation accessors
(record_first_event, tool counters, milestone) now read the queue
back to match, so observations land on the live turn.

#305's queued_second_turn_survives_single_observation_finish locked
"one finish consumes exactly one turn" with FIFO attribution as a
side effect; the attribution assertions are adapted to the tail-pop
semantics (terminal lands on the newest turn, stale first gets no
terminal, emptied-queue finish is a no-op) while keeping the
double-finish protection intent.

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit that referenced this pull request Aug 25, 2026
Rebase onto latest main (#305 registered CodeWhale r8) exposed stale
facts and a few audit leftovers; all comment/doc-only:

- THIRD_PARTY_NOTICES: CodeWhale baseline r7 -> r8 (gitlink d127aed11)
- fork-modifications(.en)/fork-policy(.en): reconcile dates with the
  2026-08-24 r8 registration; fix forkguard count 23 -> 34 in section 4
  (stale on main since #305)
- codewhale-upgrade report: snapshot note now describes section 9 as
  living text (PR #285 -> PR #305) instead of the pre-#305 reading
- acceptance checklist: drop dangling section 8.2 reference in row 3.2
- codex-acp: collapse inline npm/brew upgrade commands into the
  multi-agent-acp.md link (dedup the PR itself introduces)
- plugin-package-spec section 10: MCP id charset no longer claims
  lowercase enforcement the importer only applies to package ids
- plugin-protocol section 13.1: dependency-download row status now
  matches sections 3/10 (plugin.json dependencies key unconsumed)
- run-mac-verify.sh comment: point to pinvou3-app/INSTALL.md instead of
  a nonexistent README prerequisites section
- release-packages.yml comment: only package-lock.json is
  existence-gated; the other five version files are required

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit that referenced this pull request Aug 25, 2026
Rebase onto latest main (#305 registered CodeWhale r8) exposed stale
facts and a few audit leftovers; all comment/doc-only:

- THIRD_PARTY_NOTICES: CodeWhale baseline r7 -> r8 (gitlink d127aed11)
- fork-modifications(.en)/fork-policy(.en): reconcile dates with the
  2026-08-24 r8 registration; fix forkguard count 23 -> 34 in section 4
  (stale on main since #305)
- codewhale-upgrade report: snapshot note now describes section 9 as
  living text (PR #285 -> PR #305) instead of the pre-#305 reading
- acceptance checklist: drop dangling section 8.2 reference in row 3.2
- codex-acp: collapse inline npm/brew upgrade commands into the
  multi-agent-acp.md link (dedup the PR itself introduces)
- plugin-package-spec section 10: MCP id charset no longer claims
  lowercase enforcement the importer only applies to package ids
- plugin-protocol section 13.1: dependency-download row status now
  matches sections 3/10 (plugin.json dependencies key unconsumed)
- run-mac-verify.sh comment: point to pinvou3-app/INSTALL.md instead of
  a nonexistent README prerequisites section
- release-packages.yml comment: only package-lock.json is
  existence-gated; the other five version files are required

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit that referenced this pull request Aug 25, 2026
Rebase conflict in timing.rs: #305 introduced finish_turn_internal
with ActiveTurnTiming entries and benchmark-hook observation params,
but popped the queue front (FIFO). The tail-pop semantics (terminal
attributed to the newest queued turn, stale unsubmitted-cancel
residue cleared without a terminal, queue cleared whole) is ported
into finish_turn_internal; the five observation accessors
(record_first_event, tool counters, milestone) now read the queue
back to match, so observations land on the live turn.

#305's queued_second_turn_survives_single_observation_finish locked
"one finish consumes exactly one turn" with FIFO attribution as a
side effect; the attribution assertions are adapted to the tail-pop
semantics (terminal lands on the newest turn, stale first gets no
terminal, emptied-queue finish is a no-op) while keeping the
double-finish protection intent.

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit that referenced this pull request Aug 25, 2026
Rebase onto latest main (#305 registered CodeWhale r8) exposed stale
facts and a few audit leftovers; all comment/doc-only:

- THIRD_PARTY_NOTICES: CodeWhale baseline r7 -> r8 (gitlink d127aed11)
- fork-modifications(.en)/fork-policy(.en): reconcile dates with the
  2026-08-24 r8 registration; fix forkguard count 23 -> 34 in section 4
  (stale on main since #305)
- codewhale-upgrade report: snapshot note now describes section 9 as
  living text (PR #285 -> PR #305) instead of the pre-#305 reading
- acceptance checklist: drop dangling section 8.2 reference in row 3.2
- codex-acp: collapse inline npm/brew upgrade commands into the
  multi-agent-acp.md link (dedup the PR itself introduces)
- plugin-package-spec section 10: MCP id charset no longer claims
  lowercase enforcement the importer only applies to package ids
- plugin-protocol section 13.1: dependency-download row status now
  matches sections 3/10 (plugin.json dependencies key unconsumed)
- run-mac-verify.sh comment: point to pinvou3-app/INSTALL.md instead of
  a nonexistent README prerequisites section
- release-packages.yml comment: only package-lock.json is
  existence-gated; the other five version files are required

Signed-off-by: asto <asto18089@126.com>
zhuowp pushed a commit to zhuowp/pinvou-agent that referenced this pull request Aug 25, 2026
* docs: repo-wide documentation truthfulness audit

Audit all project-owned docs (root governance, README bilingual, fork
governance, plugin/marketplace specs, ACP/code-mode, knowledge domain,
remote control/ADR, frontend and Rust architecture READMEs, scripts and
CI comments) against the actual code, and fix the confirmed findings:

- README bilingual: remove unimplemented in-app update/OTA claim (only
  unsupported stubs exist), correct macOS target to universal builds,
  React 18->19 badge, complete model preset and repository layout lists
- INSTALL.md: replace hardcoded 0.6.2 deb name with the actual release
  artifact naming, add Ubuntu 22.04+/glibc 2.35+ floor, complete preset
  list
- THIRD_PARTY_NOTICES: CodeWhale baseline pinvou-v0.9.0-r4 -> r7, fix
  <os>/<arch> path template; lark NOTICE visual-design location
- fork governance: correct the gitlink bump attribution (Pinvou#285, not
  Pinvou#302), rewrite acceptance checklist references to retired v0.8
  forkguard tests, unify net-lines drift metric, sync lagging .en docs,
  add historical-snapshot note to the 0.9.5 upgrade report
- plugin protocol/spec: flat mcp/ layout (dir must be exactly 'mcp'),
  mark archive.zip and credentials/config_fields/dependencies as
  unimplemented, complete the import rejection rules incl. same-id
  update semantics, server.py hard constraint, DenyAll default note
- code-native-agent: add revision notes to §8.3/§8.6/§8.7 superseded by
  disabled_bundles.json and three-lane mode persistence; fix §8.6->§8.7
  cross-reference; dedupe codex-acp against multi-agent-acp
- remote control: fix phase1 doc file names (manager/, relay_client.rs),
  drop dangling ADR-0001 reference; PROTOCOL.md documents credential
  stripping, 24 error codes and desktop_endpoint_replaced (4001)
- ADR: add directory README (0001-0005 history, terminology mapping),
  rename 0010/0013 to match their titles, cross-link 0009/0019, 0010/0022
- Rust/frontend architecture READMEs: remove stale include!/#[path]
  migration description, nonexistent AttachmentStager, complete
  resources/platforms tree, codex-bridge official-install wording,
  updater stub comment, Cargo.lock 0.8.5->0.8.6
- governance: DCO.md matches actual CI behavior (presence check, merge
  commit exemption), zh CONTRIBUTING precedence note, document the
  local commit-msg hook, dedupe AGENTS/CONTRIBUTING fork-boundary and
  language-rule duplication

Verification: sync-version --check, architecture-guard, fork-guard
--fast, relay tests 23/23, frontend node tests 232/232 (2 skipped by
design), cargo check --locked, cargo test --lib 1393 passed (single-
threaded).

Signed-off-by: asto <asto@pinvou.com>
Signed-off-by: asto <asto18089@126.com>

* docs: correct audit errata and close gaps

Review follow-up for the repo-wide documentation audit (second-party
verification of ~150 claims; 96% verified, no fabrications):

- INSTALL.md: gemini preset goes through the official OpenAI-compatible
  endpoint (bridge.rs routes it on the openai wire), not a separate
  channel; keep the anthropic note as is
- CONTRIBUTING (en/zh): restore the conflict-resolution rule deleted
  from AGENTS.md (preserve compatible functionality from both sides,
  never silently pick between behaviorally different alternatives) so
  the AGENTS.md pointer to a single source of truth is no longer hollow
- fork-modifications.md: the gitlink/r7-tag identity still holds, but
  pinvou3-clean HEAD moved to d127aed1 via CodeWhale PR Pinvou#15 (r8 in
  flight, to be registered by the r8 sync PR); also record the Pinvou#302
  parent-side switch to the single disabled_bundles.json so the
  authoritative Chinese register matches the English summary
- remote-control-phase1-architecture.md: exclusive subscriptions, event
  sequencing, and replay state live in manager/ (mod.rs StreamState +
  rpc.rs helpers); relay_client.rs is the pure WebSocket transport
  layer (connect/reconnect backoff, heartbeat, backpressure)
- release-packages.yml comment: sync-version.mjs does not check
  pinvou3-app/src-tauri/Cargo.lock (hence the manual bump in this PR);
  package-lock.json is included only when present
- run-mac-verify.sh: list p7zip in the probe comment (the loop still
  checks it)
- README (en/zh): built-in template list also covers Anthropic and
  Gemini, matching model-catalog.js
- sbom.md: drop the dangling reference to a nonexistent manual release
  checklist; state the SBOM duty directly

Verification: sync-version --check, architecture-guard, fork-guard
--fast, cargo check --lib --locked all pass; bash -n on the touched
script.

Signed-off-by: asto <asto18089@126.com>

* docs: fix residual audit inaccuracies

Third-party re-verification of the whole PR against code, git
history, CI, and the release pipeline found five factual errors
introduced by the audit itself, plus six residual stale spots;
all fixed in place:

- plugin-package-spec Pinvou#11-9: 'uninstall then re-import' cannot
  clear the same-id conflict - uninstall deliberately retains
  bundles/<id>/ for Upload-source packages (user's only copy,
  mod.rs); corrected to package-id change or manual directory
  removal
- marketplace-unification-todo: the rewritten authority claim
  matched the write path only; the read path already flipped to
  BundleStore (bundle.rs installed-truth reversal, installed.json
  as corruption fallback; legacy list_marketplace_tools view and
  frontend fallback noted)
- code-native-agent: 18 is the NATIVE_CHAT_EVENTS subscription
  list length; the lane reducer consumes 19 chat:* events - both
  lines now attribute the counts correctly
- CONTRIBUTING.zh-CN: sync the merge-commit DCO exemption into
  the Chinese DCO sentence (en/zh parity)
- macos codex-bridge README: missing Codex CLI on macOS goes
  through the official install script (like Linux); brew only
  upgrades an existing brew-managed install
- residuals: entitlements.plist mac.signingIdentity ->
  macOS.signingIdentity; codex-acp.md release section now routes
  upgrades by install source per multi-agent-acp.md matrix;
  L1-judge-rubric stale illustrative numbers (r1 example, 4-item
  template note, 4->5 dim bump example); fork-modifications r8
  wording (CodeWhale tag already published, parent registration
  pending); prepare-codex-bridge-runtime.sh comment drops the
  stale 'managed Codex' phrase

Verification: sync-version --check, architecture-guard,
fork-guard --fast, bash -n on the touched script, plutil -lint
on entitlements.plist.

Signed-off-by: asto <asto18089@126.com>

* docs: fix rebase-stale facts and dangling refs

Rebase onto latest main (Pinvou#305 registered CodeWhale r8) exposed stale
facts and a few audit leftovers; all comment/doc-only:

- THIRD_PARTY_NOTICES: CodeWhale baseline r7 -> r8 (gitlink d127aed11)
- fork-modifications(.en)/fork-policy(.en): reconcile dates with the
  2026-08-24 r8 registration; fix forkguard count 23 -> 34 in section 4
  (stale on main since Pinvou#305)
- codewhale-upgrade report: snapshot note now describes section 9 as
  living text (PR Pinvou#285 -> PR Pinvou#305) instead of the pre-Pinvou#305 reading
- acceptance checklist: drop dangling section 8.2 reference in row 3.2
- codex-acp: collapse inline npm/brew upgrade commands into the
  multi-agent-acp.md link (dedup the PR itself introduces)
- plugin-package-spec section 10: MCP id charset no longer claims
  lowercase enforcement the importer only applies to package ids
- plugin-protocol section 13.1: dependency-download row status now
  matches sections 3/10 (plugin.json dependencies key unconsumed)
- run-mac-verify.sh comment: point to pinvou3-app/INSTALL.md instead of
  a nonexistent README prerequisites section
- release-packages.yml comment: only package-lock.json is
  existence-gated; the other five version files are required

Signed-off-by: asto <asto18089@126.com>

* docs: correct importer claims and plan_resolved wording

- plugin-package-spec.md: the manifest-driven import path validates only the
  package id charset; MCP/Skill component ids get no charset check and the
  Skill frontmatter is not parsed, so document the actual enforcement and
  mark the consistency/charset gaps as known and separately tracked. The
  unix_mode check matches symlinks only (S_IFLNK), not hardlinks.
- code-native-agent.md: NATIVE_CHAT_EVENTS subscribes to 18 chat:* events;
  chat:plan_resolved is emitted at runtime by discard_plan (interaction.rs)
  and consumed on the bridge path, but it is absent from NATIVE_CHAT_EVENTS,
  so the lane's switch branch is unreachable through this subscription and
  the consumed count stays 18, consistent with the architecture table.

Signed-off-by: asto <asto18089@126.com>

---------

Signed-off-by: asto <asto@pinvou.com>
Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit that referenced this pull request Aug 26, 2026
Rebase conflict in timing.rs: #305 introduced finish_turn_internal
with ActiveTurnTiming entries and benchmark-hook observation params,
but popped the queue front (FIFO). The tail-pop semantics (terminal
attributed to the newest queued turn, stale unsubmitted-cancel
residue cleared without a terminal, queue cleared whole) is ported
into finish_turn_internal; the five observation accessors
(record_first_event, tool counters, milestone) now read the queue
back to match, so observations land on the live turn.

#305's queued_second_turn_survives_single_observation_finish locked
"one finish consumes exactly one turn" with FIFO attribution as a
side effect; the attribution assertions are adapted to the tail-pop
semantics (terminal lands on the newest turn, stale first gets no
terminal, emptied-queue finish is a no-op) while keeping the
double-finish protection intent.

Signed-off-by: asto <asto18089@126.com>
asto18089 added a commit that referenced this pull request Aug 26, 2026
Rebase conflict in timing.rs: #305 introduced finish_turn_internal
with ActiveTurnTiming entries and benchmark-hook observation params,
but popped the queue front (FIFO). The tail-pop semantics (terminal
attributed to the newest queued turn, stale unsubmitted-cancel
residue cleared without a terminal, queue cleared whole) is ported
into finish_turn_internal; the five observation accessors
(record_first_event, tool counters, milestone) now read the queue
back to match, so observations land on the live turn.

#305's queued_second_turn_survives_single_observation_finish locked
"one finish consumes exactly one turn" with FIFO attribution as a
side effect; the attribution assertions are adapted to the tail-pop
semantics (terminal lands on the newest turn, stale first gets no
terminal, emptied-queue finish is a no-op) while keeping the
double-finish protection intent.

Signed-off-by: asto <asto18089@126.com>
h3c-hexin pushed a commit that referenced this pull request Aug 26, 2026
* fix: memory leaks and object reuse repo-wide

Whole-repo memory audit (frontend, src-tauri, platform layer) with
safe, behavior-preserving fixes; CodeWhale submodule findings are
reported upstream-first and intentionally untouched here.

Unbounded growth (P0):
- Session working-set buffers only pruned scheduled sessions; normal
  sessions stayed resident for app lifetime. Add an all-session LRU
  (cap 96, protects active/busy/queued/remote-turn buffers) on both
  tauri and web bridges; evicted buffers rehydrate from disk.
- render_artifact_visual cache held multi-MB payloads keyed by
  path|mtime forever. Bound it with an LRU (16 entries / 96 MiB,
  same-path stale keys yield on insert).
- KnowledgeView output-preview ref cache grew per browsed artifact;
  cap at 48 entries.
- Startup timing entries in index.html appended forever; keep last 64
  and keep the flush cursor consistent.

Structural leaks (P1):
- TurnLifecycle transcript rules accumulated one full raw prompt +
  display clone per turn for the engine lifetime. Resynced engines are
  seeded with already-sanitized history, so prune rules after a
  successful SyncSession (active turn guarded); regression test added.
- timing turn queue: unsubmitted-cancel paths left stale ids that the
  next finish_turn misattributed; pop from the tail and clear the
  session queue, plus clear_session on session delete.
- codex_acp event bridge kept terminal tool_call entries with full
  raw payloads in the tools map; remove on terminal notification.
- Web domain-adapter built fresh slices per notify, re-rendering every
  domain subscriber on any change; cache (full, slice) per subscriber
  and keep the flat/domain subtree-sharing contract (test updated).
- Unix zombies: open/xdg-open/notify-send spawned without wait; add a
  shared detached reaper helper with tests.
- Reuse heavy clients: shared SystemCredentialStore handle (bridge),
  shared reqwest clients for monitor probes and IMA connector with
  per-request timeouts.

Hygiene (P2/P3): notification dedup set bounded per session, credential
delete removes cache and key-lock entries, modeStateEpochs purged with
session buffers, SearchView group refs deleted on unmount, cached
Intl date formatters, pet window skips identical 600ms ticks and
memoizes markdown cards.

Verification: cargo test --lib 1396 passed / 0 failed (2 stale contract
tests updated to the new behavior); node --test suite has zero new
failures versus the pre-existing sandbox baseline; architecture-guard
passes; no submodule changes.

Signed-off-by: asto <asto18089@126.com>

* fix: cache capacity and eviction gaps

Second-pass cache audit (capacity + necessity lenses, 12 read-only
subagent sweeps over frontend bridges, src-tauri, and CodeWhale).
This commit lands the findings that affect code changed in this PR
plus small same-area gaps; larger redesigns and CodeWhale findings
are reported for follow-up instead.

- toolMeta (tauri bridge): historical tool_use metadata written during
  rerenderFromMessages was never cleared and rode into session buffers
  (write/patch args can be hundreds of KB). Clear it at replay start;
  the live-event path stays insert/delete balanced.
- outPreviewCache (KnowledgeView): count-only cap missed the byte axis
  (48 multi-MB office HTML entries could reach hundreds of MB). Add a
  32 MiB cumulative budget with insertion-order eviction; count cap 48
  kept as secondary axis.
- MAX_SESSION_BUFFERS 96 -> 32 on both bridges: heavy sessions run
  1-4 MB each and real switching hot sets are single-digit, so 96 paid
  worst-case hundreds of MB for near-zero hit rate; eviction falls back
  to a one-time disk rehydrate.
- Prune-path gaps: the scheduled-buffer LRU skipped the
  onSessionBufferPurged hook (leaking modeStateEpochs keys) and both
  LRU loops missed personaPlaceholderTitles; scene-events localStorage
  keys are now removed on session purge/eviction (tauri via the purge
  hook, web inline in all three paths).
- visual_cache key now uses millisecond mtime (same-second rewrites
  previously returned a stale preview) and gains unit tests for
  same-path eviction, entry cap, and byte budget.
- pending_user_input cleared on session delete alongside the existing
  timing/turn-capture cleanup.

Verification: cargo test --lib 1399 passed / 0 failed; frontend
node --test zero new failures vs the pre-existing baseline;
architecture-guard passes.

Signed-off-by: asto <asto18089@126.com>

* fix: review follow-ups for eviction and cleanup paths

Review follow-ups for the memory-leak audit fixes:

- Tauri bridge fast-path switch: an event listener recreates an
  unloaded empty buffer via getBuffer for any session a late event
  names (chat:usage/artifact:disk after eviction). Switching to such
  a buffer showed an empty conversation because the cached fast path
  only checked buffer existence. Gate the fast path on
  loadedFromDisk/busy/remoteTurnActive/content (web bridge already
  gates on loadedFromDisk) so poisoned buffers fall through to the
  durable reload.
- rerenderFromMessages toolMeta wipe: the live-session hydration path
  (hydrateLiveSession) restores toolMeta from the live buffer and then
  rerendered, wiping entries for in-flight tools whose tool_use is not
  yet in messages; the subsequent chat:tool_end then missed its meta
  (stuck request_user_input card, degraded present_artifact card).
  Add opts.keepLiveToolMeta and pass it on the live-hydration call
  site (tauri + web). The web bridge also gains the durable-replay
  toolMeta reset that only tauri had, closing the same leak there.
- posix spawn_detached_and_reap: the doc claimed a synchronous-wait
  fallback on reaper-thread failure, but the Child had been moved into
  the dropped closure, so no wait happened and callers surfaced a
  spurious error for a successfully spawned command. Route the Child
  through a shared Option slot so the failure path can wait on the
  caller thread, and return Ok (the command did launch).
- Shared reqwest clients: Client::builder().build().unwrap_or_default()
  re-runs an identical build on failure and panics (Client::default()
  expects). ima.rs keeps per-call error propagation; model_probe
  returns None so the probe falls back to configured values, matching
  the pre-shared-client degradation.
- Web session-creation rollback (remote_control): clear timing queue
  and pending_user_input alongside store.delete, same as delete_session.
- Comment accuracy: onSessionBufferPurged has no return-value
  protocol; eviction also drops buffer-resident drafts (not "pure
  presentation cache"); credential KEY_LOCKS doc notes the delete-time
  registration exception; stale test renamed to the asserted behavior.

Verification: cargo test --lib 1396 passed / 3 failed (the three
codex_acp attachments tests are the known /tmp-worktree environment
failures, unchanged by this commit); frontend npm test 232 passed /
0 failed / 2 skipped; architecture-guard and fork-guard --fast pass.

Signed-off-by: asto <asto18089@126.com>

* fix: harden eviction gates and rule pruning

Independent re-review fixes (round 3) on top of the audit fixes:

- Fast-path gate: drop the chatItems clause. A non-turn event
  (chat:compaction) recreating an empty buffer via getBuffer only
  adds a system chatItem without setting busy or messages; admitting
  it on the fast path showed a history-less view with no self-heal.
  Gate on loadedFromDisk/busy/remoteTurnActive/messages only.
- switchActiveTo: set the new activeSessionId before touching the
  old buffer. The touch-triggered LRU prune protects the target via
  activeSessionId; with the old ordering an idle target could be
  evicted mid-switch and silently replaced by freshBuffer() (both
  bridges).
- Transcript rule pruning: replace the state.active bail (a no-op on
  every interactive path — reserve() always precedes spawn) with a
  retain that keeps only the in-flight reservation's rule, and prune
  stale rules on engine reclaim so rules no longer outlive the engine
  in turn_lifecycles until session delete. Test rewritten to lock the
  production shape (stale rules cleared while a reservation is held).
- credential_store: deregister KEY_LOCKS entries only when no other
  thread still holds the lock handle (Arc::strong_count == 2), so a
  concurrent get/set can no longer build a second lock and lose
  mutual exclusion against an in-flight op.
- visual cache: parse keys with rsplit_once('|') — filenames may
  contain '|' and left-splitting evicted unrelated entries.
- PetWindow: include body in sameActivities — copy-derived bodies
  change on language switch without any event field changing.
- domain-adapter: comment accurately scopes the stablePick cache
  (whole-snapshot granularity, weaker than the desktop per-domain
  revision cache).
- cargo fmt on model_probe.rs/ima.rs (rust-lint gate).

cargo test --lib green (one pre-existing runtime_bundle flaky passes
in isolation); npm test 5 failures identical to the pre-PR sandbox
baseline; architecture-guard passes.

Signed-off-by: asto <asto18089@126.com>

* fix(web): stabilize subscribeMany combined slice identity

The stablePick cache stabilized each per-domain slice but the combined
outer object passed to subscribeMany callbacks was rebuilt (and frozen)
on every notification. The only domain consumer is a React setState
(useBridge.js), which bails out solely on Object.is identity, so every
no-change notify still caused a full re-render and the re-render churn
this change set out to eliminate was unaffected.

Memoize the combined object on the transport snapshot reference (same
single-slot pattern as stablePick) and lock the behavior with a
discriminating contract test (identity reuse on a no-change notify, new
identity on a real change).

Signed-off-by: asto <asto@users.noreply.github.com>

* fix(chat): keep drafts and scene keys on eviction

Fifth-round review findings on the session-buffer LRU:

P1 - Evicting an idle working set discarded its composerDraft. The
disk transcript only holds committed content (neither bridge persists
drafts), so after 33 sessions the unsent draft silently became
unrecoverable. Eviction now stashes non-empty drafts into a bounded
side table (256 entries, strings only) and every buffer-rebuild path
(getBuffer, switchActiveTo fallback, slow-path rehydrate, hydrateLive
entry) restores it; real session deletion (purgeSessionBuffer)
invalidates the stash so it never flows back into a recycled id.

P2 - LRU eviction removed the scene-events localStorage key, which is
the only offline recovery copy when the sidecar save fails (that
failure is intentionally swallowed and sync relies on the cache to
replay). Eviction no longer touches the key; only real session
deletion cleans it (tauri via the new reason="delete" purge hook,
web via purgeSessionBuffer only).

Adds tests/session_buffer_eviction.test.mjs: 33-session eviction with
draft restore and bounded buffer count (tauri+web), protection
predicates, stash bounds, delete-vs-evict semantics, and the
save-fail + evict + rehydrate scene recovery path (web, driven through
public switchToSession/setComposerDraft/deleteSession). All 7 tests
fail on the pre-fix head.

Signed-off-by: asto <asto18089@126.com>

* fix: bound draft stash and reap browser spawn

Sixth-pass review fixes:
- Cap stashed eviction drafts at 64K chars (both bridges): the side
  table promised short strings only, but external transport callers
  bypass the composer input limit, so 256 unbounded strings could
  re-introduce the retained-memory leak the PR removes.
- Reap the agent-login browser spawn via the shared posix reaper,
  exposed through platform::os::spawn_detached_and_reap (unix reaps,
  windows has no zombie contract). open/xdg-open/notify-send were
  fixed earlier; this browser launch was the missed same-class site.
- Lock the timing queue semantics with discriminating tests (tail-pop
  attribution + clear_session silencing late finish).
- Extend the eviction suite: scheduled-run reopen after eviction and
  late chat:usage getBuffer rebuild both restore the stashed draft
  (mutation-verified discrimination).

Verified: cargo test --lib 1377 passed (1 pre-existing runtime_bundle
flaky, green in isolation); node --test 243/250 (5 failures identical
to pre-PR baseline); cargo fmt, architecture-guard, commit gate pass.

Signed-off-by: asto <asto18089@126.com>

* fix: rebase onto #305 and adapt timing tests

Rebase conflict in timing.rs: #305 introduced finish_turn_internal
with ActiveTurnTiming entries and benchmark-hook observation params,
but popped the queue front (FIFO). The tail-pop semantics (terminal
attributed to the newest queued turn, stale unsubmitted-cancel
residue cleared without a terminal, queue cleared whole) is ported
into finish_turn_internal; the five observation accessors
(record_first_event, tool counters, milestone) now read the queue
back to match, so observations land on the live turn.

#305's queued_second_turn_survives_single_observation_finish locked
"one finish consumes exactly one turn" with FIFO attribution as a
side effect; the attribution assertions are adapted to the tail-pop
semantics (terminal lands on the newest turn, stale first gets no
terminal, emptied-queue finish is a no-op) while keeping the
double-finish protection intent.

Signed-off-by: asto <asto18089@126.com>

* fix: harden ACP turn timing and retention purge gaps

Round-7 review fixes:

- ACP start_turn now runs only after send_message's busy admission
  succeeds (codex.rs). ACP has no native-lane reserve/terminal_closing
  gate, so a concurrent second submit failing admission used to leave a
  ghost queue entry behind; its send_error finish then cleared the
  whole queue and swallowed the in-flight turn's assistant_done.
- Startup marks cursor no longer counts spliced-out entries: the flush
  success callback relocates `sent` by the in-flight batch tail's
  identity, so late real entries are never short-circuited as already
  reported (index.html). Comment now states the web-end (sent stays 0)
  truncation semantics honestly.
- visual_cache key parsing regression test for filenames containing
  '|' (artifacts.rs): left-split would make same-prefix files evict
  each other's entries.
- Retention-driven session deletions now clear process-level turn
  state maps via a SessionPurgedHook registered at the composition
  root (dependency inversion: sessions must not depend on assistant).
  Covers chat retention, scheduled cleanup, delete_scheduled_run and
  purge_all_scheduled_side_maps; also prevents late finish_turn from
  rebuilding orphan timing sidecars outside deleted session dirs.

Verification: cargo test --lib 1405 passed / 3 failed (pre-existing
/tmp-worktree codex_acp attachments env failures, identical on the
pre-fix head); new tests retention_purge_notifies_session_purged_hooks
and pipe_in_filename_parse... pass; timing suite 16/16; architecture
guard passes; affected node suites green.

Signed-off-by: asto <asto18089@126.com>

* fix(chat): stop eviction-time draft and title loss

Review follow-ups on the session-buffer LRU:

- tauri switchActiveTo fresh branch now marks the materialized empty
  buffer loadedFromDisk (web bridge parity). Without it the new
  cachedBufferUsable gate rejected fresh persona/ensureSession buffers,
  the slow path replaced them via freshBuffer() without stashing, and
  unsent composer drafts were silently discarded.
- personaPlaceholderTitles is session metadata, not buffer residue, and
  no rehydrate path restores it; capacity eviction must keep it. Only
  real session deletion (purgeSessionBuffer) clears it now, on both
  bridges.
- New regression tests: fresh-buffer draft survival (mutation-verified),
  placeholder-title eviction/delete semantics, and the 64K draft-stash
  char bound on both bridges.
- Correct stale comments: 32-buffer cap arithmetic, draft char-limit
  wording, and the toolMeta replay-residue description (replay re-adds
  historical entries; the LRU bounds their retention).

Signed-off-by: asto <asto18089@126.com>

* fix(artifacts): skip over-budget insert, reap soffice

- visual_cache_insert: a single result larger than the 96 MiB byte
  budget previously wiped the whole cache including itself via the
  eviction loop. Over-budget results are now skipped entirely (the
  caller response was already cloned before insert). Covered by a new
  mutation-verified unit test.
- open_with_libreoffice still spawned soffice without reaping (Unix
  zombie per invocation); route it through the shared
  platform::os::spawn_detached_and_reap helper like the other
  fire-and-forget launches.

Signed-off-by: asto <asto18089@126.com>

* fix(assistant): clear timing state on eval teardown

delete_chat_session_with_gate (used by eval teardown and
ProductChatRuntime::close) left unpaired timing queue keys behind;
a GAIA pass creates/deletes ~165 sessions, so ACTIVE_TURNS grew
unboundedly. Clear the timing state on delete (idempotent with the
SessionPurgedHook cleanup). Adds a mutation-verified regression test
and renames the stale tail-pop queue test to match its semantics.

Signed-off-by: asto <asto18089@126.com>

* refactor(sessions): fire purge hooks from store delete

SessionStore::delete now fires notify_session_purged after all store
locks are dropped, so every deletion path (delete_session command,
remote-control rollback, retention purge) gets the same process-level
cleanup instead of hand-duplicated clears at two call sites. The hook
registered in lib.rs additionally clears memory turn captures and the
monitor self-metrics keys (warmed_sessions grew per session and were
never reclaimed). Hooks remain optional; unregistered deletion proceeds
unchanged.

Signed-off-by: asto <asto18089@126.com>

* docs: align cache and reaper comments with behavior

- posix: the reaper rationale now covers the long-lived browser case
  (agent-login first instance parks the reaper thread until exit).
- credential_store: document that delete no longer leaves a cached
  None, so the first post-delete get re-touches the keyring backend
  once per process (trade against the #175 anti-popup cache).
- ima/model_probe: document that the OnceLock-shared reqwest client
  snapshots the system proxy at first build (mid-session proxy changes
  need a restart) and caches build failure process-wide.

Signed-off-by: asto <asto18089@126.com>

* refactor(notifications): call reaper via os interface

send_notify_send reached into platform::os::posix directly; use the
unconditional platform::os::spawn_detached_and_reap wrapper introduced
in this PR so callers stay off platform internals (same idiom as the
codex_acp agent-login call site).

Signed-off-by: asto <asto18089@126.com>

* fix(sessions): refuse eviction instead of dropping unsent drafts

The draft stash's bounds (256 entries, per-draft char cap) previously
silently discarded user input: an over-cap draft or the 257th drafted
session was dropped at eviction, and legitimate UI drafts between 64K
and the composer's 100K input cap were also lost (the old cap was
below the UI limit). Both bounds now refuse the eviction instead —
the buffer stays resident and the draft survives; the char cap rises
to 1M (10x the composer input cap). Tests assert recovery for
oversized and capacity-boundary drafts and are mutation-verified
(reverting the refusal turns them red).

Signed-off-by: asto18089 <asto18089@users.noreply.github.com>

* chore: translate PR-added comments to English

Per AGENTS.md, new code comments and diagnostics must be English.
Translates all 460+ PR-added Chinese comment blocks and assertion
messages across the Rust platform/feature layers, both bridges, the
frontend components, and the eviction regression suite (pre-existing
localized strings merely moved by refactors are exempt and untouched).
No behavior change; the eviction suite header also documents the new
refuse-eviction stash semantics.

Signed-off-by: asto18089 <asto18089@users.noreply.github.com>

* fix(codex): register ACP timing turn inside admission

Move the timing turn registration from the caller (after send_message
returns) into the admitted section of AcpPool::send_message, immediately
before the prompt task is spawned, via the new begin_prompt_turn gate
helper.

The spawned prompt task can complete (fast mock response, instant
connection error) and call timing::finish_turn before send_message
returns, so a caller-side registration after the await raced that
finish: the completion found an empty queue and was dropped, while the
late start_turn left a stale unpaired queue entry attributed to the
next turn. Registering after busy admission succeeds also keeps the
round-7 invariant: a rejected concurrent submit never enqueues a ghost
turn whose send_error finish would clear the in-flight turn's queue.

Adds two regression tests (operation_gate): immediate completion is
recorded on the registered turn with the queue left empty, and a busy-
rejected submit neither enqueues a ghost entry nor swallows the
in-flight terminal. Both are mutation-verified (registration removed /
moved before admission each turn the suite red).

Signed-off-by: asto <asto18089@126.com>

* chore: collapse doubled comment markers from translation

Normalize the malformed comment formatting left by the English
translation pass: comment continuation lines carried duplicated
markers and indentation artifacts ("/// ///", "//     //",
"///     ///"), which produced malformed rustdoc and hurt readability.
The text itself is unchanged; only the repeated marker prefixes are
collapsed to a single marker. 228 affected lines across 19 files,
verified to leave no residue and to touch no code lines.

Signed-off-by: asto <asto18089@126.com>

* fix(codex): make ACP admission atomic across touch failure

Signed-off-by: asto <asto18089@126.com>

* test(codex): update activity-touch contract for admission gate

Signed-off-by: asto <asto18089@126.com>

* chore: translate round-9 contract comment to English

Signed-off-by: asto <asto18089@126.com>

---------

Signed-off-by: asto <asto18089@126.com>
Signed-off-by: asto <asto@users.noreply.github.com>
Signed-off-by: asto18089 <asto18089@users.noreply.github.com>
Co-authored-by: asto <asto@users.noreply.github.com>
Co-authored-by: asto <asto@local>
@zhuowp zhuowp mentioned this pull request Aug 26, 2026
7 tasks
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.

4 participants