Skip to content
114 changes: 108 additions & 6 deletions codex-rs/config/src/config_requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,61 @@ use crate::types::WindowsSandboxModeToml;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RequirementSource {
Unknown,
MdmManagedPreferences { domain: String, key: String },
MdmManagedPreferences {
domain: String,
key: String,
},
CloudRequirements,
SystemRequirementsToml { file: AbsolutePathBuf },
LegacyManagedConfigTomlFromFile { file: AbsolutePathBuf },
/// Multiple requirements layers contributed to the final value. Sources are
/// stored highest-priority first, matching the order surfaced in errors.
Composite {
sources: Vec<RequirementSource>,
},
/// A backend-delivered enterprise-managed layer. `id` is the stable backend
/// identifier; `name` is the admin-facing display name.
EnterpriseManaged {
id: String,
name: String,
},
SystemRequirementsToml {
file: AbsolutePathBuf,
},
LegacyManagedConfigTomlFromFile {
file: AbsolutePathBuf,
},
LegacyManagedConfigTomlFromMdm,
}

impl RequirementSource {
pub fn composite(sources: impl IntoIterator<Item = RequirementSource>) -> Self {
let mut flattened = Vec::new();
for source in sources {
source.append_to_composite(&mut flattened);
}

match flattened.len() {
0 => RequirementSource::Unknown,
1 => flattened.remove(0),
_ => RequirementSource::Composite { sources: flattened },
}
}

fn append_to_composite(self, flattened: &mut Vec<RequirementSource>) {
match self {
RequirementSource::Composite { sources } => {
for source in sources {
source.append_to_composite(flattened);
}
}
source => {
if !flattened.contains(&source) {
flattened.push(source);
}
}
}
}
}

impl fmt::Display for RequirementSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expand All @@ -42,6 +90,19 @@ impl fmt::Display for RequirementSource {
RequirementSource::CloudRequirements => {
write!(f, "cloud requirements")
}
RequirementSource::Composite { sources } => {
write!(f, "requirements layers: ")?;
for (index, source) in sources.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{source}")?;
}
Ok(())
}
RequirementSource::EnterpriseManaged { id, name } => {
write!(f, "enterprise-managed requirements {name} ({id})")
}
RequirementSource::SystemRequirementsToml { file } => {
write!(f, "{}", file.as_path().display())
}
Expand Down Expand Up @@ -983,7 +1044,7 @@ fn hostname_matches_any_pattern(hostname: &str, patterns: &[String]) -> bool {

/// Currently, `external-sandbox` is not supported in config.toml, but it is
/// supported through programmatic use.
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
Comment thread
joeflorencio-openai marked this conversation as resolved.
pub enum SandboxModeRequirement {
#[serde(rename = "read-only")]
ReadOnly,
Expand Down Expand Up @@ -1430,6 +1491,41 @@ mod tests {
)?)
}

#[test]
fn composite_requirement_source_flattens_and_deduplicates_sources() {
let mdm_source = RequirementSource::MdmManagedPreferences {
domain: "com.openai.codex".to_string(),
key: "requirements_toml_base64".to_string(),
};
let legacy_source = RequirementSource::LegacyManagedConfigTomlFromMdm;

assert_eq!(
RequirementSource::composite([
mdm_source.clone(),
RequirementSource::composite([legacy_source.clone(), mdm_source.clone()]),
]),
RequirementSource::Composite {
sources: vec![mdm_source, legacy_source],
}
);
}

#[test]
fn composite_requirement_source_display_lists_sources_in_priority_order() {
let source = RequirementSource::composite([
RequirementSource::MdmManagedPreferences {
domain: "com.openai.codex".to_string(),
key: "requirements_toml_base64".to_string(),
},
RequirementSource::LegacyManagedConfigTomlFromMdm,
]);

assert_eq!(
source.to_string(),
"requirements layers: MDM com.openai.codex:requirements_toml_base64, MDM managed_config.toml (legacy)"
);
}

fn with_unknown_source(toml: ConfigRequirementsToml) -> ConfigRequirementsWithSources {
let ConfigRequirementsToml {
allowed_approval_policies,
Expand Down Expand Up @@ -2273,14 +2369,20 @@ allowed_approvals_reviewers = ["user"]
}

#[test]
fn constraint_error_includes_cloud_requirements_source() -> Result<()> {
fn constraint_error_includes_composite_requirement_source() -> Result<()> {
let source: ConfigRequirementsToml = from_str(
r#"
allowed_approval_policies = ["on-request"]
"#,
)?;

let source_location = RequirementSource::CloudRequirements;
let source_location = RequirementSource::composite([
RequirementSource::MdmManagedPreferences {
domain: "com.openai.codex".to_string(),
key: "requirements_toml_base64".to_string(),
},
RequirementSource::LegacyManagedConfigTomlFromMdm,
]);

let mut target = ConfigRequirementsWithSources::default();
target.merge_unset_fields(source_location.clone(), source);
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod plugin_edit;
pub mod profile_toml;
mod project_root_markers;
mod requirements_exec_policy;
mod requirements_layers;
pub mod schema;
mod skills_config;
mod state;
Expand Down Expand Up @@ -117,6 +118,8 @@ pub use requirements_exec_policy::RequirementsExecPolicyParseError;
pub use requirements_exec_policy::RequirementsExecPolicyPatternTokenToml;
pub use requirements_exec_policy::RequirementsExecPolicyPrefixRuleToml;
pub use requirements_exec_policy::RequirementsExecPolicyToml;
pub use requirements_layers::RequirementsLayerEntry;
pub use requirements_layers::compose_requirements;
pub use skills_config::BundledSkillsConfig;
pub use skills_config::SkillConfig;
pub use skills_config::SkillsConfig;
Expand Down
Loading
Loading