Commit Graph

7 Commits

  • feat: migrate to new constraint-based loading strategy (#8251)
    This is a significant change to how layers of configuration are applied.
    In particular, the `ConfigLayerStack` now has two important fields:
    
    - `layers: Vec<ConfigLayerEntry>`
    - `requirements: ConfigRequirements`
    
    We merge `TomlValue`s across the layers, but they are subject to
    `ConfigRequirements` before creating a `Config`.
    
    How I would review this PR:
    
    - start with `codex-rs/app-server-protocol/src/protocol/v2.rs` and note
    the new variants added to the `ConfigLayerSource` enum:
    `LegacyManagedConfigTomlFromFile` and `LegacyManagedConfigTomlFromMdm`
    - note that `ConfigLayerSource` now has a `precedence()` method and
    implements `PartialOrd`
    - `codex-rs/core/src/config_loader/layer_io.rs` is responsible for
    loading "admin" preferences from `/etc/codex/managed_config.toml` and
    MDM. Because `/etc/codex/managed_config.toml` is now deprecated in favor
    of `/etc/codex/requirements.toml` and `/etc/codex/config.toml`, we now
    include some extra information on the `LoadedConfigLayers` returned in
    `layer_io.rs`.
    - `codex-rs/core/src/config_loader/mod.rs` has major changes to
    `load_config_layers_state()`, which is what produces `ConfigLayerStack`.
    The docstring has the new specification and describes the various layers
    that will be loaded and the precedence order.
    - It uses the information from `LoaderOverrides` "twice," both in the
    spirit of legacy support:
    - We use one instances to derive an instance of `ConfigRequirements`.
    Currently, the only field in `managed_config.toml` that contributes to
    `ConfigRequirements` is `approval_policy`. This PR introduces
    `Constrained::allow_only()` to support this.
    - We use a clone of `LoaderOverrides` to derive
    `ConfigLayerSource::LegacyManagedConfigTomlFromFile` and
    `ConfigLayerSource::LegacyManagedConfigTomlFromMdm` layers, as
    appropriate. As before, this ends up being a "best effort" at enterprise
    controls, but is enforcement is not guaranteed like it is for
    `ConfigRequirements`.
    - Now we only create a "user" layer if `$CODEX_HOME/config.toml` exists.
    (Previously, a user layer was always created for `ConfigLayerStack`.)
    - Similarly, we only add a "session flags" layer if there are CLI
    overrides.
    - `config_loader/state.rs` contains the updated implementation for
    `ConfigLayerStack`. Note the public API is largely the same as before,
    but the implementation is quite different. We leverage the fact that
    `ConfigLayerSource` is now `PartialOrd` to ensure layers are in the
    correct order.
    - A `Config` constructed via `ConfigBuilder.build()` will use
    `load_config_layers_state()` to create the `ConfigLayerStack` and use
    the associated `ConfigRequirements` when constructing the `Config`
    object.
    - That said, a `Config` constructed via
    `Config::load_from_base_config_with_overrides()` does _not_ yet use
    `ConfigBuilder`, so it creates a `ConfigRequirements::default()` instead
    of loading a proper `ConfigRequirements`. I will fix this in a
    subsequent PR.
    
    Then the following files are mostly test changes:
    
    ```
    codex-rs/app-server/tests/suite/v2/config_rpc.rs
    codex-rs/core/src/config/service.rs
    codex-rs/core/src/config_loader/tests.rs
    ```
    
    Again, because we do not always include "user" and "session flags"
    layers when the contents are empty, `ConfigLayerStack` sometimes has
    fewer layers than before (and the precedence order changed slightly),
    which is the main reason integration tests changed.
  • chore: prefer AsRef<Path> to &Path (#8249)
    This is some minor API cleanup that will make it easier to use
    `AbsolutePathBuf` in more places in a subsequent PR.
  • chore: simplify loading of Mac-specific logic in config_loader (#8248)
    Over in `config_loader/macos.rs`, we were doing this complicated `mod`
    thing to expose one version of `load_managed_admin_config_layer()` for
    Mac:
    
    
    https://github.com/openai/codex/blob/580c59aa9af61cb4bffb5b204bd16a5dcc4bc911/codex-rs/core/src/config_loader/macos.rs#L4-L5
    
    While exposing a trivial implementation for non-Mac:
    
    
    https://github.com/openai/codex/blob/580c59aa9af61cb4bffb5b204bd16a5dcc4bc911/codex-rs/core/src/config_loader/macos.rs#L110-L117
    
    That was being used like this:
    
    
    https://github.com/openai/codex/blob/580c59aa9af61cb4bffb5b204bd16a5dcc4bc911/codex-rs/core/src/config_loader/layer_io.rs#L47-L48
    
    This PR simplifies that callsite in `layer_io.rs` to just be:
    
    ```rust
        #[cfg(not(target_os = "macos"))]
        let managed_preferences = None;
    ```
    
    And updates `config_loader/mod.rs` so we only pull in `macos.rs` on Mac:
    
    ```rust
    #[cfg(target_os = "macos")]
    mod macos;
    ```
    
    This simplifies `macos.rs` considerably, though it looks like a big
    change because everything gets unindented and reformatted because we can
    drop the whole `mod native` thing now.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8248).
    * #8251
    * #8249
    * __->__ #8248
  • feat: change ConfigLayerName into a disjoint union rather than a simple enum (#8095)
    This attempts to tighten up the types related to "config layers."
    Currently, `ConfigLayerEntry` is defined as follows:
    
    
    https://github.com/openai/codex/blob/bef36f4ae765f471d7cd69372fcf1b92c8f0367a/codex-rs/core/src/config_loader/state.rs#L19-L25
    
    but the `source` field is a bit of a lie, as:
    
    - for `ConfigLayerName::Mdm`, it is
    `"com.openai.codex/config_toml_base64"`
    - for `ConfigLayerName::SessionFlags`, it is `"--config"`
    - for `ConfigLayerName::User`, it is `"config.toml"` (just the file
    name, not the path to the `config.toml` on disk that was read)
    - for `ConfigLayerName::System`, it seems like it is usually
    `/etc/codex/managed_config.toml` in practice, though on Windows, it is
    `%CODEX_HOME%/managed_config.toml`:
    
    
    https://github.com/openai/codex/blob/bef36f4ae765f471d7cd69372fcf1b92c8f0367a/codex-rs/core/src/config_loader/layer_io.rs#L84-L101
    
    All that is to say, in three out of the four `ConfigLayerName`, `source`
    is a `PathBuf` that is not an absolute path (or even a true path).
    
    This PR tries to uplevel things by eliminating `source` from
    `ConfigLayerEntry` and turning `ConfigLayerName` into a disjoint union
    named `ConfigLayerSource` that has the appropriate metadata for each
    variant, favoring the use of `AbsolutePathBuf` where appropriate:
    
    ```rust
    pub enum ConfigLayerSource {
        /// Managed preferences layer delivered by MDM (macOS only).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        Mdm { domain: String, key: String },
        /// Managed config layer from a file (usually `managed_config.toml`).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        System { file: AbsolutePathBuf },
        /// Session-layer overrides supplied via `-c`/`--config`.
        SessionFlags,
        /// User config layer from a file (usually `config.toml`).
        #[serde(rename_all = "camelCase")]
        #[ts(rename_all = "camelCase")]
        User { file: AbsolutePathBuf },
    }
    ```
  • feat: clean config loading and config api (#7924)
    Check the README of the `config_loader` for details
  • add(core): managed config (#3868)
    ## Summary
    
    - Factor `load_config_as_toml` into `core::config_loader` so config
    loading is reusable across callers.
    - Layer `~/.codex/config.toml`, optional `~/.codex/managed_config.toml`,
    and macOS managed preferences (base64) with recursive table merging and
    scoped threads per source.
    
    ## Config Flow
    
    ```
    Managed prefs (macOS profile: com.openai.codex/config_toml_base64)
                                   ▲
                                   │
    ~/.codex/managed_config.toml   │  (optional file-based override)
                                   ▲
                                   │
                    ~/.codex/config.toml (user-defined settings)
    ```
    
    - The loader searches under the resolved `CODEX_HOME` directory
    (defaults to `~/.codex`).
    - Managed configs let administrators ship fleet-wide overrides via
    device profiles which is useful for enforcing certain settings like
    sandbox or approval defaults.
    - For nested hash tables: overlays merge recursively. Child tables are
    merged key-by-key, while scalar or array values replace the prior layer
    entirely. This lets admins add or tweak individual fields without
    clobbering unrelated user settings.