Codex Handbook
core/src/guardian/tests.rs 3196 lines
use super::*;use crate::config::Config;use crate::config::ConfigOverrides;use crate::config::Constrained;use crate::config::ManagedFeatures;use crate::config::NetworkProxySpec;use crate::config::test_config;use crate::guardian::approval_request::guardian_request_target_item_id;use crate::session::session::Session;use crate::session::turn_context::TurnContext;use crate::test_support;use codex_analytics::GuardianApprovalRequestSource;use codex_config::ConfigLayerStack;use codex_config::FeatureRequirementsToml;use codex_config::NetworkConstraints;use codex_config::NetworkDomainPermissionToml;use codex_config::NetworkDomainPermissionsToml;use codex_config::RequirementSource;use codex_config::Sourced;use codex_config::config_toml::ConfigToml;use codex_config::types::McpServerConfig;use codex_exec_server::LOCAL_FS;use codex_features::Feature;use codex_model_provider::create_model_provider;use codex_model_provider_info::AMAZON_BEDROCK_GPT_5_4_MODEL_ID;use codex_model_provider_info::AMAZON_BEDROCK_PROVIDER_ID;use codex_model_provider_info::ModelProviderInfo;use codex_model_provider_info::OPENAI_PROVIDER_ID;use codex_models_manager::manager::StaticModelsManager;use codex_network_proxy::NetworkProxyConfig;use codex_protocol::ThreadId;use codex_protocol::approvals::NetworkApprovalProtocol;use codex_protocol::config_types::ApprovalsReviewer;use codex_protocol::models::ContentItem;use codex_protocol::models::PermissionProfile;use codex_protocol::models::ResponseItem;use codex_protocol::openai_models::ModelsResponse;use codex_protocol::openai_models::ReasoningEffort;use codex_protocol::permissions::FileSystemAccessMode;use codex_protocol::permissions::FileSystemPath;use codex_protocol::permissions::FileSystemSandboxEntry;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::protocol::AskForApproval;use codex_protocol::protocol::Event;use codex_protocol::protocol::EventMsg;use codex_protocol::protocol::GranularApprovalConfig;use codex_protocol::protocol::GuardianAssessmentStatus;use codex_protocol::protocol::GuardianRiskLevel;use codex_protocol::protocol::GuardianUserAuthorization;use codex_protocol::protocol::ReviewDecision;use codex_protocol::protocol::RolloutItem;use codex_protocol::protocol::TurnCompleteEvent;use core_test_support::PathBufExt;use core_test_support::TempDirExt;use core_test_support::context_snapshot;use core_test_support::context_snapshot::ContextSnapshotOptions;use core_test_support::responses::ev_assistant_message;use core_test_support::responses::ev_completed;use core_test_support::responses::ev_response_created;use core_test_support::responses::mount_response_sequence;use core_test_support::responses::mount_sse_once;use core_test_support::responses::mount_sse_sequence;use core_test_support::responses::sse;use core_test_support::responses::sse_failed;use core_test_support::responses::start_mock_server;use core_test_support::skip_if_no_network;use core_test_support::streaming_sse::StreamingSseChunk;use core_test_support::streaming_sse::start_streaming_sse_server;use core_test_support::test_path_buf;use insta::Settings;use insta::assert_snapshot;use pretty_assertions::assert_eq;use std::collections::BTreeMap;use std::collections::HashMap;use std::sync::Arc;use std::time::Duration;use tempfile::TempDir;use tokio_util::sync::CancellationToken;fn fixed_guardian_parent_session_id() -> ThreadId {    ThreadId::from_string("11111111-1111-4111-8111-111111111111")        .expect("fixed parent session id should be a valid UUID")}const GUARDIAN_MEMORY_CONTEXT_PROBE: &str = "guardian memory context probe";const GUARDIAN_SKILL_NAME: &str = "guardian-context-probe";const GUARDIAN_SKILL_BODY_PROBE: &str = "guardian skill body probe";// The memories extension depends on codex-core, so this probe verifies the nested Guardian config// at request assembly without introducing a circular test dependency.struct GuardianMemoryContextEnabled(bool);struct GuardianMemoryContextProbe;impl codex_extension_api::ThreadLifecycleContributor<Config> for GuardianMemoryContextProbe {    fn on_thread_start<'a>(        &'a self,        input: codex_extension_api::ThreadStartInput<'a, Config>,    ) -> codex_extension_api::ExtensionFuture<'a, ()> {        Box::pin(async move {            input.thread_store.insert(GuardianMemoryContextEnabled(                input.config.memories.use_memories,            ));        })    }}impl codex_extension_api::ContextContributor for GuardianMemoryContextProbe {    fn contribute<'a>(        &'a self,        _session_store: &'a codex_extension_api::ExtensionData,        thread_store: &'a codex_extension_api::ExtensionData,    ) -> codex_extension_api::ExtensionFuture<'a, Vec<codex_extension_api::PromptFragment>> {        Box::pin(async move {            if thread_store                .get::<GuardianMemoryContextEnabled>()                .is_some_and(|enabled| enabled.0)            {                vec![codex_extension_api::PromptFragment::developer_policy(                    GUARDIAN_MEMORY_CONTEXT_PROBE,                )]            } else {                Vec::new()            }        })    }}#[test]fn guardian_rejection_circuit_breaker_interrupts_after_three_consecutive_denials() {    let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::Continue    );    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::Continue    );    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::InterruptTurn {            consecutive_denials: 3,            recent_denials: 3,        }    );    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::Continue    );}#[test]fn guardian_rejection_circuit_breaker_resets_consecutive_denials_on_non_denial() {    let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::Continue    );    circuit_breaker.record_non_denial("turn-1");    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::Continue    );    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::Continue    );    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::InterruptTurn {            consecutive_denials: 3,            recent_denials: 4,        }    );}#[test]fn auto_review_rejection_circuit_breaker_interrupts_after_ten_recent_denials() {    let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();    for _ in 0..9 {        assert_eq!(            circuit_breaker.record_denial("turn-1"),            GuardianRejectionCircuitBreakerAction::Continue        );        circuit_breaker.record_non_denial("turn-1");    }    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::InterruptTurn {            consecutive_denials: 1,            recent_denials: 10,        }    );}#[test]fn auto_review_rejection_circuit_breaker_forgets_denials_outside_recent_review_window() {    let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();    for _ in 0..9 {        assert_eq!(            circuit_breaker.record_denial("turn-1"),            GuardianRejectionCircuitBreakerAction::Continue        );        circuit_breaker.record_non_denial("turn-1");    }    for _ in 0..(AUTO_REVIEW_DENIAL_WINDOW_SIZE - 18) {        circuit_breaker.record_non_denial("turn-1");    }    assert_eq!(        circuit_breaker.record_denial("turn-1"),        GuardianRejectionCircuitBreakerAction::Continue    );}async fn guardian_test_session_and_turn(    server: &wiremock::MockServer,) -> (Arc<Session>, Arc<TurnContext>) {    guardian_test_session_and_turn_with_base_url(server.uri().as_str()).await}async fn guardian_test_session_turn_and_rx(    server: &wiremock::MockServer,) -> (    Arc<Session>,    Arc<TurnContext>,    async_channel::Receiver<Event>,) {    let (mut session, mut turn, rx) =        crate::session::tests::make_session_and_context_with_rx().await;    Arc::get_mut(&mut session)        .expect("session should be uniquely owned")        .thread_id = fixed_guardian_parent_session_id();    let mut config = (*turn.config).clone();    config.model_provider.base_url = Some(format!("{}/v1", server.uri()));    let config = Arc::new(config);    let models_manager = test_support::models_manager_with_provider(        config.codex_home.to_path_buf(),        Arc::clone(&session.services.auth_manager),        config.model_provider.clone(),    );    Arc::get_mut(&mut session)        .expect("session should be uniquely owned")        .services        .models_manager = models_manager;    let turn_mut = Arc::get_mut(&mut turn).expect("turn should be uniquely owned");    turn_mut.config = Arc::clone(&config);    turn_mut.provider =        create_model_provider(config.model_provider.clone(), turn_mut.auth_manager.clone());    turn_mut.user_instructions = None;    (session, turn, rx)}fn guardian_shell_request(id: &str) -> GuardianApprovalRequest {    GuardianApprovalRequest::Shell {        id: id.to_string(),        command: vec!["git".to_string(), "push".to_string()],        cwd: test_path_buf("/repo/codex-rs/core").abs(),        sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,        additional_permissions: None,        justification: Some("Need to push the reviewed docs fix.".to_string()),    }}async fn guardian_test_session_and_turn_with_base_url(    base_url: &str,) -> (Arc<Session>, Arc<TurnContext>) {    let (mut session, mut turn) = crate::session::tests::make_session_and_context().await;    session.thread_id = fixed_guardian_parent_session_id();    let mut config = (*turn.config).clone();    config.model_provider.base_url = Some(format!("{base_url}/v1"));    let config = Arc::new(config);    let models_manager = test_support::models_manager_with_provider(        config.codex_home.to_path_buf(),        Arc::clone(&session.services.auth_manager),        config.model_provider.clone(),    );    session.services.models_manager = models_manager;    turn.config = Arc::clone(&config);    turn.provider = create_model_provider(config.model_provider.clone(), turn.auth_manager.clone());    turn.user_instructions = None;    (Arc::new(session), Arc::new(turn))}async fn seed_guardian_parent_history(session: &Arc<Session>, turn: &Arc<TurnContext>) {    session        .record_conversation_items(            turn.as_ref(),            &[                ResponseItem::Message {                    id: None,                    role: "user".to_string(),                    content: vec![ContentItem::InputText {                        text: "Please check the repo visibility and push the docs fix if needed."                            .to_string(),                    }],                    phase: None,                    metadata: None,                },                ResponseItem::FunctionCall {                    id: None,                    name: "gh_repo_view".to_string(),                    namespace: None,                    arguments: "{\"repo\":\"openai/codex\"}".to_string(),                    call_id: "call-1".to_string(),                    metadata: None,                },                ResponseItem::FunctionCallOutput {                    call_id: "call-1".to_string(),                    output: codex_protocol::models::FunctionCallOutputPayload::from_text(                        "repo visibility: public".to_string(),                    ),                    metadata: None,                },                ResponseItem::Message {                    id: None,                    role: "assistant".to_string(),                    content: vec![ContentItem::OutputText {                        text: "The repo is public; I now need approval to push the docs fix."                            .to_string(),                    }],                    phase: None,                    metadata: None,                },            ],        )        .await;}fn rollout_item_contains_message_text(item: &RolloutItem, needle: &str) -> bool {    let RolloutItem::ResponseItem(response_item) = item else {        return false;    };    response_item_contains_message_text(response_item, needle)}fn response_item_contains_message_text(item: &ResponseItem, needle: &str) -> bool {    let ResponseItem::Message { content, .. } = item else {        return false;    };    content.iter().any(|item| match item {        ContentItem::InputText { text } | ContentItem::OutputText { text } => text.contains(needle),        ContentItem::InputImage { .. } => false,    })}fn guardian_snapshot_options() -> ContextSnapshotOptions {    ContextSnapshotOptions::default()        .strip_capability_instructions()        .strip_agents_md_user_context()}fn normalize_guardian_snapshot_paths(text: String) -> String {    let mut text = text;    for canonical_path in ["/repo/codex-rs/core", "/repo"] {        let platform_path = test_path_buf(canonical_path).display().to_string();        if platform_path == canonical_path {            continue;        }        let escaped_platform_path = serde_json::to_string(&platform_path)            .expect("test path should serialize")            .trim_matches('"')            .to_string();        text = text            .replace(&escaped_platform_path, canonical_path)            .replace(&platform_path, canonical_path);    }    text}fn guardian_prompt_text(items: &[codex_protocol::user_input::UserInput]) -> String {    items        .iter()        .map(|item| match item {            codex_protocol::user_input::UserInput::Text { text, .. } => text.as_str(),            _ => "",        })        .collect::<String>()}fn last_user_message_text_from_body(body: &serde_json::Value) -> String {    body["input"]        .as_array()        .expect("request input array")        .iter()        .filter(|item| item.get("role").and_then(serde_json::Value::as_str) == Some("user"))        .filter_map(|item| item.get("content").and_then(serde_json::Value::as_array))        .next_back()        .expect("user message content")        .iter()        .filter(|span| span.get("type").and_then(serde_json::Value::as_str) == Some("input_text"))        .filter_map(|span| span.get("text").and_then(serde_json::Value::as_str))        .collect::<String>()}#[test]fn build_guardian_transcript_keeps_original_numbering() {    let entries = [        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::User,            text: "first".to_string(),        },        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Assistant,            text: "second".to_string(),        },        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Assistant,            text: "third".to_string(),        },    ];    let (transcript, omission) = render_guardian_transcript_entries(&entries[..2]);    assert_eq!(        transcript,        vec![            "[1] user: first".to_string(),            "[2] assistant: second".to_string()        ]    );    assert!(omission.is_none());}#[tokio::test(flavor = "current_thread")]async fn build_guardian_prompt_full_mode_preserves_initial_review_format() -> anyhow::Result<()> {    let (session, turn) = guardian_test_session_and_turn_with_base_url("http://localhost").await;    seed_guardian_parent_history(&session, &turn).await;    let prompt = build_guardian_prompt_items(        session.as_ref(),        Some("Sandbox denied outbound git push to github.com.".to_string()),        GuardianApprovalRequest::Shell {            id: "shell-1".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Need to push the reviewed docs fix.".to_string()),        },        GuardianPromptMode::Full,    )    .await?;    let text = guardian_prompt_text(&prompt.items);    assert!(text.contains("whose request action you are assessing"));    assert!(text.contains(">>> TRANSCRIPT START\n"));    assert!(text.contains(">>> TRANSCRIPT END\n"));    assert!(text.contains("The Codex agent has requested the following action:\n"));    assert!(!text.contains("TRANSCRIPT DELTA"));    assert_eq!(prompt.transcript_cursor.transcript_entry_count, 4);    Ok(())}#[tokio::test(flavor = "current_thread")]async fn build_guardian_prompt_includes_parent_turn_denied_reads() -> anyhow::Result<()> {    let (mut session, mut turn) = crate::session::tests::make_session_and_context().await;    session.thread_id = fixed_guardian_parent_session_id();    let denied_root = test_path_buf("/repo/private").abs();    let denied_glob = test_path_buf("/repo/private/**").display().to_string();    turn.permission_profile = PermissionProfile::from_runtime_permissions(        &FileSystemSandboxPolicy::restricted(vec![            FileSystemSandboxEntry {                path: FileSystemPath::Special {                    value: codex_protocol::permissions::FileSystemSpecialPath::Root,                },                access: FileSystemAccessMode::Read,            },            FileSystemSandboxEntry {                path: FileSystemPath::Path {                    path: denied_root.clone(),                },                access: FileSystemAccessMode::Deny,            },            FileSystemSandboxEntry {                path: FileSystemPath::GlobPattern {                    pattern: denied_glob.clone(),                },                access: FileSystemAccessMode::Deny,            },        ]),        NetworkSandboxPolicy::Restricted,    );    let session = Arc::new(session);    let turn = Arc::new(turn);    seed_guardian_parent_history(&session, &turn).await;    let prompt = build_guardian_prompt_items_with_parent_turn(        session.as_ref(),        Some(turn.as_ref()),        Some("Sandbox denied reading /repo/private/secret.txt.".to_string()),        GuardianApprovalRequest::Shell {            id: "shell-1".to_string(),            command: vec!["cat".to_string(), "/repo/private/secret.txt".to_string()],            cwd: test_path_buf("/repo").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::RequireEscalated,            additional_permissions: None,            justification: Some("Need to inspect the secret file.".to_string()),        },        GuardianPromptMode::Full,    )    .await?;    let text = guardian_prompt_text(&prompt.items);    assert!(text.contains("PARENT TURN PERMISSION CONTEXT START"));    assert!(text.contains("do not approve escalation whose purpose is to read them"));    assert!(text.contains(denied_root.to_string_lossy().as_ref()));    assert!(text.contains(&format!("glob `{denied_glob}`")));    Ok(())}#[tokio::test(flavor = "current_thread")]async fn build_guardian_prompt_delta_mode_preserves_original_numbering() -> anyhow::Result<()> {    let (session, turn) = guardian_test_session_and_turn_with_base_url("http://localhost").await;    seed_guardian_parent_history(&session, &turn).await;    session        .record_conversation_items(            turn.as_ref(),            &[                ResponseItem::Message {                    id: None,                    role: "user".to_string(),                    content: vec![ContentItem::InputText {                        text: "Please also push the second docs fix.".to_string(),                    }],                    phase: None,                    metadata: None,                },                ResponseItem::Message {                    id: None,                    role: "assistant".to_string(),                    content: vec![ContentItem::OutputText {                        text: "I need approval for the second push.".to_string(),                    }],                    phase: None,                    metadata: None,                },            ],        )        .await;    let prompt = build_guardian_prompt_items(        session.as_ref(),        /*retry_reason*/ None,        GuardianApprovalRequest::Shell {            id: "shell-2".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Need to push the second docs fix.".to_string()),        },        GuardianPromptMode::Delta {            cursor: GuardianTranscriptCursor {                parent_history_version: 0,                transcript_entry_count: 4,            },        },    )    .await?;    let text = guardian_prompt_text(&prompt.items);    assert!(text.contains("added since your last approval assessment"));    assert!(text.contains(">>> TRANSCRIPT DELTA START\n"));    assert!(text.contains("[5] user: Please also push the second docs fix."));    assert!(text.contains("[6] assistant: I need approval for the second push."));    assert!(text.contains(">>> TRANSCRIPT DELTA END\n"));    assert!(text.contains("The Codex agent has requested the following next action:\n"));    assert!(!text.contains("[1] user: Please check the repo visibility"));    assert_eq!(prompt.transcript_cursor.transcript_entry_count, 6);    Ok(())}#[tokio::test(flavor = "current_thread")]async fn build_guardian_prompt_delta_mode_handles_empty_delta() -> anyhow::Result<()> {    let (session, turn) = guardian_test_session_and_turn_with_base_url("http://localhost").await;    seed_guardian_parent_history(&session, &turn).await;    let prompt = build_guardian_prompt_items(        session.as_ref(),        /*retry_reason*/ None,        GuardianApprovalRequest::Shell {            id: "shell-2".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Need to push the second docs fix.".to_string()),        },        GuardianPromptMode::Delta {            cursor: GuardianTranscriptCursor {                parent_history_version: 0,                transcript_entry_count: 4,            },        },    )    .await?;    let text = guardian_prompt_text(&prompt.items);    assert!(text.contains(">>> TRANSCRIPT DELTA START\n"));    assert!(text.contains("<no retained transcript delta entries>"));    assert!(text.contains(">>> TRANSCRIPT DELTA END\n"));    assert_eq!(prompt.transcript_cursor.transcript_entry_count, 4);    Ok(())}#[tokio::test(flavor = "current_thread")]async fn build_guardian_prompt_stale_delta_cursor_falls_back_to_full_prompt() -> anyhow::Result<()>{    let (session, turn) = guardian_test_session_and_turn_with_base_url("http://localhost").await;    seed_guardian_parent_history(&session, &turn).await;    let prompt = build_guardian_prompt_items(        session.as_ref(),        /*retry_reason*/ None,        GuardianApprovalRequest::Shell {            id: "shell-3".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Need to push the docs fix.".to_string()),        },        GuardianPromptMode::Delta {            cursor: GuardianTranscriptCursor {                parent_history_version: 0,                transcript_entry_count: 99,            },        },    )    .await?;    let text = guardian_prompt_text(&prompt.items);    assert!(text.contains("whose request action you are assessing"));    assert!(text.contains(">>> TRANSCRIPT START\n"));    assert!(!text.contains("TRANSCRIPT DELTA"));    assert_eq!(prompt.transcript_cursor.transcript_entry_count, 4);    Ok(())}#[tokio::test(flavor = "current_thread")]async fn build_guardian_prompt_stale_delta_version_falls_back_to_full_prompt() -> anyhow::Result<()>{    let (session, turn) = guardian_test_session_and_turn_with_base_url("http://localhost").await;    seed_guardian_parent_history(&session, &turn).await;    session        .replace_history(            vec![                ResponseItem::Message {                    id: None,                    role: "user".to_string(),                    content: vec![ContentItem::InputText {                        text: "Compacted retained user request.".to_string(),                    }],                    phase: None,                    metadata: None,                },                ResponseItem::Message {                    id: None,                    role: "assistant".to_string(),                    content: vec![ContentItem::OutputText {                        text: "Compacted summary of earlier guardian context.".to_string(),                    }],                    phase: None,                    metadata: None,                },            ],            /*reference_context_item*/ None,        )        .await;    session        .record_conversation_items(            turn.as_ref(),            &[                ResponseItem::Message {                    id: None,                    role: "user".to_string(),                    content: vec![ContentItem::InputText {                        text: "Please push after the compaction.".to_string(),                    }],                    phase: None,                    metadata: None,                },                ResponseItem::Message {                    id: None,                    role: "assistant".to_string(),                    content: vec![ContentItem::OutputText {                        text: "I need approval for the post-compaction push.".to_string(),                    }],                    phase: None,                    metadata: None,                },            ],        )        .await;    let prompt = build_guardian_prompt_items(        session.as_ref(),        /*retry_reason*/ None,        GuardianApprovalRequest::Shell {            id: "shell-4".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Need to push after the compaction.".to_string()),        },        GuardianPromptMode::Delta {            cursor: GuardianTranscriptCursor {                parent_history_version: 0,                transcript_entry_count: 4,            },        },    )    .await?;    let text = guardian_prompt_text(&prompt.items);    assert!(text.contains("whose request action you are assessing"));    assert!(text.contains(">>> TRANSCRIPT START\n"));    assert!(!text.contains("TRANSCRIPT DELTA"));    assert!(text.contains("[3] user: Please push after the compaction."));    assert!(text.contains("[4] assistant: I need approval for the post-compaction push."));    assert_eq!(prompt.transcript_cursor.parent_history_version, 1);    assert_eq!(prompt.transcript_cursor.transcript_entry_count, 4);    Ok(())}#[test]fn collect_guardian_transcript_entries_skips_contextual_user_messages() {    let items = vec![        ResponseItem::Message {            id: None,            role: "user".to_string(),            content: vec![ContentItem::InputText {                text: "<environment_context>\n<cwd>/tmp</cwd>\n</environment_context>".to_string(),            }],            phase: None,            metadata: None,        },        ResponseItem::Message {            id: None,            role: "assistant".to_string(),            content: vec![ContentItem::OutputText {                text: "hello".to_string(),            }],            phase: None,            metadata: None,        },    ];    let entries = collect_guardian_transcript_entries(&items);    assert_eq!(entries.len(), 1);    assert_eq!(        entries[0],        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Assistant,            text: "hello".to_string(),        }    );}#[test]fn collect_guardian_transcript_entries_keeps_manual_approval_developer_message() {    let approval_text =        format!("{AUTO_REVIEW_DENIED_ACTION_APPROVAL_DEVELOPER_PREFIX}\n\nApproved action:\n{{}}");    let items = vec![        ResponseItem::Message {            id: None,            role: "developer".to_string(),            content: vec![ContentItem::InputText {                text: "ordinary developer context".to_string(),            }],            phase: None,            metadata: None,        },        ResponseItem::Message {            id: None,            role: "developer".to_string(),            content: vec![ContentItem::InputText {                text: approval_text.clone(),            }],            phase: None,            metadata: None,        },    ];    let entries = collect_guardian_transcript_entries(&items);    assert_eq!(        entries,        vec![GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Developer,            text: approval_text,        }]    );}#[test]fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() {    let items = vec![        ResponseItem::Message {            id: None,            role: "user".to_string(),            content: vec![ContentItem::InputText {                text: "check the repo".to_string(),            }],            phase: None,            metadata: None,        },        ResponseItem::FunctionCall {            id: None,            name: "read_file".to_string(),            namespace: None,            arguments: "{\"path\":\"README.md\"}".to_string(),            call_id: "call-1".to_string(),            metadata: None,        },        ResponseItem::FunctionCallOutput {            call_id: "call-1".to_string(),            output: codex_protocol::models::FunctionCallOutputPayload::from_text(                "repo is public".to_string(),            ),            metadata: None,        },        ResponseItem::Message {            id: None,            role: "assistant".to_string(),            content: vec![ContentItem::OutputText {                text: "I need to push a fix".to_string(),            }],            phase: None,            metadata: None,        },    ];    let entries = collect_guardian_transcript_entries(&items);    assert_eq!(entries.len(), 4);    assert_eq!(        entries[1],        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Tool("tool read_file call".to_string()),            text: "{\"path\":\"README.md\"}".to_string(),        }    );    assert_eq!(        entries[2],        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Tool("tool read_file result".to_string()),            text: "repo is public".to_string(),        }    );}#[test]fn guardian_truncate_text_keeps_prefix_suffix_and_xml_marker() {    let content = "prefix ".repeat(200) + &" suffix".repeat(200);    let (truncated, was_truncated) = guardian_truncate_text(&content, /*token_cap*/ 20);    assert!(truncated.starts_with("prefix"));    assert!(truncated.contains("<truncated omitted_approx_tokens=\""));    assert!(truncated.ends_with("suffix"));    assert!(was_truncated);}#[test]fn format_guardian_action_pretty_truncates_large_string_fields() -> serde_json::Result<()> {    let patch = "line\n".repeat(100_000);    let action = GuardianApprovalRequest::ApplyPatch {        id: "patch-1".to_string(),        cwd: test_path_buf("/tmp").abs(),        files: Vec::new(),        patch: patch.clone(),    };    let rendered = format_guardian_action_pretty(&action)?;    assert!(rendered.text.contains("\"tool\": \"apply_patch\""));    assert!(rendered.text.contains("<truncated omitted_approx_tokens="));    assert!(rendered.text.len() < patch.len());    assert!(rendered.truncated);    Ok(())}#[test]fn format_guardian_action_pretty_reports_no_truncation_for_small_payload() -> serde_json::Result<()>{    let action = GuardianApprovalRequest::ApplyPatch {        id: "patch-1".to_string(),        cwd: test_path_buf("/tmp").abs(),        files: Vec::new(),        patch: "line\n".to_string(),    };    let rendered = format_guardian_action_pretty(&action)?;    assert!(rendered.text.contains("\"tool\": \"apply_patch\""));    assert!(!rendered.truncated);    Ok(())}#[test]fn guardian_approval_request_to_json_renders_mcp_tool_call_shape() -> serde_json::Result<()> {    let action = GuardianApprovalRequest::McpToolCall {        id: "call-1".to_string(),        server: "mcp_server".to_string(),        tool_name: "browser_navigate".to_string(),        arguments: Some(serde_json::json!({            "url": "https://example.com",        })),        connector_id: None,        connector_name: Some("Playwright".to_string()),        connector_description: None,        tool_title: Some("Navigate".to_string()),        tool_description: None,        annotations: Some(GuardianMcpAnnotations {            destructive_hint: Some(true),            open_world_hint: None,            read_only_hint: Some(false),        }),    };    assert_eq!(        guardian_approval_request_to_json(&action)?,        serde_json::json!({            "tool": "mcp_tool_call",            "server": "mcp_server",            "tool_name": "browser_navigate",            "arguments": {                "url": "https://example.com",            },            "connector_name": "Playwright",            "tool_title": "Navigate",            "annotations": {                "destructive_hint": true,                "read_only_hint": false,            },        })    );    Ok(())}#[test]fn guardian_approval_request_to_json_renders_network_access_trigger() -> serde_json::Result<()> {    let cwd = test_path_buf("/repo").abs();    let action = GuardianApprovalRequest::NetworkAccess {        id: "network-1".to_string(),        turn_id: "turn-1".to_string(),        target: "https://example.com:443".to_string(),        host: "example.com".to_string(),        protocol: NetworkApprovalProtocol::Https,        port: 443,        trigger: Some(GuardianNetworkAccessTrigger {            call_id: "call-1".to_string(),            tool_name: "shell".to_string(),            command: vec!["curl".to_string(), "https://example.com".to_string()],            cwd: cwd.clone(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Fetch the release metadata.".to_string()),            tty: None,        }),    };    assert_eq!(        guardian_approval_request_to_json(&action)?,        serde_json::json!({            "tool": "network_access",            "target": "https://example.com:443",            "host": "example.com",            "protocol": "https",            "port": 443,            "trigger": {                "callId": "call-1",                "toolName": "shell",                "command": ["curl", "https://example.com"],                "cwd": cwd.to_string_lossy().to_string(),                "sandboxPermissions": "use_default",                "justification": "Fetch the release metadata.",            },        })    );    Ok(())}#[tokio::test(flavor = "current_thread")]async fn build_guardian_prompt_items_explains_network_access_review_scope() -> anyhow::Result<()> {    let (session, turn) = guardian_test_session_and_turn_with_base_url("http://localhost").await;    seed_guardian_parent_history(&session, &turn).await;    let cwd = test_path_buf("/repo").abs();    let prompt = build_guardian_prompt_items(        session.as_ref(),        Some("Network access to \"example.com\" is blocked by policy.".to_string()),        GuardianApprovalRequest::NetworkAccess {            id: "network-1".to_string(),            turn_id: "turn-1".to_string(),            target: "https://example.com:443".to_string(),            host: "example.com".to_string(),            protocol: NetworkApprovalProtocol::Https,            port: 443,            trigger: Some(GuardianNetworkAccessTrigger {                call_id: "call-1".to_string(),                tool_name: "shell".to_string(),                command: vec!["curl".to_string(), "https://example.com".to_string()],                cwd,                sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,                additional_permissions: None,                justification: Some("Fetch the release metadata.".to_string()),                tty: None,            }),        },        GuardianPromptMode::Full,    )    .await?;    let text = guardian_prompt_text(&prompt.items);    assert!(text.contains("Below is a proposed network access request under review."));    assert!(!text.contains("Network approval context:"));    assert!(        !text.contains(            "This approval request is about network access to the target in the network access JSON below"        )    );    assert!(        text.contains(            "When assessing this request, focus primarily on whether the triggering command is authorised by the user and whether it is within the rules."        )    );    assert!(        text.contains(            "The user does not need to have explicitly authorised this exact network connection, as long as the network access is a reasonable consequence of the triggering command."        )    );    assert!(text.contains("\"trigger\""));    assert!(text.contains("Network access JSON:"));    assert!(!text.contains("The Codex agent has requested the following action:"));    assert!(!text.contains("Planned action JSON:"));    assert!(!text.contains("Retry reason:"));    assert!(!text.contains("Network access to \"example.com\" is blocked by policy."));    let mut settings = Settings::clone_current();    settings.set_snapshot_path("snapshots");    settings.set_prepend_module_to_snapshot(false);    settings.bind(|| {        assert_snapshot!(            "codex_core__guardian__tests__network_access_guardian_prompt_layout",            normalize_guardian_snapshot_paths(text)        );    });    Ok(())}#[test]fn guardian_assessment_action_redacts_apply_patch_patch_text() {    let cwd = test_path_buf("/tmp").abs();    let file = test_path_buf("/tmp/guardian.txt").abs();    let action = GuardianApprovalRequest::ApplyPatch {        id: "patch-1".to_string(),        cwd: cwd.clone(),        files: vec![file.clone()],        patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+secret\n*** End Patch"            .to_string(),    };    assert_eq!(        serde_json::to_value(guardian_assessment_action(&action)).expect("serialize action"),        serde_json::json!({            "type": "apply_patch",            "cwd": cwd,            "files": [file],        }),    );}#[test]fn guardian_request_turn_id_prefers_network_access_owner_turn() {    let network_access = GuardianApprovalRequest::NetworkAccess {        id: "network-1".to_string(),        turn_id: "owner-turn".to_string(),        target: "https://example.com:443".to_string(),        host: "example.com".to_string(),        protocol: NetworkApprovalProtocol::Https,        port: 443,        trigger: None,    };    let apply_patch = GuardianApprovalRequest::ApplyPatch {        id: "patch-1".to_string(),        cwd: test_path_buf("/tmp").abs(),        files: vec![test_path_buf("/tmp/guardian.txt").abs()],        patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"            .to_string(),    };    assert_eq!(        guardian_request_turn_id(&network_access, "fallback-turn"),        "owner-turn"    );    assert_eq!(        guardian_request_turn_id(&apply_patch, "fallback-turn"),        "fallback-turn"    );}#[test]fn guardian_request_target_item_id_omits_network_access_trigger_call_id() {    let network_access = GuardianApprovalRequest::NetworkAccess {        id: "network-1".to_string(),        turn_id: "owner-turn".to_string(),        target: "https://example.com:443".to_string(),        host: "example.com".to_string(),        protocol: NetworkApprovalProtocol::Https,        port: 443,        trigger: Some(GuardianNetworkAccessTrigger {            call_id: "call-1".to_string(),            tool_name: "shell".to_string(),            command: vec!["curl".to_string(), "https://example.com".to_string()],            cwd: test_path_buf("/repo").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: None,            tty: None,        }),    };    assert_eq!(guardian_request_target_item_id(&network_access), None);}#[tokio::test]async fn cancelled_guardian_review_emits_terminal_abort_without_warning() {    let (session, turn, rx) = crate::session::tests::make_session_and_context_with_rx().await;    let cancel_token = CancellationToken::new();    cancel_token.cancel();    let decision = review_approval_request_with_cancel(        &session,        &turn,        "review-cancelled-guardian".to_string(),        GuardianApprovalRequest::ApplyPatch {            id: "patch-1".to_string(),            cwd: test_path_buf("/tmp").abs(),            files: vec![test_path_buf("/tmp/guardian.txt").abs()],            patch: "*** Begin Patch\n*** Update File: guardian.txt\n@@\n+hello\n*** End Patch"                .to_string(),        },        /*retry_reason*/ None,        GuardianApprovalRequestSource::MainTurn,        cancel_token,    )    .await;    assert_eq!(decision, ReviewDecision::Abort);    let mut guardian_statuses = Vec::new();    let mut warnings = Vec::new();    while let Ok(event) = rx.try_recv() {        match event.msg {            EventMsg::GuardianAssessment(event) => guardian_statuses.push(event.status),            EventMsg::GuardianWarning(event) => warnings.push(event.message),            _ => {}        }    }    assert_eq!(        guardian_statuses,        vec![            GuardianAssessmentStatus::InProgress,            GuardianAssessmentStatus::Aborted,        ]    );    assert!(warnings.is_empty());}#[test]fn guardian_timeout_message_distinguishes_timeout_from_policy_denial() {    let message = guardian_timeout_message();    assert!(message.contains("did not finish before its deadline"));    assert!(message.contains("retry once"));    assert!(!message.contains("unacceptable risk"));}#[tokio::test]async fn routes_approval_to_guardian_requires_guardian_reviewer() {    let (_session, mut turn) = crate::session::tests::make_session_and_context().await;    let mut config = (*turn.config).clone();    config.approvals_reviewer = ApprovalsReviewer::User;    turn.config = Arc::new(config.clone());    assert!(!routes_approval_to_guardian(&turn));    config.approvals_reviewer = ApprovalsReviewer::AutoReview;    turn.config = Arc::new(config);    assert!(routes_approval_to_guardian(&turn));}#[tokio::test]async fn routes_approval_to_guardian_can_use_app_reviewer_override() {    let (_session, turn) = crate::session::tests::make_session_and_context().await;    assert!(!routes_approval_to_guardian_with_reviewer(        &turn,        ApprovalsReviewer::User    ));    assert!(routes_approval_to_guardian_with_reviewer(        &turn,        ApprovalsReviewer::AutoReview    ));}#[tokio::test]async fn routes_approval_to_guardian_allows_granular_review_policy() {    let (_session, mut turn) = crate::session::tests::make_session_and_context().await;    let mut config = (*turn.config).clone();    config.approvals_reviewer = ApprovalsReviewer::AutoReview;    turn.config = Arc::new(config);    turn.approval_policy        .set(AskForApproval::Granular(GranularApprovalConfig {            sandbox_approval: true,            rules: true,            skill_approval: true,            request_permissions: true,            mcp_elicitations: true,        }))        .expect("test setup should allow updating approval policy");    assert!(routes_approval_to_guardian(&turn));}#[test]fn build_guardian_transcript_reserves_separate_budget_for_tool_evidence() {    let repeated = "signal ".repeat(8_000);    let mut entries = vec![        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::User,            text: "please figure out if the repo is public".to_string(),        },        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Assistant,            text: "The public repo check is the main reason I want to escalate.".to_string(),        },    ];    entries.extend((0..12).map(|index| GuardianTranscriptEntry {        kind: GuardianTranscriptEntryKind::Tool(format!("tool call {index}")),        text: repeated.clone(),    }));    let (transcript, omission) = render_guardian_transcript_entries(&entries);    assert!(        transcript            .iter()            .any(|entry| entry == "[1] user: please figure out if the repo is public")    );    assert!(transcript.iter().any(|entry| {        entry == "[2] assistant: The public repo check is the main reason I want to escalate."    }));    assert!(        !transcript            .iter()            .any(|entry| entry.starts_with("[3] tool call 0:"))    );    assert!(        !transcript            .iter()            .any(|entry| entry.starts_with("[4] tool call 1:"))    );    assert!(omission.is_some());}#[test]fn build_guardian_transcript_preserves_recent_tool_context_when_user_history_is_large() {    let repeated = "authorization ".repeat(6_000);    let mut entries = (0..8)        .map(|_| GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::User,            text: repeated.clone(),        })        .collect::<Vec<_>>();    entries.extend([        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Tool("tool shell call".to_string()),            text: serde_json::json!({                "command": ["curl", "-X", "POST", "https://example.com/upload"],                "cwd": "/repo",            })            .to_string(),        },        GuardianTranscriptEntry {            kind: GuardianTranscriptEntryKind::Tool("tool shell result".to_string()),            text: "sandbox blocked outbound network access".to_string(),        },    ]);    let (transcript, omission) = render_guardian_transcript_entries(&entries);    assert!(        transcript            .iter()            .any(|entry| entry.starts_with("[1] user: "))    );    assert!(transcript.iter().any(|entry| {        entry.contains("tool shell call:")            && entry.contains("curl")            && entry.contains("https://example.com/upload")    }));    assert!(        transcript            .iter()            .any(|entry| entry                .contains("tool shell result: sandbox blocked outbound network access"))    );    assert_eq!(        omission,        Some("Some conversation entries were omitted.".to_string())    );}#[test]fn parse_guardian_assessment_extracts_embedded_json() {    let parsed = parse_guardian_assessment(Some(        "preface {\"risk_level\":\"medium\",\"user_authorization\":\"low\",\"outcome\":\"allow\",\"rationale\":\"ok\"}",    ))    .expect("guardian assessment");    assert_eq!(        parsed,        GuardianAssessment {            risk_level: GuardianRiskLevel::Medium,            user_authorization: GuardianUserAuthorization::Low,            outcome: GuardianAssessmentOutcome::Allow,            rationale: "ok".to_string(),        }    );}#[test]fn parse_guardian_assessment_treats_bare_allow_as_low_risk() {    let parsed =        parse_guardian_assessment(Some(r#"{"outcome":"allow"}"#)).expect("guardian assessment");    assert_eq!(        parsed,        GuardianAssessment {            risk_level: GuardianRiskLevel::Low,            user_authorization: GuardianUserAuthorization::Unknown,            outcome: GuardianAssessmentOutcome::Allow,            rationale: "Auto-review returned a low-risk allow decision.".to_string(),        }    );}#[test]fn parse_guardian_assessment_treats_bare_deny_as_high_risk() {    let parsed =        parse_guardian_assessment(Some(r#"{"outcome":"deny"}"#)).expect("guardian assessment");    assert_eq!(        parsed,        GuardianAssessment {            risk_level: GuardianRiskLevel::High,            user_authorization: GuardianUserAuthorization::Unknown,            outcome: GuardianAssessmentOutcome::Deny,            rationale: "Auto-review returned a deny decision without a rationale.".to_string(),        }    );}#[test]fn guardian_output_schema_requires_only_outcome_and_allows_optional_details() {    let schema = guardian_output_schema();    assert_eq!(        schema,        serde_json::json!({            "type": "object",            "additionalProperties": false,            "properties": {                "risk_level": {                    "type": "string",                    "enum": ["low", "medium", "high", "critical"]                },                "user_authorization": {                    "type": "string",                    "enum": ["unknown", "low", "medium", "high"]                },                "outcome": {                    "type": "string",                    "enum": ["allow", "deny"]                },                "rationale": {                    "type": "string"                }            },            "required": ["outcome"]        })    );}enum GuardianTestCatalog {    Bundled,    ParentOnly,}async fn guardian_request_model_for_auto_review(    auto_review_model_override: Option<String>,    catalog: GuardianTestCatalog,) -> anyhow::Result<(    String,    String,    String,    codex_analytics::GuardianReviewAnalyticsResult,)> {    let server = start_mock_server().await;    let guardian_assessment = serde_json::json!({        "outcome": "allow",    })    .to_string();    let request_log = mount_sse_once(        &server,        sse(vec![            ev_response_created("resp-guardian"),            ev_assistant_message("msg-guardian", &guardian_assessment),            ev_completed("resp-guardian"),        ]),    )    .await;    let (mut session, mut turn) = guardian_test_session_and_turn(&server).await;    match catalog {        GuardianTestCatalog::Bundled => {}        GuardianTestCatalog::ParentOnly => {            let parent_model = turn.model_info.clone();            let auth_manager = Arc::clone(&session.services.auth_manager);            let models_manager = StaticModelsManager::new(                Some(auth_manager),                ModelsResponse {                    models: vec![parent_model],                },            );            Arc::get_mut(&mut session)                .expect("session should be unique")                .services                .models_manager = Arc::new(models_manager);        }    }    Arc::get_mut(&mut turn)        .expect("turn should be unique")        .model_info        .auto_review_model_override = auto_review_model_override;    let parent_model = turn.model_info.slug.clone();    let preferred_model = turn.provider.approval_review_preferred_model().to_string();    seed_guardian_parent_history(&session, &turn).await;    let (outcome, analytics_result) = run_guardian_review_session_for_test(        Arc::clone(&session),        turn,        GuardianApprovalRequest::Shell {            id: "shell-1".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: None,        },        Some("Sandbox denied outbound git push to github.com.".to_string()),        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 1,    )    .await;    let GuardianReviewOutcome::Completed(_) = outcome else {        panic!("expected guardian assessment");    };    let request = request_log.single_request();    let request_model = request        .body_json()        .get("model")        .and_then(|value| value.as_str())        .expect("guardian request should include a model")        .to_string();    Ok((        request_model,        parent_model,        preferred_model,        analytics_result,    ))}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_uses_model_catalog_override_when_preferred_review_model_exists()-> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let override_model = "guardian-review-model-override".to_string();    let (request_model, parent_model, preferred_model, analytics_result) =        guardian_request_model_for_auto_review(            Some(override_model.clone()),            GuardianTestCatalog::Bundled,        )        .await?;    assert_eq!(request_model, override_model);    assert_ne!(request_model, parent_model);    assert_ne!(request_model, preferred_model);    assert_eq!(        analytics_result.guardian_catalog_contains_auto_review,        Some(true)    );    assert_eq!(        analytics_result.guardian_default_review_model_id.as_deref(),        Some(preferred_model.as_str())    );    assert_eq!(        analytics_result.guardian_review_model_overridden,        Some(true)    );    assert_eq!(        analytics_result.guardian_review_model_override.as_deref(),        Some(override_model.as_str())    );    assert_eq!(        analytics_result.guardian_model_provider_id.as_deref(),        Some(OPENAI_PROVIDER_ID)    );    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_uses_preferred_review_model_without_model_catalog_override()-> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let (request_model, parent_model, preferred_model, analytics_result) =        guardian_request_model_for_auto_review(            /*auto_review_model_override*/ None,            GuardianTestCatalog::Bundled,        )        .await?;    assert_eq!(request_model, preferred_model);    assert_ne!(request_model, parent_model);    assert_eq!(        analytics_result.guardian_catalog_contains_auto_review,        Some(true)    );    assert_eq!(        analytics_result.guardian_default_review_model_id.as_deref(),        Some(preferred_model.as_str())    );    assert_eq!(        analytics_result.guardian_review_model_overridden,        Some(false)    );    assert_eq!(        analytics_result.guardian_review_model_override.as_deref(),        None    );    assert_eq!(        analytics_result.guardian_model_provider_id.as_deref(),        Some(OPENAI_PROVIDER_ID)    );    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_records_missing_auto_review_model_in_analytics_metadata()-> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let (request_model, parent_model, preferred_model, analytics_result) =        guardian_request_model_for_auto_review(            /*auto_review_model_override*/ None,            GuardianTestCatalog::ParentOnly,        )        .await?;    assert_eq!(request_model, parent_model);    assert_ne!(request_model, preferred_model);    assert_eq!(        analytics_result.guardian_catalog_contains_auto_review,        Some(false)    );    assert_eq!(        analytics_result.guardian_default_review_model_id.as_deref(),        Some(preferred_model.as_str())    );    assert_eq!(        analytics_result.guardian_review_model_overridden,        Some(false)    );    assert_eq!(        analytics_result.guardian_review_model_override.as_deref(),        None    );    assert_eq!(        analytics_result.guardian_model_provider_id.as_deref(),        Some(OPENAI_PROVIDER_ID)    );    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_request_layout_matches_model_visible_request_snapshot()-> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let guardian_assessment = serde_json::json!({        "risk_level": "medium",        "user_authorization": "high",        "outcome": "allow",        "rationale": "The user explicitly requested pushing the reviewed branch to the known remote.",    })    .to_string();    let request_log = mount_sse_once(        &server,        sse(vec![            ev_response_created("resp-guardian"),            ev_assistant_message("msg-guardian", &guardian_assessment),            ev_completed("resp-guardian"),        ]),    )    .await;    let (mut session, mut turn) = crate::session::tests::make_session_and_context().await;    session.thread_id = fixed_guardian_parent_session_id();    let temp_cwd = TempDir::new()?;    let mut config = (*turn.config).clone();    config.cwd = temp_cwd.abs();    config.model_provider.base_url = Some(format!("{}/v1", server.uri()));    config.memories.use_memories = true;    config        .features        .enable(Feature::MemoryTool)        .expect("memory tool feature is configurable");    let config = Arc::new(config);    let models_manager = test_support::models_manager_with_provider(        config.codex_home.to_path_buf(),        Arc::clone(&session.services.auth_manager),        config.model_provider.clone(),    );    session.services.models_manager = models_manager;    let memory_extension = Arc::new(GuardianMemoryContextProbe);    let mut extensions = codex_extension_api::ExtensionRegistryBuilder::<Config>::new();    extensions.thread_lifecycle_contributor(memory_extension.clone());    extensions.prompt_contributor(memory_extension);    session.services.extensions = Arc::new(extensions.build());    let skill_dir = config        .codex_home        .to_path_buf()        .join("skills")        .join(GUARDIAN_SKILL_NAME);    std::fs::create_dir_all(&skill_dir)?;    std::fs::write(        skill_dir.join("SKILL.md"),        format!(            "---\nname: {GUARDIAN_SKILL_NAME}\ndescription: Guardian skill injection probe.\n---\n\n{GUARDIAN_SKILL_BODY_PROBE}\n"        ),    )?;    session.services.skills_manager.clear_cache();    turn.config = Arc::clone(&config);    turn.provider = create_model_provider(config.model_provider.clone(), turn.auth_manager.clone());    let session = Arc::new(session);    let turn = Arc::new(turn);    seed_guardian_parent_history(&session, &turn).await;    session        .record_conversation_items(            turn.as_ref(),            &[ResponseItem::Message {                id: None,                role: "user".to_string(),                content: vec![ContentItem::InputText {                    text: format!(                        "Use ${GUARDIAN_SKILL_NAME} before deciding whether the push is safe."                    ),                }],                phase: None,                metadata: None,            }],        )        .await;    let request = GuardianApprovalRequest::Shell {        id: "shell-1".to_string(),        command: vec![            "git".to_string(),            "push".to_string(),            "origin".to_string(),            "guardian-approval-mvp".to_string(),        ],        cwd: test_path_buf("/repo/codex-rs/core").abs(),        sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,        additional_permissions: None,        justification: Some("Need to push the reviewed docs fix to the repo remote.".to_string()),    };    let outcome = run_guardian_review_session_for_test(        Arc::clone(&session),        Arc::clone(&turn),        request,        Some("Sandbox denied outbound git push to github.com.".to_string()),        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 1,    )    .await;    let (GuardianReviewOutcome::Completed(assessment), metadata) = outcome else {        panic!("expected guardian assessment");    };    let guardian_thread_id = metadata        .guardian_thread_id        .as_deref()        .expect("guardian thread id");    assert_eq!(assessment.outcome, GuardianAssessmentOutcome::Allow);    assert_ne!(guardian_thread_id, session.thread_id.to_string());    ThreadId::from_string(guardian_thread_id).expect("guardian thread id should be a valid UUID");    assert!(matches!(        metadata.guardian_session_kind,        Some(codex_analytics::GuardianReviewSessionKind::TrunkNew)    ));    let request = request_log.single_request();    let request_body = request.body_json();    let guardian_user_text = request.message_input_texts("user").join("\n");    assert!(        guardian_user_text.contains(&format!("${GUARDIAN_SKILL_NAME}")),        "guardian request should contain the untrusted skill mention from the parent transcript"    );    assert!(        !request.body_contains_text(GUARDIAN_SKILL_BODY_PROBE),        "guardian request should not inject a skill body from its generated review prompt"    );    assert!(        !request.body_contains_text(GUARDIAN_MEMORY_CONTEXT_PROBE),        "guardian request should not include memory context"    );    assert_eq!(        request_body.pointer("/text/format/strict"),        Some(&serde_json::json!(false))    );    assert_eq!(        request_body.pointer("/text/format/schema"),        Some(&serde_json::json!({            "type": "object",            "additionalProperties": false,            "properties": {                "risk_level": {                    "type": "string",                    "enum": ["low", "medium", "high", "critical"]                },                "user_authorization": {                    "type": "string",                    "enum": ["unknown", "low", "medium", "high"]                },                "outcome": {                    "type": "string",                    "enum": ["allow", "deny"]                },                "rationale": {                    "type": "string"                }            },            "required": ["outcome"]        }))    );    let request_model = request_body        .get("model")        .and_then(|value| value.as_str())        .expect("guardian request should include a model");    let request_reasoning_effort = request_body        .get("reasoning")        .and_then(|reasoning| reasoning.get("effort"))        .and_then(|value| value.as_str());    assert_eq!(metadata.guardian_model.as_deref(), Some(request_model));    assert_eq!(        metadata.guardian_reasoning_effort.as_deref(),        request_reasoning_effort    );    assert_eq!(metadata.had_prior_review_context, Some(false));    assert!(        metadata.time_to_first_token_ms.is_some(),        "guardian review metadata should capture TTFT when the nested turn completes"    );    let mut settings = Settings::clone_current();    settings.set_snapshot_path("snapshots");    settings.set_prepend_module_to_snapshot(false);    settings.bind(|| {        assert_snapshot!(            "codex_core__guardian__tests__guardian_review_request_layout",            normalize_guardian_snapshot_paths(context_snapshot::format_labeled_requests_snapshot(                "Guardian review request layout",                &[("Guardian Review Request", &request)],                &guardian_snapshot_options(),            ))        );    });    Ok(())}#[tokio::test]async fn build_guardian_prompt_items_includes_parent_session_id() -> anyhow::Result<()> {    let (session, _) = crate::session::tests::make_session_and_context().await;    let prompt = build_guardian_prompt_items(        &session,        /*retry_reason*/ None,        GuardianApprovalRequest::Shell {            id: "shell-1".to_string(),            command: vec!["git".to_string(), "status".to_string()],            cwd: test_path_buf("/repo").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: None,        },        GuardianPromptMode::Full,    )    .await?;    let prompt_text = prompt        .items        .into_iter()        .map(|item| match item {            codex_protocol::user_input::UserInput::Text { text, .. } => text,            codex_protocol::user_input::UserInput::Image { .. } => String::new(),            _ => String::new(),        })        .collect::<String>();    assert!(        prompt_text.contains(&format!(            ">>> TRANSCRIPT END\nReviewed Codex session id: {}\n",            session.thread_id        )),        "guardian prompt should expose the parent session id immediately after the transcript end"    );    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_reuses_prompt_cache_key_and_appends_prior_reviews() -> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let first_rationale = "first guardian rationale from the prior review";    let request_log = mount_sse_sequence(        &server,        vec![            sse(vec![                ev_response_created("resp-guardian-1"),                ev_assistant_message(                    "msg-guardian-1",                    &format!(                        "{{\"risk_level\":\"low\",\"user_authorization\":\"high\",\"outcome\":\"allow\",\"rationale\":\"{first_rationale}\"}}"                    ),                ),                ev_completed("resp-guardian-1"),            ]),            sse(vec![                ev_response_created("resp-guardian-2"),                ev_assistant_message(                    "msg-guardian-2",                    "{\"risk_level\":\"low\",\"user_authorization\":\"high\",\"outcome\":\"allow\",\"rationale\":\"second guardian rationale\"}",                ),                ev_completed("resp-guardian-2"),            ]),            sse(vec![                ev_response_created("resp-guardian-3"),                ev_assistant_message(                    "msg-guardian-3",                    "{\"risk_level\":\"low\",\"user_authorization\":\"high\",\"outcome\":\"allow\",\"rationale\":\"third guardian rationale\"}",                ),                ev_completed("resp-guardian-3"),            ]),        ],    )    .await;    let (session, turn) = guardian_test_session_and_turn(&server).await;    seed_guardian_parent_history(&session, &turn).await;    let first_request = GuardianApprovalRequest::Shell {        id: "shell-1".to_string(),        command: vec!["git".to_string(), "push".to_string()],        cwd: test_path_buf("/repo/codex-rs/core").abs(),        sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,        additional_permissions: None,        justification: Some("Need to push the first docs fix.".to_string()),    };    let first_outcome = run_guardian_review_session_for_test(        Arc::clone(&session),        Arc::clone(&turn),        first_request,        Some("First retry reason".to_string()),        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 1,    )    .await;    session        .record_conversation_items(            turn.as_ref(),            &[                ResponseItem::Message {                    id: None,                    role: "user".to_string(),                    content: vec![ContentItem::InputText {                        text: "Please push the second docs fix too.".to_string(),                    }],                    phase: None,                    metadata: None,                },                ResponseItem::Message {                    id: None,                    role: "assistant".to_string(),                    content: vec![ContentItem::OutputText {                        text: "I need approval for the second docs fix.".to_string(),                    }],                    phase: None,                    metadata: None,                },            ],        )        .await;    let second_request = GuardianApprovalRequest::Shell {        id: "shell-2".to_string(),        command: vec![            "git".to_string(),            "push".to_string(),            "--force-with-lease".to_string(),        ],        cwd: test_path_buf("/repo/codex-rs/core").abs(),        sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,        additional_permissions: None,        justification: Some("Need to push the second docs fix.".to_string()),    };    let second_outcome = run_guardian_review_session_for_test(        Arc::clone(&session),        Arc::clone(&turn),        second_request,        Some("Second retry reason".to_string()),        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 1,    )    .await;    session        .record_conversation_items(            turn.as_ref(),            &[                ResponseItem::Message {                    id: None,                    role: "user".to_string(),                    content: vec![ContentItem::InputText {                        text: "Please push the third docs fix too.".to_string(),                    }],                    phase: None,                    metadata: None,                },                ResponseItem::Message {                    id: None,                    role: "assistant".to_string(),                    content: vec![ContentItem::OutputText {                        text: "I need approval for the third docs fix.".to_string(),                    }],                    phase: None,                    metadata: None,                },            ],        )        .await;    let third_request = GuardianApprovalRequest::Shell {        id: "shell-3".to_string(),        command: vec!["git".to_string(), "push".to_string()],        cwd: test_path_buf("/repo/codex-rs/core").abs(),        sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,        additional_permissions: None,        justification: Some("Need to push the third docs fix.".to_string()),    };    let third_outcome = run_guardian_review_session_for_test(        Arc::clone(&session),        Arc::clone(&turn),        third_request,        Some("Third retry reason".to_string()),        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 1,    )    .await;    let (GuardianReviewOutcome::Completed(first_assessment), first_metadata) = first_outcome else {        panic!("expected first guardian assessment");    };    let (GuardianReviewOutcome::Completed(second_assessment), second_metadata) = second_outcome    else {        panic!("expected second guardian assessment");    };    let (GuardianReviewOutcome::Completed(third_assessment), third_metadata) = third_outcome else {        panic!("expected third guardian assessment");    };    assert_eq!(first_assessment.outcome, GuardianAssessmentOutcome::Allow);    assert_eq!(second_assessment.outcome, GuardianAssessmentOutcome::Allow);    assert_eq!(third_assessment.outcome, GuardianAssessmentOutcome::Allow);    assert!(matches!(        first_metadata.guardian_session_kind,        Some(codex_analytics::GuardianReviewSessionKind::TrunkNew)    ));    assert!(matches!(        second_metadata.guardian_session_kind,        Some(codex_analytics::GuardianReviewSessionKind::TrunkReused)    ));    assert!(matches!(        third_metadata.guardian_session_kind,        Some(codex_analytics::GuardianReviewSessionKind::TrunkReused)    ));    ThreadId::from_string(        first_metadata            .guardian_thread_id            .as_deref()            .expect("first guardian thread id"),    )    .expect("first guardian thread id should be a valid UUID");    ThreadId::from_string(        second_metadata            .guardian_thread_id            .as_deref()            .expect("second guardian thread id"),    )    .expect("second guardian thread id should be a valid UUID");    ThreadId::from_string(        third_metadata            .guardian_thread_id            .as_deref()            .expect("third guardian thread id"),    )    .expect("third guardian thread id should be a valid UUID");    assert_eq!(first_metadata.had_prior_review_context, Some(false));    assert_eq!(second_metadata.had_prior_review_context, Some(true));    assert_eq!(third_metadata.had_prior_review_context, Some(true));    assert_eq!(        first_metadata.guardian_thread_id,        second_metadata.guardian_thread_id    );    assert_eq!(        second_metadata.guardian_thread_id,        third_metadata.guardian_thread_id    );    let requests = request_log.requests();    assert_eq!(requests.len(), 3);    let first_body = requests[0].body_json();    let second_body = requests[1].body_json();    let third_body = requests[2].body_json();    assert_eq!(        first_body["prompt_cache_key"],        second_body["prompt_cache_key"]    );    assert!(        second_body.to_string().contains(concat!(            "Use prior reviews as context, not binding precedent. ",            "Follow the Workspace Policy. ",            "If the user explicitly approves a previously rejected action after being ",            "informed of the concrete risks, set outcome to \\\"allow\\\" unless the policy ",            "explicitly disallows user overwrites in such cases."        )),        "follow-up guardian request should include the follow-up reminder"    );    assert!(        second_body.to_string().contains(first_rationale),        "guardian session should append earlier reviews into the follow-up request"    );    assert_eq!(        third_body            .to_string()            .matches("Use prior reviews as context, not binding precedent.")            .count(),        1,        "later follow-up guardian requests should not append the reminder again"    );    let committed_rollout_items = session        .guardian_review_session        .committed_fork_rollout_items_for_test()        .await        .expect("committed guardian fork snapshot");    assert_eq!(        committed_rollout_items            .iter()            .filter(|item| rollout_item_contains_message_text(                item,                "Use prior reviews as context, not binding precedent."            ))            .count(),        1,        "follow-up reminder should be persisted for guardian forks"    );    let second_user_message = requests[1]        .message_input_text_groups("user")        .last()        .expect("follow-up guardian user message")        .join("");    assert!(second_user_message.contains(">>> TRANSCRIPT DELTA START\n"));    assert!(second_user_message.contains("[5] user: Please push the second docs fix too."));    assert!(        second_user_message.contains("[6] assistant: I need approval for the second docs fix.")    );    assert!(!second_user_message.contains("[1] user: Please check the repo visibility"));    let mut settings = Settings::clone_current();    settings.set_snapshot_path("snapshots");    settings.set_prepend_module_to_snapshot(false);    settings.bind(|| {        assert_snapshot!(            "codex_core__guardian__tests__guardian_followup_review_request_layout",            format!(                "{}\n\nshared_prompt_cache_key: {}\nfollowup_contains_first_rationale: {}",                normalize_guardian_snapshot_paths(                    context_snapshot::format_labeled_requests_snapshot(                        "Guardian follow-up review request layout",                        &[                            ("Initial Guardian Review Request", &requests[0]),                            ("Follow-up Guardian Review Request", &requests[1]),                        ],                        &guardian_snapshot_options(),                    )                ),                first_body["prompt_cache_key"] == second_body["prompt_cache_key"],                second_body.to_string().contains(first_rationale),            )        );    });    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_reused_trunk_ignores_stale_prior_turn_completion() -> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let request_log = mount_sse_sequence(        &server,        vec![            sse(vec![                ev_response_created("resp-guardian-1"),                ev_assistant_message(                    "msg-guardian-1",                    "{\"risk_level\":\"low\",\"user_authorization\":\"high\",\"outcome\":\"allow\",\"rationale\":\"first guardian rationale\"}",                ),                ev_completed("resp-guardian-1"),            ]),            sse(vec![                ev_response_created("resp-guardian-2"),                ev_assistant_message(                    "msg-guardian-2",                    "{\"risk_level\":\"low\",\"user_authorization\":\"high\",\"outcome\":\"allow\",\"rationale\":\"second guardian rationale\"}",                ),                ev_completed("resp-guardian-2"),            ]),        ],    )    .await;    let (session, turn) = guardian_test_session_and_turn(&server).await;    let first_outcome = run_guardian_review_session_for_test(        Arc::clone(&session),        Arc::clone(&turn),        GuardianApprovalRequest::Shell {            id: "shell-1".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Need to push the first docs fix.".to_string()),        },        /*retry_reason*/ None,        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 1,    )    .await;    let (GuardianReviewOutcome::Completed(first_assessment), first_metadata) = first_outcome else {        panic!("expected first guardian assessment");    };    assert_eq!(first_assessment.rationale, "first guardian rationale");    assert!(matches!(        first_metadata.guardian_session_kind,        Some(codex_analytics::GuardianReviewSessionKind::TrunkNew)    ));    session        .guardian_review_session        .send_trunk_event_raw_for_test(Event {            id: "stale-turn".to_string(),            msg: EventMsg::TurnComplete(TurnCompleteEvent {                turn_id: "stale-turn".to_string(),                last_agent_message: Some(                    "{\"risk_level\":\"high\",\"user_authorization\":\"low\",\"outcome\":\"deny\",\"rationale\":\"stale guardian rationale\"}"                        .to_string(),                ),                completed_at: None,                duration_ms: None,                time_to_first_token_ms: Some(1),            }),        })        .await;    let second_outcome = run_guardian_review_session_for_test(        Arc::clone(&session),        Arc::clone(&turn),        GuardianApprovalRequest::Shell {            id: "shell-2".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Need to push the second docs fix.".to_string()),        },        /*retry_reason*/ None,        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 1,    )    .await;    let (GuardianReviewOutcome::Completed(second_assessment), second_metadata) = second_outcome    else {        panic!("expected second guardian assessment");    };    assert_eq!(second_assessment.outcome, GuardianAssessmentOutcome::Allow);    assert_eq!(second_assessment.rationale, "second guardian rationale");    assert!(matches!(        second_metadata.guardian_session_kind,        Some(codex_analytics::GuardianReviewSessionKind::TrunkReused)    ));    assert_eq!(        request_log.requests().len(),        2,        "the reused trunk should wait for the real follow-up review"    );    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() -> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let error_message =        "Item 'rs_test' of type 'reasoning' was provided without its required following item.";    let request_log = mount_response_sequence(        &server,        vec![            wiremock::ResponseTemplate::new(400).set_body_json(serde_json::json!({                "error": {                    "message": error_message,                    "type": "invalid_request_error",                    "param": "input"                }            })),        ],    )    .await;    let (mut session, mut turn, rx) =        crate::session::tests::make_session_and_context_with_rx().await;    let mut config = (*turn.config).clone();    config.model_provider.base_url = Some(format!("{}/v1", server.uri()));    let config = Arc::new(config);    let models_manager = test_support::models_manager_with_provider(        config.codex_home.to_path_buf(),        Arc::clone(&session.services.auth_manager),        config.model_provider.clone(),    );    Arc::get_mut(&mut session)        .expect("session should be uniquely owned")        .services        .models_manager = models_manager;    let turn_mut = Arc::get_mut(&mut turn).expect("turn should be uniquely owned");    turn_mut.config = Arc::clone(&config);    turn_mut.provider =        create_model_provider(config.model_provider.clone(), turn_mut.auth_manager.clone());    turn_mut.user_instructions = None;    seed_guardian_parent_history(&session, &turn).await;    let decision = review_approval_request(        &session,        &turn,        "review-shell-guardian-error".to_string(),        GuardianApprovalRequest::Shell {            id: "shell-guardian-error".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Need to push the reviewed docs fix.".to_string()),        },        /*retry_reason*/ None,    )    .await;    assert_eq!(decision, ReviewDecision::Denied);    assert_eq!(request_log.requests().len(), 1);    let mut warnings = Vec::new();    let mut denial_rationales = Vec::new();    while let Ok(event) = rx.try_recv() {        match event.msg {            EventMsg::GuardianWarning(event) => warnings.push(event.message),            EventMsg::GuardianAssessment(event)                if event.status == GuardianAssessmentStatus::Denied =>            {                denial_rationales.push(event.rationale)            }            _ => {}        }    }    assert!(        warnings            .iter()            .any(|message| message.contains(error_message)),        "warning should include the underlying responses api error"    );    assert!(        denial_rationales            .iter()            .flatten()            .any(|message| message.contains(error_message)),        "denial rationale should include the underlying responses api error"    );    assert!(        denial_rationales.iter().flatten().all(|message| {            !message.contains("guardian review completed without an assessment payload")        }),        "denial rationale should not fall back to the generic missing payload error"    );    {        let rationales = session.services.guardian_rejections.lock().await;        assert!(rationales.contains_key("review-shell-guardian-error"));        assert!(!rationales.contains_key("shell-guardian-error"));    }    let rejection_message =        guardian_rejection_message(session.as_ref(), "review-shell-guardian-error").await;    assert!(        rejection_message.contains("Reason: Automatic approval review failed:")            && rejection_message.contains(error_message),        "rejection message should include guardian rationale: {rejection_message}"    );    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_retries_transient_session_failure_then_approves() -> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let approval = serde_json::json!({        "risk_level": "low",        "user_authorization": "high",        "outcome": "allow",        "rationale": "retry succeeded",    })    .to_string();    let request_log = mount_sse_sequence(        &server,        vec![            sse_failed(                "resp-session-failure",                "server_is_overloaded",                "temporary reviewer overload",            ),            sse(vec![                ev_response_created("resp-approved"),                ev_assistant_message("msg-approved", &approval),                ev_completed("resp-approved"),            ]),        ],    )    .await;    let (session, turn) = guardian_test_session_and_turn(&server).await;    seed_guardian_parent_history(&session, &turn).await;    let (outcome, metadata) = run_guardian_review_session_for_test(        Arc::clone(&session),        Arc::clone(&turn),        guardian_shell_request("shell-session-retry"),        /*retry_reason*/ None,        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 3,    )    .await;    let GuardianReviewOutcome::Completed(assessment) = outcome else {        panic!("expected guardian assessment");    };    assert_eq!(assessment.outcome, GuardianAssessmentOutcome::Allow);    assert_eq!(assessment.rationale, "retry succeeded");    assert_eq!(metadata.attempt_count, 2);    assert!(matches!(        metadata.guardian_session_kind,        Some(codex_analytics::GuardianReviewSessionKind::TrunkReused)    ));    assert_eq!(request_log.requests().len(), 2);    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_does_not_retry_missing_assessment_payload() -> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let request_log = mount_sse_sequence(        &server,        vec![sse(vec![            ev_response_created("resp-missing-assessment"),            ev_completed("resp-missing-assessment"),        ])],    )    .await;    let (session, turn) = guardian_test_session_and_turn(&server).await;    seed_guardian_parent_history(&session, &turn).await;    let decision = review_approval_request(        &session,        &turn,        "review-missing-assessment".to_string(),        guardian_shell_request("shell-missing-assessment"),        /*retry_reason*/ None,    )    .await;    assert_eq!(decision, ReviewDecision::Denied);    assert_eq!(request_log.requests().len(), 1);    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_retries_two_parse_failures_then_approves() -> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let approval = serde_json::json!({        "risk_level": "low",        "user_authorization": "high",        "outcome": "allow",        "rationale": "retry succeeded",    })    .to_string();    let request_log = mount_sse_sequence(        &server,        vec![            sse(vec![                ev_response_created("resp-parse-failure-1"),                ev_assistant_message("msg-parse-failure-1", "not valid guardian json"),                ev_completed("resp-parse-failure-1"),            ]),            sse(vec![                ev_response_created("resp-parse-failure-2"),                ev_assistant_message("msg-parse-failure-2", "still not valid guardian json"),                ev_completed("resp-parse-failure-2"),            ]),            sse(vec![                ev_response_created("resp-approved"),                ev_assistant_message("msg-approved", &approval),                ev_completed("resp-approved"),            ]),        ],    )    .await;    let (session, turn) = guardian_test_session_and_turn(&server).await;    seed_guardian_parent_history(&session, &turn).await;    let (outcome, metadata) = run_guardian_review_session_for_test(        Arc::clone(&session),        Arc::clone(&turn),        guardian_shell_request("shell-parse-retry"),        /*retry_reason*/ None,        guardian_output_schema(),        /*external_cancel*/ None,        /*max_attempts*/ 3,    )    .await;    let GuardianReviewOutcome::Completed(assessment) = outcome else {        panic!("expected guardian assessment");    };    assert_eq!(assessment.outcome, GuardianAssessmentOutcome::Allow);    assert_eq!(assessment.rationale, "retry succeeded");    assert_eq!(metadata.attempt_count, 3);    assert!(matches!(        metadata.guardian_session_kind,        Some(codex_analytics::GuardianReviewSessionKind::TrunkReused)    ));    assert_eq!(request_log.requests().len(), 3);    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_exhausts_three_failures_with_one_terminal_event() -> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let request_log = mount_sse_sequence(        &server,        vec![            sse(vec![                ev_response_created("resp-parse-failure-1"),                ev_assistant_message("msg-parse-failure-1", "invalid one"),                ev_completed("resp-parse-failure-1"),            ]),            sse(vec![                ev_response_created("resp-parse-failure-2"),                ev_assistant_message("msg-parse-failure-2", "invalid two"),                ev_completed("resp-parse-failure-2"),            ]),            sse(vec![                ev_response_created("resp-parse-failure-3"),                ev_assistant_message("msg-parse-failure-3", "invalid three"),                ev_completed("resp-parse-failure-3"),            ]),        ],    )    .await;    let (session, turn, rx) = guardian_test_session_turn_and_rx(&server).await;    seed_guardian_parent_history(&session, &turn).await;    let decision = review_approval_request(        &session,        &turn,        "review-exhausted-retry".to_string(),        guardian_shell_request("shell-exhausted-retry"),        /*retry_reason*/ None,    )    .await;    assert_eq!(decision, ReviewDecision::Denied);    assert_eq!(request_log.requests().len(), 3);    let mut statuses = Vec::new();    while let Ok(event) = rx.try_recv() {        if let EventMsg::GuardianAssessment(event) = event.msg {            statuses.push(event.status);        }    }    assert_eq!(        statuses,        vec![            GuardianAssessmentStatus::InProgress,            GuardianAssessmentStatus::Denied,        ]    );    Ok(())}#[tokio::test(flavor = "multi_thread", worker_threads = 2)]async fn guardian_review_does_not_retry_valid_denial() -> anyhow::Result<()> {    skip_if_no_network!(Ok(()));    let server = start_mock_server().await;    let denial = serde_json::json!({        "risk_level": "high",        "user_authorization": "unknown",        "outcome": "deny",        "rationale": "unsafe",    })    .to_string();    let request_log = mount_sse_sequence(        &server,        vec![sse(vec![            ev_response_created("resp-denied"),            ev_assistant_message("msg-denied", &denial),            ev_completed("resp-denied"),        ])],    )    .await;    let (session, turn) = guardian_test_session_and_turn(&server).await;    seed_guardian_parent_history(&session, &turn).await;    let decision = review_approval_request(        &session,        &turn,        "review-valid-denial".to_string(),        guardian_shell_request("shell-valid-denial"),        /*retry_reason*/ None,    )    .await;    assert_eq!(decision, ReviewDecision::Denied);    assert_eq!(request_log.requests().len(), 1);    Ok(())}#[tokio::test]async fn guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history() -> anyhow::Result<()>{    const TEST_STACK_SIZE_BYTES: usize = 4 * 1024 * 1024;    let handle =        std::thread::Builder::new()            .name("guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history".to_string())            .stack_size(TEST_STACK_SIZE_BYTES)            .spawn(|| -> anyhow::Result<()> {                let runtime = tokio::runtime::Builder::new_current_thread()                    .enable_all()                    .build()?;                runtime.block_on(Box::pin(async {        let first_assessment = serde_json::json!({            "risk_level": "low",            "user_authorization": "high",            "outcome": "allow",            "rationale": "first guardian rationale",        })        .to_string();        let second_assessment = serde_json::json!({            "risk_level": "low",            "user_authorization": "high",            "outcome": "allow",            "rationale": "second guardian rationale",        })        .to_string();        let third_assessment = serde_json::json!({            "risk_level": "low",            "user_authorization": "high",            "outcome": "allow",            "rationale": "third guardian rationale",        })        .to_string();        let (gate_tx, gate_rx) = tokio::sync::oneshot::channel();        let (server, _) = start_streaming_sse_server(vec![            vec![StreamingSseChunk {                gate: None,                body: sse(vec![                    ev_response_created("resp-guardian-1"),                    ev_assistant_message("msg-guardian-1", &first_assessment),                    ev_completed("resp-guardian-1"),                ]),            }],            vec![                StreamingSseChunk {                    gate: None,                    body: sse(vec![ev_response_created("resp-guardian-2")]),                },                StreamingSseChunk {                    gate: Some(gate_rx),                    body: sse(vec![                        ev_assistant_message("msg-guardian-2", &second_assessment),                        ev_completed("resp-guardian-2"),                    ]),                },            ],            vec![StreamingSseChunk {                gate: None,                body: sse(vec![                    ev_response_created("resp-guardian-3"),                    ev_assistant_message("msg-guardian-3", "not valid guardian json"),                    ev_completed("resp-guardian-3"),                ]),            }],            vec![StreamingSseChunk {                gate: None,                body: sse(vec![                    ev_response_created("resp-guardian-4"),                    ev_assistant_message("msg-guardian-4", &third_assessment),                    ev_completed("resp-guardian-4"),                ]),            }],        ])        .await;        let (session, turn) = guardian_test_session_and_turn_with_base_url(server.uri()).await;        seed_guardian_parent_history(&session, &turn).await;        let initial_request = GuardianApprovalRequest::Shell {            id: "shell-guardian-1".to_string(),            command: vec!["git".to_string(), "status".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Inspect repo state before proceeding.".to_string()),        };        assert_eq!(            review_approval_request(                &session,                &turn,                "review-shell-guardian-1".to_string(),                initial_request,                /*retry_reason*/ None            )            .await,            ReviewDecision::Approved        );        session            .record_conversation_items(                turn.as_ref(),                &[                    ResponseItem::Message {                        id: None,                        role: "user".to_string(),                        content: vec![ContentItem::InputText {                            text: "Please inspect pending changes before pushing.".to_string(),                        }],                        phase: None,                        metadata: None,},                    ResponseItem::Message {                        id: None,                        role: "assistant".to_string(),                        content: vec![ContentItem::OutputText {                            text: "I need approval to run git diff.".to_string(),                        }],                        phase: None,                        metadata: None,},                ],            )            .await;        let second_request = GuardianApprovalRequest::Shell {            id: "shell-guardian-2".to_string(),            command: vec!["git".to_string(), "diff".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Inspect pending changes before proceeding.".to_string()),        };        let third_request = GuardianApprovalRequest::Shell {            id: "shell-guardian-3".to_string(),            command: vec!["git".to_string(), "push".to_string()],            cwd: test_path_buf("/repo/codex-rs/core").abs(),            sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,            additional_permissions: None,            justification: Some("Inspect whether pushing is safe before proceeding.".to_string()),        };        let session_for_second = Arc::clone(&session);        let turn_for_second = Arc::clone(&turn);        let mut second_review = tokio::spawn(async move {            review_approval_request(                &session_for_second,                &turn_for_second,                "review-shell-guardian-2".to_string(),                second_request,                Some("trunk follow-up".to_string()),            )            .await        });        let second_request_observed = tokio::time::timeout(Duration::from_secs(5), async {            loop {                if server.requests().await.len() >= 2 {                    break;                }                tokio::task::yield_now().await;            }        })        .await;        assert!(            second_request_observed.is_ok(),            "second guardian request was not observed"        );        session            .record_conversation_items(                turn.as_ref(),                &[                    ResponseItem::Message {                        id: None,                        role: "user".to_string(),                        content: vec![ContentItem::InputText {                            text: "Now inspect whether pushing is safe.".to_string(),                        }],                        phase: None,                        metadata: None,},                    ResponseItem::Message {                        id: None,                        role: "assistant".to_string(),                        content: vec![ContentItem::OutputText {                            text: "I need approval to push after the diff check.".to_string(),                        }],                        phase: None,                        metadata: None,},                ],            )            .await;        let third_decision = review_approval_request(            &session,            &turn,            "review-shell-guardian-3".to_string(),            third_request,            Some("parallel follow-up".to_string()),        )        .await;        assert_eq!(third_decision, ReviewDecision::Approved);        let requests = server.requests().await;        assert_eq!(requests.len(), 4);        let second_request_body = serde_json::from_slice::<serde_json::Value>(&requests[1])?;        let failed_ephemeral_request_body =            serde_json::from_slice::<serde_json::Value>(&requests[2])?;        let retried_ephemeral_request_body =            serde_json::from_slice::<serde_json::Value>(&requests[3])?;        assert_eq!(            second_request_body["prompt_cache_key"],            failed_ephemeral_request_body["prompt_cache_key"],            "forked guardian review should reuse the trunk guardian prompt cache key"        );        assert_eq!(            failed_ephemeral_request_body["prompt_cache_key"],            retried_ephemeral_request_body["prompt_cache_key"],            "retried ephemeral review should preserve the guardian prompt cache key"        );        let third_request_body_text = retried_ephemeral_request_body.to_string();        assert!(            third_request_body_text.contains("first guardian rationale"),            "forked guardian review should include the last committed trunk assessment"        );        let third_user_message = last_user_message_text_from_body(&retried_ephemeral_request_body);        assert!(third_user_message.contains(">>> TRANSCRIPT DELTA START\n"));        assert!(            third_user_message.contains("[5] user: Please inspect pending changes before pushing.")        );        assert!(third_user_message.contains("[7] user: Now inspect whether pushing is safe."));        assert!(!third_user_message.contains("[1] user: Please check the repo visibility"));        assert!(            !third_request_body_text.contains("second guardian rationale"),            "forked guardian review should not include the still in-flight trunk assessment"        );        assert!(            tokio::time::timeout(Duration::from_millis(100), &mut second_review)                .await                .is_err(),            "the trunk guardian review should still be blocked on its gated response"        );        gate_tx            .send(())            .expect("second guardian review gate should still be open");        assert_eq!(second_review.await?, ReviewDecision::Approved);        server.shutdown().await;        Ok(())                }))            })?;    match handle.join() {        Ok(result) => result,        Err(_) => Err(anyhow::anyhow!(            "guardian_ephemeral_retry_preserves_parallel_trunk_and_fork_history thread panicked"        )),    }}#[tokio::test]async fn guardian_review_session_config_preserves_parent_network_proxy() {    let mut parent_config = test_config().await;    let network = NetworkProxySpec::from_config_and_constraints(        NetworkProxyConfig::default(),        Some(NetworkConstraints {            enabled: Some(true),            domains: Some(NetworkDomainPermissionsToml {                entries: std::collections::BTreeMap::from([(                    "github.com".to_string(),                    NetworkDomainPermissionToml::Allow,                )]),            }),            ..Default::default()        }),        parent_config.permissions.permission_profile(),    )    .expect("network proxy spec");    parent_config.permissions.network = Some(network.clone());    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        "parent-active-model",        Some(codex_protocol::openai_models::ReasoningEffort::Low),    )    .expect("guardian config");    assert_eq!(guardian_config.permissions.network, Some(network));    assert_eq!(        guardian_config.model,        Some("parent-active-model".to_string())    );    assert_eq!(        guardian_config.model_reasoning_effort,        Some(codex_protocol::openai_models::ReasoningEffort::Low)    );    assert_eq!(        guardian_config.permissions.approval_policy,        Constrained::allow_only(AskForApproval::Never)    );    assert_eq!(        guardian_config.permissions.permission_profile(),        &PermissionProfile::read_only()    );}#[tokio::test]async fn guardian_review_session_config_clears_parent_developer_instructions() {    let mut parent_config = test_config().await;    parent_config.developer_instructions =        Some("parent or managed config should not replace guardian policy".to_string());    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        "active-model",        /*reasoning_effort*/ None,    )    .expect("guardian config");    assert_eq!(guardian_config.developer_instructions, None);    assert_eq!(        guardian_config.base_instructions,        Some(guardian_policy_prompt())    );}#[tokio::test]async fn guardian_review_session_config_clears_legacy_notify() {    let mut parent_config = test_config().await;    parent_config.notify = Some(vec![        "/path/to/notify".to_string(),        "turn-ended".to_string(),    ]);    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        "active-model",        /*reasoning_effort*/ None,    )    .expect("guardian config");    assert_eq!(guardian_config.notify, None);}#[tokio::test]async fn guardian_review_session_config_uses_live_network_proxy_state() {    let mut parent_config = test_config().await;    let mut parent_network = NetworkProxyConfig::default();    parent_network.network.enabled = true;    parent_network        .network        .set_allowed_domains(vec!["parent.example".to_string()]);    parent_config.permissions.network = Some(        NetworkProxySpec::from_config_and_constraints(            parent_network,            /*requirements*/ None,            parent_config.permissions.permission_profile(),        )        .expect("parent network proxy spec"),    );    let mut live_network = NetworkProxyConfig::default();    live_network.network.enabled = true;    live_network        .network        .set_allowed_domains(vec!["github.com".to_string()]);    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        Some(live_network.clone()),        "active-model",        /*reasoning_effort*/ None,    )    .expect("guardian config");    assert_eq!(        guardian_config.permissions.network,        Some(            NetworkProxySpec::from_config_and_constraints(                live_network,                /*requirements*/ None,                &PermissionProfile::read_only(),            )            .expect("live network proxy spec")        )    );}#[tokio::test]async fn guardian_review_session_config_disables_mcp_apps_plugins_and_memories() {    let mut parent_config = test_config().await;    let server: McpServerConfig =        toml::from_str("command = \"docs-server\"").expect("deserialize MCP server");    parent_config        .mcp_servers        .set(HashMap::from([("docs".to_string(), server)]))        .expect("parent MCP servers are configurable");    parent_config        .features        .enable(Feature::Apps)        .expect("apps feature is configurable");    parent_config        .features        .enable(Feature::Plugins)        .expect("plugins feature is configurable");    parent_config.include_apps_instructions = true;    parent_config.memories.use_memories = true;    parent_config.memories.dedicated_tools = true;    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        "active-model",        /*reasoning_effort*/ None,    )    .expect("guardian config");    assert!(guardian_config.mcp_servers.get().is_empty());    assert!(!guardian_config.features.enabled(Feature::Apps));    assert!(!guardian_config.features.enabled(Feature::Plugins));    assert!(!guardian_config.include_apps_instructions);    assert!(!guardian_config.memories.use_memories);    assert!(!guardian_config.memories.dedicated_tools);}#[tokio::test]async fn guardian_review_session_config_allows_pinned_disabled_feature() {    let mut parent_config = test_config().await;    parent_config.features = ManagedFeatures::from_configured(        parent_config.features.get().clone(),        Some(Sourced {            value: FeatureRequirementsToml {                entries: BTreeMap::from([("multi_agent".to_string(), true)]),            },            source: RequirementSource::Unknown,        }),    )    .expect("managed features");    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        "active-model",        /*reasoning_effort*/ None,    )    .expect("guardian config should continue when a disabled feature is pinned on");    assert!(guardian_config.features.enabled(Feature::Collab));    assert!(guardian_config.mcp_servers.get().is_empty());    assert!(!guardian_config.include_apps_instructions);}#[tokio::test]async fn guardian_review_session_config_uses_parent_active_model_instead_of_hardcoded_slug() {    let mut parent_config = test_config().await;    parent_config.model = Some("configured-model".to_string());    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        "active-model",        /*reasoning_effort*/ None,    )    .expect("guardian config");    assert_eq!(guardian_config.model, Some("active-model".to_string()));}#[tokio::test]async fn guardian_review_session_config_keeps_bedrock_provider_for_bedrock_gpt_5_4() {    let mut parent_config = test_config().await;    parent_config.model_provider_id = AMAZON_BEDROCK_PROVIDER_ID.to_string();    parent_config.model_provider =        ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None);    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        AMAZON_BEDROCK_GPT_5_4_MODEL_ID,        Some(ReasoningEffort::Low),    )    .expect("guardian config");    let mut expected_model_provider =        ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None);    expected_model_provider.request_max_retries = Some(1);    expected_model_provider.stream_max_retries = Some(1);    assert_eq!(        (            guardian_config.model,            guardian_config.model_provider_id,            guardian_config.model_provider,        ),        (            Some(AMAZON_BEDROCK_GPT_5_4_MODEL_ID.to_string()),            AMAZON_BEDROCK_PROVIDER_ID.to_string(),            expected_model_provider,        )    );}#[tokio::test]async fn guardian_review_session_config_uses_requirements_guardian_policy_config() {    let codex_home = tempfile::tempdir().expect("create temp dir");    let workspace = tempfile::tempdir().expect("create temp dir");    let config_layer_stack = ConfigLayerStack::new(        Vec::new(),        Default::default(),        codex_config::ConfigRequirementsToml {            guardian_policy_config: Some(                "  Use the workspace-managed guardian policy.  ".to_string(),            ),            ..Default::default()        },    )    .expect("config layer stack");    let parent_config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        ConfigToml::default(),        ConfigOverrides {            cwd: Some(workspace.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        config_layer_stack,    )    .await    .expect("load config");    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        "active-model",        /*reasoning_effort*/ None,    )    .expect("guardian config");    assert_eq!(guardian_config.developer_instructions, None);    assert_eq!(        guardian_config.base_instructions,        Some(guardian_policy_prompt_with_config(            "Use the workspace-managed guardian policy."        ))    );}#[tokio::test]async fn guardian_review_session_config_uses_default_guardian_policy_without_requirements_override(){    let codex_home = tempfile::tempdir().expect("create temp dir");    let workspace = tempfile::tempdir().expect("create temp dir");    let config_layer_stack =        ConfigLayerStack::new(Vec::new(), Default::default(), Default::default())            .expect("config layer stack");    let parent_config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        ConfigToml::default(),        ConfigOverrides {            cwd: Some(workspace.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        config_layer_stack,    )    .await    .expect("load config");    let guardian_config = build_guardian_review_session_config_for_test(        &parent_config,        /*live_network_config*/ None,        "active-model",        /*reasoning_effort*/ None,    )    .expect("guardian config");    assert_eq!(guardian_config.developer_instructions, None);    assert_eq!(        guardian_config.base_instructions,        Some(guardian_policy_prompt())    );}