Propagate response tickets to Guardian reviews (#42758)

## What changed

- Request and parse server-issued Guardian tickets for eligible Codex backend responses.
- Scope each ticket to the response that produced a Guardian review or classifier request, including HTTP and WebSocket retries, without adding it to model-visible or persisted context.
- Validate ticket shape, reserve its metadata keys, redact it from debug and request-body logging, and attach it only at the Guardian transport boundary.

## Testing

- Cover ticket parsing, endpoint and authentication routing, retry scoping, classifier retries, and redaction for compressed and uncompressed requests.

GitOrigin-RevId: 6219dd15c9f19ed3387de516aac1b8e7c9cdb0e3
This commit is contained in:
jif authored and copyberry committed 2026-09-04 12:56:26 +00:00
1 parent 97e46694e1
commit cc4b8bdeb8
32 files changed
+562 -44

No files matched your search

+23 -6
View File
@@ -9,6 +9,8 @@ use crate::telemetry::SseTelemetry;
use codex_client::ByteStream;
use codex_client::StreamResponse;
use codex_protocol::ResponseUsageMetadata;
use codex_protocol::guardian_ticket::GUARDIAN_TICKET_HEADER;
use codex_protocol::guardian_ticket::GuardianTicket;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::MisalignmentErrorDetails;
use codex_protocol::protocol::ModelVerification;
@@ -406,8 +408,18 @@ pub fn process_responses_event(
}
}
"response.created" => {
if event.response.is_some() {
return Ok(Some(ResponseEvent::Created {}));
if let Some(response) = event.response {
let guardian_ticket = response
.get("headers")
.and_then(Value::as_object)
.and_then(|headers| {
headers
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case(GUARDIAN_TICKET_HEADER))
})
.and_then(|(_, value)| value.as_str())
.and_then(GuardianTicket::from_server);
return Ok(Some(ResponseEvent::Created { guardian_ticket }));
}
}
"response.failed" => {
@@ -1396,7 +1408,7 @@ mod tests {
}
fn is_created(ev: &ResponseEvent) -> bool {
matches!(ev, ResponseEvent::Created)
matches!(ev, ResponseEvent::Created { .. })
}
fn is_output(ev: &ResponseEvent) -> bool {
matches!(ev, ResponseEvent::OutputItemDone(_))
@@ -1578,7 +1590,7 @@ mod tests {
.await;
assert_eq!(events.len(), 2);
assert_matches!(&events[0], ResponseEvent::Created);
assert_matches!(&events[0], ResponseEvent::Created { .. });
assert_matches!(
&events[1],
ResponseEvent::Completed {
@@ -1616,7 +1628,12 @@ mod tests {
&events[0],
ResponseEvent::ServerModel(model) if model == CYBER_RESTRICTED_MODEL_FOR_TESTS
);
assert_matches!(&events[1], ResponseEvent::Created);
assert_matches!(
&events[1],
ResponseEvent::Created {
guardian_ticket: None
}
);
assert_matches!(
&events[2],
ResponseEvent::Completed {
@@ -1738,7 +1755,7 @@ mod tests {
.await;
assert_eq!(events.len(), 7);
assert_matches!(&events[0], ResponseEvent::Created);
assert_matches!(&events[0], ResponseEvent::Created { .. });
assert_matches!(
&events[1],
ResponseEvent::SafetyBuffering(buffering)