feat(ai): auto-execute read-only Tier-2 calls under per_step approval (#3130) - #3156
Merged
Conversation
…#3130) Decision on #3130: yes to skipping the per-step prompt for reads, no to doing it tier-wide — Tier 2 is "low-risk mutations + audit", so auto-executing all of Tier <= 2 would have removed confirmation from ~60 mutating actions and made per_step indistinguishable from auto_approve. Instead, two explicit read-only allowlists in aiGuardrails.ts (TIER2_READONLY_ACTIONS for action-multiplexed tools, TIER2_READONLY_TOOLS for single-purpose get/list/search tools) mark strictly-read Tier-2 resolutions with readOnly: true, and createSessionPreToolUse auto-executes those under every approval mode — keeping the Tier-2 ai_tool_executions audit-ledger row (deliberately NOT a Tier-1 demotion, which writes none). A paused session still prompts for everything Tier 2+: pause stays a hard brake. Rider: the ai.tool.* audit event hard-coded `approved: true` for every tier>=2 execution, misrepresenting auto-approved calls — and it would have become actively misleading here. It now records `approved` only when the specific call was explicitly decided, plus `approvalMethod` (per_step_user / action_intent / pam / plan_step / auto_approve_mode / read_only_auto). New contract suite (aiGuardrails.readonly.contract.test.ts) pins the allowlists structurally: action pairs must be a subset of TIER2_ACTIONS, whole-tool entries must be base tier 2 with no per-action tier-table presence, and readOnly must never leak outside the allowlists. Closes #3130 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying breeze with
|
| Latest commit: |
76b6c7d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://22bd46dc.breeze-9te.pages.dev |
| Branch Preview URL: | https://feat-3130-per-step-readonly.breeze-9te.pages.dev |
Review finding on #3156: the read-only fast path fired in action_plan/hybrid_plan sessions too, returning before matchPlanStep — a read that WAS the current plan step left currentPlanStepIndex un-advanced, so the next real step read as a deviation, postToolUse mis-attributed plan_step_complete to the stale index, and a plan ending in a read never reached plan_complete. Carve active plan execution out of the fast path: plan-matched reads auto-execute through the plan branch (which advances the index), and an unmatched read during a plan is a deviation that deliberately still prompts, same as before. per_step — the #3088 fatigue scenario — is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Decision on #3130
Yes to skipping the per-step prompt for read-only calls; no to the tier-wide option. Tier 2 is "low-risk mutations + audit" — auto-executing all of Tier ≤ 2 under
per_stepwould have removed confirmation from ~60 mutating actions (policy activate/deactivate, bulk patch approve, backup profile delete,collect_evidence,revoke_elevation, …) and madeper_stepbehaviorally identical toauto_approve. The narrow read-only allowlist gets the #3088 approval-fatigue win without that blast radius.What changed
aiGuardrails.ts: two explicit allowlists —TIER2_READONLY_ACTIONS(execute_command{event_logs_list,file_list,list_processes},file_operations{list},manage_services{list}) andTIER2_READONLY_TOOLS(the nine single-purpose get/list/search catalog, contract, invoice, and quote tools). Matching Tier-2 resolutions now carryreadOnly: trueon theGuardrailCheck.aiAgentSdk.ts:createSessionPreToolUseauto-executesreadOnlyTier-2 calls under every approval mode, keeping the Tier-2ai_tool_executionsaudit-ledger row (deliberately not a Tier-1 demotion — Tier 1 writes no ledger row, and recon reads stay in the audit trail per the SR5-01 precedent). A paused session still prompts for everything Tier 2+ — pause remains a hard brake. Helper (PAM) sessions are unaffected: their branch precedes the fast path and still cannot self-relax.ai.tool.*audit event previously hard-codedapproved: truefor every tier ≥ 2 execution — false forauto_approve/plan auto-executions and about to get worse. It now recordsapprovedonly when the specific call was explicitly decided, plusapprovalMethod(per_step_user/action_intent/pam/plan_step/auto_approve_mode/read_only_auto).tierConfig.tsand theai.mdxAside updated to describe the read-only carve-out.Tests
aiGuardrails.readonly.contract.test.ts(real registry, no mocks): allowlist action pairs ⊆TIER2_ACTIONS; every whole-tool entry is base tier 2 with no per-action tier-table presence (single-purpose reads only);readOnlyresolves true throughcheckGuardrailsand never leaks to mutating Tier-2, Tier-3, or unknown/missing commandTypes (fail-closed).aiAgentSdk.test.ts: per_step read-only auto-exec (executing ledger row, no prompt, no intent), paused-session still prompts, mutating Tier-2 still bridges, and audit-detail assertions forread_only_autovsper_step_user.tsc --noEmitclean.Closes #3130
🤖 Generated with Claude Code