From e51a91b2f4a1b04c4fd8e1db671bb34f93da4e16 Mon Sep 17 00:00:00 2001 From: felixxia-oai Date: Tue, 18 Aug 2026 22:18:08 +0000 Subject: [PATCH] Keep Guardian v2 risk scores in memory (#39304) ## What changed - Stop writing Guardian v2 security risk scores to rollout history. - Treat resumed and forked threads as having no previous Guardian score, so their first tool approval is classified and reviewed normally. - Remove the now-unused `codex-history` dependency from the Guardian v2 extension. ## Testing - Update app-server coverage for new, resumed, and forked threads to wait for classification completion and verify the expected approval reviews. GitOrigin-RevId: 74d9caa903a1ff9d38e577b4697b5f8970f11ebb --- codex-rs/Cargo.lock | 1 - .../app-server-exports-stable.json.zst | Bin 135127 -> 135118 bytes .../app-server/tests/suite/v2/guardian_v2.rs | 107 ++++++------------ codex-rs/ext/guardian-v2/Cargo.toml | 1 - codex-rs/ext/guardian-v2/src/extension.rs | 7 -- .../ext/guardian-v2/src/extension_tests.rs | 16 --- codex-rs/protocol/src/security_risk.rs | 6 +- 7 files changed, 38 insertions(+), 100 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index bb1ea9c667..d10aeda3bc 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -3312,7 +3312,6 @@ dependencies = [ "codex-core", "codex-extension-api", "codex-features", - "codex-history", "codex-http-client", "codex-login", "codex-model-provider", diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index 6e58e8df4b06c47ed18e2161ccb453b441805608..12f6c93c4e449331ad69d842143a65f646ff2de6 100644 GIT binary patch delta 7205 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

SxF%Tpf`e2!_f(~t%$26zA*Sv8WeF-4F5Mj!8n z5LeAWq}Pdnk?Z=P>7T%v2@0Kv45=ovsK4F)a)Y+}Fp4Gz8=RfkubME~oDMf={}iE9 zcU@1i4p)r67?x@9pDnp*UwcDi9`J%4VLYP$)tP*3?|@_# ziJEb(stfx{p$1P;oPfI>RcBQj?~uRWkdQo7W1oNM?UE~UU=JhG_q_C!@> zLy1*FxnsQbS1gEG9$JeuL=6yBZD27*tB>^@;k7^uTYGPYb)UY4A23Hslgoj>z#Fk| z8}bPtZ6jG%%%SkhM9m4j2*`}(cV^D7MbB07Y$}A3r-{|=h5^-Fpm}wwVyS=JT}=X+ z1!M*jNXF6P(_1g>P#Wh$@h^nXvHI9*-9AP>D%%Es>cm#tF%b8H1u2KErV^=Pt08wv zFNDlhqF;@t=Y=rZ9d(m%-)HocdFJBbXfa+0^>E^aaQtFgz3a%npP&`I5VlZ%7sA_T zy$j)NiPK#OuS(p7&=@e=g>Zk06pMBt{N~6mg#Li^&5*gxYnmYe1!ikr!_1!~U{)!2 zl{!z6Lh~9iwm3k6SLQXD@?>7qb6tt($LSfJ+R($0UHeT4U^iKXqn zYli@V1s_&T*?Pij;->z(Mjm&R?L~=6#ye@@)I7k$7ADsDZGf5!=4jB7KA0lsQ2?IpOe(@N$lDIxT|U#QzdxF}>Ovk#$ujQlu0x}kq;v{6;FvZ`cEs_iKu zoOt5@v!e7TfMh<-?IMRPkJ*7uU&j9-JsVSa zpCVxWA}P39^O=8*C5ZBb16XFe*niCD8Eg+82ZrfU^7*3^MYd)ZQA`_{hMEwQL{FGq zM8@8FckL7O+Bdc6F-DkX9PCCMSg~C?eJb*3RB$Jr?=$+Tw0>#GSP0*|xLw44<35JO zMv+*Om8(Sb__ZgfUeJt471 z$emgxnd5)Efv2z1I4*(Byn)xW2Ho4d$F}8f!;cD%pR2>^%nbHrRNoRd(VM*>LMhm> zr#kB(Yw+&JrSTDTtHS=~A8*Q8+^D0X=I^)$Lc<7UUp6naNf{f0e@_D&TxO(_o$Nz3 z+Ye~2H`#aH4*w$K4es|0Sfo7@$${OfYuq#nA6tLD7$K4Ir=2kmOS*1`srOo;W*Om8 zu7t2PVnmOYsuUwWG!|mX03D9c9kCY361neYJwU8n-s09=I5&Oq?{-y6L)>b}W@sx2tz|K_eTc!MIA8kBne{4YPqxBHZ3jnTX;iBdLFTB907e*-h=mQ6UI#tEa=DurTVVbyPf) z($G`r3ZixqnE1iF*00xRsxJ6MO{=O0fK136S#T-%yci7`mQ)i%3w@kD|-$6uvPQ3i+O(t zp|^fBeH9l@2bJDo!h(V-Df8wQZBB(&hwhC`+0>rhmy(+M)>cyFU4glGG;7SCGTyg zhi}BZe2q|TGH5`yjuZgG5RUDNx}Ul^3@Je1&-+ zvW+^o9<46)8obVJih&s#yK;c%&P>)oZnBat=4Ka5#m4%s9#f;N`Qni4YGtJ_8KY{T zybGi_w=za5xlfFW#~wTHw=JW*Xl-1zz{J0@k(TqKBEjArBQTY3L63i$LmBl-EpYqV z#{b+=jE5HS%#gAa#c0Stj2rMi)L<*IRQl3x*_R1FY)B^a_K5D=Th_HGjhji zrGF&BT@z=$Qk3+LuX8MqG2P+Edy#MT!odYPP&uLtYdBS^u!mwA^&sCKZB!_!VFq^u zag`M2<@{wnT6>a~rA>d&H+_rZ&`2FsYAS3X?96sMl`7?!4p!Nkx9{eNfB_imB5{}{ zU~dT#hiOooXi3ZJXBScY8X*>`3IJ*aD^cEP19l}R=#lVt@_v*+_+E<&PQ=eET5bRm zfGP44hVCcJ(1>k>xoWWZD+B%POyC8`n^sJOYt5f*RCTF_9kqX>V$n30^c2hj=y)Dohif)3&jbt=t{q4v&S()Mn wR}06`e5_0#rGO%emEP!?^_j#<1ANCy&y)5$~0HsET$oT?W_;}eTfKi=fu#{d8T diff --git a/codex-rs/app-server/tests/suite/v2/guardian_v2.rs b/codex-rs/app-server/tests/suite/v2/guardian_v2.rs index d5def352a3..c2ba9b5901 100644 --- a/codex-rs/app-server/tests/suite/v2/guardian_v2.rs +++ b/codex-rs/app-server/tests/suite/v2/guardian_v2.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::sync::Arc; use std::sync::Mutex; use std::sync::atomic::AtomicUsize; @@ -9,7 +8,6 @@ use anyhow::Result; use app_test_support::MockResponsesConfig; use app_test_support::TestAppServer; use app_test_support::create_fake_rollout; -use app_test_support::rollout_path; use axum::Json; use axum::Router; use axum::extract::State; @@ -18,6 +16,7 @@ use axum::extract::ws::WebSocketUpgrade; use axum::http::header; use axum::response::IntoResponse; use axum::routing::get; +use axum::routing::post; use codex_app_server_protocol::ApprovalsReviewer; use codex_app_server_protocol::AskForApproval; use codex_app_server_protocol::ItemGuardianApprovalReviewStartedNotification; @@ -31,9 +30,6 @@ use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::UserInput; use codex_features::Feature; -use codex_protocol::security_risk::SecurityRiskScore; -use codex_rollout::RolloutItem; -use codex_rollout::append_rollout_item_to_path; use core_test_support::responses; use core_test_support::skip_if_no_network; use pretty_assertions::assert_eq; @@ -58,6 +54,7 @@ struct MockResponsesState { luna_requests: Mutex>, allow_luna: Notify, allow_guardian_review: Notify, + classification_completed: Notify, luna_score: f64, } @@ -170,11 +167,10 @@ async fn guardian_v2_routes_tool_approvals( risk: GuardianRisk, lifecycle: ThreadLifecycle, ) -> Result<()> { - let (luna_score, expected_guardian_reviews) = match (risk, lifecycle) { - (GuardianRisk::Low, ThreadLifecycle::New) => (0.25, 1), - (GuardianRisk::Low, ThreadLifecycle::Resume | ThreadLifecycle::Fork) => (0.25, 0), - (GuardianRisk::Threshold, _) => (0.5, 2), - (GuardianRisk::High, _) => (0.95, 2), + let (luna_score, expected_guardian_reviews) = match risk { + GuardianRisk::Low => (0.25, 1), + GuardianRisk::Threshold => (0.5, 2), + GuardianRisk::High => (0.95, 2), }; let responses_state = Arc::new(MockResponsesState { luna_score, @@ -184,6 +180,16 @@ async fn guardian_v2_routes_tool_approvals( let responses_url = format!("http://{}", listener.local_addr()?); let router = Router::new() .route("/v1/responses", get(luna_websocket).post(parent_response)) + .route( + "/metrics", + post( + |State(state): State>, body: String| async move { + if body.contains("codex.guardian_v2.classification") { + state.classification_completed.notify_one(); + } + }, + ), + ) .with_state(Arc::clone(&responses_state)); let responses_server = tokio::spawn(async move { let _ = axum::serve(listener, router).await; @@ -196,39 +202,25 @@ async fn guardian_v2_routes_tool_approvals( .with_approval_policy("on-request") .with_root_config("approvals_reviewer = \"auto_review\"") .with_extra_config(&format!( - "[mcp_servers.{TEST_SERVER_NAME}]\nurl = \"{mcp_server_url}/mcp\"\ndefault_tools_approval_mode = \"prompt\"" + "[mcp_servers.{TEST_SERVER_NAME}]\nurl = \"{mcp_server_url}/mcp\"\ndefault_tools_approval_mode = \"prompt\"\n\n[analytics]\nenabled = true\n\n[otel]\nmetrics_exporter = {{ otlp-http = {{ endpoint = \"{responses_url}/metrics\", protocol = \"json\" }} }}" )) .enable_feature(Feature::GuardianV2) .enable_feature(Feature::GuardianApproval) .write(codex_home.path())?; let original_thread_id = match lifecycle { ThreadLifecycle::New => None, - ThreadLifecycle::Resume | ThreadLifecycle::Fork => { - let thread_id = create_fake_rollout( - codex_home.path(), - "2025-01-05T12-00-00", - "2025-01-05T12:00:00Z", - USER_CONTEXT, - Some("mock_provider"), - /*git_info*/ None, - )?; - let original_rollout = - rollout_path(codex_home.path(), "2025-01-05T12-00-00", &thread_id); - for action_risk in [0.95, 0.1] { - append_rollout_item_to_path( - &original_rollout, - &RolloutItem::SecurityRiskScore(SecurityRiskScore { - scores: BTreeMap::from([("action_risk".to_owned(), action_risk)]), - sampled_at: None, - }), - ) - .await?; - } - Some(thread_id) - } + ThreadLifecycle::Resume | ThreadLifecycle::Fork => Some(create_fake_rollout( + codex_home.path(), + "2025-01-05T12-00-00", + "2025-01-05T12:00:00Z", + USER_CONTEXT, + Some("mock_provider"), + /*git_info*/ None, + )?), }; let mut app_server = TestAppServer::builder() .with_codex_home(codex_home.path()) + .with_env_overrides(&[("OTEL_METRIC_EXPORT_INTERVAL", Some("25"))]) .build_initialized_with_timeout(TIMEOUT) .await?; let thread = match lifecycle { @@ -274,7 +266,6 @@ async fn guardian_v2_routes_tool_approvals( } }; let thread_id = thread.id; - let rollout = thread.path.expect("thread should be persisted"); let turn_request_id = app_server .send_turn_start_request(TurnStartParams { thread_id: thread_id.clone(), @@ -289,14 +280,12 @@ async fn guardian_v2_routes_tool_approvals( .await?; let _: TurnStartResponse = timeout(TIMEOUT, app_server.read_response(turn_request_id)).await??; - if matches!(lifecycle, ThreadLifecycle::New) { - let review_started: ItemGuardianApprovalReviewStartedNotification = timeout( - TIMEOUT, - app_server.read_notification("item/autoApprovalReview/started"), - ) - .await??; - assert_eq!(review_started.thread_id, thread_id); - } + let review_started: ItemGuardianApprovalReviewStartedNotification = timeout( + TIMEOUT, + app_server.read_notification("item/autoApprovalReview/started"), + ) + .await??; + assert_eq!(review_started.thread_id, thread_id); let luna_request = timeout(TIMEOUT, async { loop { @@ -332,34 +321,8 @@ async fn guardian_v2_routes_tool_approvals( }) }) ); - if matches!(lifecycle, ThreadLifecycle::Resume | ThreadLifecycle::Fork) { - timeout(TIMEOUT, async { - while responses_state.parent_requests.load(Ordering::SeqCst) < 3 { - tokio::task::yield_now().await; - } - }) - .await?; - assert_eq!(responses_state.guardian_reviews.load(Ordering::SeqCst), 0); - } - responses_state.allow_luna.notify_one(); - timeout(TIMEOUT, async { - loop { - if tokio::fs::read_to_string(&rollout) - .await? - .lines() - .filter_map(|line| serde_json::from_str::(line).ok()) - .any(|line| { - line["type"] == "security_risk_score" - && line["payload"]["scores"]["action_risk"] == json!(luna_score) - }) - { - return Ok::<(), std::io::Error>(()); - } - tokio::task::yield_now().await; - } - }) - .await??; + timeout(TIMEOUT, responses_state.classification_completed.notified()).await?; responses_state.allow_guardian_review.notify_one(); responses_state.allow_luna.notify_one(); timeout( @@ -425,13 +388,13 @@ async fn guardian_v2_threshold_score_requires_full_reviews() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn resumed_thread_inherits_latest_guardian_score() -> Result<()> { +async fn resumed_thread_starts_without_guardian_score() -> Result<()> { skip_if_no_network!(Ok(())); guardian_v2_routes_tool_approvals(GuardianRisk::Low, ThreadLifecycle::Resume).await } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn forked_thread_inherits_latest_guardian_score() -> Result<()> { +async fn forked_thread_starts_without_guardian_score() -> Result<()> { skip_if_no_network!(Ok(())); guardian_v2_routes_tool_approvals(GuardianRisk::Low, ThreadLifecycle::Fork).await } diff --git a/codex-rs/ext/guardian-v2/Cargo.toml b/codex-rs/ext/guardian-v2/Cargo.toml index 15e9d2cf29..850b0d2d4a 100644 --- a/codex-rs/ext/guardian-v2/Cargo.toml +++ b/codex-rs/ext/guardian-v2/Cargo.toml @@ -17,7 +17,6 @@ codex-api = { workspace = true } codex-core = { workspace = true } codex-extension-api = { workspace = true } codex-features = { workspace = true } -codex-history = { workspace = true } codex-http-client = { workspace = true } codex-login = { workspace = true } codex-model-provider = { workspace = true } diff --git a/codex-rs/ext/guardian-v2/src/extension.rs b/codex-rs/ext/guardian-v2/src/extension.rs index b8b5c6d366..d0393d9bce 100644 --- a/codex-rs/ext/guardian-v2/src/extension.rs +++ b/codex-rs/ext/guardian-v2/src/extension.rs @@ -27,7 +27,6 @@ use codex_extension_api::ToolName; use codex_extension_api::ToolPayload; use codex_extension_api::ToolStartInput; use codex_features::Feature; -use codex_history::RolloutItem; use codex_login::AgentIdentityAuthPolicy; use codex_login::AuthManager; use codex_model_provider::create_model_provider; @@ -614,12 +613,6 @@ impl ToolLifecycleContributor for GuardianV2Extension { .latest_scored_tool_call .fetch_max(tool_call_index, Ordering::Release); classification_finished_at = Some(Instant::now()); - if !config.ephemeral { - thread - .append_rollout_items(&[RolloutItem::SecurityRiskScore(score)]) - .await - .map_err(|error| error.to_string())?; - } Ok("success") } .await; diff --git a/codex-rs/ext/guardian-v2/src/extension_tests.rs b/codex-rs/ext/guardian-v2/src/extension_tests.rs index d4237bd3b0..8e529c1f37 100644 --- a/codex-rs/ext/guardian-v2/src/extension_tests.rs +++ b/codex-rs/ext/guardian-v2/src/extension_tests.rs @@ -18,7 +18,6 @@ use codex_extension_api::ToolName; use codex_extension_api::ToolPayload; use codex_extension_api::ToolStartInput; use codex_features::Feature; -use codex_history::RolloutItem; use codex_login::AuthManager; use codex_login::CodexAuth; use codex_login::ExternalAuth; @@ -1201,21 +1200,6 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result< .await, None ); - test.codex.ensure_rollout_materialized().await; - test.codex.flush_rollout().await?; - let persisted_scores = test - .codex - .load_history(/*include_archived*/ false) - .await? - .items - .into_iter() - .filter_map(|item| match item { - RolloutItem::SecurityRiskScore(score) => Some(score), - _ => None, - }) - .collect::>(); - assert_eq!(persisted_scores, vec![score.as_ref().clone()]); - thread_store.insert(SecurityRiskScore { scores: BTreeMap::from([("action_risk".to_string(), 0.5)]), sampled_at: None, diff --git a/codex-rs/protocol/src/security_risk.rs b/codex-rs/protocol/src/security_risk.rs index 7d1dde8aab..ca16fae4f5 100644 --- a/codex-rs/protocol/src/security_risk.rs +++ b/codex-rs/protocol/src/security_risk.rs @@ -5,10 +5,10 @@ use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; -/// A durable, thread-owned snapshot of security risk classifier scores. +/// A thread-owned, in-memory snapshot of security risk classifier scores. /// -/// These records belong to rollout history only and must not enter model-visible -/// conversation context or user-visible thread item projections. +/// Scores must not enter model-visible conversation context or user-visible thread +/// item projections. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct SecurityRiskScore { pub scores: BTreeMap,