use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use anyhow::anyhow; use codex_app_server_protocol::ConfigBatchWriteParams; use codex_app_server_protocol::ConfigLayer; use codex_app_server_protocol::ConfigLayerMetadata; use codex_app_server_protocol::ConfigLayerName; use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigReadResponse; use codex_app_server_protocol::ConfigValueWriteParams; use codex_app_server_protocol::ConfigWriteErrorCode; use codex_app_server_protocol::ConfigWriteResponse; use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::MergeStrategy; use codex_app_server_protocol::OverriddenMetadata; use codex_app_server_protocol::WriteStatus; use codex_core::config::ConfigToml; use codex_core::config_loader::LoadedConfigLayers; use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::load_config_layers_with_overrides; use codex_core::config_loader::merge_toml_values; use serde_json::Value as JsonValue; use serde_json::json; use sha2::Digest; use sha2::Sha256; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; use tempfile::NamedTempFile; use tokio::task; use toml::Value as TomlValue; const SESSION_FLAGS_SOURCE: &str = "--config"; const MDM_SOURCE: &str = "com.openai.codex/config_toml_base64"; const CONFIG_FILE_NAME: &str = "config.toml"; #[derive(Clone)] pub(crate) struct ConfigApi { codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, } impl ConfigApi { pub(crate) fn new(codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>) -> Self { Self { codex_home, cli_overrides, loader_overrides: LoaderOverrides::default(), } } #[cfg(test)] fn with_overrides( codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, ) -> Self { Self { codex_home, cli_overrides, loader_overrides, } } pub(crate) async fn read( &self, params: ConfigReadParams, ) -> Result { let layers = self .load_layers_state() .await .map_err(|err| internal_error("failed to read configuration layers", err))?; let effective = layers.effective_config(); validate_config(&effective).map_err(|err| internal_error("invalid configuration", err))?; let response = ConfigReadResponse { config: to_json_value(&effective), origins: layers.origins(), layers: params.include_layers.then(|| layers.layers_high_to_low()), }; Ok(response) } pub(crate) async fn write_value( &self, params: ConfigValueWriteParams, ) -> Result { let edits = vec![(params.key_path, params.value, params.merge_strategy)]; self.apply_edits(params.file_path, params.expected_version, edits) .await } pub(crate) async fn batch_write( &self, params: ConfigBatchWriteParams, ) -> Result { let edits = params .edits .into_iter() .map(|edit| (edit.key_path, edit.value, edit.merge_strategy)) .collect(); self.apply_edits(params.file_path, params.expected_version, edits) .await } async fn apply_edits( &self, file_path: Option, expected_version: Option, edits: Vec<(String, JsonValue, MergeStrategy)>, ) -> Result { let allowed_path = self.codex_home.join(CONFIG_FILE_NAME); let provided_path = file_path .as_ref() .map(PathBuf::from) .unwrap_or_else(|| allowed_path.clone()); if !paths_match(&allowed_path, &provided_path) { return Err(config_write_error( ConfigWriteErrorCode::ConfigLayerReadonly, "Only writes to the user config are allowed", )); } let layers = self .load_layers_state() .await .map_err(|err| internal_error("failed to load configuration", err))?; if let Some(expected) = expected_version.as_deref() && expected != layers.user.version { return Err(config_write_error( ConfigWriteErrorCode::ConfigVersionConflict, "Configuration was modified since last read. Fetch latest version and retry.", )); } let mut user_config = layers.user.config.clone(); let mut mutated = false; let mut parsed_segments = Vec::new(); for (key_path, value, strategy) in edits.into_iter() { let segments = parse_key_path(&key_path).map_err(|message| { config_write_error(ConfigWriteErrorCode::ConfigValidationError, message) })?; let parsed_value = parse_value(value).map_err(|message| { config_write_error(ConfigWriteErrorCode::ConfigValidationError, message) })?; let changed = apply_merge(&mut user_config, &segments, parsed_value.as_ref(), strategy) .map_err(|err| match err { MergeError::PathNotFound => config_write_error( ConfigWriteErrorCode::ConfigPathNotFound, "Path not found", ), MergeError::Validation(message) => { config_write_error(ConfigWriteErrorCode::ConfigValidationError, message) } })?; mutated |= changed; parsed_segments.push(segments); } validate_config(&user_config).map_err(|err| { config_write_error( ConfigWriteErrorCode::ConfigValidationError, format!("Invalid configuration: {err}"), ) })?; let updated_layers = layers.with_user_config(user_config.clone()); let effective = updated_layers.effective_config(); validate_config(&effective).map_err(|err| { config_write_error( ConfigWriteErrorCode::ConfigValidationError, format!("Invalid configuration: {err}"), ) })?; if mutated { self.persist_user_config(&user_config) .await .map_err(|err| internal_error("failed to persist config.toml", err))?; } let overridden = first_overridden_edit(&updated_layers, &effective, &parsed_segments); let status = overridden .as_ref() .map(|_| WriteStatus::OkOverridden) .unwrap_or(WriteStatus::Ok); let file_path = provided_path .canonicalize() .unwrap_or(provided_path.clone()) .display() .to_string(); Ok(ConfigWriteResponse { status, version: updated_layers.user.version.clone(), file_path, overridden_metadata: overridden, }) } async fn load_layers_state(&self) -> std::io::Result { let LoadedConfigLayers { base, managed_config, managed_preferences, } = load_config_layers_with_overrides(&self.codex_home, self.loader_overrides.clone()) .await?; let user = LayerState::new( ConfigLayerName::User, self.codex_home.join(CONFIG_FILE_NAME), base, ); let session_flags = LayerState::new( ConfigLayerName::SessionFlags, PathBuf::from(SESSION_FLAGS_SOURCE), { let mut root = TomlValue::Table(toml::map::Map::new()); for (path, value) in self.cli_overrides.iter() { apply_override(&mut root, path, value.clone()); } root }, ); let system = managed_config.map(|cfg| { LayerState::new( ConfigLayerName::System, system_config_path(&self.codex_home), cfg, ) }); let mdm = managed_preferences .map(|cfg| LayerState::new(ConfigLayerName::Mdm, PathBuf::from(MDM_SOURCE), cfg)); Ok(LayersState { user, session_flags, system, mdm, }) } async fn persist_user_config(&self, user_config: &TomlValue) -> anyhow::Result<()> { let codex_home = self.codex_home.clone(); let serialized = toml::to_string_pretty(user_config)?; task::spawn_blocking(move || -> anyhow::Result<()> { std::fs::create_dir_all(&codex_home)?; let target = codex_home.join(CONFIG_FILE_NAME); let tmp = NamedTempFile::new_in(&codex_home)?; std::fs::write(tmp.path(), serialized.as_bytes())?; tmp.persist(&target)?; Ok(()) }) .await .map_err(|err| anyhow!("config persistence task panicked: {err}"))??; Ok(()) } } fn parse_value(value: JsonValue) -> Result, String> { if value.is_null() { return Ok(None); } serde_json::from_value::(value) .map(Some) .map_err(|err| format!("invalid value: {err}")) } fn parse_key_path(path: &str) -> Result, String> { if path.trim().is_empty() { return Err("keyPath must not be empty".to_string()); } Ok(path .split('.') .map(std::string::ToString::to_string) .collect()) } fn apply_override(target: &mut TomlValue, path: &str, value: TomlValue) { use toml::value::Table; let segments: Vec<&str> = path.split('.').collect(); let mut current = target; for (idx, segment) in segments.iter().enumerate() { let is_last = idx == segments.len() - 1; if is_last { match current { TomlValue::Table(table) => { table.insert(segment.to_string(), value); } _ => { let mut table = Table::new(); table.insert(segment.to_string(), value); *current = TomlValue::Table(table); } } return; } match current { TomlValue::Table(table) => { current = table .entry((*segment).to_string()) .or_insert_with(|| TomlValue::Table(Table::new())); } _ => { *current = TomlValue::Table(Table::new()); if let TomlValue::Table(tbl) = current { current = tbl .entry((*segment).to_string()) .or_insert_with(|| TomlValue::Table(Table::new())); } } } } } #[derive(Debug)] enum MergeError { PathNotFound, Validation(String), } fn apply_merge( root: &mut TomlValue, segments: &[String], value: Option<&TomlValue>, strategy: MergeStrategy, ) -> Result { let Some(value) = value else { return clear_path(root, segments); }; let Some((last, parents)) = segments.split_last() else { return Err(MergeError::Validation( "keyPath must not be empty".to_string(), )); }; let mut current = root; for segment in parents { match current { TomlValue::Table(table) => { current = table .entry(segment.clone()) .or_insert_with(|| TomlValue::Table(toml::map::Map::new())); } _ => { *current = TomlValue::Table(toml::map::Map::new()); if let TomlValue::Table(table) = current { current = table .entry(segment.clone()) .or_insert_with(|| TomlValue::Table(toml::map::Map::new())); } } } } let table = current.as_table_mut().ok_or_else(|| { MergeError::Validation("cannot set value on non-table parent".to_string()) })?; if matches!(strategy, MergeStrategy::Upsert) && let Some(existing) = table.get_mut(last) && matches!(existing, TomlValue::Table(_)) && matches!(value, TomlValue::Table(_)) { merge_toml_values(existing, value); return Ok(true); } let changed = table .get(last) .map(|existing| Some(existing) != Some(value)) .unwrap_or(true); table.insert(last.clone(), value.clone()); Ok(changed) } fn clear_path(root: &mut TomlValue, segments: &[String]) -> Result { let Some((last, parents)) = segments.split_last() else { return Err(MergeError::Validation( "keyPath must not be empty".to_string(), )); }; let mut current = root; for segment in parents { match current { TomlValue::Table(table) => { current = table.get_mut(segment).ok_or(MergeError::PathNotFound)?; } _ => return Err(MergeError::PathNotFound), } } let Some(parent) = current.as_table_mut() else { return Err(MergeError::PathNotFound); }; Ok(parent.remove(last).is_some()) } #[derive(Clone)] struct LayerState { name: ConfigLayerName, source: PathBuf, config: TomlValue, version: String, } impl LayerState { fn new(name: ConfigLayerName, source: PathBuf, config: TomlValue) -> Self { let version = version_for_toml(&config); Self { name, source, config, version, } } fn metadata(&self) -> ConfigLayerMetadata { ConfigLayerMetadata { name: self.name.clone(), source: self.source.display().to_string(), version: self.version.clone(), } } fn as_layer(&self) -> ConfigLayer { ConfigLayer { name: self.name.clone(), source: self.source.display().to_string(), version: self.version.clone(), config: to_json_value(&self.config), } } } #[derive(Clone)] struct LayersState { user: LayerState, session_flags: LayerState, system: Option, mdm: Option, } impl LayersState { fn with_user_config(self, user_config: TomlValue) -> Self { Self { user: LayerState::new(self.user.name, self.user.source, user_config), session_flags: self.session_flags, system: self.system, mdm: self.mdm, } } fn effective_config(&self) -> TomlValue { let mut merged = self.user.config.clone(); merge_toml_values(&mut merged, &self.session_flags.config); if let Some(system) = &self.system { merge_toml_values(&mut merged, &system.config); } if let Some(mdm) = &self.mdm { merge_toml_values(&mut merged, &mdm.config); } merged } fn origins(&self) -> HashMap { let mut origins = HashMap::new(); let mut path = Vec::new(); record_origins( &self.user.config, &self.user.metadata(), &mut path, &mut origins, ); record_origins( &self.session_flags.config, &self.session_flags.metadata(), &mut path, &mut origins, ); if let Some(system) = &self.system { record_origins(&system.config, &system.metadata(), &mut path, &mut origins); } if let Some(mdm) = &self.mdm { record_origins(&mdm.config, &mdm.metadata(), &mut path, &mut origins); } origins } fn layers_high_to_low(&self) -> Vec { let mut layers = Vec::new(); if let Some(mdm) = &self.mdm { layers.push(mdm.as_layer()); } if let Some(system) = &self.system { layers.push(system.as_layer()); } layers.push(self.session_flags.as_layer()); layers.push(self.user.as_layer()); layers } } fn record_origins( value: &TomlValue, meta: &ConfigLayerMetadata, path: &mut Vec, origins: &mut HashMap, ) { match value { TomlValue::Table(table) => { for (key, val) in table { path.push(key.clone()); record_origins(val, meta, path, origins); path.pop(); } } TomlValue::Array(items) => { for (idx, item) in items.iter().enumerate() { path.push(idx.to_string()); record_origins(item, meta, path, origins); path.pop(); } } _ => { if !path.is_empty() { origins.insert(path.join("."), meta.clone()); } } } } fn to_json_value(value: &TomlValue) -> JsonValue { serde_json::to_value(value).unwrap_or(JsonValue::Null) } fn validate_config(value: &TomlValue) -> Result<(), toml::de::Error> { let _: ConfigToml = value.clone().try_into()?; Ok(()) } fn version_for_toml(value: &TomlValue) -> String { let json = to_json_value(value); let canonical = canonical_json(&json); let serialized = serde_json::to_vec(&canonical).unwrap_or_default(); let mut hasher = Sha256::new(); hasher.update(serialized); let hash = hasher.finalize(); let hex = hash .iter() .map(|byte| format!("{byte:02x}")) .collect::(); format!("sha256:{hex}") } fn canonical_json(value: &JsonValue) -> JsonValue { match value { JsonValue::Object(map) => { let mut sorted = serde_json::Map::new(); let mut keys = map.keys().cloned().collect::>(); keys.sort(); for key in keys { if let Some(val) = map.get(&key) { sorted.insert(key, canonical_json(val)); } } JsonValue::Object(sorted) } JsonValue::Array(items) => JsonValue::Array(items.iter().map(canonical_json).collect()), other => other.clone(), } } fn paths_match(expected: &Path, provided: &Path) -> bool { if let (Ok(expanded_expected), Ok(expanded_provided)) = (expected.canonicalize(), provided.canonicalize()) { return expanded_expected == expanded_provided; } expected == provided } fn value_at_path<'a>(root: &'a TomlValue, segments: &[String]) -> Option<&'a TomlValue> { let mut current = root; for segment in segments { match current { TomlValue::Table(table) => { current = table.get(segment)?; } TomlValue::Array(items) => { let idx: usize = segment.parse().ok()?; current = items.get(idx)?; } _ => return None, } } Some(current) } fn override_message(layer: &ConfigLayerName) -> String { match layer { ConfigLayerName::Mdm => "Overridden by managed policy (mdm)".to_string(), ConfigLayerName::System => "Overridden by managed config (system)".to_string(), ConfigLayerName::SessionFlags => "Overridden by session flags".to_string(), ConfigLayerName::User => "Overridden by user config".to_string(), } } fn compute_override_metadata( layers: &LayersState, effective: &TomlValue, segments: &[String], ) -> Option { let user_value = value_at_path(&layers.user.config, segments); let effective_value = value_at_path(effective, segments); if user_value.is_some() && user_value == effective_value { return None; } if user_value.is_none() && effective_value.is_none() { return None; } let effective_layer = find_effective_layer(layers, segments); let overriding_layer = effective_layer.unwrap_or_else(|| layers.user.metadata()); let message = override_message(&overriding_layer.name); Some(OverriddenMetadata { message, overriding_layer, effective_value: effective_value .map(to_json_value) .unwrap_or(JsonValue::Null), }) } fn first_overridden_edit( layers: &LayersState, effective: &TomlValue, edits: &[Vec], ) -> Option { for segments in edits { if let Some(meta) = compute_override_metadata(layers, effective, segments) { return Some(meta); } } None } fn find_effective_layer(layers: &LayersState, segments: &[String]) -> Option { let check = |state: &LayerState| value_at_path(&state.config, segments).map(|_| state.metadata()); if let Some(mdm) = &layers.mdm && let Some(meta) = check(mdm) { return Some(meta); } if let Some(system) = &layers.system && let Some(meta) = check(system) { return Some(meta); } if let Some(meta) = check(&layers.session_flags) { return Some(meta); } check(&layers.user) } fn system_config_path(codex_home: &Path) -> PathBuf { if let Ok(path) = std::env::var("CODEX_MANAGED_CONFIG_PATH") { return PathBuf::from(path); } #[cfg(unix)] { let _ = codex_home; PathBuf::from("/etc/codex/managed_config.toml") } #[cfg(not(unix))] { codex_home.join("managed_config.toml") } } fn internal_error(context: &str, err: E) -> JSONRPCErrorError { JSONRPCErrorError { code: INTERNAL_ERROR_CODE, message: format!("{context}: {err}"), data: None, } } fn config_write_error(code: ConfigWriteErrorCode, message: impl Into) -> JSONRPCErrorError { JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: message.into(), data: Some(json!({ "config_write_error_code": code, })), } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; use tempfile::tempdir; #[tokio::test] async fn read_includes_origins_and_layers() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "model = \"user\"").unwrap(); let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); let api = ConfigApi::with_overrides( tmp.path().to_path_buf(), vec![], LoaderOverrides { managed_config_path: Some(managed_path), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, ); let response = api .read(ConfigReadParams { include_layers: true, }) .await .expect("response"); assert_eq!( response.config.get("approval_policy"), Some(&json!("never")) ); assert_eq!( response .origins .get("approval_policy") .expect("origin") .name, ConfigLayerName::System ); let layers = response.layers.expect("layers present"); assert_eq!(layers.first().unwrap().name, ConfigLayerName::System); assert_eq!(layers.get(1).unwrap().name, ConfigLayerName::SessionFlags); assert_eq!(layers.last().unwrap().name, ConfigLayerName::User); } #[tokio::test] async fn write_value_reports_override() { let tmp = tempdir().expect("tempdir"); std::fs::write( tmp.path().join(CONFIG_FILE_NAME), "approval_policy = \"on-request\"", ) .unwrap(); let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); let api = ConfigApi::with_overrides( tmp.path().to_path_buf(), vec![], LoaderOverrides { managed_config_path: Some(managed_path), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, ); let result = api .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), key_path: "approval_policy".to_string(), value: json!("never"), merge_strategy: MergeStrategy::Replace, expected_version: None, }) .await .expect("result"); let read_after = api .read(ConfigReadParams { include_layers: true, }) .await .expect("read"); let config_object = read_after.config.as_object().expect("object"); assert_eq!(config_object.get("approval_policy"), Some(&json!("never"))); assert_eq!( read_after .origins .get("approval_policy") .expect("origin") .name, ConfigLayerName::System ); assert_eq!(result.status, WriteStatus::Ok); assert!(result.overridden_metadata.is_none()); } #[tokio::test] async fn version_conflict_rejected() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "model = \"user\"").unwrap(); let api = ConfigApi::new(tmp.path().to_path_buf(), vec![]); let error = api .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), key_path: "model".to_string(), value: json!("gpt-5"), merge_strategy: MergeStrategy::Replace, expected_version: Some("sha256:bogus".to_string()), }) .await .expect_err("should fail"); assert_eq!(error.code, INVALID_REQUEST_ERROR_CODE); assert_eq!( error .data .as_ref() .and_then(|d| d.get("config_write_error_code")) .and_then(serde_json::Value::as_str), Some("configVersionConflict") ); } #[tokio::test] async fn write_value_defaults_to_user_config_path() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "").unwrap(); let api = ConfigApi::new(tmp.path().to_path_buf(), vec![]); api.write_value(ConfigValueWriteParams { file_path: None, key_path: "model".to_string(), value: json!("gpt-new"), merge_strategy: MergeStrategy::Replace, expected_version: None, }) .await .expect("write succeeds"); let contents = std::fs::read_to_string(tmp.path().join(CONFIG_FILE_NAME)).expect("read config"); assert!( contents.contains("model = \"gpt-new\""), "config.toml should be updated even when file_path is omitted" ); } #[tokio::test] async fn invalid_user_value_rejected_even_if_overridden_by_managed() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "model = \"user\"").unwrap(); let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); let api = ConfigApi::with_overrides( tmp.path().to_path_buf(), vec![], LoaderOverrides { managed_config_path: Some(managed_path), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, ); let error = api .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), key_path: "approval_policy".to_string(), value: json!("bogus"), merge_strategy: MergeStrategy::Replace, expected_version: None, }) .await .expect_err("should fail validation"); assert_eq!(error.code, INVALID_REQUEST_ERROR_CODE); assert_eq!( error .data .as_ref() .and_then(|d| d.get("config_write_error_code")) .and_then(serde_json::Value::as_str), Some("configValidationError") ); let contents = std::fs::read_to_string(tmp.path().join(CONFIG_FILE_NAME)).expect("read config"); assert_eq!(contents.trim(), "model = \"user\""); } #[tokio::test] async fn read_reports_managed_overrides_user_and_session_flags() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "model = \"user\"").unwrap(); let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "model = \"system\"").unwrap(); let cli_overrides = vec![( "model".to_string(), TomlValue::String("session".to_string()), )]; let api = ConfigApi::with_overrides( tmp.path().to_path_buf(), cli_overrides, LoaderOverrides { managed_config_path: Some(managed_path), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, ); let response = api .read(ConfigReadParams { include_layers: true, }) .await .expect("response"); assert_eq!(response.config.get("model"), Some(&json!("system"))); assert_eq!( response.origins.get("model").expect("origin").name, ConfigLayerName::System ); let layers = response.layers.expect("layers"); assert_eq!(layers.first().unwrap().name, ConfigLayerName::System); assert_eq!(layers.get(1).unwrap().name, ConfigLayerName::SessionFlags); assert_eq!(layers.get(2).unwrap().name, ConfigLayerName::User); } #[tokio::test] async fn write_value_reports_managed_override() { let tmp = tempdir().expect("tempdir"); std::fs::write(tmp.path().join(CONFIG_FILE_NAME), "").unwrap(); let managed_path = tmp.path().join("managed_config.toml"); std::fs::write(&managed_path, "approval_policy = \"never\"").unwrap(); let api = ConfigApi::with_overrides( tmp.path().to_path_buf(), vec![], LoaderOverrides { managed_config_path: Some(managed_path), #[cfg(target_os = "macos")] managed_preferences_base64: None, }, ); let result = api .write_value(ConfigValueWriteParams { file_path: Some(tmp.path().join(CONFIG_FILE_NAME).display().to_string()), key_path: "approval_policy".to_string(), value: json!("on-request"), merge_strategy: MergeStrategy::Replace, expected_version: None, }) .await .expect("result"); assert_eq!(result.status, WriteStatus::OkOverridden); let overridden = result.overridden_metadata.expect("overridden metadata"); assert_eq!(overridden.overriding_layer.name, ConfigLayerName::System); assert_eq!(overridden.effective_value, json!("never")); } }