From 85a1b0e33db92f94f02e54484b0ef1d0db1050f6 Mon Sep 17 00:00:00 2001 From: Sean Huang Date: Thu, 20 Aug 2026 17:06:18 +0000 Subject: [PATCH] Expose uncompiled permission profile selection (#39752) ## What changed - Add `resolve_permission_profile_selection` to select the effective profile using configured defaults, managed requirements, and allowlists. - Return the merged configured and managed profile catalog without compiling platform-specific paths, leaving path interpretation to the executor. ## Testing - Verify that a managed default overrides a configured default while preserving Windows-style paths. - Verify that an allowlisted but undefined profile is rejected. GitOrigin-RevId: 0172193d48f0346522f931d3b806054895d199c0 --- codex-rs/core/src/config/mod.rs | 3 + .../config/permission_profile_selection.rs | 39 ++++++++ .../permission_profile_selection_tests.rs | 96 +++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 codex-rs/core/src/config/permission_profile_selection.rs create mode 100644 codex-rs/core/src/config/permission_profile_selection_tests.rs diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 0a3ebf4587..2f74cf41bc 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -159,6 +159,7 @@ mod managed_features; mod network_proxy_spec; mod otel; mod permission_profile_catalog; +mod permission_profile_selection; mod permissions; mod requirements; mod resolved_permission_profile; @@ -182,6 +183,8 @@ pub use permission_profile_catalog::permission_profile_catalog; use permission_profile_catalog::permission_profile_catalog_from_permissions; use permission_profile_catalog::permission_profile_is_allowed; use permission_profile_catalog::validate_permission_profile_for_deny_read; +pub use permission_profile_selection::ResolvedPermissionProfileSelection; +pub use permission_profile_selection::resolve_permission_profile_selection; pub(crate) use permissions::is_builtin_permission_profile_name; pub(crate) use resolved_permission_profile::PermissionProfileState; diff --git a/codex-rs/core/src/config/permission_profile_selection.rs b/codex-rs/core/src/config/permission_profile_selection.rs new file mode 100644 index 0000000000..025fc9b9a5 --- /dev/null +++ b/codex-rs/core/src/config/permission_profile_selection.rs @@ -0,0 +1,39 @@ +use codex_config::ConfigRequirementsToml; +use codex_config::permissions_toml::PermissionsToml; + +/// A selected permission profile and its uncompiled configured and managed catalog. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedPermissionProfileSelection<'a> { + /// The effective profile selected by configuration and managed requirements. + pub profile_id: Option<&'a str>, + /// Configured and managed named profiles, without platform-specific compilation. + pub profiles: Option, +} + +/// Resolves the effective permission profile without interpreting profile paths. +/// +/// Managed profiles and allowlists are applied using Core's normal selection rules, +/// while the merged profile catalog remains uncompiled for the executor to interpret. +pub fn resolve_permission_profile_selection<'a>( + configured_profiles: Option<&PermissionsToml>, + configured_default_profile_id: Option<&'a str>, + requirements: &'a ConfigRequirementsToml, +) -> std::io::Result> { + let selection = super::resolve_effective_permission_selection( + configured_profiles, + /*default_permissions_override*/ None, + /*persisted_profile_id*/ None, + configured_default_profile_id, + requirements, + &mut Vec::new(), + )?; + + Ok(ResolvedPermissionProfileSelection { + profile_id: selection.selected_profile_id, + profiles: selection.profiles, + }) +} + +#[cfg(test)] +#[path = "permission_profile_selection_tests.rs"] +mod tests; diff --git a/codex-rs/core/src/config/permission_profile_selection_tests.rs b/codex-rs/core/src/config/permission_profile_selection_tests.rs new file mode 100644 index 0000000000..5392c5a3df --- /dev/null +++ b/codex-rs/core/src/config/permission_profile_selection_tests.rs @@ -0,0 +1,96 @@ +use crate::config::ResolvedPermissionProfileSelection; +use crate::config::resolve_permission_profile_selection; +use codex_config::ConfigRequirementsToml; +use codex_config::permissions_toml::PermissionsToml; +use pretty_assertions::assert_eq; +use std::collections::BTreeMap; + +#[test] +fn resolves_managed_profile_without_compiling_executor_paths() -> anyhow::Result<()> { + let configured_profiles = toml::from_str::( + r#" +[configured-windows] +extends = ":workspace" + +[configured-windows.workspace_roots] +'C:\Users\agent\workspace' = true + +[configured-windows.filesystem] +'C:\Users\agent\workspace' = "write" +"#, + )?; + let requirements = toml::from_str::( + r#" +default_permissions = "managed-windows" + +[allowed_permission_profiles] +configured-windows = false +managed-windows = true + +[permissions.managed-windows.workspace_roots] +'D:\Managed\workspace' = true + +[permissions.managed-windows.filesystem] +'D:\Managed\workspace' = "read" +"#, + )?; + let expected_profiles = PermissionsToml { + entries: BTreeMap::from([ + ( + "configured-windows".to_string(), + configured_profiles.entries["configured-windows"].clone(), + ), + ( + "managed-windows".to_string(), + requirements + .permissions + .as_ref() + .expect("managed profiles should deserialize") + .profiles["managed-windows"] + .clone(), + ), + ]), + }; + + let resolved = resolve_permission_profile_selection( + Some(&configured_profiles), + Some("configured-windows"), + &requirements, + )?; + + assert_eq!( + resolved, + ResolvedPermissionProfileSelection { + profile_id: Some("managed-windows"), + profiles: Some(expected_profiles), + } + ); + + Ok(()) +} + +#[test] +fn rejects_undefined_allowlisted_permission_profile() -> anyhow::Result<()> { + let requirements = toml::from_str::( + r#" +default_permissions = "missing-profile" + +[allowed_permission_profiles] +missing-profile = true +"#, + )?; + + let error = resolve_permission_profile_selection( + /*configured_profiles*/ None, + /*configured_default_profile_id*/ None, + &requirements, + ) + .expect_err("undefined allowlisted profiles should be rejected"); + + assert_eq!( + error.to_string(), + "requirements.toml allowed_permission_profiles refers to undefined profile `missing-profile`" + ); + + Ok(()) +}