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}
88
89/// System event types for audit log.
90pub mod audit_events {
91    pub const RECORDED: &str = "_system.audit.recorded";
92}
93
94/// System event types for configuration.
95pub mod config_events {
96    pub const SET: &str = "_system.config.set";
97    pub const DELETED: &str = "_system.config.deleted";
98}
99
100/// System event types for schema registry.
101pub mod schema_events {
102    pub const REGISTERED: &str = "_system.schema.registered";
103    pub const UPDATED: &str = "_system.schema.updated";
104    pub const DELETED: &str = "_system.schema.deleted";
105}
106
107/// System event types for policies.
108pub mod policy_events {
109    pub const CREATED: &str = "_system.policy.created";
110    pub const UPDATED: &str = "_system.policy.updated";
111    pub const DELETED: &str = "_system.policy.deleted";
112}
113
114/// System event types for authentication (API keys, users).
115pub mod auth_events {
116    pub const KEY_PROVISIONED: &str = "_system.auth.key_provisioned";
117    pub const KEY_REVOKED: &str = "_system.auth.key_revoked";
118}
119
120/// System event types for consumer cursor tracking.
121pub mod consumer_events {
122    pub const REGISTERED: &str = "_system.consumer.registered";
123    pub const ACK_UPDATED: &str = "_system.consumer.ack_updated";
124    pub const DELETED: &str = "_system.consumer.deleted";
125}
126
127/// Check whether an event type string belongs to the system namespace.
128pub fn is_system_event_type(event_type: &str) -> bool {
129    event_type.starts_with(SYSTEM_EVENT_TYPE_PREFIX)
130}
131
132/// Check whether an entity ID string belongs to the system namespace.
133pub fn is_system_entity_id(entity_id: &str) -> bool {
134    entity_id.starts_with(SYSTEM_ENTITY_ID_PREFIX)
135}
136
137/// Check whether a tenant ID is the reserved system tenant.
138pub fn is_system_tenant_id(tenant_id: &str) -> bool {
139    tenant_id == SYSTEM_TENANT_ID
140}
141
142/// Build a system entity ID for a given domain and resource ID.
143///
144/// Example: `system_entity_id(SystemDomain::Tenant, "acme-corp")` → `_system:tenant:acme-corp`
145pub fn system_entity_id(domain: SystemDomain, resource_id: &str) -> String {
146    format!(
147        "{}{}:{}",
148        SYSTEM_ENTITY_ID_PREFIX,
149        domain.as_str(),
150        resource_id
151    )
152}
153
154/// Get the system tenant ID as a `TenantId` value object.
155pub fn system_tenant_id() -> TenantId {
156    TenantId::new_unchecked(SYSTEM_TENANT_ID.to_string())
157}
158
159/// Create a system event type from a raw string constant.
160///
161/// Uses `new_unchecked` because system event type constants are compile-time validated.
162pub fn system_event_type(event_type_str: &str) -> EventType {
163    debug_assert!(
164        event_type_str.starts_with(SYSTEM_EVENT_TYPE_PREFIX),
165        "System event type must start with '{SYSTEM_EVENT_TYPE_PREFIX}'"
166    );
167    EventType::new_unchecked(event_type_str.to_string())
168}
169
170/// Create a system entity ID value object.
171pub fn system_entity_id_value(domain: SystemDomain, resource_id: &str) -> EntityId {
172    EntityId::new_unchecked(system_entity_id(domain, resource_id))
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_system_event_type_prefix() {
181        assert!(is_system_event_type("_system.tenant.created"));
182        assert!(is_system_event_type("_system.config.set"));
183        assert!(!is_system_event_type("order.placed"));
184        assert!(!is_system_event_type("system.tenant.created"));
185    }
186
187    #[test]
188    fn test_system_entity_id_prefix() {
189        assert!(is_system_entity_id("_system:tenant:acme"));
190        assert!(is_system_entity_id("_system:config:max_conn"));
191        assert!(!is_system_entity_id("user-123"));
192        assert!(!is_system_entity_id("system:tenant:acme"));
193    }
194
195    #[test]
196    fn test_system_tenant_id() {
197        assert!(is_system_tenant_id("_system"));
198        assert!(!is_system_tenant_id("default"));
199        assert!(!is_system_tenant_id("acme-corp"));
200    }
201
202    #[test]
203    fn test_system_entity_id_construction() {
204        let id = system_entity_id(SystemDomain::Tenant, "acme-corp");
205        assert_eq!(id, "_system:tenant:acme-corp");
206
207        let id = system_entity_id(SystemDomain::Config, "max_connections");
208        assert_eq!(id, "_system:config:max_connections");
209
210        let id = system_entity_id(SystemDomain::Audit, "tenant-1");
211        assert_eq!(id, "_system:audit:tenant-1");
212    }
213
214    #[test]
215    fn test_system_domain_all() {
216        let domains = SystemDomain::all();
217        assert_eq!(domains.len(), 7);
218    }
219
220    #[test]
221    fn test_system_event_type_constants_are_valid() {
222        // All system event type constants should pass EventType validation
223        let constants = [
224            tenant_events::CREATED,
225            tenant_events::UPDATED,
226            tenant_events::SUSPENDED,
227            tenant_events::REACTIVATED,
228            tenant_events::DELETED,
229            tenant_events::QUOTA_UPDATED,
230            tenant_events::USAGE_UPDATED,
231            audit_events::RECORDED,
232            config_events::SET,
233            config_events::DELETED,
234            schema_events::REGISTERED,
235            schema_events::UPDATED,
236            schema_events::DELETED,
237            policy_events::CREATED,
238            policy_events::UPDATED,
239            policy_events::DELETED,
240            auth_events::KEY_PROVISIONED,
241            auth_events::KEY_REVOKED,
242            consumer_events::REGISTERED,
243            consumer_events::ACK_UPDATED,
244            consumer_events::DELETED,
245        ];
246
247        for constant in constants {
248            let result = EventType::new(constant.to_string());
249            assert!(
250                result.is_ok(),
251                "System event type '{}' failed validation: {:?}",
252                constant,
253                result.err()
254            );
255        }
256    }
257
258    #[test]
259    fn test_system_tenant_id_is_valid() {
260        let result = TenantId::new(SYSTEM_TENANT_ID.to_string());
261        assert!(result.is_ok(), "System tenant ID should be valid");
262    }
263
264    #[test]
265    fn test_system_entity_ids_are_valid() {
266        for domain in SystemDomain::all() {
267            let id_str = system_entity_id(*domain, "test-resource");
268            let result = EntityId::new(id_str.clone());
269            assert!(
270                result.is_ok(),
271                "System entity ID '{}' failed validation: {:?}",
272                id_str,
273                result.err()
274            );
275        }
276    }
277
278    #[test]
279    fn test_system_event_type_helper() {
280        let et = system_event_type(tenant_events::CREATED);
281        assert_eq!(et.as_str(), "_system.tenant.created");
282    }
283
284    #[test]
285    fn test_system_entity_id_value_helper() {
286        let eid = system_entity_id_value(SystemDomain::Tenant, "acme");
287        assert_eq!(eid.as_str(), "_system:tenant:acme");
288    }
289}