pub struct ActionBudget<'a> { /* private fields */ }Expand description
Budget enforcement bound to an accounting scope (a session id, or a stable principal id) and a state store.
Historically this type was hard-coded to a session-scoped view, but a
session-only budget can be
bypassed by any caller that rotates the caller-chosen X-AV-Session
header — every request would land on a virgin counter. Callers that
want to catch that vector layer a principal-scoped ActionBudget on
top of the session-scoped one; the anti-rotation property lives in the
key prefix, not in the check logic.
Implementations§
Source§impl<'a> ActionBudget<'a>
impl<'a> ActionBudget<'a>
Sourcepub fn new(
store: &'a dyn StateStore,
session: &'a str,
spec: &'a BudgetSpec,
) -> Self
pub fn new( store: &'a dyn StateStore, session: &'a str, spec: &'a BudgetSpec, ) -> Self
Bind a spec to a session id.
Sourcepub fn for_principal(
store: &'a dyn StateStore,
principal: &'a str,
spec: &'a BudgetSpec,
) -> Self
pub fn for_principal( store: &'a dyn StateStore, principal: &'a str, spec: &'a BudgetSpec, ) -> Self
Bind a spec to a principal id (JWT sub/instance_uid, HMAC-KID,
or the sentinel "anonymous" when require_identity = false).
The resulting counters aggregate across every session belonging to
the same principal, which is what makes a header-rotation attack
visible — the token debit persists even after the client mints a
fresh X-AV-Session.
Sourcepub fn session_prefix(session: &str) -> String
pub fn session_prefix(session: &str) -> String
Common key prefix for every budget counter of session. Callers use
this with StateStore::remove_prefix to drop a finalized session’s
counters (per-tool keys are dynamic, so single-key removal cannot
enumerate them).
Sourcepub fn principal_prefix(principal: &str) -> String
pub fn principal_prefix(principal: &str) -> String
Common key prefix for every principal-scoped budget counter. Distinct
namespace from Self::session_prefix so a session cleanup on close does
not touch the principal ledger — the principal ledger persists across
session boundaries by design.
Sourcepub fn try_tool_call(
&self,
tool: &str,
payout_usd_micros: u64,
) -> Result<BudgetDecision, StateError>
pub fn try_tool_call( &self, tool: &str, payout_usd_micros: u64, ) -> Result<BudgetDecision, StateError>
Check-and-spend one invocation of tool, with an optional payout
amount in micro-USD carried by this call.
A non-zero payout with NO configured max_payout_usd_micros
refuses first (fail-closed: an unbounded payout dimension must
never spend), citing max_payout_usd_micros(unset) — even when
another dimension is also at cap. The remaining dimensions are
then checked in a fixed order (total calls → per-tool → payout)
and committed atomically via try_spend_many: every dimension
is validated first and either all spends commit or none do, so
a refused call consumes nothing.
Sourcepub fn refund_tool_call(&self, tool: &str, payout_usd_micros: u64)
pub fn refund_tool_call(&self, tool: &str, payout_usd_micros: u64)
Compensating refund for a previously-successful
Self::try_tool_call. Reverses the spend on exactly the same
dimensions that were debited (total_calls, per-tool, payout) so
a lost-race path in the caller (concurrent identical MCP
request loses execution.claim() after the sandbox gate has
already spent) does not double-charge the session budget.
Best-effort: any backend error is silently absorbed by the
underlying StateStore::refund contract — a Redis blip on
the compensation path must never turn a lost-race response
into a 5xx.
Sourcepub fn refund_tokens(&self, tokens: u64)
pub fn refund_tokens(&self, tokens: u64)
Compensating refund for a previously-successful Self::try_tokens
on a request that provably never reached the provider (sanitize
block, capture failure, in-flight-marker write failure, connect
failure). Same rationale and best-effort contract as
Self::refund_tool_call: without it, each client retry against
a flaky-but-unreachable upstream re-debits max_tokens with zero
LLM work performed, draining the session budget.
Sourcepub fn try_tokens(&self, tokens: u64) -> Result<BudgetDecision, StateError>
pub fn try_tokens(&self, tokens: u64) -> Result<BudgetDecision, StateError>
Check-and-spend tokens against max_tokens.
Auto Trait Implementations§
impl<'a> Freeze for ActionBudget<'a>
impl<'a> !RefUnwindSafe for ActionBudget<'a>
impl<'a> Send for ActionBudget<'a>
impl<'a> Sync for ActionBudget<'a>
impl<'a> Unpin for ActionBudget<'a>
impl<'a> UnsafeUnpin for ActionBudget<'a>
impl<'a> !UnwindSafe for ActionBudget<'a>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more