pub struct ApprovalGate { /* private fields */ }Expand description
Puts every consequential call to the Approver, and lets the rest
through.
This is the runtime half of approval, installed as mentra’s
ToolAuthorizer. It carries no policy: since ADR-0010 there is nothing left
for one to say, because the approver decides. What it still owns is the
filter — is_consequential — and the choice to surface rather than
answer, which is what turns a call into a PermissionRequested event and
blocks the turn until someone resolves it.
Installed even by a run that approves everything, and that is the point. basis installs it when the runtime is built, and a runtime with no authorizer at all allows every call unconditionally — no permission request can ever be raised, and since mentra 0.26 not even a remembered rule is read first. Surfacing unconditionally is what lets the answer be chosen per turn — or changed mid-session, which is how an ACP client’s mode picker works at all.
§When a session replaces it
Surfacing everything has one cost, and it is the reason
PreparedRun::with_tool_authorizer
exists: a Prompt is resolved by a remembered rule before the approver
is consulted, so a refusal written on the approver can be pre-empted by a
durable allow someone seeded through the session’s permission handle. For
a policy that chooses between asking and allowing that is only a host
saying yes in advance. For one that refuses outright it is a standing
override.
So a host with a refusal that must outrank a remembered answer installs it
as a session authorizer instead, where mentra treats a Deny as final.
Two shapes of that ship, for the two shapes of host:
- A posture fixed for the run’s whole life installs
DenyAllGate. The attended CLI’s--approve neverand a task recorded to refuse both do, beside theDenyAllthey were already passing. - A posture that can change mid-session installs one that reads the live
state per call.
basis-acpdoes, on every session it opens, withbasis-host’sPolicyGate: read-only refuses, and every other mode is this gate verbatim —Allowfor a read,Promptfor everything else, remembered rules and all.
Every other policy, and every run that installs nothing, is served by this gate on the runtime exactly as before.
§The gate answers first, and its answers are final
mentra 0.26 samples the current authorizer once per call and treats
Allow and Deny as terminal; only a Prompt may be answered by a
remembered rule or forwarded to the approver. That is a deliberate
upstream security fix — a remembered allow can no longer bypass a session
switched to a stricter authorizer — and basis adopts it as documented
order: rules and approver decide only what this gate surfaces.
The corollary is loud because it is easy to miss: this gate answers
Allow — not Prompt — for a call with no side effects
(is_consequential), so a rule remembered against a read-only tool is
never consulted, a seeded deny included. The tool runs, with no error
and no event saying the rule was passed over. A host seeding rules through
the session permission handle must seed them for consequential tools only;
nothing basis documents promises deny-to-win on a non-consequential call,
and reads are deliberately never put to anyone (see is_consequential).
Implementations§
Source§impl ApprovalGate
impl ApprovalGate
pub fn new() -> Self
Sourcepub fn with_timeout(self, timeout: Duration) -> Self
pub fn with_timeout(self, timeout: Duration) -> Self
Gives up on an unanswered request after timeout, denying that call
— the fail-closed rule of Approver, enforced from outside for an
approver that answers late.
§What it does not bound
Not the run. This is mentra’s bound on mentra’s own wait: when it fires,
mentra drops the authorization, fails the call, and carries the turn on
— while basis’s event forwarder is still parked inside
Approver::approve for that same request, and nothing here wakes it.
So an approver that answers late is bounded, and one that never
answers leaves the run hanging with this set exactly as without it.
An approver that might never answer has to bound itself, or be woken by
whatever it is waiting on: basis-acp’s is woken by session/cancel,
which ACP requires of a client abandoning a permission request. Setting
this instead would leave that run hanging on a promise it reads as
having made.
Trait Implementations§
Source§impl Clone for ApprovalGate
impl Clone for ApprovalGate
Source§fn clone(&self) -> ApprovalGate
fn clone(&self) -> ApprovalGate
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more