Skip to main content

allsource_core/domain/value_objects/
system_stream.rs

1use crate::domain::value_objects::{EntityId, EventType, TenantId};
2
3/// System stream namespace prefix.
4///
5/// All system streams use event types starting with `_system.` and
6/// a reserved tenant ID of `_system`. This isolates operational metadata
7/// from user event data while leveraging the same durable storage engine.
8///
9/// # Naming Convention
10///
11/// - Event types: `_system.<domain>.<action>` (e.g., `_system.tenant.created`)
12/// - Entity IDs: `_system:<domain>:<id>` (e.g., `_system:tenant:acme-corp`)
13/// - Tenant ID: `_system` (reserved, rejected for user tenants)
14///
15/// # Inspiration
16///
17/// - Kafka KRaft: `__consumer_offsets`, `__transaction_state`
18/// - CockroachDB: `system.*` tables
19/// - FoundationDB: `\xff` system keyspace
20/// - EventStoreDB: `$` prefix for system streams
21///
22/// The reserved event type prefix for system streams.
23pub const SYSTEM_EVENT_TYPE_PREFIX: &str = "_system.";
24
25/// The reserved tenant ID for system metadata.
26pub const SYSTEM_TENANT_ID: &str = "_system";
27
28/// The reserved entity ID prefix for system streams.
29pub const SYSTEM_ENTITY_ID_PREFIX: &str = "_system:";
30
31/// Known system stream domains.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum SystemDomain {
34    /// Tenant lifecycle: create, update, suspend, reactivate, delete
35    Tenant,
36    /// Audit log: immutable append-only security/compliance log
37    Audit,
38    /// Configuration: key-value settings (log-compacted)
39    Config,
40    /// Schema registry: schema definitions and versions
41    Schema,
42    /// Policies: access policies and retention rules
43    Policy,
44    /// Consumer: durable subscription cursor positions
45    Consumer,
46    /// Auth: API keys, users, and authentication metadata
47    Auth,
48}
49
50impl SystemDomain {
51    /// Get the domain name as used in event types and entity IDs.
52    pub fn as_str(&self) -> &'static str {
53        match self {
54            Self::Tenant => "tenant",
55            Self::Audit => "audit",
56            Self::Config => "config",
57            Self::Schema => "schema",
58            Self::Policy => "policy",
59            Self::Consumer => "consumer",
60            Self::Auth => "auth",
61        }
62    }
63
64    /// All known system domains.
65    pub fn all() -> &'static [SystemDomain] {
66        &[
67            Self::Tenant,
68            Self::Audit,
69            Self::Config,
70            Self::Schema,
71            Self::Policy,
72            Self::Consumer,
73            Self::Auth,
74        ]
75    }
76}
77
78/// System event types for tenant lifecycle.
79pub mod tenant_events {
80    pub const CREATED: &str = "_system.tenant.created";
81    pub const UPDATED: &str = "_system.tenant.updated";
82    pub const SUSPENDED: &str = "_system.tenant.suspended";
83    pub const REACTIVATED: &str = "_system.tenant.reactivated";
84    pub const DELETED: &str = "_system.tenant.deleted";
85    pub const QUOTA_UPDATED: &str = "_system.tenant.quota_updated";
86    pub const USAGE_UPDATED: &str = "_system.tenant.usage_updated";
87    pub const SCHEMA_ENFORCEMENT_UPDATED: &str = "_system.tenant.schema_enforcement_updated";
88}
89
90/// System event types for audit log.
91pub mod audit_events {
92    pub const RECORDED: &str = "_system.audit.recorded";
93}
94
95/// System event types for configuration.
96pub mod config_events {
97    pub const SET: &str = "_system.config.set";
98    pub const DELETED: &str = "_system.config.deleted";
99}
100
101/// System event types for schema registry.
102pub mod schema_events {
103    pub const REGISTERED: &str = "_system.schema.registered";
104    pub const UPDATED: &str = "_system.schema.updated";
105    pub const DELETED: &str = "_system.schema.deleted";
106}
107
108/// System event types for policies.
109pub mod policy_events {
110    pub const CREATED: &str = "_system.policy.created";
111    pub const UPDATED: &str = "_system.policy.updated";
112    pub const DELETED: &str = "_system.policy.deleted";
113}
114
115/// System event types for authentication (API keys, users).
116pub mod auth_events {
117    pub const KEY_PROVISIONED: &str = "_system.auth.key_provisioned";
118    pub const KEY_REVOKED: &str = "_system.auth.key_revoked";
119}
120
121/// System event types for consumer cursor tracking.
122pub mod consumer_events {
123    pub const REGISTERED: &str = "_system.consumer.registered";
124    pub const ACK_UPDATED: &str = "_system.consumer.ack_updated";
125    pub const DELETED: &str = "_system.consumer.deleted";
126}
127
128/// Check whether an event type string belongs to the system namespace.
129pub fn is_system_event_type(event_type: &str) -> bool {
130    event_type.starts_with(SYSTEM_EVENT_TYPE_PREFIX)
131}
132
133/// Check whether an entity ID string belongs to the system namespace.
134pub fn is_system_entity_id(entity_id: &str) -> bool {
135    entity_id.starts_with(SYSTEM_ENTITY_ID_PREFIX)
136}
137
138/// Check whether a tenant ID is the reserved system tenant.
139pub fn is_system_tenant_id(tenant_id: &str) -> bool {
140    tenant_id == SYSTEM_TENANT_ID
141}
142
143/// Build a system entity ID for a given domain and resource ID.
144///
145/// Example: `system_entity_id(SystemDomain::Tenant, "acme-corp")` → `_system:tenant:acme-corp`
146pub fn system_entity_id(domain: SystemDomain, resource_id: &str) -> String {
147    format!(
148        "{}{}:{}",
149        SYSTEM_ENTITY_ID_PREFIX,
150        domain.as_str(),
151        resource_id
152    )
153}
154
155/// Get the system tenant ID as a `TenantId` value object.
156pub fn system_tenant_id() -> TenantId {
157    TenantId::new_unchecked(SYSTEM_TENANT_ID.to_string())
158}
159
160/// Create a system event type from a raw string constant.
161///
162/// Uses `new_unchecked` because system event type constants are compile-time validated.
163pub fn system_event_type(event_type_str: &str) -> EventType {
164    debug_assert!(
165        event_type_str.starts_with(SYSTEM_EVENT_TYPE_PREFIX),
166        "System event type must start with '{SYSTEM_EVENT_TYPE_PREFIX}'"
167    );
168    EventType::new_unchecked(event_type_str.to_string())
169}
170
171/// Create a system entity ID value object.
172pub fn system_entity_id_value(domain: SystemDomain, resource_id: &str) -> EntityId {
173    EntityId::new_unchecked(system_entity_id(domain, resource_id))
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_system_event_type_prefix() {
182        assert!(is_system_event_type("_system.tenant.created"));
183        assert!(is_system_event_type("_system.config.set"));
184        assert!(!is_system_event_type("order.placed"));
185        assert!(!is_system_event_type("system.tenant.created"));
186    }
187
188    #[test]
189    fn test_system_entity_id_prefix() {
190        assert!(is_system_entity_id("_system:tenant:acme"));
191        assert!(is_system_entity_id("_system:config:max_conn"));
192        assert!(!is_system_entity_id("user-123"));
193        assert!(!is_system_entity_id("system:tenant:acme"));
194    }
195
196    #[test]
197    fn test_system_tenant_id() {
198        assert!(is_system_tenant_id("_system"));
199        assert!(!is_system_tenant_id("default"));
200        assert!(!is_system_tenant_id("acme-corp"));
201    }
202
203    #[test]
204    fn test_system_entity_id_construction() {
205        let id = system_entity_id(SystemDomain::Tenant, "acme-corp");
206        assert_eq!(id, "_system:tenant:acme-corp");
207
208        let id = system_entity_id(SystemDomain::Config, "max_connections");
209        assert_eq!(id, "_system:config:max_connections");
210
211        let id = system_entity_id(SystemDomain::Audit, "tenant-1");
212        assert_eq!(id, "_system:audit:tenant-1");
213    }
214
215    #[test]
216    fn test_system_domain_all() {
217        let domains = SystemDomain::all();
218        assert_eq!(domains.len(), 7);
219    }
220
221    #[test]
222    fn test_system_event_type_constants_are_valid() {
223        // All system event type constants should pass EventType validation
224        let constants = [
225            tenant_events::CREATED,
226            tenant_events::UPDATED,
227            tenant_events::SUSPENDED,
228            tenant_events::REACTIVATED,
229            tenant_events::DELETED,
230            tenant_events::QUOTA_UPDATED,
231            tenant_events::USAGE_UPDATED,
232            audit_events::RECORDED,
233            config_events::SET,
234            config_events::DELETED,
235            schema_events::REGISTERED,
236            schema_events::UPDATED,
237            schema_events::DELETED,
238            policy_events::CREATED,
239            policy_events::UPDATED,
240            policy_events::DELETED,
241            auth_events::KEY_PROVISIONED,
242            auth_events::KEY_REVOKED,
243            consumer_events::REGISTERED,
244            consumer_events::ACK_UPDATED,
245            consumer_events::DELETED,
246        ];
247
248        for constant in constants {
249            let result = EventType::new(constant.to_string());
250            assert!(
251                result.is_ok(),
252                "System event type '{}' failed validation: {:?}",
253                constant,
254                result.err()
255            );
256        }
257    }
258
259    #[test]
260    fn test_system_tenant_id_is_valid() {
261        let result = TenantId::new(SYSTEM_TENANT_ID.to_string());
262        assert!(result.is_ok(), "System tenant ID should be valid");
263    }
264
265    #[test]
266    fn test_system_entity_ids_are_valid() {
267        for domain in SystemDomain::all() {
268            let id_str = system_entity_id(*domain, "test-resource");
269            let result = EntityId::new(id_str.clone());
270            assert!(
271                result.is_ok(),
272                "System entity ID '{}' failed validation: {:?}",
273                id_str,
274                result.err()
275            );
276        }
277    }
278
279    #[test]
280    fn test_system_event_type_helper() {
281        let et = system_event_type(tenant_events::CREATED);
282        assert_eq!(et.as_str(), "_system.tenant.created");
283    }
284
285    #[test]
286    fn test_system_entity_id_value_helper() {
287        let eid = system_entity_id_value(SystemDomain::Tenant, "acme");
288        assert_eq!(eid.as_str(), "_system:tenant:acme");
289    }
290}