config: add cloud-managed layer provenance

This commit is contained in:
hefuc-oai
2026-07-07 22:09:22 +00:00
parent 2589f7a52d
commit 4ec0da7e88
23 changed files with 497 additions and 8 deletions
@@ -7083,6 +7083,14 @@
}
]
},
"CloudManagedLayer": {
"description": "Position of a fragment within the cloud-managed configuration contract.",
"enum": [
"baseline",
"systemOverlay"
],
"type": "string"
},
"CodexErrorInfo": {
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
"oneOf": [
@@ -8160,6 +8168,42 @@
"title": "EnterpriseManagedConfigLayerSource",
"type": "object"
},
{
"description": "Config fragment delivered through the cloud-managed layer contract.",
"properties": {
"id": {
"description": "Stable identifier for the delivered fragment.",
"type": "string"
},
"layer": {
"allOf": [
{
"$ref": "#/definitions/v2/CloudManagedLayer"
}
],
"description": "Precedence tier that contains the fragment."
},
"name": {
"description": "Human-readable name for diagnostics and source attribution.",
"type": "string"
},
"type": {
"enum": [
"cloudManaged"
],
"title": "CloudManagedConfigLayerSourceType",
"type": "string"
}
},
"required": [
"id",
"layer",
"name",
"type"
],
"title": "CloudManagedConfigLayerSource",
"type": "object"
},
{
"description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory",
"properties": {
@@ -3323,6 +3323,14 @@
],
"title": "ClientRequest"
},
"CloudManagedLayer": {
"description": "Position of a fragment within the cloud-managed configuration contract.",
"enum": [
"baseline",
"systemOverlay"
],
"type": "string"
},
"CodexErrorInfo": {
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
"oneOf": [
@@ -4400,6 +4408,42 @@
"title": "EnterpriseManagedConfigLayerSource",
"type": "object"
},
{
"description": "Config fragment delivered through the cloud-managed layer contract.",
"properties": {
"id": {
"description": "Stable identifier for the delivered fragment.",
"type": "string"
},
"layer": {
"allOf": [
{
"$ref": "#/definitions/CloudManagedLayer"
}
],
"description": "Precedence tier that contains the fragment."
},
"name": {
"description": "Human-readable name for diagnostics and source attribution.",
"type": "string"
},
"type": {
"enum": [
"cloudManaged"
],
"title": "CloudManagedConfigLayerSourceType",
"type": "string"
}
},
"required": [
"id",
"layer",
"name",
"type"
],
"title": "CloudManagedConfigLayerSource",
"type": "object"
},
{
"description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory",
"properties": {
@@ -237,6 +237,14 @@
}
]
},
"CloudManagedLayer": {
"description": "Position of a fragment within the cloud-managed configuration contract.",
"enum": [
"baseline",
"systemOverlay"
],
"type": "string"
},
"Config": {
"additionalProperties": true,
"properties": {
@@ -555,6 +563,42 @@
"title": "EnterpriseManagedConfigLayerSource",
"type": "object"
},
{
"description": "Config fragment delivered through the cloud-managed layer contract.",
"properties": {
"id": {
"description": "Stable identifier for the delivered fragment.",
"type": "string"
},
"layer": {
"allOf": [
{
"$ref": "#/definitions/CloudManagedLayer"
}
],
"description": "Precedence tier that contains the fragment."
},
"name": {
"description": "Human-readable name for diagnostics and source attribution.",
"type": "string"
},
"type": {
"enum": [
"cloudManaged"
],
"title": "CloudManagedConfigLayerSourceType",
"type": "string"
}
},
"required": [
"id",
"layer",
"name",
"type"
],
"title": "CloudManagedConfigLayerSource",
"type": "object"
},
{
"description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory",
"properties": {
@@ -5,6 +5,14 @@
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"CloudManagedLayer": {
"description": "Position of a fragment within the cloud-managed configuration contract.",
"enum": [
"baseline",
"systemOverlay"
],
"type": "string"
},
"ConfigLayerMetadata": {
"properties": {
"name": {
@@ -100,6 +108,42 @@
"title": "EnterpriseManagedConfigLayerSource",
"type": "object"
},
{
"description": "Config fragment delivered through the cloud-managed layer contract.",
"properties": {
"id": {
"description": "Stable identifier for the delivered fragment.",
"type": "string"
},
"layer": {
"allOf": [
{
"$ref": "#/definitions/CloudManagedLayer"
}
],
"description": "Precedence tier that contains the fragment."
},
"name": {
"description": "Human-readable name for diagnostics and source attribution.",
"type": "string"
},
"type": {
"enum": [
"cloudManaged"
],
"title": "CloudManagedConfigLayerSourceType",
"type": "string"
}
},
"required": [
"id",
"layer",
"name",
"type"
],
"title": "CloudManagedConfigLayerSource",
"type": "object"
},
{
"description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory",
"properties": {
@@ -0,0 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Position of a fragment within the cloud-managed configuration contract.
*/
export type CloudManagedLayer = "baseline" | "systemOverlay";
@@ -2,6 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { CloudManagedLayer } from "./CloudManagedLayer";
export type ConfigLayerSource = { "type": "mdm", domain: string, key: string, } | { "type": "system",
/**
@@ -18,6 +19,18 @@ id: string,
* diagnostics so users know which cloud layer needs administrator
* attention.
*/
name: string, } | { "type": "cloudManaged",
/**
* Precedence tier that contains the fragment.
*/
layer: CloudManagedLayer,
/**
* Stable identifier for the delivered fragment.
*/
id: string,
/**
* Human-readable name for diagnostics and source attribution.
*/
name: string, } | { "type": "user",
/**
* This is the path to the user's config.toml file, though it is not
@@ -44,6 +44,7 @@ export type { CapabilityRootLocation } from "./CapabilityRootLocation";
export type { ChatgptAuthTokensRefreshParams } from "./ChatgptAuthTokensRefreshParams";
export type { ChatgptAuthTokensRefreshReason } from "./ChatgptAuthTokensRefreshReason";
export type { ChatgptAuthTokensRefreshResponse } from "./ChatgptAuthTokensRefreshResponse";
export type { CloudManagedLayer } from "./CloudManagedLayer";
export type { CodexErrorInfo } from "./CodexErrorInfo";
export type { CollabAgentState } from "./CollabAgentState";
export type { CollabAgentStatus } from "./CollabAgentStatus";
@@ -21,6 +21,15 @@ use std::collections::HashMap;
use std::path::PathBuf;
use ts_rs::TS;
/// Position of a fragment within the cloud-managed configuration contract.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
pub enum CloudManagedLayer {
Baseline,
SystemOverlay,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
@@ -56,6 +65,20 @@ pub enum ConfigLayerSource {
name: String,
},
/// Config fragment delivered through the cloud-managed layer contract.
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
CloudManaged {
/// Precedence tier that contains the fragment.
layer: CloudManagedLayer,
/// Stable identifier for the delivered fragment.
id: String,
/// Human-readable name for diagnostics and source attribution.
name: String,
},
/// User config layer from $CODEX_HOME/config.toml. This layer is special
/// in that it is expected to be:
/// - writable by the user
@@ -104,6 +127,10 @@ impl ConfigLayerSource {
ConfigLayerSource::Mdm { .. } => 0,
ConfigLayerSource::System { .. } => 10,
ConfigLayerSource::EnterpriseManaged { .. } => 15,
ConfigLayerSource::CloudManaged { layer, .. } => match layer {
CloudManagedLayer::Baseline => -10,
CloudManagedLayer::SystemOverlay => 15,
},
ConfigLayerSource::User { profile, .. } => {
if profile.is_some() {
21
@@ -72,6 +72,45 @@ fn test_absolute_path() -> AbsolutePathBuf {
absolute_path("readable")
}
#[test]
fn cloud_managed_config_layers_round_trip_with_provenance() {
for (managed_layer, serialized_layer) in [
(CloudManagedLayer::Baseline, "baseline"),
(CloudManagedLayer::SystemOverlay, "systemOverlay"),
] {
let layer = ConfigLayer {
name: ConfigLayerSource::CloudManaged {
layer: managed_layer,
id: "policy-1".to_string(),
name: "Workspace policy".to_string(),
},
version: "etag-1".to_string(),
config: json!({"model": "gpt-5"}),
disabled_reason: Some("conflicts with another managed fragment".to_string()),
};
let value = serde_json::to_value(&layer).expect("serialize cloud-managed layer");
assert_eq!(
value,
json!({
"name": {
"type": "cloudManaged",
"layer": serialized_layer,
"id": "policy-1",
"name": "Workspace policy",
},
"version": "etag-1",
"config": {"model": "gpt-5"},
"disabledReason": "conflicts with another managed fragment",
})
);
assert_eq!(
serde_json::from_value::<ConfigLayer>(value).expect("deserialize cloud-managed layer"),
layer
);
}
}
#[test]
fn thread_sources_round_trip_as_scalar_labels() {
for (source, label) in [
+13
View File
@@ -245,6 +245,19 @@ Example with notification opt-out:
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads, including multiple `desktop.*` edits.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, managed new-thread defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
Cloud-managed sources in `config/read` identify the precedence tier and fragment that supplied a value:
```json
{
"type": "cloudManaged",
"layer": "baseline",
"id": "policy-1",
"name": "Default model policy"
}
```
`layer` is either `baseline` or `systemOverlay`. The existing `enterpriseManaged` source remains supported.
### Example: Start or resume a thread
Start a fresh thread when you need a new Codex conversation.
+13
View File
@@ -1,6 +1,8 @@
use codex_app_server_protocol::CloudManagedLayer as ApiCloudManagedLayer;
use codex_app_server_protocol::ConfigLayer as ApiConfigLayer;
use codex_app_server_protocol::ConfigLayerMetadata as ApiConfigLayerMetadata;
use codex_app_server_protocol::ConfigLayerSource as ApiConfigLayerSource;
use codex_config::CloudManagedLayer;
use codex_config::ConfigLayer;
use codex_config::ConfigLayerMetadata;
use codex_config::ConfigLayerSource;
@@ -18,6 +20,13 @@ pub(crate) fn config_layer_source_to_api(source: ConfigLayerSource) -> ApiConfig
ConfigLayerSource::EnterpriseManaged { id, name } => {
ApiConfigLayerSource::EnterpriseManaged { id, name }
}
ConfigLayerSource::CloudManaged { layer, id, name } => {
let layer = match layer {
CloudManagedLayer::Baseline => ApiCloudManagedLayer::Baseline,
CloudManagedLayer::SystemOverlay => ApiCloudManagedLayer::SystemOverlay,
};
ApiConfigLayerSource::CloudManaged { layer, id, name }
}
ConfigLayerSource::User { file, profile } => ApiConfigLayerSource::User { file, profile },
ConfigLayerSource::Project { dot_codex_folder } => {
ApiConfigLayerSource::Project { dot_codex_folder }
@@ -61,3 +70,7 @@ pub(crate) fn config_layer_to_api(layer: ConfigLayer) -> ApiConfigLayer {
disabled_reason: layer.disabled_reason,
}
}
#[cfg(test)]
#[path = "config_layer_tests.rs"]
mod tests;
@@ -0,0 +1,39 @@
use super::*;
use pretty_assertions::assert_eq;
use serde_json::json;
#[test]
fn cloud_managed_layers_map_to_complete_api_objects() {
for (domain_layer, api_layer) in [
(CloudManagedLayer::Baseline, ApiCloudManagedLayer::Baseline),
(
CloudManagedLayer::SystemOverlay,
ApiCloudManagedLayer::SystemOverlay,
),
] {
let layer = ConfigLayer {
name: ConfigLayerSource::CloudManaged {
layer: domain_layer,
id: "policy-1".to_string(),
name: "Workspace policy".to_string(),
},
version: "etag-1".to_string(),
config: json!({"model": "gpt-5"}),
disabled_reason: Some("conflicts with another managed fragment".to_string()),
};
assert_eq!(
config_layer_to_api(layer),
ApiConfigLayer {
name: ApiConfigLayerSource::CloudManaged {
layer: api_layer,
id: "policy-1".to_string(),
name: "Workspace policy".to_string(),
},
version: "etag-1".to_string(),
config: json!({"model": "gpt-5"}),
disabled_reason: Some("conflicts with another managed fragment".to_string()),
}
);
}
}
@@ -633,6 +633,9 @@ fn override_message(layer: &ConfigLayerSource) -> String {
ConfigLayerSource::EnterpriseManaged { id: _, name } => {
format!("Overridden by enterprise-managed config: {name}")
}
ConfigLayerSource::CloudManaged { layer, id: _, name } => {
format!("Overridden by cloud-managed {layer} config: {name}")
}
ConfigLayerSource::Project { dot_codex_folder } => format!(
"Overridden by project config: {}/{CONFIG_TOML_FILE}",
dot_codex_folder.display(),
@@ -1,5 +1,24 @@
use codex_utils_absolute_path::AbsolutePathBuf;
use serde_json::Value as JsonValue;
use std::fmt;
/// Position of a fragment within the cloud-managed configuration contract.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CloudManagedLayer {
/// Lowest-precedence defaults supplied by the cloud bundle.
Baseline,
/// Managed overrides applied above system configuration.
SystemOverlay,
}
impl fmt::Display for CloudManagedLayer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CloudManagedLayer::Baseline => f.write_str("baseline"),
CloudManagedLayer::SystemOverlay => f.write_str("system-overlay"),
}
}
}
/// Provenance for one layer in the effective Codex configuration.
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -10,6 +29,12 @@ pub enum ConfigLayerSource {
System { file: AbsolutePathBuf },
/// Configuration delivered by an enterprise cloud bundle.
EnterpriseManaged { id: String, name: String },
/// Configuration delivered through the generic cloud-managed layer contract.
CloudManaged {
layer: CloudManagedLayer,
id: String,
name: String,
},
/// User configuration, optionally augmented by a selected profile.
User {
file: AbsolutePathBuf,
@@ -33,6 +58,10 @@ impl ConfigLayerSource {
ConfigLayerSource::Mdm { .. } => 0,
ConfigLayerSource::System { .. } => 10,
ConfigLayerSource::EnterpriseManaged { .. } => 15,
ConfigLayerSource::CloudManaged { layer, .. } => match layer {
CloudManagedLayer::Baseline => -10,
CloudManagedLayer::SystemOverlay => 15,
},
ConfigLayerSource::User { profile, .. } => {
if profile.is_some() {
21
@@ -83,6 +112,9 @@ pub fn format_config_layer_source(source: &ConfigLayerSource, config_toml_file:
ConfigLayerSource::EnterpriseManaged { id, name } => {
format!("enterprise-managed ({name}, {id})")
}
ConfigLayerSource::CloudManaged { layer, id, name } => {
format!("cloud-managed {layer} ({name}, {id})")
}
ConfigLayerSource::User { file, .. } => {
format!("user ({})", file.as_path().display())
}
@@ -101,3 +133,7 @@ pub fn format_config_layer_source(source: &ConfigLayerSource, config_toml_file:
}
}
}
#[cfg(test)]
#[path = "config_layer_source_tests.rs"]
mod tests;
@@ -0,0 +1,34 @@
use super::CloudManagedLayer;
use super::ConfigLayerSource;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
#[test]
fn cloud_managed_layers_have_their_contract_precedence() {
let baseline = ConfigLayerSource::CloudManaged {
layer: CloudManagedLayer::Baseline,
id: "baseline-1".to_string(),
name: "Baseline".to_string(),
};
let mdm = ConfigLayerSource::Mdm {
domain: "com.openai.codex".to_string(),
key: "config".to_string(),
};
let system = ConfigLayerSource::System {
file: test_path_buf("/etc/codex/config.toml").abs(),
};
let overlay = ConfigLayerSource::CloudManaged {
layer: CloudManagedLayer::SystemOverlay,
id: "overlay-1".to_string(),
name: "System overlay".to_string(),
};
let user = ConfigLayerSource::User {
file: test_path_buf("/home/test/.codex/config.toml").abs(),
profile: None,
};
assert!(baseline < mdm);
assert!(mdm < system);
assert!(system < overlay);
assert!(overlay < user);
}
@@ -17,6 +17,7 @@ use wildmatch::WildMatchPattern;
use super::requirements_exec_policy::RequirementsExecPolicy;
use super::requirements_exec_policy::RequirementsExecPolicyToml;
use crate::CloudManagedLayer;
use crate::Constrained;
use crate::ConstraintError;
use crate::ManagedHooksRequirementsToml;
@@ -43,6 +44,12 @@ pub enum RequirementSource {
id: String,
name: String,
},
/// A fragment delivered through the generic cloud-managed layer contract.
CloudManaged {
layer: CloudManagedLayer,
id: String,
name: String,
},
SystemRequirementsToml {
file: AbsolutePathBuf,
},
@@ -102,6 +109,9 @@ impl fmt::Display for RequirementSource {
RequirementSource::EnterpriseManaged { id, name } => {
write!(f, "enterprise-managed requirements {name} ({id})")
}
RequirementSource::CloudManaged { layer, id, name } => {
write!(f, "cloud-managed {layer} requirements {name} ({id})")
}
RequirementSource::SystemRequirementsToml { file } => {
write!(f, "{}", file.as_path().display())
}
+1
View File
@@ -256,6 +256,7 @@ fn config_path_for_layer(layer: &ConfigLayerEntry, config_toml_file: &str) -> Op
ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => Some(file.to_path_buf()),
ConfigLayerSource::Mdm { .. }
| ConfigLayerSource::EnterpriseManaged { .. }
| ConfigLayerSource::CloudManaged { .. }
| ConfigLayerSource::SessionFlags
| ConfigLayerSource::LegacyManagedConfigTomlFromMdm => None,
}
+1
View File
@@ -48,6 +48,7 @@ pub use cloud_config_layers::cloud_config_layers_from_fragments;
pub use codex_protocol::config_types::ProfileV2Name;
pub use codex_protocol::config_types::ProfileV2NameParseError;
pub use codex_utils_absolute_path::AbsolutePathBuf;
pub use config_layer_source::CloudManagedLayer;
pub use config_layer_source::ConfigLayer;
pub use config_layer_source::ConfigLayerMetadata;
pub use config_layer_source::ConfigLayerSource;
+1
View File
@@ -209,6 +209,7 @@ impl ConfigLayerEntry {
ConfigLayerSource::Mdm { .. } => None,
ConfigLayerSource::System { file } => file.parent(),
ConfigLayerSource::EnterpriseManaged { .. } => None,
ConfigLayerSource::CloudManaged { .. } => None,
ConfigLayerSource::User { file, .. } => file.parent(),
ConfigLayerSource::Project { dot_codex_folder } => Some(dot_codex_folder.clone()),
ConfigLayerSource::SessionFlags => None,
+1
View File
@@ -361,6 +361,7 @@ fn skill_roots_from_layer_stack_inner(
}
ConfigLayerSource::Mdm { .. }
| ConfigLayerSource::EnterpriseManaged { .. }
| ConfigLayerSource::CloudManaged { .. }
| ConfigLayerSource::SessionFlags
| ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. }
| ConfigLayerSource::LegacyManagedConfigTomlFromMdm => {}
+58
View File
@@ -291,6 +291,13 @@ fn fallback_managed_hooks_source_path(
"<enterprise-managed:{name}:{id}>/requirements.toml"
))
}
Some(RequirementSource::CloudManaged { layer, id, name }) => {
let name = escape_xml_text(name);
let id = escape_xml_text(id);
synthetic_layer_path(&format!(
"<cloud-managed:{layer}:{name}:{id}>/requirements.toml"
))
}
Some(RequirementSource::LegacyManagedConfigTomlFromMdm) => {
synthetic_layer_path("<legacy-managed-config.toml-mdm>/managed_config.toml")
}
@@ -378,6 +385,9 @@ fn config_toml_source_path(layer: &ConfigLayerEntry) -> AbsolutePathBuf {
ConfigLayerSource::EnterpriseManaged { id, name } => synthetic_layer_path(&format!(
"<enterprise-managed:{name}:{id}>/{CONFIG_TOML_FILE}"
)),
ConfigLayerSource::CloudManaged { layer, id, name } => synthetic_layer_path(&format!(
"<cloud-managed:{layer}:{name}:{id}>/{CONFIG_TOML_FILE}"
)),
ConfigLayerSource::LegacyManagedConfigTomlFromMdm => {
synthetic_layer_path("<legacy-managed-config.toml-mdm>/managed_config.toml")
}
@@ -619,6 +629,7 @@ fn hook_metadata_for_config_layer_source(source: &ConfigLayerSource) -> (HookSou
ConfigLayerSource::Project { .. } => (HookSource::Project, false),
ConfigLayerSource::Mdm { .. } => (HookSource::Mdm, true),
ConfigLayerSource::EnterpriseManaged { .. } => (HookSource::CloudManagedConfig, true),
ConfigLayerSource::CloudManaged { .. } => (HookSource::CloudManagedConfig, true),
ConfigLayerSource::SessionFlags => (HookSource::SessionFlags, false),
ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => {
(HookSource::LegacyManagedConfigFile, true)
@@ -647,12 +658,14 @@ fn hook_source_for_requirement_source(source: Option<&RequirementSource>) -> Hoo
hook_source_for_requirement_source(sources.first())
}
Some(RequirementSource::EnterpriseManaged { .. }) => HookSource::CloudRequirements,
Some(RequirementSource::CloudManaged { .. }) => HookSource::CloudRequirements,
Some(RequirementSource::Unknown) | None => HookSource::Unknown,
}
}
#[cfg(test)]
mod tests {
use codex_config::CloudManagedLayer;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerSource;
use codex_config::HookEventsToml;
@@ -748,6 +761,43 @@ mod tests {
assert!(!source_path.contains("Name <Admin>"));
}
#[test]
fn cloud_managed_synthetic_path_identifies_layer_and_escapes_display_fields() {
let source = RequirementSource::CloudManaged {
layer: CloudManagedLayer::SystemOverlay,
id: "id<&>".to_string(),
name: "Name <Admin> & \"Ops\"".to_string(),
};
let source_path = super::fallback_managed_hooks_source_path(Some(&source));
let source_path = source_path.display().to_string();
assert!(source_path.contains("cloud-managed:system-overlay"));
assert!(source_path.contains("Name &lt;Admin&gt; &amp; &quot;Ops&quot;"));
assert!(source_path.contains("id&lt;&amp;&gt;"));
assert!(!source_path.contains("Name <Admin>"));
assert_eq!(
super::hook_source_for_requirement_source(Some(&source)),
HookSource::CloudRequirements
);
}
#[test]
fn cloud_managed_config_source_path_identifies_layer() {
let layer = ConfigLayerEntry::new(
ConfigLayerSource::CloudManaged {
layer: CloudManagedLayer::Baseline,
id: "layer-1".to_string(),
name: "Defaults".to_string(),
},
TomlValue::Table(Default::default()),
);
let source_path = super::config_toml_source_path(&layer).display().to_string();
assert!(source_path.contains("cloud-managed:baseline:Defaults:layer-1"));
assert!(source_path.ends_with("config.toml"));
}
fn command_group(matcher: Option<&str>) -> MatcherGroup {
MatcherGroup {
matcher: matcher.map(str::to_string),
@@ -1073,6 +1123,14 @@ mod tests {
}),
(HookSource::CloudManagedConfig, true),
);
assert_eq!(
super::hook_metadata_for_config_layer_source(&ConfigLayerSource::CloudManaged {
layer: CloudManagedLayer::SystemOverlay,
id: "cfg_456".to_string(),
name: "Managed overlay".to_string(),
}),
(HookSource::CloudManagedConfig, true),
);
assert_eq!(
super::hook_metadata_for_config_layer_source(&ConfigLayerSource::SessionFlags),
(HookSource::SessionFlags, false),
+20 -7
View File
@@ -309,6 +309,7 @@ fn render_non_file_layer_details(layer: &ConfigLayerEntry) -> Vec<Line<'static>>
ConfigLayerSource::SessionFlags => render_session_flag_details(&layer.config),
ConfigLayerSource::Mdm { .. }
| ConfigLayerSource::EnterpriseManaged { .. }
| ConfigLayerSource::CloudManaged { .. }
| ConfigLayerSource::LegacyManagedConfigTomlFromMdm => render_non_file_layer_value(layer),
ConfigLayerSource::System { .. }
| ConfigLayerSource::User { .. }
@@ -373,6 +374,7 @@ fn non_file_layer_value_label(source: &ConfigLayerSource) -> &'static str {
"MDM value"
}
ConfigLayerSource::EnterpriseManaged { .. } => "Enterprise-managed config value",
ConfigLayerSource::CloudManaged { .. } => "Cloud-managed config value",
ConfigLayerSource::SessionFlags
| ConfigLayerSource::System { .. }
| ConfigLayerSource::User { .. }
@@ -557,6 +559,7 @@ mod tests {
use super::session_all_proxy_url;
use crate::legacy_core::config::Permissions;
use codex_app_server_protocol::AskForApproval;
use codex_config::CloudManagedLayer;
use codex_config::ConfigLayerEntry;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
@@ -800,13 +803,23 @@ mod tests {
absolute_path("/home/alice/.codex/config.toml")
};
let stack = ConfigLayerStack::new(
vec![ConfigLayerEntry::new(
ConfigLayerSource::User {
file: user_file,
profile: None,
},
empty_toml_table(),
)],
vec![
ConfigLayerEntry::new(
ConfigLayerSource::CloudManaged {
layer: CloudManagedLayer::Baseline,
id: "policy-1".to_string(),
name: "Baseline policy".to_string(),
},
TomlValue::Table(toml::toml! { model = "managed" }),
),
ConfigLayerEntry::new(
ConfigLayerSource::User {
file: user_file,
profile: None,
},
empty_toml_table(),
),
],
requirements,
requirements_toml,
)
@@ -5,7 +5,9 @@ expression: rendered.as_str()
/debug-config
Config layer stack (lowest precedence first):
1. user (/home/alice/.codex/config.toml) (enabled)
1. cloud-managed baseline (Baseline policy, policy-1) (enabled)
Cloud-managed config value: { model = "managed" }
2. user (/home/alice/.codex/config.toml) (enabled)
Requirements:
- allowed_approval_policies: on-request (source: MDM managed_config.toml (legacy))