delegated 0.2.1

Minimal fail-closed capability-token evaluation core
Documentation
//! Revocation, emergency-deny, and atomic replay-state contracts.

use chrono::{DateTime, Utc};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::sync::RwLock;

/// Error returned by a trust-state backend.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TrustStateError(String);

impl TrustStateError {
    /// Creates a backend error with an operator-facing explanation.
    pub fn new(message: impl Into<String>) -> Self {
        Self(message.into())
    }
}

impl Display for TrustStateError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}

impl Error for TrustStateError {}

/// Security state required during evaluation.
///
/// `consume_nonce` must be atomic across all evaluator instances sharing a trust domain.
/// Any backend error is treated as denial by the evaluator.
pub trait TrustStateStore: Send + Sync {
    /// Returns whether a token is revoked within an issuer namespace.
    fn is_token_revoked(&self, issuer: &str, token_id: &str) -> Result<bool, TrustStateError>;
    /// Returns whether an agent is emergency-denied within an issuer namespace.
    fn is_agent_denied(&self, issuer: &str, agent_id: &str) -> Result<bool, TrustStateError>;
    /// Atomically records a nonce and returns `true`, or returns `false` if it was already used.
    ///
    /// Distributed implementations must make the check-and-insert operation atomic across all
    /// evaluator instances and retain the nonce through `valid_until`.
    fn consume_nonce(
        &self,
        issuer: &str,
        nonce: &str,
        observed_at: DateTime<Utc>,
        valid_until: DateTime<Utc>,
    ) -> Result<bool, TrustStateError>;
}

/// Administrative mutations for revocation and emergency-deny state.
pub trait TrustStateAdmin: Send + Sync {
    /// Permanently revokes a token within an issuer namespace.
    fn revoke_token(&self, issuer: &str, token_id: &str) -> Result<(), TrustStateError>;
    /// Emergency-denies an agent within an issuer namespace.
    fn deny_agent(&self, issuer: &str, agent_id: &str) -> Result<(), TrustStateError>;
}

/// Reference store for tests and single-process services. Distributed deployments must
/// implement `TrustStateStore` with shared, atomic nonce consumption.
#[derive(Default)]
pub struct InMemoryTrustState {
    inner: RwLock<State>,
}

#[derive(Default)]
struct State {
    revoked_tokens: HashSet<(String, String)>,
    denied_agents: HashSet<(String, String)>,
    nonces: HashMap<(String, String), DateTime<Utc>>,
}

impl InMemoryTrustState {
    /// Creates empty process-local trust state.
    pub fn new() -> Self {
        Self::default()
    }
}

impl TrustStateStore for InMemoryTrustState {
    fn is_token_revoked(&self, issuer: &str, token_id: &str) -> Result<bool, TrustStateError> {
        Ok(self
            .inner
            .read()
            .map_err(lock_error)?
            .revoked_tokens
            .contains(&(issuer.to_owned(), token_id.to_owned())))
    }

    fn is_agent_denied(&self, issuer: &str, agent_id: &str) -> Result<bool, TrustStateError> {
        Ok(self
            .inner
            .read()
            .map_err(lock_error)?
            .denied_agents
            .contains(&(issuer.to_owned(), agent_id.to_owned())))
    }

    fn consume_nonce(
        &self,
        issuer: &str,
        nonce: &str,
        observed_at: DateTime<Utc>,
        valid_until: DateTime<Utc>,
    ) -> Result<bool, TrustStateError> {
        let mut state = self.inner.write().map_err(lock_error)?;
        state.nonces.retain(|_, expiry| *expiry > observed_at);
        let key = (issuer.to_owned(), nonce.to_owned());
        if state.nonces.contains_key(&key) {
            return Ok(false);
        }
        state.nonces.insert(key, valid_until);
        Ok(true)
    }
}

impl TrustStateAdmin for InMemoryTrustState {
    fn revoke_token(&self, issuer: &str, token_id: &str) -> Result<(), TrustStateError> {
        self.inner
            .write()
            .map_err(lock_error)?
            .revoked_tokens
            .insert((issuer.to_owned(), token_id.to_owned()));
        Ok(())
    }

    fn deny_agent(&self, issuer: &str, agent_id: &str) -> Result<(), TrustStateError> {
        self.inner
            .write()
            .map_err(lock_error)?
            .denied_agents
            .insert((issuer.to_owned(), agent_id.to_owned()));
        Ok(())
    }
}

fn lock_error<T>(_: std::sync::PoisonError<T>) -> TrustStateError {
    TrustStateError::new("trust state lock poisoned")
}