fix(guardian, app-server): introduce guardian review ids (#17298)

## Description

This PR introduces `review_id` as the stable identifier for guardian
reviews and exposes it in app-server `item/autoApprovalReview/started`
and `item/autoApprovalReview/completed` events.

Internally, guardian rejection state is now keyed by `review_id` instead
of the reviewed tool item ID. `target_item_id` is still included when a
review maps to a concrete thread item, but it is no longer overloaded as
the review lifecycle identifier.

## Motivation

We'd like to give users the ability to preempt a guardian review while
it's running (approve or decline).

However, we can't implement the API that allows the user to override a
running guardian review because we didn't have a unique `review_id` per
guardian review. Using `target_item_id` is not correct since:
- with execve reviews, there can be multiple execve calls (and therefore
guardian reviews) per shell command
- with network policy reviews, there is no target item ID

The PR that actually implements user overrides will use `review_id` as
the stable identifier.
This commit is contained in:
Owen Lin
2026-04-10 16:21:02 -07:00
committed by GitHub
parent 7999b0f60f
commit a3be74143a
36 changed files with 577 additions and 172 deletions

View File

@@ -39,6 +39,7 @@ use crate::codex::TurnContext;
use crate::codex::emit_subagent_session_started;
use crate::config::Config;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::new_guardian_review_id;
use crate::guardian::review_approval_request_with_cancel;
use crate::guardian::routes_approval_to_guardian;
use crate::mcp_tool_call::MCP_TOOL_APPROVAL_ACCEPT;
@@ -459,6 +460,7 @@ async fn handle_exec_approval(
let review_rx = spawn_guardian_review(
Arc::clone(parent_session),
Arc::clone(parent_ctx),
new_guardian_review_id(),
GuardianApprovalRequest::Shell {
id: call_id.clone(),
command,
@@ -566,6 +568,7 @@ async fn handle_patch_approval(
let review_rx = spawn_guardian_review(
Arc::clone(parent_session),
Arc::clone(parent_ctx),
new_guardian_review_id(),
GuardianApprovalRequest::ApplyPatch {
id: approval_id.clone(),
cwd: parent_ctx.cwd.to_path_buf(),
@@ -686,6 +689,7 @@ async fn maybe_auto_review_mcp_request_user_input(
let review_rx = spawn_guardian_review(
Arc::clone(parent_session),
Arc::clone(parent_ctx),
new_guardian_review_id(),
build_guardian_mcp_tool_review_request(&event.call_id, &invocation, metadata.as_ref()),
/*retry_reason*/ None,
review_cancel.clone(),
@@ -729,6 +733,7 @@ async fn maybe_auto_review_mcp_request_user_input(
fn spawn_guardian_review(
session: Arc<Session>,
turn: Arc<TurnContext>,
review_id: String,
request: GuardianApprovalRequest,
retry_reason: Option<String>,
cancel_token: CancellationToken,
@@ -745,6 +750,7 @@ fn spawn_guardian_review(
let decision = runtime.block_on(review_approval_request_with_cancel(
&session,
&turn,
review_id,
request,
retry_reason,
cancel_token,