Route approvals through the extension decision API (#43432)

## What changed

- Let approval extensions choose between cached approval, synchronous review, and a user prompt for tool and permission requests, while core enforces mandatory Guardian and fresh-review requirements.
- Carry an optional `review_reason` on Guardian assessment events and use it to trigger strict-review notifications, retaining the fallback for older events.
- Require synchronous review when cached evidence has an unusable encrypted parent compaction.

## Testing

Add regression coverage for manual prompts and cached approvals with non-UTF-8 working directories. Update approval contributors in existing tests to use the decision API.

GitOrigin-RevId: a4e66416070bdaa881daa41429c49021629f44c1
This commit is contained in:
jif authored and copyberry committed 2026-09-07 11:07:28 +00:00
1 parent 0df39752cb
commit e1eb98461c
24 files changed
+605 -250

No files matched your search

@@ -5,6 +5,7 @@ use super::authorization::ScoreAuthorization;
use super::config::GuardianV2Config;
use super::coverage::GuardianPolicy;
use super::extension::GuardianV2ScoreProgress;
use super::extension::encrypted_parent_compaction;
use super::extension::requires_sync_for_compaction;
use super::metrics::TOOL_CALL_LAG_METRIC;
use super::metrics::record_fast_decision;
@@ -139,7 +140,10 @@ async fn cached_evidence(
.get::<LunaSampler>()
.ok_or(GuardianReviewReason::MissingScore)?;
let history = thread.conversation_history_snapshot().await;
if requires_sync_for_compaction(config, history.as_ref(), &sampler) {
if requires_sync_for_compaction(config, history.as_ref(), &sampler)
|| encrypted_parent_compaction(history.items(), config.max_parent_compaction_tokens)
.is_err()
{
record_fast_decision(metrics, "deferred", "incompatible_compaction");
return Err(GuardianReviewReason::IncompatibleCompaction);
}
@@ -987,7 +987,7 @@ impl GuardianV2Extension {
}
#[derive(Debug, Eq, PartialEq)]
enum ParentCompactionError {
pub(super) enum ParentCompactionError {
Serialization,
Oversized,
Unusable,
@@ -1010,7 +1010,7 @@ pub(super) fn requires_sync_for_compaction(
// An unusable latest compaction must never fall back to an older one. Missing
// encrypted content is rejected here; only legacy callers may omit that checkpoint.
fn encrypted_parent_compaction<'a>(
pub(super) fn encrypted_parent_compaction<'a>(
items: impl Iterator<Item = &'a ResponseItem>,
max_parent_compaction_tokens: usize,
) -> Result<Option<ResponseItem>, ParentCompactionError> {