Skip to main content

chio_kernel/
revocation_runtime.rs

1use std::collections::HashSet;
2use std::sync::{Mutex, MutexGuard};
3
4use crate::{budget_store::RevocationCommitMetadata, RevocationStoreError};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct RevocationObservation {
8    pub revoked: bool,
9    pub commit: Option<RevocationCommitMetadata>,
10}
11
12/// Trait for checking whether a capability has been revoked.
13///
14/// Implementations may be in-memory, SQLite-backed, or subscribe to a
15/// distributed revocation feed via Spine/NATS.
16pub trait RevocationStore: Send + Sync {
17    /// Check if a capability ID has been revoked.
18    fn is_revoked(&self, capability_id: &str) -> Result<bool, RevocationStoreError>;
19
20    /// Revoke a capability. Returns `true` if it was newly revoked.
21    fn revoke(&self, capability_id: &str) -> Result<bool, RevocationStoreError>;
22
23    fn observe_revocation(
24        &self,
25        capability_id: &str,
26    ) -> Result<RevocationObservation, RevocationStoreError> {
27        Ok(RevocationObservation {
28            revoked: self.is_revoked(capability_id)?,
29            commit: None,
30        })
31    }
32
33    /// Whether this store loses its revocation set on process restart. The
34    /// default is the safe (loud) assumption so an unknown store is treated as
35    /// ephemeral; durable and remote stores override to `false`.
36    fn is_ephemeral(&self) -> bool {
37        true
38    }
39}
40
41/// In-memory revocation store for development and testing.
42#[derive(Debug, Default)]
43pub struct InMemoryRevocationStore {
44    revoked: Mutex<HashSet<String>>,
45}
46
47impl InMemoryRevocationStore {
48    /// Create an empty revocation store.
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    fn revoked(&self) -> Result<MutexGuard<'_, HashSet<String>>, RevocationStoreError> {
54        self.revoked.lock().map_err(|_| {
55            RevocationStoreError::Sync("in-memory revocation store lock poisoned".to_string())
56        })
57    }
58}
59
60impl RevocationStore for InMemoryRevocationStore {
61    fn is_revoked(&self, capability_id: &str) -> Result<bool, RevocationStoreError> {
62        Ok(self.revoked()?.contains(capability_id))
63    }
64
65    fn revoke(&self, capability_id: &str) -> Result<bool, RevocationStoreError> {
66        Ok(self.revoked()?.insert(capability_id.to_owned()))
67    }
68}