Codex Handbook
tui/src/debug_config.rs 1231 lines
use crate::history_cell::PlainHistoryCell;use crate::legacy_core::config::Config;use crate::legacy_core::config::Permissions;use crate::session_state::SessionNetworkProxyRuntime;use codex_app_server_protocol::ConfigLayerSource;use codex_config::CONFIG_TOML_FILE;use codex_config::ConfigLayerEntry;use codex_config::ConfigLayerStack;use codex_config::ConfigLayerStackOrdering;use codex_config::ManagedHooksRequirementsToml;use codex_config::NetworkConstraints;use codex_config::NetworkDomainPermissionToml;use codex_config::NetworkUnixSocketPermissionToml;use codex_config::RequirementSource;use codex_config::ResidencyRequirement;use codex_config::SandboxModeRequirement;use codex_config::WebSearchModeRequirement;use codex_config::format_config_layer_source;use codex_protocol::models::PermissionProfile;use codex_protocol::permissions::NetworkSandboxPolicy;use ratatui::style::Stylize;use ratatui::text::Line;use toml::Value as TomlValue;pub(crate) fn new_debug_config_output(    config: &Config,    session_network_proxy: Option<&SessionNetworkProxyRuntime>,) -> PlainHistoryCell {    let mut lines = render_debug_config_lines(&config.config_layer_stack, |mode| {        sandbox_mode_is_allowed_by_permissions(&config.permissions, mode)    });    if let Some(proxy) = session_network_proxy {        lines.push("".into());        lines.push("Session runtime:".bold().into());        lines.push("  - network_proxy".into());        let SessionNetworkProxyRuntime {            http_addr,            socks_addr,        } = proxy;        let all_proxy = session_all_proxy_url(            http_addr,            socks_addr,            config                .permissions                .network                .as_ref()                .is_some_and(crate::legacy_core::config::NetworkProxySpec::socks_enabled),        );        lines.push(format!("    - HTTP_PROXY  = http://{http_addr}").into());        lines.push(format!("    - ALL_PROXY   = {all_proxy}").into());    }    PlainHistoryCell::new(lines)}fn sandbox_mode_is_allowed_by_permissions(    permissions: &Permissions,    mode: SandboxModeRequirement,) -> bool {    let permission_profile = match mode {        SandboxModeRequirement::ReadOnly => PermissionProfile::read_only(),        SandboxModeRequirement::WorkspaceWrite => PermissionProfile::workspace_write(),        SandboxModeRequirement::DangerFullAccess => PermissionProfile::Disabled,        SandboxModeRequirement::ExternalSandbox => PermissionProfile::External {            network: NetworkSandboxPolicy::Restricted,        },    };    permissions        .can_set_permission_profile(&permission_profile)        .is_ok()}fn session_all_proxy_url(http_addr: &str, socks_addr: &str, socks_enabled: bool) -> String {    if socks_enabled {        format!("socks5h://{socks_addr}")    } else {        format!("http://{http_addr}")    }}fn render_debug_config_lines(    stack: &ConfigLayerStack,    sandbox_mode_is_effectively_allowed: impl Fn(SandboxModeRequirement) -> bool,) -> Vec<Line<'static>> {    let mut lines = vec!["/debug-config".magenta().into(), "".into()];    lines.push(        "Config layer stack (lowest precedence first):"            .bold()            .into(),    );    let layers = stack.get_layers(        ConfigLayerStackOrdering::LowestPrecedenceFirst,        /*include_disabled*/ true,    );    if layers.is_empty() {        lines.push("  <none>".dim().into());    } else {        for (index, layer) in layers.iter().enumerate() {            let source = format_config_layer_source(&layer.name, CONFIG_TOML_FILE);            let status = if layer.is_disabled() {                "disabled"            } else {                "enabled"            };            lines.push(format!("  {}. {source} ({status})", index + 1).into());            lines.extend(render_non_file_layer_details(layer));            if let Some(reason) = &layer.disabled_reason {                lines.push(format!("     reason: {reason}").dim().into());            }        }    }    let requirements = stack.requirements();    let requirements_toml = stack.requirements_toml();    lines.push("".into());    lines.push("Requirements:".bold().into());    let mut requirement_lines = Vec::new();    if let Some(policies) = requirements_toml.allowed_approval_policies.as_ref() {        let value = join_or_empty(policies.iter().map(ToString::to_string).collect::<Vec<_>>());        requirement_lines.push(requirement_line(            "allowed_approval_policies",            value,            requirements.approval_policy.source.as_ref(),        ));    }    if let Some(reviewers) = requirements_toml.allowed_approvals_reviewers.as_ref() {        let value = join_or_empty(            reviewers                .iter()                .map(ToString::to_string)                .collect::<Vec<_>>(),        );        requirement_lines.push(requirement_line(            "allowed_approvals_reviewers",            value,            requirements.approvals_reviewer.source.as_ref(),        ));    }    if let Some(modes) = requirements_toml.allowed_sandbox_modes.as_ref() {        let value = join_or_empty(            modes                .iter()                .copied()                .filter(|mode| sandbox_mode_is_effectively_allowed(*mode))                .map(format_sandbox_mode_requirement)                .collect::<Vec<_>>(),        );        requirement_lines.push(requirement_line(            "allowed_sandbox_modes",            value,            requirements.permission_profile.source.as_ref(),        ));    }    if let Some(modes) = requirements_toml.allowed_web_search_modes.as_ref() {        let normalized = normalize_allowed_web_search_modes(modes);        let value = join_or_empty(            normalized                .iter()                .map(ToString::to_string)                .collect::<Vec<_>>(),        );        requirement_lines.push(requirement_line(            "allowed_web_search_modes",            value,            requirements.web_search_mode.source.as_ref(),        ));    }    if let Some(allow_managed_hooks_only) = requirements_toml.allow_managed_hooks_only {        requirement_lines.push(requirement_line(            "allow_managed_hooks_only",            allow_managed_hooks_only.to_string(),            requirements                .allow_managed_hooks_only                .as_ref()                .map(|sourced| &sourced.source),        ));    }    if let Some(allow_appshots) = requirements_toml.allow_appshots {        requirement_lines.push(requirement_line(            "allow_appshots",            allow_appshots.to_string(),            requirements                .allow_appshots                .as_ref()                .map(|sourced| &sourced.source),        ));    }    if let Some(allow_remote_control) = requirements_toml.allow_remote_control {        requirement_lines.push(requirement_line(            "allow_remote_control",            allow_remote_control.to_string(),            requirements                .allow_remote_control                .as_ref()                .map(|sourced| &sourced.source),        ));    }    if requirements_toml.guardian_policy_config.is_some() {        requirement_lines.push(requirement_line(            "guardian_policy_config",            "configured".to_string(),            requirements.guardian_policy_config_source.as_ref(),        ));    }    if let Some(feature_requirements) = requirements.feature_requirements.as_ref() {        let value = join_or_empty(            feature_requirements                .value                .entries                .iter()                .map(|(feature, enabled)| format!("{feature}={enabled}"))                .collect::<Vec<_>>(),        );        requirement_lines.push(requirement_line(            "features",            value,            Some(&feature_requirements.source),        ));    }    if let Some(hooks) = requirements_toml.hooks.as_ref() {        requirement_lines.push(requirement_line(            "hooks",            format_managed_hooks_requirements(hooks),            requirements                .managed_hooks                .as_ref()                .and_then(|managed_hooks| managed_hooks.source.as_ref()),        ));    }    if let Some(servers) = requirements_toml.mcp_servers.as_ref() {        let value = join_or_empty(servers.keys().cloned().collect::<Vec<_>>());        requirement_lines.push(requirement_line(            "mcp_servers",            value,            requirements                .mcp_servers                .as_ref()                .map(|sourced| &sourced.source),        ));    }    // TODO(gt): Expand this debug output with detailed skills and rules display.    if requirements_toml.rules.is_some() {        requirement_lines.push(requirement_line(            "rules",            "configured".to_string(),            requirements.exec_policy_source(),        ));    }    if let Some(residency) = requirements_toml.enforce_residency {        requirement_lines.push(requirement_line(            "enforce_residency",            format_residency_requirement(residency),            requirements.enforce_residency.source.as_ref(),        ));    }    if let Some(network) = requirements.network.as_ref() {        requirement_lines.push(requirement_line(            "experimental_network",            format_network_constraints(&network.value),            Some(&network.source),        ));    }    if let Some(filesystem) = requirements.filesystem.as_ref() {        let deny_read = join_or_empty(            filesystem                .value                .deny_read                .iter()                .map(|pattern| pattern.as_str().to_string())                .collect::<Vec<_>>(),        );        requirement_lines.push(requirement_line(            "permissions.filesystem.deny_read",            deny_read,            Some(&filesystem.source),        ));    }    if requirement_lines.is_empty() {        lines.push("  <none>".dim().into());    } else {        lines.extend(requirement_lines);    }    lines}fn render_non_file_layer_details(layer: &ConfigLayerEntry) -> Vec<Line<'static>> {    match &layer.name {        ConfigLayerSource::SessionFlags => render_session_flag_details(&layer.config),        ConfigLayerSource::Mdm { .. }        | ConfigLayerSource::EnterpriseManaged { .. }        | ConfigLayerSource::LegacyManagedConfigTomlFromMdm => render_non_file_layer_value(layer),        ConfigLayerSource::System { .. }        | ConfigLayerSource::User { .. }        | ConfigLayerSource::Project { .. }        | ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => Vec::new(),    }}fn render_session_flag_details(config: &TomlValue) -> Vec<Line<'static>> {    let mut pairs = Vec::new();    flatten_toml_key_values(config, /*prefix*/ None, &mut pairs);    if pairs.is_empty() {        return vec!["     - <none>".dim().into()];    }    pairs        .into_iter()        .map(|(key, value)| format!("     - {key} = {value}").into())        .collect()}fn format_managed_hooks_requirements(hooks: &ManagedHooksRequirementsToml) -> String {    let mut parts = Vec::new();    if let Some(managed_dir) = hooks.managed_dir.as_ref() {        parts.push(format!("managed_dir={}", managed_dir.display()));    }    if let Some(windows_managed_dir) = hooks.windows_managed_dir.as_ref() {        parts.push(format!(            "windows_managed_dir={}",            windows_managed_dir.display()        ));    }    parts.push(format!("handlers={}", hooks.handler_count()));    join_or_empty(parts)}fn render_non_file_layer_value(layer: &ConfigLayerEntry) -> Vec<Line<'static>> {    let label = non_file_layer_value_label(&layer.name);    let value = layer        .raw_toml()        .map(ToString::to_string)        .unwrap_or_else(|| format_toml_value(&layer.config));    if value.is_empty() {        return vec![format!("     {label}: <empty>").dim().into()];    }    if value.contains('\n') {        let mut lines = vec![format!("     {label}:").into()];        lines.extend(value.lines().map(|line| format!("       {line}").into()));        lines    } else {        vec![format!("     {label}: {value}").into()]    }}fn non_file_layer_value_label(source: &ConfigLayerSource) -> &'static str {    match source {        ConfigLayerSource::Mdm { .. } | ConfigLayerSource::LegacyManagedConfigTomlFromMdm => {            "MDM value"        }        ConfigLayerSource::EnterpriseManaged { .. } => "Enterprise-managed config value",        ConfigLayerSource::SessionFlags        | ConfigLayerSource::System { .. }        | ConfigLayerSource::User { .. }        | ConfigLayerSource::Project { .. }        | ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => "Layer value",    }}fn flatten_toml_key_values(    value: &TomlValue,    prefix: Option<&str>,    out: &mut Vec<(String, String)>,) {    match value {        TomlValue::Table(table) => {            let mut entries = table.iter().collect::<Vec<_>>();            entries.sort_by_key(|(key, _)| key.as_str());            for (key, child) in entries {                let next_prefix = if let Some(prefix) = prefix {                    format!("{prefix}.{key}")                } else {                    key.to_string()                };                flatten_toml_key_values(child, Some(&next_prefix), out);            }        }        _ => {            let key = prefix.unwrap_or("<value>").to_string();            out.push((key, format_toml_value(value)));        }    }}fn format_toml_value(value: &TomlValue) -> String {    value.to_string()}fn requirement_line(    name: &str,    value: String,    source: Option<&RequirementSource>,) -> Line<'static> {    let source = source        .map(ToString::to_string)        .unwrap_or_else(|| "<unspecified>".to_string());    format!("  - {name}: {value} (source: {source})").into()}fn join_or_empty(values: Vec<String>) -> String {    if values.is_empty() {        "<empty>".to_string()    } else {        values.join(", ")    }}fn normalize_allowed_web_search_modes(    modes: &[WebSearchModeRequirement],) -> Vec<WebSearchModeRequirement> {    if modes.is_empty() {        return vec![WebSearchModeRequirement::Disabled];    }    let mut normalized = modes.to_vec();    if !normalized.contains(&WebSearchModeRequirement::Disabled) {        normalized.push(WebSearchModeRequirement::Disabled);    }    normalized}fn format_sandbox_mode_requirement(mode: SandboxModeRequirement) -> String {    match mode {        SandboxModeRequirement::ReadOnly => "read-only".to_string(),        SandboxModeRequirement::WorkspaceWrite => "workspace-write".to_string(),        SandboxModeRequirement::DangerFullAccess => "danger-full-access".to_string(),        SandboxModeRequirement::ExternalSandbox => "external-sandbox".to_string(),    }}fn format_residency_requirement(requirement: ResidencyRequirement) -> String {    match requirement {        ResidencyRequirement::Us => "us".to_string(),    }}fn format_network_constraints(network: &NetworkConstraints) -> String {    let mut parts = Vec::new();    let NetworkConstraints {        enabled,        http_port,        socks_port,        allow_upstream_proxy,        dangerously_allow_non_loopback_proxy,        dangerously_allow_all_unix_sockets,        domains,        managed_allowed_domains_only,        unix_sockets,        allow_local_binding,    } = network;    if let Some(enabled) = enabled {        parts.push(format!("enabled={enabled}"));    }    if let Some(http_port) = http_port {        parts.push(format!("http_port={http_port}"));    }    if let Some(socks_port) = socks_port {        parts.push(format!("socks_port={socks_port}"));    }    if let Some(allow_upstream_proxy) = allow_upstream_proxy {        parts.push(format!("allow_upstream_proxy={allow_upstream_proxy}"));    }    if let Some(dangerously_allow_non_loopback_proxy) = dangerously_allow_non_loopback_proxy {        parts.push(format!(            "dangerously_allow_non_loopback_proxy={dangerously_allow_non_loopback_proxy}"        ));    }    if let Some(dangerously_allow_all_unix_sockets) = dangerously_allow_all_unix_sockets {        parts.push(format!(            "dangerously_allow_all_unix_sockets={dangerously_allow_all_unix_sockets}"        ));    }    if let Some(domains) = domains {        parts.push(format!(            "domains={}",            format_network_permission_entries(&domains.entries, format_network_domain_permission)        ));    }    if let Some(managed_allowed_domains_only) = managed_allowed_domains_only {        parts.push(format!(            "managed_allowed_domains_only={managed_allowed_domains_only}"        ));    }    if let Some(unix_sockets) = unix_sockets {        parts.push(format!(            "unix_sockets={}",            format_network_permission_entries(                &unix_sockets.entries,                format_network_unix_socket_permission,            )        ));    }    if let Some(allow_local_binding) = allow_local_binding {        parts.push(format!("allow_local_binding={allow_local_binding}"));    }    join_or_empty(parts)}fn format_network_permission_entries<T: Copy>(    entries: &std::collections::BTreeMap<String, T>,    format_value: impl Fn(T) -> &'static str,) -> String {    let parts = entries        .iter()        .map(|(key, value)| format!("{key}={}", format_value(*value)))        .collect::<Vec<_>>();    format!("{{{}}}", parts.join(", "))}fn format_network_domain_permission(permission: NetworkDomainPermissionToml) -> &'static str {    match permission {        NetworkDomainPermissionToml::Allow => "allow",        NetworkDomainPermissionToml::Deny => "deny",    }}fn format_network_unix_socket_permission(    permission: NetworkUnixSocketPermissionToml,) -> &'static str {    match permission {        NetworkUnixSocketPermissionToml::Allow => "allow",        NetworkUnixSocketPermissionToml::Deny => "deny",    }}#[cfg(test)]mod tests {    use super::render_debug_config_lines;    use super::sandbox_mode_is_allowed_by_permissions;    use super::session_all_proxy_url;    use crate::legacy_core::config::Permissions;    use codex_app_server_protocol::AskForApproval;    use codex_app_server_protocol::ConfigLayerSource;    use codex_config::ConfigLayerEntry;    use codex_config::ConfigLayerStack;    use codex_config::ConfigRequirements;    use codex_config::ConfigRequirementsToml;    use codex_config::Constrained;    use codex_config::ConstrainedWithSource;    use codex_config::ConstraintError;    use codex_config::FeatureRequirementsToml;    use codex_config::FilesystemConstraints;    use codex_config::HookEventsToml;    use codex_config::HookHandlerConfig;    use codex_config::ManagedHooksRequirementsToml;    use codex_config::MatcherGroup;    use codex_config::McpServerIdentity;    use codex_config::McpServerRequirement;    use codex_config::NetworkConstraints;    use codex_config::NetworkDomainPermissionToml;    use codex_config::NetworkDomainPermissionsToml;    use codex_config::NetworkUnixSocketPermissionToml;    use codex_config::NetworkUnixSocketPermissionsToml;    use codex_config::RequirementSource;    use codex_config::ResidencyRequirement;    use codex_config::SandboxModeRequirement;    use codex_config::Sourced;    use codex_config::WebSearchModeRequirement;    use codex_config::sandbox_mode_requirement_for_permission_profile;    use codex_protocol::config_types::ApprovalsReviewer;    use codex_protocol::config_types::WebSearchMode;    use codex_protocol::models::PermissionProfile;    use codex_utils_absolute_path::AbsolutePathBuf;    use ratatui::text::Line;    use std::collections::BTreeMap;    use toml::Value as TomlValue;    fn empty_toml_table() -> TomlValue {        TomlValue::Table(toml::map::Map::new())    }    fn absolute_path(path: &str) -> AbsolutePathBuf {        AbsolutePathBuf::from_absolute_path(path).expect("absolute path")    }    fn render_to_text(lines: &[Line<'static>]) -> String {        lines            .iter()            .map(|line| {                line.spans                    .iter()                    .map(|span| span.content.as_ref())                    .collect::<String>()            })            .collect::<Vec<_>>()            .join("\n")    }    fn render_stack_to_text(stack: &ConfigLayerStack) -> String {        render_stack_to_text_with_sandbox_mode_filter(stack, |_| true)    }    fn render_stack_to_text_with_sandbox_mode_filter(        stack: &ConfigLayerStack,        sandbox_mode_is_effectively_allowed: impl Fn(SandboxModeRequirement) -> bool,    ) -> String {        render_to_text(&render_debug_config_lines(            stack,            sandbox_mode_is_effectively_allowed,        ))    }    #[test]    fn debug_config_output_lists_all_layers_including_disabled() {        let system_file = if cfg!(windows) {            absolute_path("C:\\etc\\codex\\config.toml")        } else {            absolute_path("/etc/codex/config.toml")        };        let project_folder = if cfg!(windows) {            absolute_path("C:\\repo\\.codex")        } else {            absolute_path("/repo/.codex")        };        let layers = vec![            ConfigLayerEntry::new(                ConfigLayerSource::System { file: system_file },                empty_toml_table(),            ),            ConfigLayerEntry::new_disabled(                ConfigLayerSource::Project {                    dot_codex_folder: project_folder,                },                empty_toml_table(),                "project is untrusted",            ),        ];        let stack = ConfigLayerStack::new(            layers,            ConfigRequirements::default(),            ConfigRequirementsToml::default(),        )        .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        assert!(rendered.contains("(enabled)"));        assert!(rendered.contains("(disabled)"));        assert!(rendered.contains("reason: project is untrusted"));        assert!(rendered.contains("Requirements:"));        assert!(rendered.contains("  <none>"));    }    #[test]    fn debug_config_output_lists_requirement_sources() {        let requirements_file = if cfg!(windows) {            absolute_path("C:\\ProgramData\\OpenAI\\Codex\\requirements.toml")        } else {            absolute_path("/etc/codex/requirements.toml")        };        let denied_path = if cfg!(windows) {            absolute_path("C:\\Users\\alice\\.gitconfig")        } else {            absolute_path("/home/alice/.gitconfig")        };        let requirements = ConfigRequirements {            approval_policy: ConstrainedWithSource::new(                Constrained::allow_any(AskForApproval::OnRequest.to_core()),                Some(RequirementSource::LegacyManagedConfigTomlFromMdm),            ),            approvals_reviewer: ConstrainedWithSource::new(                Constrained::allow_any(ApprovalsReviewer::AutoReview),                Some(RequirementSource::LegacyManagedConfigTomlFromMdm),            ),            permission_profile: ConstrainedWithSource::new(                Constrained::allow_any(PermissionProfile::read_only()),                Some(RequirementSource::SystemRequirementsToml {                    file: requirements_file.clone(),                }),            ),            mcp_servers: Some(Sourced::new(                BTreeMap::from([(                    "docs".to_string(),                    McpServerRequirement {                        identity: McpServerIdentity::Command {                            command: "codex-mcp".to_string(),                        },                    },                )]),                RequirementSource::LegacyManagedConfigTomlFromMdm,            )),            enforce_residency: ConstrainedWithSource::new(                Constrained::allow_any(Some(ResidencyRequirement::Us)),                Some(RequirementSource::LegacyManagedConfigTomlFromMdm),            ),            web_search_mode: ConstrainedWithSource::new(                Constrained::allow_any(WebSearchMode::Cached),                Some(RequirementSource::LegacyManagedConfigTomlFromMdm),            ),            allow_managed_hooks_only: Some(Sourced::new(                /*value*/ true,                RequirementSource::LegacyManagedConfigTomlFromMdm,            )),            allow_appshots: Some(Sourced::new(                /*value*/ false,                RequirementSource::LegacyManagedConfigTomlFromMdm,            )),            allow_remote_control: Some(Sourced::new(                /*value*/ false,                RequirementSource::LegacyManagedConfigTomlFromMdm,            )),            feature_requirements: Some(Sourced::new(                FeatureRequirementsToml {                    entries: BTreeMap::from([("guardian_approval".to_string(), true)]),                },                RequirementSource::LegacyManagedConfigTomlFromMdm,            )),            network: Some(Sourced::new(                NetworkConstraints {                    enabled: Some(true),                    domains: Some(NetworkDomainPermissionsToml {                        entries: BTreeMap::from([(                            "example.com".to_string(),                            NetworkDomainPermissionToml::Allow,                        )]),                    }),                    ..Default::default()                },                RequirementSource::LegacyManagedConfigTomlFromMdm,            )),            filesystem: Some(Sourced::new(                FilesystemConstraints {                    deny_read: vec![denied_path.clone().into()],                },                RequirementSource::SystemRequirementsToml {                    file: requirements_file.clone(),                },            )),            guardian_policy_config_source: Some(RequirementSource::LegacyManagedConfigTomlFromMdm),            ..ConfigRequirements::default()        };        let requirements_toml = ConfigRequirementsToml {            allowed_approval_policies: Some(vec![AskForApproval::OnRequest.to_core()]),            allowed_approvals_reviewers: Some(vec![ApprovalsReviewer::AutoReview]),            allowed_sandbox_modes: Some(vec![SandboxModeRequirement::ReadOnly]),            allowed_permission_profiles: None,            default_permissions: None,            remote_sandbox_config: None,            allowed_web_search_modes: Some(vec![WebSearchModeRequirement::Cached]),            allow_managed_hooks_only: Some(true),            allow_appshots: Some(false),            allow_remote_control: Some(false),            computer_use: None,            windows: None,            guardian_policy_config: Some("Use the managed guardian policy.".to_string()),            feature_requirements: Some(FeatureRequirementsToml {                entries: BTreeMap::from([("guardian_approval".to_string(), true)]),            }),            hooks: None,            mcp_servers: Some(BTreeMap::from([(                "docs".to_string(),                McpServerRequirement {                    identity: McpServerIdentity::Command {                        command: "codex-mcp".to_string(),                    },                },            )])),            plugins: None,            apps: None,            rules: None,            enforce_residency: Some(ResidencyRequirement::Us),            network: None,            permissions: None,        };        let user_file = if cfg!(windows) {            absolute_path("C:\\users\\alice\\.codex\\config.toml")        } else {            absolute_path("/home/alice/.codex/config.toml")        };        let stack = ConfigLayerStack::new(            vec![ConfigLayerEntry::new(                ConfigLayerSource::User {                    file: user_file,                    profile: None,                },                empty_toml_table(),            )],            requirements,            requirements_toml,        )        .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        #[cfg(not(windows))]        insta::assert_snapshot!("debug_config_requirement_sources", rendered.as_str());        let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string();        assert!(rendered.contains(&format!(            "allowed_approval_policies: on-request (source: {requirements_source})"        )));        assert!(rendered.contains(            "allowed_approvals_reviewers: auto_review (source: MDM managed_config.toml (legacy))"        ));        assert!(            rendered.contains(                format!(                    "allowed_sandbox_modes: read-only (source: {})",                    requirements_file.as_path().display()                )                .as_str(),            )        );        assert!(rendered.contains(&format!(            "allowed_web_search_modes: cached, disabled (source: {requirements_source})"        )));        assert!(rendered.contains(&format!(            "allow_managed_hooks_only: true (source: {requirements_source})"        )));        assert!(rendered.contains(&format!(            "allow_appshots: false (source: {requirements_source})"        )));        assert!(rendered.contains(&format!(            "allow_remote_control: false (source: {requirements_source})"        )));        assert!(rendered.contains(&format!(            "guardian_policy_config: configured (source: {requirements_source})"        )));        assert!(rendered.contains(&format!(            "features: guardian_approval=true (source: {requirements_source})"        )));        assert!(rendered.contains("mcp_servers: docs (source: MDM managed_config.toml (legacy))"));        assert!(rendered.contains(&format!(            "enforce_residency: us (source: {requirements_source})"        )));        assert!(rendered.contains(&format!(            "experimental_network: enabled=true, domains={{example.com=allow}} (source: {requirements_source})"        )));        assert!(            rendered.contains(                format!(                    "permissions.filesystem.deny_read: {}",                    denied_path.as_path().display()                )                .as_str()            )        );        assert!(!rendered.contains("  - rules:"));    }    #[test]    fn debug_config_output_filters_sandbox_modes_blocked_by_deny_read_requirements() {        let requirements_file = if cfg!(windows) {            absolute_path("C:\\ProgramData\\OpenAI\\Codex\\requirements.toml")        } else {            absolute_path("/etc/codex/requirements.toml")        };        let denied_path = if cfg!(windows) {            absolute_path("C:\\Users\\alice\\.gitconfig")        } else {            absolute_path("/home/alice/.gitconfig")        };        let requirements = ConfigRequirements {            permission_profile: ConstrainedWithSource::new(                Constrained::allow_any(PermissionProfile::read_only()),                Some(RequirementSource::SystemRequirementsToml {                    file: requirements_file.clone(),                }),            ),            filesystem: Some(Sourced::new(                FilesystemConstraints {                    deny_read: vec![denied_path.into()],                },                RequirementSource::SystemRequirementsToml {                    file: requirements_file.clone(),                },            )),            ..ConfigRequirements::default()        };        let requirements_toml = ConfigRequirementsToml {            allowed_sandbox_modes: Some(vec![                SandboxModeRequirement::ReadOnly,                SandboxModeRequirement::WorkspaceWrite,                SandboxModeRequirement::DangerFullAccess,                SandboxModeRequirement::ExternalSandbox,            ]),            ..ConfigRequirementsToml::default()        };        let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)            .expect("config layer stack");        let constrained_permission_profile =            Constrained::new(PermissionProfile::read_only(), |candidate| {                let mode = sandbox_mode_requirement_for_permission_profile(candidate);                match mode {                    SandboxModeRequirement::ReadOnly | SandboxModeRequirement::WorkspaceWrite => {                        Ok(())                    }                    SandboxModeRequirement::DangerFullAccess                    | SandboxModeRequirement::ExternalSandbox => {                        Err(ConstraintError::InvalidValue {                            field_name: "sandbox_mode",                            candidate: format!("{mode:?}"),                            allowed: "[read-only, workspace-write]".to_string(),                            requirement_source: RequirementSource::Unknown,                        })                    }                }            })            .expect("constrained permission profile");        let permissions = Permissions::from_approval_and_profile(            Constrained::allow_any(AskForApproval::OnRequest.to_core()),            constrained_permission_profile,        )        .expect("permissions");        let rendered = render_stack_to_text_with_sandbox_mode_filter(&stack, |mode| {            sandbox_mode_is_allowed_by_permissions(&permissions, mode)        });        #[cfg(not(windows))]        insta::assert_snapshot!(            "debug_config_effective_sandbox_modes_with_deny_read",            rendered.as_str()        );        assert!(            rendered.contains(                format!(                    "allowed_sandbox_modes: read-only, workspace-write (source: {})",                    requirements_file.as_path().display()                )                .as_str()            )        );        assert!(!rendered.contains("danger-full-access"));        assert!(!rendered.contains("external-sandbox"));    }    #[test]    fn debug_config_output_lists_approvals_reviewer_as_requirement() {        let requirements = ConfigRequirements {            approvals_reviewer: ConstrainedWithSource::new(                Constrained::allow_any(ApprovalsReviewer::AutoReview),                Some(RequirementSource::LegacyManagedConfigTomlFromMdm),            ),            ..ConfigRequirements::default()        };        let requirements_toml = ConfigRequirementsToml {            allowed_approvals_reviewers: Some(vec![ApprovalsReviewer::AutoReview]),            ..ConfigRequirementsToml::default()        };        let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)            .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        assert!(rendered.contains(            "allowed_approvals_reviewers: auto_review (source: MDM managed_config.toml (legacy))"        ));        assert!(!rendered.contains("Requirements:\n  <none>"));    }    #[test]    fn debug_config_output_formats_unix_socket_permissions() {        let requirements = ConfigRequirements {            network: Some(Sourced::new(                NetworkConstraints {                    unix_sockets: Some(NetworkUnixSocketPermissionsToml {                        entries: BTreeMap::from([                            (                                "/tmp/codex.sock".to_string(),                                NetworkUnixSocketPermissionToml::Allow,                            ),                            (                                "/tmp/blocked.sock".to_string(),                                NetworkUnixSocketPermissionToml::Deny,                            ),                        ]),                    }),                    ..Default::default()                },                RequirementSource::LegacyManagedConfigTomlFromMdm,            )),            ..ConfigRequirements::default()        };        let stack =            ConfigLayerStack::new(Vec::new(), requirements, ConfigRequirementsToml::default())                .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string();        assert!(rendered.contains(&format!(            "experimental_network: unix_sockets={{/tmp/blocked.sock=deny, /tmp/codex.sock=allow}} (source: {requirements_source})"        )));    }    #[test]    fn debug_config_output_lists_session_flag_key_value_pairs() {        let session_flags = toml::from_str::<TomlValue>(            r#"model = "gpt-5"[sandbox_workspace_write]network_access = truewritable_roots = ["/tmp"]"#,        )        .expect("session flags");        let stack = ConfigLayerStack::new(            vec![ConfigLayerEntry::new(                ConfigLayerSource::SessionFlags,                session_flags,            )],            ConfigRequirements::default(),            ConfigRequirementsToml::default(),        )        .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        assert!(rendered.contains("session-flags (enabled)"));        assert!(rendered.contains("     - model = \"gpt-5\""));        assert!(rendered.contains("     - sandbox_workspace_write.network_access = true"));        assert!(rendered.contains("sandbox_workspace_write.writable_roots"));        assert!(rendered.contains("/tmp"));    }    #[test]    fn debug_config_output_shows_legacy_mdm_layer_value() {        let raw_mdm_toml = r#"# managed by MDMmodel = "managed_model"approval_policy = "never""#;        let mdm_value = toml::from_str::<TomlValue>(raw_mdm_toml).expect("MDM value");        let mdm_base_dir = if cfg!(windows) {            absolute_path("C:\\codex")        } else {            absolute_path("/var/lib/codex")        };        let stack = ConfigLayerStack::new(            vec![ConfigLayerEntry::new_with_raw_toml(                ConfigLayerSource::LegacyManagedConfigTomlFromMdm,                mdm_value,                raw_mdm_toml.to_string(),                mdm_base_dir,            )],            ConfigRequirements::default(),            ConfigRequirementsToml::default(),        )        .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        assert!(rendered.contains("legacy managed_config.toml (MDM) (enabled)"));        assert!(rendered.contains("MDM value:"));        assert!(rendered.contains("# managed by MDM"));        assert!(rendered.contains("model = \"managed_model\""));        assert!(rendered.contains("approval_policy = \"never\""));    }    #[test]    fn debug_config_output_shows_enterprise_managed_layer_value() {        let raw_cloud_toml = r#"# managed by cloudmodel = "enterprise_model"approval_policy = "never""#;        let cloud_value = toml::from_str::<TomlValue>(raw_cloud_toml).expect("cloud value");        let cloud_base_dir = if cfg!(windows) {            absolute_path("C:\\codex")        } else {            absolute_path("/var/lib/codex")        };        let stack = ConfigLayerStack::new(            vec![ConfigLayerEntry::new_with_raw_toml(                ConfigLayerSource::EnterpriseManaged {                    id: "cfg_123".to_string(),                    name: "Base policy".to_string(),                },                cloud_value,                raw_cloud_toml.to_string(),                cloud_base_dir,            )],            ConfigRequirements::default(),            ConfigRequirementsToml::default(),        )        .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        assert!(rendered.contains("enterprise-managed (Base policy, cfg_123) (enabled)"));        assert!(rendered.contains("Enterprise-managed config value:"));        assert!(!rendered.contains("MDM value:"));        assert!(rendered.contains("# managed by cloud"));        assert!(rendered.contains("model = \"enterprise_model\""));        assert!(rendered.contains("approval_policy = \"never\""));    }    #[test]    fn debug_config_output_normalizes_empty_web_search_mode_list() {        let requirements = ConfigRequirements {            web_search_mode: ConstrainedWithSource::new(                Constrained::allow_any(WebSearchMode::Disabled),                Some(RequirementSource::LegacyManagedConfigTomlFromMdm),            ),            ..ConfigRequirements::default()        };        let requirements_toml = 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::new()),            allow_managed_hooks_only: None,            allow_appshots: None,            allow_remote_control: None,            computer_use: None,            windows: None,            guardian_policy_config: None,            feature_requirements: None,            hooks: None,            mcp_servers: None,            plugins: None,            apps: None,            rules: None,            enforce_residency: None,            network: None,            permissions: None,        };        let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)            .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string();        assert!(rendered.contains(&format!(            "allowed_web_search_modes: disabled (source: {requirements_source})"        )));    }    #[test]    fn debug_config_output_lists_managed_hooks_requirement() {        let requirements = ConfigRequirements {            managed_hooks: Some(ConstrainedWithSource::new(                Constrained::allow_any(ManagedHooksRequirementsToml {                    managed_dir: Some(if cfg!(windows) {                        std::path::PathBuf::from(r"C:\enterprise\hooks")                    } else {                        std::path::PathBuf::from("/enterprise/hooks")                    }),                    windows_managed_dir: Some(std::path::PathBuf::from(r"C:\enterprise\hooks")),                    hooks: HookEventsToml {                        pre_tool_use: vec![MatcherGroup {                            matcher: Some("^Bash$".to_string()),                            hooks: vec![HookHandlerConfig::Command {                                command: "python3 /enterprise/hooks/pre.py".to_string(),                                command_windows: None,                                timeout_sec: Some(10),                                r#async: false,                                status_message: Some("checking".to_string()),                            }],                        }],                        ..Default::default()                    },                }),                Some(RequirementSource::LegacyManagedConfigTomlFromMdm),            )),            ..ConfigRequirements::default()        };        let requirements_toml = ConfigRequirementsToml {            hooks: requirements                .managed_hooks                .as_ref()                .map(|hooks| hooks.get().clone()),            ..ConfigRequirementsToml::default()        };        let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)            .expect("config layer stack");        let rendered = render_stack_to_text(&stack);        let requirements_source = (RequirementSource::LegacyManagedConfigTomlFromMdm).to_string();        assert!(rendered.contains("hooks:"));        assert!(rendered.contains("handlers=1"));        assert!(rendered.contains(&format!("(source: {requirements_source})")));    }    #[test]    fn session_all_proxy_url_uses_socks_when_enabled() {        assert_eq!(            session_all_proxy_url(                "127.0.0.1:3128",                "127.0.0.1:8081",                /*socks_enabled*/ true            ),            "socks5h://127.0.0.1:8081".to_string()        );    }    #[test]    fn session_all_proxy_url_uses_http_when_socks_disabled() {        assert_eq!(            session_all_proxy_url(                "127.0.0.1:3128",                "127.0.0.1:8081",                /*socks_enabled*/ false            ),            "http://127.0.0.1:3128".to_string()        );    }}