Skip to main content

khive_types/
event.rs

1//! Event substrate — universal system log.
2//!
3//! Every verb execution produces an Event. Audit, usage metering, derived
4//! state, and evolutionary learning (edge reinforcement, traversal history)
5//! are all computed via Fold over the Event stream.
6
7extern crate alloc;
8use alloc::string::String;
9use core::fmt;
10
11use crate::{Header, Id128, SubstrateKind};
12
13/// A system event. Append-only, never mutated or deleted.
14#[derive(Clone, Debug)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct Event {
17    #[cfg_attr(feature = "serde", serde(flatten))]
18    pub header: Header,
19    /// The verb that was executed (e.g., "create", "search", "traverse").
20    pub verb: String,
21    /// Which substrate type was acted upon.
22    pub substrate: SubstrateKind,
23    /// Who performed the action (free-form actor string).
24    pub actor: String,
25    /// Outcome of the verb execution.
26    pub outcome: EventOutcome,
27    /// Optional verb-specific structured data (JSON in DB).
28    pub data: Option<String>,
29    /// Duration of the verb execution in microseconds.
30    pub duration_us: u64,
31    /// ID of the substrate record that was acted upon, if applicable.
32    pub target_id: Option<Id128>,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
38pub enum EventOutcome {
39    #[default]
40    Success,
41    Denied,
42    Error,
43}
44
45impl EventOutcome {
46    pub const fn name(self) -> &'static str {
47        match self {
48            Self::Success => "success",
49            Self::Denied => "denied",
50            Self::Error => "error",
51        }
52    }
53}
54
55impl fmt::Display for EventOutcome {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.write_str(self.name())
58    }
59}
60
61/// Builder for events. Used by the verb dispatch path.
62pub struct EventBuilder {
63    verb: String,
64    substrate: SubstrateKind,
65    actor: String,
66    outcome: EventOutcome,
67    data: Option<String>,
68    duration_us: u64,
69    target_id: Option<Id128>,
70}
71
72impl EventBuilder {
73    pub fn new(
74        verb: impl Into<String>,
75        substrate: SubstrateKind,
76        actor: impl Into<String>,
77    ) -> Self {
78        Self {
79            verb: verb.into(),
80            substrate,
81            actor: actor.into(),
82            outcome: EventOutcome::Success,
83            data: None,
84            duration_us: 0,
85            target_id: None,
86        }
87    }
88
89    pub fn outcome(mut self, outcome: EventOutcome) -> Self {
90        self.outcome = outcome;
91        self
92    }
93
94    pub fn data(mut self, data: impl Into<String>) -> Self {
95        self.data = Some(data.into());
96        self
97    }
98
99    pub fn duration_us(mut self, us: u64) -> Self {
100        self.duration_us = us;
101        self
102    }
103
104    pub fn target_id(mut self, id: Id128) -> Self {
105        self.target_id = Some(id);
106        self
107    }
108
109    pub fn build(self, header: Header) -> Event {
110        Event {
111            header,
112            verb: self.verb,
113            substrate: self.substrate,
114            actor: self.actor,
115            outcome: self.outcome,
116            data: self.data,
117            duration_us: self.duration_us,
118            target_id: self.target_id,
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use crate::{Namespace, Timestamp};
127
128    fn header() -> Header {
129        Header::new(
130            Id128::from_u128(1),
131            Namespace::default(),
132            Timestamp::from_secs(1700000000),
133        )
134    }
135
136    #[test]
137    fn event_builder() {
138        let event = EventBuilder::new("search", SubstrateKind::Note, "agent:research")
139            .outcome(EventOutcome::Success)
140            .duration_us(1500)
141            .target_id(Id128::from_u128(42))
142            .build(header());
143
144        assert_eq!(event.verb, "search");
145        assert_eq!(event.substrate, SubstrateKind::Note);
146        assert_eq!(event.actor, "agent:research");
147        assert_eq!(event.outcome, EventOutcome::Success);
148        assert_eq!(event.duration_us, 1500);
149        assert_eq!(event.target_id, Some(Id128::from_u128(42)));
150    }
151
152    #[test]
153    fn denied_outcome() {
154        let event = EventBuilder::new("create", SubstrateKind::Note, "user:ocean")
155            .outcome(EventOutcome::Denied)
156            .build(header());
157        assert_eq!(event.outcome, EventOutcome::Denied);
158    }
159}