Files
codex/codex-rs/guardian-context/src/verified_answers.rs
T
felixxia-oai aa12ab45df Recover missing Guardian root instructions in acceptance order (#43472)
## Why

Incomplete retained checkpoints can omit root user instructions that still survive in live history. Queued input can also reach model history after a later-accepted answer, so recording order cannot reliably order grants and restrictions for subagent authorization reviews.

## What changed

- Reconcile retained evidence with surviving local user messages using source identity and persisted acceptance order, including answers present only in the checkpoint.
- Preserve checkpoint gaps and mark evidence incomplete when recovered instructions lack an order or conflict with an existing order.
- Restore the input-order counter from surviving local metadata so new instructions sort after recovered evidence, even without a retained checkpoint.

## Testing

Add unit coverage for source matching, acceptance ordering, persistent gaps, conflicting orders, and counter restoration. Extend subagent authorization tests to cover checkpoint resume, queued approvals, missing sources, and a subsequent revocation.

GitOrigin-RevId: 8bfbfd2c797d725796187e5cecf7f3f11a5f3380
2026-09-07 13:40:29 +00:00

63 lines
2.2 KiB
Rust

//! Stateless selection of original host-verified question/answer records.
//! Omit a record atomically when it cannot fit; never truncate a restriction out of a grant.
use codex_history::RetainedContext;
use codex_history::RetainedContextEntry;
use codex_history::VerifiedAnswer;
use crate::GuardianRootMessage;
use codex_protocol::protocol::TruncationPolicy;
const MAX_ANSWER_TOKENS: usize = 900;
/// Request-bounded answers and whether they are complete enough for cached fast approval.
pub struct RenderedVerifiedAnswers {
pub fragments: Vec<String>,
pub complete: bool,
}
/// Renders each response with its original source roles and the existing approximate per-answer budget.
pub fn render_verified_answers(context: &RetainedContext) -> RenderedVerifiedAnswers {
let mut complete = context.verified_answers_complete();
let mut fragments = Vec::new();
for (order, (_, entry)) in context.ordered_entries().enumerate() {
let RetainedContextEntry::VerifiedAnswer(answer) = entry else {
continue;
};
if let Some(text) = render_verified_answer(answer) {
// The bounded order label is framing, not part of the existing answer budget.
fragments.push(format!("Retained source order: {order}\n{text}"));
} else {
complete = false;
}
}
if !complete {
fragments.insert(
/*index*/ 0,
GuardianRootMessage::IncompleteVerifiedAnswers.render(),
);
}
RenderedVerifiedAnswers {
fragments,
complete,
}
}
/// Selects one complete response, preserving both sides of every question/answer pair.
/// Returning None means the caller must account for missing evidence, not partial permission.
pub fn render_verified_answer(answer: &VerifiedAnswer) -> Option<String> {
let text = answer
.questions
.iter()
.map(|pair| {
format!(
"{}{}",
GuardianRootMessage::Assistant(pair.question.clone()).render(),
GuardianRootMessage::User(pair.answer.clone()).render()
)
})
.collect::<String>();
(!text.is_empty() && text.len() <= TruncationPolicy::Tokens(MAX_ANSWER_TOKENS).byte_budget())
.then_some(text)
}