Skip to main content

ApprovalGate

Struct ApprovalGate 

Source
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 never and a task recorded to refuse both do, beside the DenyAll they were already passing.
  • A posture that can change mid-session installs one that reads the live state per call. basis-acp does, on every session it opens, with basis-host’s PolicyGate: read-only refuses, and every other mode is this gate verbatim — Allow for a read, Prompt for 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

Source

pub fn new() -> Self

Source

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

Source§

fn clone(&self) -> ApprovalGate

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ApprovalGate

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ApprovalGate

Source§

fn default() -> ApprovalGate

Returns the “default value” for a type. Read more
Source§

impl ToolAuthorizer for ApprovalGate

Source§

fn authorize<'life0, 'life1, 'async_trait>( &'life0 self, request: &'life1 ToolAuthorizationRequest, ) -> Pin<Box<dyn Future<Output = Result<ToolAuthorizationDecision, RuntimeError>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Source§

fn timeout(&self) -> Option<Duration>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more