Clarify config layer iteration APIs (#36774)

## What changed

- Replace the ordering enum and `include_disabled` boolean with named
  `ConfigLayerStack` iterators for each precedence direction.
- Keep enabled-only iteration separate from `all_layers_*` iteration, which
  includes disabled layers.
- Update config consumers to use the iterator matching their precedence and
  disabled-layer requirements without allocating an intermediate `Vec`.

## Testing

- Add coverage that verifies ordering and disabled-layer filtering for all four
  iterators.

GitOrigin-RevId: d14df3db4a2eae80ba97cdec246bff405e6c5f3d
This commit is contained in:
jif
2026-08-03 17:29:42 +00:00
committed by copyberry
parent 224ea64cdc
commit 78306a32af
32 changed files with 150 additions and 309 deletions
@@ -16,7 +16,6 @@ use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerMetadata;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::ConfigRequirementsToml;
use codex_config::ShellEnvironmentPolicyFilterRepresentation;
use codex_config::config_toml::ConfigToml;
@@ -160,11 +159,7 @@ impl ConfigManager {
.collect(),
layers: params.include_layers.then(|| {
layers
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.iter()
.all_layers_high_to_low()
.map(|layer| config_layer_to_api(layer.as_layer()))
.collect()
}),
+1 -5
View File
@@ -4,7 +4,6 @@
use codex_arg0::Arg0DispatchPaths;
use codex_code_mode::CodeModeSessionProvider;
use codex_code_mode::WebSocketCodeModeSessionProvider;
use codex_config::ConfigLayerStackOrdering;
use codex_config::LoaderOverrides;
use codex_config::NoopThreadConfigLoader;
use codex_config::RemoteThreadConfigLoader;
@@ -355,10 +354,7 @@ fn app_text_range(range: &CoreTextRange) -> AppTextRange {
fn project_config_warning(config: &Config) -> Option<ConfigWarningNotification> {
let mut disabled_folders = Vec::new();
for layer in config.config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
) {
for layer in config.config_layer_stack.all_layers_low_to_high() {
let ConfigLayerSource::Project { dot_codex_folder } = &layer.name else {
continue;
};
@@ -30,7 +30,6 @@ pub(super) async fn set_user_model_provider_to_bedrock(
};
if let Some((overriding_layer, effective_provider)) = layers
.layers_high_to_low()
.into_iter()
.filter(|layer| layer.name.precedence() > user_precedence)
.find_map(|layer| {
layer
+1 -6
View File
@@ -3,7 +3,6 @@ use std::path::Path;
use std::path::PathBuf;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStackOrdering;
use codex_core::config::Config;
use codex_git_utils::get_git_repo_root;
use unicode_segmentation::UnicodeSegmentation;
@@ -171,11 +170,7 @@ fn terminal_title_project_root(config: &Config, cwd: &Path) -> Option<ProjectTit
config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.iter()
.all_layers_low_to_high()
.find_map(|layer| match &layer.name {
ConfigLayerSource::Project { dot_codex_folder } => dot_codex_folder
.as_path()
@@ -1,7 +1,6 @@
use super::*;
use crate::CONFIG_TOML_FILE;
use crate::ConfigLayerStack;
use crate::ConfigLayerStackOrdering;
use crate::ConfigRequirements;
use crate::ConfigRequirementsToml;
use crate::config_toml::ConfigToml;
@@ -121,11 +120,7 @@ review_model = "system-review"
assert_eq!(
stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false
)
.iter()
.layers_low_to_high()
.map(|layer| layer.name.clone())
.collect::<Vec<_>>(),
vec![
+2 -9
View File
@@ -4,7 +4,6 @@
use crate::ConfigLayerEntry;
use crate::ConfigLayerSource;
use crate::ConfigLayerStack;
use crate::ConfigLayerStackOrdering;
use crate::format_config_layer_source;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use serde::de::DeserializeOwned;
@@ -175,14 +174,8 @@ pub async fn first_layer_config_error<T: DeserializeOwned>(
// When the merged config fails schema validation, we surface the first concrete
// per-file error to point users at a specific file and range rather than an
// opaque merged-layer failure.
first_layer_config_error_for_entries::<T, _>(
layers.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
),
config_toml_file,
)
.await
first_layer_config_error_for_entries::<T, _>(layers.layers_low_to_high(), config_toml_file)
.await
}
pub async fn first_layer_config_error_from_entries<T: DeserializeOwned>(
-1
View File
@@ -153,7 +153,6 @@ pub use skills_config::SkillConfig;
pub use skills_config::SkillsConfig;
pub use state::ConfigLayerEntry;
pub use state::ConfigLayerStack;
pub use state::ConfigLayerStackOrdering;
pub use state::ConfigLoadOptions;
pub use state::LoaderOverrides;
pub use strict_config::config_error_from_ignored_toml_fields;
+2 -2
View File
@@ -14,7 +14,7 @@ Exported from `codex_config::loader`:
- `ConfigLayerStack`
- `effective_config() -> toml::Value`
- `origins() -> HashMap<String, ConfigLayerMetadata>`
- `layers_high_to_low() -> Vec<ConfigLayer>`
- `layers_high_to_low() -> impl Iterator<Item = &ConfigLayerEntry>`
- `with_user_config(user_config) -> ConfigLayerStack`
- `ConfigLayerEntry` (one layer’s `{name, config, version, disabled_reason}`; `name` carries source metadata)
- `ConfigLoadOptions` (user-facing load behavior such as strict config validation)
@@ -68,7 +68,7 @@ let layers = load_config_layers_state(
let effective = layers.effective_config();
let origins = layers.origins();
let layers_for_ui = layers.layers_high_to_low();
let layers_for_ui = layers.layers_high_to_low().collect::<Vec<_>>();
```
## Internal layout
+30 -58
View File
@@ -233,12 +233,6 @@ impl ConfigLayerEntry {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigLayerStackOrdering {
LowestPrecedenceFirst,
HighestPrecedenceFirst,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ConfigLayerStack {
/// Layers are listed from lowest precedence (base) to highest (top), so
@@ -323,34 +317,16 @@ impl ConfigLayerStack {
Some(file)
}
/// Returns all user config layers in the requested precedence order.
///
/// With profile-v2 enabled, `LowestPrecedenceFirst` returns the base user
/// config before the profile overlay, while `HighestPrecedenceFirst` returns
/// the profile overlay before the base user config.
pub fn get_user_layers(
&self,
ordering: ConfigLayerStackOrdering,
include_disabled: bool,
) -> Vec<&ConfigLayerEntry> {
self.get_layers(ordering, include_disabled)
.into_iter()
.filter(|layer| matches!(layer.name, ConfigLayerSource::User { .. }))
.collect()
}
/// Returns the merged config from enabled user layers only.
///
/// When profile config is active, this includes the base user config followed
/// by the profile override config.
pub fn effective_user_config(&self) -> Option<TomlValue> {
let user_layers = self.get_user_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
);
if user_layers.is_empty() {
return None;
}
let mut user_layers = self
.layers_low_to_high()
.filter(|layer| matches!(layer.name, ConfigLayerSource::User { .. }))
.peekable();
user_layers.peek()?;
let mut merged = TomlValue::Table(toml::map::Map::new());
for layer in user_layers {
@@ -468,10 +444,7 @@ impl ConfigLayerStack {
/// tracked separately.
pub fn effective_config(&self) -> TomlValue {
let mut merged = TomlValue::Table(toml::map::Map::new());
for layer in self.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
for layer in self.layers_low_to_high() {
merge_toml_values(&mut merged, &layer.config);
}
merged
@@ -484,10 +457,7 @@ impl ConfigLayerStack {
let mut origins = HashMap::new();
let mut path = Vec::new();
for layer in self.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
for layer in self.layers_low_to_high() {
let config = normalized_with_key_aliases(&layer.config, &[]);
record_origins(&config, &layer.metadata(), &mut path, &mut origins);
}
@@ -495,33 +465,35 @@ impl ConfigLayerStack {
origins
}
/// Returns config layers from highest precedence to lowest precedence.
/// Returns enabled config layers from lowest precedence to highest.
///
/// Requirement sources are tracked separately and are not included here.
pub fn layers_high_to_low(&self) -> Vec<&ConfigLayerEntry> {
self.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ false,
)
pub fn layers_low_to_high(&self) -> impl DoubleEndedIterator<Item = &ConfigLayerEntry> {
self.all_layers_low_to_high()
.filter(|layer| !layer.is_disabled())
}
/// Returns config layers in the requested precedence order.
/// Returns enabled config layers from highest precedence to lowest.
///
/// Requirement sources are tracked separately and are not included here.
pub fn get_layers(
&self,
ordering: ConfigLayerStackOrdering,
include_disabled: bool,
) -> Vec<&ConfigLayerEntry> {
let mut layers: Vec<&ConfigLayerEntry> = self
.layers
.iter()
.filter(|layer| include_disabled || !layer.is_disabled())
.collect();
if ordering == ConfigLayerStackOrdering::HighestPrecedenceFirst {
layers.reverse();
}
layers
pub fn layers_high_to_low(&self) -> impl DoubleEndedIterator<Item = &ConfigLayerEntry> {
self.layers_low_to_high().rev()
}
/// Returns all config layers, including disabled layers, from lowest
/// precedence to highest.
///
/// Requirement sources are tracked separately and are not included here.
pub fn all_layers_low_to_high(&self) -> impl DoubleEndedIterator<Item = &ConfigLayerEntry> {
self.layers.iter()
}
/// Returns all config layers, including disabled layers, from highest
/// precedence to lowest.
///
/// Requirement sources are tracked separately and are not included here.
pub fn all_layers_high_to_low(&self) -> impl DoubleEndedIterator<Item = &ConfigLayerEntry> {
self.all_layers_low_to_high().rev()
}
}
+57
View File
@@ -250,6 +250,63 @@ approval_policy = "on-request"
);
}
#[test]
fn layer_iterators_preserve_precedence_and_disabled_layers() {
let temp_dir = TempDir::new().expect("tempdir");
let user_source = ConfigLayerSource::User {
file: test_user_config_path(&temp_dir, "config.toml"),
profile: None,
};
let project_source = ConfigLayerSource::Project {
dot_codex_folder: test_user_config_path(&temp_dir, ".codex"),
};
let session_source = ConfigLayerSource::SessionFlags;
let empty_config = TomlValue::Table(toml::map::Map::new());
let stack = ConfigLayerStack::new(
vec![
ConfigLayerEntry::new(user_source.clone(), empty_config.clone()),
ConfigLayerEntry::new_disabled(
project_source.clone(),
empty_config.clone(),
"project is untrusted",
),
ConfigLayerEntry::new(session_source.clone(), empty_config),
],
ConfigRequirements::default(),
ConfigRequirementsToml::default(),
)
.expect("layer stack should be valid");
assert_eq!(
stack
.layers_low_to_high()
.map(|layer| &layer.name)
.collect::<Vec<_>>(),
vec![&user_source, &session_source]
);
assert_eq!(
stack
.layers_high_to_low()
.map(|layer| &layer.name)
.collect::<Vec<_>>(),
vec![&session_source, &user_source]
);
assert_eq!(
stack
.all_layers_low_to_high()
.map(|layer| &layer.name)
.collect::<Vec<_>>(),
vec![&user_source, &project_source, &session_source]
);
assert_eq!(
stack
.all_layers_high_to_low()
.map(|layer| &layer.name)
.collect::<Vec<_>>(),
vec![&session_source, &project_source, &user_source]
);
}
#[test]
fn with_user_config_updates_matching_user_layer_without_replacing_active_profile() {
let temp_dir = TempDir::new().expect("tempdir");
+1 -5
View File
@@ -2,7 +2,6 @@ use std::collections::HashSet;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::SkillConfig;
use codex_config::SkillsConfig;
pub use codex_skills::SkillConfigRule;
@@ -15,10 +14,7 @@ use crate::SkillMetadata;
pub fn skill_config_rules_from_stack(config_layer_stack: &ConfigLayerStack) -> SkillConfigRules {
let mut entries = Vec::new();
for layer in config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
) {
for layer in config_layer_stack.all_layers_low_to_high() {
if !matches!(
layer.name,
ConfigLayerSource::User { .. } | ConfigLayerSource::SessionFlags
+2 -9
View File
@@ -19,7 +19,6 @@ use crate::model::SkillToolDependency;
use crate::system::system_cache_root_dir;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::default_project_root_markers;
use codex_config::merge_toml_values;
use codex_config::project_root_markers_from_config;
@@ -296,10 +295,7 @@ fn skill_roots_from_layer_stack_inner(
) -> Vec<SkillRoot> {
let mut roots = Vec::new();
for layer in config_layer_stack.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
) {
for layer in config_layer_stack.all_layers_high_to_low() {
let Some(config_folder) = layer.config_folder() else {
continue;
};
@@ -429,10 +425,7 @@ async fn repo_agents_skill_roots(
fn project_root_markers_from_stack(config_layer_stack: &ConfigLayerStack) -> Vec<String> {
let mut merged = TomlValue::Table(toml::map::Map::new());
for layer in config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
for layer in config_layer_stack.layers_low_to_high() {
if matches!(layer.name, ConfigLayerSource::Project { .. }) {
continue;
}
+1 -6
View File
@@ -15,7 +15,6 @@ use anyhow::anyhow;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::config_toml::ConfigToml;
use codex_config::loader::resolve_relative_paths_in_config_toml;
use codex_exec_server::LOCAL_FS;
@@ -236,11 +235,7 @@ mod reload {
fn existing_layers(config: &Config) -> Vec<ConfigLayerEntry> {
config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.cloned()
.collect()
}
+2 -11
View File
@@ -2,7 +2,6 @@ use super::*;
use crate::SkillsService;
use crate::config::ConfigBuilder;
use crate::skills_load_input_from_config;
use codex_config::ConfigLayerStackOrdering;
use codex_core_plugins::PluginsManager;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::openai_models::ReasoningEffort;
@@ -39,11 +38,7 @@ async fn write_role_config(home: &TempDir, name: &str, contents: &str) -> PathBu
fn session_flags_layer_count(config: &Config) -> usize {
config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.filter(|layer| layer.name == ConfigLayerSource::SessionFlags)
.count()
}
@@ -302,11 +297,7 @@ writable_roots = ["./sandbox-root"]
let role_layer = config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.rfind(|layer| layer.name == ConfigLayerSource::SessionFlags)
.expect("expected a session flags layer");
let sandbox_workspace_write = role_layer
+1 -5
View File
@@ -19,7 +19,6 @@ use crate::config::Config;
use crate::context::UserInstructions as ContextUserInstructions;
use crate::environment_selection::TurnEnvironmentSnapshot;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStackOrdering;
use codex_config::default_project_root_markers;
use codex_config::merge_toml_values;
use codex_config::project_root_markers_from_config;
@@ -160,10 +159,7 @@ async fn agents_md_paths(
let dir = cwd.clone();
let mut merged = TomlValue::Table(toml::map::Map::new());
for layer in config.config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
for layer in config.config_layer_stack.layers_low_to_high() {
if matches!(layer.name, ConfigLayerSource::Project { .. }) {
continue;
}
+2 -6
View File
@@ -1,6 +1,5 @@
use super::AgentRoleConfig;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::config_toml::AgentRoleToml;
use codex_config::config_toml::AgentsToml;
use codex_config::config_toml::ConfigToml;
@@ -22,11 +21,8 @@ pub(crate) async fn load_agent_roles(
config_layer_stack: &ConfigLayerStack,
startup_warnings: &mut Vec<String>,
) -> std::io::Result<BTreeMap<String, AgentRoleConfig>> {
let layers = config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
);
if layers.is_empty() {
let mut layers = config_layer_stack.layers_low_to_high().peekable();
if layers.peek().is_none() {
return load_agent_roles_without_layers(fs, cfg).await;
}
+12 -55
View File
@@ -9,7 +9,6 @@ use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigError;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStackOrdering;
use codex_config::ConfigLoadError;
use codex_config::ConfigLoadOptions;
use codex_config::ConfigRequirements;
@@ -844,7 +843,6 @@ async fn returns_empty_when_all_layers_missing() {
);
let num_system_layers = layers
.layers_high_to_low()
.iter()
.filter(|layer| matches!(layer.name, ConfigLayerSource::System { .. }))
.count();
assert_eq!(
@@ -896,10 +894,10 @@ approval_policy = "on-request"
.await
.expect("load layers");
let user_layers = layers.get_user_layers(
super::ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
);
let user_layers = layers
.layers_low_to_high()
.filter(|layer| matches!(layer.name, ConfigLayerSource::User { .. }))
.collect::<Vec<_>>();
assert_eq!(user_layers.len(), 2);
assert_eq!(
user_layers[0].name,
@@ -962,7 +960,6 @@ async fn includes_thread_config_layers_in_stack() -> anyhow::Result<()> {
let layer_sources = layers
.layers_high_to_low()
.into_iter()
.map(|layer| layer.name.clone())
.collect::<Vec<_>>();
assert_eq!(
@@ -1048,7 +1045,6 @@ flag = false
assert_eq!(nested.get("flag"), Some(&TomlValue::Boolean(false)));
let mdm_layer = state
.layers_high_to_low()
.into_iter()
.find(|layer| {
matches!(
layer.name,
@@ -2292,11 +2288,7 @@ model_provider = "cloud-provider"
);
assert_eq!(
layers
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
)
.iter()
.layers_low_to_high()
.map(|layer| layer.name.clone())
.collect::<Vec<_>>(),
vec![
@@ -2634,7 +2626,6 @@ async fn project_layers_prefer_closest_cwd() -> std::io::Result<()> {
let project_layers: Vec<_> = layers
.layers_high_to_low()
.into_iter()
.filter_map(|layer| match &layer.name {
ConfigLayerSource::Project { dot_codex_folder } => Some(dot_codex_folder),
_ => None,
@@ -2716,7 +2707,6 @@ async fn linked_worktree_project_layers_keep_worktree_config_but_use_root_repo_h
let project_layers: Vec<_> = layers
.layers_high_to_low()
.into_iter()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
assert_eq!(project_layers.len(), 2);
@@ -2797,7 +2787,6 @@ async fn linked_worktree_project_layers_use_root_repo_hooks_without_worktree_con
let project_layers: Vec<_> = layers
.layers_high_to_low()
.into_iter()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
assert_eq!(project_layers.len(), 1);
@@ -2867,7 +2856,6 @@ async fn nested_project_root_markers_do_not_redirect_regular_repo_hooks() -> std
let project_layers: Vec<_> = layers
.layers_high_to_low()
.into_iter()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
assert_eq!(project_layers.len(), 2);
@@ -3054,7 +3042,6 @@ async fn project_layer_is_added_when_dot_codex_exists_without_config_toml() -> s
let project_layers: Vec<_> = layers
.layers_high_to_low()
.into_iter()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
let expected_project_layer = ConfigLayerEntry::new(
@@ -3093,11 +3080,7 @@ async fn codex_home_is_not_loaded_as_project_layer_from_home_dir() -> std::io::R
.await?;
let project_layers: Vec<_> = layers
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_high_to_low()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
let expected: Vec<&ConfigLayerEntry> = Vec::new();
@@ -3158,11 +3141,7 @@ async fn codex_home_within_project_tree_is_not_double_loaded() -> std::io::Resul
.await?;
let project_layers: Vec<_> = layers
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_high_to_low()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
@@ -3232,11 +3211,7 @@ profile = "ignored"
)
.await?;
let project_layers_untrusted: Vec<_> = layers_untrusted
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_high_to_low()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
assert_eq!(project_layers_untrusted.len(), 1);
@@ -3278,11 +3253,7 @@ profile = "ignored"
)
.await?;
let project_layers_unknown: Vec<_> = layers_unknown
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_high_to_low()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
assert_eq!(project_layers_unknown.len(), 1);
@@ -3371,7 +3342,6 @@ wire_api = "responses"
let project_layer = layers
.layers_high_to_low()
.into_iter()
.find(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.expect("expected project layer");
@@ -3472,11 +3442,7 @@ async fn project_trust_does_not_match_configured_alias_for_canonical_cwd() -> st
.await?;
let project_layers: Vec<_> = layers
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_high_to_low()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
assert_eq!(project_layers.len(), 1);
@@ -3636,11 +3602,7 @@ async fn invalid_project_config_ignored_when_untrusted_or_unknown() -> std::io::
)
.await?;
let project_layers: Vec<_> = layers
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_high_to_low()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
assert_eq!(
@@ -3704,11 +3666,7 @@ async fn project_layer_without_config_toml_is_disabled_when_untrusted_or_unknown
)
.await?;
let project_layers: Vec<_> = layers
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_high_to_low()
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
.collect();
assert_eq!(
@@ -3811,7 +3769,6 @@ async fn project_root_markers_supports_alternate_markers() -> std::io::Result<()
let project_layers: Vec<_> = layers
.layers_high_to_low()
.into_iter()
.filter_map(|layer| match &layer.name {
ConfigLayerSource::Project { dot_codex_folder } => Some(dot_codex_folder),
_ => None,
+1 -5
View File
@@ -2369,11 +2369,7 @@ async fn managed_unrestricted_permission_profile_still_enables_network_requireme
let layers = config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.cloned()
.collect();
let mut requirements = config.config_layer_stack.requirements().clone();
+6 -25
View File
@@ -9,7 +9,6 @@ use crate::windows_sandbox::resolve_windows_sandbox_private_desktop;
use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::ConfigRequirements;
use codex_config::ConfigRequirementsToml;
use codex_config::ConstrainedWithSource;
@@ -1769,21 +1768,13 @@ impl Config {
) -> std::io::Result<Self> {
let mut layers = refreshed_config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.filter(|layer| !is_session_layer(&layer.name))
.cloned()
.collect::<Vec<_>>();
layers.extend(
self.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.filter(|layer| is_session_layer(&layer.name))
.cloned(),
);
@@ -2368,11 +2359,8 @@ fn resolve_tool_suggest_config_from_config(
}
};
let layers = config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
);
if layers.is_empty() {
let mut layers = config_layer_stack.layers_low_to_high().peekable();
if layers.peek().is_none() {
for disabled_tool in tool_suggest
.into_iter()
.flat_map(|tool_suggest| tool_suggest.disabled_tools.iter().cloned())
@@ -2467,11 +2455,7 @@ fn resolve_permission_config_syntax(
}
let session_flags_select_profiles = config_layer_stack
.get_layers(
ConfigLayerStackOrdering::HighestPrecedenceFirst,
/*include_disabled*/ false,
)
.into_iter()
.layers_high_to_low()
.find(|layer| matches!(layer.name, ConfigLayerSource::SessionFlags))
.and_then(|layer| {
layer
@@ -2486,10 +2470,7 @@ fn resolve_permission_config_syntax(
}
let mut selection = None;
for layer in config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
for layer in config_layer_stack.layers_low_to_high() {
let Ok(layer_selection) = layer.config.clone().try_into::<PermissionSelectionToml>() else {
continue;
};
+2 -10
View File
@@ -7,7 +7,6 @@ use arc_swap::ArcSwap;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_execpolicy::AmendError;
use codex_execpolicy::Decision;
use codex_execpolicy::Error as ExecPolicyRuleError;
@@ -181,11 +180,7 @@ pub(crate) fn child_uses_parent_exec_policy(parent_config: &Config, child_config
fn exec_policy_config_folders(config: &Config) -> Vec<AbsolutePathBuf> {
config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
)
.into_iter()
.layers_low_to_high()
.filter_map(codex_config::ConfigLayerEntry::config_folder)
.collect()
}
@@ -639,10 +634,7 @@ pub async fn load_exec_policy(config_stack: &ConfigLayerStack) -> Result<Policy,
// from each layer, so that higher-precedence layers can override
// rules defined in lower-precedence ones.
let mut policy_paths = Vec::new();
for layer in config_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
for layer in config_stack.layers_low_to_high() {
if config_stack.ignore_user_and_project_exec_policy_rules()
&& matches!(
layer.name,
+2 -11
View File
@@ -5,7 +5,6 @@ use codex_config::CONFIG_TOML_FILE;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::ConfigRequirements;
use codex_config::ConfigRequirementsToml;
use codex_config::LoaderOverrides;
@@ -130,11 +129,7 @@ async fn child_uses_parent_exec_policy_when_non_exec_policy_layers_differ() {
let mut child_config = parent_config.clone();
let mut layers: Vec<_> = child_config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.cloned()
.collect();
layers.push(ConfigLayerEntry::new(
@@ -190,11 +185,7 @@ async fn child_does_not_use_parent_exec_policy_when_requirements_exec_policy_dif
child_config.config_layer_stack = ConfigLayerStack::new(
child_config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.cloned()
.collect(),
requirements,
-1
View File
@@ -2136,7 +2136,6 @@ fn project_mcp_tool_approval_config_folder(
config
.config_layer_stack
.layers_high_to_low()
.into_iter()
.find_map(|layer| {
if !matches!(layer.name, ConfigLayerSource::Project { .. }) {
return None;
+1 -6
View File
@@ -191,7 +191,6 @@ use crate::context_manager::ContextManager;
use crate::thread_rollout_truncation::initial_history_has_prior_user_turns;
use codex_config::CONFIG_TOML_FILE;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStackOrdering;
use codex_config::types::McpServerConfig;
use codex_model_provider_info::ModelProviderInfo;
use codex_protocol::error::CodexErr;
@@ -1740,11 +1739,7 @@ impl Session {
let config = &state.session_configuration.original_config_do_not_use;
let user_config_paths = config
.config_layer_stack
.get_user_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.filter_map(|layer| match &layer.name {
ConfigLayerSource::User { file, .. } => Some(file.clone()),
_ => None,
+3 -16
View File
@@ -18,7 +18,6 @@ use crate::test_support::models_manager_with_provider;
use crate::tools::format_exec_output_str;
use crate::tools::registry::ToolRegistry;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID;
use codex_config::LoaderOverrides;
use codex_config::NetworkConstraints;
@@ -1269,11 +1268,7 @@ async fn danger_full_access_tool_attempts_do_not_enforce_managed_network() -> an
let layers = config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.cloned()
.collect();
let mut requirements = config.config_layer_stack.requirements().clone();
@@ -1899,11 +1894,7 @@ async fn refresh_mcp_config_replaces_managed_server_and_plugin_requirements() {
requirements_toml.plugins = Some(plugin_requirements.clone());
let layers = next_config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.cloned()
.collect();
next_config.config_layer_stack = ConfigLayerStack::new(layers, requirements, requirements_toml)
@@ -8617,11 +8608,7 @@ async fn record_context_updates_emits_environment_item_for_network_changes() {
));
let layers = config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.all_layers_low_to_high()
.cloned()
.collect();
config.config_layer_stack = ConfigLayerStack::new(
+1 -5
View File
@@ -2,7 +2,6 @@ use std::collections::HashMap;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::HookStateToml;
use codex_config::TomlValue;
@@ -21,10 +20,7 @@ pub fn hook_states_from_stack(
};
let mut states: HashMap<String, HookStateToml> = HashMap::new();
for layer in config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
) {
for layer in config_layer_stack.all_layers_low_to_high() {
if !matches!(
layer.name,
ConfigLayerSource::User { .. } | ConfigLayerSource::SessionFlags
+1 -5
View File
@@ -7,7 +7,6 @@ use codex_config::CONFIG_TOML_FILE;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::HookEventsToml;
use codex_config::HookHandlerConfig;
use codex_config::HookStateToml;
@@ -98,10 +97,7 @@ pub(crate) fn discover_handlers(
policy,
);
for layer in config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ false,
) {
for layer in config_layer_stack.layers_low_to_high() {
let (hook_source, is_managed) = hook_metadata_for_config_layer_source(&layer.name);
let policy_path = config_toml_source_path(layer);
let policy_source = HookHandlerSource {
-1
View File
@@ -134,7 +134,6 @@ use codex_app_server_protocol::TurnError as AppServerTurnError;
use codex_app_server_protocol::TurnStatus;
use codex_app_server_protocol::WriteStatus;
use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLayerStackOrdering;
use codex_config::LoaderOverrides;
use codex_config::types::ApprovalsReviewer;
use codex_config::types::MemoriesToml;
+12 -16
View File
@@ -30,22 +30,18 @@ pub(super) fn resume_model_settings_for_overrides(
config: &Config,
harness_overrides: &ConfigOverrides,
) -> crate::app_server_session::ResumeModelSettings {
let has_layer_override = config
.config_layer_stack
.layers_high_to_low()
.into_iter()
.any(|layer| {
matches!(
&layer.name,
ConfigLayerSource::SessionFlags
| ConfigLayerSource::User {
profile: Some(_),
..
}
) && ["model", "model_provider", "model_reasoning_effort"]
.iter()
.any(|key| layer.config.get(*key).is_some())
});
let has_layer_override = config.config_layer_stack.layers_high_to_low().any(|layer| {
matches!(
&layer.name,
ConfigLayerSource::SessionFlags
| ConfigLayerSource::User {
profile: Some(_),
..
}
) && ["model", "model_provider", "model_reasoning_effort"]
.iter()
.any(|key| layer.config.get(*key).is_some())
});
if harness_overrides.model.is_some()
|| harness_overrides.model_provider.is_some()
|| has_layer_override
+1 -4
View File
@@ -69,10 +69,7 @@ pub(super) fn emit_skill_load_warnings(app_event_tx: &AppEventSender, errors: &[
pub(super) fn emit_project_config_warnings(app_event_tx: &AppEventSender, config: &Config) {
let mut disabled_folders = Vec::new();
for layer in config.config_layer_stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
) {
for layer in config.config_layer_stack.all_layers_low_to_high() {
let ConfigLayerSource::Project { dot_codex_folder } = &layer.name else {
continue;
};
-1
View File
@@ -116,7 +116,6 @@ use codex_app_server_protocol::TurnCompletedNotification;
use codex_app_server_protocol::TurnPlanStepStatus;
use codex_app_server_protocol::TurnStatus;
use codex_app_server_protocol::UserInput;
use codex_config::ConfigLayerStackOrdering;
use codex_config::Constrained;
use codex_config::ConstraintResult;
use codex_config::types::ApprovalsReviewer;
@@ -448,11 +448,7 @@ impl ChatWidget {
self.config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.iter()
.all_layers_low_to_high()
.find_map(|layer| match &layer.name {
ConfigLayerSource::Project { dot_codex_folder } => {
dot_codex_folder.as_path().parent().map(Path::to_path_buf)
+3 -7
View File
@@ -6,7 +6,6 @@ use codex_config::CONFIG_TOML_FILE;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_config::ConfigLayerStackOrdering;
use codex_config::ManagedHooksRequirementsToml;
use codex_config::NetworkConstraints;
use codex_config::NetworkDomainPermissionToml;
@@ -131,14 +130,11 @@ fn render_debug_config_lines(
.bold()
.into(),
);
let layers = stack.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
);
if layers.is_empty() {
let mut layers = stack.all_layers_low_to_high().peekable();
if layers.peek().is_none() {
lines.push(" <none>".dim().into());
} else {
for (index, layer) in layers.iter().enumerate() {
for (index, layer) in layers.enumerate() {
let source = format_config_layer_source(&layer.name, CONFIG_TOML_FILE);
let status = if layer.is_disabled() {
"disabled"