Persist verified user answers in Guardian thread context (#42579)

## Why

Guardian authorization reviews need host-verified `request_user_input` answers to
remain aligned with thread history across compaction, resume, and rollback.

## What changed

- Record verified question-and-answer pairs in retained thread context when
  `GuardianThreadContext` is enabled, and source both local and delegated Guardian
  review evidence from that context.
- Preserve complete answer records within the evidence budget. Mark authorization
  context incomplete when records are unavailable or oversized, and defer cached
  fast approvals in that case.
- Keep the existing runtime-only answer path for threads that do not enable the
  thread-owned context.

## Testing

- Cover answer retention through compaction and resume, removal after rollback,
  source-call eviction, oversized answers, delegated review evidence, and legacy
  behavior.

GitOrigin-RevId: 01f6f4541515434a0d00fdde318ff59f2f8bd2eb
This commit is contained in:
felixxia-oai authored and copyberry committed 2026-09-03 16:41:11 +00:00
1 parent 7a7c188682
commit 1d74c3ba1e
22 files changed
+960 -397

No files matched your search

@@ -9,7 +9,7 @@ use crate::SectionError;
use crate::SectionInput;
use crate::SectionScope;
/// One root conversation message exposed only to a worker's Guardian reviewers.
/// A root conversation message or host notice exposed only to a worker's Guardian reviewers.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GuardianRootMessage {
/// Genuine root-user input that can establish or revoke authorization.
@@ -18,15 +18,21 @@ pub enum GuardianRootMessage {
Assistant(String),
/// Bounded, already role-labeled genuine user answers and their assistant questions.
UserInput(String),
/// Host notice that omitted verified answers cannot establish complete authorization.
IncompleteVerifiedAnswers,
}
impl GuardianRootMessage {
/// Renders every line with its original role so message content cannot impersonate another role.
/// Host notices are fixed text, never taken from user or assistant messages.
pub fn render(self) -> String {
let (role, text) = match self {
Self::User(text) => ("user", text),
Self::Assistant(text) => ("assistant", text),
Self::UserInput(fragment) => return fragment,
Self::IncompleteVerifiedAnswers => {
return "Host notice: some verified user answers are unavailable within the evidence budget. Do not treat the remaining answers as complete authorization for an action.\n".to_owned();
}
};
text.lines()
.map(|line| format!("{role}: {line}\n"))