mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
Enforce exact managed config values
This commit is contained in:
@@ -20,6 +20,7 @@ use crate::ConstraintError;
|
||||
use crate::ManagedHooksRequirementsToml;
|
||||
use crate::mcp_types::AppToolApproval;
|
||||
use crate::permissions_toml::PermissionProfileToml;
|
||||
use crate::types::FeedbackConfigToml;
|
||||
use crate::types::WindowsSandboxModeToml;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -142,10 +143,17 @@ impl<T> std::ops::DerefMut for ConstrainedWithSource<T> {
|
||||
/// normalization.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ConfigRequirements {
|
||||
pub sqlite_home: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub log_dir: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub model_catalog_json: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub check_for_update_on_startup: Option<Sourced<bool>>,
|
||||
pub allow_login_shell: Option<Sourced<bool>>,
|
||||
pub feedback: Option<Sourced<FeedbackConfigToml>>,
|
||||
pub approval_policy: ConstrainedWithSource<AskForApproval>,
|
||||
pub approvals_reviewer: ConstrainedWithSource<ApprovalsReviewer>,
|
||||
pub permission_profile: ConstrainedWithSource<PermissionProfile>,
|
||||
pub windows_sandbox_mode: ConstrainedWithSource<Option<WindowsSandboxModeToml>>,
|
||||
pub windows_sandbox_private_desktop: Option<Sourced<bool>>,
|
||||
pub web_search_mode: ConstrainedWithSource<WebSearchMode>,
|
||||
pub allow_managed_hooks_only: Option<Sourced<bool>>,
|
||||
pub allow_appshots: Option<Sourced<bool>>,
|
||||
@@ -168,6 +176,12 @@ pub struct ConfigRequirements {
|
||||
impl Default for ConfigRequirements {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sqlite_home: None,
|
||||
log_dir: None,
|
||||
model_catalog_json: None,
|
||||
check_for_update_on_startup: None,
|
||||
allow_login_shell: None,
|
||||
feedback: None,
|
||||
approval_policy: ConstrainedWithSource::new(
|
||||
Constrained::allow_any_from_default(),
|
||||
/*source*/ None,
|
||||
@@ -184,6 +198,7 @@ impl Default for ConfigRequirements {
|
||||
Constrained::allow_any(/*initial_value*/ None),
|
||||
/*source*/ None,
|
||||
),
|
||||
windows_sandbox_private_desktop: None,
|
||||
web_search_mode: ConstrainedWithSource::new(
|
||||
Constrained::allow_any(WebSearchMode::Cached),
|
||||
/*source*/ None,
|
||||
@@ -716,11 +731,12 @@ impl ComputerUseRequirementsToml {
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct WindowsRequirementsToml {
|
||||
pub allowed_sandbox_implementations: Option<Vec<WindowsSandboxModeToml>>,
|
||||
pub sandbox_private_desktop: Option<bool>,
|
||||
}
|
||||
|
||||
impl WindowsRequirementsToml {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.allowed_sandbox_implementations.is_none()
|
||||
self.allowed_sandbox_implementations.is_none() && self.sandbox_private_desktop.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,6 +837,12 @@ pub(crate) fn merge_app_requirements_descending(
|
||||
/// Base config deserialized from system `requirements.toml` or MDM.
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
|
||||
pub struct ConfigRequirementsToml {
|
||||
pub sqlite_home: Option<AbsolutePathBuf>,
|
||||
pub log_dir: Option<AbsolutePathBuf>,
|
||||
pub model_catalog_json: Option<AbsolutePathBuf>,
|
||||
pub check_for_update_on_startup: Option<bool>,
|
||||
pub allow_login_shell: Option<bool>,
|
||||
pub feedback: Option<FeedbackConfigToml>,
|
||||
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
|
||||
pub allowed_approvals_reviewers: Option<Vec<ApprovalsReviewer>>,
|
||||
pub allowed_sandbox_modes: Option<Vec<SandboxModeRequirement>>,
|
||||
@@ -877,6 +899,12 @@ impl<T> std::ops::Deref for Sourced<T> {
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct ConfigRequirementsWithSources {
|
||||
pub sqlite_home: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub log_dir: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub model_catalog_json: Option<Sourced<AbsolutePathBuf>>,
|
||||
pub check_for_update_on_startup: Option<Sourced<bool>>,
|
||||
pub allow_login_shell: Option<Sourced<bool>>,
|
||||
pub feedback: Option<Sourced<FeedbackConfigToml>>,
|
||||
pub allowed_approval_policies: Option<Sourced<Vec<AskForApproval>>>,
|
||||
pub allowed_approvals_reviewers: Option<Sourced<Vec<ApprovalsReviewer>>>,
|
||||
pub allowed_sandbox_modes: Option<Sourced<Vec<SandboxModeRequirement>>>,
|
||||
@@ -919,6 +947,12 @@ impl ConfigRequirementsWithSources {
|
||||
// Destructure without `..` so adding fields to `ConfigRequirementsToml`
|
||||
// forces this merge logic to be updated.
|
||||
let ConfigRequirementsToml {
|
||||
sqlite_home: _,
|
||||
log_dir: _,
|
||||
model_catalog_json: _,
|
||||
check_for_update_on_startup: _,
|
||||
allow_login_shell: _,
|
||||
feedback: _,
|
||||
allowed_approval_policies: _,
|
||||
allowed_approvals_reviewers: _,
|
||||
allowed_sandbox_modes: _,
|
||||
@@ -956,6 +990,12 @@ impl ConfigRequirementsWithSources {
|
||||
other,
|
||||
source,
|
||||
{
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
check_for_update_on_startup,
|
||||
allow_login_shell,
|
||||
feedback,
|
||||
allowed_approval_policies,
|
||||
allowed_approvals_reviewers,
|
||||
allowed_sandbox_modes,
|
||||
@@ -990,6 +1030,12 @@ impl ConfigRequirementsWithSources {
|
||||
|
||||
pub fn into_toml(self) -> ConfigRequirementsToml {
|
||||
let ConfigRequirementsWithSources {
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
check_for_update_on_startup,
|
||||
allow_login_shell,
|
||||
feedback,
|
||||
allowed_approval_policies,
|
||||
allowed_approvals_reviewers,
|
||||
allowed_sandbox_modes,
|
||||
@@ -1013,6 +1059,12 @@ impl ConfigRequirementsWithSources {
|
||||
guardian_policy_config,
|
||||
} = self;
|
||||
ConfigRequirementsToml {
|
||||
sqlite_home: sqlite_home.map(|sourced| sourced.value),
|
||||
log_dir: log_dir.map(|sourced| sourced.value),
|
||||
model_catalog_json: model_catalog_json.map(|sourced| sourced.value),
|
||||
check_for_update_on_startup: check_for_update_on_startup.map(|sourced| sourced.value),
|
||||
allow_login_shell: allow_login_shell.map(|sourced| sourced.value),
|
||||
feedback: feedback.map(|sourced| sourced.value),
|
||||
allowed_approval_policies: allowed_approval_policies.map(|sourced| sourced.value),
|
||||
allowed_approvals_reviewers: allowed_approvals_reviewers.map(|sourced| sourced.value),
|
||||
allowed_sandbox_modes: allowed_sandbox_modes.map(|sourced| sourced.value),
|
||||
@@ -1103,7 +1155,16 @@ impl ConfigRequirementsToml {
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.allowed_approval_policies.is_none()
|
||||
self.sqlite_home.is_none()
|
||||
&& self.log_dir.is_none()
|
||||
&& self.model_catalog_json.is_none()
|
||||
&& self.check_for_update_on_startup.is_none()
|
||||
&& self.allow_login_shell.is_none()
|
||||
&& self
|
||||
.feedback
|
||||
.as_ref()
|
||||
.is_none_or(|feedback| feedback == &FeedbackConfigToml::default())
|
||||
&& self.allowed_approval_policies.is_none()
|
||||
&& self.allowed_approvals_reviewers.is_none()
|
||||
&& self.allowed_sandbox_modes.is_none()
|
||||
&& self.allowed_permission_profiles.is_none()
|
||||
@@ -1157,6 +1218,12 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
// config loading and requirements API projection. The normalized
|
||||
// constraints below only need the compiled PermissionProfile envelope.
|
||||
let ConfigRequirementsWithSources {
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
check_for_update_on_startup,
|
||||
allow_login_shell,
|
||||
feedback,
|
||||
allowed_approval_policies,
|
||||
allowed_approvals_reviewers,
|
||||
allowed_sandbox_modes,
|
||||
@@ -1277,42 +1344,60 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
/*source*/ None,
|
||||
),
|
||||
};
|
||||
let windows_sandbox_mode = match windows {
|
||||
let (windows_sandbox_mode, windows_sandbox_private_desktop) = match windows {
|
||||
Some(Sourced {
|
||||
value:
|
||||
WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(implementations),
|
||||
allowed_sandbox_implementations,
|
||||
sandbox_private_desktop,
|
||||
},
|
||||
source: requirement_source,
|
||||
}) => {
|
||||
if implementations.is_empty() {
|
||||
return Err(ConstraintError::empty_field(
|
||||
"windows.allowed_sandbox_implementations",
|
||||
));
|
||||
}
|
||||
// Prefer elevated when both Windows sandbox implementations are allowed.
|
||||
let initial_value = if implementations.contains(&WindowsSandboxModeToml::Elevated) {
|
||||
WindowsSandboxModeToml::Elevated
|
||||
} else {
|
||||
WindowsSandboxModeToml::Unelevated
|
||||
};
|
||||
let sandbox_private_desktop = sandbox_private_desktop
|
||||
.map(|value| Sourced::new(value, requirement_source.clone()));
|
||||
let sandbox_mode = match allowed_sandbox_implementations {
|
||||
Some(implementations) => {
|
||||
if implementations.is_empty() {
|
||||
return Err(ConstraintError::empty_field(
|
||||
"windows.allowed_sandbox_implementations",
|
||||
));
|
||||
}
|
||||
// Prefer elevated when both Windows sandbox implementations are allowed.
|
||||
let initial_value =
|
||||
if implementations.contains(&WindowsSandboxModeToml::Elevated) {
|
||||
WindowsSandboxModeToml::Elevated
|
||||
} else {
|
||||
WindowsSandboxModeToml::Unelevated
|
||||
};
|
||||
|
||||
let requirement_source_for_error = requirement_source.clone();
|
||||
let constrained =
|
||||
Constrained::new(Some(initial_value), move |candidate| match candidate {
|
||||
Some(candidate) if implementations.contains(candidate) => Ok(()),
|
||||
_ => Err(ConstraintError::InvalidValue {
|
||||
field_name: "windows.sandbox",
|
||||
candidate: format!("{candidate:?}"),
|
||||
allowed: format!("{implementations:?}"),
|
||||
requirement_source: requirement_source_for_error.clone(),
|
||||
}),
|
||||
})?;
|
||||
ConstrainedWithSource::new(constrained, Some(requirement_source))
|
||||
let requirement_source_for_error = requirement_source.clone();
|
||||
let constrained = Constrained::new(
|
||||
Some(initial_value),
|
||||
move |candidate| match candidate {
|
||||
Some(candidate) if implementations.contains(candidate) => Ok(()),
|
||||
_ => Err(ConstraintError::InvalidValue {
|
||||
field_name: "windows.sandbox",
|
||||
candidate: format!("{candidate:?}"),
|
||||
allowed: format!("{implementations:?}"),
|
||||
requirement_source: requirement_source_for_error.clone(),
|
||||
}),
|
||||
},
|
||||
)?;
|
||||
ConstrainedWithSource::new(constrained, Some(requirement_source))
|
||||
}
|
||||
None => ConstrainedWithSource::new(
|
||||
Constrained::allow_any(/*initial_value*/ None),
|
||||
/*source*/ None,
|
||||
),
|
||||
};
|
||||
(sandbox_mode, sandbox_private_desktop)
|
||||
}
|
||||
Some(_) | None => ConstrainedWithSource::new(
|
||||
Constrained::allow_any(/*initial_value*/ None),
|
||||
/*source*/ None,
|
||||
None => (
|
||||
ConstrainedWithSource::new(
|
||||
Constrained::allow_any(/*initial_value*/ None),
|
||||
/*source*/ None,
|
||||
),
|
||||
None,
|
||||
),
|
||||
};
|
||||
let exec_policy = match rules {
|
||||
@@ -1437,10 +1522,17 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
});
|
||||
let guardian_policy_config_source = guardian_policy_config.map(|sourced| sourced.source);
|
||||
Ok(ConfigRequirements {
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
check_for_update_on_startup,
|
||||
allow_login_shell,
|
||||
feedback,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile,
|
||||
windows_sandbox_mode,
|
||||
windows_sandbox_private_desktop,
|
||||
web_search_mode,
|
||||
allow_managed_hooks_only,
|
||||
allow_appshots,
|
||||
|
||||
@@ -173,6 +173,12 @@ fn populate_merged_regular_fields_with_sources(
|
||||
// Destructure without `..` so every new requirements field must choose
|
||||
// whether it belongs in the regular TOML merge path or in a special merger.
|
||||
let ConfigRequirementsToml {
|
||||
sqlite_home,
|
||||
log_dir,
|
||||
model_catalog_json,
|
||||
check_for_update_on_startup,
|
||||
allow_login_shell,
|
||||
feedback,
|
||||
allowed_approval_policies,
|
||||
allowed_approvals_reviewers,
|
||||
allowed_sandbox_modes,
|
||||
@@ -197,6 +203,15 @@ fn populate_merged_regular_fields_with_sources(
|
||||
guardian_policy_config,
|
||||
} = requirements;
|
||||
|
||||
set_sourced!(sqlite_home, &["sqlite_home"]);
|
||||
set_sourced!(log_dir, &["log_dir"]);
|
||||
set_sourced!(model_catalog_json, &["model_catalog_json"]);
|
||||
set_sourced!(
|
||||
check_for_update_on_startup,
|
||||
&["check_for_update_on_startup"]
|
||||
);
|
||||
set_sourced!(allow_login_shell, &["allow_login_shell"]);
|
||||
set_sourced!(feedback, &["feedback"]);
|
||||
set_sourced!(allowed_approval_policies, &["allowed_approval_policies"]);
|
||||
set_sourced!(
|
||||
allowed_approvals_reviewers,
|
||||
|
||||
@@ -8330,6 +8330,12 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset()
|
||||
let fixture = create_test_fixture()?;
|
||||
|
||||
let requirements_toml = codex_config::ConfigRequirementsToml {
|
||||
sqlite_home: None,
|
||||
log_dir: None,
|
||||
model_catalog_json: None,
|
||||
check_for_update_on_startup: None,
|
||||
allow_login_shell: None,
|
||||
feedback: None,
|
||||
allowed_approval_policies: None,
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
@@ -10848,3 +10854,77 @@ fn test_tui_notification_condition_rejects_unknown_value() {
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
async fn load_with_enterprise_requirement(
|
||||
codex_home: &TempDir,
|
||||
requirements: impl Into<String>,
|
||||
) -> std::io::Result<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(requirements),
|
||||
)
|
||||
.build()
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn exact_requirements_override_only_managed_leaves() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let catalog_path = codex_home.path().join("required-models.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"),
|
||||
)?;
|
||||
std::fs::write(
|
||||
codex_home.path().join(CONFIG_TOML_FILE),
|
||||
r#"
|
||||
check_for_update_on_startup = true
|
||||
allow_login_shell = true
|
||||
|
||||
[feedback]
|
||||
enabled = true
|
||||
|
||||
[windows]
|
||||
sandbox_private_desktop = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let required_sqlite_home = codex_home.path().join("required-state");
|
||||
let required_log_dir = codex_home.path().join("required-logs");
|
||||
let requirements = format!(
|
||||
r#"
|
||||
sqlite_home = {:?}
|
||||
log_dir = {:?}
|
||||
model_catalog_json = {:?}
|
||||
check_for_update_on_startup = false
|
||||
allow_login_shell = false
|
||||
|
||||
[feedback]
|
||||
enabled = false
|
||||
|
||||
[windows]
|
||||
sandbox_private_desktop = false
|
||||
"#,
|
||||
required_sqlite_home.display(),
|
||||
required_log_dir.display(),
|
||||
catalog_path.display(),
|
||||
);
|
||||
let config = load_with_enterprise_requirement(&codex_home, requirements).await?;
|
||||
|
||||
assert_eq!(config.sqlite_home, required_sqlite_home);
|
||||
assert_eq!(config.log_dir, required_log_dir);
|
||||
assert_eq!(config.model_catalog, Some(catalog));
|
||||
assert!(!config.check_for_update_on_startup);
|
||||
assert!(!config.permissions.allow_login_shell);
|
||||
assert!(!config.feedback_enabled);
|
||||
assert!(!config.permissions.windows_sandbox_private_desktop);
|
||||
assert!(config.startup_warnings.iter().any(|warning| {
|
||||
warning.contains("Configured value for `check_for_update_on_startup` is overridden")
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ mod managed_features;
|
||||
mod network_proxy_spec;
|
||||
mod otel;
|
||||
mod permissions;
|
||||
mod requirements;
|
||||
mod resolved_permission_profile;
|
||||
#[cfg(test)]
|
||||
mod schema;
|
||||
@@ -2642,7 +2643,7 @@ impl Config {
|
||||
|
||||
pub(crate) async fn load_config_with_layer_stack(
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
cfg: ConfigToml,
|
||||
mut cfg: ConfigToml,
|
||||
overrides: ConfigOverrides,
|
||||
codex_home: AbsolutePathBuf,
|
||||
config_layer_stack: ConfigLayerStack,
|
||||
@@ -2658,13 +2659,30 @@ impl Config {
|
||||
|
||||
validate_model_providers(&cfg.model_providers)
|
||||
.map_err(|message| std::io::Error::new(std::io::ErrorKind::InvalidInput, message))?;
|
||||
// Ensure that every field of ConfigRequirements is applied to the final
|
||||
// Config.
|
||||
let mut startup_warnings = config_layer_stack
|
||||
.startup_warnings()
|
||||
.unwrap_or_default()
|
||||
.to_vec();
|
||||
requirements::apply_to_config(
|
||||
&mut cfg,
|
||||
config_layer_stack.requirements(),
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
|
||||
// Destructure every field to ensure ConfigRequirements additions are
|
||||
// either applied above or handled while constructing the final Config.
|
||||
let ConfigRequirements {
|
||||
sqlite_home: _,
|
||||
log_dir: _,
|
||||
model_catalog_json: _,
|
||||
check_for_update_on_startup: _,
|
||||
allow_login_shell: _,
|
||||
feedback: _,
|
||||
approval_policy: mut constrained_approval_policy,
|
||||
approvals_reviewer: mut constrained_approvals_reviewer,
|
||||
permission_profile: mut constrained_permission_profile,
|
||||
windows_sandbox_mode: mut constrained_windows_sandbox_mode,
|
||||
windows_sandbox_private_desktop: _,
|
||||
web_search_mode: mut constrained_web_search_mode,
|
||||
allow_managed_hooks_only: _,
|
||||
allow_appshots: _,
|
||||
@@ -2681,11 +2699,6 @@ impl Config {
|
||||
guardian_policy_config_source: _,
|
||||
} = config_layer_stack.requirements().clone();
|
||||
|
||||
let mut startup_warnings = config_layer_stack
|
||||
.startup_warnings()
|
||||
.unwrap_or_default()
|
||||
.to_vec();
|
||||
|
||||
// Destructure ConfigOverrides fully to ensure all overrides are applied.
|
||||
let ConfigOverrides {
|
||||
model,
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
use codex_config::ConfigRequirements;
|
||||
use codex_config::RequirementSource;
|
||||
use codex_config::Sourced;
|
||||
use codex_config::config_toml::ConfigToml;
|
||||
use codex_config::types::FeedbackConfigToml;
|
||||
|
||||
/// Applies managed requirements to regular config before final config construction.
|
||||
///
|
||||
/// Managed values replace their configured counterparts, and conflicts produce
|
||||
/// source-aware startup warnings.
|
||||
pub(super) fn apply_to_config(
|
||||
config: &mut ConfigToml,
|
||||
requirements: &ConfigRequirements,
|
||||
startup_warnings: &mut Vec<String>,
|
||||
) -> std::io::Result<()> {
|
||||
macro_rules! apply_exact {
|
||||
($field:ident) => {
|
||||
apply_exact_requirement(
|
||||
stringify!($field),
|
||||
&mut config.$field,
|
||||
requirements.$field.as_ref(),
|
||||
startup_warnings,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
apply_exact!(sqlite_home);
|
||||
apply_exact!(log_dir);
|
||||
apply_exact!(model_catalog_json);
|
||||
apply_exact!(check_for_update_on_startup);
|
||||
apply_exact!(allow_login_shell);
|
||||
apply_feedback_requirement(
|
||||
&mut config.feedback,
|
||||
requirements.feedback.as_ref(),
|
||||
startup_warnings,
|
||||
);
|
||||
if let Some(requirement) = requirements.windows_sandbox_private_desktop.as_ref() {
|
||||
apply_exact_requirement(
|
||||
"windows.sandbox_private_desktop",
|
||||
&mut config
|
||||
.windows
|
||||
.get_or_insert_default()
|
||||
.sandbox_private_desktop,
|
||||
Some(requirement),
|
||||
startup_warnings,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_exact_requirement<T>(
|
||||
field_name: &'static str,
|
||||
configured_value: &mut Option<T>,
|
||||
requirement: Option<&Sourced<T>>,
|
||||
startup_warnings: &mut Vec<String>,
|
||||
) where
|
||||
T: Clone + PartialEq + std::fmt::Debug,
|
||||
{
|
||||
let Some(Sourced { value, source }) = requirement else {
|
||||
return;
|
||||
};
|
||||
if configured_value
|
||||
.as_ref()
|
||||
.is_some_and(|configured| configured != value)
|
||||
{
|
||||
tracing::warn!(
|
||||
?source,
|
||||
?value,
|
||||
"configured value is overridden by an exact requirement for {field_name}"
|
||||
);
|
||||
startup_warnings.push(format!(
|
||||
"Configured value for `{field_name}` is overridden by the required value {value:?} from {source}."
|
||||
));
|
||||
}
|
||||
*configured_value = Some(value.clone());
|
||||
}
|
||||
|
||||
pub(super) fn replace_required_leaf<T: Clone + PartialEq>(
|
||||
configured: &mut Option<T>,
|
||||
required: &Option<T>,
|
||||
) -> bool {
|
||||
let Some(required) = required else {
|
||||
return false;
|
||||
};
|
||||
let conflict = configured
|
||||
.as_ref()
|
||||
.is_some_and(|configured| configured != required);
|
||||
*configured = Some(required.clone());
|
||||
conflict
|
||||
}
|
||||
|
||||
fn apply_feedback_requirement(
|
||||
configured: &mut Option<FeedbackConfigToml>,
|
||||
requirement: Option<&Sourced<FeedbackConfigToml>>,
|
||||
startup_warnings: &mut Vec<String>,
|
||||
) {
|
||||
let Some(Sourced { value, source }) = requirement else {
|
||||
return;
|
||||
};
|
||||
let FeedbackConfigToml { enabled } = value;
|
||||
let configured = configured.get_or_insert_default();
|
||||
let conflict = replace_required_leaf(&mut configured.enabled, enabled);
|
||||
push_structured_requirement_override_warning("feedback", conflict, source, startup_warnings);
|
||||
}
|
||||
|
||||
/// Emits one source-aware warning when a structured requirement replaces one
|
||||
/// or more configured values.
|
||||
pub(super) fn push_structured_requirement_override_warning(
|
||||
field_name: &str,
|
||||
conflict: bool,
|
||||
source: &RequirementSource,
|
||||
startup_warnings: &mut Vec<String>,
|
||||
) {
|
||||
if !conflict {
|
||||
return;
|
||||
}
|
||||
tracing::warn!(
|
||||
?source,
|
||||
"configured values are overridden by requirements for {field_name}"
|
||||
);
|
||||
startup_warnings.push(format!(
|
||||
"Configured values under `{field_name}` are overridden by requirements from {source}."
|
||||
));
|
||||
}
|
||||
@@ -54,6 +54,7 @@ fn windows_sandbox_requirements_stack(
|
||||
let requirements_toml = codex_config::ConfigRequirementsToml {
|
||||
windows: Some(codex_config::WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(allowed_sandbox_implementations),
|
||||
sandbox_private_desktop: None,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -790,6 +790,7 @@ mod tests {
|
||||
enforce_residency: Some(ResidencyRequirement::Us),
|
||||
network: None,
|
||||
permissions: None,
|
||||
..ConfigRequirementsToml::default()
|
||||
};
|
||||
|
||||
let user_file = if cfg!(windows) {
|
||||
@@ -1147,6 +1148,7 @@ approval_policy = "never"
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
permissions: None,
|
||||
..ConfigRequirementsToml::default()
|
||||
};
|
||||
|
||||
let stack = ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)
|
||||
|
||||
@@ -1124,7 +1124,12 @@ pub async fn run_main(
|
||||
.config_layer_stack
|
||||
.effective_config()
|
||||
.as_table()
|
||||
.is_some_and(|table| table.contains_key("log_dir"));
|
||||
.is_some_and(|table| table.contains_key("log_dir"))
|
||||
|| config
|
||||
.config_layer_stack
|
||||
.requirements_toml()
|
||||
.log_dir
|
||||
.is_some();
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
match check_execpolicy_for_warnings(&config.config_layer_stack).await {
|
||||
|
||||
Reference in New Issue
Block a user