cellos-host-cellos 0.5.1

Recursive CellOS-in-CellOS backend — runs CellOS cells as nested supervisors. Used for federated and self-hosting topologies.
Documentation
//! In-memory [`SecretBroker`] for development and residue tests — simulates TTL-bound injection and **revoke** on teardown.
//!
//! Stored simulated secret **values** use [`zeroize::Zeroizing`] so removing a row on [`SecretBroker::revoke_for_cell`]
//! overwrites the allocation (best-effort; returned [`SecretView`] still holds a plain copy for the API).

use std::collections::HashMap;
use std::sync::Arc;

use async_trait::async_trait;
use tokio::sync::Mutex;
use tracing::instrument;
use zeroize::Zeroizing;

use cellos_core::ports::SecretBroker;
use cellos_core::{CellosError, SecretView};

type MaterializedSecrets = HashMap<(String, String), Zeroizing<String>>;

#[derive(Clone, Default)]
pub struct MemorySecretBroker {
    inner: Arc<Mutex<MaterializedSecrets>>,
}

impl MemorySecretBroker {
    pub fn new() -> Self {
        Self::default()
    }

    /// Test/operator: entries remaining for this cell (key list).
    pub async fn materialized_keys_for_cell(&self, cell_id: &str) -> Vec<String> {
        let g = self.inner.lock().await;
        g.keys()
            .filter(|(c, _)| c == cell_id)
            .map(|(_, k)| k.clone())
            .collect()
    }

    /// Total broker rows (all cells) — for teardown / leak checks in tests.
    pub async fn total_materialized_rows(&self) -> usize {
        self.inner.lock().await.len()
    }
}

#[async_trait]
impl SecretBroker for MemorySecretBroker {
    #[instrument(skip(self))]
    async fn resolve(
        &self,
        key: &str,
        cell_id: &str,
        _ttl_seconds: u64,
    ) -> Result<SecretView, CellosError> {
        if key.is_empty() {
            return Err(CellosError::SecretBroker("empty secret key".into()));
        }
        let value = Zeroizing::new(format!("simulated:{cell_id}:{key}"));
        let view_value = value.to_string();
        self.inner
            .lock()
            .await
            .insert((cell_id.to_string(), key.to_string()), value);
        Ok(SecretView {
            key: key.to_string(),
            value: zeroize::Zeroizing::new(view_value),
        })
    }

    #[instrument(skip(self))]
    async fn revoke_for_cell(&self, cell_id: &str) -> Result<(), CellosError> {
        let mut g = self.inner.lock().await;
        g.retain(|(c, _), _| c != cell_id);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn revoke_drops_all_for_cell() {
        let b = MemorySecretBroker::new();
        b.resolve("K", "c1", 10).await.unwrap();
        b.resolve("K2", "c1", 10).await.unwrap();
        b.resolve("K", "c2", 10).await.unwrap();
        assert_eq!(b.materialized_keys_for_cell("c1").await.len(), 2);
        b.revoke_for_cell("c1").await.unwrap();
        assert!(b.materialized_keys_for_cell("c1").await.is_empty());
        assert_eq!(b.materialized_keys_for_cell("c2").await.len(), 1);
    }
}