diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index e9cd1c68e89..28fda873571 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -72,8 +72,8 @@ const DEFAULT_PROJECT_ROOT_MARKERS: &[&str] = &[".git"]; /// configuration layers in the following order, but a constraint defined in an /// earlier layer cannot be overridden by a later layer: /// -/// - admin: managed preferences (*) /// - cloud: managed cloud requirements +/// - admin: managed preferences (*) /// - system `/etc/codex/requirements.toml` /// /// For backwards compatibility, we also load from @@ -107,6 +107,11 @@ pub async fn load_config_layers_state( ) -> io::Result { let mut config_requirements_toml = ConfigRequirementsWithSources::default(); + if let Some(requirements) = cloud_requirements.get().await { + config_requirements_toml + .merge_unset_fields(RequirementSource::CloudRequirements, requirements); + } + #[cfg(target_os = "macos")] macos::load_managed_admin_requirements_toml( &mut config_requirements_toml, @@ -116,11 +121,6 @@ pub async fn load_config_layers_state( ) .await?; - if let Some(requirements) = cloud_requirements.get().await { - config_requirements_toml - .merge_unset_fields(RequirementSource::CloudRequirements, requirements); - } - // Honor /etc/codex/requirements.toml. if cfg!(unix) { load_requirements_toml( diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index cbd49f131c8..2d7e36d6efa 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -487,6 +487,59 @@ enforce_residency = "us" Ok(()) } +#[cfg(target_os = "macos")] +#[tokio::test] +async fn cloud_requirements_take_precedence_over_mdm_requirements() -> anyhow::Result<()> { + use base64::Engine; + + let tmp = tempdir()?; + let state = load_config_layers_state( + tmp.path(), + Some(AbsolutePathBuf::try_from(tmp.path())?), + &[] as &[(String, TomlValue)], + LoaderOverrides { + macos_managed_config_requirements_base64: Some( + base64::prelude::BASE64_STANDARD.encode( + r#" +allowed_approval_policies = ["on-request"] +"# + .as_bytes(), + ), + ), + ..LoaderOverrides::default() + }, + CloudRequirementsLoader::new(async { + Some(ConfigRequirementsToml { + allowed_approval_policies: Some(vec![AskForApproval::Never]), + allowed_sandbox_modes: None, + mcp_servers: None, + rules: None, + enforce_residency: None, + }) + }), + ) + .await?; + + assert_eq!( + state.requirements().approval_policy.value(), + AskForApproval::Never + ); + assert_eq!( + state + .requirements() + .approval_policy + .can_set(&AskForApproval::OnRequest), + Err(ConstraintError::InvalidValue { + field_name: "approval_policy", + candidate: "OnRequest".into(), + allowed: "[Never]".into(), + requirement_source: RequirementSource::CloudRequirements, + }) + ); + + Ok(()) +} + #[tokio::test(flavor = "current_thread")] async fn cloud_requirements_are_not_overwritten_by_system_requirements() -> anyhow::Result<()> { let tmp = tempdir()?;