Skip to main content

basil_core/core/
event.rs

1// SPDX-FileCopyrightText: 2026 OpenBasil Contributors
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Internal broker event source shared by Admin Watch and future SPIFFE streams.
6
7use std::time::SystemTime;
8
9use tokio::sync::broadcast;
10
11/// Broker event fanout with bounded lag semantics.
12#[derive(Debug, Clone)]
13pub struct EventSource {
14    sender: broadcast::Sender<BrokerEvent>,
15}
16
17/// A public broker event.
18#[derive(Debug, Clone)]
19pub struct BrokerEvent {
20    /// Event creation time.
21    pub at: SystemTime,
22    /// Event payload.
23    pub kind: BrokerEventKind,
24}
25
26/// Event payload variants.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum BrokerEventKind {
29    /// A catalog key rotated to a new version.
30    KeyRotated {
31        /// Dotted catalog key id.
32        key_id: String,
33        /// New latest version.
34        new_version: u32,
35    },
36    /// A trust-domain bundle changed.
37    BundleChanged {
38        /// Trust domain whose bundle changed.
39        trust_domain: String,
40    },
41    /// A credential or bundle item was revoked.
42    Revoked {
43        /// Trust domain containing the revoked id.
44        trust_domain: String,
45        /// Public revocation id, such as an X.509 serial or JWT jti.
46        id: String,
47    },
48}
49
50impl Default for EventSource {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl EventSource {
57    /// Build an event source with a bounded broadcast buffer.
58    #[must_use]
59    pub fn new() -> Self {
60        let (sender, _) = broadcast::channel(1024);
61        Self { sender }
62    }
63
64    /// Subscribe to future events.
65    #[must_use]
66    pub fn subscribe(&self) -> broadcast::Receiver<BrokerEvent> {
67        self.sender.subscribe()
68    }
69
70    /// Publish a key rotation event.
71    pub fn key_rotated(&self, key_id: impl Into<String>, new_version: u32) {
72        self.publish(BrokerEventKind::KeyRotated {
73            key_id: key_id.into(),
74            new_version,
75        });
76    }
77
78    /// Publish a bundle change event.
79    pub fn bundle_changed(&self, trust_domain: impl Into<String>) {
80        self.publish(BrokerEventKind::BundleChanged {
81            trust_domain: trust_domain.into(),
82        });
83    }
84
85    /// Publish a revocation event.
86    pub fn revoked(&self, trust_domain: impl Into<String>, id: impl Into<String>) {
87        self.publish(BrokerEventKind::Revoked {
88            trust_domain: trust_domain.into(),
89            id: id.into(),
90        });
91    }
92
93    fn publish(&self, kind: BrokerEventKind) {
94        let _receivers = self.sender.send(BrokerEvent {
95            at: SystemTime::now(),
96            kind,
97        });
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[tokio::test]
106    async fn subscribers_receive_events() {
107        let source = EventSource::new();
108        let mut rx = source.subscribe();
109        source.key_rotated("app.key", 2);
110        let event = rx.recv().await.expect("event received");
111        assert_eq!(
112            event.kind,
113            BrokerEventKind::KeyRotated {
114                key_id: "app.key".to_string(),
115                new_version: 2,
116            }
117        );
118    }
119}