config requirements: improve requirement error messages (#8843)

**Before:**
```
Error loading configuration: value `Never` is not in the allowed set [OnRequest]
```

**After:**
```
Error loading configuration: invalid value for `approval_policy`: `Never` is not in the
allowed set [OnRequest] (set by MDM com.openai.codex:requirements_toml_base64)
```

Done by introducing a new struct `ConfigRequirementsWithSources` onto
which we `merge_unset_fields` now. Also introduces a pair of requirement
value and its `RequirementSource` (inspired by `ConfigLayerSource`):

```rust
pub struct Sourced<T> {
    pub value: T,
    pub source: RequirementSource,
}
```
This commit is contained in:
gt-oai
2026-01-08 16:11:14 +00:00
committed by GitHub
parent 484f6f4c26
commit 932a5a446f
6 changed files with 378 additions and 140 deletions

View File

@@ -12,6 +12,7 @@ mod tests;
use crate::config::CONFIG_TOML_FILE;
use crate::config::ConfigToml;
use crate::config_loader::config_requirements::ConfigRequirementsWithSources;
use crate::config_loader::layer_io::LoadedConfigLayers;
use codex_app_server_protocol::ConfigLayerSource;
use codex_protocol::config_types::SandboxMode;
@@ -25,6 +26,7 @@ use toml::Value as TomlValue;
pub use config_requirements::ConfigRequirements;
pub use config_requirements::ConfigRequirementsToml;
pub use config_requirements::RequirementSource;
pub use config_requirements::SandboxModeRequirement;
pub use merge::merge_toml_values;
pub use state::ConfigLayerEntry;
@@ -77,7 +79,7 @@ pub async fn load_config_layers_state(
cli_overrides: &[(String, TomlValue)],
overrides: LoaderOverrides,
) -> io::Result<ConfigLayerStack> {
let mut config_requirements_toml = ConfigRequirementsToml::default();
let mut config_requirements_toml = ConfigRequirementsWithSources::default();
#[cfg(target_os = "macos")]
macos::load_managed_admin_requirements_toml(
@@ -202,9 +204,11 @@ pub async fn load_config_layers_state(
));
}
let requirements_toml = config_requirements_toml.clone();
let requirements = config_requirements_toml.try_into()?;
ConfigLayerStack::new(layers, requirements, requirements_toml)
ConfigLayerStack::new(
layers,
config_requirements_toml.clone().try_into()?,
config_requirements_toml.into_toml(),
)
}
/// Attempts to load a config.toml file from `config_toml`.
@@ -256,9 +260,11 @@ async fn load_config_toml_for_required_layer(
/// If available, apply requirements from `/etc/codex/requirements.toml` to
/// `config_requirements_toml` by filling in any unset fields.
async fn load_requirements_toml(
config_requirements_toml: &mut ConfigRequirementsToml,
config_requirements_toml: &mut ConfigRequirementsWithSources,
requirements_toml_file: impl AsRef<Path>,
) -> io::Result<()> {
let requirements_toml_file =
AbsolutePathBuf::from_absolute_path(requirements_toml_file.as_ref())?;
match tokio::fs::read_to_string(&requirements_toml_file).await {
Ok(contents) => {
let requirements_config: ConfigRequirementsToml =
@@ -271,7 +277,12 @@ async fn load_requirements_toml(
),
)
})?;
config_requirements_toml.merge_unset_fields(requirements_config);
config_requirements_toml.merge_unset_fields(
RequirementSource::SystemRequirementsToml {
file: requirements_toml_file.clone(),
},
requirements_config,
);
}
Err(e) => {
if e.kind() != io::ErrorKind::NotFound {
@@ -290,7 +301,7 @@ async fn load_requirements_toml(
}
async fn load_requirements_from_legacy_scheme(
config_requirements_toml: &mut ConfigRequirementsToml,
config_requirements_toml: &mut ConfigRequirementsWithSources,
loaded_config_layers: LoadedConfigLayers,
) -> io::Result<()> {
// In this implementation, earlier layers cannot be overwritten by later
@@ -300,12 +311,16 @@ async fn load_requirements_from_legacy_scheme(
managed_config,
managed_config_from_mdm,
} = loaded_config_layers;
for config in [
managed_config_from_mdm,
managed_config.map(|c| c.managed_config),
]
.into_iter()
.flatten()
for (source, config) in managed_config_from_mdm
.map(|config| (RequirementSource::LegacyManagedConfigTomlFromMdm, config))
.into_iter()
.chain(managed_config.map(|c| {
(
RequirementSource::LegacyManagedConfigTomlFromFile { file: c.file },
c.managed_config,
)
}))
{
let legacy_config: LegacyManagedConfigToml =
config.try_into().map_err(|err: toml::de::Error| {
@@ -316,7 +331,7 @@ async fn load_requirements_from_legacy_scheme(
})?;
let new_requirements_toml = ConfigRequirementsToml::from(legacy_config);
config_requirements_toml.merge_unset_fields(new_requirements_toml);
config_requirements_toml.merge_unset_fields(source, new_requirements_toml);
}
Ok(())