Skip to main content

StateStore

Trait StateStore 

Source
pub trait StateStore: Send + Sync {
    // Required methods
    fn add(&self, key: &str, delta: u64) -> Result<u64, StateError>;
    fn get(&self, key: &str) -> Result<u64, StateError>;
    fn try_spend(
        &self,
        key: &str,
        amount: u64,
        limit: u64,
    ) -> Result<bool, StateError>;
    fn try_spend_many(
        &self,
        spends: &[Spend],
    ) -> Result<Option<usize>, StateError>;
    fn remove(&self, key: &str);

    // Provided methods
    fn refund(&self, key: &str, amount: u64) { ... }
    fn remove_prefix(&self, prefix: &str) { ... }
}
Expand description

Atomic counter operations. Every mutation is atomic with respect to concurrent callers; try_spend is a single check-and-spend (never a read-then-write).

Required Methods§

Source

fn add(&self, key: &str, delta: u64) -> Result<u64, StateError>

Add delta to key, returning the new value.

Source

fn get(&self, key: &str) -> Result<u64, StateError>

Read the current value of key (0 if absent).

Source

fn try_spend( &self, key: &str, amount: u64, limit: u64, ) -> Result<bool, StateError>

Atomically spend amount from the remaining budget limit - spent(key). Returns Ok(true) and records the spend if the full amount fits, Ok(false) (recording nothing) otherwise.

Source

fn try_spend_many(&self, spends: &[Spend]) -> Result<Option<usize>, StateError>

Atomically validate and commit every spend, or commit none. Returns the index of the first dimension that would exceed its limit.

Every Spend in spends must carry a distinct key; two entries for the same key would each observe the pre-commit value in the check phase and pass their independent limit checks, then the commit phase would sum them and blow through the cap. Duplicate keys return StateError::Backend (not Overflow), matching the API-misuse class.

Source

fn remove(&self, key: &str)

Remove a key (session cleanup).

Provided Methods§

Source

fn refund(&self, key: &str, amount: u64)

Return a previously-spent amount to a counter. Saturating: if the stored value is below amount (e.g. a concurrent remove_prefix cleared it first, or another refund already covered part of the debt) the counter clamps at 0 rather than underflowing into “negative budget”. Backends must never propagate a refund error to the caller — the refund is best-effort compensation on a lost-race path where the primary verdict has already been decided. Default remove_prefix semantics apply: backends with native TTL expiry may fold this into their own cleanup if they prefer.

Round-33 F1: introduced to close the round-32 F3 concurrent-MCP budget double-spend. When two identical MCP requests race and one loses the atomic execution.claim(), the sandbox-gate spend is refunded so payout_remaining and per-tool counters reflect only the admitted work.

Source

fn remove_prefix(&self, prefix: &str)

Remove every key beginning with prefix (whole-session cleanup at finalization). Backends with native expiry (e.g. Redis TTLs) may leave this as the default no-op; in-process backends must implement it or session-keyed counters accumulate for the process lifetime.

Implementors§