Codex Handbook
core/src/config/config_tests.rs 10850 lines
use crate::config::edit::ConfigEdit;use crate::config::edit::ConfigEditsBuilder;use crate::config::edit::apply_blocking;use assert_matches::assert_matches;use codex_config::CONFIG_TOML_FILE;use codex_config::ConfigLayerEntry;use codex_config::ConfigLayerStack;use codex_config::ProfileV2Name;use codex_config::RequirementSource;use codex_config::Sourced;use codex_config::config_toml::AgentRoleToml;use codex_config::config_toml::AgentsToml;use codex_config::config_toml::AutoReviewToml;use codex_config::config_toml::ConfigToml;use codex_config::config_toml::ExperimentalRequestUserInput;use codex_config::config_toml::ProjectConfig;use codex_config::config_toml::RealtimeArchitecture;use codex_config::config_toml::RealtimeConfig;use codex_config::config_toml::RealtimeToml;use codex_config::config_toml::RealtimeTransport;use codex_config::config_toml::RealtimeWsMode;use codex_config::config_toml::RealtimeWsVersion;use codex_config::config_toml::ToolsToml;use codex_config::loader::project_trust_key;use codex_config::permissions_toml::FilesystemPermissionToml;use codex_config::permissions_toml::FilesystemPermissionsToml;use codex_config::permissions_toml::NetworkDomainPermissionToml;use codex_config::permissions_toml::NetworkDomainPermissionsToml;use codex_config::permissions_toml::NetworkMitmActionToml;use codex_config::permissions_toml::NetworkMitmHookToml;use codex_config::permissions_toml::NetworkMitmToml;use codex_config::permissions_toml::NetworkToml;use codex_config::permissions_toml::PermissionProfileToml;use codex_config::permissions_toml::PermissionsToml;use codex_config::permissions_toml::WorkspaceRootsToml;use codex_config::types::AppToolApproval;use codex_config::types::ApprovalsReviewer;use codex_config::types::BundledSkillsConfig;use codex_config::types::FeedbackConfigToml;use codex_config::types::HistoryPersistence;use codex_config::types::McpServerEnvVar;use codex_config::types::McpServerOAuthConfig;use codex_config::types::McpServerToolConfig;use codex_config::types::McpServerTransportConfig;use codex_config::types::MemoriesConfig;use codex_config::types::MemoriesToml;use codex_config::types::ModelAvailabilityNuxConfig;use codex_config::types::Notice;use codex_config::types::NotificationCondition;use codex_config::types::NotificationMethod;use codex_config::types::Notifications;use codex_config::types::OtelConfigToml;use codex_config::types::OtelExporterKind;use codex_config::types::SandboxWorkspaceWrite;use codex_config::types::SessionPickerViewMode;use codex_config::types::SkillsConfig;use codex_config::types::ToolSuggestDisabledTool;use codex_config::types::ToolSuggestDiscoverableType;use codex_config::types::Tui;use codex_config::types::TuiKeymap;use codex_config::types::TuiNotificationSettings;use codex_config::types::TuiPetAnchor;use codex_config::types::WindowsSandboxModeToml;use codex_config::types::WindowsToml;use codex_core_plugins::PluginsManager;use codex_exec_server::LOCAL_FS;use codex_features::Feature;use codex_features::FeaturesToml;use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;use codex_model_provider_info::WireApi;use codex_models_manager::bundled_models_response;use codex_network_proxy::NetworkMode;use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;use codex_protocol::config_types::ServiceTier;use codex_protocol::models::ActivePermissionProfile;use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY;use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;use codex_protocol::models::ManagedFileSystemPermissions;use codex_protocol::models::PermissionProfile;use codex_protocol::models::SandboxEnforcement;use codex_protocol::permissions::FileSystemAccessMode;use codex_protocol::permissions::FileSystemPath;use codex_protocol::permissions::FileSystemSandboxEntry;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::FileSystemSpecialPath;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::protocol::MultiAgentVersion;use codex_protocol::protocol::NetworkAccess;use codex_protocol::protocol::RealtimeVoice;use codex_protocol::protocol::SandboxPolicy;use serde::Deserialize;use tempfile::tempdir;use super::*;use core_test_support::PathBufExt;use core_test_support::PathExt;use core_test_support::TempDirExt;use core_test_support::test_absolute_path;use indexmap::IndexMap;use pretty_assertions::assert_eq;use rmcp::model::ElicitationCapability;use rmcp::model::FormElicitationCapability;use rmcp::model::UrlElicitationCapability;use codex_config::test_support::CloudConfigBundleFixture;use std::collections::BTreeMap;use std::collections::HashMap;use std::path::Path;use std::time::Duration;use tempfile::TempDir;fn stdio_mcp(command: &str) -> McpServerConfig {    McpServerConfig {        transport: McpServerTransportConfig::Stdio {            command: command.to_string(),            args: Vec::new(),            env: None,            env_vars: Vec::new(),            cwd: None,        },        environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),        enabled: true,        required: false,        supports_parallel_tool_calls: false,        disabled_reason: None,        startup_timeout_sec: None,        tool_timeout_sec: None,        default_tools_approval_mode: None,        enabled_tools: None,        disabled_tools: None,        scopes: None,        oauth: None,        oauth_resource: None,        tools: HashMap::new(),    }}fn http_mcp(url: &str) -> McpServerConfig {    McpServerConfig {        transport: McpServerTransportConfig::StreamableHttp {            url: url.to_string(),            bearer_token_env_var: None,            http_headers: None,            env_http_headers: None,        },        environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),        enabled: true,        required: false,        supports_parallel_tool_calls: false,        disabled_reason: None,        startup_timeout_sec: None,        tool_timeout_sec: None,        default_tools_approval_mode: None,        enabled_tools: None,        disabled_tools: None,        scopes: None,        oauth: None,        oauth_resource: None,        tools: HashMap::new(),    }}async fn derive_legacy_sandbox_policy_for_test(    cfg: &ConfigToml,    sandbox_mode_override: Option<SandboxMode>,    windows_sandbox_level: WindowsSandboxLevel,    active_project: Option<&ProjectConfig>,    permission_profile_constraint: Option<&Constrained<PermissionProfile>>,) -> SandboxPolicy {    let permission_profile = cfg        .derive_permission_profile(            sandbox_mode_override,            windows_sandbox_level,            active_project,            permission_profile_constraint,        )        .await;    permission_profile        .to_legacy_sandbox_policy(Path::new("/"))        .unwrap_or_else(|err| {            tracing::warn!(                error = %err,                "derived permission profile cannot be represented as a legacy sandbox policy; falling back to read-only"            );            SandboxPolicy::new_read_only_policy()        })}#[tokio::test]async fn load_config_normalizes_relative_cwd_override() -> std::io::Result<()> {    let expected_cwd = AbsolutePathBuf::relative_to_current_dir("nested")?;    let codex_home = tempdir()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides {            cwd: Some(PathBuf::from("nested")),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(config.cwd, expected_cwd);    Ok(())}#[tokio::test]async fn test_toml_parsing() {    let history_with_persistence = r#"[history]persistence = "save-all""#;    let history_with_persistence_cfg = toml::from_str::<ConfigToml>(history_with_persistence)        .expect("TOML deserialization should succeed");    assert_eq!(        Some(History {            persistence: HistoryPersistence::SaveAll,            max_bytes: None,        }),        history_with_persistence_cfg.history    );    let history_no_persistence = r#"[history]persistence = "none""#;    let history_no_persistence_cfg = toml::from_str::<ConfigToml>(history_no_persistence)        .expect("TOML deserialization should succeed");    assert_eq!(        Some(History {            persistence: HistoryPersistence::None,            max_bytes: None,        }),        history_no_persistence_cfg.history    );    let memories = r#"[memories]disable_on_external_context = truegenerate_memories = falseuse_memories = falsededicated_tools = truemax_raw_memories_for_consolidation = 512max_unused_days = 21max_rollout_age_days = 42max_rollouts_per_startup = 9min_rollout_idle_hours = 24min_rate_limit_remaining_percent = 12extract_model = "gpt-5-mini"consolidation_model = "gpt-5.2""#;    let memories_cfg =        toml::from_str::<ConfigToml>(memories).expect("TOML deserialization should succeed");    assert_eq!(        Some(MemoriesToml {            disable_on_external_context: Some(true),            generate_memories: Some(false),            use_memories: Some(false),            dedicated_tools: Some(true),            max_raw_memories_for_consolidation: Some(512),            max_unused_days: Some(21),            max_rollout_age_days: Some(42),            max_rollouts_per_startup: Some(9),            min_rollout_idle_hours: Some(24),            min_rate_limit_remaining_percent: Some(12),            extract_model: Some("gpt-5-mini".to_string()),            consolidation_model: Some("gpt-5.2".to_string()),        }),        memories_cfg.memories    );    let config = Config::load_from_base_config_with_overrides(        memories_cfg,        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load config from memories settings");    assert_eq!(        config.memories,        MemoriesConfig {            disable_on_external_context: true,            generate_memories: false,            use_memories: false,            dedicated_tools: true,            max_raw_memories_for_consolidation: 512,            max_unused_days: 21,            max_rollout_age_days: 42,            max_rollouts_per_startup: 9,            min_rollout_idle_hours: 24,            min_rate_limit_remaining_percent: 12,            extract_model: Some("gpt-5-mini".to_string()),            consolidation_model: Some("gpt-5.2".to_string()),        }    );    let legacy_memories_cfg =        toml::from_str::<ConfigToml>("[memories]\nno_memories_if_mcp_or_web_search = true\n")            .expect("legacy memories TOML should deserialize");    assert!(        MemoriesConfig::from(            legacy_memories_cfg                .memories                .expect("legacy memories config")        )        .disable_on_external_context    );}#[test]fn parses_bundled_skills_config() {    let cfg: ConfigToml = toml::from_str(        r#"[skills]include_instructions = false[skills.bundled]enabled = false"#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.skills,        Some(SkillsConfig {            bundled: Some(BundledSkillsConfig { enabled: false }),            include_instructions: Some(false),            config: Vec::new(),        })    );}#[test]fn tools_web_search_true_deserializes_to_none() {    let cfg: ConfigToml = toml::from_str(        r#"[tools]web_search = true"#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.tools,        Some(ToolsToml {            web_search: None,            experimental_request_user_input: None,        })    );}#[test]fn tools_web_search_false_deserializes_to_none() {    let cfg: ConfigToml = toml::from_str(        r#"[tools]web_search = false"#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.tools,        Some(ToolsToml {            web_search: None,            experimental_request_user_input: None,        })    );}#[test]fn tools_experimental_request_user_input_defaults_to_enabled() {    let cfg: ConfigToml = toml::from_str(        r#"[tools.experimental_request_user_input]"#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.tools,        Some(ToolsToml {            web_search: None,            experimental_request_user_input: Some(ExperimentalRequestUserInput { enabled: true }),        })    );}#[test]fn tools_experimental_request_user_input_can_be_disabled() {    let cfg: ConfigToml = toml::from_str(        r#"[tools.experimental_request_user_input]enabled = false"#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.tools,        Some(ToolsToml {            web_search: None,            experimental_request_user_input: Some(ExperimentalRequestUserInput { enabled: false }),        })    );}#[tokio::test]async fn load_config_resolves_experimental_request_user_input_enabled() -> std::io::Result<()> {    let codex_home = tempdir()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            tools: Some(ToolsToml {                web_search: None,                experimental_request_user_input: Some(ExperimentalRequestUserInput {                    enabled: false,                }),            }),            ..ConfigToml::default()        },        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert!(!config.experimental_request_user_input_enabled);    Ok(())}#[tokio::test]async fn load_config_resolves_code_mode_config() -> std::io::Result<()> {    let codex_home = tempdir()?;    let config_toml: ConfigToml = toml::from_str(        r#"[features.code_mode]enabled = trueexcluded_tool_namespaces = ["mcp__codex_apps", "multi_agent_v1"]"#,    )    .expect("TOML deserialization should succeed");    let config = Config::load_from_base_config_with_overrides(        config_toml,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.code_mode.excluded_tool_namespaces,        vec!["mcp__codex_apps".to_string(), "multi_agent_v1".to_string()]    );    assert!(config.features.enabled(Feature::CodeMode));    Ok(())}#[test]fn rejects_provider_auth_with_env_key() {    let err = toml::from_str::<ConfigToml>(        r#"[model_providers.corp]name = "Corp"env_key = "CORP_TOKEN"[model_providers.corp.auth]command = "print-token""#,    )    .unwrap_err();    assert!(        err.to_string()            .contains("model_providers.corp: provider auth cannot be combined with env_key")    );}#[test]fn rejects_provider_aws_for_custom_provider() {    let err = toml::from_str::<ConfigToml>(        r#"[model_providers.custom]name = "Custom Provider"[model_providers.custom.aws]profile = "codex-bedrock""#,    )    .unwrap_err();    assert!(        err.to_string().contains(            "model_providers.custom: provider aws is only supported for `amazon-bedrock`"        )    );}#[test]fn accepts_amazon_bedrock_aws_profile_override() {    let cfg = toml::from_str::<ConfigToml>(        r#"[model_providers.amazon-bedrock.aws]profile = "codex-bedrock"region = "us-west-2""#,    )    .expect("Amazon Bedrock AWS overrides should deserialize");    assert_eq!(        cfg.model_providers            .get("amazon-bedrock")            .and_then(|provider| provider.aws.as_ref())            .and_then(|aws| aws.profile.as_deref()),        Some("codex-bedrock")    );    assert_eq!(        cfg.model_providers            .get("amazon-bedrock")            .and_then(|provider| provider.aws.as_ref())            .and_then(|aws| aws.region.as_deref()),        Some("us-west-2")    );}#[tokio::test]async fn load_config_applies_amazon_bedrock_aws_profile_override() {    let cfg = toml::from_str::<ConfigToml>(        r#"model_provider = "amazon-bedrock"[model_providers.amazon-bedrock.aws]profile = "codex-bedrock"region = "us-west-2""#,    )    .expect("Amazon Bedrock AWS overrides should deserialize");    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load config");    assert_eq!(config.model_provider_id, "amazon-bedrock");    assert_eq!(        config            .model_provider            .aws            .as_ref()            .and_then(|aws| aws.profile.as_deref()),        Some("codex-bedrock")    );    assert_eq!(        config            .model_provider            .aws            .as_ref()            .and_then(|aws| aws.region.as_deref()),        Some("us-west-2")    );}#[tokio::test]async fn load_config_rejects_unsupported_amazon_bedrock_overrides() {    let cfg = toml::from_str::<ConfigToml>(        r#"model_provider = "amazon-bedrock"[model_providers.amazon-bedrock]name = "Custom Bedrock"base_url = "https://bedrock.example.com/v1"requires_openai_auth = truesupports_websockets = true[model_providers.amazon-bedrock.aws]profile = "codex-bedrock"region = "us-west-2""#,    )    .expect("Amazon Bedrock unsupported overrides should deserialize");    let err = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .unwrap_err();    assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);    assert!(err.to_string().contains(        "model_providers.amazon-bedrock only supports changing `aws.profile` and `aws.region`; other non-default provider fields are not supported"    ));}#[test]fn config_toml_deserializes_model_availability_nux() {    let toml = r#"[tui.model_availability_nux]"gpt-foo" = 2"gpt-bar" = 4"#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for TUI NUX");    assert_eq!(        cfg.tui.expect("tui config should deserialize"),        Tui {            notification_settings: TuiNotificationSettings::default(),            animations: true,            show_tooltips: true,            vim_mode_default: false,            raw_output_mode: false,            alternate_screen: AltScreenMode::default(),            status_line: None,            status_line_use_colors: true,            terminal_title: None,            theme: None,            pet: None,            pet_anchor: TuiPetAnchor::Composer,            session_picker_view: None,            keymap: TuiKeymap::default(),            model_availability_nux: ModelAvailabilityNuxConfig {                shown_count: HashMap::from([                    ("gpt-bar".to_string(), 4),                    ("gpt-foo".to_string(), 2),                ]),            },            terminal_resize_reflow_max_rows: None,        }    );}#[test]fn config_toml_status_line_use_colors_defaults_to_enabled() {    let toml = r#"[tui]"#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for TUI config");    assert!(        cfg.tui            .expect("tui config should deserialize")            .status_line_use_colors    );}#[test]fn config_toml_deserializes_status_line_use_colors_disabled() {    let toml = r#"[tui]status_line_use_colors = false"#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for TUI config");    assert!(        !cfg.tui            .expect("tui config should deserialize")            .status_line_use_colors    );}#[test]fn config_toml_deserializes_terminal_resize_reflow_config() {    let toml = r#"[tui]terminal_resize_reflow_max_rows = 9000"#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for resize reflow config");    assert_eq!(        cfg.tui            .expect("tui config should deserialize")            .terminal_resize_reflow_max_rows,        Some(9000)    );}#[tokio::test]async fn runtime_config_defaults_model_availability_nux() {    let cfg = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load config");    assert_eq!(        cfg.model_availability_nux,        ModelAvailabilityNuxConfig::default()    );}#[test]fn test_tui_vim_mode_default_defaults_to_false() {    let toml = r#"        [tui]    "#;    let parsed: ConfigToml = toml::from_str(toml).expect("deserialize empty [tui] table");    assert!(        !parsed            .tui            .expect("config should include tui section")            .vim_mode_default    );}#[test]fn test_tui_vim_mode_default_true() {    let toml = r#"        [tui]        vim_mode_default = true    "#;    let parsed: ConfigToml = toml::from_str(toml).expect("deserialize vim_mode_default=true");    assert!(        parsed            .tui            .expect("config should include tui section")            .vim_mode_default    );}#[test]fn test_tui_raw_output_mode_defaults_to_false() {    let toml = r#"        [tui]    "#;    let parsed: ConfigToml = toml::from_str(toml).expect("deserialize empty [tui] table");    assert!(        !parsed            .tui            .expect("config should include tui section")            .raw_output_mode    );}#[test]fn test_tui_raw_output_mode_true() {    let toml = r#"        [tui]        raw_output_mode = true    "#;    let parsed: ConfigToml = toml::from_str(toml).expect("deserialize raw_output_mode=true");    assert!(        parsed            .tui            .expect("config should include tui section")            .raw_output_mode    );}#[tokio::test]async fn runtime_config_uses_tui_raw_output_mode() {    let toml = r#"        [tui]        raw_output_mode = true    "#;    let cfg_toml: ConfigToml = toml::from_str(toml).expect("deserialize raw_output_mode=true");    let cfg = Config::load_from_base_config_with_overrides(        cfg_toml,        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load config");    assert!(cfg.tui_raw_output_mode);}#[test]fn config_toml_deserializes_permission_profiles() {    let toml = r#"default_permissions = "dev"[permissions.dev]description = "Day-to-day workspace access."[permissions.dev.workspace_roots]"~/code/openai" = true"~/code/ignored" = false[permissions.dev.filesystem]":minimal" = "read""/tmp/secret.env" = "deny"[permissions.dev.filesystem.":workspace_roots"]"." = "write""docs" = "read"[permissions.dev.network]enabled = trueproxy_url = "http://127.0.0.1:43128"enable_socks5 = falseallow_upstream_proxy = falsemode = "full"[permissions.dev.network.domains]"openai.com" = "allow"[permissions.dev.network.mitm.hooks.github_write]host = "api.github.com"methods = ["POST", "PUT"]path_prefixes = ["/repos/openai/"]action = ["strip_auth"][permissions.dev.network.mitm.actions.strip_auth]strip_request_headers = ["authorization"]"#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for permissions profiles");    assert_eq!(cfg.default_permissions.as_deref(), Some("dev"));    assert_eq!(        cfg.permissions.expect("[permissions] should deserialize"),        PermissionsToml {            entries: BTreeMap::from([(                "dev".to_string(),                PermissionProfileToml {                    description: Some("Day-to-day workspace access.".to_string()),                    extends: None,                    workspace_roots: Some(WorkspaceRootsToml {                        entries: BTreeMap::from([                            ("~/code/ignored".to_string(), false),                            ("~/code/openai".to_string(), true),                        ]),                    }),                    filesystem: Some(FilesystemPermissionsToml {                        glob_scan_max_depth: None,                        entries: BTreeMap::from([                            (                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            ),                            (                                "/tmp/secret.env".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Deny),                            ),                            (                                ":workspace_roots".to_string(),                                FilesystemPermissionToml::Scoped(BTreeMap::from([                                    (".".to_string(), FileSystemAccessMode::Write),                                    ("docs".to_string(), FileSystemAccessMode::Read),                                ])),                            ),                        ]),                    }),                    network: Some(NetworkToml {                        enabled: Some(true),                        proxy_url: Some("http://127.0.0.1:43128".to_string()),                        enable_socks5: Some(false),                        socks_url: None,                        enable_socks5_udp: None,                        allow_upstream_proxy: Some(false),                        dangerously_allow_non_loopback_proxy: None,                        dangerously_allow_all_unix_sockets: None,                        mode: Some(NetworkMode::Full),                        domains: Some(NetworkDomainPermissionsToml {                            entries: BTreeMap::from([(                                "openai.com".to_string(),                                NetworkDomainPermissionToml::Allow,                            )]),                        }),                        unix_sockets: None,                        allow_local_binding: None,                        mitm: Some(NetworkMitmToml {                            hooks: Some(IndexMap::from([(                                "github_write".to_string(),                                NetworkMitmHookToml {                                    host: "api.github.com".to_string(),                                    methods: vec!["POST".to_string(), "PUT".to_string()],                                    path_prefixes: vec!["/repos/openai/".to_string()],                                    query: BTreeMap::new(),                                    headers: BTreeMap::new(),                                    body: None,                                    action: vec!["strip_auth".to_string()],                                },                            )])),                            actions: Some(IndexMap::from([(                                "strip_auth".to_string(),                                NetworkMitmActionToml {                                    strip_request_headers: vec!["authorization".to_string()],                                    inject_request_headers: Vec::new(),                                },                            )])),                        }),                    }),                },            )]),        }    );}#[test]fn config_toml_rejects_empty_mitm_action_reference_list() {    let toml = r#"default_permissions = "workspace"[permissions.workspace.network.mitm.hooks.github_write]host = "api.github.com"methods = ["POST"]path_prefixes = ["/repos/openai/"]action = [][permissions.workspace.network.mitm.actions.strip_auth]strip_request_headers = ["authorization"]"#;    let err =        toml::from_str::<ConfigToml>(toml).expect_err("empty MITM action refs should fail closed");    assert!(        err.to_string()            .contains("network.mitm.hooks.github_write.action must not be empty"),        "{err}"    );}#[test]fn config_toml_rejects_empty_mitm_action_definition() {    let toml = r#"default_permissions = "workspace"[permissions.workspace.network.mitm.hooks.github_write]host = "api.github.com"methods = ["POST"]path_prefixes = ["/repos/openai/"]action = ["strip_auth"][permissions.workspace.network.mitm.actions.strip_auth]"#;    let err = toml::from_str::<ConfigToml>(toml)        .expect_err("empty MITM action definitions should fail closed");    assert!(        err.to_string()            .contains("network.mitm.actions.strip_auth must define at least one operation"),        "{err}"    );}#[test]fn permissions_profile_network_to_proxy_config_preserves_mitm_hooks() {    let network = NetworkToml {        mode: Some(NetworkMode::Full),        mitm: Some(NetworkMitmToml {            hooks: Some(IndexMap::from([(                "github_write".to_string(),                NetworkMitmHookToml {                    host: "api.github.com".to_string(),                    methods: vec!["POST".to_string()],                    path_prefixes: vec!["/repos/openai/".to_string()],                    action: vec!["strip_auth".to_string()],                    ..NetworkMitmHookToml::default()                },            )])),            actions: Some(IndexMap::from([(                "strip_auth".to_string(),                NetworkMitmActionToml {                    strip_request_headers: vec!["authorization".to_string()],                    inject_request_headers: Vec::new(),                },            )])),        }),        ..NetworkToml::default()    };    let config = network.to_network_proxy_config();    assert_eq!(config.network.mode, NetworkMode::Full);    assert!(config.network.mitm);    assert_eq!(config.network.mitm_hooks.len(), 1);    assert_eq!(config.network.mitm_hooks[0].host, "api.github.com");    assert_eq!(        config.network.mitm_hooks[0].matcher.methods,        vec!["POST".to_string()]    );    assert_eq!(        config.network.mitm_hooks[0].actions.strip_request_headers,        vec!["authorization".to_string()]    );}#[test]fn permissions_profile_network_to_proxy_config_preserves_mitm_hook_declaration_order() {    let toml = r#"default_permissions = "workspace"[permissions.workspace.network.mitm.actions.noop]strip_request_headers = ["authorization"][permissions.workspace.network.mitm.hooks.z_first]host = "api.github.com"methods = ["POST"]path_prefixes = ["/repos/openai/"]action = ["noop"][permissions.workspace.network.mitm.hooks.a_second]host = "api.github.com"methods = ["POST"]path_prefixes = ["/repos/"]action = ["noop"]"#;    let cfg: ConfigToml = toml::from_str(toml).expect("permissions profile should deserialize");    let permissions = cfg.permissions.expect("permissions should deserialize");    let network = permissions        .entries        .get("workspace")        .expect("workspace profile should exist")        .network        .as_ref()        .expect("network profile should exist");    let config = network.to_network_proxy_config();    assert_eq!(config.network.mitm_hooks.len(), 2);    assert_eq!(        config.network.mitm_hooks[0].matcher.path_prefixes,        vec!["/repos/openai/".to_string()]    );    assert_eq!(        config.network.mitm_hooks[1].matcher.path_prefixes,        vec!["/repos/".to_string()]    );}#[tokio::test]async fn permissions_profiles_proxy_policy_does_not_start_managed_network_proxy_without_feature()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: Some(NetworkToml {                            enabled: Some(true),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Enabled    );    assert!(        config.permissions.network.is_none(),        "bare profile network.enabled should not start the managed network proxy"    );    Ok(())}#[tokio::test]async fn permissions_profiles_proxy_policy_starts_managed_network_proxy() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: Some(NetworkToml {                            enabled: Some(true),                            proxy_url: Some("http://127.0.0.1:43128".to_string()),                            enable_socks5: Some(false),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Enabled    );    assert!(        config.permissions.network.is_none(),        "profile proxy policy should not start the managed network proxy without the feature"    );    Ok(())}#[tokio::test]async fn network_proxy_feature_is_no_op_without_sandbox_network() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            features: Some(toml::from_str("network_proxy = true").expect("valid features")),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Restricted    );    assert!(        config.permissions.network.is_none(),        "network_proxy should not start the managed network proxy while network access is off"    );    Ok(())}#[tokio::test]async fn network_proxy_feature_matrix_preserves_sandbox_network_semantics() -> std::io::Result<()> {    #[derive(Clone, Copy)]    enum Surface {        PermissionProfile,        LegacyWorkspaceWrite,    }    struct Case {        name: &'static str,        surface: Surface,        network_enabled: bool,        proxy_enabled: bool,        expected_network_policy: NetworkSandboxPolicy,    }    let cases = [        Case {            name: "permission profile network disabled without proxy",            surface: Surface::PermissionProfile,            network_enabled: false,            proxy_enabled: false,            expected_network_policy: NetworkSandboxPolicy::Restricted,        },        Case {            name: "permission profile network disabled with proxy",            surface: Surface::PermissionProfile,            network_enabled: false,            proxy_enabled: true,            expected_network_policy: NetworkSandboxPolicy::Restricted,        },        Case {            name: "permission profile network enabled without proxy",            surface: Surface::PermissionProfile,            network_enabled: true,            proxy_enabled: false,            expected_network_policy: NetworkSandboxPolicy::Enabled,        },        Case {            name: "permission profile network enabled with proxy",            surface: Surface::PermissionProfile,            network_enabled: true,            proxy_enabled: true,            expected_network_policy: NetworkSandboxPolicy::Enabled,        },        Case {            name: "legacy workspace write network disabled without proxy",            surface: Surface::LegacyWorkspaceWrite,            network_enabled: false,            proxy_enabled: false,            expected_network_policy: NetworkSandboxPolicy::Restricted,        },        Case {            name: "legacy workspace write network disabled with proxy",            surface: Surface::LegacyWorkspaceWrite,            network_enabled: false,            proxy_enabled: true,            expected_network_policy: NetworkSandboxPolicy::Restricted,        },        Case {            name: "legacy workspace write network enabled without proxy",            surface: Surface::LegacyWorkspaceWrite,            network_enabled: true,            proxy_enabled: false,            expected_network_policy: NetworkSandboxPolicy::Enabled,        },        Case {            name: "legacy workspace write network enabled with proxy",            surface: Surface::LegacyWorkspaceWrite,            network_enabled: true,            proxy_enabled: true,            expected_network_policy: NetworkSandboxPolicy::Enabled,        },    ];    for case in cases {        let codex_home = TempDir::new()?;        let cwd = TempDir::new()?;        std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;        let features = case            .proxy_enabled            .then(|| toml::from_str("network_proxy = true").expect("valid features"));        let base_config = match case.surface {            Surface::PermissionProfile => ConfigToml {                default_permissions: Some("dev".to_string()),                permissions: Some(PermissionsToml {                    entries: BTreeMap::from([(                        "dev".to_string(),                        PermissionProfileToml {                            description: None,                            extends: None,                            workspace_roots: None,                            filesystem: Some(FilesystemPermissionsToml {                                glob_scan_max_depth: None,                                entries: BTreeMap::from([(                                    ":minimal".to_string(),                                    FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                                )]),                            }),                            network: Some(NetworkToml {                                enabled: Some(case.network_enabled),                                ..Default::default()                            }),                        },                    )]),                }),                features,                ..Default::default()            },            Surface::LegacyWorkspaceWrite => ConfigToml {                sandbox_mode: Some(SandboxMode::WorkspaceWrite),                sandbox_workspace_write: Some(SandboxWorkspaceWrite {                    network_access: case.network_enabled,                    ..Default::default()                }),                windows: Some(WindowsToml {                    sandbox: Some(WindowsSandboxModeToml::Elevated),                    sandbox_private_desktop: None,                }),                features,                ..Default::default()            },        };        let config = Config::load_from_base_config_with_overrides(            base_config,            ConfigOverrides {                cwd: Some(cwd.path().to_path_buf()),                ..Default::default()            },            codex_home.abs(),        )        .await?;        assert_eq!(            config.permissions.network_sandbox_policy(),            case.expected_network_policy,            "{}",            case.name        );        assert_eq!(            config.permissions.network.is_some(),            case.network_enabled && case.proxy_enabled,            "{}",            case.name        );    }    Ok(())}#[tokio::test]async fn network_proxy_cli_overrides_merge_toggle_with_proxy_config() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"sandbox_mode = "workspace-write"[sandbox_workspace_write]network_access = true[windows]sandbox = "elevated""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .cli_overrides(vec![            (                "features.network_proxy.enabled".to_string(),                toml::Value::Boolean(true),            ),            (                "features.network_proxy.enable_socks5".to_string(),                toml::Value::Boolean(false),            ),        ])        .harness_overrides(ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        })        .build()        .await?;    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Enabled    );    let network = config        .permissions        .network        .as_ref()        .expect("network_proxy should start the managed network proxy");    assert_eq!(network.proxy_host_and_port(), "127.0.0.1:3128");    assert!(!network.socks_enabled());    Ok(())}#[tokio::test]async fn experimental_network_requirements_enable_proxy_without_feature() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[experimental_network]enabled = true"#,            ),        )        .build()        .await?;    assert!(!config.features.enabled(Feature::NetworkProxy));    assert!(config.managed_network_requirements_enabled());    assert!(        config            .permissions            .network            .as_ref()            .expect("experimental_network should configure the managed proxy")            .enabled()    );    Ok(())}#[tokio::test]async fn network_proxy_feature_uses_profile_network_proxy_settings() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            features: Some(toml::from_str("network_proxy = true").expect("valid features")),            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: Some(NetworkToml {                            enabled: Some(true),                            proxy_url: Some("http://127.0.0.1:43128".to_string()),                            enable_socks5: Some(false),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Enabled    );    let network = config        .permissions        .network        .as_ref()        .expect("network_proxy should start the managed network proxy");    assert_eq!(network.proxy_host_and_port(), "127.0.0.1:43128");    assert!(!network.socks_enabled());    Ok(())}#[tokio::test]async fn disabled_network_proxy_feature_does_not_start_profile_proxy_policy() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            features: Some(                toml::from_str(                    r#"[network_proxy]enabled = false"#,                )                .expect("valid features"),            ),            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: Some(NetworkToml {                            enabled: Some(true),                            proxy_url: Some("http://127.0.0.1:43128".to_string()),                            enable_socks5: Some(false),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert!(!config.features.enabled(Feature::NetworkProxy));    assert!(        config.permissions.network.is_none(),        "disabled feature should keep profile proxy policy from starting the managed proxy"    );    Ok(())}#[tokio::test]async fn permissions_profiles_network_disabled_by_default_does_not_start_proxy()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: Some(NetworkToml {                            domains: Some(NetworkDomainPermissionsToml {                                entries: BTreeMap::from([(                                    "openai.com".to_string(),                                    NetworkDomainPermissionToml::Allow,                                )]),                            }),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert!(config.permissions.network.is_none());    Ok(())}#[tokio::test]async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::create_dir_all(cwd.path().join("docs"))?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let cfg = ConfigToml {        default_permissions: Some("dev".to_string()),        permissions: Some(PermissionsToml {            entries: BTreeMap::from([(                "dev".to_string(),                PermissionProfileToml {                    description: None,                    extends: None,                    workspace_roots: None,                    filesystem: Some(FilesystemPermissionsToml {                        glob_scan_max_depth: None,                        entries: BTreeMap::from([                            (                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            ),                            (                                ":workspace_roots".to_string(),                                FilesystemPermissionToml::Scoped(BTreeMap::from([                                    (".".to_string(), FileSystemAccessMode::Write),                                    ("docs".to_string(), FileSystemAccessMode::Read),                                ])),                            ),                        ]),                    }),                    network: None,                },            )]),        }),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let cwd_root = cwd.path().abs();    assert_eq!(        config.permissions.file_system_sandbox_policy(),        FileSystemSandboxPolicy::restricted(vec![            FileSystemSandboxEntry {                path: FileSystemPath::Special {                    value: FileSystemSpecialPath::Minimal,                },                access: FileSystemAccessMode::Read,            },            FileSystemSandboxEntry {                path: FileSystemPath::Path {                    path: cwd_root.clone(),                },                access: FileSystemAccessMode::Write,            },            FileSystemSandboxEntry {                path: FileSystemPath::Path {                    path: cwd_root.join("docs"),                },                access: FileSystemAccessMode::Read,            },        ]),    );    assert_eq!(        &config.legacy_sandbox_policy(),        &SandboxPolicy::WorkspaceWrite {            writable_roots: vec![],            network_access: false,            exclude_tmpdir_env_var: true,            exclude_slash_tmp: true,        }    );    assert!(        !config            .permissions            .file_system_sandbox_policy()            .can_write_path_with_cwd(&cwd.path().join(".git"), cwd.path())    );    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Restricted    );    assert_eq!(        config            .permissions            .active_permission_profile()            .as_ref()            .map(|active| active.id.as_str()),        Some("dev")    );    Ok(())}#[tokio::test]async fn default_permissions_extended_profile_preserves_parent_metadata() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([                    (                        "base".to_string(),                        PermissionProfileToml {                            description: None,                            extends: None,                            workspace_roots: None,                            filesystem: Some(FilesystemPermissionsToml {                                glob_scan_max_depth: None,                                entries: BTreeMap::from([(                                    ":minimal".to_string(),                                    FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                                )]),                            }),                            network: None,                        },                    ),                    (                        "dev".to_string(),                        PermissionProfileToml {                            description: None,                            extends: Some("base".to_string()),                            workspace_roots: None,                            filesystem: None,                            network: None,                        },                    ),                ]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.permissions.active_permission_profile(),        Some(ActivePermissionProfile {            id: "dev".to_string(),            extends: Some("base".to_string()),        })    );    Ok(())}#[tokio::test]async fn permission_profile_override_populates_runtime_permissions() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let permission_profile = PermissionProfile::Disabled;    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            permission_profile: Some(permission_profile.clone()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.permissions.effective_permission_profile(),        permission_profile    );    assert_eq!(config.permissions.active_permission_profile(), None);    assert_eq!(        &config.legacy_sandbox_policy(),        &SandboxPolicy::DangerFullAccess    );    Ok(())}#[test]fn permission_snapshot_setter_preserves_permission_constraints() {    let initial_profile = PermissionProfile::read_only();    let mut permissions = Permissions::from_approval_and_profile(        Constrained::allow_any(AskForApproval::Never),        Constrained::allow_only(initial_profile.clone()),    )    .expect("initial permissions should satisfy constraints");    let err = permissions        .set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(            PermissionProfile::workspace_write(),            ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE),        ))        .expect_err("workspace profile should violate read-only constraint");    assert_eq!(permissions.permission_profile(), &initial_profile);    assert_eq!(permissions.active_permission_profile(), None);    assert!(        matches!(err, ConstraintError::InvalidValue { .. }),        "expected invalid value constraint error, got {err:?}"    );}#[tokio::test]async fn permission_profile_override_preserves_managed_unrestricted_filesystem()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let permission_profile = PermissionProfile::Managed {        file_system: ManagedFileSystemPermissions::Unrestricted,        network: NetworkSandboxPolicy::Restricted,    };    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            permission_profile: Some(permission_profile.clone()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.permissions.effective_permission_profile(),        permission_profile    );    assert_eq!(        &config.legacy_sandbox_policy(),        &SandboxPolicy::ExternalSandbox {            network_access: NetworkAccess::Restricted,        }    );    Ok(())}#[tokio::test]async fn managed_unrestricted_permission_profile_still_enables_network_requirements()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let permission_profile = PermissionProfile::Managed {        file_system: ManagedFileSystemPermissions::Unrestricted,        network: NetworkSandboxPolicy::Enabled,    };    let mut config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            permission_profile: Some(permission_profile),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        &config.legacy_sandbox_policy(),        &SandboxPolicy::DangerFullAccess,        "the legacy projection is intentionally lossy for managed unrestricted profiles"    );    let layers = config        .config_layer_stack        .get_layers(            ConfigLayerStackOrdering::LowestPrecedenceFirst,            /*include_disabled*/ true,        )        .into_iter()        .cloned()        .collect();    let mut requirements = config.config_layer_stack.requirements().clone();    requirements.network = Some(Sourced::new(        codex_config::NetworkConstraints {            enabled: Some(true),            ..Default::default()        },        RequirementSource::LegacyManagedConfigTomlFromMdm,    ));    let mut requirements_toml = config.config_layer_stack.requirements_toml().clone();    requirements_toml.network = Some(codex_config::NetworkRequirementsToml {        enabled: Some(true),        ..Default::default()    });    config.config_layer_stack = ConfigLayerStack::new(layers, requirements, requirements_toml)        .expect("config layer stack with network requirements");    assert!(config.managed_network_requirements_enabled());    Ok(())}#[tokio::test]async fn permission_profile_override_keeps_memories_root_out_of_legacy_projection()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let permission_profile = PermissionProfile::from_runtime_permissions(        &FileSystemSandboxPolicy::restricted(vec![            FileSystemSandboxEntry {                path: FileSystemPath::Special {                    value: FileSystemSpecialPath::Root,                },                access: FileSystemAccessMode::Read,            },            FileSystemSandboxEntry {                path: FileSystemPath::Special {                    value: FileSystemSpecialPath::project_roots(/*subpath*/ None),                },                access: FileSystemAccessMode::Write,            },        ]),        NetworkSandboxPolicy::Restricted,    );    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            permission_profile: Some(permission_profile),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let memories_root = codex_home.path().join("memories").abs();    assert!(        !config            .permissions            .file_system_sandbox_policy()            .can_write_path_with_cwd(memories_root.as_path(), cwd.path())    );    assert_eq!(        &config.legacy_sandbox_policy(),        &SandboxPolicy::WorkspaceWrite {            writable_roots: vec![],            network_access: false,            exclude_tmpdir_env_var: true,            exclude_slash_tmp: true,        }    );    Ok(())}#[tokio::test]async fn permission_profile_override_preserves_configured_network_policy_without_starting_proxy()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let permission_profile = PermissionProfile::Disabled;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: Some(NetworkToml {                            enabled: Some(true),                            proxy_url: Some("http://127.0.0.1:43128".to_string()),                            enable_socks5: Some(false),                            allow_upstream_proxy: Some(false),                            domains: Some(NetworkDomainPermissionsToml {                                entries: BTreeMap::from([(                                    "openai.com".to_string(),                                    NetworkDomainPermissionToml::Allow,                                )]),                            }),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            permission_profile: Some(permission_profile.clone()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert!(        config.permissions.network.is_none(),        "profile network.enabled should not start the managed network proxy"    );    assert_eq!(        config.permissions.effective_permission_profile(),        permission_profile    );    Ok(())}#[tokio::test]async fn workspace_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let extra_root = TempDir::new()?;    tokio::fs::write(cwd.path().join(".git"), "gitdir: nowhere").await?;    tokio::fs::write(extra_root.path().join(".git"), "gitdir: nowhere").await?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: Some(2),                            entries: BTreeMap::from([(                                ":workspace_roots".to_string(),                                FilesystemPermissionToml::Scoped(BTreeMap::from([                                    (".".to_string(), FileSystemAccessMode::Write),                                    ("**/*.env".to_string(), FileSystemAccessMode::Deny),                                ])),                            )]),                        }),                        network: None,                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            additional_writable_roots: vec![extra_root.path().to_path_buf()],            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config            .permissions            .file_system_sandbox_policy()            .glob_scan_max_depth,        Some(2)    );    for root in [cwd.path(), extra_root.path()] {        let expected_pattern = AbsolutePathBuf::resolve_path_against_base("**/*.env", root)            .to_string_lossy()            .into_owned();        assert!(            config                .permissions                .file_system_sandbox_policy()                .entries                .contains(&FileSystemSandboxEntry {                    path: FileSystemPath::GlobPattern {                        pattern: expected_pattern,                    },                    access: FileSystemAccessMode::Deny,                })        );    }    assert!(        !config            .permissions            .file_system_sandbox_policy()            .entries            .iter()            .any(|entry| matches!(                &entry.path,                FileSystemPath::Special {                    value: FileSystemSpecialPath::ProjectRoots { subpath: Some(subpath) },                } if subpath == std::path::Path::new("**/*.env")            )),        "glob should compile to a filesystem pattern entry, not a literal filesystem entry"    );    Ok(())}#[tokio::test]async fn permissions_profiles_require_default_permissions() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let err = Config::load_from_base_config_with_overrides(        ConfigToml {            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: None,                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await    .expect_err("missing default_permissions should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "config defines `[permissions]` profiles but does not set `default_permissions`"    );    Ok(())}#[tokio::test]async fn default_permissions_can_select_builtin_profile_without_permissions_table()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert!(config.explicit_permission_profile_mode);    assert!(config.custom_permission_profiles.is_empty());    let policy = config.permissions.file_system_sandbox_policy();    assert_eq!(        config            .permissions            .active_permission_profile()            .as_ref()            .map(|active| active.id.as_str()),        Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE)    );    assert!(        policy.can_write_path_with_cwd(cwd.path(), cwd.path()),        "expected :workspace to allow writing the project root, policy: {policy:?}"    );    assert!(        !policy.can_write_path_with_cwd(&cwd.path().join(".git"), cwd.path()),        "expected :workspace to protect project metadata, policy: {policy:?}"    );    Ok(())}#[tokio::test]async fn default_permissions_read_only_keeps_add_dir_read_only() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let extra_root = TempDir::new()?;    let extra_root = extra_root.path().abs();    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY.to_string()),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            additional_writable_roots: vec![extra_root.to_path_buf()],            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    assert!(        !policy.can_write_path_with_cwd(extra_root.as_path(), cwd.path()),        "expected :read-only to stay read-only for runtime workspace roots, policy: {policy:?}"    );    assert_eq!(        config.permissions.active_permission_profile(),        Some(ActivePermissionProfile::new(            BUILT_IN_PERMISSION_PROFILE_READ_ONLY,        ))    );    Ok(())}#[tokio::test]async fn workspace_profile_applies_rules_to_runtime_and_profile_workspace_roots()-> std::io::Result<()> {    let temp_dir = TempDir::new()?;    let codex_home = temp_dir.path().join("codex-home");    let cwd = temp_dir.path().join("frontend");    let runtime_root = temp_dir.path().join("backend");    let profile_root = temp_dir.path().join("shared");    for root in [&cwd, &runtime_root, &profile_root] {        std::fs::create_dir_all(root.join(".git"))?;        std::fs::create_dir_all(root.join(".codex"))?;    }    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: Some(WorkspaceRootsToml {                            entries: BTreeMap::from([(                                profile_root.to_string_lossy().into_owned(),                                true,                            )]),                        }),                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":workspace_roots".to_string(),                                FilesystemPermissionToml::Scoped(BTreeMap::from([                                    (".".to_string(), FileSystemAccessMode::Write),                                    (".git".to_string(), FileSystemAccessMode::Read),                                    (".codex".to_string(), FileSystemAccessMode::Read),                                ])),                            )]),                        }),                        network: None,                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.clone()),            additional_writable_roots: vec![runtime_root.clone()],            ..Default::default()        },        codex_home.abs(),    )    .await?;    let cwd_abs = cwd.abs();    let runtime_root_abs = runtime_root.abs();    let profile_root_abs = profile_root.abs();    assert_eq!(        config.workspace_roots,        vec![cwd_abs.clone(), runtime_root_abs.clone()]    );    assert_eq!(        config.permissions.workspace_roots(),        &[cwd_abs.clone(), runtime_root_abs.clone()]    );    assert_eq!(        config.effective_workspace_roots(),        vec![            cwd_abs.clone(),            runtime_root_abs.clone(),            profile_root_abs.clone()        ]    );    let policy = config.permissions.file_system_sandbox_policy();    for root in [cwd_abs, runtime_root_abs, profile_root_abs.clone()] {        assert!(            policy.can_write_path_with_cwd(root.as_path(), cwd.as_path()),            "expected workspace root to be writable, policy: {policy:?}"        );        assert!(            !policy.can_write_path_with_cwd(&root.join(".git"), cwd.as_path()),            "expected .git carveout under {root:?}, policy: {policy:?}"        );        assert!(            !policy.can_write_path_with_cwd(&root.join(".codex"), cwd.as_path()),            "expected .codex carveout under {root:?}, policy: {policy:?}"        );    }    assert_eq!(        config.permissions.profile_workspace_roots(),        std::slice::from_ref(&profile_root_abs)    );    assert_eq!(        config.permissions.active_permission_profile(),        Some(ActivePermissionProfile::new("dev"))    );    Ok(())}#[tokio::test]async fn explicit_builtin_workspace_profile_ignores_legacy_workspace_write_settings()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let extra_root = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),            sandbox_workspace_write: Some(SandboxWorkspaceWrite {                writable_roots: vec![extra_root.path().abs()],                network_access: true,                exclude_tmpdir_env_var: true,                exclude_slash_tmp: true,            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Restricted    );    assert!(        !policy.entries.iter().any(|entry| matches!(            &entry.path,            FileSystemPath::Path { path } if path.as_path() == extra_root.path()        )),        "explicit :workspace should not inherit sandbox_workspace_write roots as concrete grants, \         policy: {policy:?}"    );    Ok(())}#[tokio::test]async fn default_permissions_profile_can_extend_builtin_workspace() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("workspace-with-network".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "workspace-with-network".to_string(),                    PermissionProfileToml {                        description: None,                        extends: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":tmpdir".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: Some(NetworkToml {                            enabled: Some(true),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    assert!(        policy.can_write_path_with_cwd(cwd.path(), cwd.path()),        "expected profile extending :workspace to keep project-root writes, policy: {policy:?}"    );    assert!(        !policy.can_write_path_with_cwd(&cwd.path().join(".git"), cwd.path()),        "expected profile extending :workspace to keep metadata carveouts, policy: {policy:?}"    );    assert!(        policy.entries.iter().any(|entry| matches!(            entry,            FileSystemSandboxEntry {                path: FileSystemPath::Special {                    value: FileSystemSpecialPath::SlashTmp,                },                access: FileSystemAccessMode::Write,            }        )),        "expected profile extending :workspace to keep inherited :slash_tmp writes, policy: {policy:?}"    );    assert!(        policy.entries.iter().any(|entry| matches!(            entry,            FileSystemSandboxEntry {                path: FileSystemPath::Special {                    value: FileSystemSpecialPath::Tmpdir,                },                access: FileSystemAccessMode::Read,            }        )),        "expected child :tmpdir read entry to replace the inherited write entry, policy: {policy:?}"    );    assert!(        !policy.entries.iter().any(|entry| matches!(            entry,            FileSystemSandboxEntry {                path: FileSystemPath::Special {                    value: FileSystemSpecialPath::Tmpdir,                },                access: FileSystemAccessMode::Write,            }        )),        "expected inherited :tmpdir write entry to be removed, policy: {policy:?}"    );    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Enabled    );    assert_eq!(        config.permissions.active_permission_profile(),        Some(ActivePermissionProfile {            id: "workspace-with-network".to_string(),            extends: Some(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),        })    );    Ok(())}#[tokio::test]async fn default_permissions_profile_can_extend_builtin_read_only() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("read-only-with-network".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "read-only-with-network".to_string(),                    PermissionProfileToml {                        description: None,                        extends: Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY.to_string()),                        workspace_roots: None,                        filesystem: None,                        network: Some(NetworkToml {                            enabled: Some(true),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    assert!(        policy.can_read_path_with_cwd(cwd.path(), cwd.path()),        "expected profile extending :read-only to keep read access, policy: {policy:?}"    );    assert!(        !policy.can_write_path_with_cwd(cwd.path(), cwd.path()),        "expected profile extending :read-only to stay non-writable, policy: {policy:?}"    );    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Enabled    );    assert_eq!(        config.permissions.active_permission_profile(),        Some(ActivePermissionProfile {            id: "read-only-with-network".to_string(),            extends: Some(BUILT_IN_PERMISSION_PROFILE_READ_ONLY.to_string()),        })    );    Ok(())}#[tokio::test]async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let project_key = cwd.path().to_string_lossy().to_string();    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            projects: Some(HashMap::from([(                project_key,                ProjectConfig {                    trust_level: Some(TrustLevel::Trusted),                },            )])),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    assert_eq!(        config            .permissions            .active_permission_profile()            .as_ref()            .map(|active| active.id.as_str()),        Some(if cfg!(target_os = "windows") {            BUILT_IN_PERMISSION_PROFILE_READ_ONLY        } else {            BUILT_IN_PERMISSION_PROFILE_WORKSPACE        })    );    if cfg!(target_os = "windows") {        assert!(            !policy.can_write_path_with_cwd(cwd.path(), cwd.path()),            "expected trusted project fallback to stay read-only without Windows sandbox support, policy: {policy:?}"        );    } else {        assert!(            policy.can_write_path_with_cwd(cwd.path(), cwd.path()),            "expected trusted project fallback to use :workspace, policy: {policy:?}"        );        assert!(            !policy.can_write_path_with_cwd(&cwd.path().join(".codex"), cwd.path()),            "expected :workspace metadata carveouts, policy: {policy:?}"        );    }    Ok(())}#[tokio::test]async fn empty_config_defaults_to_builtin_profile_for_untrusted_project() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let project_key = cwd.path().to_string_lossy().to_string();    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            projects: Some(HashMap::from([(                project_key,                ProjectConfig {                    trust_level: Some(TrustLevel::Untrusted),                },            )])),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    assert_eq!(        config            .permissions            .active_permission_profile()            .as_ref()            .map(|active| active.id.as_str()),        Some(if cfg!(target_os = "windows") {            BUILT_IN_PERMISSION_PROFILE_READ_ONLY        } else {            BUILT_IN_PERMISSION_PROFILE_WORKSPACE        })    );    assert!(        policy.can_read_path_with_cwd(cwd.path(), cwd.path()),        "expected untrusted project fallback to allow reads, policy: {policy:?}"    );    if cfg!(target_os = "windows") {        assert!(            !policy.can_write_path_with_cwd(cwd.path(), cwd.path()),            "expected untrusted project fallback to stay read-only without Windows sandbox support, policy: {policy:?}"        );    } else {        assert!(            policy.can_write_path_with_cwd(cwd.path(), cwd.path()),            "expected untrusted project fallback to use :workspace, policy: {policy:?}"        );        assert!(            !policy.can_write_path_with_cwd(&cwd.path().join(".codex"), cwd.path()),            "expected :workspace metadata carveouts, policy: {policy:?}"        );    }    Ok(())}#[tokio::test]async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_settings()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let extra_root = TempDir::new()?;    let extra_root = extra_root.path().abs();    let project_key = cwd.path().to_string_lossy().to_string();    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            projects: Some(HashMap::from([(                project_key,                ProjectConfig {                    trust_level: Some(TrustLevel::Trusted),                },            )])),            sandbox_workspace_write: Some(SandboxWorkspaceWrite {                writable_roots: vec![extra_root.clone()],                network_access: true,                exclude_tmpdir_env_var: true,                exclude_slash_tmp: false,            }),            windows: Some(WindowsToml {                sandbox: Some(WindowsSandboxModeToml::Elevated),                sandbox_private_desktop: None,            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    assert!(        policy.can_write_path_with_cwd(extra_root.as_path(), cwd.path()),        "expected implicit :workspace to preserve sandbox_workspace_write.writable_roots, policy: {policy:?}"    );    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Enabled    );    assert_eq!(        config.permissions.active_permission_profile(),        None,        "implicit :workspace cannot be faithfully re-selected when it includes \         legacy sandbox_workspace_write settings"    );    match config.legacy_sandbox_policy() {        SandboxPolicy::WorkspaceWrite {            writable_roots,            network_access,            exclude_tmpdir_env_var,            exclude_slash_tmp,        } => {            assert!(writable_roots.contains(&extra_root));            assert!(network_access);            assert!(exclude_tmpdir_env_var);            assert!(!exclude_slash_tmp);        }        sandbox_policy => panic!("expected workspace-write projection, got {sandbox_policy:?}"),    }    Ok(())}#[tokio::test]async fn implicit_builtin_workspace_profile_preserves_add_dir_metadata_carveouts()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let extra_root = TempDir::new()?;    for subpath in [".git", ".agents", ".codex"] {        std::fs::create_dir_all(extra_root.path().join(subpath))?;    }    let project_key = cwd.path().to_string_lossy().to_string();    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            projects: Some(HashMap::from([(                project_key,                ProjectConfig {                    trust_level: Some(TrustLevel::Trusted),                },            )])),            windows: Some(WindowsToml {                sandbox: Some(WindowsSandboxModeToml::Elevated),                sandbox_private_desktop: None,            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            additional_writable_roots: vec![extra_root.path().to_path_buf()],            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    let extra_root = extra_root.path().abs();    assert!(        policy.can_write_path_with_cwd(extra_root.as_path(), cwd.path()),        "expected implicit :workspace to preserve additional writable roots, policy: {policy:?}"    );    for subpath in [".git", ".agents", ".codex"] {        assert!(            !policy.can_write_path_with_cwd(&extra_root.join(subpath), cwd.path()),            "expected implicit :workspace to preserve legacy metadata carveout for {subpath}, \             policy: {policy:?}"        );    }    Ok(())}#[tokio::test]async fn empty_config_defaults_to_builtin_read_only_without_trust_decision() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    let policy = config.permissions.file_system_sandbox_policy();    assert!(        policy.can_read_path_with_cwd(cwd.path(), cwd.path()),        "expected :read-only to allow reads, policy: {policy:?}"    );    assert!(        !policy.can_write_path_with_cwd(cwd.path(), cwd.path()),        "expected :read-only to deny writes, policy: {policy:?}"    );    Ok(())}#[tokio::test]async fn default_permissions_can_select_builtin_full_access_profile() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS.to_string()),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.permissions.effective_permission_profile(),        PermissionProfile::Disabled    );    assert_eq!(        config            .permissions            .active_permission_profile()            .as_ref()            .map(|active| active.id.as_str()),        Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS)    );    Ok(())}#[tokio::test]async fn legacy_danger_no_sandbox_is_rejected() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let err = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some(":danger-no-sandbox".to_string()),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await    .expect_err("legacy full-access alias should be rejected");    assert_eq!(        err.to_string(),        "default_permissions refers to unknown built-in profile `:danger-no-sandbox`"    );    Ok(())}#[tokio::test]async fn user_defined_permission_profile_names_cannot_use_builtin_prefix() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let err = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some(":custom".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    ":custom".to_string(),                    PermissionProfileToml::default(),                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await    .expect_err("reserved profile name should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "permissions profile `:custom` uses a reserved built-in profile prefix"    );    Ok(())}#[tokio::test]async fn unknown_builtin_permission_profile_name_is_rejected() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let err = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some(":unknown".to_string()),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await    .expect_err("unknown built-in profile name should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "default_permissions refers to unknown built-in profile `:unknown`"    );    Ok(())}#[tokio::test]async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let external_write_dir = TempDir::new()?;    let external_write_path =        AbsolutePathBuf::from_absolute_path(std::fs::canonicalize(external_write_dir.path())?)?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: Some("Workspace access.".to_string()),                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                external_write_path.to_string_lossy().into_owned(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Write),                            )]),                        }),                        network: None,                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.custom_permission_profiles,        vec![CustomPermissionProfileSummary {            id: "dev".to_string(),            description: Some("Workspace access.".to_string()),        }]    );    assert!(        config            .permissions            .file_system_sandbox_policy()            .can_write_path_with_cwd(external_write_path.as_path(), cwd.path())    );    assert_eq!(        &config.legacy_sandbox_policy(),        &SandboxPolicy::WorkspaceWrite {            writable_roots: vec![external_write_path],            network_access: false,            exclude_tmpdir_env_var: true,            exclude_slash_tmp: true,        }    );    Ok(())}#[tokio::test]async fn permissions_profiles_reject_nested_entries_for_non_workspace_roots() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let err = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Scoped(BTreeMap::from([(                                    "docs".to_string(),                                    FileSystemAccessMode::Read,                                )])),                            )]),                        }),                        network: None,                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await    .expect_err("nested entries outside :workspace_roots should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "filesystem path `:minimal` does not support nested entries"    );    Ok(())}async fn load_workspace_permission_profile(    profile: PermissionProfileToml,) -> std::io::Result<Config> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([("dev".to_string(), profile)]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await}#[tokio::test]async fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<()> {    let config = load_workspace_permission_profile(PermissionProfileToml {        description: None,        extends: None,        workspace_roots: None,        filesystem: Some(FilesystemPermissionsToml {            glob_scan_max_depth: None,            entries: BTreeMap::from([(                ":future_special_path".to_string(),                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),            )]),        }),        network: None,    })    .await?;    assert_eq!(        config.permissions.file_system_sandbox_policy(),        FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {            path: FileSystemPath::Special {                value: FileSystemSpecialPath::unknown(                    ":future_special_path",                    /*subpath*/ None                ),            },            access: FileSystemAccessMode::Read,        }]),    );    assert_eq!(        &config.legacy_sandbox_policy(),        &SandboxPolicy::ReadOnly {            network_access: false,        }    );    assert!(        config.startup_warnings.iter().any(|warning| warning.contains(            "Configured filesystem path `:future_special_path` is not recognized by this version of Codex and will be ignored."        )),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn permissions_profiles_allow_unknown_special_paths_with_nested_entries()-> std::io::Result<()> {    let config = load_workspace_permission_profile(PermissionProfileToml {        description: None,        extends: None,        workspace_roots: None,        filesystem: Some(FilesystemPermissionsToml {            glob_scan_max_depth: None,            entries: BTreeMap::from([(                ":future_special_path".to_string(),                FilesystemPermissionToml::Scoped(BTreeMap::from([(                    "docs".to_string(),                    FileSystemAccessMode::Read,                )])),            )]),        }),        network: None,    })    .await?;    assert_eq!(        config.permissions.file_system_sandbox_policy(),        FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {            path: FileSystemPath::Special {                value: FileSystemSpecialPath::unknown(":future_special_path", Some("docs".into())),            },            access: FileSystemAccessMode::Read,        }]),    );    assert!(        config.startup_warnings.iter().any(|warning| warning.contains(            "Configured filesystem path `:future_special_path` with nested entry `docs` is not recognized by this version of Codex and will be ignored."        )),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn permissions_profiles_allow_missing_filesystem_with_warning() -> std::io::Result<()> {    let config = load_workspace_permission_profile(PermissionProfileToml {        description: None,        extends: None,        workspace_roots: None,        filesystem: None,        network: None,    })    .await?;    assert_eq!(        config.permissions.file_system_sandbox_policy(),        FileSystemSandboxPolicy::restricted(Vec::new())    );    assert_eq!(        &config.legacy_sandbox_policy(),        &SandboxPolicy::ReadOnly {            network_access: false,        }    );    assert!(        config.startup_warnings.iter().any(|warning| warning.contains(            "Permissions profile `dev` does not define any recognized filesystem entries for this version of Codex."        )),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn permissions_profiles_allow_empty_filesystem_with_warning() -> std::io::Result<()> {    let config = load_workspace_permission_profile(PermissionProfileToml {        description: None,        extends: None,        workspace_roots: None,        filesystem: Some(FilesystemPermissionsToml {            glob_scan_max_depth: None,            entries: BTreeMap::new(),        }),        network: None,    })    .await?;    assert_eq!(        config.permissions.file_system_sandbox_policy(),        FileSystemSandboxPolicy::restricted(Vec::new())    );    assert!(        config.startup_warnings.iter().any(|warning| warning.contains(            "Permissions profile `dev` does not define any recognized filesystem entries for this version of Codex."        )),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn permissions_profiles_reject_workspace_root_parent_traversal() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let err = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":workspace_roots".to_string(),                                FilesystemPermissionToml::Scoped(BTreeMap::from([(                                    "../sibling".to_string(),                                    FileSystemAccessMode::Read,                                )])),                            )]),                        }),                        network: None,                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await    .expect_err("parent traversal should be rejected for project root subpaths");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "filesystem subpath `../sibling` must be a descendant path without `.` or `..` components"    );    Ok(())}#[tokio::test]async fn permissions_profiles_allow_network_enablement() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            default_permissions: Some("dev".to_string()),            permissions: Some(PermissionsToml {                entries: BTreeMap::from([(                    "dev".to_string(),                    PermissionProfileToml {                        description: None,                        extends: None,                        workspace_roots: None,                        filesystem: Some(FilesystemPermissionsToml {                            glob_scan_max_depth: None,                            entries: BTreeMap::from([(                                ":minimal".to_string(),                                FilesystemPermissionToml::Access(FileSystemAccessMode::Read),                            )]),                        }),                        network: Some(NetworkToml {                            enabled: Some(true),                            ..Default::default()                        }),                    },                )]),            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert!(        config.permissions.network_sandbox_policy().is_enabled(),        "expected network sandbox policy to be enabled",    );    assert!(config.legacy_sandbox_policy().has_full_network_access());    Ok(())}#[test]fn tui_theme_deserializes_from_toml() {    let cfg = r#"[tui]theme = "dracula""#;    let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");    assert_eq!(        parsed.tui.as_ref().and_then(|t| t.theme.as_deref()),        Some("dracula"),    );}#[test]fn tui_theme_defaults_to_none() {    let cfg = r#"[tui]"#;    let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");    assert_eq!(parsed.tui.as_ref().and_then(|t| t.theme.as_deref()), None);}#[test]fn tui_session_picker_view_deserializes_from_toml() {    let cfg = r#"[tui]session_picker_view = "dense""#;    let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");    assert_eq!(        parsed.tui.as_ref().and_then(|t| t.session_picker_view),        Some(SessionPickerViewMode::Dense),    );}#[test]fn tui_pet_deserializes_from_toml() {    let cfg = r#"[tui]pet = "chefito""#;    let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");    assert_eq!(        parsed.tui.as_ref().and_then(|t| t.pet.as_deref()),        Some("chefito"),    );}#[test]fn tui_session_picker_view_defaults_to_none() {    let cfg = r#"[tui]"#;    let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");    assert_eq!(        parsed.tui.as_ref().and_then(|t| t.session_picker_view),        None,    );}#[test]fn tui_pet_defaults_to_none() {    let cfg = r#"[tui]"#;    let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");    assert_eq!(parsed.tui.as_ref().and_then(|t| t.pet.as_deref()), None);}#[test]fn tui_pet_anchor_deserializes_from_toml() {    let cfg = r#"[tui]pet_anchor = "screen-bottom""#;    let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");    assert_eq!(        parsed.tui.as_ref().map(|t| t.pet_anchor),        Some(TuiPetAnchor::ScreenBottom),    );}#[test]fn tui_pet_anchor_defaults_to_composer() {    let cfg = r#"[tui]"#;    let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");    assert_eq!(        parsed.tui.as_ref().map(|t| t.pet_anchor),        Some(TuiPetAnchor::Composer),    );}#[test]fn tui_pet_anchor_rejects_unknown_value() {    let cfg = r#"[tui]pet_anchor = "bottom""#;    let err = toml::from_str::<ConfigToml>(cfg).expect_err("reject unknown pet anchor");    let err = err.to_string();    assert!(        err.contains("unknown variant `bottom`")            && err.contains("composer")            && err.contains("screen-bottom"),        "unexpected error: {err}"    );}#[test]fn tui_config_missing_notifications_field_defaults_to_enabled() {    let cfg = r#"[tui]"#;    let parsed =        toml::from_str::<ConfigToml>(cfg).expect("TUI config without notifications should succeed");    let tui = parsed.tui.expect("config should include tui section");    assert_eq!(        tui,        Tui {            notification_settings: TuiNotificationSettings::default(),            animations: true,            show_tooltips: true,            vim_mode_default: false,            raw_output_mode: false,            alternate_screen: AltScreenMode::Auto,            status_line: None,            status_line_use_colors: true,            terminal_title: None,            theme: None,            pet: None,            pet_anchor: TuiPetAnchor::Composer,            session_picker_view: None,            keymap: TuiKeymap::default(),            model_availability_nux: ModelAvailabilityNuxConfig::default(),            terminal_resize_reflow_max_rows: None,        }    );}#[tokio::test]async fn runtime_config_resolves_terminal_resize_reflow_defaults_and_overrides() {    let cfg = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load default config");    assert_eq!(        cfg.terminal_resize_reflow,        TerminalResizeReflowConfig::default()    );    assert_eq!(        cfg.terminal_resize_reflow.max_rows,        TerminalResizeReflowMaxRows::Auto    );    let cfg = Config::load_from_base_config_with_overrides(        ConfigToml {            tui: Some(Tui {                terminal_resize_reflow_max_rows: Some(9000),                ..Default::default()            }),            ..Default::default()        },        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load overridden config");    assert_eq!(        cfg.terminal_resize_reflow.max_rows,        TerminalResizeReflowMaxRows::Limit(9000)    );    let cfg = Config::load_from_base_config_with_overrides(        ConfigToml {            tui: Some(Tui {                terminal_resize_reflow_max_rows: Some(0),                ..Default::default()            }),            ..Default::default()        },        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load config with disabled resize reflow limits");    assert_eq!(        cfg.terminal_resize_reflow.max_rows,        TerminalResizeReflowMaxRows::Disabled    );}#[tokio::test]async fn forced_chatgpt_workspace_id_empty_values_disable_runtime_restriction()-> std::io::Result<()> {    let cases: Vec<(&str, &str, Option<Vec<&str>>)> = vec![        ("unset", "", None),        ("empty string", r#"forced_chatgpt_workspace_id = """#, None),        (            "whitespace string",            r#"forced_chatgpt_workspace_id = "   ""#,            None,        ),        ("empty list", r#"forced_chatgpt_workspace_id = []"#, None),        (            "blank list entries",            r#"forced_chatgpt_workspace_id = ["", "  "]"#,            None,        ),        (            "mixed list entries",            r#"forced_chatgpt_workspace_id = ["", " 123e4567-e89b-42d3-a456-426614174000 ", "123e4567-e89b-42d3-a456-426614174001"]"#,            Some(vec![                "123e4567-e89b-42d3-a456-426614174000",                "123e4567-e89b-42d3-a456-426614174001",            ]),        ),    ];    for (name, toml, expected) in cases {        let cfg_toml: ConfigToml = toml::from_str(toml)            .unwrap_or_else(|err| panic!("{name} should parse forced_chatgpt_workspace_id: {err}"));        let config = Config::load_from_base_config_with_overrides(            cfg_toml,            ConfigOverrides::default(),            tempdir().expect("tempdir").abs(),        )        .await?;        let expected = expected.map(|values| {            values                .into_iter()                .map(ToString::to_string)                .collect::<Vec<_>>()        });        assert_eq!(config.forced_chatgpt_workspace_id, expected, "{name}");    }    Ok(())}#[tokio::test]async fn legacy_remote_thread_store_endpoint_is_rejected() {    let cfg: ConfigToml =        toml::from_str(r#"experimental_thread_store_endpoint = "https://example.com""#)            .expect("legacy remote thread-store endpoint should still deserialize");    let err = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect_err("legacy remote thread-store endpoint should be rejected at load time");    assert!(        err.to_string()            .contains("experimental_thread_store_endpoint")    );    assert!(err.to_string().contains("no longer supported"));}#[test]fn profile_tui_rejects_unsupported_settings() {    let err = toml::from_str::<ConfigToml>(        r#"profile = "work"[profiles.work.tui]theme = "dark""#,    )    .expect_err("profile TUI config should only accept supported fields");    assert!(err.to_string().contains("unknown field"));    assert!(err.to_string().contains("theme"));}#[tokio::test]async fn runtime_config_resolves_session_picker_view_default_and_override() {    let cfg = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load default config");    assert_eq!(cfg.tui_session_picker_view, SessionPickerViewMode::Dense);    let cfg = Config::load_from_base_config_with_overrides(        ConfigToml {            tui: Some(Tui {                session_picker_view: Some(SessionPickerViewMode::Comfortable),                ..Default::default()            }),            ..Default::default()        },        ConfigOverrides::default(),        tempdir().expect("tempdir").abs(),    )    .await    .expect("load root override config");    assert_eq!(        cfg.tui_session_picker_view,        SessionPickerViewMode::Comfortable    );}#[tokio::test]async fn test_sandbox_config_parsing() {    let sandbox_full_access = r#"sandbox_mode = "danger-full-access"[sandbox_workspace_write]network_access = false  # This should be ignored."#;    let sandbox_full_access_cfg = toml::from_str::<ConfigToml>(sandbox_full_access)        .expect("TOML deserialization should succeed");    let sandbox_mode_override = None;    let resolution = derive_legacy_sandbox_policy_for_test(        &sandbox_full_access_cfg,        sandbox_mode_override,        WindowsSandboxLevel::Disabled,        /*active_project*/ None,        /*permission_profile_constraint*/ None,    )    .await;    assert_eq!(resolution, SandboxPolicy::DangerFullAccess);    let sandbox_read_only = r#"sandbox_mode = "read-only"[sandbox_workspace_write]network_access = true  # This should be ignored."#;    let sandbox_read_only_cfg = toml::from_str::<ConfigToml>(sandbox_read_only)        .expect("TOML deserialization should succeed");    let sandbox_mode_override = None;    let resolution = derive_legacy_sandbox_policy_for_test(        &sandbox_read_only_cfg,        sandbox_mode_override,        WindowsSandboxLevel::Disabled,        /*active_project*/ None,        /*permission_profile_constraint*/ None,    )    .await;    assert_eq!(resolution, SandboxPolicy::new_read_only_policy());    let writable_root = test_absolute_path("/my/workspace");    let sandbox_workspace_write = format!(        r#"sandbox_mode = "workspace-write"[sandbox_workspace_write]writable_roots = [    {},]exclude_tmpdir_env_var = trueexclude_slash_tmp = true[projects."/tmp/test"]trust_level = "trusted""#,        serde_json::json!(writable_root)    );    let sandbox_workspace_write_cfg = toml::from_str::<ConfigToml>(&sandbox_workspace_write)        .expect("TOML deserialization should succeed");    let sandbox_mode_override = None;    let resolution = derive_legacy_sandbox_policy_for_test(        &sandbox_workspace_write_cfg,        sandbox_mode_override,        WindowsSandboxLevel::Disabled,        /*active_project*/ None,        /*permission_profile_constraint*/ None,    )    .await;    if cfg!(target_os = "windows") {        assert_eq!(resolution, SandboxPolicy::new_read_only_policy());    } else {        assert_eq!(            resolution,            SandboxPolicy::WorkspaceWrite {                writable_roots: vec![writable_root.clone()],                network_access: false,                exclude_tmpdir_env_var: true,                exclude_slash_tmp: true,            }        );    }    let sandbox_workspace_write = format!(        r#"sandbox_mode = "workspace-write"[sandbox_workspace_write]writable_roots = [    {},]exclude_tmpdir_env_var = trueexclude_slash_tmp = true"#,        serde_json::json!(writable_root)    );    let sandbox_workspace_write_cfg = toml::from_str::<ConfigToml>(&sandbox_workspace_write)        .expect("TOML deserialization should succeed");    let sandbox_mode_override = None;    let resolution = derive_legacy_sandbox_policy_for_test(        &sandbox_workspace_write_cfg,        sandbox_mode_override,        WindowsSandboxLevel::Disabled,        /*active_project*/ None,        /*permission_profile_constraint*/ None,    )    .await;    if cfg!(target_os = "windows") {        assert_eq!(resolution, SandboxPolicy::new_read_only_policy());    } else {        assert_eq!(            resolution,            SandboxPolicy::WorkspaceWrite {                writable_roots: vec![writable_root],                network_access: false,                exclude_tmpdir_env_var: true,                exclude_slash_tmp: true,            }        );    }}#[tokio::test]async fn legacy_sandbox_mode_builds_profiles_with_compatible_projection() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let extra_root = test_absolute_path("/tmp/legacy-extra-root");    let cases = vec![        (            "danger-full-access".to_string(),            r#"sandbox_mode = "danger-full-access""#            .to_string(),        ),        (            "read-only".to_string(),            r#"sandbox_mode = "read-only""#            .to_string(),        ),        (            "workspace-write".to_string(),            format!(                r#"sandbox_mode = "workspace-write"[sandbox_workspace_write]writable_roots = [{}]exclude_tmpdir_env_var = trueexclude_slash_tmp = true"#,                serde_json::json!(extra_root)            ),        ),    ];    for (name, config_toml) in cases {        let cfg = toml::from_str::<ConfigToml>(&config_toml)            .unwrap_or_else(|err| panic!("case `{name}` should parse: {err}"));        let config = Config::load_from_base_config_with_overrides(            cfg,            ConfigOverrides {                cwd: Some(cwd.path().to_path_buf()),                ..Default::default()            },            codex_home.abs(),        )        .await?;        let sandbox_policy = config.legacy_sandbox_policy();        let file_system_policy = config.permissions.file_system_sandbox_policy();        let network_policy = config.permissions.network_sandbox_policy();        assert_eq!(            network_policy,            NetworkSandboxPolicy::from(&sandbox_policy),            "case `{name}` should preserve network semantics from legacy config"        );        assert_eq!(            file_system_policy                .to_legacy_sandbox_policy(network_policy, cwd.path())                .unwrap_or_else(|err| panic!("case `{name}` should round-trip: {err}")),            sandbox_policy,            "case `{name}` should preserve its legacy compatibility projection"        );        match name.as_str() {            "danger-full-access" | "read-only" => {                assert_eq!(                    file_system_policy,                    FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(                        &sandbox_policy,                        cwd.path()                    ),                    "case `{name}` should match the legacy filesystem projection exactly"                );            }            "workspace-write" => {                if cfg!(target_os = "windows") {                    assert_eq!(                        sandbox_policy,                        SandboxPolicy::new_read_only_policy(),                        "legacy workspace-write should keep the existing Windows downgrade when \                         the experimental Windows sandbox is disabled"                    );                    assert_eq!(                        file_system_policy,                        FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(                            &sandbox_policy,                            cwd.path()                        ),                        "downgraded workspace-write should match the legacy read-only projection"                    );                    continue;                }                assert_eq!(                    config.permissions.workspace_roots(),                    &[cwd.abs(), extra_root.clone()]                );                assert!(                    file_system_policy                        .entries                        .contains(&FileSystemSandboxEntry {                            path: FileSystemPath::Path { path: cwd.abs() },                            access: FileSystemAccessMode::Write,                        })                );                assert!(                    file_system_policy                        .entries                        .contains(&FileSystemSandboxEntry {                            path: FileSystemPath::Path {                                path: extra_root.clone(),                            },                            access: FileSystemAccessMode::Write,                        })                );                for subpath in [".git", ".agents", ".codex"] {                    assert!(                        file_system_policy                            .entries                            .contains(&FileSystemSandboxEntry {                                path: FileSystemPath::Path {                                    path: AbsolutePathBuf::resolve_path_against_base(                                        subpath,                                        cwd.path()                                    ),                                },                                access: FileSystemAccessMode::Read,                            }),                        "case `{name}` should materialize `{subpath}` for the runtime workspace \                         root"                    );                }            }            _ => unreachable!("unexpected test case `{name}`"),        }    }    Ok(())}#[test]fn filter_mcp_servers_by_allowlist_enforces_identity_rules() {    const MISMATCHED_COMMAND_SERVER: &str = "mismatched-command-should-disable";    const MISMATCHED_URL_SERVER: &str = "mismatched-url-should-disable";    const MATCHED_COMMAND_SERVER: &str = "matched-command-should-allow";    const MATCHED_URL_SERVER: &str = "matched-url-should-allow";    const DIFFERENT_NAME_SERVER: &str = "different-name-should-disable";    const GOOD_CMD: &str = "good-cmd";    const GOOD_URL: &str = "https://example.com/good";    let mut servers = HashMap::from([        (MISMATCHED_COMMAND_SERVER.to_string(), stdio_mcp("docs-cmd")),        (            MISMATCHED_URL_SERVER.to_string(),            http_mcp("https://example.com/mcp"),        ),        (MATCHED_COMMAND_SERVER.to_string(), stdio_mcp(GOOD_CMD)),        (MATCHED_URL_SERVER.to_string(), http_mcp(GOOD_URL)),        (DIFFERENT_NAME_SERVER.to_string(), stdio_mcp("same-cmd")),    ]);    let source = RequirementSource::LegacyManagedConfigTomlFromMdm;    let requirements = Sourced::new(        BTreeMap::from([            (                MISMATCHED_URL_SERVER.to_string(),                McpServerRequirement {                    identity: McpServerIdentity::Url {                        url: "https://example.com/other".to_string(),                    },                },            ),            (                MISMATCHED_COMMAND_SERVER.to_string(),                McpServerRequirement {                    identity: McpServerIdentity::Command {                        command: "other-cmd".to_string(),                    },                },            ),            (                MATCHED_URL_SERVER.to_string(),                McpServerRequirement {                    identity: McpServerIdentity::Url {                        url: GOOD_URL.to_string(),                    },                },            ),            (                MATCHED_COMMAND_SERVER.to_string(),                McpServerRequirement {                    identity: McpServerIdentity::Command {                        command: GOOD_CMD.to_string(),                    },                },            ),        ]),        source.clone(),    );    filter_mcp_servers_by_requirements(&mut servers, Some(&requirements));    let reason = Some(McpServerDisabledReason::Requirements { source });    assert_eq!(        servers            .iter()            .map(|(name, server)| (                name.clone(),                (server.enabled, server.disabled_reason.clone())            ))            .collect::<HashMap<String, (bool, Option<McpServerDisabledReason>)>>(),        HashMap::from([            (MISMATCHED_URL_SERVER.to_string(), (false, reason.clone())),            (                MISMATCHED_COMMAND_SERVER.to_string(),                (false, reason.clone()),            ),            (MATCHED_URL_SERVER.to_string(), (true, None)),            (MATCHED_COMMAND_SERVER.to_string(), (true, None)),            (DIFFERENT_NAME_SERVER.to_string(), (false, reason)),        ])    );}#[test]fn filter_mcp_servers_by_allowlist_allows_all_when_unset() {    let mut servers = HashMap::from([        ("server-a".to_string(), stdio_mcp("cmd-a")),        ("server-b".to_string(), http_mcp("https://example.com/b")),    ]);    filter_mcp_servers_by_requirements(&mut servers, /*mcp_requirements*/ None);    assert_eq!(        servers            .iter()            .map(|(name, server)| (                name.clone(),                (server.enabled, server.disabled_reason.clone())            ))            .collect::<HashMap<String, (bool, Option<McpServerDisabledReason>)>>(),        HashMap::from([            ("server-a".to_string(), (true, None)),            ("server-b".to_string(), (true, None)),        ])    );}#[test]fn filter_mcp_servers_by_allowlist_blocks_all_when_empty() {    let mut servers = HashMap::from([        ("server-a".to_string(), stdio_mcp("cmd-a")),        ("server-b".to_string(), http_mcp("https://example.com/b")),    ]);    let source = RequirementSource::LegacyManagedConfigTomlFromMdm;    let requirements = Sourced::new(BTreeMap::new(), source.clone());    filter_mcp_servers_by_requirements(&mut servers, Some(&requirements));    let reason = Some(McpServerDisabledReason::Requirements { source });    assert_eq!(        servers            .iter()            .map(|(name, server)| (                name.clone(),                (server.enabled, server.disabled_reason.clone())            ))            .collect::<HashMap<String, (bool, Option<McpServerDisabledReason>)>>(),        HashMap::from([            ("server-a".to_string(), (false, reason.clone())),            ("server-b".to_string(), (false, reason)),        ])    );}#[test]fn filter_plugin_mcp_servers_by_allowlist_enforces_plugin_and_identity_rules() {    const MATCHED_SERVER: &str = "matched-should-allow";    const MISMATCHED_SERVER: &str = "mismatched-should-disable";    const UNLISTED_SERVER: &str = "unlisted-should-disable";    const GOOD_CMD: &str = "good-cmd";    let mut servers = HashMap::from([        (MATCHED_SERVER.to_string(), stdio_mcp(GOOD_CMD)),        (MISMATCHED_SERVER.to_string(), stdio_mcp("bad-cmd")),        (            UNLISTED_SERVER.to_string(),            http_mcp("https://example.com/mcp"),        ),    ]);    let source = RequirementSource::LegacyManagedConfigTomlFromMdm;    let requirements = Sourced::new(        BTreeMap::from([(            "sample@test".to_string(),            codex_config::PluginRequirementsToml {                mcp_servers: Some(BTreeMap::from([                    (                        MATCHED_SERVER.to_string(),                        McpServerRequirement {                            identity: McpServerIdentity::Command {                                command: GOOD_CMD.to_string(),                            },                        },                    ),                    (                        MISMATCHED_SERVER.to_string(),                        McpServerRequirement {                            identity: McpServerIdentity::Command {                                command: GOOD_CMD.to_string(),                            },                        },                    ),                ])),            },        )]),        source.clone(),    );    filter_plugin_mcp_servers_by_requirements("sample@test", &mut servers, Some(&requirements));    let reason = Some(McpServerDisabledReason::Requirements { source });    assert_eq!(        servers            .iter()            .map(|(name, server)| (                name.clone(),                (server.enabled, server.disabled_reason.clone())            ))            .collect::<HashMap<String, (bool, Option<McpServerDisabledReason>)>>(),        HashMap::from([            (MATCHED_SERVER.to_string(), (true, None)),            (MISMATCHED_SERVER.to_string(), (false, reason.clone())),            (UNLISTED_SERVER.to_string(), (false, reason)),        ])    );}#[test]fn filter_plugin_mcp_servers_by_allowlist_blocks_unlisted_plugin() {    let mut servers = HashMap::from([("server-a".to_string(), stdio_mcp("cmd-a"))]);    let source = RequirementSource::LegacyManagedConfigTomlFromMdm;    let requirements = Sourced::new(        BTreeMap::from([(            "other@test".to_string(),            codex_config::PluginRequirementsToml {                mcp_servers: Some(BTreeMap::from([(                    "server-a".to_string(),                    McpServerRequirement {                        identity: McpServerIdentity::Command {                            command: "cmd-a".to_string(),                        },                    },                )])),            },        )]),        source.clone(),    );    filter_plugin_mcp_servers_by_requirements("sample@test", &mut servers, Some(&requirements));    assert_eq!(        servers            .iter()            .map(|(name, server)| (                name.clone(),                (server.enabled, server.disabled_reason.clone())            ))            .collect::<HashMap<String, (bool, Option<McpServerDisabledReason>)>>(),        HashMap::from([(            "server-a".to_string(),            (                false,                Some(McpServerDisabledReason::Requirements { source })            )        )])    );}#[tokio::test]async fn rebuild_preserving_session_layers_refreshes_requirements() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let user_file = AbsolutePathBuf::resolve_path_against_base(CONFIG_TOML_FILE, codex_home.path());    let project_dot_codex =        AbsolutePathBuf::resolve_path_against_base("project/.codex", codex_home.path());    let mcp_requirements = BTreeMap::from([        (            "session_overrides_user".to_string(),            McpServerRequirement {                identity: McpServerIdentity::Command {                    command: "session-command".to_string(),                },            },        ),        (            "managed_overrides_session".to_string(),            McpServerRequirement {                identity: McpServerIdentity::Command {                    command: "managed-command".to_string(),                },            },        ),        (            "fresh_global".to_string(),            McpServerRequirement {                identity: McpServerIdentity::Command {                    command: "fresh-global-command".to_string(),                },            },        ),        (            "fresh_project".to_string(),            McpServerRequirement {                identity: McpServerIdentity::Command {                    command: "fresh-project-command".to_string(),                },            },        ),    ]);    let requirements_toml = codex_config::ConfigRequirementsToml {        mcp_servers: Some(mcp_requirements.clone()),        ..Default::default()    };    let requirements = codex_config::ConfigRequirements {        mcp_servers: Some(Sourced::new(mcp_requirements, RequirementSource::Unknown)),        ..Default::default()    };    let refreshed_layer_stack = ConfigLayerStack::new(        vec![            ConfigLayerEntry::new(                codex_app_server_protocol::ConfigLayerSource::User {                    file: user_file.clone(),                    profile: None,                },                toml::toml! {                    [mcp_servers.session_overrides_user]                    command = "new-user-command"                    [mcp_servers.managed_overrides_session]                    command = "new-user-command"                    [mcp_servers.fresh_global]                    command = "fresh-global-command"                }                .into(),            ),            ConfigLayerEntry::new(                codex_app_server_protocol::ConfigLayerSource::Project {                    dot_codex_folder: project_dot_codex.clone(),                },                toml::toml! {                    [mcp_servers.fresh_project]                    command = "fresh-project-command"                }                .into(),            ),            ConfigLayerEntry::new(                codex_app_server_protocol::ConfigLayerSource::LegacyManagedConfigTomlFromMdm,                toml::toml! {                    [mcp_servers.managed_overrides_session]                    command = "managed-command"                }                .into(),            ),        ],        requirements,        requirements_toml,    )    .map_err(std::io::Error::other)?;    let refreshed_toml = refreshed_layer_stack        .effective_config()        .try_into()        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;    let refreshed_config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        refreshed_toml,        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        refreshed_layer_stack,    )    .await?;    let thread_layer_stack = ConfigLayerStack::new(        vec![            ConfigLayerEntry::new(                codex_app_server_protocol::ConfigLayerSource::User {                    file: user_file.clone(),                    profile: None,                },                toml::toml! {                    [mcp_servers.session_overrides_user]                    command = "old-user-command"                    [mcp_servers.managed_overrides_session]                    command = "old-user-command"                    [mcp_servers.fresh_global]                    command = "old-global-command"                }                .into(),            ),            ConfigLayerEntry::new(                codex_app_server_protocol::ConfigLayerSource::Project {                    dot_codex_folder: project_dot_codex,                },                toml::toml! {                    [mcp_servers.fresh_project]                    command = "old-project-command"                }                .into(),            ),            ConfigLayerEntry::new(                codex_app_server_protocol::ConfigLayerSource::SessionFlags,                toml::toml! {                    [mcp_servers.session_overrides_user]                    command = "session-command"                    [mcp_servers.managed_overrides_session]                    command = "session-command"                    [mcp_servers.blocked_session]                    command = "blocked-session-command"                }                .into(),            ),            ConfigLayerEntry::new(                codex_app_server_protocol::ConfigLayerSource::LegacyManagedConfigTomlFromMdm,                toml::toml! {                    [mcp_servers.managed_overrides_session]                    command = "old-managed-command"                }                .into(),            ),        ],        Default::default(),        Default::default(),    )    .map_err(std::io::Error::other)?;    let thread_toml = thread_layer_stack        .effective_config()        .try_into()        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;    let thread_config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        thread_toml,        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        thread_layer_stack,    )    .await?;    let config = thread_config        .rebuild_preserving_session_layers(&refreshed_config)        .await?;    assert_eq!(        config.mcp_servers.get(),        &HashMap::from([            (                "session_overrides_user".to_string(),                stdio_mcp("session-command"),            ),            (                "managed_overrides_session".to_string(),                stdio_mcp("managed-command"),            ),            (                "fresh_global".to_string(),                stdio_mcp("fresh-global-command"),            ),            (                "fresh_project".to_string(),                stdio_mcp("fresh-project-command"),            ),            (                "blocked_session".to_string(),                McpServerConfig {                    enabled: false,                    disabled_reason: Some(McpServerDisabledReason::Requirements {                        source: RequirementSource::Unknown,                    }),                    ..stdio_mcp("blocked-session-command")                },            ),        ])    );    Ok(())}#[tokio::test]async fn rebuild_preserving_session_layers_refreshes_plugin_derived_mcp_config()-> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let plugin_root = codex_home        .path()        .join("plugins/cache")        .join("test/sample/local");    std::fs::create_dir_all(plugin_root.join(".codex-plugin"))?;    std::fs::write(        plugin_root.join(".codex-plugin/plugin.json"),        r#"{"name":"sample"}"#,    )?;    std::fs::write(        plugin_root.join(".mcp.json"),        r#"{  "mcpServers": {    "sample": {      "type": "http",      "url": "https://sample.example/mcp"    }  }}"#,    )?;    let user_file = AbsolutePathBuf::resolve_path_against_base(CONFIG_TOML_FILE, codex_home.path());    let refreshed_layer_stack = ConfigLayerStack::new(        vec![ConfigLayerEntry::new(            codex_app_server_protocol::ConfigLayerSource::User {                file: user_file.clone(),                profile: None,            },            toml::toml! {                [features]                plugins = true                [plugins."sample@test"]                enabled = true            }            .into(),        )],        Default::default(),        Default::default(),    )?;    let refreshed_config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        refreshed_layer_stack.effective_config().try_into()?,        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        refreshed_layer_stack,    )    .await?;    let thread_layer_stack = ConfigLayerStack::new(        vec![ConfigLayerEntry::new(            codex_app_server_protocol::ConfigLayerSource::User {                file: user_file,                profile: None,            },            toml::toml! {                [features]                plugins = false                [plugins."sample@test"]                enabled = true            }            .into(),        )],        Default::default(),        Default::default(),    )?;    let thread_config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        thread_layer_stack.effective_config().try_into()?,        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        thread_layer_stack,    )    .await?;    let config = thread_config        .rebuild_preserving_session_layers(&refreshed_config)        .await?;    let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());    let mcp_config = config.to_mcp_config(&plugins_manager).await;    let configured_servers = mcp_config.mcp_server_catalog.configured_servers();    assert_eq!(        configured_servers.get("sample"),        Some(&http_mcp("https://sample.example/mcp"))    );    assert_eq!(        mcp_config            .mcp_server_catalog            .plugin_attributions_by_server_name(),        HashMap::from([(            "sample".to_string(),            McpPluginAttribution::new("sample@test".to_string(), "sample".to_string()),        )])    );    Ok(())}#[tokio::test]async fn to_mcp_config_omits_plugin_id_when_user_server_shadows_plugin_mcp() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let plugin_root = codex_home        .path()        .join("plugins/cache")        .join("test/sample/local");    std::fs::create_dir_all(plugin_root.join(".codex-plugin"))?;    std::fs::write(        plugin_root.join(".codex-plugin/plugin.json"),        r#"{"name":"sample"}"#,    )?;    std::fs::write(        plugin_root.join(".mcp.json"),        r#"{  "mcpServers": {    "sample": {      "type": "http",      "url": "https://plugin.example/mcp"    }  }}"#,    )?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features]plugins = true[mcp_servers.sample]url = "https://user.example/mcp"[plugins."sample@test"]enabled = true"#,    )?;    let config = ConfigBuilder::default()        .codex_home(codex_home.path().to_path_buf())        .build()        .await?;    let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());    let mcp_config = config.to_mcp_config(&plugins_manager).await;    let configured_servers = mcp_config.mcp_server_catalog.configured_servers();    assert_eq!(        configured_servers.get("sample"),        Some(&http_mcp("https://user.example/mcp"))    );    assert!(        mcp_config            .mcp_server_catalog            .plugin_attributions_by_server_name()            .is_empty()    );    Ok(())}#[tokio::test]async fn selected_plugin_wins_after_discovered_plugin_requirements() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let plugin_root = codex_home        .path()        .join("plugins/cache")        .join("test/sample/local");    std::fs::create_dir_all(plugin_root.join(".codex-plugin"))?;    std::fs::write(        plugin_root.join(".codex-plugin/plugin.json"),        r#"{"name":"sample"}"#,    )?;    std::fs::write(        plugin_root.join(".mcp.json"),        r#"{  "mcpServers": {    "sample": {      "type": "http",      "url": "https://sample.example/mcp"    },    "unlisted": {      "type": "http",      "url": "https://unlisted.example/mcp"    }  }}"#,    )?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features]plugins = true[plugins."sample@test"]enabled = true"#,    )?;    let config = ConfigBuilder::default()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[plugins."sample@test".mcp_servers.sample.identity]url = "https://sample.example/mcp""#,            ),        )        .build()        .await?;    let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());    let mcp_config = config.to_mcp_config(&plugins_manager).await;    let configured_servers = mcp_config.mcp_server_catalog.configured_servers();    assert_eq!(        configured_servers            .get("sample")            .map(|server| (server.enabled, server.disabled_reason.clone())),        Some((true, None))    );    assert_eq!(        configured_servers            .get("unlisted")            .map(|server| (server.enabled, server.disabled_reason.clone())),        Some((            false,            Some(McpServerDisabledReason::Requirements {                source: RequirementSource::EnterpriseManaged {                    id: "req_1".to_string(),                    name: "Base requirements".to_string(),                },            })        ))    );    let selected = http_mcp("https://selected.example/mcp");    let mcp_config = config        .to_mcp_config_with_plugin_registrations(            &plugins_manager,            [McpServerRegistration::from_selected_plugin(                "unlisted".to_string(),                McpPluginAttribution::new(                    "selected-root".to_string(),                    "Selected Plugin".to_string(),                ),                /*selection_order*/ 0,                selected.clone(),            )],        )        .await;    assert_eq!(        mcp_config            .mcp_server_catalog            .server("unlisted")            .map(|server| (server.source().clone(), server.config().clone())),        Some((            codex_mcp::McpServerSource::SelectedPlugin(McpPluginAttribution::new(                "selected-root".to_string(),                "Selected Plugin".to_string(),            )),            selected,        ))    );    Ok(())}#[tokio::test]async fn to_mcp_config_empty_mcp_requirements_disable_plugin_mcps() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let plugin_root = codex_home        .path()        .join("plugins/cache")        .join("test/sample/local");    std::fs::create_dir_all(plugin_root.join(".codex-plugin"))?;    std::fs::write(        plugin_root.join(".codex-plugin/plugin.json"),        r#"{"name":"sample"}"#,    )?;    std::fs::write(        plugin_root.join(".mcp.json"),        r#"{  "mcpServers": {    "sample": {      "type": "http",      "url": "https://sample.example/mcp"    }  }}"#,    )?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features]plugins = true[plugins."sample@test"]enabled = true"#,    )?;    let config = ConfigBuilder::default()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[mcp_servers]"#,            ),        )        .build()        .await?;    let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());    let mcp_config = config.to_mcp_config(&plugins_manager).await;    let configured_servers = mcp_config.mcp_server_catalog.configured_servers();    assert_eq!(        configured_servers            .get("sample")            .map(|server| (server.enabled, server.disabled_reason.clone())),        Some((            false,            Some(McpServerDisabledReason::Requirements {                source: RequirementSource::EnterpriseManaged {                    id: "req_1".to_string(),                    name: "Base requirements".to_string(),                },            })        ))    );    Ok(())}#[tokio::test]async fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result<()> {    let temp_dir = TempDir::new()?;    let frontend = temp_dir.path().join("frontend");    let backend = temp_dir.path().join("backend");    std::fs::create_dir_all(&frontend)?;    std::fs::create_dir_all(&backend)?;    let overrides = ConfigOverrides {        cwd: Some(frontend),        sandbox_mode: Some(SandboxMode::WorkspaceWrite),        additional_writable_roots: vec![PathBuf::from("../backend"), backend.clone()],        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        overrides,        temp_dir.path().abs(),    )    .await?;    let expected_backend = backend.abs();    if cfg!(target_os = "windows") {        match &config.legacy_sandbox_policy() {            SandboxPolicy::ReadOnly { .. } => {}            other => panic!("expected read-only policy on Windows, got {other:?}"),        }    } else {        match &config.legacy_sandbox_policy() {            SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {                assert_eq!(                    writable_roots                        .iter()                        .filter(|root| **root == expected_backend)                        .count(),                    1,                    "expected single writable root entry for {}",                    expected_backend.display()                );            }            other => panic!("expected workspace-write policy, got {other:?}"),        }    }    Ok(())}#[tokio::test]async fn default_zsh_path_sets_runtime_zsh_path() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let default_zsh_path = codex_home.path().join("packaged-zsh");    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides {            default_zsh_path: Some(default_zsh_path.abs()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(config.zsh_path, Some(default_zsh_path));    Ok(())}#[tokio::test]async fn sqlite_home_defaults_to_codex_home_for_workspace_write() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides {            sandbox_mode: Some(SandboxMode::WorkspaceWrite),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(config.sqlite_home, codex_home.path().to_path_buf());    Ok(())}#[tokio::test]async fn workspace_write_includes_configured_writable_root_once_without_memories_root()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let memories_root = codex_home.path().join("memories");    let writable_root = codex_home.path().join("writable").abs();    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            sandbox_workspace_write: Some(SandboxWorkspaceWrite {                writable_roots: vec![writable_root.clone(), writable_root.clone()],                ..Default::default()            }),            ..Default::default()        },        ConfigOverrides {            sandbox_mode: Some(SandboxMode::WorkspaceWrite),            ..Default::default()        },        codex_home.abs(),    )    .await?;    if cfg!(target_os = "windows") {        match &config.legacy_sandbox_policy() {            SandboxPolicy::ReadOnly { .. } => {}            other => panic!("expected read-only policy on Windows, got {other:?}"),        }    } else {        assert!(            !memories_root.exists(),            "expected config load not to create memories root at {}",            memories_root.display()        );        let expected_memories_root = memories_root.abs();        match &config.legacy_sandbox_policy() {            SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {                assert!(!writable_roots.contains(&expected_memories_root));                assert_eq!(                    writable_roots                        .iter()                        .filter(|root| **root == writable_root)                        .count(),                    1,                    "expected single writable root entry for {}",                    writable_root.display()                );            }            other => panic!("expected workspace-write policy, got {other:?}"),        }    }    Ok(())}#[tokio::test]async fn memory_tool_makes_memories_root_readable_without_creating_or_widening_writes()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = TempDir::new()?;    let memories_root = codex_home.path().join("memories");    let memories_root_abs = memories_root.abs();    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            features: Some(FeaturesToml::from(BTreeMap::from([(                "memories".to_string(),                true,            )]))),            sandbox_workspace_write: Some(SandboxWorkspaceWrite {                exclude_tmpdir_env_var: true,                exclude_slash_tmp: true,                ..Default::default()            }),            ..Default::default()        },        ConfigOverrides {            cwd: Some(cwd.path().to_path_buf()),            sandbox_mode: Some(SandboxMode::WorkspaceWrite),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert!(        !memories_root.exists(),        "expected config load not to create memories root at {}",        memories_root.display()    );    let file_system_policy = config.permissions.file_system_sandbox_policy();    assert!(file_system_policy.can_read_path_with_cwd(memories_root_abs.as_path(), cwd.path()));    assert!(!file_system_policy.can_write_path_with_cwd(memories_root_abs.as_path(), cwd.path()));    if cfg!(target_os = "windows") {        match &config.legacy_sandbox_policy() {            SandboxPolicy::ReadOnly { .. } => {}            other => panic!("expected read-only policy on Windows, got {other:?}"),        }    } else {        match &config.legacy_sandbox_policy() {            SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {                assert!(!writable_roots.contains(&memories_root_abs));            }            other => panic!("expected workspace-write policy, got {other:?}"),        }    }    Ok(())}#[tokio::test]async fn config_defaults_to_file_cli_auth_store_mode() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml::default();    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.cli_auth_credentials_store_mode,        AuthCredentialsStoreMode::File,    );    Ok(())}#[tokio::test]async fn config_resolves_explicit_keyring_auth_store_mode() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        cli_auth_credentials_store: Some(AuthCredentialsStoreMode::Keyring),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.cli_auth_credentials_store_mode,        resolve_cli_auth_credentials_store_mode(            AuthCredentialsStoreMode::Keyring,            env!("CARGO_PKG_VERSION"),        ),    );    Ok(())}#[tokio::test]async fn config_resolves_default_oauth_store_mode() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml::default();    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.mcp_oauth_credentials_store_mode,        resolve_mcp_oauth_credentials_store_mode(            OAuthCredentialsStoreMode::Auto,            env!("CARGO_PKG_VERSION"),        ),    );    Ok(())}#[test]fn local_dev_builds_force_file_cli_auth_store_modes() {    assert_eq!(        resolve_cli_auth_credentials_store_mode(            AuthCredentialsStoreMode::Keyring,            LOCAL_DEV_BUILD_VERSION,        ),        AuthCredentialsStoreMode::File,    );    assert_eq!(        resolve_cli_auth_credentials_store_mode(            AuthCredentialsStoreMode::Auto,            LOCAL_DEV_BUILD_VERSION,        ),        AuthCredentialsStoreMode::File,    );    assert_eq!(        resolve_cli_auth_credentials_store_mode(            AuthCredentialsStoreMode::Ephemeral,            LOCAL_DEV_BUILD_VERSION,        ),        AuthCredentialsStoreMode::Ephemeral,    );    assert_eq!(        resolve_cli_auth_credentials_store_mode(AuthCredentialsStoreMode::Keyring, "1.2.3"),        AuthCredentialsStoreMode::Keyring,    );}#[test]fn local_dev_builds_force_file_mcp_oauth_store_modes() {    assert_eq!(        resolve_mcp_oauth_credentials_store_mode(            OAuthCredentialsStoreMode::Keyring,            LOCAL_DEV_BUILD_VERSION,        ),        OAuthCredentialsStoreMode::File,    );    assert_eq!(        resolve_mcp_oauth_credentials_store_mode(            OAuthCredentialsStoreMode::Auto,            LOCAL_DEV_BUILD_VERSION,        ),        OAuthCredentialsStoreMode::File,    );    assert_eq!(        resolve_mcp_oauth_credentials_store_mode(OAuthCredentialsStoreMode::Keyring, "1.2.3"),        OAuthCredentialsStoreMode::Keyring,    );}#[tokio::test]async fn feedback_enabled_defaults_to_true() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        feedback: Some(FeedbackConfigToml::default()),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(config.feedback_enabled, true);    Ok(())}#[test]fn web_search_mode_defaults_to_none_if_unset() {    let cfg = ConfigToml::default();    let features = Features::with_defaults();    assert_eq!(resolve_web_search_mode(&cfg, &features), None);}#[test]fn web_search_mode_prefers_config_over_legacy_flags() {    let cfg = ConfigToml {        web_search: Some(WebSearchMode::Live),        ..Default::default()    };    let mut features = Features::with_defaults();    features.enable(Feature::WebSearchCached);    assert_eq!(        resolve_web_search_mode(&cfg, &features),        Some(WebSearchMode::Live)    );}#[test]fn web_search_mode_disabled_overrides_legacy_request() {    let cfg = ConfigToml {        web_search: Some(WebSearchMode::Disabled),        ..Default::default()    };    let mut features = Features::with_defaults();    features.enable(Feature::WebSearchRequest);    assert_eq!(        resolve_web_search_mode(&cfg, &features),        Some(WebSearchMode::Disabled)    );}#[test]fn web_search_mode_for_turn_uses_preference_for_read_only() {    let web_search_mode = Constrained::allow_any(WebSearchMode::Cached);    let permission_profile = PermissionProfile::read_only();    let mode = resolve_web_search_mode_for_turn(&web_search_mode, &permission_profile);    assert_eq!(mode, WebSearchMode::Cached);}#[test]fn web_search_mode_for_turn_prefers_live_for_disabled_permissions() {    let web_search_mode = Constrained::allow_any(WebSearchMode::Cached);    let mode = resolve_web_search_mode_for_turn(&web_search_mode, &PermissionProfile::Disabled);    assert_eq!(mode, WebSearchMode::Live);}#[test]fn web_search_mode_for_turn_respects_disabled_for_disabled_permissions() {    let web_search_mode = Constrained::allow_any(WebSearchMode::Disabled);    let mode = resolve_web_search_mode_for_turn(&web_search_mode, &PermissionProfile::Disabled);    assert_eq!(mode, WebSearchMode::Disabled);}#[test]fn web_search_mode_for_turn_falls_back_when_live_is_disallowed() -> anyhow::Result<()> {    let allowed = [WebSearchMode::Disabled, WebSearchMode::Cached];    let web_search_mode = Constrained::new(WebSearchMode::Cached, move |candidate| {        if allowed.contains(candidate) {            Ok(())        } else {            Err(ConstraintError::InvalidValue {                field_name: "web_search_mode",                candidate: format!("{candidate:?}"),                allowed: format!("{allowed:?}"),                requirement_source: RequirementSource::Unknown,            })        }    })?;    let mode = resolve_web_search_mode_for_turn(&web_search_mode, &PermissionProfile::Disabled);    assert_eq!(mode, WebSearchMode::Cached);    Ok(())}#[tokio::test]async fn project_profiles_are_ignored() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let workspace = TempDir::new()?;    let workspace_key = workspace.path().to_string_lossy().replace('\\', "\\\\");    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[projects."{workspace_key}"]trust_level = "trusted""#,        ),    )?;    let project_config_dir = workspace.path().join(".codex");    std::fs::create_dir_all(&project_config_dir)?;    std::fs::write(        project_config_dir.join(CONFIG_TOML_FILE),        r#"profile = "project"[profiles.project]model = "gpt-project-local""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .harness_overrides(ConfigOverrides {            cwd: Some(workspace.path().to_path_buf()),            ..Default::default()        })        .build()        .await?;    assert_eq!(config.model, None);    assert!(        config.startup_warnings.iter().any(|warning| {            warning.contains("profile")                && warning.contains("profiles")                && warning.contains(                    "If you want these settings to apply, manually set them in your user-level config.toml."                )        }),        "expected warning for ignored project-local profile keys: {:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn feature_table_overrides_legacy_flags() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let mut entries = BTreeMap::new();    entries.insert("apply_patch_freeform".to_string(), false);    let cfg = ConfigToml {        features: Some(FeaturesToml::from(entries)),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert!(!config.features.enabled(Feature::ApplyPatchFreeform));    Ok(())}#[tokio::test]async fn legacy_toggles_map_to_features() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        experimental_use_unified_exec_tool: Some(true),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert!(config.features.enabled(Feature::UnifiedExec));    assert!(config.use_experimental_unified_exec_tool);    Ok(())}#[tokio::test]async fn responses_websocket_features_do_not_change_wire_api() -> std::io::Result<()> {    for feature_key in ["responses_websockets", "responses_websockets_v2"] {        let codex_home = TempDir::new()?;        let mut entries = BTreeMap::new();        entries.insert(feature_key.to_string(), true);        let cfg = ConfigToml {            features: Some(FeaturesToml::from(entries)),            ..Default::default()        };        let config = Config::load_from_base_config_with_overrides(            cfg,            ConfigOverrides::default(),            codex_home.abs(),        )        .await?;        assert_eq!(config.model_provider.wire_api, WireApi::Responses);    }    Ok(())}#[tokio::test]async fn config_honors_explicit_file_oauth_store_mode() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        mcp_oauth_credentials_store: Some(OAuthCredentialsStoreMode::File),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.mcp_oauth_credentials_store_mode,        OAuthCredentialsStoreMode::File,    );    Ok(())}#[tokio::test]async fn managed_config_overrides_oauth_store_mode() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let managed_path = codex_home.path().join("managed_config.toml");    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    std::fs::write(&config_path, "mcp_oauth_credentials_store = \"file\"\n")?;    std::fs::write(&managed_path, "mcp_oauth_credentials_store = \"keyring\"\n")?;    let overrides = LoaderOverrides::with_managed_config_path_for_tests(managed_path.clone());    let cwd = codex_home.path().abs();    let config_layer_stack = load_config_layers_state(        LOCAL_FS.as_ref(),        codex_home.path(),        Some(cwd),        &Vec::new(),        overrides,        &codex_config::NoopThreadConfigLoader,    )    .await?;    let cfg =        deserialize_config_toml_with_base(config_layer_stack.effective_config(), codex_home.path())            .map_err(|e| {                tracing::error!("Failed to deserialize overridden config: {e}");                e            })?;    assert_eq!(        cfg.mcp_oauth_credentials_store,        Some(OAuthCredentialsStoreMode::Keyring),    );    let final_config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        final_config.mcp_oauth_credentials_store_mode,        resolve_mcp_oauth_credentials_store_mode(            OAuthCredentialsStoreMode::Keyring,            env!("CARGO_PKG_VERSION"),        ),    );    Ok(())}#[tokio::test]async fn load_global_mcp_servers_returns_empty_if_missing() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = load_global_mcp_servers(codex_home.path()).await?;    assert!(servers.is_empty());    Ok(())}#[tokio::test]async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let mut servers = BTreeMap::new();    servers.insert(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::Stdio {                command: "echo".to_string(),                args: vec!["hello".to_string()],                env: None,                env_vars: Vec::new(),                cwd: Some(codex_home.path().to_path_buf()),            },            environment_id: "remote".to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: Some(Duration::from_secs(3)),            tool_timeout_sec: Some(Duration::from_secs(5)),            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    );    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let loaded = load_global_mcp_servers(codex_home.path()).await?;    assert_eq!(loaded.len(), 1);    let docs = loaded.get("docs").expect("docs entry");    match &docs.transport {        McpServerTransportConfig::Stdio {            command,            args,            env,            env_vars,            cwd,        } => {            assert_eq!(command, "echo");            assert_eq!(args, &vec!["hello".to_string()]);            assert!(env.is_none());            assert!(env_vars.is_empty());            assert_eq!(cwd, &Some(codex_home.path().to_path_buf()));        }        other => panic!("unexpected transport {other:?}"),    }    assert_eq!(docs.startup_timeout_sec, Some(Duration::from_secs(3)));    assert_eq!(docs.tool_timeout_sec, Some(Duration::from_secs(5)));    assert_eq!(docs.environment_id, "remote");    assert!(docs.enabled);    let empty = BTreeMap::new();    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(empty.clone())],    )?;    let loaded = load_global_mcp_servers(codex_home.path()).await?;    assert!(loaded.is_empty());    Ok(())}#[tokio::test]async fn managed_config_wins_over_cli_overrides() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let managed_path = codex_home.path().join("managed_config.toml");    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        "model = \"base\"\n",    )?;    std::fs::write(&managed_path, "model = \"managed_config\"\n")?;    let overrides = LoaderOverrides::with_managed_config_path_for_tests(managed_path);    let cwd = codex_home.path().abs();    let config_layer_stack = load_config_layers_state(        LOCAL_FS.as_ref(),        codex_home.path(),        Some(cwd),        &[("model".to_string(), TomlValue::String("cli".to_string()))],        overrides,        &codex_config::NoopThreadConfigLoader,    )    .await?;    let cfg =        deserialize_config_toml_with_base(config_layer_stack.effective_config(), codex_home.path())            .map_err(|e| {                tracing::error!("Failed to deserialize overridden config: {e}");                e            })?;    assert_eq!(cfg.model.as_deref(), Some("managed_config"));    Ok(())}#[tokio::test]async fn load_global_mcp_servers_accepts_legacy_ms_field() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    std::fs::write(        &config_path,        r#"[mcp_servers][mcp_servers.docs]command = "echo"startup_timeout_ms = 2500"#,    )?;    let servers = load_global_mcp_servers(codex_home.path()).await?;    let docs = servers.get("docs").expect("docs entry");    assert_eq!(docs.startup_timeout_sec, Some(Duration::from_millis(2500)));    Ok(())}#[test]fn mcp_servers_toml_parses_per_tool_approval_overrides() {    let config = toml::from_str::<ConfigToml>(        r#"[mcp_servers.docs]command = "docs-server"name = "Docs"default_tools_approval_mode = "prompt"[mcp_servers.docs.tools.search]approval_mode = "approve""#,    )    .expect("TOML deserialization should succeed");    let server = config        .mcp_servers        .get("docs")        .expect("docs server config exists");    assert_eq!(        server.default_tools_approval_mode,        Some(AppToolApproval::Prompt)    );    assert_eq!(        server.tools.get("search"),        Some(&McpServerToolConfig {            approval_mode: Some(AppToolApproval::Approve),        })    );}#[test]fn mcp_servers_toml_ignores_unknown_server_fields() {    let config = toml::from_str::<ConfigToml>(        r#"[mcp_servers.docs]command = "docs-server"trust_level = "trusted""#,    )    .expect("unknown MCP server fields should be ignored");    assert_eq!(        config.mcp_servers.get("docs"),        Some(&stdio_mcp("docs-server"))    );}#[test]fn mcp_servers_toml_parses_tool_approval_override_for_reserved_name() {    let config = toml::from_str::<ConfigToml>(        r#"[mcp_servers.docs]command = "docs-server"[mcp_servers.docs.tools.command]approval_mode = "approve""#,    )    .expect("TOML deserialization should succeed");    let tool = config        .mcp_servers        .get("docs")        .and_then(|server| server.tools.get("command"))        .expect("docs/command tool config exists");    assert_eq!(        tool,        &McpServerToolConfig {            approval_mode: Some(AppToolApproval::Approve),        }    );}#[test]fn desktop_toml_round_trips_opaque_nested_values() -> anyhow::Result<()> {    let parsed = toml::from_str::<ConfigToml>(        r#"[desktop]appearanceTheme = "dark"selected-avatar-id = "codex"recentViews = ["threads", "settings"][desktop.workspace]collapsed = truewidth = 320pane = { selected = "console", expanded = false }"#,    )?;    let desktop = parsed        .desktop        .as_ref()        .expect("desktop settings should deserialize");    assert_eq!(        desktop.get("appearanceTheme"),        Some(&serde_json::json!("dark"))    );    assert_eq!(        desktop.get("selected-avatar-id"),        Some(&serde_json::json!("codex"))    );    assert_eq!(        desktop.get("recentViews"),        Some(&serde_json::json!(["threads", "settings"]))    );    assert_eq!(        desktop.get("workspace"),        Some(&serde_json::json!({            "collapsed": true,            "width": 320,            "pane": {                "selected": "console",                "expanded": false,            },        }))    );    let serialized = toml::to_string(&parsed)?;    let reparsed = toml::from_str::<ConfigToml>(&serialized)?;    assert_eq!(reparsed.desktop, parsed.desktop);    Ok(())}#[tokio::test]async fn to_mcp_config_preserves_apps_feature_from_config() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let mut config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());    config.apps_mcp_product_sku = Some("tpp".to_string());    let mcp_config = config.to_mcp_config(&plugins_manager).await;    assert!(mcp_config.apps_enabled);    assert_eq!(mcp_config.apps_mcp_product_sku.as_deref(), Some("tpp"));    let _ = config.features.disable(Feature::Apps);    let mcp_config = config.to_mcp_config(&plugins_manager).await;    assert!(!mcp_config.apps_enabled);    let _ = config.features.enable(Feature::Apps);    let mcp_config = config.to_mcp_config(&plugins_manager).await;    assert!(mcp_config.apps_enabled);    Ok(())}#[tokio::test]async fn to_mcp_config_flows_mcp_tool_prefix_from_feature() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let mut config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());    let mcp_config = config.to_mcp_config(&plugins_manager).await;    assert!(mcp_config.prefix_mcp_tool_names);    let _ = config.features.enable(Feature::NonPrefixedMcpToolNames);    let mcp_config = config.to_mcp_config(&plugins_manager).await;    assert!(!mcp_config.prefix_mcp_tool_names);    Ok(())}#[tokio::test]async fn to_mcp_config_preserves_auth_elicitation_feature_from_config() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let mut config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    let plugins_manager = PluginsManager::new(codex_home.path().to_path_buf());    let mcp_config = config.to_mcp_config(&plugins_manager).await;    assert_eq!(        mcp_config.client_elicitation_capability,        ElicitationCapability::default()    );    let _ = config.features.enable(Feature::AuthElicitation);    let mcp_config = config.to_mcp_config(&plugins_manager).await;    assert_eq!(        mcp_config.client_elicitation_capability,        ElicitationCapability {            form: Some(FormElicitationCapability::default()),            url: Some(UrlElicitationCapability::default()),        }    );    Ok(())}#[tokio::test]async fn load_global_mcp_servers_rejects_inline_bearer_token() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    std::fs::write(        &config_path,        r#"[mcp_servers.docs]url = "https://example.com/mcp"bearer_token = "secret""#,    )?;    let err = load_global_mcp_servers(codex_home.path())        .await        .expect_err("bearer_token entries should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);    assert!(err.to_string().contains("bearer_token"));    assert!(err.to_string().contains("bearer_token_env_var"));    Ok(())}#[tokio::test]async fn replace_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::Stdio {                command: "docs-server".to_string(),                args: vec!["--verbose".to_string()],                env: Some(HashMap::from([                    ("ZIG_VAR".to_string(), "3".to_string()),                    ("ALPHA_VAR".to_string(), "1".to_string()),                ])),                env_vars: Vec::new(),                cwd: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert_eq!(        serialized,        r#"[mcp_servers.docs]command = "docs-server"args = ["--verbose"][mcp_servers.docs.env]ALPHA_VAR = "1"ZIG_VAR = "3""#    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    match &docs.transport {        McpServerTransportConfig::Stdio {            command,            args,            env,            env_vars,            cwd,        } => {            assert_eq!(command, "docs-server");            assert_eq!(args, &vec!["--verbose".to_string()]);            let env = env                .as_ref()                .expect("env should be preserved for stdio transport");            assert_eq!(env.get("ALPHA_VAR"), Some(&"1".to_string()));            assert_eq!(env.get("ZIG_VAR"), Some(&"3".to_string()));            assert!(env_vars.is_empty());            assert!(cwd.is_none());        }        other => panic!("unexpected transport {other:?}"),    }    Ok(())}#[tokio::test]async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::Stdio {                command: "docs-server".to_string(),                args: Vec::new(),                env: None,                env_vars: vec!["ALPHA".into(), "BETA".into()],                cwd: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert!(        serialized.contains(r#"env_vars = ["ALPHA", "BETA"]"#),        "serialized config missing env_vars field:\n{serialized}"    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    match &docs.transport {        McpServerTransportConfig::Stdio { env_vars, .. } => {            assert_eq!(env_vars, &vec!["ALPHA".into(), "BETA".into()]);        }        other => panic!("unexpected transport {other:?}"),    }    Ok(())}#[tokio::test]async fn replace_mcp_servers_serializes_sourced_env_vars() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::Stdio {                command: "docs-server".to_string(),                args: Vec::new(),                env: None,                env_vars: vec![                    "LEGACY".into(),                    McpServerEnvVar::Config {                        name: "REMOTE_TOKEN".to_string(),                        source: Some("remote".to_string()),                    },                ],                cwd: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert!(        serialized            .contains(r#"env_vars = ["LEGACY", { name = "REMOTE_TOKEN", source = "remote" }]"#),        "serialized config missing sourced env_vars field:\n{serialized}"    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    assert_eq!(loaded, servers);    Ok(())}#[tokio::test]async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let cwd_path = PathBuf::from("/tmp/codex-mcp");    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::Stdio {                command: "docs-server".to_string(),                args: Vec::new(),                env: None,                env_vars: Vec::new(),                cwd: Some(cwd_path.clone()),            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert!(        serialized.contains(r#"cwd = "/tmp/codex-mcp""#),        "serialized config missing cwd field:\n{serialized}"    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    match &docs.transport {        McpServerTransportConfig::Stdio { cwd, .. } => {            assert_eq!(cwd.as_deref(), Some(Path::new("/tmp/codex-mcp")));        }        other => panic!("unexpected transport {other:?}"),    }    Ok(())}#[tokio::test]async fn replace_mcp_servers_streamable_http_serializes_bearer_token() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::StreamableHttp {                url: "https://example.com/mcp".to_string(),                bearer_token_env_var: Some("MCP_TOKEN".to_string()),                http_headers: None,                env_http_headers: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: Some(Duration::from_secs(2)),            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert_eq!(        serialized,        r#"[mcp_servers.docs]url = "https://example.com/mcp"bearer_token_env_var = "MCP_TOKEN"startup_timeout_sec = 2.0"#    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    match &docs.transport {        McpServerTransportConfig::StreamableHttp {            url,            bearer_token_env_var,            http_headers,            env_http_headers,        } => {            assert_eq!(url, "https://example.com/mcp");            assert_eq!(bearer_token_env_var.as_deref(), Some("MCP_TOKEN"));            assert!(http_headers.is_none());            assert!(env_http_headers.is_none());        }        other => panic!("unexpected transport {other:?}"),    }    assert_eq!(docs.startup_timeout_sec, Some(Duration::from_secs(2)));    Ok(())}#[tokio::test]async fn replace_mcp_servers_streamable_http_serializes_custom_headers() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::StreamableHttp {                url: "https://example.com/mcp".to_string(),                bearer_token_env_var: Some("MCP_TOKEN".to_string()),                http_headers: Some(HashMap::from([("X-Doc".to_string(), "42".to_string())])),                env_http_headers: Some(HashMap::from([(                    "X-Auth".to_string(),                    "DOCS_AUTH".to_string(),                )])),            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: Some(Duration::from_secs(2)),            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert_eq!(        serialized,        r#"[mcp_servers.docs]url = "https://example.com/mcp"bearer_token_env_var = "MCP_TOKEN"startup_timeout_sec = 2.0[mcp_servers.docs.http_headers]X-Doc = "42"[mcp_servers.docs.env_http_headers]X-Auth = "DOCS_AUTH""#    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    match &docs.transport {        McpServerTransportConfig::StreamableHttp {            http_headers,            env_http_headers,            ..        } => {            assert_eq!(                http_headers,                &Some(HashMap::from([("X-Doc".to_string(), "42".to_string())]))            );            assert_eq!(                env_http_headers,                &Some(HashMap::from([(                    "X-Auth".to_string(),                    "DOCS_AUTH".to_string()                )]))            );        }        other => panic!("unexpected transport {other:?}"),    }    Ok(())}#[tokio::test]async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let mut servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::StreamableHttp {                url: "https://example.com/mcp".to_string(),                bearer_token_env_var: Some("MCP_TOKEN".to_string()),                http_headers: Some(HashMap::from([("X-Doc".to_string(), "42".to_string())])),                env_http_headers: Some(HashMap::from([(                    "X-Auth".to_string(),                    "DOCS_AUTH".to_string(),                )])),            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: Some(Duration::from_secs(2)),            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let serialized_with_optional = std::fs::read_to_string(&config_path)?;    assert!(serialized_with_optional.contains("bearer_token_env_var = \"MCP_TOKEN\""));    assert!(serialized_with_optional.contains("[mcp_servers.docs.http_headers]"));    assert!(serialized_with_optional.contains("[mcp_servers.docs.env_http_headers]"));    servers.insert(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::StreamableHttp {                url: "https://example.com/mcp".to_string(),                bearer_token_env_var: None,                http_headers: None,                env_http_headers: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    );    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let serialized = std::fs::read_to_string(&config_path)?;    assert_eq!(        serialized,        r#"[mcp_servers.docs]url = "https://example.com/mcp""#    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    match &docs.transport {        McpServerTransportConfig::StreamableHttp {            url,            bearer_token_env_var,            http_headers,            env_http_headers,        } => {            assert_eq!(url, "https://example.com/mcp");            assert!(bearer_token_env_var.is_none());            assert!(http_headers.is_none());            assert!(env_http_headers.is_none());        }        other => panic!("unexpected transport {other:?}"),    }    assert!(docs.startup_timeout_sec.is_none());    Ok(())}#[tokio::test]async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers() -> anyhow::Result<()>{    let codex_home = TempDir::new()?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let servers = BTreeMap::from([        (            "docs".to_string(),            McpServerConfig {                transport: McpServerTransportConfig::StreamableHttp {                    url: "https://example.com/mcp".to_string(),                    bearer_token_env_var: Some("MCP_TOKEN".to_string()),                    http_headers: Some(HashMap::from([("X-Doc".to_string(), "42".to_string())])),                    env_http_headers: Some(HashMap::from([(                        "X-Auth".to_string(),                        "DOCS_AUTH".to_string(),                    )])),                },                environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),                enabled: true,                required: false,                supports_parallel_tool_calls: false,                disabled_reason: None,                startup_timeout_sec: Some(Duration::from_secs(2)),                tool_timeout_sec: None,                default_tools_approval_mode: None,                enabled_tools: None,                disabled_tools: None,                scopes: None,                oauth: None,                oauth_resource: None,                tools: HashMap::new(),            },        ),        (            "logs".to_string(),            McpServerConfig {                transport: McpServerTransportConfig::Stdio {                    command: "logs-server".to_string(),                    args: vec!["--follow".to_string()],                    env: None,                    env_vars: Vec::new(),                    cwd: None,                },                environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),                enabled: true,                required: false,                supports_parallel_tool_calls: false,                disabled_reason: None,                startup_timeout_sec: None,                tool_timeout_sec: None,                default_tools_approval_mode: None,                enabled_tools: None,                disabled_tools: None,                scopes: None,                oauth: None,                oauth_resource: None,                tools: HashMap::new(),            },        ),    ]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let serialized = std::fs::read_to_string(&config_path)?;    assert!(        serialized.contains("[mcp_servers.docs.http_headers]"),        "serialized config missing docs headers section:\n{serialized}"    );    assert!(        !serialized.contains("[mcp_servers.logs.http_headers]"),        "serialized config should not add logs headers section:\n{serialized}"    );    assert!(        !serialized.contains("[mcp_servers.logs.env_http_headers]"),        "serialized config should not add logs env headers section:\n{serialized}"    );    assert!(        !serialized.contains("mcp_servers.logs.bearer_token_env_var"),        "serialized config should not add bearer token to logs:\n{serialized}"    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    match &docs.transport {        McpServerTransportConfig::StreamableHttp {            http_headers,            env_http_headers,            ..        } => {            assert_eq!(                http_headers,                &Some(HashMap::from([("X-Doc".to_string(), "42".to_string())]))            );            assert_eq!(                env_http_headers,                &Some(HashMap::from([(                    "X-Auth".to_string(),                    "DOCS_AUTH".to_string()                )]))            );        }        other => panic!("unexpected transport {other:?}"),    }    let logs = loaded.get("logs").expect("logs entry");    match &logs.transport {        McpServerTransportConfig::Stdio { env, .. } => {            assert!(env.is_none());        }        other => panic!("unexpected transport {other:?}"),    }    Ok(())}#[tokio::test]async fn replace_mcp_servers_serializes_disabled_flag() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::Stdio {                command: "docs-server".to_string(),                args: Vec::new(),                env: None,                env_vars: Vec::new(),                cwd: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: false,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert!(        serialized.contains("enabled = false"),        "serialized config missing disabled flag:\n{serialized}"    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    assert!(!docs.enabled);    Ok(())}#[tokio::test]async fn replace_mcp_servers_serializes_required_flag() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::Stdio {                command: "docs-server".to_string(),                args: Vec::new(),                env: None,                env_vars: Vec::new(),                cwd: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: true,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert!(        serialized.contains("required = true"),        "serialized config missing required flag:\n{serialized}"    );    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    assert!(docs.required);    Ok(())}#[tokio::test]async fn replace_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::Stdio {                command: "docs-server".to_string(),                args: Vec::new(),                env: None,                env_vars: Vec::new(),                cwd: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: Some(vec!["allowed".to_string()]),            disabled_tools: Some(vec!["blocked".to_string()]),            scopes: None,            oauth: None,            oauth_resource: None,            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert!(serialized.contains(r#"enabled_tools = ["allowed"]"#));    assert!(serialized.contains(r#"disabled_tools = ["blocked"]"#));    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    assert_eq!(        docs.enabled_tools.as_ref(),        Some(&vec!["allowed".to_string()])    );    assert_eq!(        docs.disabled_tools.as_ref(),        Some(&vec!["blocked".to_string()])    );    Ok(())}#[tokio::test]async fn replace_mcp_servers_streamable_http_serializes_oauth_resource() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let servers = BTreeMap::from([(        "docs".to_string(),        McpServerConfig {            transport: McpServerTransportConfig::StreamableHttp {                url: "https://example.com/mcp".to_string(),                bearer_token_env_var: None,                http_headers: None,                env_http_headers: None,            },            environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),            enabled: true,            required: false,            supports_parallel_tool_calls: false,            disabled_reason: None,            startup_timeout_sec: None,            tool_timeout_sec: None,            default_tools_approval_mode: None,            enabled_tools: None,            disabled_tools: None,            scopes: None,            oauth: Some(McpServerOAuthConfig {                client_id: Some("eci-prd-pub-codex-123".to_string()),            }),            oauth_resource: Some("https://resource.example.com".to_string()),            tools: HashMap::new(),        },    )]);    apply_blocking(        codex_home.path(),        &[ConfigEdit::ReplaceMcpServers(servers.clone())],    )?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    let serialized = std::fs::read_to_string(&config_path)?;    assert!(serialized.contains("[mcp_servers.docs.oauth]"));    assert!(serialized.contains(r#"client_id = "eci-prd-pub-codex-123""#));    assert!(serialized.contains(r#"oauth_resource = "https://resource.example.com""#));    let loaded = load_global_mcp_servers(codex_home.path()).await?;    let docs = loaded.get("docs").expect("docs entry");    assert_eq!(        docs.oauth_resource.as_deref(),        Some("https://resource.example.com")    );    assert_eq!(docs.oauth_client_id(), Some("eci-prd-pub-codex-123"));    Ok(())}#[tokio::test]async fn set_model_updates_defaults() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    ConfigEditsBuilder::new(codex_home.path())        .set_model(Some("gpt-5.4"), Some(ReasoningEffort::High))        .apply()        .await?;    let serialized = tokio::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).await?;    let parsed: ConfigToml = toml::from_str(&serialized)?;    assert_eq!(parsed.model.as_deref(), Some("gpt-5.4"));    assert_eq!(parsed.model_reasoning_effort, Some(ReasoningEffort::High));    Ok(())}#[tokio::test]async fn for_config_writes_selected_user_config_file() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let base_config = codex_home.path().join(CONFIG_TOML_FILE);    let selected_config = codex_home.path().join("work.config.toml");    tokio::fs::write(&base_config, r#"model_provider = "openai""#).await?;    tokio::fs::write(&selected_config, r#"model = "gpt-old""#).await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .loader_overrides(LoaderOverrides {            user_config_path: Some(selected_config.abs()),            user_config_profile: Some("work".parse().expect("profile-v2 name")),            ..LoaderOverrides::without_managed_config_for_tests()        })        .build()        .await?;    ConfigEditsBuilder::for_config(&config)        .set_model(Some("gpt-new"), Some(ReasoningEffort::High))        .apply()        .await?;    let selected_serialized = tokio::fs::read_to_string(&selected_config).await?;    let selected: ConfigToml = toml::from_str(&selected_serialized)?;    assert_eq!(selected.model.as_deref(), Some("gpt-new"));    assert_eq!(selected.model_reasoning_effort, Some(ReasoningEffort::High));    assert_eq!(        tokio::fs::read_to_string(&base_config).await?,        r#"model_provider = "openai""#    );    Ok(())}#[test]fn profile_v2_config_path_resolves_validated_names() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let profile_name: ProfileV2Name = "work".parse()?;    assert_eq!(        resolve_profile_v2_config_path(codex_home.path(), &profile_name),        codex_home.path().join("work.config.toml").abs()    );    Ok(())}#[tokio::test]async fn set_model_overwrites_existing_model() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let config_path = codex_home.path().join(CONFIG_TOML_FILE);    tokio::fs::write(        &config_path,        r#"model = "gpt-5.4"model_reasoning_effort = "medium"[profiles.dev]model = "gpt-4.1""#,    )    .await?;    ConfigEditsBuilder::new(codex_home.path())        .set_model(Some("o4-mini"), Some(ReasoningEffort::High))        .apply()        .await?;    let serialized = tokio::fs::read_to_string(config_path).await?;    let parsed: ConfigToml = toml::from_str(&serialized)?;    assert_eq!(parsed.model.as_deref(), Some("o4-mini"));    assert_eq!(parsed.model_reasoning_effort, Some(ReasoningEffort::High));    assert_eq!(        parsed            .profiles            .get("dev")            .and_then(|profile| profile.model.as_deref()),        Some("gpt-4.1"),    );    Ok(())}struct PrecedenceTestFixture {    cwd: TempDir,    codex_home: TempDir,    cfg: ConfigToml,}impl PrecedenceTestFixture {    fn cwd_path(&self) -> PathBuf {        self.cwd.path().to_path_buf()    }    fn codex_home(&self) -> AbsolutePathBuf {        self.codex_home.abs()    }}#[tokio::test]async fn cli_override_sets_compact_prompt() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let overrides = ConfigOverrides {        compact_prompt: Some("Use the compact override".to_string()),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        ConfigToml::default(),        overrides,        codex_home.abs(),    )    .await?;    assert_eq!(        config.compact_prompt.as_deref(),        Some("Use the compact override")    );    Ok(())}#[tokio::test]async fn loads_compact_prompt_from_file() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let workspace = codex_home.path().join("workspace");    std::fs::create_dir_all(&workspace)?;    let prompt_path = workspace.join("compact_prompt.txt");    std::fs::write(&prompt_path, "  summarize differently  ")?;    let cfg = ConfigToml {        experimental_compact_prompt_file: Some(prompt_path.abs()),        ..Default::default()    };    let overrides = ConfigOverrides {        cwd: Some(workspace),        ..Default::default()    };    let config =        Config::load_from_base_config_with_overrides(cfg, overrides, codex_home.abs()).await?;    assert_eq!(        config.compact_prompt.as_deref(),        Some("summarize differently")    );    Ok(())}#[tokio::test]async fn load_config_uses_requirements_guardian_policy_config() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    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()        },    )    .map_err(std::io::Error::other)?;    let config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        ConfigToml::default(),        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        config_layer_stack,    )    .await?;    assert_eq!(        config.guardian_policy_config.as_deref(),        Some("Use the workspace-managed guardian policy.")    );    Ok(())}#[test]fn config_toml_deserializes_auto_review_policy() {    let cfg = toml::from_str::<ConfigToml>(        r#"[auto_review]policy = "Use the user-configured guardian policy.""#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.auto_review            .as_ref()            .and_then(|auto_review| auto_review.policy.as_deref()),        Some("Use the user-configured guardian policy.")    );}#[tokio::test]async fn load_config_uses_auto_review_guardian_policy_config() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        auto_review: Some(AutoReviewToml {            policy: Some("  Use the user-configured guardian policy.  ".to_string()),        }),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(        config.guardian_policy_config.as_deref(),        Some("Use the user-configured guardian policy.")    );    Ok(())}#[tokio::test]async fn requirements_guardian_policy_beats_auto_review() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config_layer_stack = ConfigLayerStack::new(        Vec::new(),        Default::default(),        codex_config::ConfigRequirementsToml {            guardian_policy_config: Some("Use the managed guardian policy.".to_string()),            ..Default::default()        },    )    .map_err(std::io::Error::other)?;    let cfg = ConfigToml {        auto_review: Some(AutoReviewToml {            policy: Some("Use the user-configured guardian policy.".to_string()),        }),        ..Default::default()    };    let config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        cfg,        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        config_layer_stack,    )    .await?;    assert_eq!(        config.guardian_policy_config.as_deref(),        Some("Use the managed guardian policy.")    );    Ok(())}#[tokio::test]async fn load_config_ignores_empty_auto_review_guardian_policy_config() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        auto_review: Some(AutoReviewToml {            policy: Some("   ".to_string()),        }),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    assert_eq!(config.guardian_policy_config, None);    Ok(())}#[tokio::test]async fn load_config_ignores_empty_requirements_guardian_policy_config() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config_layer_stack = ConfigLayerStack::new(        Vec::new(),        Default::default(),        codex_config::ConfigRequirementsToml {            guardian_policy_config: Some("   ".to_string()),            ..Default::default()        },    )    .map_err(std::io::Error::other)?;    let config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        ConfigToml::default(),        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        config_layer_stack,    )    .await?;    assert_eq!(config.guardian_policy_config, None);    Ok(())}#[tokio::test]async fn load_config_rejects_missing_agent_role_config_file() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let missing_path = codex_home.path().join("agents").join("researcher.toml");    let cfg = ConfigToml {        agents: Some(AgentsToml {            max_threads: None,            max_depth: None,            job_max_runtime_seconds: None,            interrupt_message: None,            roles: BTreeMap::from([(                "researcher".to_string(),                AgentRoleToml {                    description: Some("Research role".to_string()),                    config_file: Some(missing_path.abs()),                    nickname_candidates: None,                },            )]),        }),        ..Default::default()    };    let result = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await;    let err = result.expect_err("missing role config file should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    let message = err.to_string();    assert!(message.contains("agents.researcher.config_file"));    assert!(message.contains("must point to an existing file"));    Ok(())}#[tokio::test]async fn agent_role_relative_config_file_resolves_against_config_toml() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let role_config_path = codex_home.path().join("agents").join("researcher.toml");    tokio::fs::create_dir_all(        role_config_path            .parent()            .expect("role config should have a parent directory"),    )    .await?;    tokio::fs::write(        &role_config_path,        "developer_instructions = \"Research carefully\"\nmodel = \"gpt-5\"",    )    .await?;    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[agents.researcher]description = "Research role"config_file = "./agents/researcher.toml"nickname_candidates = ["Hypatia", "Noether"]"#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.config_file.as_ref()),        Some(&role_config_path)    );    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Hypatia", "Noether"])    );    Ok(())}#[tokio::test]async fn agent_role_relative_config_file_resolves_from_config_layer() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let role_config_path = codex_home.path().join("agents").join("researcher.toml");    tokio::fs::create_dir_all(        role_config_path            .parent()            .expect("role config should have a parent directory"),    )    .await?;    tokio::fs::write(        &role_config_path,        "developer_instructions = \"Research carefully\"\nmodel = \"gpt-5\"",    )    .await?;    let layer_config = toml::from_str(        r#"[agents.researcher]description = "Research role"config_file = "./agents/researcher.toml""#,    )    .expect("agent role layer config should parse");    let config_layer_stack = codex_config::ConfigLayerStack::new(        vec![codex_config::ConfigLayerEntry::new(            codex_app_server_protocol::ConfigLayerSource::User {                file: codex_home.path().join(CONFIG_TOML_FILE).abs(),                profile: None,            },            layer_config,        )],        Default::default(),        codex_config::ConfigRequirementsToml::default(),    )    .map_err(std::io::Error::other)?;    let config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        ConfigToml::default(),        ConfigOverrides {            cwd: Some(codex_home.path().to_path_buf()),            ..Default::default()        },        codex_home.abs(),        config_layer_stack,    )    .await?;    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.config_file.as_ref()),        Some(&role_config_path)    );    Ok(())}#[tokio::test]async fn agent_role_file_metadata_overrides_config_toml_metadata() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let role_config_path = codex_home.path().join("agents").join("researcher.toml");    tokio::fs::create_dir_all(        role_config_path            .parent()            .expect("role config should have a parent directory"),    )    .await?;    tokio::fs::write(        &role_config_path,        r#"description = "Role metadata from file"nickname_candidates = ["Hypatia"]developer_instructions = "Research carefully"model = "gpt-5.2""#,    )    .await?;    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[agents.researcher]description = "Research role from config"config_file = "./agents/researcher.toml"nickname_candidates = ["Noether"]"#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    let role = config        .agent_roles        .get("researcher")        .expect("researcher role should load");    assert_eq!(role.description.as_deref(), Some("Role metadata from file"));    assert_eq!(role.config_file.as_ref(), Some(&role_config_path));    assert_eq!(        role.nickname_candidates            .as_ref()            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Hypatia"])    );    Ok(())}#[tokio::test]async fn agent_role_file_without_developer_instructions_is_dropped_with_warning()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let repo_root = TempDir::new()?;    let nested_cwd = repo_root.path().join("packages").join("app");    std::fs::create_dir_all(repo_root.path().join(".git"))?;    std::fs::create_dir_all(&nested_cwd)?;    let workspace_key = repo_root.path().to_string_lossy().replace('\\', "\\\\");    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[projects."{workspace_key}"]trust_level = "trusted""#        ),    )    .await?;    let standalone_agents_dir = repo_root.path().join(".codex").join("agents");    tokio::fs::create_dir_all(&standalone_agents_dir).await?;    tokio::fs::write(        standalone_agents_dir.join("researcher.toml"),        r#"name = "researcher"description = "Role metadata from file"model = "gpt-5.2""#,    )    .await?;    tokio::fs::write(        standalone_agents_dir.join("reviewer.toml"),        r#"name = "reviewer"description = "Review role"developer_instructions = "Review carefully"model = "gpt-5.2""#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .harness_overrides(ConfigOverrides {            cwd: Some(nested_cwd),            ..Default::default()        })        .build()        .await?;    assert!(!config.agent_roles.contains_key("researcher"));    assert_eq!(        config            .agent_roles            .get("reviewer")            .and_then(|role| role.description.as_deref()),        Some("Review role")    );    assert!(        config            .startup_warnings            .iter()            .any(|warning| warning.contains("must define `developer_instructions`"))    );    Ok(())}#[tokio::test]async fn legacy_agent_role_config_file_allows_missing_developer_instructions() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    let role_config_path = codex_home.path().join("agents").join("researcher.toml");    tokio::fs::create_dir_all(        role_config_path            .parent()            .expect("role config should have a parent directory"),    )    .await?;    tokio::fs::write(        &role_config_path,        r#"model = "gpt-5.2"model_reasoning_effort = "high""#,    )    .await?;    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[agents.researcher]description = "Research role from config"config_file = "./agents/researcher.toml""#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.description.as_deref()),        Some("Research role from config")    );    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.config_file.as_ref()),        Some(&role_config_path)    );    Ok(())}#[tokio::test]async fn agent_role_without_description_after_merge_is_dropped_with_warning() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    let role_config_path = codex_home.path().join("agents").join("researcher.toml");    tokio::fs::create_dir_all(        role_config_path            .parent()            .expect("role config should have a parent directory"),    )    .await?;    tokio::fs::write(        &role_config_path,        r#"developer_instructions = "Research carefully"model = "gpt-5.2""#,    )    .await?;    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[agents.researcher]config_file = "./agents/researcher.toml"[agents.reviewer]description = "Review role""#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert!(!config.agent_roles.contains_key("researcher"));    assert_eq!(        config            .agent_roles            .get("reviewer")            .and_then(|role| role.description.as_deref()),        Some("Review role")    );    assert!(        config            .startup_warnings            .iter()            .any(|warning| warning.contains("agent role `researcher` must define a description"))    );    Ok(())}#[tokio::test]async fn discovered_agent_role_file_without_name_is_dropped_with_warning() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let repo_root = TempDir::new()?;    let nested_cwd = repo_root.path().join("packages").join("app");    std::fs::create_dir_all(repo_root.path().join(".git"))?;    std::fs::create_dir_all(&nested_cwd)?;    let workspace_key = repo_root.path().to_string_lossy().replace('\\', "\\\\");    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[projects."{workspace_key}"]trust_level = "trusted""#        ),    )    .await?;    let standalone_agents_dir = repo_root.path().join(".codex").join("agents");    tokio::fs::create_dir_all(&standalone_agents_dir).await?;    tokio::fs::write(        standalone_agents_dir.join("researcher.toml"),        r#"description = "Role metadata from file"developer_instructions = "Research carefully""#,    )    .await?;    tokio::fs::write(        standalone_agents_dir.join("reviewer.toml"),        r#"name = "reviewer"description = "Review role"developer_instructions = "Review carefully""#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .harness_overrides(ConfigOverrides {            cwd: Some(nested_cwd),            ..Default::default()        })        .build()        .await?;    assert!(!config.agent_roles.contains_key("researcher"));    assert_eq!(        config            .agent_roles            .get("reviewer")            .and_then(|role| role.description.as_deref()),        Some("Review role")    );    assert!(        config            .startup_warnings            .iter()            .any(|warning| warning.contains("must define a non-empty `name`"))    );    Ok(())}#[tokio::test]async fn agent_role_file_name_takes_precedence_over_config_key() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let role_config_path = codex_home.path().join("agents").join("researcher.toml");    tokio::fs::create_dir_all(        role_config_path            .parent()            .expect("role config should have a parent directory"),    )    .await?;    tokio::fs::write(        &role_config_path,        r#"name = "archivist"description = "Role metadata from file"developer_instructions = "Research carefully"model = "gpt-5.2""#,    )    .await?;    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[agents.researcher]description = "Research role from config"config_file = "./agents/researcher.toml""#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(config.agent_roles.contains_key("researcher"), false);    let role = config        .agent_roles        .get("archivist")        .expect("role should use file-provided name");    assert_eq!(role.description.as_deref(), Some("Role metadata from file"));    assert_eq!(role.config_file.as_ref(), Some(&role_config_path));    Ok(())}#[tokio::test]async fn loads_legacy_split_agent_roles_from_config_toml() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let researcher_path = codex_home.path().join("agents").join("researcher.toml");    let reviewer_path = codex_home.path().join("agents").join("reviewer.toml");    tokio::fs::create_dir_all(        researcher_path            .parent()            .expect("role config should have a parent directory"),    )    .await?;    tokio::fs::write(        &researcher_path,        "developer_instructions = \"Research carefully\"\nmodel = \"gpt-5\"",    )    .await?;    tokio::fs::write(        &reviewer_path,        "developer_instructions = \"Review carefully\"\nmodel = \"gpt-4.1\"",    )    .await?;    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[agents.researcher]description = "Research role"config_file = "./agents/researcher.toml"nickname_candidates = ["Hypatia", "Noether"][agents.reviewer]description = "Review role"config_file = "./agents/reviewer.toml"nickname_candidates = ["Atlas"]"#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.description.as_deref()),        Some("Research role")    );    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.config_file.as_ref()),        Some(&researcher_path)    );    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Hypatia", "Noether"])    );    assert_eq!(        config            .agent_roles            .get("reviewer")            .and_then(|role| role.description.as_deref()),        Some("Review role")    );    assert_eq!(        config            .agent_roles            .get("reviewer")            .and_then(|role| role.config_file.as_ref()),        Some(&reviewer_path)    );    assert_eq!(        config            .agent_roles            .get("reviewer")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Atlas"])    );    Ok(())}#[tokio::test]async fn discovers_multiple_standalone_agent_role_files() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let repo_root = TempDir::new()?;    let nested_cwd = repo_root.path().join("packages").join("app");    std::fs::create_dir_all(repo_root.path().join(".git"))?;    std::fs::create_dir_all(&nested_cwd)?;    let workspace_key = repo_root.path().to_string_lossy().replace('\\', "\\\\");    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[projects."{workspace_key}"]trust_level = "trusted""#        ),    )?;    let root_agent = repo_root        .path()        .join(".codex")        .join("agents")        .join("root.toml");    std::fs::create_dir_all(        root_agent            .parent()            .expect("root agent should have a parent directory"),    )?;    std::fs::write(        &root_agent,        r#"name = "researcher"description = "from root"developer_instructions = "Research carefully""#,    )?;    let nested_agent = repo_root        .path()        .join("packages")        .join(".codex")        .join("agents")        .join("review")        .join("nested.toml");    std::fs::create_dir_all(        nested_agent            .parent()            .expect("nested agent should have a parent directory"),    )?;    std::fs::write(        &nested_agent,        r#"name = "reviewer"description = "from nested"nickname_candidates = ["Atlas"]developer_instructions = "Review carefully""#,    )?;    let sibling_agent = repo_root        .path()        .join("packages")        .join(".codex")        .join("agents")        .join("writer.toml");    std::fs::create_dir_all(        sibling_agent            .parent()            .expect("sibling agent should have a parent directory"),    )?;    std::fs::write(        &sibling_agent,        r#"name = "writer"description = "from sibling"nickname_candidates = ["Sagan"]developer_instructions = "Write carefully""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .harness_overrides(ConfigOverrides {            cwd: Some(nested_cwd),            ..Default::default()        })        .build()        .await?;    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.description.as_deref()),        Some("from root")    );    assert_eq!(        config            .agent_roles            .get("reviewer")            .and_then(|role| role.description.as_deref()),        Some("from nested")    );    assert_eq!(        config            .agent_roles            .get("reviewer")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Atlas"])    );    assert_eq!(        config            .agent_roles            .get("writer")            .and_then(|role| role.description.as_deref()),        Some("from sibling")    );    assert_eq!(        config            .agent_roles            .get("writer")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Sagan"])    );    Ok(())}#[tokio::test]async fn mixed_legacy_and_standalone_agent_role_sources_merge_with_precedence()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let repo_root = TempDir::new()?;    let nested_cwd = repo_root.path().join("packages").join("app");    std::fs::create_dir_all(repo_root.path().join(".git"))?;    std::fs::create_dir_all(&nested_cwd)?;    let workspace_key = repo_root.path().to_string_lossy().replace('\\', "\\\\");    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[projects."{workspace_key}"]trust_level = "trusted"[agents.researcher]description = "Research role from config"config_file = "./agents/researcher.toml"nickname_candidates = ["Noether"][agents.critic]description = "Critic role from config"config_file = "./agents/critic.toml"nickname_candidates = ["Ada"]"#        ),    )    .await?;    let home_agents_dir = codex_home.path().join("agents");    tokio::fs::create_dir_all(&home_agents_dir).await?;    tokio::fs::write(        home_agents_dir.join("researcher.toml"),        r#"developer_instructions = "Research carefully"model = "gpt-5.2""#,    )    .await?;    tokio::fs::write(        home_agents_dir.join("critic.toml"),        r#"developer_instructions = "Critique carefully"model = "gpt-4.1""#,    )    .await?;    let standalone_agents_dir = repo_root.path().join(".codex").join("agents");    tokio::fs::create_dir_all(&standalone_agents_dir).await?;    tokio::fs::write(        standalone_agents_dir.join("researcher.toml"),        r#"name = "researcher"description = "Research role from file"nickname_candidates = ["Hypatia"]developer_instructions = "Research from file"model = "gpt-5-mini""#,    )    .await?;    tokio::fs::write(        standalone_agents_dir.join("writer.toml"),        r#"name = "writer"description = "Writer role from file"nickname_candidates = ["Sagan"]developer_instructions = "Write carefully"model = "gpt-5.2""#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .harness_overrides(ConfigOverrides {            cwd: Some(nested_cwd),            ..Default::default()        })        .build()        .await?;    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.description.as_deref()),        Some("Research role from file")    );    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.config_file.as_ref()),        Some(&standalone_agents_dir.join("researcher.toml"))    );    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Hypatia"])    );    assert_eq!(        config            .agent_roles            .get("critic")            .and_then(|role| role.description.as_deref()),        Some("Critic role from config")    );    assert_eq!(        config            .agent_roles            .get("critic")            .and_then(|role| role.config_file.as_ref()),        Some(&home_agents_dir.join("critic.toml"))    );    assert_eq!(        config            .agent_roles            .get("critic")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Ada"])    );    assert_eq!(        config            .agent_roles            .get("writer")            .and_then(|role| role.description.as_deref()),        Some("Writer role from file")    );    assert_eq!(        config            .agent_roles            .get("writer")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Sagan"])    );    Ok(())}#[tokio::test]async fn higher_precedence_agent_role_can_inherit_description_from_lower_layer()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let repo_root = TempDir::new()?;    let nested_cwd = repo_root.path().join("packages").join("app");    std::fs::create_dir_all(repo_root.path().join(".git"))?;    std::fs::create_dir_all(&nested_cwd)?;    let workspace_key = repo_root.path().to_string_lossy().replace('\\', "\\\\");    tokio::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[projects."{workspace_key}"]trust_level = "trusted"[agents.researcher]description = "Research role from config"config_file = "./agents/researcher.toml""#        ),    )    .await?;    let home_agents_dir = codex_home.path().join("agents");    tokio::fs::create_dir_all(&home_agents_dir).await?;    tokio::fs::write(        home_agents_dir.join("researcher.toml"),        r#"developer_instructions = "Research carefully"model = "gpt-5.2""#,    )    .await?;    let standalone_agents_dir = repo_root.path().join(".codex").join("agents");    tokio::fs::create_dir_all(&standalone_agents_dir).await?;    tokio::fs::write(        standalone_agents_dir.join("researcher.toml"),        r#"name = "researcher"nickname_candidates = ["Hypatia"]developer_instructions = "Research from file"model = "gpt-5-mini""#,    )    .await?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .harness_overrides(ConfigOverrides {            cwd: Some(nested_cwd),            ..Default::default()        })        .build()        .await?;    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.description.as_deref()),        Some("Research role from config")    );    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.config_file.as_ref()),        Some(&standalone_agents_dir.join("researcher.toml"))    );    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Hypatia"])    );    Ok(())}#[tokio::test]async fn load_config_resolves_agent_interrupt_message() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        agents: Some(AgentsToml {            interrupt_message: Some(false),            ..Default::default()        }),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert!(!config.agent_interrupt_message_enabled);    Ok(())}#[tokio::test]async fn load_config_normalizes_agent_role_nickname_candidates() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        agents: Some(AgentsToml {            max_threads: None,            max_depth: None,            job_max_runtime_seconds: None,            interrupt_message: None,            roles: BTreeMap::from([(                "researcher".to_string(),                AgentRoleToml {                    description: Some("Research role".to_string()),                    config_file: None,                    nickname_candidates: Some(vec![                        "  Hypatia  ".to_string(),                        "Noether".to_string(),                    ]),                },            )]),        }),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config            .agent_roles            .get("researcher")            .and_then(|role| role.nickname_candidates.as_ref())            .map(|candidates| candidates.iter().map(String::as_str).collect::<Vec<_>>()),        Some(vec!["Hypatia", "Noether"])    );    Ok(())}#[tokio::test]async fn load_config_rejects_empty_agent_role_nickname_candidates() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        agents: Some(AgentsToml {            max_threads: None,            max_depth: None,            job_max_runtime_seconds: None,            interrupt_message: None,            roles: BTreeMap::from([(                "researcher".to_string(),                AgentRoleToml {                    description: Some("Research role".to_string()),                    config_file: None,                    nickname_candidates: Some(Vec::new()),                },            )]),        }),        ..Default::default()    };    let result = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await;    let err = result.expect_err("empty nickname candidates should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert!(        err.to_string()            .contains("agents.researcher.nickname_candidates")    );    Ok(())}#[tokio::test]async fn load_config_rejects_duplicate_agent_role_nickname_candidates() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        agents: Some(AgentsToml {            max_threads: None,            max_depth: None,            job_max_runtime_seconds: None,            interrupt_message: None,            roles: BTreeMap::from([(                "researcher".to_string(),                AgentRoleToml {                    description: Some("Research role".to_string()),                    config_file: None,                    nickname_candidates: Some(vec!["Hypatia".to_string(), " Hypatia ".to_string()]),                },            )]),        }),        ..Default::default()    };    let result = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await;    let err = result.expect_err("duplicate nickname candidates should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert!(        err.to_string()            .contains("agents.researcher.nickname_candidates cannot contain duplicates")    );    Ok(())}#[tokio::test]async fn load_config_rejects_unsafe_agent_role_nickname_candidates() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        agents: Some(AgentsToml {            max_threads: None,            max_depth: None,            job_max_runtime_seconds: None,            interrupt_message: None,            roles: BTreeMap::from([(                "researcher".to_string(),                AgentRoleToml {                    description: Some("Research role".to_string()),                    config_file: None,                    nickname_candidates: Some(vec!["Agent <One>".to_string()]),                },            )]),        }),        ..Default::default()    };    let result = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await;    let err = result.expect_err("unsafe nickname candidates should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert!(err.to_string().contains(            "agents.researcher.nickname_candidates may only contain ASCII letters, digits, spaces, hyphens, and underscores"        ));    Ok(())}#[tokio::test]async fn model_catalog_json_loads_from_path() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let catalog_path = codex_home.path().join("catalog.json");    let mut catalog = bundled_models_response()        .unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));    catalog.models = catalog.models.into_iter().take(1).collect();    std::fs::write(        &catalog_path,        serde_json::to_string(&catalog).expect("serialize catalog"),    )?;    let cfg = ConfigToml {        model_catalog_json: Some(catalog_path.abs()),        ..Default::default()    };    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(config.model_catalog, Some(catalog));    Ok(())}#[tokio::test]async fn model_catalog_json_rejects_empty_catalog() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let catalog_path = codex_home.path().join("catalog.json");    std::fs::write(&catalog_path, r#"{"models":[]}"#)?;    let cfg = ConfigToml {        model_catalog_json: Some(catalog_path.abs()),        ..Default::default()    };    let err = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await    .expect_err("empty custom catalog should fail config load");    assert_eq!(err.kind(), ErrorKind::InvalidData);    assert!(        err.to_string().contains("must contain at least one model"),        "unexpected error: {err}"    );    Ok(())}fn create_test_fixture() -> std::io::Result<PrecedenceTestFixture> {    let toml = r#"model = "o3"approval_policy = "untrusted"[analytics]enabled = true[model_providers.openai-custom]name = "OpenAI custom"base_url = "https://api.openai.com/v1"env_key = "OPENAI_API_KEY"wire_api = "responses"request_max_retries = 4            # retry failed HTTP requestsstream_max_retries = 10            # retry dropped SSE streamsstream_idle_timeout_ms = 300000    # 5m idle timeoutwebsocket_connect_timeout_ms = 15000[profiles.o3]model = "o3"model_provider = "openai"approval_policy = "never"model_reasoning_effort = "high"model_reasoning_summary = "detailed"[profiles.gpt3]model = "gpt-3.5-turbo"model_provider = "openai-custom"[profiles.zdr]model = "o3"model_provider = "openai"approval_policy = "on-failure"[profiles.zdr.analytics]enabled = false[profiles.gpt5]model = "gpt-5.4"model_provider = "openai"approval_policy = "on-failure"model_reasoning_effort = "high"model_reasoning_summary = "detailed"model_verbosity = "high""#;    let cfg: ConfigToml = toml::from_str(toml).expect("TOML deserialization should succeed");    // Use a temporary directory for the cwd so it does not contain an    // AGENTS.md file.    let cwd_temp_dir = TempDir::new().unwrap();    let cwd = cwd_temp_dir.path().to_path_buf();    // Make it look like a Git repo so it does not search for AGENTS.md in    // a parent folder, either.    std::fs::write(cwd.join(".git"), "gitdir: nowhere")?;    let codex_home_temp_dir = TempDir::new().unwrap();    Ok(PrecedenceTestFixture {        cwd: cwd_temp_dir,        codex_home: codex_home_temp_dir,        cfg,    })}#[tokio::test]async fn legacy_profile_selection_is_rejected() -> std::io::Result<()> {    let mut fixture = create_test_fixture()?;    fixture.cfg.profile = Some("gpt3".to_string());    let err = Config::load_from_base_config_with_overrides(        fixture.cfg.clone(),        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            ..Default::default()        },        fixture.codex_home(),    )    .await    .expect_err("legacy profile selection should be rejected");    assert_eq!(err.kind(), ErrorKind::InvalidData);    assert!(        err.to_string()            .contains("legacy `profile = \"gpt3\"` config is no longer supported"),        "unexpected error: {err}"    );    Ok(())}#[tokio::test]async fn metrics_exporter_defaults_to_statsig_when_missing() -> std::io::Result<()> {    let fixture = create_test_fixture()?;    let config = Config::load_from_base_config_with_overrides(        fixture.cfg.clone(),        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            ..Default::default()        },        fixture.codex_home(),    )    .await?;    assert_eq!(config.otel.metrics_exporter, OtelExporterKind::Statsig);    Ok(())}#[tokio::test]async fn trace_exporter_defaults_to_none_when_log_exporter_is_set() -> std::io::Result<()> {    let fixture = create_test_fixture()?;    let mut cfg = fixture.cfg.clone();    cfg.otel = Some(OtelConfigToml {        exporter: Some(OtelExporterKind::OtlpHttp {            endpoint: "http://localhost:14318/v1/logs".to_string(),            headers: HashMap::new(),            protocol: codex_config::types::OtelHttpProtocol::Binary,            tls: None,        }),        metrics_exporter: Some(OtelExporterKind::None),        ..Default::default()    });    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            ..Default::default()        },        fixture.codex_home(),    )    .await?;    assert!(matches!(        config.otel.exporter,        OtelExporterKind::OtlpHttp { .. }    ));    assert_eq!(config.otel.trace_exporter, OtelExporterKind::None);    Ok(())}#[tokio::test]async fn load_config_applies_otel_trace_metadata() -> std::io::Result<()> {    let mut fixture = create_test_fixture()?;    fixture.cfg = toml::from_str(        r#"[otel.span_attributes]"example.trace_attr" = "enabled"[otel.tracestate.example]alpha = "one"beta = "two""#,    )    .expect("TOML deserialization should succeed");    let config = Config::load_from_base_config_with_overrides(        fixture.cfg.clone(),        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            ..Default::default()        },        fixture.codex_home(),    )    .await?;    assert_eq!(        config.otel.span_attributes,        BTreeMap::from([("example.trace_attr".to_string(), "enabled".to_string())])    );    assert_eq!(        config.otel.tracestate,        BTreeMap::from([(            "example".to_string(),            BTreeMap::from([                ("alpha".to_string(), "one".to_string()),                ("beta".to_string(), "two".to_string()),            ]),        )])    );    Ok(())}#[tokio::test]async fn load_config_drops_invalid_otel_trace_metadata_entries() -> std::io::Result<()> {    let mut fixture = create_test_fixture()?;    fixture.cfg = toml::from_str(        r#"[otel]environment = "test"[otel.span_attributes]"" = "missing-key""example.trace_attr" = "enabled"[otel.tracestate.example]alpha = "one"beta = "two\ntoo"[otel.tracestate.bad]alpha = "one\ntwo""#,    )    .expect("TOML deserialization should succeed");    let config = Config::load_from_base_config_with_overrides(        fixture.cfg.clone(),        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            ..Default::default()        },        fixture.codex_home(),    )    .await?;    assert_eq!(config.otel.environment, "test");    assert_eq!(        config.otel.span_attributes,        BTreeMap::from([("example.trace_attr".to_string(), "enabled".to_string())])    );    assert_eq!(        config.otel.tracestate,        BTreeMap::from([(            "example".to_string(),            BTreeMap::from([("alpha".to_string(), "one".to_string())]),        )])    );    assert!(        config.startup_warnings.iter().any(|warning| {            warning.contains("Ignoring invalid `otel.span_attributes` config")                && warning.contains("configured span attribute key must not be empty")        }),        "{:?}",        config.startup_warnings    );    assert!(        config.startup_warnings.iter().any(|warning| {            warning.contains("Ignoring invalid `otel.tracestate` config")                && warning.contains("invalid configured tracestate value for example.beta")        }),        "{:?}",        config.startup_warnings    );    assert!(        config.startup_warnings.iter().any(|warning| {            warning.contains("Ignoring invalid `otel.tracestate` config")                && warning.contains("invalid configured tracestate value for bad.alpha")        }),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn explicit_null_service_tier_override_maps_to_default_service_tier() -> std::io::Result<()> {    let fixture = create_test_fixture()?;    let config = Config::load_from_base_config_with_overrides(        fixture.cfg.clone(),        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            service_tier: Some(None),            ..Default::default()        },        fixture.codex_home(),    )    .await?;    assert_eq!(        config.service_tier,        Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())    );    assert_eq!(config.notices.fast_default_opt_out, None);    Ok(())}#[tokio::test]async fn default_service_tier_override_uses_default_request_value() -> std::io::Result<()> {    let fixture = create_test_fixture()?;    let config = Config::load_from_base_config_with_overrides(        fixture.cfg.clone(),        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            service_tier: Some(Some("default".to_string())),            ..Default::default()        },        fixture.codex_home(),    )    .await?;    assert_eq!(        config.service_tier,        Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string())    );    Ok(())}#[tokio::test]async fn legacy_fast_service_tier_override_uses_priority_request_value() -> std::io::Result<()> {    let fixture = create_test_fixture()?;    let config = Config::load_from_base_config_with_overrides(        fixture.cfg.clone(),        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            service_tier: Some(Some("fast".to_string())),            ..Default::default()        },        fixture.codex_home(),    )    .await?;    assert_eq!(        config.service_tier,        Some(ServiceTier::Fast.request_value().to_string())    );    Ok(())}#[tokio::test]async fn config_toml_priority_service_tier_uses_priority_request_value() -> std::io::Result<()> {    let mut fixture = create_test_fixture()?;    fixture.cfg.service_tier = Some(ServiceTier::Fast.request_value().to_string());    let cwd = fixture.cwd_path();    let codex_home = fixture.codex_home();    let config = Config::load_from_base_config_with_overrides(        fixture.cfg,        ConfigOverrides {            cwd: Some(cwd),            ..Default::default()        },        codex_home,    )    .await?;    assert_eq!(        config.service_tier,        Some(ServiceTier::Fast.request_value().to_string())    );    Ok(())}#[tokio::test]async fn config_toml_service_tier_accepts_arbitrary_string() -> std::io::Result<()> {    let mut fixture = create_test_fixture()?;    fixture.cfg.service_tier = Some("experimental-tier-id".to_string());    let cwd = fixture.cwd_path();    let codex_home = fixture.codex_home();    let config = Config::load_from_base_config_with_overrides(        fixture.cfg,        ConfigOverrides {            cwd: Some(cwd),            ..Default::default()        },        codex_home,    )    .await?;    assert_eq!(        config.service_tier,        Some("experimental-tier-id".to_string())    );    Ok(())}#[tokio::test]async fn config_toml_legacy_fast_service_tier_uses_priority_request_value() -> std::io::Result<()> {    let mut fixture = create_test_fixture()?;    fixture.cfg.service_tier = Some("fast".to_string());    let cwd = fixture.cwd_path();    let codex_home = fixture.codex_home();    let config = Config::load_from_base_config_with_overrides(        fixture.cfg,        ConfigOverrides {            cwd: Some(cwd),            ..Default::default()        },        codex_home,    )    .await?;    assert_eq!(        config.service_tier,        Some(ServiceTier::Fast.request_value().to_string())    );    Ok(())}#[tokio::test]async fn fast_default_opt_out_notice_config_is_respected() -> std::io::Result<()> {    let fixture = create_test_fixture()?;    let mut cfg = fixture.cfg.clone();    cfg.notice = Some(Notice {        fast_default_opt_out: Some(true),        ..Default::default()    });    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            ..Default::default()        },        fixture.codex_home(),    )    .await?;    assert_eq!(config.service_tier, None);    assert_eq!(config.notices.fast_default_opt_out, Some(true));    Ok(())}#[tokio::test]async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset() -> anyhow::Result<()>{    let fixture = create_test_fixture()?;    let requirements_toml = codex_config::ConfigRequirementsToml {        allowed_approval_policies: None,        allowed_approvals_reviewers: None,        allowed_sandbox_modes: None,        allowed_permission_profiles: None,        default_permissions: None,        remote_sandbox_config: None,        allowed_web_search_modes: Some(vec![codex_config::WebSearchModeRequirement::Cached]),        allow_managed_hooks_only: None,        allow_appshots: None,        allow_remote_control: None,        computer_use: None,        windows: None,        feature_requirements: None,        hooks: None,        mcp_servers: None,        plugins: None,        apps: None,        rules: None,        enforce_residency: None,        network: None,        permissions: None,        guardian_policy_config: None,    };    let requirement_source = codex_config::RequirementSource::Unknown;    let requirement_source_for_error = requirement_source.clone();    let allowed = vec![WebSearchMode::Disabled, WebSearchMode::Cached];    let constrained = Constrained::new(WebSearchMode::Cached, move |candidate| {        if matches!(candidate, WebSearchMode::Cached | WebSearchMode::Disabled) {            Ok(())        } else {            Err(ConstraintError::InvalidValue {                field_name: "web_search_mode",                candidate: format!("{candidate:?}"),                allowed: format!("{allowed:?}"),                requirement_source: requirement_source_for_error.clone(),            })        }    })?;    let requirements = codex_config::ConfigRequirements {        web_search_mode: codex_config::ConstrainedWithSource::new(            constrained,            Some(requirement_source),        ),        ..Default::default()    };    let config_layer_stack =        codex_config::ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)            .expect("config layer stack");    let config = Config::load_config_with_layer_stack(        LOCAL_FS.as_ref(),        fixture.cfg.clone(),        ConfigOverrides {            cwd: Some(fixture.cwd_path()),            ..Default::default()        },        fixture.codex_home(),        config_layer_stack,    )    .await?;    assert!(        !config            .startup_warnings            .iter()            .any(|warning| warning.contains("Configured value for `web_search_mode`")),        "{:?}",        config.startup_warnings    );    Ok(())}#[test]fn test_set_project_trusted_writes_explicit_tables() -> anyhow::Result<()> {    let project_dir = Path::new("/some/path");    let mut doc = DocumentMut::new();    set_project_trust_level_inner(&mut doc, project_dir, TrustLevel::Trusted)?;    let contents = doc.to_string();    let raw_path = project_dir.to_string_lossy();    let path_str = if raw_path.contains('\\') {        format!("'{raw_path}'")    } else {        format!("\"{raw_path}\"")    };    let expected = format!(        r#"[projects.{path_str}]trust_level = "trusted""#    );    assert_eq!(contents, expected);    Ok(())}#[test]fn test_set_project_trusted_converts_inline_to_explicit() -> anyhow::Result<()> {    let project_dir = Path::new("/some/path");    // Seed config.toml with an inline project entry under [projects]    let raw_path = project_dir.to_string_lossy();    let path_str = if raw_path.contains('\\') {        format!("'{raw_path}'")    } else {        format!("\"{raw_path}\"")    };    // Use a quoted key so backslashes don't require escaping on Windows    let initial = format!(        r#"[projects]{path_str} = {{ trust_level = "untrusted" }}"#    );    let mut doc = initial.parse::<DocumentMut>()?;    // Run the function; it should convert to explicit tables and set trusted    set_project_trust_level_inner(&mut doc, project_dir, TrustLevel::Trusted)?;    let contents = doc.to_string();    // Assert exact output after conversion to explicit table    let expected = format!(        r#"[projects][projects.{path_str}]trust_level = "trusted""#    );    assert_eq!(contents, expected);    Ok(())}#[test]fn test_set_project_trusted_migrates_top_level_inline_projects_preserving_entries()-> anyhow::Result<()> {    let initial = r#"toplevel = "baz"projects = { "/Users/mbolin/code/codex4" = { trust_level = "trusted", foo = "bar" } , "/Users/mbolin/code/codex3" = { trust_level = "trusted" } }model = "foo""#;    let mut doc = initial.parse::<DocumentMut>()?;    // Approve a new directory    let new_project = Path::new("/Users/mbolin/code/codex2");    set_project_trust_level_inner(&mut doc, new_project, TrustLevel::Trusted)?;    let contents = doc.to_string();    // Since we created the [projects] table as part of migration, it is kept implicit.    // Expect explicit per-project tables, preserving prior entries and appending the new one.    let new_project_key = project_trust_key(new_project);    let expected = format!(        r#"toplevel = "baz"model = "foo"[projects."/Users/mbolin/code/codex4"]trust_level = "trusted"foo = "bar"[projects."/Users/mbolin/code/codex3"]trust_level = "trusted"[projects."{new_project_key}"]trust_level = "trusted""#    );    assert_eq!(contents, expected);    Ok(())}#[cfg(unix)]#[tokio::test]async fn active_project_does_not_match_configured_alias_for_canonical_cwd() -> anyhow::Result<()> {    let tmp = tempdir()?;    let project_root = tmp.path().join("project");    let alias_root = tmp.path().join("project_alias");    std::fs::create_dir_all(&project_root)?;    std::os::unix::fs::symlink(&project_root, &alias_root)?;    let config = ConfigToml {        projects: Some(HashMap::from([(            alias_root.to_string_lossy().to_string(),            ProjectConfig {                trust_level: Some(TrustLevel::Trusted),            },        )])),        ..Default::default()    };    assert_eq!(        config.get_active_project(&project_root, /*repo_root*/ None),        None    );    Ok(())}#[test]fn test_set_default_oss_provider() -> std::io::Result<()> {    let temp_dir = TempDir::new()?;    let codex_home = temp_dir.path();    let config_path = codex_home.join(CONFIG_TOML_FILE);    // Test setting valid provider on empty config    set_default_oss_provider(codex_home, OLLAMA_OSS_PROVIDER_ID)?;    let content = std::fs::read_to_string(&config_path)?;    assert!(content.contains("oss_provider = \"ollama\""));    // Test updating existing config    std::fs::write(&config_path, "model = \"gpt-4\"\n")?;    set_default_oss_provider(codex_home, LMSTUDIO_OSS_PROVIDER_ID)?;    let content = std::fs::read_to_string(&config_path)?;    assert!(content.contains("oss_provider = \"lmstudio\""));    assert!(content.contains("model = \"gpt-4\""));    // Test overwriting existing oss_provider    set_default_oss_provider(codex_home, OLLAMA_OSS_PROVIDER_ID)?;    let content = std::fs::read_to_string(&config_path)?;    assert!(content.contains("oss_provider = \"ollama\""));    assert!(!content.contains("oss_provider = \"lmstudio\""));    // Test invalid provider    let result = set_default_oss_provider(codex_home, "invalid_provider");    assert!(result.is_err());    let error = result.unwrap_err();    assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);    assert!(error.to_string().contains("Invalid OSS provider"));    assert!(error.to_string().contains("invalid_provider"));    Ok(())}#[test]fn test_set_default_oss_provider_rejects_legacy_ollama_chat_provider() -> std::io::Result<()> {    let temp_dir = TempDir::new()?;    let codex_home = temp_dir.path();    let result = set_default_oss_provider(codex_home, LEGACY_OLLAMA_CHAT_PROVIDER_ID);    assert!(result.is_err());    let error = result.unwrap_err();    assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);    assert!(        error            .to_string()            .contains(OLLAMA_CHAT_PROVIDER_REMOVED_ERROR)    );    Ok(())}#[tokio::test]async fn test_load_config_rejects_legacy_ollama_chat_provider_with_helpful_error()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg = ConfigToml {        model_provider: Some(LEGACY_OLLAMA_CHAT_PROVIDER_ID.to_string()),        ..Default::default()    };    let result = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await;    assert!(result.is_err());    let error = result.unwrap_err();    assert_eq!(error.kind(), std::io::ErrorKind::NotFound);    assert!(        error            .to_string()            .contains(OLLAMA_CHAT_PROVIDER_REMOVED_ERROR)    );    Ok(())}#[tokio::test]async fn test_untrusted_project_gets_workspace_write_sandbox() -> anyhow::Result<()> {    let config_with_untrusted = r#"[projects."/tmp/test"]trust_level = "untrusted""#;    let cfg = toml::from_str::<ConfigToml>(config_with_untrusted)        .expect("TOML deserialization should succeed");    let active_project = ProjectConfig {        trust_level: Some(TrustLevel::Untrusted),    };    let resolution = derive_legacy_sandbox_policy_for_test(        &cfg,        /*sandbox_mode_override*/ None,        WindowsSandboxLevel::Disabled,        Some(&active_project),        /*permission_profile_constraint*/ None,    )    .await;    // Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade)    if cfg!(target_os = "windows") {        assert!(            matches!(resolution, SandboxPolicy::ReadOnly { .. }),            "Expected ReadOnly on Windows, got {resolution:?}"        );    } else {        assert!(            matches!(resolution, SandboxPolicy::WorkspaceWrite { .. }),            "Expected WorkspaceWrite for untrusted project, got {resolution:?}"        );    }    Ok(())}#[tokio::test]async fn derive_sandbox_policy_falls_back_to_read_only_for_implicit_defaults() -> anyhow::Result<()>{    let project_dir = TempDir::new()?;    let project_path = project_dir.path().to_path_buf();    let project_key = project_path.to_string_lossy().to_string();    let cfg = ConfigToml {        projects: Some(HashMap::from([(            project_key,            ProjectConfig {                trust_level: Some(TrustLevel::Trusted),            },        )])),        ..Default::default()    };    let active_project = ProjectConfig {        trust_level: Some(TrustLevel::Trusted),    };    let constrained = Constrained::new(PermissionProfile::read_only(), |candidate| {        if candidate == &PermissionProfile::read_only() {            Ok(())        } else {            Err(ConstraintError::InvalidValue {                field_name: "sandbox_mode",                candidate: format!("{candidate:?}"),                allowed: "[ReadOnly]".to_string(),                requirement_source: RequirementSource::Unknown,            })        }    })?;    let resolution = derive_legacy_sandbox_policy_for_test(        &cfg,        /*sandbox_mode_override*/ None,        WindowsSandboxLevel::Disabled,        Some(&active_project),        Some(&constrained),    )    .await;    assert_eq!(resolution, SandboxPolicy::new_read_only_policy());    Ok(())}#[tokio::test]async fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback()-> anyhow::Result<()> {    let project_dir = TempDir::new()?;    let project_path = project_dir.path().to_path_buf();    let project_key = project_path.to_string_lossy().to_string();    let cfg = ConfigToml {        projects: Some(HashMap::from([(            project_key,            ProjectConfig {                trust_level: Some(TrustLevel::Trusted),            },        )])),        ..Default::default()    };    let active_project = ProjectConfig {        trust_level: Some(TrustLevel::Trusted),    };    let constrained = Constrained::new(PermissionProfile::workspace_write(), |candidate| {        if matches!(            candidate,            PermissionProfile::Managed {                file_system: ManagedFileSystemPermissions::Restricted { entries, .. },                ..            } if entries                    .iter()                    .any(|entry| entry.access.can_write())        ) {            Ok(())        } else {            Err(ConstraintError::InvalidValue {                field_name: "sandbox_mode",                candidate: format!("{candidate:?}"),                allowed: "[WorkspaceWrite]".to_string(),                requirement_source: RequirementSource::Unknown,            })        }    })?;    let resolution = derive_legacy_sandbox_policy_for_test(        &cfg,        /*sandbox_mode_override*/ None,        WindowsSandboxLevel::Disabled,        Some(&active_project),        Some(&constrained),    )    .await;    if cfg!(target_os = "windows") {        assert_eq!(resolution, SandboxPolicy::new_read_only_policy());    } else {        assert_eq!(resolution, SandboxPolicy::new_workspace_write_policy());    }    Ok(())}#[test]fn test_resolve_oss_provider_explicit_override() {    let config_toml = ConfigToml::default();    let result = resolve_oss_provider(Some("custom-provider"), &config_toml);    assert_eq!(result, Some("custom-provider".to_string()));}#[test]fn test_resolve_oss_provider_from_global_config() {    let config_toml = ConfigToml {        oss_provider: Some("global-provider".to_string()),        ..Default::default()    };    let result = resolve_oss_provider(/*explicit_provider*/ None, &config_toml);    assert_eq!(result, Some("global-provider".to_string()));}#[test]fn test_resolve_oss_provider_none_when_not_configured() {    let config_toml = ConfigToml::default();    let result = resolve_oss_provider(/*explicit_provider*/ None, &config_toml);    assert_eq!(result, None);}#[test]fn test_resolve_oss_provider_explicit_overrides_global() {    let config_toml = ConfigToml {        oss_provider: Some("global-provider".to_string()),        ..Default::default()    };    let result = resolve_oss_provider(Some("explicit-provider"), &config_toml);    assert_eq!(result, Some("explicit-provider".to_string()));}#[test]fn config_toml_deserializes_mcp_oauth_callback_port() {    let toml = r#"mcp_oauth_callback_port = 4321"#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for callback port");    assert_eq!(cfg.mcp_oauth_callback_port, Some(4321));}#[test]fn config_toml_deserializes_mcp_oauth_callback_url() {    let toml = r#"mcp_oauth_callback_url = "https://example.com/callback""#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for callback URL");    assert_eq!(        cfg.mcp_oauth_callback_url.as_deref(),        Some("https://example.com/callback")    );}#[tokio::test]async fn config_loads_mcp_oauth_callback_port_from_toml() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let toml = r#"model = "gpt-5.4"mcp_oauth_callback_port = 5678"#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for callback port");    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(config.mcp_oauth_callback_port, Some(5678));    Ok(())}#[tokio::test]async fn config_loads_allow_login_shell_from_toml() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cfg: ConfigToml = toml::from_str(        r#"model = "gpt-5.4"allow_login_shell = false"#,    )    .expect("TOML deserialization should succeed for allow_login_shell");    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert!(!config.permissions.allow_login_shell);    Ok(())}#[tokio::test]async fn config_loads_apps_mcp_product_sku_from_toml() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let toml = r#"model = "gpt-5.4"apps_mcp_product_sku = "tpp""#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for apps MCP SKU");    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(config.apps_mcp_product_sku.as_deref(), Some("tpp"));    Ok(())}#[tokio::test]async fn config_loads_mcp_oauth_callback_url_from_toml() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let toml = r#"model = "gpt-5.4"mcp_oauth_callback_url = "https://example.com/callback""#;    let cfg: ConfigToml =        toml::from_str(toml).expect("TOML deserialization should succeed for callback URL");    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.mcp_oauth_callback_url.as_deref(),        Some("https://example.com/callback")    );    Ok(())}#[tokio::test]async fn test_untrusted_project_gets_unless_trusted_approval_policy() -> anyhow::Result<()> {    let codex_home = TempDir::new()?;    let test_project_dir = TempDir::new()?;    let test_path = test_project_dir.path();    let config = Config::load_from_base_config_with_overrides(        ConfigToml {            projects: Some(HashMap::from([(                test_path.to_string_lossy().to_string(),                ProjectConfig {                    trust_level: Some(TrustLevel::Untrusted),                },            )])),            ..Default::default()        },        ConfigOverrides {            cwd: Some(test_path.to_path_buf()),            ..Default::default()        },        codex_home.abs(),    )    .await?;    // Verify that untrusted projects get UnlessTrusted approval policy    assert_eq!(        config.permissions.approval_policy.value(),        AskForApproval::UnlessTrusted,        "Expected UnlessTrusted approval policy for untrusted project"    );    // Verify that untrusted projects still get WorkspaceWrite sandbox (or ReadOnly on Windows)    if cfg!(target_os = "windows") {        assert!(            matches!(                &config.legacy_sandbox_policy(),                SandboxPolicy::ReadOnly { .. }            ),            "Expected ReadOnly on Windows"        );    } else {        assert!(            matches!(                &config.legacy_sandbox_policy(),                SandboxPolicy::WorkspaceWrite { .. }            ),            "Expected WorkspaceWrite sandbox for untrusted project"        );    }    Ok(())}#[tokio::test]async fn requirements_disallowing_default_sandbox_falls_back_to_required_default()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_sandbox_modes = ["read-only"]"#,            ),        )        .build()        .await?;    assert_eq!(        config.legacy_sandbox_policy(),        SandboxPolicy::new_read_only_policy()    );    Ok(())}#[tokio::test]async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"sandbox_mode = "danger-full-access""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_sandbox_modes = ["read-only"]"#,            ),        )        .build()        .await?;    assert_eq!(        config.legacy_sandbox_policy(),        SandboxPolicy::new_read_only_policy()    );    Ok(())}#[tokio::test]async fn windows_sandbox_mode_falls_back_when_disallowed_by_requirements() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[windows]sandbox = "unelevated""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[windows]allowed_sandbox_implementations = ["elevated"]"#,            ),        )        .build()        .await?;    assert_eq!(        config.permissions.windows_sandbox_mode,        Some(codex_config::types::WindowsSandboxModeToml::Elevated)    );    assert!(        config.startup_warnings.iter().any(|warning| warning            .contains("Configured value for `windows.sandbox` is disallowed by requirements")),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn danger_full_access_with_never_is_rejected_when_requirements_force_read_only()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"approval_policy = "never"sandbox_mode = "danger-full-access""#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_sandbox_modes = ["read-only"]"#,            ),        )        .build()        .await        .expect_err("requirements-constrained yolo should require sandbox approval");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Choose an `approval_policy` based on what you need, such as `on-request`, or choose an allowed sandbox mode."    );    Ok(())}#[tokio::test]async fn named_full_access_profile_with_never_is_rejected_when_requirements_force_read_only()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"approval_policy = "never"default_permissions = "dev"[permissions.dev.filesystem]":root" = "write""#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_sandbox_modes = ["read-only"]"#,            ),        )        .build()        .await        .expect_err("requirements-constrained full-access profile should require sandbox approval");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "`approval_policy = \"never\"` cannot be used because requirements do not allow `sandbox_mode = \"danger-full-access\"`; Codex would fall back to read-only permissions with approvals disabled. Choose an `approval_policy` based on what you need, such as `on-request`, or choose an allowed sandbox mode."    );    Ok(())}#[tokio::test]async fn permission_profile_override_falls_back_when_disallowed_by_requirements()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .harness_overrides(ConfigOverrides {            permission_profile: Some(PermissionProfile::Disabled),            ..Default::default()        })        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_sandbox_modes = ["read-only"]"#,            ),        )        .build()        .await?;    let expected_sandbox_policy = SandboxPolicy::new_read_only_policy();    assert_eq!(config.legacy_sandbox_policy(), expected_sandbox_policy);    assert_eq!(        config.permissions.effective_permission_profile(),        PermissionProfile::read_only()    );    Ok(())}#[tokio::test]async fn active_profile_is_cleared_when_requirements_force_fallback() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .harness_overrides(ConfigOverrides {            default_permissions: Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS.to_string()),            ..Default::default()        })        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_sandbox_modes = ["read-only"]"#,            ),        )        .build()        .await?;    assert_eq!(        config.permissions.effective_permission_profile(),        PermissionProfile::read_only()    );    assert_eq!(config.permissions.active_permission_profile(), None);    assert!(        config.startup_warnings.iter().any(|warning| warning            .contains("Configured value for `permission_profile` is disallowed by requirements")),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn bypass_hook_trust_adds_startup_warning() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .harness_overrides(ConfigOverrides {            bypass_hook_trust: Some(true),            ..Default::default()        })        .build()        .await?;    assert!(        config.startup_warnings.iter().any(|warning| warning            == "`--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without review for this invocation."),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn permission_profile_override_preserves_split_write_roots() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let cwd = codex_home.path().join("workspace");    let outside_root = codex_home.path().join("outside-write");    std::fs::create_dir_all(&cwd)?;    std::fs::create_dir_all(&outside_root)?;    let outside_root =        AbsolutePathBuf::from_absolute_path(outside_root).expect("outside root is absolute");    let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![        FileSystemSandboxEntry {            path: FileSystemPath::Special {                value: FileSystemSpecialPath::Root,            },            access: FileSystemAccessMode::Read,        },        FileSystemSandboxEntry {            path: FileSystemPath::Path {                path: outside_root.clone(),            },            access: FileSystemAccessMode::Write,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(        SandboxEnforcement::Managed,        &file_system_sandbox_policy,        NetworkSandboxPolicy::Restricted,    );    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(cwd))        .harness_overrides(ConfigOverrides {            permission_profile: Some(permission_profile),            ..Default::default()        })        .build()        .await?;    assert!(        config            .permissions            .file_system_sandbox_policy()            .can_write_path_with_cwd(outside_root.as_path(), config.cwd.as_path())    );    assert!(matches!(        &config.legacy_sandbox_policy(),        SandboxPolicy::WorkspaceWrite { .. }    ));    assert_eq!(        config.permissions.network_sandbox_policy(),        NetworkSandboxPolicy::Restricted    );    Ok(())}#[tokio::test]async fn requirements_web_search_mode_overrides_danger_full_access_default() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"sandbox_mode = "danger-full-access""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_web_search_modes = ["cached"]"#,            ),        )        .build()        .await?;    assert_eq!(config.web_search_mode.value(), WebSearchMode::Cached);    assert_eq!(        resolve_web_search_mode_for_turn(            &config.web_search_mode,            &config.permissions.effective_permission_profile(),        ),        WebSearchMode::Cached,    );    Ok(())}#[tokio::test]async fn requirements_disallowing_default_approval_falls_back_to_required_default()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let workspace = TempDir::new()?;    let workspace_key = workspace.path().to_string_lossy().replace('\\', "\\\\");    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[projects."{workspace_key}"]trust_level = "untrusted""#        ),    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(workspace.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_approval_policies = ["on-request"]"#,            ),        )        .build()        .await?;    assert_eq!(        config.permissions.approval_policy.value(),        AskForApproval::OnRequest    );    Ok(())}#[tokio::test]async fn explicit_approval_policy_falls_back_when_disallowed_by_requirements() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"approval_policy = "untrusted""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_approval_policies = ["on-request"]"#,            ),        )        .build()        .await?;    assert_eq!(        config.permissions.approval_policy.value(),        AskForApproval::OnRequest    );    Ok(())}#[tokio::test]async fn feature_requirements_normalize_effective_feature_values() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[features]personality = trueshell_tool = false"#,            ),        )        .build()        .await?;    assert!(config.features.enabled(Feature::Personality));    assert!(!config.features.enabled(Feature::ShellTool));    assert!(        !config            .startup_warnings            .iter()            .any(|warning| warning.contains("Configured value for `features`")),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn feature_requirements_auto_review_disables_guardian_approval() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[features]auto_review = false"#,            ),        )        .build()        .await?;    assert!(!config.features.enabled(Feature::GuardianApproval));    Ok(())}#[tokio::test]async fn browser_feature_requirements_are_valid() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[features]in_app_browser = falsebrowser_use = false"#,            ),        )        .build()        .await?;    assert!(!config.features.enabled(Feature::InAppBrowser));    assert!(!config.features.enabled(Feature::BrowserUse));    Ok(())}#[tokio::test]async fn debug_config_lockfile_export_settings_load_from_nested_table() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[debug.config_lockfile]export_dir = "locks"allow_codex_version_mismatch = truesave_fields_resolved_from_model_catalog = false"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(        config.config_lock_export_dir,        Some(AbsolutePathBuf::resolve_path_against_base(            "locks",            codex_home.path()        ))    );    assert!(config.config_lock_allow_codex_version_mismatch);    assert!(!config.config_lock_save_fields_resolved_from_model_catalog);    Ok(())}#[tokio::test]async fn debug_config_lockfile_load_path_loads_lock_from_nested_table() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let lock_path = codex_home.path().join("session.config.lock.toml");    std::fs::write(        &lock_path,        format!(            r#"version = {}codex_version = "older-version"[config]"#,            crate::config_lock::CONFIG_LOCK_VERSION        ),    )?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[debug.config_lockfile]load_path = '{}'allow_codex_version_mismatch = truesave_fields_resolved_from_model_catalog = false"#,            lock_path.display()        ),    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert!(config.config_lock_toml.is_some());    assert!(config.config_lock_allow_codex_version_mismatch);    assert!(!config.config_lock_save_fields_resolved_from_model_catalog);    Ok(())}#[tokio::test]async fn explicit_feature_config_is_normalized_by_requirements() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features]personality = falseshell_tool = true"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[features]personality = trueshell_tool = false"#,            ),        )        .build()        .await?;    assert!(config.features.enabled(Feature::Personality));    assert!(!config.features.enabled(Feature::ShellTool));    assert!(        !config            .startup_warnings            .iter()            .any(|warning| warning.contains("Configured value for `features`")),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn approvals_reviewer_defaults_to_manual_only_without_guardian_feature() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(config.approvals_reviewer, ApprovalsReviewer::User);    Ok(())}#[tokio::test]async fn prompt_instruction_blocks_can_be_disabled_from_config() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"include_permissions_instructions = falseinclude_apps_instructions = falseinclude_collaboration_mode_instructions = falseinclude_environment_context = false[skills]include_instructions = false"#,    )?;    let config = ConfigBuilder::default()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert!(!config.include_permissions_instructions);    assert!(!config.include_apps_instructions);    assert!(!config.include_collaboration_mode_instructions);    assert!(!config.include_skill_instructions);    assert!(!config.include_environment_context);    Ok(())}#[tokio::test]async fn approvals_reviewer_stays_manual_only_when_guardian_feature_is_enabled()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features]guardian_approval = true"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(config.approvals_reviewer, ApprovalsReviewer::User);    Ok(())}#[tokio::test]async fn approvals_reviewer_can_be_set_in_config_without_guardian_approval() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"approvals_reviewer = "user""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(config.approvals_reviewer, ApprovalsReviewer::User);    Ok(())}#[tokio::test]async fn requirements_disallowing_default_approvals_reviewer_falls_back_to_required_default()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_approvals_reviewers = ["guardian_subagent"]"#,            ),        )        .build()        .await?;    assert_eq!(config.approvals_reviewer, ApprovalsReviewer::AutoReview);    Ok(())}#[tokio::test]async fn root_approvals_reviewer_falls_back_when_disallowed_by_requirements() -> std::io::Result<()>{    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"approvals_reviewer = "user""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_approvals_reviewers = ["guardian_subagent"]"#,            ),        )        .build()        .await?;    assert_eq!(config.approvals_reviewer, ApprovalsReviewer::AutoReview);    assert!(        config.startup_warnings.iter().any(|warning| {            warning                .contains("Configured value for `approvals_reviewer` is disallowed by requirements")        }),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn profile_approvals_reviewer_falls_back_when_disallowed_by_requirements()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    let selected_config = codex_home.path().join("default.config.toml");    std::fs::write(        &selected_config,        r#"approvals_reviewer = "user""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .loader_overrides(LoaderOverrides {            user_config_path: Some(selected_config.abs()),            user_config_profile: Some("default".parse().expect("profile-v2 name")),            ..LoaderOverrides::without_managed_config_for_tests()        })        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_approvals_reviewers = ["guardian_subagent"]"#,            ),        )        .build()        .await?;    assert_eq!(config.approvals_reviewer, ApprovalsReviewer::AutoReview);    Ok(())}#[tokio::test]async fn approvals_reviewer_preserves_valid_user_choice_when_allowed_by_requirements()-> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"approvals_reviewer = "guardian_subagent""#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"allowed_approvals_reviewers = ["user", "guardian_subagent"]"#,            ),        )        .build()        .await?;    assert_eq!(config.approvals_reviewer, ApprovalsReviewer::AutoReview);    assert!(        config            .startup_warnings            .iter()            .all(|warning| !warning.contains("approvals_reviewer")),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn smart_approvals_alias_is_ignored() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features]smart_approvals = true"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert!(config.features.enabled(Feature::GuardianApproval));    assert_eq!(config.approvals_reviewer, ApprovalsReviewer::User);    let serialized = tokio::fs::read_to_string(codex_home.path().join(CONFIG_TOML_FILE)).await?;    assert!(serialized.contains("smart_approvals = true"));    assert!(!serialized.contains("guardian_approval"));    assert!(!serialized.contains("approvals_reviewer"));    Ok(())}#[tokio::test]async fn multi_agent_v2_config_from_feature_table() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemax_concurrent_threads_per_session = 5min_wait_timeout_ms = 2500max_wait_timeout_ms = 120000default_wait_timeout_ms = 30000usage_hint_enabled = falseusage_hint_text = "Custom delegation guidance."root_agent_usage_hint_text = "Root guidance."subagent_usage_hint_text = "Subagent guidance."tool_namespace = "agents"hide_spawn_agent_metadata = truenon_code_mode_only = true"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert!(config.features.enabled(Feature::MultiAgentV2));    assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 5);    assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 2500);    assert_eq!(config.multi_agent_v2.max_wait_timeout_ms, 120000);    assert_eq!(config.multi_agent_v2.default_wait_timeout_ms, 30000);    assert_eq!(        (            config.agent_max_threads,            config.effective_agent_max_threads(MultiAgentVersion::V2)        ),        (None, Some(4))    );    assert!(!config.multi_agent_v2.usage_hint_enabled);    assert_eq!(        config.multi_agent_v2.usage_hint_text.as_deref(),        Some("Custom delegation guidance.")    );    assert_eq!(        config.multi_agent_v2.root_agent_usage_hint_text.as_deref(),        Some("Root guidance.")    );    assert_eq!(        config.multi_agent_v2.subagent_usage_hint_text.as_deref(),        Some("Subagent guidance.")    );    assert_eq!(        config.multi_agent_v2.tool_namespace.as_deref(),        Some("agents")    );    assert!(config.multi_agent_v2.hide_spawn_agent_metadata);    assert!(config.multi_agent_v2.non_code_mode_only);    Ok(())}#[tokio::test]async fn multi_agent_v2_default_session_thread_cap_counts_root() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = true"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(config.multi_agent_v2, MultiAgentV2Config::default());    assert_eq!(        (            config.agent_max_threads,            config.effective_agent_max_threads(MultiAgentVersion::V2)        ),        (None, Some(3))    );    Ok(())}#[test]fn multi_agent_v2_default_usage_hints_use_configured_thread_cap() {    let config_toml = toml::from_str(        r#"[features.multi_agent_v2]enabled = truemax_concurrent_threads_per_session = 17"#,    )    .expect("multi-agent v2 config should parse");    let config = resolve_multi_agent_v2_config(&config_toml);    let concurrency_guidance = "There are 17 available concurrency slots, meaning that up to 17 agents can be active at once, including you.";    let expected_suffix = format!(        "{DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT}\n{concurrency_guidance}\n\n{DEFAULT_MULTI_AGENT_V2_NO_SPAWN_HINT_TEXT}"    );    assert!(        [            config.root_agent_usage_hint_text,            config.subagent_usage_hint_text,        ]        .into_iter()        .all(|hint| hint.is_some_and(|hint| hint.ends_with(expected_suffix.as_str())))    );}#[tokio::test]async fn multi_agent_v2_empty_usage_hint_overrides_clear_default_hints() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = trueroot_agent_usage_hint_text = ""subagent_usage_hint_text = """#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(config.multi_agent_v2.root_agent_usage_hint_text, None);    assert_eq!(config.multi_agent_v2.subagent_usage_hint_text, None);    Ok(())}#[tokio::test]async fn multi_agent_v2_feature_rejects_agents_max_threads() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = true[agents]max_threads = 3"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    let err = config        .validate_multi_agent_v2_config()        .expect_err("agents.max_threads should conflict with multi_agent_v2");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "agents.max_threads cannot be set when features.multi_agent_v2 is enabled"    );    assert_eq!(        config.effective_agent_max_threads(MultiAgentVersion::V2),        Some(3)    );    Ok(())}#[tokio::test]async fn catalog_v2_allows_agents_max_threads_when_feature_disabled() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = false[agents]max_threads = 3"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    config.validate_multi_agent_v2_config()?;    assert_eq!(        config.effective_agent_max_threads(MultiAgentVersion::V2),        Some(3)    );    Ok(())}#[tokio::test]async fn multi_agent_v2_rejects_invalid_wait_timeouts() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemin_wait_timeout_ms = 0max_wait_timeout_ms = 0default_wait_timeout_ms = 0"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 0);    assert_eq!(config.multi_agent_v2.max_wait_timeout_ms, 0);    assert_eq!(config.multi_agent_v2.default_wait_timeout_ms, 0);    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemin_wait_timeout_ms = -1"#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await        .expect_err("negative min_wait_timeout_ms should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "features.multi_agent_v2.min_wait_timeout_ms must be at least 0"    );    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemin_wait_timeout_ms = 3600001"#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await        .expect_err("too large min_wait_timeout_ms should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "features.multi_agent_v2.min_wait_timeout_ms must be at most 3600000"    );    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemax_wait_timeout_ms = -1"#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await        .expect_err("negative max_wait_timeout_ms should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "features.multi_agent_v2.max_wait_timeout_ms must be at least 0"    );    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemax_wait_timeout_ms = 3600001"#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await        .expect_err("too large max_wait_timeout_ms should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "features.multi_agent_v2.max_wait_timeout_ms must be at most 3600000"    );    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truedefault_wait_timeout_ms = -1"#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await        .expect_err("negative default_wait_timeout_ms should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "features.multi_agent_v2.default_wait_timeout_ms must be at least 0"    );    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemin_wait_timeout_ms = 1000max_wait_timeout_ms = 500"#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await        .expect_err("min greater than max should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "features.multi_agent_v2.min_wait_timeout_ms must be at most features.multi_agent_v2.max_wait_timeout_ms"    );    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemin_wait_timeout_ms = 1000max_wait_timeout_ms = 2000default_wait_timeout_ms = 500"#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await        .expect_err("default less than min should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "features.multi_agent_v2.default_wait_timeout_ms must be at least features.multi_agent_v2.min_wait_timeout_ms"    );    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemin_wait_timeout_ms = 1000max_wait_timeout_ms = 2000default_wait_timeout_ms = 2500"#,    )?;    let err = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await        .expect_err("default greater than max should be rejected");    assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);    assert_eq!(        err.to_string(),        "features.multi_agent_v2.default_wait_timeout_ms must be at most features.multi_agent_v2.max_wait_timeout_ms"    );    Ok(())}#[tokio::test]async fn multi_agent_v2_rejects_invalid_tool_namespace() -> std::io::Result<()> {    for (namespace, expected_message) in [        (            "bad namespace",            "features.multi_agent_v2.tool_namespace must match ^[a-zA-Z0-9_-]+$",        ),        (            "functions",            "features.multi_agent_v2.tool_namespace uses a reserved namespace: functions",        ),    ] {        let codex_home = TempDir::new()?;        std::fs::write(            codex_home.path().join(CONFIG_TOML_FILE),            format!(                r#"[features.multi_agent_v2]enabled = truetool_namespace = "{namespace}""#            ),        )?;        let err = ConfigBuilder::without_managed_config_for_tests()            .codex_home(codex_home.path().to_path_buf())            .fallback_cwd(Some(codex_home.path().to_path_buf()))            .build()            .await            .expect_err("invalid multi_agent_v2 tool namespace should fail");        assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);        assert_eq!(err.to_string(), expected_message);    }    Ok(())}#[tokio::test]async fn multi_agent_v2_session_thread_cap_one_disallows_subagents() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        r#"[features.multi_agent_v2]enabled = truemax_concurrent_threads_per_session = 1"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .fallback_cwd(Some(codex_home.path().to_path_buf()))        .build()        .await?;    assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 1);    assert_eq!(        (            config.agent_max_threads,            config.effective_agent_max_threads(MultiAgentVersion::V2)        ),        (None, Some(0))    );    Ok(())}#[tokio::test]async fn feature_requirements_normalize_runtime_feature_mutations() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let mut config = ConfigBuilder::default()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[features]personality = trueshell_tool = false"#,            ),        )        .build()        .await?;    let mut requested = config.features.get().clone();    requested        .disable(Feature::Personality)        .enable(Feature::ShellTool);    assert!(config.features.can_set(&requested).is_ok());    config        .features        .set(requested)        .expect("managed feature mutations should normalize successfully");    assert!(config.features.enabled(Feature::Personality));    assert!(!config.features.enabled(Feature::ShellTool));    Ok(())}#[tokio::test]async fn feature_requirements_warn_on_collab_legacy_alias() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[features]collab = true"#,            ),        )        .build()        .await?;    assert!(config.features.enabled(Feature::Collab));    assert!(        config.startup_warnings.iter().any(|warning| {            warning.contains("Using legacy `features` requirement `collab`")                && warning.contains("prefer canonical feature key `multi_agent`")        }),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn feature_requirements_warn_and_ignore_unknown_feature() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .cloud_config_bundle(            CloudConfigBundleFixture::loader_with_enterprise_requirement(                r#"[features]made_up_feature = true"#,            ),        )        .build()        .await?;    assert!(        config            .startup_warnings            .iter()            .any(|warning| warning                .contains("Ignoring unknown `features` requirement `made_up_feature`")),        "{:?}",        config.startup_warnings    );    Ok(())}#[tokio::test]async fn tool_suggest_discoverables_load_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"[tool_suggest]discoverables = [  { type = "connector", id = "connector_alpha" },  { type = "plugin", id = "plugin_alpha@openai-curated" },  { type = "connector", id = "   " }]"#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.tool_suggest,        Some(ToolSuggestConfig {            discoverables: vec![                ToolSuggestDiscoverable {                    kind: ToolSuggestDiscoverableType::Connector,                    id: "connector_alpha".to_string(),                },                ToolSuggestDiscoverable {                    kind: ToolSuggestDiscoverableType::Plugin,                    id: "plugin_alpha@openai-curated".to_string(),                },                ToolSuggestDiscoverable {                    kind: ToolSuggestDiscoverableType::Connector,                    id: "   ".to_string(),                },            ],            disabled_tools: Vec::new(),        })    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.tool_suggest,        ToolSuggestConfig {            discoverables: vec![                ToolSuggestDiscoverable {                    kind: ToolSuggestDiscoverableType::Connector,                    id: "connector_alpha".to_string(),                },                ToolSuggestDiscoverable {                    kind: ToolSuggestDiscoverableType::Plugin,                    id: "plugin_alpha@openai-curated".to_string(),                },            ],            disabled_tools: Vec::new(),        }    );    Ok(())}#[tokio::test]async fn tool_suggest_disabled_tools_load_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"[tool_suggest]disabled_tools = [  { type = "connector", id = " connector_calendar " },  { type = "connector", id = "connector_calendar" },  { type = "connector", id = "   " },  { type = "plugin", id = "slack@openai-curated" }]"#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.tool_suggest,        Some(ToolSuggestConfig {            discoverables: Vec::new(),            disabled_tools: vec![                ToolSuggestDisabledTool::connector(" connector_calendar "),                ToolSuggestDisabledTool::connector("connector_calendar"),                ToolSuggestDisabledTool::connector("   "),                ToolSuggestDisabledTool::plugin("slack@openai-curated"),            ],        })    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.tool_suggest,        ToolSuggestConfig {            discoverables: Vec::new(),            disabled_tools: vec![                ToolSuggestDisabledTool::connector("connector_calendar"),                ToolSuggestDisabledTool::plugin("slack@openai-curated"),            ],        }    );    Ok(())}#[tokio::test]async fn tool_suggest_disabled_tools_merge_across_config_layers() -> std::io::Result<()> {    let codex_home = TempDir::new()?;    let workspace = TempDir::new()?;    let workspace_key = workspace.path().to_string_lossy().replace('\\', "\\\\");    std::fs::write(        codex_home.path().join(CONFIG_TOML_FILE),        format!(            r#"[projects."{workspace_key}"]trust_level = "trusted"[tool_suggest]disabled_tools = [  {{ type = "connector", id = " user_connector " }},  {{ type = "plugin", id = "shared_plugin" }},  {{ type = "connector", id = "project_connector" }},]"#        ),    )?;    let project_config_dir = workspace.path().join(".codex");    std::fs::create_dir_all(&project_config_dir)?;    std::fs::write(        project_config_dir.join(CONFIG_TOML_FILE),        r#"[tool_suggest]disabled_tools = [  { type = "connector", id = "project_connector" },  { type = "plugin", id = "project_plugin" },  { type = "plugin", id = "shared_plugin" },]"#,    )?;    let config = ConfigBuilder::without_managed_config_for_tests()        .codex_home(codex_home.path().to_path_buf())        .harness_overrides(ConfigOverrides {            cwd: Some(workspace.path().to_path_buf()),            ..Default::default()        })        .build()        .await?;    assert_eq!(        config.tool_suggest.disabled_tools,        vec![            ToolSuggestDisabledTool::connector("user_connector"),            ToolSuggestDisabledTool::plugin("shared_plugin"),            ToolSuggestDisabledTool::connector("project_connector"),            ToolSuggestDisabledTool::plugin("project_plugin"),        ]    );    Ok(())}#[tokio::test]async fn experimental_realtime_start_instructions_load_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"experimental_realtime_start_instructions = "start instructions from config""#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.experimental_realtime_start_instructions.as_deref(),        Some("start instructions from config")    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.experimental_realtime_start_instructions.as_deref(),        Some("start instructions from config")    );    Ok(())}#[tokio::test]async fn experimental_thread_config_endpoint_loads_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"experimental_thread_config_endpoint = "http://127.0.0.1:8061""#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.experimental_thread_config_endpoint.as_deref(),        Some("http://127.0.0.1:8061")    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.experimental_thread_config_endpoint.as_deref(),        Some("http://127.0.0.1:8061")    );    Ok(())}#[tokio::test]async fn experimental_realtime_ws_base_url_loads_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"experimental_realtime_ws_base_url = "http://127.0.0.1:8011"experimental_realtime_webrtc_call_base_url = "http://127.0.0.1:8082/v1""#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.experimental_realtime_ws_base_url.as_deref(),        Some("http://127.0.0.1:8011")    );    assert_eq!(        cfg.experimental_realtime_webrtc_call_base_url.as_deref(),        Some("http://127.0.0.1:8082/v1")    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.experimental_realtime_ws_base_url.as_deref(),        Some("http://127.0.0.1:8011")    );    assert_eq!(        config.experimental_realtime_webrtc_call_base_url.as_deref(),        Some("http://127.0.0.1:8082/v1")    );    Ok(())}#[tokio::test]async fn experimental_realtime_ws_backend_prompt_loads_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"experimental_realtime_ws_backend_prompt = "prompt from config""#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.experimental_realtime_ws_backend_prompt.as_deref(),        Some("prompt from config")    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.experimental_realtime_ws_backend_prompt.as_deref(),        Some("prompt from config")    );    Ok(())}#[tokio::test]async fn experimental_realtime_ws_startup_context_loads_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"experimental_realtime_ws_startup_context = "startup context from config""#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.experimental_realtime_ws_startup_context.as_deref(),        Some("startup context from config")    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.experimental_realtime_ws_startup_context.as_deref(),        Some("startup context from config")    );    Ok(())}#[tokio::test]async fn experimental_realtime_ws_model_loads_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"experimental_realtime_ws_model = "realtime-test-model""#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.experimental_realtime_ws_model.as_deref(),        Some("realtime-test-model")    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.experimental_realtime_ws_model.as_deref(),        Some("realtime-test-model")    );    Ok(())}#[tokio::test]async fn realtime_config_partial_table_uses_realtime_defaults() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"[realtime]voice = "marin""#,    )    .expect("TOML deserialization should succeed");    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.realtime,        RealtimeConfig {            voice: Some(RealtimeVoice::Marin),            ..RealtimeConfig::default()        }    );    Ok(())}#[tokio::test]async fn realtime_loads_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"[realtime]architecture = "avas"version = "v2"type = "transcription"transport = "webrtc"voice = "cedar""#,    )    .expect("TOML deserialization should succeed");    assert_eq!(        cfg.realtime,        Some(RealtimeToml {            architecture: Some(RealtimeArchitecture::Avas),            version: Some(RealtimeWsVersion::V2),            session_type: Some(RealtimeWsMode::Transcription),            transport: Some(RealtimeTransport::WebRtc),            voice: Some(RealtimeVoice::Cedar),        })    );    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(        config.realtime,        RealtimeConfig {            architecture: RealtimeArchitecture::Avas,            version: RealtimeWsVersion::V2,            session_type: RealtimeWsMode::Transcription,            transport: RealtimeTransport::WebRtc,            voice: Some(RealtimeVoice::Cedar),        }    );    Ok(())}#[tokio::test]async fn realtime_audio_loads_from_config_toml() -> std::io::Result<()> {    let cfg: ConfigToml = toml::from_str(        r#"[audio]microphone = "USB Mic"speaker = "Desk Speakers""#,    )    .expect("TOML deserialization should succeed");    let realtime_audio = cfg        .audio        .as_ref()        .expect("realtime audio config should be present");    assert_eq!(realtime_audio.microphone.as_deref(), Some("USB Mic"));    assert_eq!(realtime_audio.speaker.as_deref(), Some("Desk Speakers"));    let codex_home = TempDir::new()?;    let config = Config::load_from_base_config_with_overrides(        cfg,        ConfigOverrides::default(),        codex_home.abs(),    )    .await?;    assert_eq!(config.realtime_audio.microphone.as_deref(), Some("USB Mic"));    assert_eq!(        config.realtime_audio.speaker.as_deref(),        Some("Desk Speakers")    );    Ok(())}#[derive(Deserialize, Debug, PartialEq)]struct TuiTomlTest {    #[serde(default, flatten)]    notifications: TuiNotificationSettings,}#[derive(Deserialize, Debug, PartialEq)]struct RootTomlTest {    tui: TuiTomlTest,}#[test]fn test_tui_notifications_true() {    let toml = r#"            [tui]            notifications = true        "#;    let parsed: RootTomlTest = toml::from_str(toml).expect("deserialize notifications=true");    assert_matches!(        parsed.tui.notifications.notifications,        Notifications::Enabled(true)    );}#[test]fn test_tui_notifications_custom_array() {    let toml = r#"            [tui]            notifications = ["foo"]        "#;    let parsed: RootTomlTest = toml::from_str(toml).expect("deserialize notifications=[\"foo\"]");    assert_matches!(        parsed.tui.notifications.notifications,        Notifications::Custom(ref v) if v == &vec!["foo".to_string()]    );}#[test]fn test_tui_notification_method() {    let toml = r#"            [tui]            notification_method = "bel"        "#;    let parsed: RootTomlTest =        toml::from_str(toml).expect("deserialize notification_method=\"bel\"");    assert_eq!(parsed.tui.notifications.method, NotificationMethod::Bel);}#[test]fn test_tui_notification_condition_defaults_to_unfocused() {    let toml = r#"            [tui]        "#;    let parsed: RootTomlTest =        toml::from_str(toml).expect("deserialize default notification condition");    assert_eq!(        parsed.tui.notifications.condition,        NotificationCondition::Unfocused    );}#[test]fn test_tui_notification_condition_always() {    let toml = r#"            [tui]            notification_condition = "always"        "#;    let parsed: RootTomlTest =        toml::from_str(toml).expect("deserialize notification_condition=\"always\"");    assert_eq!(        parsed.tui.notifications.condition,        NotificationCondition::Always    );}#[test]fn test_tui_notification_condition_rejects_unknown_value() {    let toml = r#"            [tui]            notification_condition = "background"        "#;    let err = toml::from_str::<RootTomlTest>(toml).expect_err("reject unknown condition");    let err = err.to_string();    assert!(        err.contains("unknown variant `background`")            && err.contains("unfocused")            && err.contains("always"),        "unexpected error: {err}"    );}