Skip to main content

khive_storage/
event.rs

1//! Event storage capability — append-only operation log.
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use uuid::Uuid;
7
8use khive_types::{EventKind, EventOutcome, SubstrateKind};
9
10use crate::types::{BatchWriteSummary, Page, PageRequest, StorageResult};
11
12/// Storage-level event record. Every verb execution produces one.
13/// Immutable once appended; projection rows are written beside it at append time.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Event {
16    pub id: Uuid,
17    pub namespace: String,
18    pub verb: String,
19    pub substrate: SubstrateKind,
20    pub actor: String,
21    pub kind: EventKind,
22    pub outcome: EventOutcome,
23    pub payload: Value,
24    pub payload_schema_version: u32,
25    pub profile_state_version: Option<u64>,
26    pub duration_us: i64,
27    pub target_id: Option<Uuid>,
28    pub session_id: Option<Uuid>,
29    pub aggregate_kind: Option<String>,
30    pub aggregate_id: Option<Uuid>,
31    pub created_at: i64,
32}
33
34impl Event {
35    /// Create a new event with a generated UUID and current timestamp.
36    pub fn new(
37        namespace: impl Into<String>,
38        verb: impl Into<String>,
39        kind: EventKind,
40        substrate: SubstrateKind,
41        actor: impl Into<String>,
42    ) -> Self {
43        Self {
44            id: Uuid::new_v4(),
45            namespace: namespace.into(),
46            verb: verb.into(),
47            substrate,
48            actor: actor.into(),
49            kind,
50            outcome: EventOutcome::Success,
51            payload: Value::Object(Default::default()),
52            payload_schema_version: 1,
53            profile_state_version: None,
54            duration_us: 0,
55            target_id: None,
56            session_id: None,
57            aggregate_kind: None,
58            aggregate_id: None,
59            created_at: chrono::Utc::now().timestamp_micros(),
60        }
61    }
62
63    /// Set the event outcome (success/failure).
64    pub fn with_outcome(mut self, o: EventOutcome) -> Self {
65        self.outcome = o;
66        self
67    }
68
69    /// Set the event payload JSON.
70    pub fn with_payload(mut self, payload: Value) -> Self {
71        self.payload = payload;
72        self
73    }
74
75    /// Set the payload schema version for forward compatibility.
76    pub fn with_payload_schema_version(mut self, version: u32) -> Self {
77        self.payload_schema_version = version;
78        self
79    }
80
81    /// Set the brain profile state version at event time.
82    pub fn with_profile_state_version(mut self, version: u64) -> Self {
83        self.profile_state_version = Some(version);
84        self
85    }
86
87    /// Set the operation duration in microseconds.
88    pub fn with_duration_us(mut self, us: i64) -> Self {
89        self.duration_us = us;
90        self
91    }
92
93    /// Set the target entity/note ID for this event.
94    pub fn with_target(mut self, id: Uuid) -> Self {
95        self.target_id = Some(id);
96        self
97    }
98
99    /// Set the session ID for correlating related events.
100    pub fn with_session_id(mut self, id: Uuid) -> Self {
101        self.session_id = Some(id);
102        self
103    }
104
105    /// Set the aggregate kind and ID for event-sourced projections.
106    pub fn with_aggregate(mut self, kind: impl Into<String>, id: Uuid) -> Self {
107        self.aggregate_kind = Some(kind.into());
108        self.aggregate_id = Some(id);
109        self
110    }
111}
112
113/// Which substrate (entity or note) the referent record lives in.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
115#[serde(rename_all = "snake_case")]
116pub enum ReferentKind {
117    Entity,
118    Note,
119}
120
121impl ReferentKind {
122    /// Return the lowercase string name for this referent kind.
123    pub const fn name(self) -> &'static str {
124        match self {
125            Self::Entity => "entity",
126            Self::Note => "note",
127        }
128    }
129}
130
131/// Role of a referent in a brain observation (candidate, selected, target, signal).
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
133#[serde(rename_all = "snake_case")]
134pub enum ObservationRole {
135    Candidate,
136    Selected,
137    Target,
138    Signal,
139}
140
141impl ObservationRole {
142    /// Return the lowercase string name for this observation role.
143    pub const fn name(self) -> &'static str {
144        match self {
145            Self::Candidate => "candidate",
146            Self::Selected => "selected",
147            Self::Target => "target",
148            Self::Signal => "signal",
149        }
150    }
151}
152
153/// A single entity observation recorded alongside an event.
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155pub struct EventObservation {
156    pub event_id: Uuid,
157    pub entity_id: Uuid,
158    pub referent_kind: ReferentKind,
159    pub role: ObservationRole,
160    pub position: u32,
161}
162
163/// An event together with its associated observations.
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct EventView {
166    pub event: Event,
167    pub observations: Vec<EventObservation>,
168}
169
170/// Filter for querying events. Namespace is implicit in the scoped EventStore.
171#[derive(Clone, Debug, Default, Serialize, Deserialize)]
172pub struct EventFilter {
173    pub ids: Vec<Uuid>,
174    pub kinds: Vec<EventKind>,
175    pub verbs: Vec<String>,
176    pub substrates: Vec<SubstrateKind>,
177    pub actors: Vec<String>,
178    pub after: Option<i64>,
179    pub before: Option<i64>,
180    pub session_id: Option<Uuid>,
181    pub observed: Vec<Uuid>,
182    pub selected: Vec<Uuid>,
183    pub payload_proposal_id: Option<Uuid>,
184}
185
186/// Append-only operation log for verb executions.
187#[async_trait]
188pub trait EventStore: Send + Sync + 'static {
189    /// Append a single event to the log.
190    async fn append_event(&self, event: Event) -> StorageResult<()>;
191    /// Append a batch of events to the log.
192    async fn append_events(&self, events: Vec<Event>) -> StorageResult<BatchWriteSummary>;
193    /// Fetch an event by UUID, returning `None` if absent.
194    async fn get_event(&self, id: Uuid) -> StorageResult<Option<Event>>;
195    /// Query events matching a filter with pagination.
196    async fn query_events(
197        &self,
198        filter: EventFilter,
199        page: PageRequest,
200    ) -> StorageResult<Page<Event>>;
201    /// Count events matching a filter.
202    async fn count_events(&self, filter: EventFilter) -> StorageResult<u64>;
203}