Rebrand approvals reviewer config to auto-review (#18504)

### Why

Auto-review is the user-facing name for the approvals reviewer, but the
config/API value still exposed the old `guardian_subagent` name. That
made new configs and generated schemas point users at Guardian
terminology even though the intended product surface is Auto-review.

This PR updates the external `approvals_reviewer` value while preserving
compatibility for existing configs and clients.

### What changed

- Makes `auto_review` the canonical serialized value for
`approvals_reviewer`.
- Keeps `guardian_subagent` accepted as a legacy alias.
- Keeps `user` accepted and serialized as `user`.
- Updates generated config and app-server schemas so
`approvals_reviewer` includes:
  - `user`
  - `auto_review`
  - `guardian_subagent`
- Updates app-server README docs for the reviewer value.
- Updates analytics and config requirements tests for the canonical
auto_review value.


### Compatibility

Existing configs and API payloads using:

```toml
approvals_reviewer = "guardian_subagent"
```

continue to load and map to the Auto-review reviewer behavior. 

New serialization emits: 
```toml
approvals_reviewer = "auto_review" 
```

This PR intentionally does not rename the [features].guardian_approval
key or broad internal Guardian symbols. Those are split out for a
follow-up PR to keep this migration small and avoid touching large
TUI/internal surfaces.

**Verification**
cargo test -p codex-protocol
approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent
cargo test -p codex-app-server-protocol
approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent
This commit is contained in:
Won Park authored and GitHub committed 2026-04-22 15:45:35 -07:00
1 parent 0e25c5ff42
commit 46142c3cb0
22 files changed
+197 -39

No files matched your search

@@ -1334,7 +1334,7 @@ fn subagent_thread_started_other_serializes_explicit_parent_thread_id() {
}, },
)); ));
let payload = serde_json::to_value(&event).expect("serialize guardian subagent event"); let payload = serde_json::to_value(&event).expect("serialize auto-review subagent event");
assert_eq!(payload["event_params"]["subagent_source"], "guardian"); assert_eq!(payload["event_params"]["subagent_source"], "guardian");
assert_eq!( assert_eq!(
payload["event_params"]["parent_thread_id"], payload["event_params"]["parent_thread_id"],
@@ -1758,7 +1758,7 @@ fn turn_event_serializes_expected_shape() {
reasoning_summary: Some("detailed".to_string()), reasoning_summary: Some("detailed".to_string()),
service_tier: "flex".to_string(), service_tier: "flex".to_string(),
approval_policy: "on-request".to_string(), approval_policy: "on-request".to_string(),
approvals_reviewer: "guardian_subagent".to_string(), approvals_reviewer: "auto_review".to_string(),
sandbox_network_access: true, sandbox_network_access: true,
collaboration_mode: Some("plan"), collaboration_mode: Some("plan"),
personality: Some("pragmatic".to_string()), personality: Some("pragmatic".to_string()),
@@ -1819,7 +1819,7 @@ fn turn_event_serializes_expected_shape() {
"reasoning_summary": "detailed", "reasoning_summary": "detailed",
"service_tier": "flex", "service_tier": "flex",
"approval_policy": "on-request", "approval_policy": "on-request",
"approvals_reviewer": "guardian_subagent", "approvals_reviewer": "auto_review",
"sandbox_network_access": true, "sandbox_network_access": true,
"collaboration_mode": "plan", "collaboration_mode": "plan",
"personality": "pragmatic", "personality": "pragmatic",
+2 -1
View File
@@ -13,9 +13,10 @@
"type": "string" "type": "string"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -5728,9 +5728,10 @@
"type": "object" "type": "object"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -605,9 +605,10 @@
"type": "object" "type": "object"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -97,9 +97,10 @@
"type": "object" "type": "object"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -2,9 +2,10 @@
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"definitions": { "definitions": {
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -6,9 +6,10 @@
"type": "string" "type": "string"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -9,9 +9,10 @@
"type": "string" "type": "string"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -6,9 +6,10 @@
"type": "string" "type": "string"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -9,9 +9,10 @@
"type": "string" "type": "string"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -6,9 +6,10 @@
"type": "string" "type": "string"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -9,9 +9,10 @@
"type": "string" "type": "string"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -6,9 +6,10 @@
"type": "string" "type": "string"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
@@ -5,8 +5,8 @@
/** /**
* Configures who approval requests are routed to for review. Examples * Configures who approval requests are routed to for review. Examples
* include sandbox escapes, blocked network access, MCP approval prompts, and * include sandbox escapes, blocked network access, MCP approval prompts, and
* ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully * ARC escalations. Defaults to `user`. `auto_review` uses a carefully
* prompted subagent to gather relevant context and apply a risk-based * prompted subagent to gather relevant context and apply a risk-based
* decision framework before approving or denying the request. * decision framework before approving or denying the request.
*/ */
export type ApprovalsReviewer = "user" | "guardian_subagent"; export type ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent";
@@ -101,6 +101,11 @@ use codex_protocol::user_input::TextElement as CoreTextElement;
use codex_protocol::user_input::UserInput as CoreUserInput; use codex_protocol::user_input::UserInput as CoreUserInput;
use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema; use schemars::JsonSchema;
use schemars::r#gen::SchemaGenerator;
use schemars::schema::InstanceType;
use schemars::schema::Metadata;
use schemars::schema::Schema;
use schemars::schema::SchemaObject;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
@@ -304,19 +309,54 @@ impl From<CoreAskForApproval> for AskForApproval {
} }
} }
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, TS)]
#[serde(rename_all = "snake_case")] #[ts(
#[ts(rename_all = "snake_case", export_to = "v2/")] type = r#""user" | "auto_review" | "guardian_subagent""#,
export_to = "v2/"
)]
/// Configures who approval requests are routed to for review. Examples /// Configures who approval requests are routed to for review. Examples
/// include sandbox escapes, blocked network access, MCP approval prompts, and /// include sandbox escapes, blocked network access, MCP approval prompts, and
/// ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully /// ARC escalations. Defaults to `user`. `auto_review` uses a carefully
/// prompted subagent to gather relevant context and apply a risk-based /// prompted subagent to gather relevant context and apply a risk-based
/// decision framework before approving or denying the request. /// decision framework before approving or denying the request.
pub enum ApprovalsReviewer { pub enum ApprovalsReviewer {
#[serde(rename = "user")]
User, User,
#[serde(rename = "auto_review", alias = "guardian_subagent")]
GuardianSubagent, GuardianSubagent,
} }
impl JsonSchema for ApprovalsReviewer {
fn schema_name() -> String {
"ApprovalsReviewer".to_string()
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
string_enum_schema_with_description(
&["user", "auto_review", "guardian_subagent"],
"Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
)
}
}
fn string_enum_schema_with_description(values: &[&str], description: &str) -> Schema {
let mut schema = SchemaObject {
instance_type: Some(InstanceType::String.into()),
metadata: Some(Box::new(Metadata {
description: Some(description.to_string()),
..Default::default()
})),
..Default::default()
};
schema.enum_values = Some(
values
.iter()
.map(|value| JsonValue::String((*value).to_string()))
.collect(),
);
Schema::Object(schema)
}
impl ApprovalsReviewer { impl ApprovalsReviewer {
pub fn to_core(self) -> CoreApprovalsReviewer { pub fn to_core(self) -> CoreApprovalsReviewer {
match self { match self {
@@ -7346,6 +7386,31 @@ mod tests {
absolute_path("readable") absolute_path("readable")
} }
#[test]
fn approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent() {
assert_eq!(
serde_json::to_string(&ApprovalsReviewer::User).expect("serialize reviewer"),
"\"user\""
);
assert_eq!(
serde_json::to_string(&ApprovalsReviewer::GuardianSubagent)
.expect("serialize reviewer"),
"\"auto_review\""
);
for value in ["user", "auto_review", "guardian_subagent"] {
let json = format!("\"{value}\"");
let reviewer: ApprovalsReviewer =
serde_json::from_str(&json).expect("deserialize reviewer");
let expected = if value == "user" {
ApprovalsReviewer::User
} else {
ApprovalsReviewer::GuardianSubagent
};
assert_eq!(expected, reviewer);
}
}
#[test] #[test]
fn thread_list_params_accepts_single_cwd() { fn thread_list_params_accepts_single_cwd() {
let params = serde_json::from_value::<ThreadListParams>(json!({ let params = serde_json::from_value::<ThreadListParams>(json!({
+1 -1
View File
@@ -529,7 +529,7 @@ You can optionally specify config overrides on the new turn. If specified, these
`approvalsReviewer` accepts: `approvalsReviewer` accepts:
- `"user"` — default. Review approval requests directly in the client. - `"user"` — default. Review approval requests directly in the client.
- `"guardian_subagent"` — route approval requests to a carefully prompted subagent, which gathers relevant context and applies a risk-based decision framework before approving or denying the request. - `"auto_review"` — route approval requests to a carefully prompted subagent, which gathers relevant context and applies a risk-based decision framework before approving or denying the request. The legacy value `"guardian_subagent"` is still accepted for compatibility.
```json ```json
{ "method": "turn/start", "id": 30, "params": { { "method": "turn/start", "id": 30, "params": {
+19 -3
View File
@@ -1649,7 +1649,7 @@ allowed_approvals_reviewers = ["user"]
let source: ConfigRequirementsToml = from_str( let source: ConfigRequirementsToml = from_str(
r#" r#"
allowed_approval_policies = ["on-request"] allowed_approval_policies = ["on-request"]
allowed_approvals_reviewers = ["guardian_subagent"] allowed_approvals_reviewers = ["auto_review"]
allowed_sandbox_modes = ["read-only"] allowed_sandbox_modes = ["read-only"]
"#, "#,
)?; )?;
@@ -1730,7 +1730,7 @@ allowed_approvals_reviewers = ["user"]
let source: ConfigRequirementsToml = from_str( let source: ConfigRequirementsToml = from_str(
r#" r#"
allowed_approval_policies = ["on-request"] allowed_approval_policies = ["on-request"]
allowed_approvals_reviewers = ["guardian_subagent"] allowed_approvals_reviewers = ["auto_review"]
allowed_sandbox_modes = ["read-only"] allowed_sandbox_modes = ["read-only"]
allowed_web_search_modes = ["cached"] allowed_web_search_modes = ["cached"]
enforce_residency = "us" enforce_residency = "us"
@@ -1830,7 +1830,7 @@ allowed_approvals_reviewers = ["user"]
#[test] #[test]
fn deserialize_allowed_approvals_reviewers() -> Result<()> { fn deserialize_allowed_approvals_reviewers() -> Result<()> {
let toml_str = r#" let toml_str = r#"
allowed_approvals_reviewers = ["guardian_subagent", "user"] allowed_approvals_reviewers = ["auto_review", "user"]
"#; "#;
let config: ConfigRequirementsToml = from_str(toml_str)?; let config: ConfigRequirementsToml = from_str(toml_str)?;
let requirements: ConfigRequirements = with_unknown_source(config).try_into()?; let requirements: ConfigRequirements = with_unknown_source(config).try_into()?;
@@ -1856,6 +1856,22 @@ allowed_approvals_reviewers = ["user"]
Ok(()) Ok(())
} }
#[test]
fn deserialize_legacy_allowed_approvals_reviewer() -> Result<()> {
let toml_str = r#"
allowed_approvals_reviewers = ["guardian_subagent", "user"]
"#;
let config: ConfigRequirementsToml = from_str(toml_str)?;
let requirements: ConfigRequirements = with_unknown_source(config).try_into()?;
assert_eq!(
requirements.approvals_reviewer.value(),
ApprovalsReviewer::GuardianSubagent
);
Ok(())
}
#[test] #[test]
fn empty_allowed_approvals_reviewers_is_rejected() -> Result<()> { fn empty_allowed_approvals_reviewers_is_rejected() -> Result<()> {
let toml_str = r#" let toml_str = r#"
+2 -1
View File
@@ -169,9 +169,10 @@
"type": "object" "type": "object"
}, },
"ApprovalsReviewer": { "ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
"enum": [ "enum": [
"user", "user",
"auto_review",
"guardian_subagent" "guardian_subagent"
], ],
"type": "string" "type": "string"
+3 -3
View File
@@ -1025,8 +1025,8 @@ async fn load_project_layers(
/// ///
/// If present, re-interpret `managed_config.toml` as a `requirements.toml` /// If present, re-interpret `managed_config.toml` as a `requirements.toml`
/// where each specified field is treated as a constraint. Most fields allow /// where each specified field is treated as a constraint. Most fields allow
/// only the specified value. `approvals_reviewer = "guardian_subagent"` also /// only the specified value. `approvals_reviewer = "auto_review"` also allows
/// allows `user` so people can opt out of the guardian reviewer. /// `user` so people can opt out of the auto-reviewer.
#[derive(Deserialize, Debug, Clone, Default, PartialEq)] #[derive(Deserialize, Debug, Clone, Default, PartialEq)]
struct LegacyManagedConfigToml { struct LegacyManagedConfigToml {
approval_policy: Option<AskForApproval>, approval_policy: Option<AskForApproval>,
@@ -1145,7 +1145,7 @@ foo = "xyzzy"
requirements.allowed_approvals_reviewers, requirements.allowed_approvals_reviewers,
Some(vec![ Some(vec![
ApprovalsReviewer::GuardianSubagent, ApprovalsReviewer::GuardianSubagent,
ApprovalsReviewer::User ApprovalsReviewer::User,
]) ])
); );
} }
+69 -5
View File
@@ -1,7 +1,13 @@
use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema; use schemars::JsonSchema;
use schemars::r#gen::SchemaGenerator;
use schemars::schema::InstanceType;
use schemars::schema::Metadata;
use schemars::schema::Schema;
use schemars::schema::SchemaObject;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use serde_json::Value;
use std::num::NonZeroU64; use std::num::NonZeroU64;
use std::time::Duration; use std::time::Duration;
use strum_macros::Display; use strum_macros::Display;
@@ -69,22 +75,54 @@ pub enum SandboxMode {
DangerFullAccess, DangerFullAccess,
} }
#[derive( #[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Display, TS)]
Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Display, JsonSchema, TS,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
#[ts(type = r#""user" | "auto_review" | "guardian_subagent""#)]
/// Configures who approval requests are routed to for review. Examples /// Configures who approval requests are routed to for review. Examples
/// include sandbox escapes, blocked network access, MCP approval prompts, and /// include sandbox escapes, blocked network access, MCP approval prompts, and
/// ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully /// ARC escalations. Defaults to `user`. `auto_review` uses a carefully
/// prompted subagent to gather relevant context and apply a risk-based /// prompted subagent to gather relevant context and apply a risk-based
/// decision framework before approving or denying the request. /// decision framework before approving or denying the request.
pub enum ApprovalsReviewer { pub enum ApprovalsReviewer {
#[default] #[default]
#[serde(rename = "user")]
User, User,
#[serde(rename = "auto_review", alias = "guardian_subagent")]
#[strum(serialize = "auto_review")]
GuardianSubagent, GuardianSubagent,
} }
impl JsonSchema for ApprovalsReviewer {
fn schema_name() -> String {
"ApprovalsReviewer".to_string()
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
string_enum_schema_with_description(
&["user", "auto_review", "guardian_subagent"],
"Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
)
}
}
fn string_enum_schema_with_description(values: &[&str], description: &str) -> Schema {
let mut schema = SchemaObject {
instance_type: Some(InstanceType::String.into()),
metadata: Some(Box::new(Metadata {
description: Some(description.to_string()),
..Default::default()
})),
..Default::default()
};
schema.enum_values = Some(
values
.iter()
.map(|value| Value::String((*value).to_string()))
.collect(),
);
Schema::Object(schema)
}
#[derive( #[derive(
Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Display, JsonSchema, TS, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Display, JsonSchema, TS,
)] )]
@@ -562,6 +600,32 @@ mod tests {
} }
} }
#[test]
fn approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent() {
assert_eq!(ApprovalsReviewer::User.to_string(), "user");
assert_eq!(
serde_json::to_string(&ApprovalsReviewer::User).expect("serialize reviewer"),
"\"user\""
);
assert_eq!(
serde_json::to_string(&ApprovalsReviewer::GuardianSubagent)
.expect("serialize reviewer"),
"\"auto_review\""
);
for value in ["user", "auto_review", "guardian_subagent"] {
let json = format!("\"{value}\"");
let reviewer: ApprovalsReviewer =
serde_json::from_str(&json).expect("deserialize reviewer");
let expected = if value == "user" {
ApprovalsReviewer::User
} else {
ApprovalsReviewer::GuardianSubagent
};
assert_eq!(expected, reviewer);
}
}
#[test] #[test]
fn tui_visible_collaboration_modes_match_mode_kind_visibility() { fn tui_visible_collaboration_modes_match_mode_kind_visibility() {
let expected = [ModeKind::Default, ModeKind::Plan]; let expected = [ModeKind::Default, ModeKind::Plan];
+3 -3
View File
@@ -1675,7 +1675,7 @@ async fn update_feature_flags_enabling_guardian_selects_guardian_approvals() ->
let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?; let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
assert!(config.contains("guardian_approval = true")); assert!(config.contains("guardian_approval = true"));
assert!(config.contains("approvals_reviewer = \"guardian_subagent\"")); assert!(config.contains("approvals_reviewer = \"auto_review\""));
assert!(config.contains("approval_policy = \"on-request\"")); assert!(config.contains("approval_policy = \"on-request\""));
assert!(config.contains("sandbox_mode = \"workspace-write\"")); assert!(config.contains("sandbox_mode = \"workspace-write\""));
Ok(()) Ok(())
@@ -1835,7 +1835,7 @@ async fn update_feature_flags_enabling_guardian_overrides_explicit_manual_review
); );
let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?; let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
assert!(config.contains("approvals_reviewer = \"guardian_subagent\"")); assert!(config.contains("approvals_reviewer = \"auto_review\""));
assert!(config.contains("guardian_approval = true")); assert!(config.contains("guardian_approval = true"));
assert!(config.contains("approval_policy = \"on-request\"")); assert!(config.contains("approval_policy = \"on-request\""));
assert!(config.contains("sandbox_mode = \"workspace-write\"")); assert!(config.contains("sandbox_mode = \"workspace-write\""));
@@ -1969,7 +1969,7 @@ async fn update_feature_flags_enabling_guardian_in_profile_sets_profile_auto_rev
); );
assert_eq!( assert_eq!(
profile_config.get("approvals_reviewer"), profile_config.get("approvals_reviewer"),
Some(&TomlValue::String("guardian_subagent".to_string())) Some(&TomlValue::String("auto_review".to_string()))
); );
Ok(()) Ok(())
} }
+2 -2
View File
@@ -690,7 +690,7 @@ mod tests {
rendered.contains("allowed_approval_policies: on-request (source: cloud requirements)") rendered.contains("allowed_approval_policies: on-request (source: cloud requirements)")
); );
assert!(rendered.contains( assert!(rendered.contains(
"allowed_approvals_reviewers: guardian_subagent (source: MDM managed_config.toml (legacy))" "allowed_approvals_reviewers: auto_review (source: MDM managed_config.toml (legacy))"
)); ));
assert!( assert!(
rendered.contains( rendered.contains(
@@ -745,7 +745,7 @@ mod tests {
let rendered = render_to_text(&render_debug_config_lines(&stack)); let rendered = render_to_text(&render_debug_config_lines(&stack));
assert!(rendered.contains( assert!(rendered.contains(
"allowed_approvals_reviewers: guardian_subagent (source: MDM managed_config.toml (legacy))" "allowed_approvals_reviewers: auto_review (source: MDM managed_config.toml (legacy))"
)); ));
assert!(!rendered.contains("Requirements:\n <none>")); assert!(!rendered.contains("Requirements:\n <none>"));
} }