From 162f4e3183e00010680c30d1d41ddf58e29517ed Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 28 Apr 2026 17:10:42 -0700 Subject: [PATCH 1/2] core tests: migrate safety check turns to profiles (#20024) ## Why This stack is retiring direct `SandboxPolicy` construction from tests so core coverage exercises the same `PermissionProfile` turn path used by runtime code. `safety_check_downgrade.rs` still submitted each test turn as `SandboxPolicy::DangerFullAccess` with no permission profile, even though the tests are about model verification/reroute behavior rather than legacy sandbox conversion. ## What Changed - Adds a local `disabled_text_turn()` helper that derives both the compatibility `sandbox_policy` and canonical `permission_profile` from `PermissionProfile::Disabled`. - Replaces repeated hand-built `Op::UserTurn` literals in `codex-rs/core/tests/suite/safety_check_downgrade.rs` with that helper. - Removes all `SandboxPolicy` references from the safety-check suite, reducing the remaining `codex-rs/core/tests` files that mention `SandboxPolicy` from 17 to 16. ## Verification - `cargo check -p codex-core --tests` --- .../tests/suite/safety_check_downgrade.rs | 171 ++++-------------- 1 file changed, 37 insertions(+), 134 deletions(-) diff --git a/codex-rs/core/tests/suite/safety_check_downgrade.rs b/codex-rs/core/tests/suite/safety_check_downgrade.rs index 2aa887a174..eb70dafe5f 100644 --- a/codex-rs/core/tests/suite/safety_check_downgrade.rs +++ b/codex-rs/core/tests/suite/safety_check_downgrade.rs @@ -1,5 +1,6 @@ use anyhow::Result; use codex_protocol::models::ContentItem; +use codex_protocol::models::PermissionProfile; use codex_protocol::models::ResponseItem; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::CodexErrorInfo; @@ -7,7 +8,6 @@ use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ModelRerouteReason; use codex_protocol::protocol::ModelVerification; use codex_protocol::protocol::Op; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::user_input::UserInput; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_function_call; @@ -20,7 +20,9 @@ use core_test_support::responses::sse_completed; use core_test_support::responses::sse_response; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; +use core_test_support::test_codex::TestCodex; use core_test_support::test_codex::test_codex; +use core_test_support::test_codex::turn_permission_fields; use core_test_support::wait_for_event; use pretty_assertions::assert_eq; use wiremock::ResponseTemplate; @@ -32,6 +34,30 @@ const TRUSTED_ACCESS_FOR_CYBER_VERIFICATION: &str = "trusted_access_for_cyber"; const CYBER_POLICY_MESSAGE: &str = "This request has been flagged for potentially high-risk cyber activity."; +fn disabled_text_turn(test: &TestCodex, text: &str) -> Op { + let (sandbox_policy, permission_profile) = + turn_permission_fields(PermissionProfile::Disabled, test.cwd_path()); + Op::UserTurn { + environments: None, + items: vec![UserInput::Text { + text: text.to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: AskForApproval::Never, + approvals_reviewer: None, + sandbox_policy, + permission_profile, + model: REQUESTED_MODEL.to_string(), + effort: test.config.model_reasoning_effort, + summary: None, + service_tier: None, + collaboration_mode: None, + personality: None, + } +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn openai_model_header_mismatch_emits_warning_event_and_warning_item() -> Result<()> { skip_if_no_network!(Ok(())); @@ -45,25 +71,7 @@ async fn openai_model_header_mismatch_emits_warning_event_and_warning_item() -> let test = builder.build(&server).await?; test.codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "trigger safety check".to_string(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: REQUESTED_MODEL.to_string(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) + .submit(disabled_text_turn(&test, "trigger safety check")) .await?; let reroute = wait_for_event(&test.codex, |event| { @@ -141,25 +149,7 @@ async fn cyber_policy_response_emits_typed_error_without_retry() -> Result<()> { let test = builder.build(&server).await?; test.codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "trigger cyber policy error".to_string(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: REQUESTED_MODEL.to_string(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) + .submit(disabled_text_turn(&test, "trigger cyber policy error")) .await?; let error = wait_for_event(&test.codex, |event| matches!(event, EventMsg::Error(_))).await; @@ -198,25 +188,7 @@ async fn response_model_field_mismatch_emits_warning_when_header_matches_request let test = builder.build(&server).await?; test.codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "trigger response model check".to_string(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: REQUESTED_MODEL.to_string(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) + .submit(disabled_text_turn(&test, "trigger response model check")) .await?; let reroute = wait_for_event(&test.codex, |event| { @@ -286,25 +258,7 @@ async fn openai_model_header_mismatch_only_emits_one_warning_per_turn() -> Resul let test = builder.build(&server).await?; test.codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "trigger follow-up turn".to_string(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: REQUESTED_MODEL.to_string(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) + .submit(disabled_text_turn(&test, "trigger follow-up turn")) .await?; let mut warning_count = 0; @@ -338,25 +292,7 @@ async fn openai_model_header_casing_only_mismatch_does_not_warn() -> Result<()> let test = builder.build(&server).await?; test.codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "trigger casing check".to_string(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: REQUESTED_MODEL.to_string(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) + .submit(disabled_text_turn(&test, "trigger casing check")) .await?; let mut reroute_count = 0; @@ -399,25 +335,7 @@ async fn model_verification_emits_structured_event_without_reroute_or_warning() let test = builder.build(&server).await?; test.codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "trigger model verification".to_string(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: REQUESTED_MODEL.to_string(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) + .submit(disabled_text_turn(&test, "trigger model verification")) .await?; let mut verification_count = 0; @@ -493,25 +411,10 @@ async fn model_verification_only_emits_once_per_turn() -> Result<()> { let test = builder.build(&server).await?; test.codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "trigger follow-up model verification".to_string(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: test.cwd_path().to_path_buf(), - approval_policy: AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: REQUESTED_MODEL.to_string(), - effort: test.config.model_reasoning_effort, - summary: None, - service_tier: None, - collaboration_mode: None, - personality: None, - }) + .submit(disabled_text_turn( + &test, + "trigger follow-up model verification", + )) .await?; let mut verification_count = 0; From dc3a9abb379ff07fc5c383f91832c9abc0f6634f Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 28 Apr 2026 17:10:47 -0700 Subject: [PATCH 2/2] core tests: migrate plan item turns to profiles --- codex-rs/core/tests/suite/items.rs | 152 +++++++++++------------------ 1 file changed, 57 insertions(+), 95 deletions(-) diff --git a/codex-rs/core/tests/suite/items.rs b/codex-rs/core/tests/suite/items.rs index db4f7d7584..053f03ca90 100644 --- a/codex-rs/core/tests/suite/items.rs +++ b/codex-rs/core/tests/suite/items.rs @@ -6,7 +6,9 @@ use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; use codex_protocol::items::AgentMessageContent; use codex_protocol::items::TurnItem; +use codex_protocol::models::PermissionProfile; use codex_protocol::models::WebSearchAction; +use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::ItemCompletedEvent; use codex_protocol::protocol::ItemStartedEvent; @@ -33,12 +35,42 @@ use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::TestCodex; use core_test_support::test_codex::test_codex; +use core_test_support::test_codex::turn_permission_fields; use core_test_support::wait_for_event; use core_test_support::wait_for_event_match; use pretty_assertions::assert_eq; use std::path::Path; use std::path::PathBuf; +fn disabled_plan_turn( + text: &str, + model: String, + collaboration_mode: CollaborationMode, +) -> anyhow::Result { + let cwd = std::env::current_dir()?; + let (sandbox_policy, permission_profile) = + turn_permission_fields(PermissionProfile::Disabled, cwd.as_path()); + Ok(Op::UserTurn { + environments: None, + items: vec![UserInput::Text { + text: text.into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd, + approval_policy: AskForApproval::Never, + approvals_reviewer: None, + sandbox_policy, + permission_profile, + model, + effort: None, + summary: None, + service_tier: None, + collaboration_mode: Some(collaboration_mode), + personality: None, + }) +} + fn image_generation_artifact_path(codex_home: &Path, session_id: &str, call_id: &str) -> PathBuf { fn sanitize(value: &str) -> String { let mut sanitized: String = value @@ -529,25 +561,11 @@ async fn plan_mode_emits_plan_item_from_proposed_plan_block() -> anyhow::Result< }; codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "please plan".into(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: std::env::current_dir()?, - approval_policy: codex_protocol::protocol::AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(collaboration_mode), - personality: None, - }) + .submit(disabled_plan_turn( + "please plan", + session_configured.model.clone(), + collaboration_mode, + )?) .await?; let plan_delta = wait_for_event_match(&codex, |ev| match ev { @@ -608,25 +626,11 @@ async fn plan_mode_strips_plan_from_agent_messages() -> anyhow::Result<()> { }; codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "please plan".into(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: std::env::current_dir()?, - approval_policy: codex_protocol::protocol::AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(collaboration_mode), - personality: None, - }) + .submit(disabled_plan_turn( + "please plan", + session_configured.model.clone(), + collaboration_mode, + )?) .await?; let mut agent_deltas = Vec::new(); @@ -719,25 +723,11 @@ async fn plan_mode_streaming_citations_are_stripped_across_added_deltas_and_done }; codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "please plan with citations".into(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: std::env::current_dir()?, - approval_policy: codex_protocol::protocol::AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(collaboration_mode), - personality: None, - }) + .submit(disabled_plan_turn( + "please plan with citations", + session_configured.model.clone(), + collaboration_mode, + )?) .await?; let mut agent_started = None; @@ -908,25 +898,11 @@ async fn plan_mode_streaming_proposed_plan_tag_split_across_added_and_delta_is_p }; codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "please plan".into(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: std::env::current_dir()?, - approval_policy: codex_protocol::protocol::AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(collaboration_mode), - personality: None, - }) + .submit(disabled_plan_turn( + "please plan", + session_configured.model.clone(), + collaboration_mode, + )?) .await?; let mut agent_started = None; @@ -1024,25 +1000,11 @@ async fn plan_mode_handles_missing_plan_close_tag() -> anyhow::Result<()> { }; codex - .submit(Op::UserTurn { - environments: None, - items: vec![UserInput::Text { - text: "please plan".into(), - text_elements: Vec::new(), - }], - final_output_json_schema: None, - cwd: std::env::current_dir()?, - approval_policy: codex_protocol::protocol::AskForApproval::Never, - approvals_reviewer: None, - sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess, - permission_profile: None, - model: session_configured.model.clone(), - effort: None, - summary: None, - service_tier: None, - collaboration_mode: Some(collaboration_mode), - personality: None, - }) + .submit(disabled_plan_turn( + "please plan", + session_configured.model.clone(), + collaboration_mode, + )?) .await?; let mut plan_delta = None;