mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
d45cd26248
## Summary - Adapts the moved `codex-cloud-config` crate from the legacy cloud requirements endpoint to the new config bundle endpoint. - Switches runtime consumers from `CloudRequirementsLoader` to `CloudConfigBundleLoader` so one shared bundle supplies cloud-delivered config and requirements. - Removes the legacy cloud requirements domain loader path. ## Details This intentionally keeps `codex-cloud-config` monolithic for review lineage: the previous PR establishes the crate move, and this PR shows the behavior change against that moved implementation. A follow-up PR splits the module back into focused files. The new bundle path preserves the important cloud requirements loader semantics where intended: account-scoped signed cache, 30 minute TTL, 5 minute refresh cadence, retry/backoff, auth recovery, and fail-closed startup loading. The cached payload changes from a single requirements TOML string to the backend-delivered bundle, and validation rejects malformed config or requirements fragments before cache write/use.
152 lines
4.9 KiB
Rust
152 lines
4.9 KiB
Rust
//! Conversion from cloud-delivered config TOML fragments into config stack layers.
|
|
//!
|
|
//! Backend fragments arrive in backend priority order. This module parses each
|
|
//! fragment, resolves relative path fields against the cloud config base
|
|
//! directory, and returns layers in `ConfigLayerStack` order.
|
|
|
|
use crate::ConfigLayerEntry;
|
|
use crate::ConfigLayerSource;
|
|
use crate::TomlValue;
|
|
use crate::config_toml::ConfigToml;
|
|
use crate::loader::resolve_relative_paths_in_config_toml;
|
|
use crate::strict_config::config_error_from_ignored_toml_value_fields_for_source_name;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use codex_utils_absolute_path::AbsolutePathBufGuard;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::fmt;
|
|
use std::io;
|
|
use thiserror::Error;
|
|
|
|
/// Config fragment delivered by the cloud config bundle.
|
|
///
|
|
/// The bundle orders fragments from highest precedence to lowest precedence.
|
|
/// This module returns config layers in stack order, so callers can append the
|
|
/// result between system and user config without re-sorting.
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
pub struct CloudConfigFragment {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub contents: String,
|
|
}
|
|
|
|
impl CloudConfigFragment {
|
|
fn source_ref(&self) -> CloudConfigFragmentSource {
|
|
CloudConfigFragmentSource {
|
|
id: self.id.clone(),
|
|
name: self.name.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct CloudConfigFragmentSource {
|
|
pub id: String,
|
|
pub name: String,
|
|
}
|
|
|
|
impl fmt::Display for CloudConfigFragmentSource {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{} ({})", self.name, self.id)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error, PartialEq, Eq)]
|
|
pub enum CloudConfigLayerError {
|
|
#[error("failed to parse cloud config fragment {fragment}: {message}")]
|
|
Parse {
|
|
fragment: CloudConfigFragmentSource,
|
|
message: String,
|
|
},
|
|
#[error("invalid cloud config fragment {fragment}: {message}")]
|
|
Invalid {
|
|
fragment: CloudConfigFragmentSource,
|
|
message: String,
|
|
},
|
|
}
|
|
|
|
pub fn cloud_config_layers_from_fragments(
|
|
fragments: impl IntoIterator<Item = CloudConfigFragment>,
|
|
base_dir: &AbsolutePathBuf,
|
|
) -> Result<Vec<ConfigLayerEntry>, CloudConfigLayerError> {
|
|
cloud_config_layers_from_fragments_impl(fragments, base_dir, /*strict_config*/ false)
|
|
}
|
|
|
|
pub(crate) fn cloud_config_layers_from_fragments_strict(
|
|
fragments: impl IntoIterator<Item = CloudConfigFragment>,
|
|
base_dir: &AbsolutePathBuf,
|
|
) -> Result<Vec<ConfigLayerEntry>, CloudConfigLayerError> {
|
|
cloud_config_layers_from_fragments_impl(fragments, base_dir, /*strict_config*/ true)
|
|
}
|
|
|
|
fn cloud_config_layers_from_fragments_impl(
|
|
fragments: impl IntoIterator<Item = CloudConfigFragment>,
|
|
base_dir: &AbsolutePathBuf,
|
|
strict_config: bool,
|
|
) -> Result<Vec<ConfigLayerEntry>, CloudConfigLayerError> {
|
|
let mut layers = Vec::new();
|
|
for fragment in fragments {
|
|
let source_ref = fragment.source_ref();
|
|
let raw_toml = fragment.contents;
|
|
let value: TomlValue =
|
|
toml::from_str(&raw_toml).map_err(|err| CloudConfigLayerError::Parse {
|
|
fragment: source_ref.clone(),
|
|
message: err.to_string(),
|
|
})?;
|
|
if strict_config {
|
|
validate_fragment_strictly(&source_ref, &raw_toml, &value, base_dir)?;
|
|
}
|
|
let resolved =
|
|
resolve_relative_paths_in_config_toml(value, base_dir.as_path()).map_err(|err| {
|
|
CloudConfigLayerError::Invalid {
|
|
fragment: source_ref.clone(),
|
|
message: err.to_string(),
|
|
}
|
|
})?;
|
|
layers.push(ConfigLayerEntry::new_with_raw_toml(
|
|
ConfigLayerSource::EnterpriseManaged {
|
|
id: fragment.id,
|
|
name: fragment.name,
|
|
},
|
|
resolved,
|
|
raw_toml,
|
|
base_dir.clone(),
|
|
));
|
|
}
|
|
|
|
// Bundle fragments arrive highest-priority first, while ConfigLayerStack
|
|
// folds lowest-priority to highest-priority.
|
|
layers.reverse();
|
|
Ok(layers)
|
|
}
|
|
|
|
fn validate_fragment_strictly(
|
|
source_ref: &CloudConfigFragmentSource,
|
|
raw_toml: &str,
|
|
value: &TomlValue,
|
|
base_dir: &AbsolutePathBuf,
|
|
) -> Result<(), CloudConfigLayerError> {
|
|
let _guard = AbsolutePathBufGuard::new(base_dir.as_path());
|
|
if let Some(config_error) = config_error_from_ignored_toml_value_fields_for_source_name::<
|
|
ConfigToml,
|
|
>(&source_ref.to_string(), raw_toml, value.clone())
|
|
{
|
|
return Err(CloudConfigLayerError::Invalid {
|
|
fragment: source_ref.clone(),
|
|
message: config_error.message,
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
impl From<CloudConfigLayerError> for io::Error {
|
|
fn from(error: CloudConfigLayerError) -> Self {
|
|
io::Error::new(io::ErrorKind::InvalidData, error)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "cloud_config_layers_tests.rs"]
|
|
mod tests;
|