Skip to main content

runifold_core/
event.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::{EffectId, EventId, RunError, RunId, Usage};
8
9/// Metadata shared by every runtime event.
10#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
11pub struct EventMeta {
12    /// Globally unique event identity.
13    pub event_id: EventId,
14    /// Monotonic sequence number within a run.
15    pub sequence: u64,
16    /// Run that emitted this event.
17    pub run_id: RunId,
18    /// Parent of the emitting run.
19    pub parent_run_id: Option<RunId>,
20    /// Event that caused this event, when known.
21    pub caused_by: Option<EventId>,
22    /// Milliseconds since the Unix epoch.
23    pub timestamp_ms: u64,
24}
25
26/// An immutable runtime fact.
27#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
28pub struct RunEvent {
29    /// Event envelope metadata.
30    pub meta: EventMeta,
31    /// Event payload.
32    pub kind: RunEventKind,
33}
34
35/// Runtime event categories.
36#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
37#[non_exhaustive]
38pub enum RunEventKind {
39    /// Run lifecycle event.
40    Lifecycle(LifecycleEvent),
41    /// External-effect event.
42    Effect(EffectEvent),
43    /// Parent-child relationship event.
44    Child(ChildEvent),
45    /// Budget-accounting event.
46    Budget(BudgetEvent),
47    /// Namespaced domain-specific event.
48    Domain(DomainEvent),
49}
50
51/// Run lifecycle facts.
52#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
53#[non_exhaustive]
54pub enum LifecycleEvent {
55    /// The run started.
56    Started,
57    /// The run completed with a serializable output.
58    Completed {
59        /// Final serializable output.
60        output: Value,
61    },
62    /// The run failed.
63    Failed {
64        /// Structured terminal error.
65        error: RunError,
66    },
67    /// The run was cancelled.
68    Cancelled,
69}
70
71/// External-effect facts.
72#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
73#[non_exhaustive]
74pub enum EffectEvent {
75    /// An effect was requested.
76    Requested {
77        /// Requested effect identity.
78        effect_id: EffectId,
79    },
80    /// Policy allowed the effect to start.
81    Started {
82        /// Started effect identity.
83        effect_id: EffectId,
84    },
85    /// The effect completed.
86    Completed {
87        /// Completed effect identity.
88        effect_id: EffectId,
89        /// Serializable effect output.
90        output: Value,
91    },
92    /// The effect failed.
93    Failed {
94        /// Failed effect identity.
95        effect_id: EffectId,
96        /// Structured effect error.
97        error: RunError,
98    },
99}
100
101/// Parent-child run relationship facts.
102#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
103#[non_exhaustive]
104pub enum ChildEvent {
105    /// A child run was created.
106    Started {
107        /// Child run identity.
108        child_run_id: RunId,
109    },
110    /// A child run completed.
111    Completed {
112        /// Child run identity.
113        child_run_id: RunId,
114    },
115    /// A child run failed.
116    Failed {
117        /// Child run identity.
118        child_run_id: RunId,
119    },
120    /// A child run was cancelled.
121    Cancelled {
122        /// Child run identity.
123        child_run_id: RunId,
124    },
125}
126
127/// Budget-accounting facts.
128#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
129#[non_exhaustive]
130pub enum BudgetEvent {
131    /// Cumulative usage changed.
132    Updated {
133        /// New cumulative usage snapshot.
134        usage: Usage,
135    },
136}
137
138/// A forward-compatible namespaced event.
139#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
140pub struct DomainEvent {
141    /// Stable namespace owned by a crate or protocol adapter.
142    pub namespace: String,
143    /// Event type within the namespace.
144    pub name: String,
145    /// Structured payload.
146    pub payload: Value,
147}
148
149/// Creates events with monotonic per-run sequence numbers.
150#[derive(Debug)]
151pub struct EventFactory {
152    run_id: RunId,
153    parent_run_id: Option<RunId>,
154    sequence: AtomicU64,
155}
156
157impl EventFactory {
158    /// Creates an event factory for a run.
159    pub const fn new(run_id: RunId, parent_run_id: Option<RunId>) -> Self {
160        Self {
161            run_id,
162            parent_run_id,
163            sequence: AtomicU64::new(0),
164        }
165    }
166
167    /// Emits the next event.
168    pub fn emit(&self, kind: RunEventKind, caused_by: Option<EventId>) -> RunEvent {
169        let sequence = self.sequence.fetch_add(1, Ordering::Relaxed);
170        let timestamp_ms = SystemTime::now()
171            .duration_since(UNIX_EPOCH)
172            .map_or(0, |duration| {
173                u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
174            });
175
176        RunEvent {
177            meta: EventMeta {
178                event_id: EventId::new(),
179                sequence,
180                run_id: self.run_id,
181                parent_run_id: self.parent_run_id,
182                caused_by,
183                timestamp_ms,
184            },
185            kind,
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::{EventFactory, LifecycleEvent, RunEventKind};
193    use crate::RunId;
194
195    #[test]
196    fn event_sequences_are_monotonic_and_causal() {
197        let factory = EventFactory::new(RunId::new(), None);
198        let started = factory.emit(RunEventKind::Lifecycle(LifecycleEvent::Started), None);
199        let completed = factory.emit(
200            RunEventKind::Lifecycle(LifecycleEvent::Completed {
201                output: serde_json::json!("done"),
202            }),
203            Some(started.meta.event_id),
204        );
205
206        assert_eq!(started.meta.sequence, 0);
207        assert_eq!(completed.meta.sequence, 1);
208        assert_eq!(completed.meta.caused_by, Some(started.meta.event_id));
209    }
210}