Skip to main content

khive_storage/
event.rs

1//! Event storage capability — append-only operation log (ADR-004).
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    pub fn new(
36        namespace: impl Into<String>,
37        verb: impl Into<String>,
38        kind: EventKind,
39        substrate: SubstrateKind,
40        actor: impl Into<String>,
41    ) -> Self {
42        Self {
43            id: Uuid::new_v4(),
44            namespace: namespace.into(),
45            verb: verb.into(),
46            substrate,
47            actor: actor.into(),
48            kind,
49            outcome: EventOutcome::Success,
50            payload: Value::Object(Default::default()),
51            payload_schema_version: 1,
52            profile_state_version: None,
53            duration_us: 0,
54            target_id: None,
55            session_id: None,
56            aggregate_kind: None,
57            aggregate_id: None,
58            created_at: chrono::Utc::now().timestamp_micros(),
59        }
60    }
61
62    pub fn with_outcome(mut self, o: EventOutcome) -> Self {
63        self.outcome = o;
64        self
65    }
66
67    pub fn with_payload(mut self, payload: Value) -> Self {
68        self.payload = payload;
69        self
70    }
71
72    pub fn with_payload_schema_version(mut self, version: u32) -> Self {
73        self.payload_schema_version = version;
74        self
75    }
76
77    pub fn with_profile_state_version(mut self, version: u64) -> Self {
78        self.profile_state_version = Some(version);
79        self
80    }
81
82    pub fn with_duration_us(mut self, us: i64) -> Self {
83        self.duration_us = us;
84        self
85    }
86
87    pub fn with_target(mut self, id: Uuid) -> Self {
88        self.target_id = Some(id);
89        self
90    }
91
92    pub fn with_session_id(mut self, id: Uuid) -> Self {
93        self.session_id = Some(id);
94        self
95    }
96
97    pub fn with_aggregate(mut self, kind: impl Into<String>, id: Uuid) -> Self {
98        self.aggregate_kind = Some(kind.into());
99        self.aggregate_id = Some(id);
100        self
101    }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
105#[serde(rename_all = "snake_case")]
106pub enum ReferentKind {
107    Entity,
108    Note,
109}
110
111impl ReferentKind {
112    pub const fn name(self) -> &'static str {
113        match self {
114            Self::Entity => "entity",
115            Self::Note => "note",
116        }
117    }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
121#[serde(rename_all = "snake_case")]
122pub enum ObservationRole {
123    Candidate,
124    Selected,
125    Target,
126    Signal,
127}
128
129impl ObservationRole {
130    pub const fn name(self) -> &'static str {
131        match self {
132            Self::Candidate => "candidate",
133            Self::Selected => "selected",
134            Self::Target => "target",
135            Self::Signal => "signal",
136        }
137    }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141pub struct EventObservation {
142    pub event_id: Uuid,
143    pub entity_id: Uuid,
144    pub referent_kind: ReferentKind,
145    pub role: ObservationRole,
146    pub position: u32,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct EventView {
151    pub event: Event,
152    pub observations: Vec<EventObservation>,
153}
154
155/// Filter for querying events. Namespace is implicit in the scoped EventStore.
156#[derive(Clone, Debug, Default, Serialize, Deserialize)]
157pub struct EventFilter {
158    pub ids: Vec<Uuid>,
159    pub kinds: Vec<EventKind>,
160    pub verbs: Vec<String>,
161    pub substrates: Vec<SubstrateKind>,
162    pub actors: Vec<String>,
163    pub after: Option<i64>,
164    pub before: Option<i64>,
165    pub session_id: Option<Uuid>,
166    pub observed: Vec<Uuid>,
167    pub selected: Vec<Uuid>,
168    pub payload_proposal_id: Option<Uuid>,
169}
170
171#[async_trait]
172pub trait EventStore: Send + Sync + 'static {
173    async fn append_event(&self, event: Event) -> StorageResult<()>;
174    async fn append_events(&self, events: Vec<Event>) -> StorageResult<BatchWriteSummary>;
175    async fn get_event(&self, id: Uuid) -> StorageResult<Option<Event>>;
176    async fn query_events(
177        &self,
178        filter: EventFilter,
179        page: PageRequest,
180    ) -> StorageResult<Page<Event>>;
181    async fn count_events(&self, filter: EventFilter) -> StorageResult<u64>;
182}