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
This commit is contained in:
felixxia-oai
2026-10-23 12:16:36 +00:00
committed by copyberry
parent d8acdf1515
commit f71c6afc30
5 changed files with 100 additions and 0 deletions
+9
View File
@@ -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::<codex_features::FeatureToml<
codex_features::GuardianV3ConfigToml,
>>(),
);
continue;
}
if feature.id == codex_features::Feature::MultiAgentV2 {
validation.properties.insert(
feature.key.to_string(),
+26
View File
@@ -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"
},
+14
View File
@@ -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<bool>,
}
impl FeatureConfig for GuardianV3ConfigToml {
fn enabled(&self) -> Option<bool> {
self.enabled
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MultiAgentV2ConfigToml {
+14
View File
@@ -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<FeatureToml<GuardianV2ConfigToml>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub guardianv3: Option<FeatureToml<GuardianV3ConfigToml>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multi_agent_v2: Option<FeatureToml<MultiAgentV2ConfigToml>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub token_budget: Option<FeatureToml<TokenBudgetConfigToml>>,
@@ -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",
+37
View File
@@ -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::<FeaturesToml>(base).expect("base features");
let profile = toml::from_str::<FeaturesToml>(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";