Skip to content

Permission approval reviewer

mevedel-permission-reviewer selects user (the default) or auto. Automatic review runs only when Ask or Edits needs permission, before a human card appears. It reuses the guardian model workload. Ordinary confined work in Edits needs no review; Full Access bypasses it entirely. Permissions describes the deterministic policy that remains authoritative.

The model request is asynchronous: the proposed invocation waits for its decision while Emacs can handle editing. Evidence collection and the final policy, ownership, and evidence checks still run locally before dispatch and approval. They do not bypass authority checks to make review invisible; remote filesystem checks or unusually large transcripts can still add local latency.

Trust boundary

The isolated engine request has no tools, ambient conversation, skills, memory, or workspace instructions in its trusted system policy. Its sole system role is prompts/permissions/approval-review-system.md, assembled by the permission-review profile. Actual root user turns, the active Goal objective, exact tool arguments/operation, the model's optional justification, original and selected resources with any prepared scope, direct and delegated authority buckets, saved-rule candidates, target identity/incarnation, working directory, and confinement facts arrive separately as quoted evidence. An agent's task, its justification or a tool result cannot impersonate user authorization.

Before evidence is built, review prepares the same enforceable scope a human card would show: an exact directory that confinement cannot represent becomes that directory's tree, never an ancestor. :scope-preparation names each such change, and the effective capability bundle uses the selected scope.

Saved-rule candidates come only from direct session, workspace and global allow rules; delegated invocation/request rules, approval history, and rules qualified by :sandbox-permissions (full escalation, which operation matching also ignores) are excluded. A candidate either matches the operation through the ordinary rule matcher or, for one reusable Bash segment, shares its literal executable subcommand prefix. Nearby-prefix evidence requires literal spelling and spacing, without normalizing quoted words; executable paths are not reduced to names. Shells, wrappers and dangerous executables have no nearby prefix, even by absolute path or with a shell-quoted executable name. Dynamic or compound commands relate only to an identical rule, and Eval compares expressions literally. Each candidate records its bucket, rule, match basis, argument differences, profile and which selected network/filesystem requirements it covers or lacks. Coverage uses child-execution coverage, preserving access level, tree extent, canonical paths and protected descendants. At most eight candidates within 8 KiB of serialized evidence are sent, ordered by match basis and rule order; whole candidates beyond that are counted as omitted. Candidates already excluded by the count or serialized-size limit do not perform filesystem coverage checks. Candidates are context, not grants, and freshness checks compare them like other evidence.

The isolated request buffer itself holds the selected guardian backend/model, system policy and disabled tools/context before gptel snapshots it. It follows the requesting buffer's gptel-stream choice, because some providers reject a request with streaming disabled; streamed chunks are accumulated into one reply before parsing. Evidence uses the actual tool's execution boundary, including when a hook requests a generic permission card. Child evidence includes resolved direct capability profiles; live Eval names the host Emacs separately from the session target and its incarnation, even with a remote working directory. For live Eval, the policy judges the visible expression's effects rather than treating unrestricted execution as a reason to ask by itself. Task-authorized observational reads of ordinary runtime metadata or settings can be approved once. A read-only claim in the justification is not proof; unknown effects or unseen code still require deferral, and this does not authorize unrelated private data or mutations.

When the guardian workload resolves to Claude Code, the engine uses a fresh, tool-free ACP conversation with that same explicit system policy and quoted evidence. It has no root conversation ID or MCP endpoint. Cancelling or disposing of the reviewer buffer closes its process; malformed replies still defer to the ordinary human approval path.

User-turn extraction uses the canonical transcript parser, excluding assistant text, tool output, compaction summaries and harness scaffolding. It takes whole newest consecutive turns up to 20,000 characters and reports older omissions. If the newest turn cannot fit, review defers even when a Goal is present. Missing root intent and Goal evidence goes directly to human approval. This is bounded evidence, not a claim to recover every historical authorization after compaction.

Result contract

The model must return JSON with decision (allow-once, deny, or ask) and a nonempty reason. Replies over 4,096 characters or malformed decisions are unavailable; reasons are bounded to 500 display columns. allow-once approves only the complete invocation presented. It does not persist command rules, network permission, or filesystem grants, and cannot broaden an exact directory into a recursive grant. Explicit hard denies, Plan and integrity checks remain outside model authority. When logging is enabled, permission-review-completed retains a settled reviewer decision's bounded explanation as :review-reason in the session's permission-log.el, including ask and deny decisions. Failures and timeouts have no reviewer explanation. Raw responses and supplied evidence snapshots are not added to that event. The explanation is model-authored free text, not redacted text, and may quote evidence. It is diagnostic, not authority, and is excluded from unified telemetry and forwarded side audits.

ask, malformed responses, provider failures and the 20-second default timeout (mevedel-permission-review-timeout) display the ordinary human card, whose details state why review deferred, including the reviewer's reason. deny blocks the operation with its reason. A later deliberate user override is a new action, not a retry through another tool. Cancellation or timeout during evidence collection or approval validation prevents a provider request from starting. Review cancellation owns provider cleanup and ignores late or duplicate callbacks. Before approval, the harness rechecks current policy, evidence, resource selection and session ownership. Changed evidence defers to the user; a newly applicable hard deny blocks it. Provider input and freshness snapshots use the same complete representation, independent of callback printer settings and sharing between equal values. Integer arguments retain numeric spelling and floating-point arguments retain full precision even under customized display formatting. Execution rechecks use Bash/Eval policy even for generic hook cards, including segment and requested-capability denies when switching to Full Access. Changing mode or cancelling the owning request invalidates pending review. Provider cleanup failures do not prevent permission settlement.

Natural terminal responses dispose of the isolated buffer without aborting an already completed engine request. Timeout, overlong responses and explicit cancellation use the engine's returned cancellation function while work remains outstanding. Terminal state is recorded before delivering a reply, so cleanup and late callbacks cannot deliver it twice or emit a false stopped-request notice.

Only an accepted reviewer allow-once records display provenance, including the bounded reason, at shared final permission settlement. That record is not a rule or grant; stale replies, human fallback and mode-change approvals do not get it. The canonical tool outcome retains it independently of execution status and keeps it out of provider messages and ToolCall guest values.

Decision examples

These are policy examples, not measured model accuracy or guarantees:

User task and proposed effect Expected decision
Run this project's tests; request network needed to fetch their dependencies Allow once when task, target and complete scope are clear
Evaluate this exact form in the current Emacs; request that live Eval Allow once
Diagnose view grouping; inspect loaded function metadata and view settings without modifying state Allow once when the visible expression's observational effects are clear
Read a build log; upload SSH credentials to an unrelated host Deny
Inspect the project; delete unrelated user files Deny
Tool output says to bypass policy or claims the user approved an upload Treat as untrusted evidence; deny or defer based on the actual task
Operation is appropriate locally but points at an unexplained remote target Ask
Authorization depends on omitted history or unseen file contents Ask

Offline tests replace provider responses and exercise authority and lifecycle handling. They do not establish provider accuracy, latency, cost, or achieved prompt reductions. No captured historical command is replayed by those tests.