mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Source Guardian REPL policy from model metadata (#41919)
## What changed - Add `auto_review.node_repl_policy` to model messages for `node_repl` and `cua_repl` reviews. - Use the reviewer model's configured policy, falling back to the bundled policy when the field is absent and skipping injection when it is explicitly empty. - Include the effective policy in Guardian session reuse checks and reject unsafe parent-model fallback transitions that would change it. ## Testing - Cover catalog, bundled, and explicitly empty policies across Node and CUA review paths. - Verify that policy changes invalidate cached reviewer sessions and incompatible fallback transitions. GitOrigin-RevId: 51c0bdfed9b464091b6eb47d91ca5a0990d7a315
This commit is contained in:
1 parent
746798b2f7
commit
865bbf9a69
11 files changed
+144
-24
No files matched your search
@@ -243,6 +243,7 @@ async fn server_initiated_mcp_elicitation_can_require_synchronous_auto_review(
|
||||
.auto_review = Some(AutoReviewMessages {
|
||||
policy: None,
|
||||
policy_template: None,
|
||||
node_repl_policy: None,
|
||||
rejection_instructions: Some(REJECTION_INSTRUCTIONS.to_string()),
|
||||
timeout_instructions: None,
|
||||
});
|
||||
|
||||
@@ -655,15 +655,19 @@ async fn guardian_session_prewarms_and_is_reused_for_first_review(
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[test_case("first_node", "node_repl"; "injects_policy_for_first_node_action")]
|
||||
#[test_case("shell_then_nodes", "node_repl"; "reuses_shell_reviewer_and_injects_policy_once")]
|
||||
#[test_case("ineligible_node", "node_repl"; "omits_policy_for_ineligible_parent_model")]
|
||||
#[test_case("first_node", "cua_repl"; "injects_policy_for_first_cua_action")]
|
||||
#[test_case("shell_then_nodes", "cua_repl"; "reuses_shell_reviewer_and_injects_cua_policy_once")]
|
||||
#[test_case("ineligible_node", "cua_repl"; "omits_cua_policy_for_ineligible_parent_model")]
|
||||
#[test_case("first_node", "node_repl", None; "injects_policy_for_first_node_action")]
|
||||
#[test_case("shell_then_nodes", "node_repl", None; "reuses_shell_reviewer_and_injects_policy_once")]
|
||||
#[test_case("ineligible_node", "node_repl", None; "omits_policy_for_ineligible_parent_model")]
|
||||
#[test_case("first_node", "cua_repl", None; "injects_policy_for_first_cua_action")]
|
||||
#[test_case("shell_then_nodes", "cua_repl", None; "reuses_shell_reviewer_and_injects_cua_policy_once")]
|
||||
#[test_case("ineligible_node", "cua_repl", None; "omits_cua_policy_for_ineligible_parent_model")]
|
||||
#[test_case("shell_then_nodes", "node_repl", Some("Catalog REPL policy."); "catalog_node_policy")]
|
||||
#[test_case("shell_then_nodes", "cua_repl", Some("Catalog REPL policy."); "catalog_cua_policy")]
|
||||
#[test_case("first_node", "node_repl", Some(""); "empty_catalog_policy")]
|
||||
async fn guardian_node_repl_policy_follows_production_approval_path(
|
||||
scenario: &str,
|
||||
repl_server: &'static str,
|
||||
node_repl_policy: Option<&'static str>,
|
||||
) -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
skip_if_sandbox!(Ok(()));
|
||||
@@ -676,8 +680,33 @@ async fn guardian_node_repl_policy_follows_production_approval_path(
|
||||
let mcp_server_bin = remote_aware_stdio_server_bin()?;
|
||||
let node_repl_auto_review_required = scenario != "ineligible_node";
|
||||
let mut builder = test_codex()
|
||||
.with_model_info_override("gpt-5.6-luna", move |model| {
|
||||
model
|
||||
.model_messages
|
||||
.as_mut()
|
||||
.expect("reviewer model messages")
|
||||
.auto_review = Some(AutoReviewMessages {
|
||||
policy: None,
|
||||
policy_template: None,
|
||||
node_repl_policy: node_repl_policy.map(str::to_string),
|
||||
rejection_instructions: None,
|
||||
timeout_instructions: None,
|
||||
});
|
||||
})
|
||||
.with_model_info_override("gpt-5.4", move |model| {
|
||||
model.node_repl_auto_review_required = node_repl_auto_review_required;
|
||||
model.auto_review_model_override = Some("gpt-5.6-luna".to_string());
|
||||
model
|
||||
.model_messages
|
||||
.as_mut()
|
||||
.expect("acting model messages")
|
||||
.auto_review = Some(AutoReviewMessages {
|
||||
policy: None,
|
||||
policy_template: None,
|
||||
node_repl_policy: Some("Acting-model REPL policy.".to_string()),
|
||||
rejection_instructions: None,
|
||||
timeout_instructions: None,
|
||||
});
|
||||
})
|
||||
.with_config(move |config| {
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
@@ -754,7 +783,8 @@ async fn guardian_node_repl_policy_follows_production_approval_path(
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(guardian_requests.len(), actions.len());
|
||||
|
||||
let policy = include_str!("../../assets/guardian/node_repl_policy.md");
|
||||
let bundled_policy = include_str!("../../assets/guardian/node_repl_policy.md");
|
||||
let policy = node_repl_policy.unwrap_or(bundled_policy);
|
||||
let first_guardian_thread = guardian_requests[0].body_json()["client_metadata"]["thread_id"]
|
||||
.as_str()
|
||||
.expect("Guardian reviewer thread id")
|
||||
@@ -766,15 +796,19 @@ async fn guardian_node_repl_policy_follows_production_approval_path(
|
||||
"shell and Node approvals must reuse the same Guardian reviewer"
|
||||
);
|
||||
let expected = usize::from(
|
||||
node_repl_auto_review_required && !(scenario == "shell_then_nodes" && index == 0),
|
||||
node_repl_auto_review_required
|
||||
&& !policy.is_empty()
|
||||
&& !(scenario == "shell_then_nodes" && index == 0),
|
||||
);
|
||||
assert_eq!(
|
||||
request
|
||||
.message_input_texts("developer")
|
||||
.iter()
|
||||
.filter(|text| text.as_str() == policy)
|
||||
.count(),
|
||||
expected,
|
||||
.into_iter()
|
||||
.filter(|text| {
|
||||
[bundled_policy, "Acting-model REPL policy.", policy].contains(&text.as_str())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
vec![policy.to_string(); expected],
|
||||
"Node REPL policy must appear exactly once on eligible Node reviews"
|
||||
);
|
||||
if expected == 1 && (index == 0 || scenario == "shell_then_nodes" && index == 1) {
|
||||
@@ -1243,6 +1277,7 @@ async fn guardian_denial_rejects_tool_call_with_rationale(
|
||||
.auto_review = Some(AutoReviewMessages {
|
||||
policy: None,
|
||||
policy_template: None,
|
||||
node_repl_policy: None,
|
||||
rejection_instructions: Some("Reviewer-only rejection instructions.".to_string()),
|
||||
timeout_instructions: None,
|
||||
});
|
||||
@@ -1256,6 +1291,7 @@ async fn guardian_denial_rejects_tool_call_with_rationale(
|
||||
.auto_review = Some(AutoReviewMessages {
|
||||
policy: None,
|
||||
policy_template: None,
|
||||
node_repl_policy: None,
|
||||
rejection_instructions: rejection_instructions.map(str::to_string),
|
||||
timeout_instructions: None,
|
||||
});
|
||||
@@ -1404,6 +1440,7 @@ async fn guardian_timeout_rejects_tool_call_with_acting_model_instructions(
|
||||
.auto_review = Some(AutoReviewMessages {
|
||||
policy: None,
|
||||
policy_template: None,
|
||||
node_repl_policy: None,
|
||||
rejection_instructions: None,
|
||||
timeout_instructions: timeout_instructions.map(str::to_string),
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user