mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Replace Guardian tickets with parent response IDs (#43002)
## What changed Capture the ID from `response.created` and pass it as `parent_response_id` in Guardian review and classifier request metadata. Clear the current response ID before sampling retries so tool reviews cannot inherit a failed response's ID. Request Guardian credits with `guardian_credits_requested` on eligible parent requests. Remove the ticket protocol, transport attachment, and ticket-specific body redaction. Keep ordinary app-server client `parent_response_id` values inside `x-codex-turn-metadata` rather than promoting them to Guardian request metadata. ## Testing Update coverage for HTTP fallback, WebSocket review reuse, missing response IDs, classifier transport retries, and code-mode yield/resume attribution. Extend app-server tests to verify client metadata placement over HTTP and WebSocket. GitOrigin-RevId: c1665e56382ecec8d260fc2ca0ecd5e20b0b2391
This commit is contained in:
1 parent
a7a4321593
commit
dee21ec1bc
32 files changed
+178
-362
No files matched your search
@@ -512,11 +512,10 @@ impl GuardianV2Extension {
|
||||
let thread_id = input.thread_store.level_id().to_owned();
|
||||
let turn_id = input.turn_id.to_owned();
|
||||
let root_turn_id = input.root_turn_id.map(str::to_owned);
|
||||
let guardian_ticket = input
|
||||
let parent_response_id = input
|
||||
.turn_store
|
||||
.get::<codex_protocol::guardian_ticket::GuardianTicket>()
|
||||
.as_deref()
|
||||
.cloned();
|
||||
.get::<codex_api::ResponseId>()
|
||||
.map(|id| id.0.clone());
|
||||
let thread_context: Result<_, String> = async {
|
||||
let parsed_thread_id =
|
||||
ThreadId::from_string(&thread_id).map_err(|error| error.to_string())?;
|
||||
@@ -839,7 +838,7 @@ impl GuardianV2Extension {
|
||||
let instructions = guardian_config.render_classifier_instructions(policy);
|
||||
let output = match sampler
|
||||
.sample(LunaSamplingRequest {
|
||||
guardian_ticket,
|
||||
parent_response_id,
|
||||
instructions,
|
||||
trusted_review_evidence,
|
||||
trusted_tool_context,
|
||||
|
||||
@@ -90,8 +90,8 @@ pub struct LunaSamplerConfig {
|
||||
|
||||
/// One tool-less Luna classification request over an already-open connection.
|
||||
pub struct LunaSamplingRequest {
|
||||
/// Runtime receipt of the response handling the classified tool.
|
||||
pub guardian_ticket: Option<codex_protocol::guardian_ticket::GuardianTicket>,
|
||||
/// ID of the response handling the classified tool.
|
||||
pub parent_response_id: Option<String>,
|
||||
/// Trusted instructions describing the requested classification.
|
||||
pub instructions: String,
|
||||
/// Host-supplied Guardian reviews isolated from untrusted transcript entries.
|
||||
@@ -454,7 +454,7 @@ impl LunaSampler {
|
||||
}
|
||||
// A classification is its own inference turn; retries keep that identity.
|
||||
let turn_id = Uuid::now_v7().to_string();
|
||||
let guardian_ticket = request.guardian_ticket;
|
||||
let parent_response_id = request.parent_response_id;
|
||||
let parent_turn_id = request.parent_turn_id;
|
||||
let root_turn_id = request.root_turn_id;
|
||||
let mut input = vec![
|
||||
@@ -629,6 +629,11 @@ impl LunaSampler {
|
||||
turn_metadata["root_turn_id"] = json!(root_turn_id);
|
||||
}
|
||||
client_metadata.insert(TURN_METADATA_KEY.to_owned(), turn_metadata.to_string());
|
||||
if lease.connection.endpoint == ResponsesEndpoint::GuardianClassifier
|
||||
&& let Some(parent_response_id) = &parent_response_id
|
||||
{
|
||||
client_metadata.insert("parent_response_id".to_owned(), parent_response_id.clone());
|
||||
}
|
||||
request.client_metadata = Some(client_metadata);
|
||||
let mut stream = match lease
|
||||
.connection
|
||||
@@ -637,7 +642,6 @@ impl LunaSampler {
|
||||
ResponsesWsRequest::ResponseCreate((&request).into()),
|
||||
/*connection_reused*/ true,
|
||||
/*turn_state*/ None,
|
||||
guardian_ticket.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -192,7 +192,7 @@ async fn connect_sampler(config: LunaSamplerConfig) -> Result<LunaSampler> {
|
||||
|
||||
fn sample_request(parent_turn_id: &str) -> LunaSamplingRequest {
|
||||
LunaSamplingRequest {
|
||||
guardian_ticket: None,
|
||||
parent_response_id: None,
|
||||
instructions: "Return high for high risk or low for low risk.".to_owned(),
|
||||
trusted_review_evidence: Vec::new(),
|
||||
trusted_tool_context: None,
|
||||
@@ -447,7 +447,7 @@ async fn preconnected_sampler_reuses_authenticated_websocket_for_classifications
|
||||
|
||||
let first = sampler
|
||||
.sample(LunaSamplingRequest {
|
||||
guardian_ticket: None,
|
||||
parent_response_id: None,
|
||||
instructions: "Return high for high risk or low for low risk.".to_owned(),
|
||||
trusted_review_evidence: Vec::new(),
|
||||
trusted_tool_context: None,
|
||||
@@ -479,7 +479,7 @@ async fn preconnected_sampler_reuses_authenticated_websocket_for_classifications
|
||||
manager.refresh_token_from_authority().await?;
|
||||
let second = sampler
|
||||
.sample(LunaSamplingRequest {
|
||||
guardian_ticket: None,
|
||||
parent_response_id: None,
|
||||
instructions: "Return high for high risk or low for low risk.".to_owned(),
|
||||
trusted_review_evidence: Vec::new(),
|
||||
trusted_tool_context: None,
|
||||
@@ -655,7 +655,7 @@ async fn sampler_returns_classification_token_before_terminal_response_events()
|
||||
let output = tokio::time::timeout(
|
||||
Duration::from_secs(2),
|
||||
sampler.sample(LunaSamplingRequest {
|
||||
guardian_ticket: None,
|
||||
parent_response_id: None,
|
||||
instructions: "Return high for high risk or low for low risk.".to_owned(),
|
||||
trusted_review_evidence: Vec::new(),
|
||||
trusted_tool_context: None,
|
||||
@@ -1119,7 +1119,7 @@ async fn sampler_limits_transient_recovery_attempts() -> Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn guardian_ticket_survives_classifier_transport_retry() -> Result<()> {
|
||||
async fn parent_response_id_survives_classifier_transport_retry() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
for free_guardian in [false, true] {
|
||||
let healthy = responses::start_websocket_server(vec![vec![vec![
|
||||
@@ -1146,10 +1146,9 @@ async fn guardian_ticket_survives_classifier_transport_retry() -> Result<()> {
|
||||
);
|
||||
}
|
||||
let sampler = connect_sampler(config).await?;
|
||||
let raw_ticket = "t".repeat(43);
|
||||
let parent_response_id = "resp-parent";
|
||||
let mut request = sample_request("turn-1");
|
||||
request.guardian_ticket =
|
||||
codex_protocol::guardian_ticket::GuardianTicket::from_server(&raw_ticket);
|
||||
request.parent_response_id = Some(parent_response_id.to_owned());
|
||||
assert_eq!(sampler.sample(request).await?, "low");
|
||||
for server in [&expired, &healthy] {
|
||||
let requests = server.single_connection();
|
||||
@@ -1157,12 +1156,12 @@ async fn guardian_ticket_survives_classifier_transport_retry() -> Result<()> {
|
||||
let body = requests[0].body_json();
|
||||
assert_eq!(
|
||||
(
|
||||
body["client_metadata"].get("guardian_ticket").cloned(),
|
||||
body["client_metadata"].get("guardian_ticket_requested"),
|
||||
body["client_metadata"].get("parent_response_id").cloned(),
|
||||
body["client_metadata"].get("guardian_credits_requested"),
|
||||
),
|
||||
(free_guardian.then(|| json!(raw_ticket)), None)
|
||||
(free_guardian.then(|| json!(parent_response_id)), None)
|
||||
);
|
||||
assert!(!body["input"].to_string().contains(&raw_ticket));
|
||||
assert!(!body["input"].to_string().contains(parent_response_id));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user