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
2026-09-07 11:07:28 +00:00
committed by copyberry
parent 0df39752cb
commit e1eb98461c
24 changed files with 605 additions and 250 deletions
@@ -129,6 +129,7 @@ fn guardian_stdin_reviews_preserve_parent_command_history() {
assert!(expected[1].items.is_empty());
let assessment = GuardianAssessmentEvent {
review_reason: None,
id: "review-stdin".into(),
target_item_id: Some("terminal-command".into()),
plugin_id: None,
@@ -159,6 +160,7 @@ fn guardian_stdin_reviews_preserve_parent_command_history() {
] {
items.push(RolloutItem::EventMsg(EventMsg::GuardianAssessment(
GuardianAssessmentEvent {
review_reason: None,
turn_id: turn_id.into(),
status,
..assessment.clone()
@@ -3339,6 +3339,7 @@ mod tests {
..Default::default()
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
review_reason: None,
id: "review-guardian-exec".into(),
target_item_id: Some("guardian-exec".into()),
plugin_id: Some("sample@openai-curated".into()),
@@ -3360,6 +3361,7 @@ mod tests {
.expect("guardian action"),
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
review_reason: None,
id: "review-guardian-exec".into(),
target_item_id: Some("guardian-exec".into()),
plugin_id: Some("sample@openai-curated".into()),
@@ -3431,6 +3433,7 @@ mod tests {
..Default::default()
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
review_reason: None,
id: "review-guardian-execve".into(),
target_item_id: Some("guardian-execve".into()),
plugin_id: Some("sample@openai-curated".into()),