mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
config: add cloud-managed layer provenance
This commit is contained in:
+44
@@ -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": {
|
||||
|
||||
+44
@@ -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 [
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -361,6 +361,7 @@ fn skill_roots_from_layer_stack_inner(
|
||||
}
|
||||
ConfigLayerSource::Mdm { .. }
|
||||
| ConfigLayerSource::EnterpriseManaged { .. }
|
||||
| ConfigLayerSource::CloudManaged { .. }
|
||||
| ConfigLayerSource::SessionFlags
|
||||
| ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. }
|
||||
| ConfigLayerSource::LegacyManagedConfigTomlFromMdm => {}
|
||||
|
||||
@@ -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 <Admin> & "Ops""));
|
||||
assert!(source_path.contains("id<&>"));
|
||||
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),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
+3
-1
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user