From f71c6afc30031011fe0b4d757b981fd0301b2536 Mon Sep 17 00:00:00 2001 From: felixxia-oai Date: Fri, 23 Oct 2026 12:06:43 +0000 Subject: [PATCH] Add Guardian V3 feature configuration (#56459) ## What changed Register `guardianv3` as an under-development feature disabled by default. Accept either a boolean toggle or a table with an optional `enabled` field, and include both forms in the configuration schema. ## Testing Add coverage for boolean and table configuration, default behavior, and profile overrides, including inheritance when a profile omits `enabled`. GitOrigin-RevId: 75333be61b2e336e22fef907d6363b70f9f7225b --- codex-rs/config/src/schema.rs | 9 ++++++ codex-rs/core/config.schema.json | 26 +++++++++++++++++ codex-rs/features/src/feature_configs.rs | 14 +++++++++ codex-rs/features/src/lib.rs | 14 +++++++++ codex-rs/features/src/tests.rs | 37 ++++++++++++++++++++++++ 5 files changed, 100 insertions(+) diff --git a/codex-rs/config/src/schema.rs b/codex-rs/config/src/schema.rs index f7d6b6a7d2..fff24a98b3 100644 --- a/codex-rs/config/src/schema.rs +++ b/codex-rs/config/src/schema.rs @@ -90,6 +90,15 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema { ); continue; } + if feature.id == codex_features::Feature::GuardianV3 { + validation.properties.insert( + feature.key.to_string(), + schema_gen.subschema_for::>(), + ); + continue; + } if feature.id == codex_features::Feature::MultiAgentV2 { validation.properties.insert( feature.key.to_string(), diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 26df04230e..811de91311 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -988,6 +988,9 @@ "guardianv2_decisions_comparison": { "type": "boolean" }, + "guardianv3": { + "$ref": "#/definitions/FeatureToml_for_GuardianV3ConfigToml" + }, "hooks": { "type": "boolean" }, @@ -1694,6 +1697,16 @@ } ] }, + "FeatureToml_for_GuardianV3ConfigToml": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/GuardianV3ConfigToml" + } + ] + }, "FeatureToml_for_MultiAgentV2ConfigToml": { "anyOf": [ { @@ -2145,6 +2158,16 @@ ], "type": "string" }, + "GuardianV3ConfigToml": { + "additionalProperties": false, + "description": "User-configurable settings for Guardian V3.", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "type": "object" + }, "History": { "additionalProperties": false, "description": "Settings that govern if and what will be written to `~/.codex/history.jsonl`.", @@ -7506,6 +7529,9 @@ "guardianv2_decisions_comparison": { "type": "boolean" }, + "guardianv3": { + "$ref": "#/definitions/FeatureToml_for_GuardianV3ConfigToml" + }, "hooks": { "type": "boolean" }, diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index 0d0bcf5b98..71ee10be44 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -284,6 +284,20 @@ where Ok(feature) } +/// User-configurable settings for Guardian V3. +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct GuardianV3ConfigToml { + #[serde(skip_serializing_if = "Option::is_none")] + pub enabled: Option, +} + +impl FeatureConfig for GuardianV3ConfigToml { + fn enabled(&self) -> Option { + self.enabled + } +} + #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)] #[serde(deny_unknown_fields)] pub struct MultiAgentV2ConfigToml { diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index b2a3a4b830..294f2994d4 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -28,6 +28,7 @@ pub use feature_configs::GuardianV2ConfigToml; pub use feature_configs::GuardianV2ReviewScopeConfigToml; pub use feature_configs::GuardianV2TranscriptConfigToml; pub use feature_configs::GuardianV2TranscriptSource; +pub use feature_configs::GuardianV3ConfigToml; pub use feature_configs::MultiAgentV2ConfigToml; pub use feature_configs::NetworkProxyConfigToml; pub use feature_configs::NetworkProxyDomainPermissionToml; @@ -401,6 +402,8 @@ pub enum Feature { GuardianV2, /// Run Decisions alongside Guardian V2 for measurement without changing approvals. GuardianV2DecisionsComparison, + /// Enable Guardian V3 integrations. + GuardianV3, /// Removed compatibility flag for the unused Guardian extension prototype. GuardianExt, /// Enable persisted thread goals and automatic goal continuation. @@ -900,6 +903,8 @@ pub struct FeaturesToml { )] pub guardianv2: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] + pub guardianv3: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] pub multi_agent_v2: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub token_budget: Option>, @@ -962,6 +967,9 @@ impl FeaturesToml { { entries.insert(Feature::GuardianThreadContext.key().to_string(), enabled); } + if let Some(enabled) = self.guardianv3.as_ref().and_then(FeatureToml::enabled) { + entries.insert(Feature::GuardianV3.key().to_string(), enabled); + } if let Some(enabled) = self.multi_agent_v2.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::MultiAgentV2.key().to_string(), enabled); } @@ -1964,6 +1972,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::UnderDevelopment, default_enabled: false, }, + FeatureSpec { + id: Feature::GuardianV3, + key: "guardianv3", + stage: Stage::UnderDevelopment, + default_enabled: false, + }, FeatureSpec { id: Feature::GuardianExt, key: "guardian_ext", diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index a0fd13decf..c791fb4684 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -211,6 +211,43 @@ fn guardian_v2_feature_config_preserves_boolean_toggle() { ); } +#[test] +fn guardian_v3_feature_config_resolves_base_and_profile_toggles() { + for (base, profile, enabled) in [ + ("guardianv3 = true", "", true), + ("guardianv3 = false", "", false), + ("[guardianv3]", "", false), + ("[guardianv3]\nenabled = true", "", true), + ("[guardianv3]\nenabled = false", "", false), + ( + "[guardianv3]\nenabled = true", + "[guardianv3]\nenabled = false", + false, + ), + ( + "[guardianv3]\nenabled = false", + "[guardianv3]\nenabled = true", + true, + ), + ("[guardianv3]\nenabled = true", "[guardianv3]", true), + ] { + let base = toml::from_str::(base).expect("base features"); + let profile = toml::from_str::(profile).expect("profile features"); + let features = Features::from_sources( + FeatureConfigSource { + features: Some(&base), + ..Default::default() + }, + FeatureConfigSource { + features: Some(&profile), + ..Default::default() + }, + FeatureOverrides::default(), + ); + assert_eq!(features.enabled(Feature::GuardianV3), enabled); + } +} + #[test] fn guardian_thread_context_is_ignored_with_a_migration_notice() { let enabled_context = "[guardianv2]\nthread_context = true";