Files
codex/codex-rs/ext/guardian-reviewer/src/feedback_tests.rs
T
jif 30bdfec59d Persist Guardian review failures for reports across restarts (#51651)
## Why

Failed Guardian review evidence was process-local, so restarting Codex lost the context needed for later diagnostic reports.

## What changed

- Persist failed-review records in SQLite before reviewer cleanup, retaining at most 64 records globally, eight per thread, and 8 MiB of evidence. Delete persisted evidence when its reviewed thread is deleted.
- Recover and deduplicate persisted and in-memory records for the reported task tree when uploading `auto-review-failures.jsonl`.
- Bound persistence attempts to 250 ms and retain an in-memory fallback when storage is unavailable. Keep capture gated by user opt-in and non-ephemeral sessions, and export evidence only when logs are included.

## Testing

Add coverage for persistence across reopen and process restart, retention limits, cascading deletion, busy-storage fallback, and report uploads with and without logs, including SQLite log failures.

GitOrigin-RevId: ea2da1818d4a5da3da29f211042ca45e834342bc
2026-10-07 11:18:05 +00:00

63 lines
2.0 KiB
Rust

use super::*;
use pretty_assertions::assert_eq;
use serde_json::json;
#[test]
fn capture_honors_privacy_and_keeps_oversized_context_action_and_decision() -> anyhow::Result<()> {
let thread_id = ThreadId::new();
let reviewer_thread_id = ThreadId::new();
let decision = r#"{"outcome":"deny","rationale":"Missing approval."}"#;
let action = r#"{"command":"git push"}"#;
let outcome = GuardianReviewSessionOutcome::Completed(Ok(Some(decision.to_owned())));
for settings in [
ReviewFeedbackSettings {
enabled: false,
ephemeral: false,
},
ReviewFeedbackSettings {
enabled: true,
ephemeral: true,
},
] {
assert!(FailedReviewFeedback::for_outcome(&outcome, settings).is_none());
}
let feedback = FailedReviewFeedback::for_outcome(
&outcome,
ReviewFeedbackSettings {
enabled: true,
ephemeral: false,
},
)
.expect("denials retain feedback");
let record = feedback.into_record(ReviewFeedbackContext {
reviewed_thread_id: thread_id,
reviewed_turn_id: "parent-turn",
target_item_id: Some("push-call"),
reviewer_thread_id,
model: "review-model",
action,
action_truncated: false,
instructions: Some(&"x".repeat(MAX_RECORD_BYTES)),
history: Vec::new(),
});
let contents = record.expect("failed-review record").record;
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&contents)?,
json!({
"reviewed_thread_id": thread_id,
"reviewed_turn_id": "parent-turn",
"target_item_id": "push-call",
"reviewer_thread_id": reviewer_thread_id,
"model": "review-model",
"status": "denied",
"decision": decision,
"action": action,
"action_truncated": false,
"instructions": null,
"history": [],
"context_omitted": true,
})
);
Ok(())
}