Skip to content

feat(engine): host-armed guardrail prompts + authority-transition notice - #131

Merged
asto18089 merged 30 commits into
pinvou3-cleanfrom
feat/host-armed-guardrail-prompts
Nov 4, 2026
Merged

asto18089 merged 30 commits into
pinvou3-cleanfrom
feat/host-armed-guardrail-prompts

Conversation

@asto18089

@asto18089 asto18089 commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

No-Issue: 底座登记批次(tri-mode 安全模型的引擎侧行为契约),不存在单一对应 issue;逐轮评审史、指纹与登记以本描述及父仓 fork-guard 记录为账。

摘要

tri-mode 安全模型(Plan / Auto / YOLO + 会话级 ask-on-guardrail 开关)的两个登记底座行为,评审史二十一个提交(f1eb10efc 之上;逐轮明细见下):998ffe27e(主体,骑 f1eb10efc)+ 复审加固提交(补 6 条测试钉,41eab46f2)+ 评审修复提交(收敛 fresh-review 发现,04c1f7843)+ 复审修复二(收敛 re-review 发现:武装发布粘滞、preview 计划姿态、补 4 条突变验证钉,5c13fc561)+ clippy 修复(去 Copy clone,10b28ef5f)+ 复审修复三(收敛第三轮 fresh-review 发现:runtime pump 尊重 force_prompt、status 后缀只报真实翻转、补 2 条突变验证钉,f972884e5)+ 复审修复四(收敛第四轮 fresh-review 发现:协议 crate 遗留 ChangeMode 载荷 serde 钉、session 同步与隔离运行时的诚实姿态回执、pump 审批卡携带 force_prompt 旗标、武装面「可应答」谓词合并与 escalation 臂去重,4 提交 7e03162d1/5910426c5/9b1e1cf13/b94cc3175)+ 复审修复五(收敛第五轮 fresh-review 发现:窄化 provenance 轮次不再误报「(host)」转换行——抑制 emits 且不污染 standing baseline;Auto-Review 窄化降级补结构化回执;2 条新钉,提交 1785dae15;远端头 1785dae15,dcd4c200f 之上第 97 个提交。登记以远端 SHA 为准)。

(a) ask_on_guardrail:宿主武装的交互式守卫提示

会话级 ask_on_guardrail 旗标,经 Op::ChangeMode(engine + wire,serde 缺省缺席 = 行为不变)与 LiveRuntimeAuthority 同 doctrine 携带(Option<bool>,None = 不改变现状;turn 安装重录会话现值,永不回滚已发布武装)。宿主武装后(已保证有真人可以应答),原本在自主姿态下 fail-closed 的守卫面改为升起审批请求:

  • 非旁路 holds(rlm/rlm_eval/start_mcp_server)在 Full Access 下改为带 approval_force_prompt 的审批(必须显式置位——普通 resolver 在 auto-approve 下会自动放行非旁路工具,feat: LLM can start MCP servers from chat context codewhale-hq/Codewhale#3866 反转语义,落空即静默执行);
  • repo-law ask holds 弹审批而非硬拒;
  • typed ask-rule Prompt 被尊重(不再被 auto_approve 静默吞掉);
  • sandbox escalation 走 Suggest 路径审批(卡片携带模型 justification);
  • execpolicy 拒绝面(denied prefixes + typed denies,完整匹配语义原样保留)升级为携带匹配证据的审批;Never 与 Auto-Review 姿态在全部武装面(deny face / repo-law / escalation)无条件保持拒绝——武装豁免显式排除这两个姿态,无人能应答的卡不会升起;
  • UI disposition:武装后 Full Access 的 forced hold 解析为弹卡(CW-7495 consent-card 形态的泛化);Auto-Review 与 Never 的拒绝无条件保留;
  • 子代理门镜像主线:武装后 delegated typed-ask 在继承 auto-approve 下也拒绝(子代理绝不静默执行武装主线要问的操作);held-call hold 路由臂保留为 fail-safe 前瞻——Bypass 下 plan decision 把 built-in gate hold 收敛为 Block,今天实际可达的武装子代理面是 typed-ask 拒绝(已如实标注)。

未武装(默认)行为逐字节等同旗标前 doctrine;既有 fail-closed pins 就地注释武装例外。Composer(!)来源保持不武装(codewhale-hq#5191:输入即批准)。

(b) turn_meta 权威转换通告

引擎记录每个 <turn_meta> 块上报过的姿态;宿主在轮间改变 mode / 姿态 / 武装时,下一块渲染且仅渲染一行:

Authority transition (host): agent (Full Access, guardrail prompts armed) -> agent (Ask, guardrail prompts disarmed); reason: host_authority_change

轮中任意次 ChangeMode 翻转折叠为下一块的一次转换;该行由轮块渲染且仅渲染一次(消费后即清,轮中 steer/预算提示不再重复渲染同一行);未变轮零新增行(codewhale-hq#3947 byte-stable doctrine);比较在轮块构建点计算(NotStarted 早退不再吞掉转换);/preview-request 以同一引擎算法重算(preview 与 live wire 哈希一致有专测)。

对齐

宿主武装提示 = Claude Code ask-rules-survive-bypassPermissions + CW-7495 consent card 的泛化;模式变更模型通告 = Codex per-policy prompt snippets / Claude system-reminder 的同域实践。设计文档(docs/safety-modes.md)与指纹/登记在父仓 PR codewhale-hq#809 落地。

测试

  • 998ffe27e 新增 7 条:forkguard_armed_full_access_forced_hold_resolves_to_prompt(含会话授予不可静默消费武装 hold、Auto-Review/Never 恒拒)、forkguard_armed_deny_face_becomes_guardrail_prompt_unarmed_stays_block、forkguard_armed_full_access_hold_prompts_instead_of_blocking(端到端:武装 hold 弹审批 → 拒绝 → 以 approval denial 收轮)、forkguard_armed_child_refuses_delegated_typed_ask_even_under_auto_approve、forkguard_authority_transition_between_renders_the_host_change、forkguard_turn_meta_authority_transition_renders_only_on_change、forkguard_live_turn_meta_builder_reads_the_pending_transition。
  • 复审加固提交补 6 条自审缺口钉:forkguard_armed_repo_law_hold_prompts_instead_of_blocking(repo-law 武装面;授予缓存不可静默消费)、forkguard_armed_sandbox_escalation_prompts_instead_of_failing_closed、forkguard_armed_full_access_typed_ask_rule_prompts_instead_of_auto_running(主线 auto_approve 下武装 typed ask;composer ! 面保持不武装)、forkguard_preview_manifest_hashes_the_same_authority_transition_line_the_wire_sends(preview/wire 哈希一致 + 无转换时逐字节稳定)、forkguard_turn_authority_install_never_rolls_back_published_arming、forkguard_apply_change_mode_status_suffixes_report_the_arming_change;并合并了 composer 路径重复注释。
  • 复审修复二补 4 条(全部突变验证转红后逐字节还原):forkguard_pending_arming_survives_arming_free_authority_publishes(武装发布粘滞 E2E:turn install / 无武装 ChangeMode 均不可吞掉 pending arm,显式 disarm 仍生效)、forkguard_armed_composer_shell_keeps_the_hard_deny_face(armed Full Access 下 composer deny-rule 命中仍硬拒,v0.9.4: user-typed ! shell commands should not hit the approval modal codewhale-hq/Codewhale#5191 不武装)、forkguard_armed_auto_review_escalation_stays_fail_closed(armed Auto-Review 升级恒拒)、forkguard_preview_transition_uses_the_planned_posture_not_the_stale_session(preview 与 live 线体哈希在假想姿态输入下逐字节一致)。
  • 评审修复提交补 1 条 + 扩展 1 条:forkguard_turn_start_detector_records_the_posture_and_renders_the_transition_once(端到端咬住 detector 的比较→记录→渲染→一次性消费,两轮 wire 断言逐字节;记录行删除与消费清空删除两条突变均转红),deny-face 钉扩展 Auto-Review 恒拒面。
  • cargo test -p codewhale-tui --lib --locked forkguard_ -- --test-threads=1:264 过 / 0 挂(既有 fail-closed pins 原样绿;--features benchmark-eval-controls forkguard_benchmark_ 6/0;preview 61/0、turn_metadata 24/0、change_mode 4/0、escalation 11/0、runtime_threads 模块 184/0 全绿;cargo fmt --check 干净;协议 crate 71/0 含新增遗留载荷钉)。
  • 复审修复三(f972884e5)补 2 条新钉 + 扩展 1 条(全部突变验证转红后逐字节还原):forkguard_pump_never_auto_approves_a_force_prompt_hold(runtime_threads 审批 pump 绑定 approval_force_prompt,Full Access 下 forced hold 必须 pending 等真人决定、不自动批准,Auto-Review fail-closed 臂保留;附带修复前存 non-bypassable 面在 pump 下被静默自动批准的缺口)、forkguard_a_not_started_turn_neither_consumes_nor_records_the_transition(NotStarted 早退不吞 transition:失败 turn 不记录姿态,下个成功 turn 渲染该行);后缀钉扩展「carrier 回显未变值不得宣称翻转」的生产形态断言。行为披露:armed hold 在 pump 部署下从「静默自动批准」改为「浮给客户端真人决定」(CW-7495 契约本义)。
  • 突变抽查(11 探针,全部按预期转红后逐字节还原;前 6 条同前,评审修复后新增 2 条:detector 记录行删除、消费清空删除——修复前记录行删除全绿(fresh-review 证实的测试网缺口),修复后由新钉咬住):repo-law 武装门删除、forced hold 显式 force_prompt 撤除(feat: LLM can start MCP servers from chat context codewhale-hq/Codewhale#3866 静默放行反转被端到端 deny 断言咬住)、never-rollback 补丁移除、authority_transition_between 恒 None(transition 专测 + preview 哈希测咬;turn_meta 渲染测按设计只测渲染不咬检测器,如实记录)、deny-face 武装条件取反(+ 子代理 parity 连带红)、子代理门回退 !auto_approve;复审修复二新增 4 条:publish 粘滞移除(新钉转红)、composer 面传入会话旗标(新钉转红——armed composer 会经 Prompt 臂放行 deny 命令)、escalation 位点 Auto/Never 豁免删除(新钉转红)、preview 姿态回退 session 源(新钉转红);复审修复三新增 3 条:pump 删 !approval_force_prompt 守卫(新钉转红——hold 被自动批准)、后缀回退 carrier 门控(扩展断言转红)、NotStarted 路径注入姿态记录(新钉转红)。复审修复四新增 5 条:协议 crate 删 skip_serializing_if(新钉转红——此前全绿,即负突变证实的缺口;如实更正:单独删 #[serde(default)] 对 Option 字段是 serde 无操作、钉不转红,round-5 评审证实并更正此处措辞——保留该属性是为显式声明缺省语义)、pump 载荷删 force_prompt 字段(扩展断言转红;单删任一载荷位点也被同一 pump 钉的两臂分别咬住,round-5 复核)、sync 重置移除(新钉转红)、isolated 门改无条件(新钉转红)、谓词丢 Never 豁免(deny-face 钉转红)。复审修复五新增 3 条:窄化轮抑制门删除(两条新钉转红)、narrowed 轮更新 baseline(新钉转红——下一权威轮渲染无人做过的反向 host 行)、窄化谓词丢 Auto 臂(Auto-Review 新钉转红——降级回执消失、误标 host 行复现)。

复审修复四(round-4 fix wave,b94cc3175)

第四轮全新评审(9 道 lane,COMMENT 5733839125 @ f972884e5:claims 全 TRUE、走私 CLEAN、门禁 9/9 精确复现、5 条行为突变全红、注册数字全部精确)确认 0 blocker/major;其 4 项实质发现已由本波 4 提交收敛(全部突变验证转红后逐字节还原):

  • 7e03162d1 test(protocol):change_mode_without_ask_on_guardrail_keeps_the_current_arming —— 负突变证实测试网缺口后补钉:删除 ask_on_guardrail 的 #[serde(default)]/skip_serializing_if 会使该钉转红(此前全套协议测试保持绿);「serde 缺省缺席 = 行为不变」从属性正确升级为测试钉咬住。
  • 5910426c5 fix(engine):Op::SyncSession 重置 last_turn_meta_authority(会话切换不再继承上一会话的已上报姿态——新会话首轮不再渲染「from」侧从未上报的转换行;武装本身 host-owned、刻意跨 sync 保留);runtime_chat_isolated 轮不再记录/消费转换(此前块-less 轮可吞掉 pending transition)。钉:forkguard_sync_session_clears_the_recorded_turn_meta_posture、forkguard_an_isolated_turn_neither_consumes_nor_records_the_transition(两条均突变转红)。
  • 9b1e1cf13 fix(runtime):pump 两处 approval.required JSON 载荷补 approval_force_prompt 字段(执法本在底库;此前 pump 部署的外部客户端无法把武装同意卡与普通审批区分开)。pump 钉扩展为同时断言 forced(true)与 ordinary(false)两条路径的载荷,突变转红。披露:这是对外 JSON 面的加字段(additive)变更,旧客户端忽略新键不受影响。
  • b94cc3175 refactor(engine):guardrail_prompts_answerable 合并三处「武装且有人可答」谓词拼写(deny face / repo-law / escalation,依 permission_posture_allows_questions 先例);escalation 两臂合并为一(仅差 armed 标记,deny 臂为显式补集);删除 force_prompt 计算中被蕴含的 || armed_full_access_hold 死析取(蕴含关系就地注释);host 转换行 doc 注明与窄化回执行族的区分。无行为变化:deny/repo-law/escalation 钉保持绿,谓词突变转红 deny-face 钉。

未收敛项(记录在评审线):pump 部署武装 hold 无界悬挂(已披露、fail-safe 方向,follow-up:超时或掉武装信号);tripwire 注释无测试伴随(已披露,可接受);6 提交横跨 3 个 author 身份(squash 合入后消失,不改写历史)。原列的「窄化 provenance 转换误标」一项:round-5 评审证实其在 runtime 线程 AgentMail 输入下实际可达,已由复审修复五修复(见上)。

复审修复五(round-5 fix wave,1785dae15)

第五轮全新评审(11 道 lane,从零开始)确认 round-4 后仍 0 blocker/major、走私 CLEAN、门禁全数精确复现,但推翻了一项 round-4 的不可达判断并暴露两处披露缺口,本波 1 提交收敛:

  • 窄化 provenance 轮次的「(host)」转换误报(round-4 判定「今日 in-tree 不可达」被证实为错):runtime 线程引擎经 AgentMail(及 ImportedTranscript / MemoryRecall / AssistantGenerated)输入的轮次会把站立姿态逐轮窄化(Full Access→Suggest),而转换比较以「上一块上报姿态」为 baseline——窄化轮上报的恰是逐轮降低的姿态,于是每次窄化轮误渲染 reason: host_authority_change(宿主什么都没做),下一个权威轮又渲染反向行;Auto-Review 站姿下更连窄化回执都没有(had_auto_authority 未把 Auto 计入)。修复:窄化轮不再 emits host 转换行(窄化回执行族本就如实上报该轮降低),也不更新站姿 baseline(last_authoritative_turn_meta_posture,否则下一权威轮把窄化解除渲染成反向 host 行);真正的宿主翻转由下一个权威轮从窄化前站姿出发渲染。Auto→Suggest 降级与 Bypass 降级同样携带结构化窄化事件。钉:forkguard_a_provenance_narrowed_turn_neither_emits_the_host_line_nor_pollutes_the_baseline、forkguard_an_auto_review_turn_narrowed_by_provenance_still_records_its_narrowing(三条突变均转红后逐字节还原)。forkguard_ 264/0;行为名 277→279。 preview 与 live 同步镜像同一 baseline/抑制语义,preview 哈希一致性钉保持绿。

登记(随父仓 PR codewhale-hq#809)

fork-guard 指纹(25 条:24 条测试钉 + 1 条 Authority transition (host): … 模型可见文案钉)、EXPECTED_HEAD→1785dae15 / EXPECTED_COMMITS→97、docs/fork-modifications.md T2 候选期条目、docs/safety-modes.md 设计文档(§7 补窄化归因语义、§8 census 24),均在父仓 PR 落地;本 PR 为纯底座行为提交。

已知边界(如实)

  • 武装 disposition resolver 的 TUI 内武装面按设计不可达(内置 TUI 不武装;武装由外部宿主经 Event::ApprovalRequired { approval_force_prompt } 消费——"host-armed" 的本义)。approval_routing.rs 的 UI authority 恒未武装、exec_agent.rs 的 headless auto-approve 已加 tripwire 注释,防止未来武装面接入时静默拒绝/放行。
  • 武装不持久化:与 auto_approve 同口径,会话恢复后从 disarm 起步,宿主须重新武装(fail-safe)。
  • 「未武装逐字节等同」指守卫行为与 wire 载荷;未武装会话的姿态变化仍会产生 (b) 的转换行(这正是 (b) 的功能)。
  • armed repo-law / sandbox / 主线 typed-ask / preview 哈希 / never-rollback / 状态后缀 6 处复审前无钉,已在 41eab46f2 补齐;detector 端到端钉(记录 + 一次性消费)在 04c1f7843 补齐。
  • 运行中的子代理不感知轮间武装变化:子代理在 spawn 时快照 ask_on_guardrail,宿主随后的 arm/disarm 只影响主线与其后新 spawn 的子代理(存活子代理的武装面维持 spawn 时快照;failing-safe 方向视宿主动作而定,已由评审指出、如实披露)。
  • 会话恢复后历史中的旧「(host) … armed」行保留而引擎已 disarm,宿主重新武装时因 baseline 为 None 不渲染新行——模型可见的最新回执可能过期(执法面不受影响,恒由 deny face 兜底;与 edit-last-turn 截断同族的已披露残窗)。
  • pump 强制 hold 的 !approval_force_prompt 守卫对未武装部署也是行为变化(原 non-bypassable 面在 pump 下被静默自动批准的缺口修复——见复审修复三披露;此即「未武装逐字节等同」的显式例外,方向为 fail-safe)。
  • re-review(8 道,COMMENT 5731536075 @ 04c1f7843)确认 0 blocker/major、smuggling CLEAN、全部登记数字属实;其 6 条 minor 已收敛:发布粘滞竞态与 preview 计划姿态(行为修复 + 新钉)、子代理 doc 收窄到子代理实际设防面(repo-law 为主线 face)、/provider 重启掉武装补 tripwire、armed composer / armed Auto-Review 升级 / 发布粘滞 3 处补钉、比较点注释补 residual 边界。已知边界补充:provider 切换重启动会话掉武装且无独立事件(app 池在 spawn 时重放武装旗标;外部宿主须重发);记录先于发送的残余窗口(append-only history 兜底,edit-last-turn 截断会丢)。第三轮 fresh review(9 道,COMMENT 5732557068 @ 5c13fc561):(a) 10 TRUE/2 PARTIAL、(b) 8 TRUE/1 PARTIAL、走私 CLEAN、变异网 SOUND、7 门禁精确复现;其 3 项实质发现已由 f972884e5 收敛(见上),其余 minor 记录在评审线。披露:LiveRuntimeAuthority::from_fields 去掉归一化链为 (a) None-keep 学说的伴随重构,全部现役 producer 发送规范形,无可达行为差异(998ffe27e 引入)。pre-existing(基线可复现,不随本 PR 修复):tools::subagent::tests::child_permission_gate::auto_review_guardian_denial_refuses_the_call_with_a_receipt 在 --test-threads=1 下顺序依赖失败,建议独立 follow-up。

复审修复六(round-13 fix wave,8c4e8e7a)

第六至十二轮评审推进期间(round-6 fd066bf2+47ada186 至 round-12 3cf2cdff9,登记见父仓 PR codewhale-hq#809),父仓 round-7 评审(10 车道,COMMENT 5746188328 @ 658843d3a)指认 SSE 兼容面缺口,本波 1 提交收敛:

  • 8c4e8e7a fix(remote):SSE 兼容流 approval.required 信封补带 approval_force_prompt 旗标——此前 runtime pump 双载荷位点与 wire EventMsg 均携带该旗标,唯独较老的 SSE 兼容面只发 {id, approval_id, tool_name, description},由该面驱动的客户端无法把武装同意卡与普通审批区分开(父仓 round-6 评审 M-1 指认、round-7 评审维持 OPEN 的一项)。事件循环早已解构 approval_force_prompt(event_loop.rs ApprovalRequired 分支),仅 record_remote_approval 接线缺口:签名补参、信封补字段、事件循环传参。测试:既有 connected-attachment 用例扩展断言 ordinary(false)与 forced(true)两条路径的线上载荷(start_mcp_server 强制卡夹具)。加字段为对外 JSON 面 additive 变更,旧客户端忽略新键不受影响。底座门禁复验:remote_control:: 77/0(--test-threads=1)、cargo fmt --check 干净。登记:普通命名回归,不入 T2 指纹普查;行为名 279 不变;父仓 gitlink 与登记随本头重钉(EXPECTED_HEAD→8c4e8e7aa/107 提交/drift 529 files +97522/−39410,见父仓 PR)。

No-Issue: issues are disabled on this repository; feature work tracked through the review rounds in this PR.

Pinvou tri-mode safety, two registered fork behaviors:

(a) ask_on_guardrail: a session-level flag carried on Op::ChangeMode
(engine + wire, serde-default-absent) and through LiveRuntimeAuthority
like every other posture field. When a host arms it, the guardrail
faces that fail closed under an autonomous posture raise an approval
request instead, because the host has guaranteed a person can answer:

- non-bypassable holds (rlm/rlm_eval/start_mcp_server) under Full
  Access prompt with approval_force_prompt instead of the pre-event
  block (the ordinary resolver auto-approves non-bypassable tools under
  auto-approve, so the hold is set explicitly);
- repo-law ask holds prompt instead of blocking;
- typed ask-rule Prompts are honored instead of silently ignored;
- sandbox escalation takes the Suggest-path approval;
- the execpolicy deny face (denied prefixes + typed denies, full
  matching semantics preserved) becomes an approval carrying the
  matching evidence; Never posture stays deny regardless;
- the UI disposition resolver resolves an armed forced hold under Full
  Access to the modal (CW-7495 consent-card shape, generalized);
  Auto-Review and Never denials stay unconditional;
- child gates mirror the main line: an armed held-call hold routes to
  the parent's approval surface, and a delegated typed-ask Prompt
  refuses even under inherited auto-approve.

Unarmed (the default) behavior is byte-identical to the pre-flag
doctrine; the existing fail-closed pins are annotated with the armed
exception. Composer ('!') provenance stays un-armed (codewhale-hq#5191).

(b) authority-transition notice: the posture each <turn_meta> block
reported is now recorded; when the host changes mode, posture, or
arming between turns, the next block renders exactly one explicit line
('Authority transition (host): ...; reason: host_authority_change').
Mid-turn flips collapse into one transition; unchanged turns emit
nothing so ordinary blocks stay byte-stable (codewhale-hq#3947 doctrine);
/preview-request reads the same value and stays byte-identical.

New forkguard tests: armed/unarmed deny-face and disposition truth
tables, an end-to-end armed Full Access hold that prompts and denies
as an approval, the armed-child delegated refusal, and the transition
contract (first turn/unchanged/change/arming-only/preview wiring).

Signed-off-by: asto <asto@users.noreply.github.com>
Follow-ups from self-review of the host-armed guardrail work: every
armed face claimed by the PR now has a dedicated forkguard pin.

- armed repo-law ask hold falls through to a forced approval (unarmed
  stays a hard block, grant-cache cannot consume it silently)
- armed sandbox escalation prompts with approval_force_prompt instead
  of failing closed; unarmed fail-closed face unchanged
- armed typed ask-rule Prompt is honored under auto_approve on the
  model-provenance mainline (composer face stays unarmed)
- the preview manifest hashes the exact authority-transition line the
  live wire body sends; no pending change keeps previews byte-stable
- installing a turn authority never rolls back published arming
- apply_change_mode status suffixes report arming changes

Also merges the duplicated composer-path arming comment.

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

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed fresh from scratch at head 41eab46f2 (7 parallel lanes: claims/completeness, unarmed-equivalence + wire/persistence compat, correctness deep-dive, perf/elegance/boundaries, smuggling/DCO, test quality, parent registration codewhale-hq#809; all gates re-run locally and mutations re-executed on a dedicated worktree). Base f1eb10efc equals the current pinvou3-clean tip — no rebase needed, MERGEABLE (BLOCKED is only the gate bot's /lgtm hold).

Verdict

Approve direction. The PR is genuinely useful and root-caused: an external host running the engine autonomously currently gets hard denies (non-bypassable holds, repo-law, sandbox escalation, execpolicy denies) or silently swallowed typed-ask prompts with no way to say "I have a human at the card"; the fix adds an explicit host-intent signal consumed at each decision gate with approval_force_prompt defending every new prompt against the codewhale-hq#3866 auto-approve reversal and grant caches. The transition notice is computed from the same engine state the blocks report and is byte-stable otherwise. 0 blockers, 0 MAJOR code defects found. Three P2-grade items below (all fail safe), plus minors.

Verified

  • Gate reproduced: cargo test -p codewhale-tui --lib --locked forkguard_ -- --test-threads=1 → 253 passed / 0 failed at head; all 13 new pins present exactly once, correctly prefixed.
  • All 13 body claims verified TRUE against code; two are PARTIAL (findings 2–3). Unarmed equivalence holds: every production ask_on_guardrail site is a session-flag gate, a literal false/None, or an inert carrier, and each new branch collapses to the base expression at the default; wire compat is safe in both directions (serde default inbound; no deny_unknown_fields in the wire Op, so old engines drop the unknown field; roundtrip fixture updated); SavedSession doesn't carry the flag (consistent with auto_approve) so pre-PR session files load identically.
  • Mutation probes reproduced RED exactly as the body claims: deny-face armed-condition disabled → forkguard_armed_deny_face_becomes_guardrail_prompt_unarmed_stays_block red; never-rollback patch → None → forkguard_turn_authority_install_never_rolls_back_published_arming red. The body's "如实记录" of the renderer-only tests is accurate, and the preview-hash test does bite the compare site.
  • Smuggling CLEAN: 2 commits, .rs files only, every hunk serves (a)/(b)/the declared commit-2 hardening; ~30 mechanical param lines verified mechanical (the relocated auto_review_auto_resolves_hallucinated_question_without_prompting body is byte-identical); per-commit DCO sign-offs match author identities; no private data.
  • Registration (parent PR codewhale-hq#809) satisfied: OPEN against main, pins this exact head, EXPECTED_COMMITS=88 independently verified via rev-list; 13/13 test-name pins match; the copy pin Authority transition (host): {} -> {}; reason: {} is byte-exact against render(); static replay of the guard's grep loop: 220/220 CodeWhale fingerprints green at head. Genuinely new behavior — no ask_on_guardrail or (host) transition line at base and no upstream duplication.
  • Perf clean: unchanged path is allocation-free O(1); no hot-path (streaming/token) changes; no unarmed-path formatting added.
  • Correction to prior review lore: the "inherited broken T3 pins at fork-guard.sh:223-224" flag from the #129 review is stale — the MCP_REGISTRY pins were re-pinned in #125's 2026-10-28 batch; fork-guard --fast has no inherited red.

P2 findings (fix here or fast-follow; all fail safe)

  1. [P2][test-net] The transition detector's recorder line is unpinned — deleting it keeps the whole suite green (mutation-verified). Removing self.last_turn_meta_authority = Some(current_posture); (crates/tui/src/core/engine.rs:5778) leaves 253/0 green: from the second turn on no transition ever renders and feature (b) dies silently. The preview-hash test pre-stuffs last_turn_meta_authority and runs a single turn, so it pins the compare but not the memory update; the body's mapping ("preview 哈希测咬检测器") is true for the compare lines but incomplete for the detector as a whole. A two-turn E2E (flip → line renders; unchanged posture → byte-stable) or a unit pin on the recorder closes it.
  2. [P2][doc] session.rs:436-440 overclaims "the deterministic floor" as an armed-converted face. The built-in safety gate is hard-Block with no arming consult anywhere (engine.rs:8427-8439 collapses AskUser+built_in_safety_gate to Block under Auto/Never/Bypass; turn_loop's Block arm applies blocked_error unconditionally), and the PR body itself never claims this face. Correct the doc (or actually convert the face, with a pin, if that was the intent).
  3. [P2][dead arm] The armed subagent held-call hold mirror is unreachable. subagent/mod.rs:14886-14893 routes an armed AutoReviewPlanDecision::ForcePrompt to the parent approval surface, but ForcePrompt never occurs in the Bypass arm it lives in (the plan decision collapses safety-gate holds to Block under Bypass). The body presents "武装 held-call hold 路由到父审批面" as a live behavior, yet only the delegated typed-ask refusal is reachable. Either consult arming before the Bypass collapse (making the mirror real, with a test) or annotate the arm as fail-safe future-proofing and adjust the body.

P3 findings (fix, disclose in 已知边界, or accept)

  1. pending_authority_transition is never cleared after the turn-start message consumes it, so mid-turn turn_meta blocks re-render the same line: steers (engine.rs ~4098), step-budget soft-landing (turn_loop.rs ~1724), budget exhaustion (~1763) — the line can appear in several messages of one turn. take() after the turn-start build fixes it.
  2. Preview reads stale pending: a ChangeMode arriving between turns makes /preview-request omit the line the live turn will send — divergence in exactly the case the feature targets; the TurnMetadataSnapshot doc ("preview stays byte-identical") is overstated, and the preview-hash test masks it by injecting both fields manually. Recompute the compare in the preview builder (same O(1)) or scope the doc claim.
  3. Turn start records last_turn_meta_authority before the late NotStarted early-return (engine.rs ~5968): a send failing exactly there records a posture that is never rendered, so the next change reports the intermediate step. Rare, cosmetic.
  4. LiveRuntimeAuthority::from_fields now stores raw fields where base normalized via agent_approval_mode_for_turn (auto_approve ⇒ Bypass). For non-canonical input (auto_approve=true, approval_mode≠Bypass) the recorded snapshot transiently diverges from the session until the next record_applied self-heals; unreachable with current callers. Declare it or normalize at intake.
  5. The armed escalation catch-all converts holds for any non-Suggest posture, including Auto-Review — outcome preserved (the resolver's AutoDenyAutoReview precedes the armed branch; verified) but the receipt wording differs, and nothing pins it. Add a pin or exclude Auto explicitly.
  6. Latent mixed-host trap: approval_routing.rs builds the UI authority via from_effective_fields (armed forever false), and exec_agent.rs ~753 auto-approves every ApprovalRequired including approval_force_prompt events. Harmless today (no in-crate host arms), but an invariant comment/guard at both sites would keep a future arming toggle from silently auto-denying (TUI) or auto-running (headless) armed holds.
  7. turn_meta_block_stays_within_measured_ceiling passes authority_transition: None only; a transition-bearing block adds ~148B against the measured ceiling, unmeasured.
  8. 已知边界 additions worth making: arming is process-memory only (lost on restart, like auto_approve — the host must re-arm); and the session.rs "unarmed behavior is byte-identical" invariant should be scoped to posture-unchanged sessions, since unarmed sessions whose mode/approval posture changes do gain the transition line (that's feature (b) working as intended, but the invariant as worded contradicts it).

Nits (no action required): double registered_tool_blocked_in_full_access call in turn_loop ~3950; AuthorityTransition could derive Copy; publish_turn_authority's 8 params could take LiveRuntimeAuthority and drop one too_many_arguments; the armed escalation card drops the model justification the Suggest card shows; unbounded recv() in the status-suffix test helper would hang rather than fail; no disarm round-trip pin; transition compare uses raw session.approval_mode while the preview standing line renders the effective chip (cosmetic divergence under auto-approve remap); three arming phrasings across surfaces — pin each separately in the parent register if kept.

Every discrepancy above fails in the deny/silent direction — nothing arms an execution path without a human event. Fixing 1–3 (one test, one doc paragraph, one arm decision) plus the two known-boundary additions would make this an easy approve. Engine-side behavior, registration, and provenance are all sound as-is.

Review follow-ups on the host-armed guardrail work:

- the authority transition is computed when the turn's metadata block
  is built (after the NotStarted gate, so a failed send can no longer
  record a posture the model never saw) and consumed by that block
  only, so steers and mid-turn runtime notices stop re-rendering the
  same line;
- the preview manifest recomputes the transition from the recorded
  posture instead of reading the consumed leftover, keeping the preview
  byte-faithful when a host flips posture between turns;
- armed face conversions (execpolicy deny face, repo-law holds, sandbox
  escalation) now exclude the Auto-Review and Never postures explicitly,
  making their unconditional denials true at every armed face, and the
  armed escalation card carries the model's justification;
- annotate the fail-safe child ForcePrompt arm and the always-unarmed
  UI approval authority, document the from_fields raw-field contract,
  and correct the session.rs face list (the deterministic floor stays a
  hard deny when armed);
- pin the detector end to end (record + consume + second-turn
  byte-stability) and the armed Auto-Review deny face.

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

Copy link
Copy Markdown
Collaborator Author

Fix wave pushed: 04c1f7843 (fresh-review findings folded in; registration re-pinned in Pinvou/pinvou-agent#809 at 125fa92b7).

Behavior fixes

  • Transition notices are now computed when the turn's metadata block is built (after the NotStarted gate, so a failed send can no longer record a posture the model never saw) and consumed by that block only — steers and mid-turn runtime notices stop re-rendering the same line (review findings 4 + 6).
  • The preview manifest recomputes the transition from the recorded posture instead of reading the consumed leftover, so a host that flips posture between turns previews exactly the line the live body will send (finding 5).
  • Armed face conversions (execpolicy deny face, repo-law holds, sandbox escalation) explicitly exclude Auto-Review and Never — their unconditional denials are now true at every armed face, not just at the resolver (finding 8); the armed escalation card now carries the model's justification.

Docs & tripwires

  • session.rs face list corrected (the deterministic floor stays a hard deny when armed) and the byte-identical invariant re-scoped; arming is documented as process-memory only (finding 2 + 11).
  • The child held-call ForcePrompt arm is annotated as fail-safe future-proofing (Bypass collapses to Block upstream — unreachable today); tripwire comments added at approval_routing.rs (always-unarmed UI authority) and exec_agent.rs (headless auto-approve) (findings 3 + 9); from_fields raw-field contract documented (finding 7).

Tests (finding 1 — the mutation-proven hole)

  • New forkguard_turn_start_detector_records_the_posture_and_renders_the_transition_once: end-to-end compare → record → render → consume, with two-turn wire assertions. Before this commit, deleting the recorder line kept the whole suite green; now both the recorder deletion and the consumption-reset deletion turn it red (mutation-verified).
  • Deny-face pin extended with the armed Auto-Review case; preview-hash test now drives the recompute path; status-suffix helper got a timeout.

Gates at 04c1f7843: forkguard_ 254 passed / 0 failed, benchmark lane 6/0, preview 60/0, turn_metadata 24/0, change_mode 4/0, escalation 10/0; static replay of the guard's fingerprint loop: 109/109 CodeWhale fingerprints green at the new head (15 T2 pins incl. the new detector pin + copy pin). codewhale-hq#809 re-pinned: EXPECTED_HEAD→04c1f7843, EXPECTED_COMMITS→89, gitlink + zh/en registers + safety-modes.md updated. The PR body reflects the new head, the 8-probe mutation list, and the expanded known-boundaries section.

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

复审(fresh re-review)@ 04c1f7843 — 8 评审道 + 全部门禁复跑 + 2 条突变探针

从零重审,不引用此前结论。Rebase 核对:本 PR merge-base == pinvou3-clean tip f1eb10efc(公开维护头未动);父仓 codewhale-hq#809 base == main 3cec34e2b——双双 MERGEABLE,无需 rebase。

结论:approve 方向。本 PR(#131)范围 0 blocker / 0 major;登记侧(codewhale-hq#809)发现 1 个 major,须在合并前修复(见下)。

覆盖与本机门禁(全绿)

  • 8 条独立评审道:armed faces(authority.rs/approval_routing/exec_agent)、transition+turn_meta(engine/turn_loop/preview/session)、plumbing+subagent parity、测试网突变分析、smuggling/DCO/数字审计、perf+并发+panic、架构+上游对比、父仓登记一致性。
  • 门禁(本机 @ 04c1f7843 复跑):forkguard_ 254/0(跑两遍,含全部 14 条新钉——以编译产物 --list 计 254 核实);--features benchmark-eval-controls 6/0;preview 60/0、turn_metadata 24/0、change_mode 4/0、escalation 10/0。父仓 @ 09393c9707(tree dcd7fe5ea 本地重建字节一致)fork-guard.sh --fast 指纹层全过(行为名实测 269)。
  • 突变探针(逐字节还原,收尾 git status 干净 + 全套 254/0 复绿):repo-law Auto/Never 豁免子句删除 → RED(该豁免有钉);repo-law 武装臂整臂删除 → RED。
  • smuggling:19 文件逐 hunk 分类((a)/(b)/测试钉/注释),无一处越界;无 Cargo/CI/版本/配置改动;3 提交 DCO 齐全、提交信息与内容逐条相符。

声明核对((a)(b) 与全部登记数字)

声明 判定
(a) 五个武装面(forced hold + approval_force_prompt 显式置位反转 codewhale-hq#3866、repo-law、typed-ask、sandbox escalation 带 justification、execpolicy deny face 保匹配语义)逐条实现,Auto-Review/Never 在全部位点无条件拒 TRUE(逐位点引证核实)
会话授予/remember 不能静默消费武装 hold(!approval_force_prompt 门 + E2E 反消费钉) TRUE
composer ! / ACP / 子代理 deny face 硬编码不武装;UI approval authority 恒未武装 + 两处 tripwire TRUE
子代理 typed-ask 武装下继承 auto-approve 仍拒(Bypass ForcePrompt 臂为 fail-safe 前瞻,注释与代码一致) TRUE
(b) 检测点在 NotStarted 早退之后、record→render→一次性消费无 await 窗口、steer/预算不复渲染、未变轮逐字节稳定、preview 与 live 哈希一致(preview.rs 走同一 authority_transition_between、非读残留)、never-rollback、状态后缀三态(armed/disarmed/None 无后缀) TRUE(全部逐条核实;消费窗口 6054→6077 无 await/return 已验证)
未武装逐字节等同 + 唯一 unarmed 协议增量 = 转换行(session.rs 如实披露) TRUE
数字:89 提交 / 87 签名(2 个未签名 squash 为既有 #107/#116)/ 269 forkguard 名 / 254 实跑 / 15 指纹(14 测试钉逐字存在 + 1 模型可见行字节精确)/ EXPECTED_HEAD/EXPECTED_COMMITS / drift 526 +93516 −39313 全 TRUE
「纯底库行为提交」(登记只在父仓) TRUE

发现

P2 minor(不阻塞;建议随合并或紧随修复)

  1. 子代理 doc 过度声明 repo-law 镜像 — subagent/mod.rs:2500-2508 末句 "children never run what the armed main line would prompt on":子代理委派路径完全不评估 repo-law(subagent/mod.rs 零 repo_law 引用,base 亦然——repo-law 本就是主线 face),故武装会话里子代理对 repo-law 保护路径的委派写会静默执行(武装主线会弹卡)。这是既有边界不是回归,但该句把属性声明到了子代理不设防的面上。建议:把句子收窄到子代理实际设防的 face(typed-ask / execpolicy deny),或把 repo-law 镜像进子代理门。
  2. 武装静默丢失竞态 — handle.rs:32-63 同步发布 op 快照、publish_runtime_authority(:89-96)全量替换;turn install 发布 ask_on_guardrail: None(runtime_threads.rs 4 处)。宿主 ChangeMode 发布与引擎 drain 之间若插入 turn install,Some(true) 被替换为 None,drain 应用快照后武装静默丢失(无事件、无回执)。其余字段无此问题(install 镜像已应用态,None 无害);唯独 arming 的 None 语义是 "不变"。修复方向:turn install 发布携带 live arming,或 drain 合并 None 而非覆盖;至少注释披露。窗口窄,宿主重发即愈。
  3. preview 转换姿态来源与 live 不一致 — preview.rs:422-424 姿态取 self.session.approval_mode,而同一 preview 的 standing 行取 input_policy.approval_mode_for_session()(:417),live 比较又发生在 apply_runtime_mode_policy 安装计划姿态之后(engine.rs:6046-6049)。当 PreviewRequestInputs 携带与 session 不同的假想姿态时 preview 哈希 ≠ live 哈希——恰是测试 13 钉的属性;内置 TUI 的 FIFO 时序今日掩之。修复:姿态改用 input_policy.approval_mode_for_session()。
  4. switch_provider 重启引擎静默掉武装 — Engine::new 重置为 false,无任何宿主可见信号。session.rs 披露的是"恢复/重启",运行中换 provider 并非重启;建议掉武装时发事件或在状态里回执。
  5. 残余测试缺口(探针后收敛) — (i) composer-! 武装反例无钉:engine.rs:2575 把 false 换成 session 旗标的突变可存活(既有 yolo 测试 unarmed 看不见);(ii) escalation 位点的 Auto/Never 豁免未探针验证(repo-law 位点已实测 RED;escalation 结构不同,建议补一条 armed-Auto 恒拒用例或自跑探针确认)。lane 评审静态分析还报了 repo-law 豁免缺口,已探针证伪(RED)——测试网比静态分析以为的更强。
  6. 发送失败后的姿态推进 — engine.rs:6058 先记录后发送;提供方 HTTP 失败时已记录姿态模型可能未收到(append-only history 通常随下一成功请求兜底;edit-last-turn 截断会丢)。PR 对此的声明只 scoped 到 NotStarted,措辞准确;可选修复:终态失败回滚 last_turn_meta_authority。
  7. from_fields 不再归一化(披露于 doc comment)— 非规范发布会在 permission_snapshot() 短暂暴露 raw 形态,record_applied 自愈;方向 fail-closed、在库生产者全规范。记录备查。

已证伪的评审道指控(如实记录,供后续评审参考)

  • "body 的 254 过时,head 实为 268":不成立。编译产物 --list = 254(含全部 14 条新钉);268/269 是源码定义计数(含既有 Windows-gated 测试与 config crate),与 "254 过 / 269 名" 双双自洽。
  • "Never+auto_approve=true 违反无条件拒绝不变量":不成立。agent_approval_mode_for_turn(authority.rs:408-417)把 auto_approve=true 一律折叠为 Bypass,组合态经唯一会话写者 apply_change_mode 不可达;规范态下 auto_approve==true ⟺ approval_mode==Bypass。

P3 nit

  • arming 谓词在 5 个位点 3 种写法(resolver 结构排除 / armed && !matches!(Auto|Never) / !auto_approve || armed)——可提 armed_guardrail_face(armed, mode) 单源化,防未来新 face 漏排除。
  • TurnMetaPosture 字面量 live/preview 双处手写(加第 4 字段时须双改;哈希测试只在校验运行时兜底)——提 current_turn_meta_posture() helper。
  • 武装 escalation 臂与 Suggest 臂主体重复(仅后缀差);3 处新增 too_many_arguments allow(可收敛为 params struct,沿既有 idiom 亦可);turn_metadata_block 读 pending_authority_transition 生产不可达(潜在双渲染只在未来有人窗外置位时——建议加不变量注释);ops.rs:429 "when this op is processed" 措辞(实际 drain 时机更早,效果相同);zh/en 登记 (a) 节两处措辞差(en 丢 "Auto-Review" 与 fail-safe 括注,en 是不完整不是错误);提交 1 与 2/3 作者身份不同(各自 DCO 自洽)。

父仓 codewhale-hq#809 侧 — [MAJOR] 合并前必须修:登记漂移三件套

THIRD_PARTY_NOTICES.md:10、docs/fork-policy.md:3/11/12/14、docs/fork-policy.en.md:3/10/11/13 仍写候选 41eab46f2(88 提交 / 86 签名 / 268 名 / 525 文件 +93293/−39351 净 +53942,drift 区间 dcd4c200f..41eab46f2),与同一棵树的 gitlink 04c1f7843、fork-guard.sh、fork-modifications zh/en(89/87/269/526/+93516/−39313)直接矛盾,且文件头自称 "最后更新 2026-10-31 / Updated: 2026-10-31"。fork-policy.md 是 AGENTS.md 指定的 single source of truth。guard 门禁仍绿(fast 层不校验这三处散文),但登记 PR 的本义就是防漂移——请在 codewhale-hq#809 重钉这三处(至 04c1f7843 或合并后 squash 头)再合。其余登记面(15 指纹逐字、EXPECTED_*、gitlink、safety-modes.md 逐节比对代码、zh/en 主体)全部核实无误。

上游对比(记录,不阻塞)

上游 Hmbown 无等价机制(ask-rules-survive-bypass 之外无 host-arming、无模型可见转换行);与三处上游已收口教义(codewhale-hq#6471 fail-closed、codewhale-hq#6310 server-owned posture、codewhale-hq#3790 mode-is-sole-authority)的张力 PR 内已如实标注,属有意的教义反转而非误实现。上游 2026-11-01 e34f207d2 已把 authority 抽到 crates/engine——下次同步 armed faces 需重接线;本能力是可复用底座关切,届时值得随上游 issue 提出(upstream-first 边界)。CW-7495 锚点公开不可核验(若为内部 spec 号则无妨)。

Disposition:#131 approve 方向可合(M1 建议顺手修);codewhale-hq#809 修完三件套漂移后合。

…d posture

Re-review follow-ups on the host-armed guardrail work:

- the authority publish path replaces the whole snapshot, and the op
  loop applies the snapshot rather than the queued op payload, so a
  posture-only publish (a turn install, or a ChangeMode op carrying no
  arming) racing a host arm silently dropped the request. `None` now
  means "keep" at the publish layer too, mirroring apply_change_mode;
  only an explicit Some may move arming;
- the preview recomputes the transition from the same posture helper as
  the live compare, so hypothetical preview inputs render exactly the
  body the live turn sends instead of reading the stale session state;
- the subagent arming doc now scopes its claim to the faces the child
  gate actually evaluates (repo-law stays a main-line face), the
  /provider switch documents that respawning drops session arming, and
  the ops.rs arming wording matches the drain semantics;
- pin the publish stickiness end to end, the armed composer hard-deny
  face, the armed Auto-Review escalation fail-closed face, and the
  preview planned-posture parity (preview hash equals the live wire
  body hash for divergent inputs) - each verified red under its mutant.

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

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fix wave pushed: 5c13fc561(re-review 5731536075 的可修复项全部收敛;登记随父仓 codewhale-hq#809 重钉至 ca92bc54f,gitlink → 5c13fc561)。

行为修复

  • 武装发布粘滞(re-review M2):publish 路径整体替换快照,而 op 循环应用快照而非 op payload——turn install 或无武装 ChangeMode 与宿主 arm 竞争时会静默吞掉请求。现在 None 在 publish 层同样表示"保持"(与 apply_change_mode 同教义),仅显式 Some 可动武装。新钉 forkguard_pending_arming_survives_arming_free_authority_publishes 突变验证转红。
  • preview 计划姿态(re-review M3):preview 转换改用与 live 比较同一 turn_meta_posture_for 助手(planned approval 来自输入、live arming 来自会话);假想姿态输入下 preview 哈希 == live 线体哈希,新钉以 live parity 断言突变验证转红(首版"双构建不等式"钉曾被本机突变存活、已重设计——如实记录)。

文档/钉子

  • 子代理 arming doc 收窄到子代理实际设防面(repo-law 保持主线 face,M1);/provider 重启掉武装补 tripwire(M4,app 池 spawn 时重放武装旗标);ops.rs 措辞对齐 drain 语义;turn_metadata_block 的 pending 读取补 await-free 不变量注释;比较点注释补发送失败 residual 边界(M6)。
  • 新增 armed composer 硬拒钉(deny-rule 命中经 Prompt 臂放行 deny 命令的突变转红)、armed Auto-Review 升级恒拒钉(豁免子句删除转红)(M5)。

突变核查:4 条新钉全部先证转红再逐字节还原;repo-law Auto/Never 豁免 与 武装臂删除 两条探针再次确认 RED。工作树收尾 git status 干净。

门禁(5c13fc561):forkguard_ 258/0;benchmark 6/0;preview 61/0;turn_metadata 24/0;change_mode 4/0;escalation 11/0;cargo fmt --check 干净。登记数字重测:90 提交 / 88 签名 / drift 526 files +94033/-39383(净 +54650)/ 273 行为名 / 19 指纹。

未采纳(如实):arming 谓词 5 处 3 形态的单源化 helper(对突变验证过的安全面做纯美化重构,收益不抵风险);from_fields 非归一化(已披露、自愈);提交 1 与 2-3 作者身份差异(重写历史不值)。

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fresh re-review(第三轮,9 条并行车道,钉在 5c13fc561)

覆盖:9 条车道——(a) 12 项主张逐条对码、(b) 9 项主张逐条对码、arming 状态机与竞态深审(10 场景 + 不变量审计)、走私逐 hunk 对账(含 −107 删除行逐项核对与两个被搬移存量测试的逐字节 diff)、变异测试 14 探针(含 bonus 探针)、设计质量、性能与健壮性、父仓 codewhale-hq#809 登记一致性(283 条指纹逐条验证 + guard 实跑)、门禁复现。本轮从头独立重审,不沿用前两轮结论。

总判定

  • (a) 12 项主张:10 TRUE,2 PARTIAL(均为表述口径而非代码缺陷:①「UI disposition 武装后弹卡」实为 resolver 能力——approval_routing.rs 刻意恒未武装(tripwire 注释在案),在库 UI 路径不产卡,武装卡由外部 wire 宿主消费 Event::ApprovalRequired { approval_force_prompt };②「未武装逐字节等同」按设计不含 (b) 的 transition 行(session.rs doc 已如实披露这处唯一 unarmed delta))。
  • (b) 9 项主张:8 TRUE,1 PARTIAL(NotStarted 早退不再吞 transition:修复真实、位置正确,但全套测试 0 例行使该路径 → F3)。
  • 走私:CLEAN(19 文件全部 hunk 可归入 (a)/(b)/测试钉/doc-tripwire;−107 删除行逐项有对应替换;两个被搬移存量测试正文逐字节一致;无 Cargo/依赖/serde rename/feature/cfg 改动;crates/ 外零改动)。
  • 变异测试:SOUND(14 探针全部转红或被证明为无效变异;无死 pin、无弱断言。1 处探针 GREEN 经代码证明为无效变异:force_prompt 的 || armed_full_access_hold 被 registered_non_bypassable 逻辑蕴含,删除不可达差异——该析取项属冗余防御代码,无害)。
  • 门禁:7/7 精确复现——forkguard_ 258/0、benchmark 6/0、preview 61/0、turn_metadata 24/0、change_mode 4/0、escalation 11/0、fmt clean。(该分支无 CI checks 报告,以上为本机复现。)
  • 父仓 codewhale-hq#809 登记:CONSISTENT——gitlink、EXPECTED_HEAD、EXPECTED_COMMITS=90、283 条指纹逐条命中(19 条新 T2 全中)、273 个 forkguard 名、526 files +94033/−39383、§8 census 18 条、zh/en parity、guard --fast 实跑绿——但发现 1 项文档级 MAJOR(F4)。
  • 无需 rebase:merge-base == pinvou3-clean tip f1eb10efc。

发现

F1 · MAJOR — runtime_threads.rs:9462 审批 pump 丢弃 approval_force_prompt,武装 hold 会被静默自动批准(修复波 3 已带)
pump 以 EngineEvent::ApprovalRequired { id, tool_name, description, intent_summary, .. } 解构丢弃该字段(全文件 0 处引用),authority.auto_approve == true(Full Access)时径直走自动批准:approval.decided { auto: true } → approve_tool_call——无任何人类决定,被守卫动作直接执行。引擎侧 armed face 的注释承诺「force_prompt 阻止 session grants 与 posture shortcut 静默消费本事件」,该承诺对此在库消费者不成立。
可达性核查:当前随附 producer 全部发 None/false,daemon 侧两处 Op::ChangeMode 均为 daemon 自组装(恒 ask_on_guardrail: None),无 client→op 透传——今日不可达(初判 BLOCKER,据此降为 MAJOR)。但本 PR 的功能契约正是「外部宿主经 wire 武装」:任何宿主武装入口抵达该部署的瞬间洞即开;且 PR 已在 exec_agent.rs:754-759 为同类缺口放置 tripwire,唯独漏掉这个在库消费者。附带事实:前存的 non-bypassable 面(CW-7495,force_prompt 先于本 PR 存在)在 pump 下同样被静默自动批准。
修复:绑定字段,auto_approve && !approval_force_prompt 才走自动路径,否则落入既有真人决策路径(register pending → approval.required → 等待决定;Auto-Review fail-closed 臂原样保留)+ tripwire 注释 + forkguard 钉。

F2 · MINOR — ChangeMode status 后缀按「有无 carrier」而非「arming 是否实际变化」门控:首次 turn 之后每次普通 posture 切换都谎称 "(guardrail prompts disarmed)"(修复波 3 已带)
apply_change_mode 的后缀分支是 if let Some(armed);生产上 op 循环应用的永远是经 record_applied_runtime_authority 回填的快照(Some(session.ask_on_guardrail)),后缀分支因此恒 Some——每次普通模式切换的状态行都追加武装宣告。既有钉走的是直调 None 路径(生产不可达),给了假信心。修复:仅在 arming 实际翻转时追加后缀,钉扩展覆盖「carrier 回显未变值」的生产形态。

F3 · MINOR — NotStarted 修复((b) 主张 6)无测试行使(修复波 3 已带)
04c1f7843 把比较移到 NotStarted 早退之后,结构正确,但「turn 启动失败不吞 transition → 下个成功 turn 渲染」0 测试。修复:补回归钉。

F4 · MAJOR(文档级,父仓 codewhale-hq#809)— 概览段仍写评审候选为 04c1f7843
docs/fork-modifications.md L73 与 fork-modifications.en.md L75「指向评审候选 04c1f7843df…」同文件内即与 EXPECTED_HEAD=5c13fc561 矛盾;「第 0 层仅在实际检出 04c1f7843 时为绿」同样过期(zh L76/L455、en L77/L337,共 6 处)。中间波 125fa92b7 的遗留,终波未同步。登记文档是 fork 基线 single source of truth,合并前必须改齐。

F5 · 观察(pre-existing,非本 PR 引入)— tools::subagent 存在确定性顺序依赖失败测试
child_permission_gate::auto_review_guardian_denial_refuses_the_call_with_a_receipt 在同模块 allow 兄弟测试之后运行时,guardian deny 决策不被强制(命令实际执行,tests.rs:22366 panic)。基线 f1eb10efc 复现(12/1)→ 非本 PR 引入,建议独立 follow-up(疑似 guardian mock 状态跨测试泄漏)。

Minor / NIT(记录,不阻断)

  • LiveRuntimeAuthority::from_fields 去掉归一化链(998ffe27e 引入的 refactor churn;全部现役 producer 发送规范形,无可达行为差异;正文已披露,建议 PR 描述补一句)。
  • armed repo-law 卡缺 arming 标记(其余三个武装面均有 "ask-on-guardrail is armed" / "(armed guardrail approval)"),宿主无法从卡面区分。
  • typed-ask armed 臂(!auto_approve || armed)与其它三面的「源头排除 Auto/Never」形状不一致:armed Auto/Never 会话会收到一个注定被 resolver 自动拒绝的事件(fail-closed,仅噪音;承袭 codewhale-hq#3790 既有形状)。
  • armed Full Access 卡在 description 为空时文案以 " — " 开头。
  • 子代理 registry 构造时冻结 arming;会话中途武装不传播到已在运行的 child(新 spawn 携带)。
  • 解除武装不唤醒已 pending 的审批等待(fail 方向安全,turn 挂起至 cancel/interrupt,可接受)。
  • narrowing 收据行与 (host) transition 行的 "Authority transition" 前缀同块可共存(字符串可区分,留意混淆)。

四维结论

  1. 有用且完备:是——(a)/(b) 落地完整、根因清楚、mutation-verified 钉网致密;F1/F2/F3 由修复波 3 收敛。
  2. 优雅:是——复用 ChangeMode/LiveRuntimeAuthority/审批卡通道,无并行机制;turn_meta_posture_for 单一算法源;三处 tripwire 准确。残留的谓词三处内联重复与卡片文案复制为可后续收敛项。
  3. 走私:CLEAN。
  4. 缺陷:F1 为唯一实质缺陷(pump 消费者契约洞),修复波 3 闭合;F5 为 pre-existing 顺序依赖测试,独立跟进。

04c1f78 made AuthorityTransition Copy but left two .clone() calls
on Option<AuthorityTransition> in the turn-meta builders. The fork
CI clippy gate (-D warnings, no --all-targets needed to see them)
fails on both with clippy::clone_on_copy. Copy semantics make the
removal byte-identical; forkguard_ suite stays 258 passed / 0 failed
and cargo fmt --all -- --check stays clean.

Signed-off-by: asto <asto18089@126.com>
@asto18089
asto18089 marked this pull request as ready for review November 2, 2026 05:51
…al arming flips

Fresh re-review (COMMENT 5732557068) findings F1-F3: the runtime-threads
approval pump dropped approval_force_prompt and auto-approved forced holds
under Full Access postures; the ChangeMode status suffix keyed off the mere
presence of the ask carrier and announced '(guardrail prompts disarmed)' on
every ordinary posture switch; and the NotStarted ordering fix had no test
exercising it. Three new mutation-verified pins.

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

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

修复波 3(收敛 fresh-review 发现,钉在 f972884e5)

按 fresh re-review(COMMENT 5732557068 @ 5c13fc561)逐条收敛;波已 rebase 到作者新推的 clippy 修复 10b28ef5f 之上。

F1(MAJOR)— runtime pump 尊重 approval_force_prompt

  • runtime_threads.rs 审批 pump 绑定该字段;auto_approve && !approval_force_prompt 才走自动批准路径,forced hold 落入既有真人决策路径(register pending → approval.required → 等待决定;Auto-Review fail-closed 臂原样保留);附 tripwire 注释。
  • 行为影响披露:前存 non-bypassable 面(CW-7495)在 pump + Full Access 下此前会被静默自动批准,现按 CW-7495 契约浮给客户端决定——与内建 TUI 同面行为一致。
  • 钉:forkguard_pump_never_auto_approves_a_force_prompt_hold(Full Access 线程 + force_prompt 事件 → 必须 pending 等真人、无 auto decided;外部 deny 才传导)。突变验证:删 !approval_force_prompt 守卫 → 该钉转红(hold 被自动批准)。

F2(MINOR)— status 后缀只在 arming 实际翻转时追加

  • apply_change_mode 以 arming_change(实际翻转)替代「有无 carrier」门控后缀;生产形态(op 循环应用回填快照,恒 Some)下普通 posture 切换不再谎称 "(guardrail prompts disarmed)"。
  • 钉扩展:forkguard_apply_change_mode_status_suffixes_report_the_arming_change 增补「carrier 回显未变值不得宣称翻转」断言(生产形态)。突变验证:后缀回退 carrier 门控 → 该钉转红。

F3(MINOR)— NotStarted 顺序补回归钉

  • 新增 forkguard_a_not_started_turn_neither_consumes_nor_records_the_transition:无 client turn 失败于块构建之前 → 记录不动、pending 不被吞 → 下个成功 turn 渲染 transition 行。突变验证:在 NotStarted 路径注入 posture 记录(模拟修复前行为)→ 该钉转红。

门禁(本机复现 @ f972884e5)

  • forkguard_:260 过 / 0 挂(258 + 2 新钉;后缀钉原位扩展);preview 61/0;turn_metadata 24/0;change_mode 4/0;escalation 11/0;--features benchmark-eval-controls forkguard_benchmark_ 6/0;cargo fmt --check 干净。
  • 本地 clippy 1.99 -D warnings 在 10b28ef5f 与 f972884e5 报同样的 5 条预存 lint(too_many_arguments ×4 / collapsible_if ×1,全部在 PR 之前的代码上;与 fork CI 工具链口径存在漂移),本波未新增任何 lint。

其余发现处置

  • F4(父仓 codewhale-hq#809 登记文档 6 处 04c1f7843 陈旧候选 SHA):随父仓登记批改齐(另行推送)。
  • F5(tools::subagent 顺序依赖失败,基线 f1eb10efc 复现):pre-existing,不在本 PR 打包修复,建议独立 follow-up。
  • Minor/NIT(armed repo-law 卡标记、typed-ask 臂形状、|| armed_full_access_hold 冗余、空 description " — " 前缀、child registry 冻结 arming 等):记录在案,留待后续收敛;from_fields 非归一化已如实披露(见 PR 描述补充)。

登记随父仓 PR codewhale-hq#809 同步

gitlink/EXPECTED_HEAD→f972884e5、提交数/签名数/漂移数字重测、T2 指纹 19→21(+2 新钉)、safety-modes §8 census 18→20、zh/en parity、六处陈旧候选 SHA 改齐。

@asto18089

asto18089 commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Ready for review. Fix wave 41eab46 -> 04c1f78 -> 5c13fc5 plus two follow-ups re-verified locally; all numbers below are from the current head f972884 (including the F1-F3 pump/status-suffix fix pushed at 06:11Z).

Local re-verification at f972884 (single-threaded forkguard suite, shared target dir):

  • cargo test -p codewhale-tui --lib --locked forkguard_ -- --test-threads=1: 260 passed, 0 failed (12054 filtered out)
  • cargo fmt --all -- --check: clean
  • fork-CI clippy, verbatim (cargo clippy --workspace --all-features --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or -A clippy::collapsible_if -A clippy::assertions_on_constants): clean

Diff review of the fix wave against 41eab46 confirms the six minors landed with no smuggled changes: transition computed and consumed at the metadata-block build only; publish_live_runtime_authority treats None arming as keep; armed faces (typed-ask deny, repo-law holds, execpolicy deny, sandbox escalation) exclude Auto-Review and Never explicitly, with the model's justification on the armed escalation card; preview recomputes the transition from the same posture helper as the live compare; subagent/provider-switch/ops/session docs scoped; end-to-end tests added (cfg(test) only). The later follow-ups are equally scoped: 10b28ef drops two clippy::clone_on_copy violations (.clone() on the now-Copy Option<AuthorityTransition>, byte-identical), and f972884 fixes the runtime pump silently auto-approving forced holds under auto-approve postures, keys the "(dis)armed" status suffix to real arming flips, and pins all three (new forkguard_pump_never_auto_approves_a_force_prompt_hold, forkguard_a_not_started_turn_neither_consumes_nor_records_the_transition).

CI status (transparency note): GitHub Actions is currently not creating runs for pull_request events in this repo — zero check-runs on every open PR (#130-#134 included), and all workflow_dispatch runs have sat in queued since Oct 29. I dispatched fork-ci on this branch (run 41398252950); it is queued like the rest, so the checks section here will stay empty until runner provisioning recovers. That outage is repo-wide, not specific to this PR, and looks worth an admin look since it blocks every open PR. Note for whoever re-runs CI later: I verified against the exact gate commands above; the only known local-vs-CI delta is the full-workspace nextest run, which shares this suite's state.

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Round-4 全新评审(9 道 lane,从零开始,不继承任何先前结论)

评审对象 f972884e5dc99c16241941c87a094140e5e7600a(6 提交)。rebase 核实:base f1eb10efc 经 GitHub API 确认仍为 pinvou3-clean 顶端(rebase 为空操作;父仓 main 3cec34e2b 未动,codewhale-hq#809 0 behind,亦无需 rebase)。

覆盖与结果

Lane 结果
(a) 武装面逐条对账 8 组 claims 全部 TRUE(file:line 级证据);守卫面穷举无遗漏;根因修在决策点(resolver/pump/gate),无旁路路径
(b) 转换通告逐条对账 8 组 claims 全部 TRUE(2 处措辞级 caveat);单一权威源比较 + 消费一次 + 无 await 窗口,数据流无漂移面
状态机对抗审查 0 blocker/major;粘滞学说(publish merge + re-record 双层)、NotStarted、未武装逐字节、子代理镜像、serde 兼容全部成立;新 .expect 有既有同型先例且受验证函数保护
门禁复现 9/9 精确一致:forkguard_ 260/0(单线程)、benchmark 6/0、preview 61/0(preview::tests:: 口径)、turn_metadata 24/0、change_mode 4/0、escalation 11/0、runtime_threads 184/0、fmt --check 干净、clippy -D warnings 恰为 5 条存量 lint(全部位于 PR diff 之外)
突变验证 5 条行为突变全部转红后逐字节还原:pump 删 !approval_force_prompt、suffix 回退 carrier 门控、NotStarted 注入姿态记录、publish None-keep 移除、子代理门回退 !auto_approve
走私审查 子仓 CLEAN:20 文件每个 hunk 均服务 (a)/(b) 或 body 已披露的伴随改动(from_fields 重构、clippy Copy、tripwire 注释、测试钉);零既有断言削弱(两处搬移的既有测试函数体逐字节比对一致;约 30 处单行改动均为签名/字段机械更新);6 提交 message 全部属实(from_fields 披露在 body 而 998ffe2 message 未提,已披露口径)
优雅性 通过严格设计评审:全程骑既有载体(Op::ChangeMode/LiveRuntimeAuthority/既有 approval_force_prompt 事件字段/PolicyNarrowingEvent 回执学说),无并行机制;层级正确(execpolicy 未动,engine 层持旗);字节钉符合 codewhale-hq#3947/codewhale-hq#5187 契约非过度钉
性能 全部新增路径 O(1);热路径无新增锁/await/堆分配;无逐帧/逐 chunk 钩子;未武装稳态开销 = 每轮一个 Copy 结构比较 + 两次 Option 存取;生产代码零新增 .clone();无界状态为零
父仓登记(codewhale-hq#809) guard --fast PASS(exit 0);EXPECTED_HEAD/gitlink=92 提交计数(rev-list 精确复算)/21 指纹(20 钉全部存在于 fork 树,零缺失零孤儿)/drift 526 files +94377−39438 /275 行为名/census 20/90 DCO 全部精确 TRUE;safety-modes.md 与底库行为逐条一致

总评:approve 方向。 四项评审要点全部通过:有用且完整、根因到位;优雅、无重复造轮;无走私;0 blocker / 0 major,性能无可测回归。

发现清单(均 MINOR;1–3 建议小修复波,其余可记录或 follow-up)

  1. wire 兼容测试缺口(负突变证实) — 删除 Op::ChangeMode.ask_on_guardrail 的 #[serde(default)] 后 codewhale-protocol 全套 92 测试仍全绿:协议 crate 无「遗留载荷缺字段可反序列化」钉(roundtrip fixture 用 Some(true) 构造,不咬反序列化;sync_session 在 op.rs:716-742 有同型先例)。建议补一条 legacy-payload 反序列化钉,把「serde 缺省缺席 = 行为不变」从属性正确升级为测试钉咬住。
  2. runtime_threads 的 approval.required JSON 面缺 approval_force_prompt — runtime_threads.rs:9503/9600 两处载荷只含 id/tool_name/description/intent_summary。宿主实答面(父 app)走 Event::ApprovalRequired(带旗标;forwarder.rs:847 → pending_approvals「force_prompt 永远弹卡」)不受影响,执法亦留在底库无旁路;但 pump 部署下消费该 JSON 的外部客户端无法把武装同意卡与普通审批区分开。建议两处载荷补该字段。
  3. Op::SyncSession 不重置 last_turn_meta_authority — 该字段唯一写点为 init(engine.rs:2427)与轮始记录(:6089),apply_synced_session(:1913-2058)重置 mode/history/workspace 但不含它与 session.ask_on_guardrail;进程内切换会话时新会话首轮可能渲染一条 "from" 姿态是新会话从未上报过的转换行(自愈于一轮)。建议 sync 时一并重置(可比照 tool 激活缓存的 process-local-but-conversation-local 学说,session.rs:177-185)。
  4. runtime_chat_isolated 轮记录但不渲染 — 块构建被 engine.rs:4637/:4718 抑制,而 :6089 记录无条件执行;块-less 轮可吞掉 pending transition(配置门控,潜伏)。
  5. 窄化 provenance 的转换误标 host_authority_change — 嵌入方 provenance(AgentMail/ImportedTranscript/MemoryRecall)在常驻自主姿态下会产出错误 host 归因的转换行;当前全部 in-tree 发送点传 ExternalUser/Runtime/SubAgentHandoff,今日不可达。可考虑独立 reason token。
  6. pump 部署武装 hold 无界悬挂(已披露,fail-safe 方向)— runtime_threads.rs:9606 决策等待仅由 cancel/interrupt/引擎死亡解除;建议 follow-up:超时或掉武装信号。
  7. 优雅性打磨 — (i)「武装且有人可答」谓词在 engine.rs:8712 / turn_loop.rs:4234 / :4343 三种拼法,建议依 permission_posture_allows_questions(authority.rs:403)先例提取共享助手;(ii) turn_loop.rs:3982 的 \|\| armed_full_access_hold 为逻辑死析取(被 registered_non_bypassable 蕴含),删除或加防御注释;(iii) escalation 两臂 ~15 行逐字重复(:4323-4336 vs :4362-4375),可折叠;(iv) 新行 Authority transition (host): … 与既有窄化行 Authority transition: …(engine.rs:4516)近碰撞、可同块共存,建议注释区分;(v) 6 提交横跨 3 个 author 身份,DCO 身份宜归一。
  8. 测试网备注 — 粘滞双钉分工覆盖不同安装路由(publish-merge 路由仅 forkguard_pending_arming_survives_arming_free_authority_publishes 咬住,另一钉守 install 路由)——覆盖互补非缺口;少数测试直接戳私有 detector 状态而非走 Op::ChangeMode 通道(refactor 敏感)。
  9. tripwire 注释(approval_routing/exec_agent)无测试伴随 — 已披露,可接受。

已知边界披露(内置 TUI 不可武装、provider 重启掉武装、edit-last-turn 截断丢回执、composer 不武装)逐条核实与 body 一致。突变豁免如实记录(渲染测不咬检测器为设计内,body 已写明)。

Pre-arming hosts emit ChangeMode without the ask_on_guardrail key at
all. The absent field must deserialize to None (= keep the session's
current arming) and an unarmed op must serialize without the key; a
serde(default) or skip_serializing_if regression would hard-require
the field on the wire and break every legacy sender. Found by a
negative mutation in the round-4 review: removing the attributes
shipped green across the whole protocol suite.

Signed-off-by: asto18089 <asto18089@users.noreply.github.com>
Two receipt-honesty gaps from the round-4 review:

- Op::SyncSession installs a conversation whose model never saw the
  previous conversation's receipts. The recorded turn_meta posture
  now resets with the rest of the conversation-boundary state, so the
  first turn establishes a fresh baseline instead of rendering a
  transition whose from-side it never reported. Arming itself is
  host-owned and deliberately survives the sync.
- runtime_chat_isolated builds no <turn_meta> block, so a posture
  recorded on an isolated turn was rendered nowhere and a pending
  transition could be consumed by a block-less turn. Isolated turns
  now neither consume nor record; the next block-building turn
  compares against the last posture a block actually reported.

Both faces pinned (forkguard_sync_session_clears_the_recorded_turn_meta_posture,
forkguard_an_isolated_turn_neither_consumes_nor_records_the_transition),
mutation-verified red.

Signed-off-by: asto18089 <asto18089@users.noreply.github.com>
The runtime-thread pump emitted approval.required without the
approval_force_prompt flag, so clients on pump deployments could not
render an armed consent card as anything but an ordinary approval
(enforcement stayed in-repo; this is the signal half). Both emission
paths now carry the flag, and the pump pin asserts the payload on
the forced (true) and ordinary (false) paths, mutation-verified
red.

Signed-off-by: asto18089 <asto18089@users.noreply.github.com>
guardrail_prompts_answerable replaces the three inline spellings of
"armed and a person can answer" (deny face, repo-law holds, sandbox
escalation), mirroring the permission_posture_allows_questions
precedent in authority.rs. The two sandbox-escalation arms fold into
one: they differed only in the armed marker, and the deny arm becomes
the explicit complement. The force_prompt computation drops the
armed_full_access_hold disjunct - the hold implies
registered_non_bypassable (both are gated on
registered_tool_forces_prompt), now documented at the site. The host
transition line's docs distinguish it from the narrowing receipt
family. No behavior change: the deny/repo-law/escalation pins stay
green, and mutating the helper turns the deny-face pin red.

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

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

修复波收官(round-4,4 提交 → b94cc3175)

对 COMMENT 5733839125 全部可修项落盘,body 已同步重写(评审史十提交、262/0、突变清单、修复披露):

评审发现 处置
1. wire 兼容测试缺口(负突变证实) 7e03162d1:协议 crate 补 change_mode_without_ask_on_guardrail_keeps_the_current_arming(缺键反序列化 = None + 未武装序列化不出键;serde 属性删除突变转红——此前全绿)
2. pump approval.required 缺 force_prompt 9b1e1cf13:两条载荷补 approval_force_prompt(additive);pump 钉扩展为 forced(true)/ordinary(false) 双路径载荷断言,突变转红
3. SyncSession 不重置姿态回执 5910426c5:sync 重置 last_turn_meta_authority(武装 host-owned 刻意保留);新钉 forkguard_sync_session_clears_the_recorded_turn_meta_posture 突变转红
4. runtime_chat_isolated 记录不渲染 同上提交:isolated 轮不记录不消费,下个建块轮与最后真实上报姿态比较;新钉 forkguard_an_isolated_turn_neither_consumes_nor_records_the_transition 突变转红
7. 谓词三拼/死析取/escalation 双臂/行族碰撞 b94cc3175:guardrail_prompts_answerable 共享助手(permission_posture_allows_questions 先例);escalation 臂合并(deny 臂为显式补集);删 || armed_full_access_hold 死析取(蕴含关系就地注释);转换行 doc 与窄化行族区分注释。无行为变化,deny/repo-law/escalation 钉全绿,谓词突变转红 deny-face 钉
5/6/8/9 + author 身份 记录为 follow-up/已披露/测试网备注;3 身份问题随 squash 合入消失,不改写历史

门禁 @ b94cc3175(全部实测):forkguard_ 262/0(单线程)+ benchmark 6/0 + preview 61/0 + turn_metadata 24/0 + change_mode 4/0 + escalation 11/0 + runtime_threads 184/0 + fmt 干净 + 协议 71/0;clippy 恰为 5 条存量 lint(全在 PR diff 外)。5 条新突变全红后逐字节还原。登记批随父仓 codewhale-hq#809 重钉:EXPECTED_HEAD→b94cc3175 / 96 提交 / 23 指纹 / census 22 / 头部文案 89→96 修正;fork-guard.sh --fast 绿。

A provenance-narrowed turn reported its reduced posture through the
same turn_meta comparison the host transition uses, so on a raised-
posture runtime thread every narrowed turn rendered an 'Authority
transition (host)' line that misattributed the per-turn reduction to
a host flip, and the next authoritative turn rendered the unwind.
The narrowing receipt family already reports the reduction: a
narrowed turn now emits no host line and leaves the standing
baseline untouched for the next authoritative turn to measure
against. SyncSession clears the baseline with the recorded posture.

Auto-Review was also standing auto authority that the narrowing
downgraded without a receipt; it now records the same structured
event the Bypass downgrade carries.

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

Copy link
Copy Markdown
Collaborator Author

Round-5 全新评审(11 道 lane,从零开始,不继承任何先前结论)

评审对象 b94cc3175a24d21180dfce0ff70512a0f5839a5e(10 提交)。Rebase 核对:merge-base == pinvou3-clean tip f1eb10efc(base 未动),rebase 为 no-op,头与远端一致。结论:方向与主体实现扎实——0 blocker、0 major;一项 round-4「不可达」判断被本轮证伪并已由修复波收敛(1785dae15,见下);两轮 lane 间互相矛盾的候选发现经复核均被驳回;登记数字全部精确。

11 道车道结果

  • (a) claims 两道(armed faces / UI·子代理·wire 兼容):18 项主张逐条对码全部 TRUE——forced hold 携带显式 approval_force_prompt(feat: LLM can start MCP servers from chat context codewhale-hq/Codewhale#3866 反转防御)、repo-law / typed-ask / sandbox escalation / execpolicy deny 面五武装面、Never/Auto-Review 全面恒拒(共享谓词 guardrail_prompts_answerable,六处武装位点逐一核对无旁路)、b94cc3175 谓词合并行为保持(死析取蕴含证明 + escalation 三臂→两臂扩展等价)、未武装逐字节等同(对 f1eb10efc 逐面比对)、serde 缺省缺席、pump 双载荷位点一致、composer 不武装、子代理门镜像。
  • (b) claims 一道(transition):10 项中 9 TRUE;1 PARTIAL——(b) 的「窄化 provenance 转换误标 host_authority_change 今日 in-tree 不可达」披露被证伪(见修复)。record/consume-once、折叠、preview 计划姿态哈希一致、sync 重置、isolated 不吞、NotStarted 早退、status 后缀只报真实翻转全部证实。
  • 状态机与并发一道:arming 全状态机枚举(唯一 session writer、publish 层 sticky-None、record 层 revision guard + backfill 不等式双保险)、批内乱序安全、幂等性、锁序(无新死锁面)全部成立。
  • 对抗 hunt / perf / 走私 / 优雅四道:全 4780 行 diff 逐 hunk 分类——走私 CLEAN(107 行删除全数对账,两项披露的删除属实,无未披露增删);perf 维持 negligible(全部新增为 armed/changed 门后的 O(1) 分支,未武装路径零新增 clone/锁);优雅面无 reinvention(复用既有审批管线与 carrier 链,fork 边界正确、注册完备)。
  • 门禁一道:10/10 精确复现——forkguard_ 262/0、benchmark 6/0、preview:: 61/0、turn_metadata 24/0、change_mode 4/0、escalation 11/0、runtime_threads:: 184/0(+2 ignored)、protocol 71/0、fmt 干净、clippy 恰 5 条既有(逐条比对 base 确认非本 PR 引入)。
  • 突变一道:8 探针 7 红;#[serde(default)] 单独删除不转红——对 Option 字段是 serde 无操作(body 措辞已如实更正;钉真正咬住的是 skip_serializing_if,M1b 验证转红)。pump 删守卫、sync 重置移除、双载荷位点单删、Auto 豁免削弱、consume 不清、子代理门回退、never-rollback backfill 置 None 全部 RED。
  • 登记一道:fork-guard.sh --fast PASS(96/23 指纹/census 22/277 名/94/96 签,逐项对树核实);JensenChen28 的两条 P3 文档项确认已修。

修复波 5(1785dae15,1 提交)

round-4 将「窄化 provenance 转换误标 host_authority_change」记录为 in-tree 不可达——本轮证伪:runtime 线程引擎经 AgentMail(及 ImportedTranscript/MemoryRecall/AssistantGenerated)输入的轮次逐轮窄化站立姿态(Full Access→Suggest),而转换比较以「上一块上报姿态」为 baseline,于是每次窄化轮误渲染 host 翻转、下一权威轮渲染反向行;Auto-Review 站姿下连窄化回执都缺失。修复:窄化轮不 emits host 行(窄化回执行族如实上报该轮)、不污染站姿 baseline(新字段 last_authoritative_turn_meta_posture,sync 同步清零);Auto→Suggest 降级补结构化窄化事件;preview 语义同步镜像。钉 2 条新测试 + 3 条突变全红后逐字节还原;forkguard_ 264/0,行为名 277→279。body 已同步:登记 25 条指纹 / EXPECTED_HEAD→1785dae15 / EXPECTED_COMMITS→97(父仓 codewhale-hq#809 重钉批已推送),serde 突变措辞更正,已知边界新增三条(存活子代理的 spawn 时武装快照、恢复后旧 armed 行残窗、pump 守卫未武装面例外——最后一条是 round-4 已披露缺口修复的显式 carve-out)。

复核驳回的候选发现(记录供后续评审参考)

  • 「pending arm 可被后台轮 backfill 擦除」:record_applied_runtime_authority 的 revision guard(revision == applied_revision || authority == applied)使携带 Some(true) 的 pending 不可被 Some(false) backfill 覆写——建议者漏读守卫条件,不成立。
  • 「app 池 spawn 不重放武装」:重放代码在父仓 engine_pool.rs 的 arm_spawned_session_safety_posture(持久化 SessionModeState → Op::ChangeMode{Some(...)}),fork 树内只见 None 属正常——主张按父仓代码核实为真。
  • 「pump 删守卫突变逃逸全部 claimed 门禁」:为陈旧测试二进制伪象(对同一突变独立复跑 forkguard_pump 过滤器即红,0 passed / 1 failed)。
  • 3 条 commit subject 超 50 字符:docs/commit-message-convention.md 为父仓文档、fork 树无此规约,且分支 96 个既有提交中 42 条已超——非本 PR 引入的违规,不做改写。

余留(全部已披露或超本 PR 范围)

pump 部署武装 hold 无界悬挂(follow-up:超时/掉武装信号);tripwire 注释无测试(已接受);父仓 JensenChen28 的 6 条 P2 为父应用侧行为,与本底库 PR 无涉。修复波后方向:approve。

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

Standards: no actionable finding in the cumulative foundation contract and latest provenance-baseline increment. Spec: live/preview suppression and the distinct standing baseline address false host attribution; Auto-Review narrowing now carries its receipt. Prior foundation source evidence through b94cc31 was reused; the new increment and provider-body/state test oracles were independently reviewed on both axes. Full diff whitespace check passed. No local Cargo/native execution, author test totals, or mutation results are claimed; previously disclosed host-availability and lifecycle boundaries remain.

The legacy text parser restarts its generated ids at 1 for every
response, so two adjacent text-encoded tool responses both presented
`xml_tool_1`. The turn loop carried that id verbatim into ToolUseState,
ToolCallStarted, and every approval event, while the approval receipt
log keys receipts by tool-call id and rejects an id that was asked more
than once: under decision gating, the second fallback call failed its
receipt commit and could never execute.

Rewrite each parsed id at the turn-loop ingestion point into a fresh
incarnation `<parsed>#<unix millis>x<counter>` backed by a process-
global AtomicU64 plus wall-clock millis, so ids stay unique across
responses and across engine restarts within the same session. The id is
opaque to every downstream consumer and keeps flowing through
ToolUseState, so everything downstream automatically uses the rewritten
value. Correct the parser's stale module doc, which claimed the engine
never invokes it.

Signed-off-by: asto18089 <asto18089@126.com>
Handle::approve_tool_call/deny_tool_call were fire-and-forget: they sent
ApprovalDecision into the channel and returned Ok(()) meaning only
"enqueued". When no turn was awaiting, the idle loop received decisions
and silently dropped every non-child id, and inside
await_tool_approval the durable receipt commit happened only for the
matching id — so a caller (the pinvou3-app approval surface) could
believe a decision was accepted while the engine never validated or
committed it.

Attach an ack oneshot to every ApprovalDecision variant. The handle
methods now await the ack under a bounded 15s timeout and return
Ok(()) only when the engine consumed the decision and durably committed
the outcome for the matching id (or a waiting child took it); Err
otherwise. await_tool_approval resolves the ack from the commit result
and nacks stale non-child ids instead of dropping them; the idle loop
nacks unroutable decisions the same way, mirroring the existing
child-approval oneshot style. The test-only mock handle resolves acks
in a small pump so mock-based suites keep their enqueue/observe
ordering.

Tests: the stale-decision and failed-commit cases now assert the
handle-level Err and that no receipt was written; the idle-loop child
test also covers the parent-shaped nack.

Signed-off-by: asto18089 <asto18089@126.com>
@asto18089
asto18089 force-pushed the feat/host-armed-guardrail-prompts branch 2 times, most recently from f7b5cfd to 47ada18 Compare November 2, 2026 16:07
@asto18089

Copy link
Copy Markdown
Collaborator Author

Round-6 integration-fix pair pushed on top of 1785dae15 (head 47ada186f), closing the two foundation findings from the pinvou-agent#809 review:

  • fd066bf2e — fresh text-fallback tool-call ids. The turn-loop ingestion now mints <parsed id>#<unix millis>x<atomic counter> so two adjacent valid XML fallback responses in one session no longer share the xml_tool_1 receipt identity (the receipt store had rejected the second distinct call before its event or execution). Uniqueness spans process restarts via the wall-clock component; replay reads stored ids and is unaffected. Behavioral regression injected_model_fallback_text_tool_ids_stay_unique_across_responses fails with the rewrite reverted and passes with it; protocol_recovery integration 10/0; the stale "engine no longer invokes this parser" module doc is corrected.
  • 47ada186f — acknowledged approval dispositions. ApprovalDecision carries an ack oneshot; approve_tool_call/deny_tool_call/retry_tool_with_policy return Ok only after the matching await_tool_approval arm durably commits the outcome (15s bounded backstop — normally the per-turn wait loop or the always-running idle loop resolves it immediately). Idle-loop non-child decisions nack explicitly instead of dropping silently, so an unrouted decision surfaces as an honest Err at the caller instead of a silently enqueued no-op. The mock engine handle gained a small ack pump so existing mock-based tests keep their send-then-observe shape.

Verification at 47ada186f: cargo test -p codewhale-tui --lib -- approval 276/0; cargo test -p codewhale-tui --test integration protocol_recovery 10/0; cargo test -p codewhale-core --lib tool_parser 8/0; clippy -D warnings clean with CI's allowed-lints set; cargo fmt --check clean. The registered foundation gate re-measured at this head: cargo test -p codewhale-tui --lib --locked forkguard_ -- --test-threads=1 → 264 passed / 0 failed. The full --lib suite shows the same pre-existing parallel-load flakes as the base commit (forkguard_model_operation_auxiliary_retry_keys fails on 1785dae15 identically) — untouched areas, not claimed as fixed.

The parent-side registration (pinvou-agent PR codewhale-hq#809 f51e600fd) re-pins gitlink/EXPECTED_HEAD here, counts 99 commits, and records the re-measured drift (528 files, +95646/−39437).

Re-requesting review.

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

复核固定 head eea741fcdd1d0eedeebea8cdec74e750a736cdb9,target/merge-base f1eb10efcfe2468fde42d1724d7f5f19d54b904c。使用子仓独立干净 detached checkout,完整审阅 fec1193→eea 的3文件增量及相关累计链,已读中间93c版本的本人审核5750549837,未重复发布该版本结论。

Standards

retained_user_messages 导入已移至包含其真实调用的测试模块,生产仍使用 origins 版本;这是静态修复核对,不声称 CI 已恢复。Summary shared helper 先用来源图、后用无来源机械位置,三个压缩入口共用政策。新增直接 helper 回归明确验证等长度/角色时4→3、无来源机械4→4;它构造 history/origins,证据范围比真实 provider/取消/编辑链窄,既有真实 builder 来源及普通 Summary wire 回归仍有独立价值。

保留非阻断 P3:legacy status_message 写入与 PruneOnly pre/post 实为 post/post 的测试精度问题。十二项 Fowler 启发式已考虑,无新阻断。

Spec

既有公开 Summary 历史关联 P2 在当前 head 源码关闭。可用 summary_origins 优先于等角色位置推断,B 能4→3;实际带 topology 的形状先按来源5→4,再移除旧载体并插入新载体,条目与实际 B 同为3,编辑 equality 可恢复 prior=A。丢失轮记录被移除,无来源机械路径保位、不可映射替换仍清空。

实际执行来源结构的 Python 模型断言,覆盖等角色有/无 topology、机械回退、丢失轮及不可映射清空;并核对准确源码分支、完整增量与回归 oracle。增量/累计 diff --check 通过。未运行 Cargo/Rust、原生/provider 取消或编辑 E2E,不借作者/同行计数,也不声称旧失败测试已经本机动态通过。无新确认 Spec 缺陷,故批准本次源码修订。

该闭合只属于 eea;父仓 codewhale-hq#809 此刻仍链接93c,不包含此修复。其他审核者、私密事项与 CI 验证保持原处置,不因本批准关闭或豁免。未改业务代码、提交修复、合并或入队。

两轴统计:Standards 保留2项P3;Spec 当前0项确认问题。

@asto18089
asto18089 requested a review from zhuowp November 3, 2026 11:19
@asto18089

Copy link
Copy Markdown
Collaborator Author

@/tmp/p131-closure.md

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

复核提交 eea741f(目标 f1eb10e)。

[P2] 原生 TUI 的会话级授权在读取和写入时使用不同的分组键。子任务现在会发出带有效工作区范围的键,TUI 用它做缓存查询,但打开审批弹窗时只传入精确键,弹窗从父会话上下文重新计算分组键。「本会话批准」随后保存的是这个重算的键。同一子工作区再次请求时仍会提示,保存的授权也偏离了用户审批的作用域。请把事件携带的分组键原样传到弹窗和授权写入,并用真实 TUI 审批往返测试覆盖同范围复用和跨范围隔离。

本轮已静态复核摘要来源重映射及手动、自动、应急三个压缩入口,未确认新增阻断。上述授权问题已通过生产调用链核实。提交本结论时,本地 Windows Rust 测试仍在运行,GitHub 尚未报告该提交的检查;合并资格未满足,本轮未评估发布。

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

补充复核固定 head eea741fcdd1d0eedeebea8cdec74e750a736cdb9,最新 base f1eb10efcfe2468fde42d1724d7f5f19d54b904c。已独立核对同行新报告的实际 TUI 消费链;现将本账号对该 head 的批准更新为请求修改。之前的 Summary 来源映射/重定位 P2 仍保持源码关闭,这是另一项会话授权问题。

Standards

[P2] 缓存查询与授权写入没有保持同一分组身份。tui/ui/event_loop.rs:3412–3417 用事件原始 approval_grouping_key 查询会话授权,但 3500–3508 创建审批弹窗仅传 approval_key,丢掉了事件分组键。tui/ui/overlays.rs:494–502 随后用父 app.workspace 构造请求,tui/approval.rs:184–186 重算分组键;它不等于子任务 tools/subagent/mod.rs:15152–15156 已绑定有效工作区的事件分组键。

Spec

正常触发:可提示姿态下,子任务发出需批准操作,用户在原生 TUI 选择“本会话批准”。tui/approval/view.rs:218 回传的是弹窗重算值,tui/ui/apply.rs:2578–2592 将其写入 approval_session_approved。同一子工作区再次发出相同操作时,读取方继续查事件携带的工作区分组键,匹配不到刚才写入的值,因而再次提示;保存的授权身份也偏离了请求携带的作用域。仅检查 key 生成与 ApprovalRequired 字段的既有测试覆盖不到这条真实弹窗往返。

请将事件分组键原样传到弹窗并用于授权写入,补真实 TUI 审批往返测试,验证同范围的操作族复用、不同范围隔离及 force_prompt 仍独立;精确拒绝键和 Summary 修复应保留。本次不扩展为未经验证的执行权限绕过结论。

实际验证:head/base 已 fetch,根及两轴独立阅读子任务生产者→事件路由→弹窗构造→决策事件→授权集合写入/下一次读取源码;未运行 Cargo、原生 UI、Windows、provider 或审批 E2E,同行仍在运行的测试不作本人证据。固定 head 源码未变化,既有 diff 检查结果仅在其原范围内保留。已读最新评论和线程,无需另发重复讨论回复;未改 PR 代码、触发 CI、合并或入队。

zhuowp review 5759493820 / JensenChen28 review 5759735821 (P2): the
session-grant read side queries `approval_session_approved` with the
engine-bound grouping key carried on the ApprovalRequired event, but
the approval dialog recomputed the grouping identity from the parent
workspace. An "approve for session" on a subagent request then stored
the recomputed identity: the next identical sub-workspace request
missed it and prompted again, and the grant lived under an identity
the user never saw.

Carry the event's grouping key into the dialog verbatim
(`push_approval_request_view` -> `ApprovalRequest::new_with_intent`
override) so the decision emission and the session-grant write answer
under the same identity the read side queries. Exact-deny keys, the
Summary origins fix, and force_prompt independence are unchanged.

Regression: a real card round-trip ('2' -> ApproveForSession) on a
workspace-bound subagent event asserts the emitted and stored identity
equal the event key, that the parent-recomputed identity is absent,
same-scope family reuse auto-approves, a different sub-workspace still
prompts, and a forced hold stays on the card. Red-checked by
reintroducing the parent recompute (the card answered
`shell:10:cargo test` instead of the bound identity) and green on the
fix; an earlier red attempt via a bare `None` was a compile error, not
a test failure, and was discarded.

Suites: `tui::approval` 114/0, `tui::ui::tests::approval` 7/0,
`tui::widgets::tests::approval` 18/0, `core::engine` 696/0; fmt clean.
Workspace clippy in the CI shape reports only the known pre-existing
base-lineage `collapsible_if` at `turn_loop.rs` (eviction-notes site,
present on merge-base f1eb10e; disclosed, not fixed here).

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

Copy link
Copy Markdown
Collaborator Author

Both reviews (zhuowp review 5759493820 and JensenChen28 review 5759735821) point at the same P2, and it is fixed on head 0583e19cb (child of eea741fcd, pushed after a tip-freshness check at eea741fcd).

Verified first. The production chain reproduces exactly as traced: the read side (resolve_ui_approval_disposition → is_session_approved_for_tool) queries approval_session_approved with the event's approval_grouping_key (workspace-bound by the subagent producer), while push_approval_request_view → ApprovalRequest::new_with_intent(..., &app.workspace) recomputed the identity from the parent workspace; ApprovalView::emit_decision_with_rules returned that recomputed value and apply_approval_decision stored it — so an "approve for session" on a subagent request landed under an identity the next identical sub-workspace request (and the read side) never queries.

Fix — carry, don't recompute. push_approval_request_view now takes the event's grouping key, and new_with_intent accepts an approval_grouping_key_override: Some uses the carried identity verbatim (the production dialog path); None keeps the workspace recompute only for flows that never ride an engine event (render tests and the #[cfg(test)] helper). The dialog, the emitted decision, and the session-grant write now answer under the same identity the read side queries — the requested "把事件携带的分组键原样传到弹窗和授权写入". The decision path (view.rs → handlers.rs → apply.rs) needed no change: it forwards request.approval_grouping_key verbatim. Exact-deny keys stay on the event-carried exact keys; the Summary origins fix is untouched; approval_force_prompt keeps riding its own field.

Regression — session_grant_round_trips_on_the_event_carried_grouping_key in tui/ui/tests.rs, the real card round-trip on a workspace-bound subagent event: pressing '2' commits ApproveForSession; the test asserts the emitted and stored identities equal the event-carried key, that the parent-recomputed identity is absent from the grant set, that the same-scope operation family auto-approves on repeat (no re-prompt), that a different sub-workspace still prompts (cross-scope isolation), and that a forced hold stays on the card (force independence through the same chain). Red-green: reintroducing the parent recompute makes the card answer shell:10:cargo test instead of the bound …@root:65:<sub-a> identity and the test fails at the carried-identity assertion; green on the fix. An earlier red attempt via a bare None was a compile error, not a test failure, and was discarded for an in-shape mutation.

Suites at 0583e19cb: tui::approval 114/0, tui::ui::tests::approval 7/0, tui::widgets::tests::approval 18/0, core::engine 696/0; cargo fmt --check clean. Workspace clippy in the CI shape reports only the known pre-existing base-lineage collapsible_if at turn_loop.rs (the eviction-notes site, present on merge-base f1eb10efc; disclosed, not fixed here). Local validation is macOS-only, as before.

Re-review requested from both.

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

批准固定 head 0583e19cb79e50e757c2e6b1638207960b9057ec,最新目标/merge-base pinvou3-clean@f1eb10efcfe2468fde42d1724d7f5f19d54b904c。完整审阅 eea741fc→0583e19c 六文件增量(175+/3−)及实际 TUI 消费链,未变范围继承此前固定源码审核。

Standards

上轮原生 TUI 会话授权分组键 P2 已在源码闭合。事件路由把原 approval_grouping_key 传到 overlay,构造请求以 Some(carried) 原样保存;view 决策、handler、grant writer 均继续透传该值,缓存查询与写入现在使用相同身份。全树调用点已核对,重算 fallback 的 None 仅用于测试辅助/渲染 fixture,生产事件路径显式使用 carried key。精确拒绝键、force_prompt 及先前 Summary 来源映射修复保持原结论;未发现新增确认阻断。

Spec

新增测试使用真实 ApprovalView 按键“2”,先核发出的 ApprovedForSession 与事件分组键一致,再调用生产授权 writer,验证保存原键、不保存父会话重算键、同范围再次请求 AutoApprove、不同子工作区仍 Prompt、force=true 仍 Prompt。测试的 writer 输入另外构造为预期键,虽已通过前一步相等断言连接逻辑,但没有驱动实际 handler/event_loop/子代理完整旅程;同操作族测试复用相同参数,也不能冒称已执行不同 flags 的变体。上述分段 oracle 对本次身份丢失修复有效,验证边界保留。

实际执行:最新 head/base fetch,完整增量和 producer→lookup→popup→decision→writer→lookup 源码及全部新签名调用点审阅;增量/累计 git diff --check 均通过,Signed-off-by 已核。未运行 Cargo、编译/原生、Windows、provider 或 TUI E2E,不采用作者114/7/18/696及负控数字;作者披露的 Clippy 基线归因未独立复验,不声明编译/CI 已全绿。旧非阻断提示写入与 PruneOnly 测试精度建议保留。不可取得的历史私密事项及其他审核者线程不代为关闭。此批准仅覆盖固定代码审核,其他审核者、CI和维护者独立决策;未改 PR 代码、触发 CI、合并、入队或开启 auto-merge。

@asto18089

Copy link
Copy Markdown
Collaborator Author

@/tmp/p131-gw.md

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

复核提交 0583e19,目标 f1eb10e。

上一轮原生 TUI 会话授权 P2 已在源码层面关闭。事件携带的工作区分组键现在从事件分发传入审批弹窗,随后原样用于会话授权写入。精确拒绝键与强制审批行为保持原契约。独立裁决核对了本轮 6 文件 17 处差异。新回归覆盖实际弹窗、决策写入及后续查找,包含同作用域复用、其他作用域隔离和强制审批。事件分发到弹窗的接线另以生产源码核对。

固定目标的 Git 合并树检查成功。本条记录已完成的源码复核。补充 Windows 验证:固定版本成功编译,授权弹窗往返、手动裁剪及摘要来源重映射三项独立定向测试通过。全量库测试仍有失败,已出现的 ACP 会话失败在上一个受审提交也出现,归因未完成。GitHub 未报告此提交的检查,合并和发布资格尚未建立。

@asto18089 asto18089 closed this Nov 4, 2026
@asto18089 asto18089 reopened this Nov 4, 2026
@JensenChen28

Copy link
Copy Markdown
Collaborator

当前 head 0583e19cb79e 的 link 检查失败于 “Require a closing keyword or an explicit opt-out”。已核对该任务实际日志:PR 正文既没有支持的关闭 issue 关键字,也没有 No-Issue: <原因> 行。请在正文关联真实对应 issue,或按项目规则补充有依据的 No-Issue 说明;无需为通过检查虚构关联。此跟进仅针对 PR 元数据,既有代码审核结论保持,未执行合并相关操作。

@asto18089

Copy link
Copy Markdown
Collaborator Author

On the test-failure note in review 5762102226: reproduced and classified locally at the same head 0583e19cb (macOS arm64, RUST_MIN_STACK=16MiB).

  • Full codewhale-tui lib standalone: 12327 passed / 0 failed / 13 ignored — fully green, including both ACP tests (acp_server::tests::tool_update_emits_typed_acp_image_content, acp_server::tests::session_list_and_load_reach_the_durable_codewhale_sessions) and runtime_api::tests::omitted_runtime_models_use_the_active_provider_default.
  • Workspace-wide --lib under full parallel load: exactly 2 failures — client::operation_tests::forkguard_model_operation_auxiliary_retry_keys (operation_tests.rs:379) and runtime_api::tests::omitted_runtime_models_use_the_active_provider_default. Both pass in isolation (2/2, 0.45s). These are the same two load-dependent timing flakes documented as pre-existing on the base lineage earlier in this PR's review cycle (base runs already showed them; omitted_runtime_models... now also passes standalone at this head). No ACP test fails anywhere locally, and 0583e19cb touches only the approval-dialog grouping-key path (six files under tui/), which has no intersection with ACP sessions.
  • So "the full suite still fails" matches the known base-lineage timing flakes — plus, on Windows, platform-local behavior not reproducible here. Your own note already observed the failures pre-date the reviewed commit; that is consistent with the above attribution.
  • CI on 0583e19cb is still pending on GitHub's side (no check runs reported yet); merge and release eligibility remain with CI and the maintainers.

@asto18089
asto18089 requested a review from zhuowp November 4, 2026 08:54

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

Approved at 0583e19c. The approval event's grouping identity is now carried through the native TUI dialog and session grant write, matching the cache lookup. The round-trip regression verifies same-scope reuse, cross-scope isolation, and forced prompting. The earlier exact-head Windows targeted tests passed for approval, manual pruning, and summary-origin remapping. The now-completed exact-head fork CI gate passed, including workspace nextest (14,915 passed, 15 skipped), and the later issue-link check passed. The prior Windows full-suite discrepancy was recorded separately and was not reproduced by this CI run.

@asto18089

Copy link
Copy Markdown
Collaborator Author

This follow-up's condition is already satisfied — closing the loop with the receipts:

  • The link failure this comment checked (run 126464997974, completed 08:10Z) turned green on the re-run (126467775093, completed 08:17:32Z).
  • The body now ends with the explicit opt-out the check accepts: No-Issue: issues are disabled on this repository; feature work tracked through the review rounds in this PR. The justification checks out — the repository has issues disabled (hasIssuesEnabled: false), so there is no real issue to link; the work is tracked through the review rounds here and in parent-repo PR pinvou-agent#809 for the register.

zhuowp's final approval already reflects the passing link check, and both reviewers hold approving reviews at head 0583e19cb (fork CI gate green, workspace nextest 14,915 passed / 15 skipped per that review). No fabricated linkage added, per your instruction.

@asto18089
asto18089 merged commit e2a0e14 into pinvou3-clean Nov 4, 2026
10 of 11 checks passed
asto18089 added a commit that referenced this pull request Nov 6, 2026
Base #131 changed the approval pump to auto_approve &&
!approval_force_prompt, so on a host that arms ask-on-guardrail
guardrails, guardrail-class calls force a real human approval even for
an auto_approve=true task. The "no approval prompts" absolute (and the
mode row skip parenthetical) now carry that exception, the mode row
discloses that trust_mode's effects still apply in plan (the retry path
has no mode re-check), and the pins assert the exception clauses and
forbid the unscoped absolute from returning.

Signed-off-by: asto18089 <asto18089@users.noreply.github.com>
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