cellstate_core/
event.rs

1//! Event types for the Event DAG system.
2//!
3//! The Event DAG provides unidirectional event flow with upstream signaling
4//! (the "tram car tracks" pattern).
5//!
6//! # Event Header
7//!
8//! The `EventHeader` is a 64-byte cache-aligned structure for optimal memory access.
9//! It contains all metadata needed to process an event without accessing the payload.
10
11use bitflags::bitflags;
12use serde::{Deserialize, Serialize};
13use std::fmt;
14use uuid::Uuid;
15
16use crate::identity::EntityIdType;
17use crate::{CellstateResult, EventId, OperationalError};
18
19// ============================================================================
20// EVENT IDENTIFICATION
21// ============================================================================
22
23// ============================================================================
24// DAG POSITION
25// ============================================================================
26
27/// Position of an event in the DAG.
28///
29/// The DAG position uniquely identifies where an event sits in the event graph.
30/// - `depth`: Distance from the root event (0 = root)
31/// - `lane`: Parallel track for fan-out scenarios
32/// - `sequence`: Monotonic counter within a lane
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
35#[repr(C)]
36pub struct DagPosition {
37    /// Distance from the root event (0 = root event)
38    pub depth: u32,
39    /// Parallel track number for fan-out (0 = main track)
40    pub lane: u32,
41    /// Monotonic sequence number within the lane
42    pub sequence: u32,
43}
44
45impl DagPosition {
46    /// Create a new DAG position.
47    pub const fn new(depth: u32, lane: u32, sequence: u32) -> Self {
48        Self {
49            depth,
50            lane,
51            sequence,
52        }
53    }
54
55    /// Create the root position.
56    pub const fn root() -> Self {
57        Self::new(0, 0, 0)
58    }
59
60    /// Create a child position on the same lane.
61    pub const fn child(&self, sequence: u32) -> Self {
62        Self::new(self.depth + 1, self.lane, sequence)
63    }
64
65    /// Create a position on a new lane (fork).
66    pub const fn fork(&self, new_lane: u32, sequence: u32) -> Self {
67        Self::new(self.depth + 1, new_lane, sequence)
68    }
69
70    /// Check if this position is a potential ancestor of another based on depth and lane.
71    ///
72    /// Returns `true` if `self` is at a shallower depth on the same lane as `other`.
73    /// Note: this is a necessary but not sufficient condition for true ancestry —
74    /// it does not verify sequence continuity along the parent chain.
75    pub const fn is_ancestor_of(&self, other: &Self) -> bool {
76        self.depth < other.depth && self.lane == other.lane
77    }
78
79    /// Check if this is the root position.
80    pub const fn is_root(&self) -> bool {
81        self.depth == 0
82    }
83}
84
85impl Default for DagPosition {
86    fn default() -> Self {
87        Self::root()
88    }
89}
90
91impl fmt::Display for DagPosition {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "{}:{}:{}", self.depth, self.lane, self.sequence)
94    }
95}
96
97// ============================================================================
98// EVENT KIND
99// ============================================================================
100
101/// Event kind encoded as a 16-bit value.
102///
103/// The upper 4 bits encode the category, the lower 12 bits encode the specific type.
104/// This allows 16 categories with 4096 event types each (65536 total).
105///
106/// Category allocation:
107/// - 0x0xxx: System events
108/// - 0x1xxx: Trajectory events
109/// - 0x2xxx: Scope events
110/// - 0x3xxx: Artifact events
111/// - 0x4xxx: Note events
112/// - 0x5xxx: Turn events
113/// - 0x6xxx: Agent events
114/// - 0x7xxx: Lock events
115/// - 0x8xxx: Message events
116/// - 0x9xxx: Delegation events
117/// - 0xAxxx: Handoff events
118/// - 0xBxxx: Edge events
119/// - 0xCxxx: Evolution events
120/// - 0xDxxx: Effect events (errors, compensations)
121/// - 0xExxx: Reserved
122/// - 0xFxxx: Custom/extension events
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
124#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
125#[repr(transparent)]
126pub struct EventKind(pub u16);
127
128impl EventKind {
129    // Generic data event (neutral/unspecified kind)
130    pub const DATA: Self = Self(0x0000);
131
132    // System events (0x0xxx)
133    pub const SYSTEM_INIT: Self = Self(0x0001);
134    pub const SYSTEM_SHUTDOWN: Self = Self(0x0002);
135    pub const SYSTEM_HEARTBEAT: Self = Self(0x0003);
136    pub const SYSTEM_CONFIG_CHANGE: Self = Self(0x0004);
137
138    // Trajectory events (0x1xxx)
139    pub const TRAJECTORY_CREATED: Self = Self(0x1001);
140    pub const TRAJECTORY_UPDATED: Self = Self(0x1002);
141    pub const TRAJECTORY_COMPLETED: Self = Self(0x1003);
142    pub const TRAJECTORY_FAILED: Self = Self(0x1004);
143    pub const TRAJECTORY_SUSPENDED: Self = Self(0x1005);
144    pub const TRAJECTORY_RESUMED: Self = Self(0x1006);
145    pub const TRAJECTORY_DELETED: Self = Self(0x1007);
146
147    // Scope events (0x2xxx)
148    pub const SCOPE_CREATED: Self = Self(0x2001);
149    pub const SCOPE_UPDATED: Self = Self(0x2002);
150    pub const SCOPE_CLOSED: Self = Self(0x2003);
151    pub const SCOPE_CHECKPOINTED: Self = Self(0x2004);
152    /// Context window was repaged mid-scope (sections evicted/promoted).
153    pub const SCOPE_CONTEXT_PAGED: Self = Self(0x2005);
154
155    // Artifact events (0x3xxx)
156    pub const ARTIFACT_CREATED: Self = Self(0x3001);
157    pub const ARTIFACT_UPDATED: Self = Self(0x3002);
158    pub const ARTIFACT_SUPERSEDED: Self = Self(0x3003);
159    pub const ARTIFACT_DELETED: Self = Self(0x3004);
160
161    // Note events (0x4xxx)
162    pub const NOTE_CREATED: Self = Self(0x4001);
163    pub const NOTE_UPDATED: Self = Self(0x4002);
164    pub const NOTE_SUPERSEDED: Self = Self(0x4003);
165    pub const NOTE_DELETED: Self = Self(0x4004);
166    pub const NOTE_ACCESSED: Self = Self(0x4005);
167
168    // Turn events (0x5xxx)
169    pub const TURN_CREATED: Self = Self(0x5001);
170
171    // Agent events (0x6xxx)
172    pub const AGENT_REGISTERED: Self = Self(0x6001);
173    pub const AGENT_UPDATED: Self = Self(0x6002);
174    pub const AGENT_UNREGISTERED: Self = Self(0x6003);
175    pub const AGENT_STATUS_CHANGED: Self = Self(0x6004);
176
177    // BDI Goal events (0x6xxx — agent subcategory)
178    pub const GOAL_CREATED: Self = Self(0x6010);
179    pub const GOAL_ACTIVATED: Self = Self(0x6011);
180    pub const GOAL_ACHIEVED: Self = Self(0x6012);
181    pub const GOAL_FAILED: Self = Self(0x6013);
182
183    // BDI Plan events (0x6xxx — agent subcategory)
184    pub const PLAN_CREATED: Self = Self(0x6020);
185    pub const PLAN_STARTED: Self = Self(0x6021);
186    pub const PLAN_COMPLETED: Self = Self(0x6022);
187    pub const PLAN_FAILED: Self = Self(0x6023);
188
189    // BDI Step events (0x6xxx — agent subcategory)
190    pub const STEP_COMPLETED: Self = Self(0x6030);
191    pub const STEP_FAILED: Self = Self(0x6031);
192
193    // BDI Belief events (0x6xxx — agent subcategory)
194    pub const BELIEF_CREATED: Self = Self(0x6040);
195    pub const BELIEF_SUPERSEDED: Self = Self(0x6041);
196
197    // BDI Deliberation events (0x6xxx — agent subcategory)
198    pub const DELIBERATION_COMPLETED: Self = Self(0x6050);
199    pub const ENGINE_STATE_PERSISTED: Self = Self(0x6051);
200
201    // Synchronization events (0x6xxx — agent subcategory)
202    /// SyncPulse emitted when multi-agent drift exceeds threshold.
203    pub const SYNC_PULSE_EMITTED: Self = Self(0x6060);
204    /// SyncPulse reconciliation completed.
205    pub const SYNC_PULSE_RECONCILED: Self = Self(0x6061);
206
207    // Lock events (0x7xxx)
208    pub const LOCK_ACQUIRED: Self = Self(0x7001);
209    pub const LOCK_EXTENDED: Self = Self(0x7002);
210    pub const LOCK_RELEASED: Self = Self(0x7003);
211    pub const LOCK_EXPIRED: Self = Self(0x7004);
212    pub const LOCK_CONTENTION: Self = Self(0x7005);
213
214    // Message events (0x8xxx)
215    pub const MESSAGE_SENT: Self = Self(0x8001);
216    pub const MESSAGE_DELIVERED: Self = Self(0x8002);
217    pub const MESSAGE_ACKNOWLEDGED: Self = Self(0x8003);
218    pub const MESSAGE_EXPIRED: Self = Self(0x8004);
219
220    // Delegation events (0x9xxx)
221    pub const DELEGATION_CREATED: Self = Self(0x9001);
222    pub const DELEGATION_ACCEPTED: Self = Self(0x9002);
223    pub const DELEGATION_REJECTED: Self = Self(0x9003);
224    pub const DELEGATION_STARTED: Self = Self(0x9004);
225    pub const DELEGATION_COMPLETED: Self = Self(0x9005);
226    pub const DELEGATION_FAILED: Self = Self(0x9006);
227
228    // Handoff events (0xAxxx)
229    pub const HANDOFF_CREATED: Self = Self(0xA001);
230    pub const HANDOFF_ACCEPTED: Self = Self(0xA002);
231    pub const HANDOFF_REJECTED: Self = Self(0xA003);
232    pub const HANDOFF_COMPLETED: Self = Self(0xA004);
233
234    // Edge events (0xBxxx)
235    pub const EDGE_CREATED: Self = Self(0xB001);
236    pub const EDGE_UPDATED: Self = Self(0xB002);
237    pub const EDGE_DELETED: Self = Self(0xB003);
238
239    // Evolution events (0xCxxx)
240    pub const EVOLUTION_SNAPSHOT_CREATED: Self = Self(0xC001);
241    pub const EVOLUTION_PHASE_CHANGED: Self = Self(0xC002);
242
243    // Background job events (0xC0xx range)
244    /// Delegation timed out by saga cleanup.
245    pub const SAGA_DELEGATION_TIMED_OUT: Self = Self(0xC010);
246    /// Handoff timed out by saga cleanup.
247    pub const SAGA_HANDOFF_TIMED_OUT: Self = Self(0xC011);
248    /// Artifact expired by TTL cleanup.
249    pub const TTL_ARTIFACT_EXPIRED: Self = Self(0xC020);
250    /// Note expired by TTL cleanup.
251    pub const TTL_NOTE_EXPIRED: Self = Self(0xC021);
252    /// Entity archived by memory decay.
253    pub const MEMORY_ENTITY_ARCHIVED: Self = Self(0xC030);
254    /// Turns reaped from closed scope.
255    pub const SCOPE_TURNS_REAPED: Self = Self(0xC040);
256    /// Hash chain violation detected.
257    pub const HASH_CHAIN_VIOLATION: Self = Self(0xC050);
258    /// Pending effects processed by effect executor.
259    pub const EFFECT_EXECUTED: Self = Self(0xC060);
260
261    // Effect events (0xDxxx)
262    pub const EFFECT_ERROR: Self = Self(0xD001);
263    pub const EFFECT_RETRY: Self = Self(0xD002);
264    pub const EFFECT_COMPENSATE: Self = Self(0xD003);
265    pub const EFFECT_ACK: Self = Self(0xD004);
266    pub const EFFECT_BACKPRESSURE: Self = Self(0xD005);
267    pub const EFFECT_CANCEL: Self = Self(0xD006);
268    pub const EFFECT_CONFLICT: Self = Self(0xD007);
269
270    // Cache invalidation events (0xExxx)
271    pub const CACHE_INVALIDATE_TRAJECTORY: Self = Self(0xE001);
272    pub const CACHE_INVALIDATE_SCOPE: Self = Self(0xE002);
273    pub const CACHE_INVALIDATE_ARTIFACT: Self = Self(0xE003);
274    pub const CACHE_INVALIDATE_NOTE: Self = Self(0xE004);
275
276    // PCP memory events (0xE0xx range)
277    pub const MEMORY_COMMIT_CREATED: Self = Self(0xE010);
278    /// Full context commit for PCP recall (carries content for retrieval).
279    pub const CONTEXT_COMMIT: Self = Self(0xE020);
280
281    // Working set events (0xF1xx range)
282    pub const WORKING_SET_UPDATED: Self = Self(0xF101);
283    pub const WORKING_SET_DELETED: Self = Self(0xF102);
284
285    // Bash execution events (0xF2xx range)
286    //
287    // Bash commands are IO operations — execution happens in the SDK's
288    // TypeScript sandbox (just-bash). Only file writes flow through
289    // MutationPipeline. These events record the execution for audit trail.
290    //
291    // Event flow:
292    // 1. BASH_EXECUTION_STARTED → command begins (correlation_id links to scope)
293    // 2. BASH_EXECUTION_COMPLETED → command finishes (exit code, duration, files modified)
294    // 3. BASH_FILE_WRITTEN → for each file write, a separate mutation event
295    pub const BASH_EXECUTION_STARTED: Self = Self(0xF201);
296    pub const BASH_EXECUTION_COMPLETED: Self = Self(0xF202);
297    pub const BASH_FILE_WRITTEN: Self = Self(0xF203);
298
299    // Browser execution events (0xF4xx range)
300    pub const BROWSER_ACTION_COMPLETED: Self = Self(0xF401);
301
302    // WebMCP events (0xF5xx range)
303    pub const WEB_MCP_TOOL_EXECUTED: Self = Self(0xF501);
304    pub const WEB_MCP_DISCOVERY_SNAPSHOT: Self = Self(0xF502);
305
306    // Proxy / reasoning trace events (0xF3xx range)
307    //
308    // Captured from upstream LLM providers (e.g., OpenRouter) when models
309    // emit extended thinking / reasoning traces alongside their responses.
310    // The proxy intercepts these traces and records them as first-class events
311    // for observability, audit trails, and real-time MemoryStream subscribers.
312    pub const REASONING_TRACE: Self = Self(0xF301);
313
314    // AG-UI protocol events (0xF0xx range - reserved for protocols)
315    //
316    // CopilotKit/AG-UI protocol compatibility layer for agent lifecycle and state management.
317    //
318    // Protocol mapping to CELLSTATE concepts:
319    // - AG_UI_RUN_STARTED    => Scope created event (trajectory begins)
320    // - AG_UI_STEP_STARTED   => Turn started event (agent begins processing)
321    // - AG_UI_STEP_FINISHED  => Turn completed event (agent finishes processing)
322    // - AG_UI_RUN_FINISHED   => Scope closed event (trajectory completes)
323    // - AG_UI_RUN_ERROR      => Trajectory failed event (unrecoverable error)
324    // - AG_UI_INTERRUPT      => Effect::Pending checkpoint (pause for user input)
325    // - AG_UI_STATE_SNAPSHOT => Working set snapshot event (full state capture)
326    // - AG_UI_STATE_DELTA    => Working set delta event (incremental state update)
327    pub const AG_UI_RUN_STARTED: Self = Self(0xF001);
328    pub const AG_UI_STEP_STARTED: Self = Self(0xF002);
329    pub const AG_UI_STEP_FINISHED: Self = Self(0xF003);
330    pub const AG_UI_RUN_FINISHED: Self = Self(0xF004);
331    pub const AG_UI_RUN_ERROR: Self = Self(0xF005);
332    pub const AG_UI_INTERRUPT: Self = Self(0xF010);
333    pub const AG_UI_STATE_SNAPSHOT: Self = Self(0xF020);
334    pub const AG_UI_STATE_DELTA: Self = Self(0xF021);
335
336    // Session events (0xF6xx range)
337    //
338    // Stateful LLM session lifecycle. Sessions allow sending only deltas
339    // (new messages) instead of full context on each tool loop round.
340    pub const SESSION_CREATED: Self = Self(0xF601);
341    pub const SESSION_ACTIVATED: Self = Self(0xF602);
342    pub const SESSION_DELTA: Self = Self(0xF603);
343    pub const SESSION_CLOSED: Self = Self(0xF604);
344    pub const SESSION_EXPIRED: Self = Self(0xF605);
345    pub const SESSION_FALLBACK: Self = Self(0xF606);
346
347    // Instance events (0xF7xx range)
348    //
349    // Multi-instance coordination. Tracks API server instances for
350    // affinity routing and failover.
351    pub const INSTANCE_REGISTERED: Self = Self(0xF701);
352    pub const INSTANCE_HEARTBEAT: Self = Self(0xF702);
353    pub const INSTANCE_DEREGISTERED: Self = Self(0xF703);
354    pub const INSTANCE_STALE: Self = Self(0xF704);
355
356    // A2UI component events (0x00xx system subcategory)
357    //
358    // Incremental UI updates via JSON Patch for A2UI components.
359    pub const COMPONENT_PATCH: Self = Self(0x0020);
360    pub const COMPONENT_SNAPSHOT: Self = Self(0x0021);
361
362    // Security events (0xF9xx range)
363    pub const INVARIANT_VIOLATION: Self = Self(0xF901);
364    pub const PII_EGRESS_BLOCKED: Self = Self(0xF902);
365    pub const PII_REDACTION_APPLIED: Self = Self(0xF903);
366    pub const PII_VAULT_WRITE_FAILED: Self = Self(0xF904);
367
368    /// Get the category (upper 4 bits).
369    pub const fn category(&self) -> u8 {
370        (self.0 >> 12) as u8
371    }
372
373    /// Get the type within the category (lower 12 bits).
374    pub const fn type_id(&self) -> u16 {
375        self.0 & 0x0FFF
376    }
377
378    /// Create a custom event kind.
379    pub const fn custom(category: u8, type_id: u16) -> Self {
380        Self(((category as u16) << 12) | (type_id & 0x0FFF))
381    }
382
383    /// Check if this is a system event.
384    pub const fn is_system(&self) -> bool {
385        self.category() == 0
386    }
387
388    /// Check if this is an effect event.
389    pub const fn is_effect(&self) -> bool {
390        self.category() == 0xD
391    }
392
393    /// Check if this event kind matches a known constant.
394    ///
395    /// Returns `false` for undefined kinds (e.g. 0x0FFF). This prevents
396    /// accidental use of unregistered event types in production.
397    pub fn is_defined(&self) -> bool {
398        matches!(
399            *self,
400            Self::DATA
401                | Self::SYSTEM_INIT
402                | Self::SYSTEM_SHUTDOWN
403                | Self::SYSTEM_HEARTBEAT
404                | Self::SYSTEM_CONFIG_CHANGE
405                | Self::TRAJECTORY_CREATED
406                | Self::TRAJECTORY_UPDATED
407                | Self::TRAJECTORY_COMPLETED
408                | Self::TRAJECTORY_FAILED
409                | Self::TRAJECTORY_SUSPENDED
410                | Self::TRAJECTORY_RESUMED
411                | Self::TRAJECTORY_DELETED
412                | Self::SCOPE_CREATED
413                | Self::SCOPE_UPDATED
414                | Self::SCOPE_CLOSED
415                | Self::SCOPE_CHECKPOINTED
416                | Self::SCOPE_CONTEXT_PAGED
417                | Self::ARTIFACT_CREATED
418                | Self::ARTIFACT_UPDATED
419                | Self::ARTIFACT_SUPERSEDED
420                | Self::ARTIFACT_DELETED
421                | Self::NOTE_CREATED
422                | Self::NOTE_UPDATED
423                | Self::NOTE_SUPERSEDED
424                | Self::NOTE_DELETED
425                | Self::NOTE_ACCESSED
426                | Self::TURN_CREATED
427                | Self::AGENT_REGISTERED
428                | Self::AGENT_UPDATED
429                | Self::AGENT_UNREGISTERED
430                | Self::AGENT_STATUS_CHANGED
431                | Self::GOAL_CREATED
432                | Self::GOAL_ACTIVATED
433                | Self::GOAL_ACHIEVED
434                | Self::GOAL_FAILED
435                | Self::PLAN_CREATED
436                | Self::PLAN_STARTED
437                | Self::PLAN_COMPLETED
438                | Self::PLAN_FAILED
439                | Self::STEP_COMPLETED
440                | Self::STEP_FAILED
441                | Self::BELIEF_CREATED
442                | Self::BELIEF_SUPERSEDED
443                | Self::DELIBERATION_COMPLETED
444                | Self::ENGINE_STATE_PERSISTED
445                | Self::SYNC_PULSE_EMITTED
446                | Self::SYNC_PULSE_RECONCILED
447                | Self::LOCK_ACQUIRED
448                | Self::LOCK_EXTENDED
449                | Self::LOCK_RELEASED
450                | Self::LOCK_EXPIRED
451                | Self::LOCK_CONTENTION
452                | Self::MESSAGE_SENT
453                | Self::MESSAGE_DELIVERED
454                | Self::MESSAGE_ACKNOWLEDGED
455                | Self::MESSAGE_EXPIRED
456                | Self::DELEGATION_CREATED
457                | Self::DELEGATION_ACCEPTED
458                | Self::DELEGATION_REJECTED
459                | Self::DELEGATION_STARTED
460                | Self::DELEGATION_COMPLETED
461                | Self::DELEGATION_FAILED
462                | Self::HANDOFF_CREATED
463                | Self::HANDOFF_ACCEPTED
464                | Self::HANDOFF_REJECTED
465                | Self::HANDOFF_COMPLETED
466                | Self::EDGE_CREATED
467                | Self::EDGE_UPDATED
468                | Self::EDGE_DELETED
469                | Self::EVOLUTION_SNAPSHOT_CREATED
470                | Self::EVOLUTION_PHASE_CHANGED
471                | Self::SAGA_DELEGATION_TIMED_OUT
472                | Self::SAGA_HANDOFF_TIMED_OUT
473                | Self::TTL_ARTIFACT_EXPIRED
474                | Self::TTL_NOTE_EXPIRED
475                | Self::MEMORY_ENTITY_ARCHIVED
476                | Self::SCOPE_TURNS_REAPED
477                | Self::HASH_CHAIN_VIOLATION
478                | Self::EFFECT_EXECUTED
479                | Self::EFFECT_ERROR
480                | Self::EFFECT_RETRY
481                | Self::EFFECT_COMPENSATE
482                | Self::EFFECT_ACK
483                | Self::EFFECT_BACKPRESSURE
484                | Self::EFFECT_CANCEL
485                | Self::EFFECT_CONFLICT
486                | Self::CACHE_INVALIDATE_TRAJECTORY
487                | Self::CACHE_INVALIDATE_SCOPE
488                | Self::CACHE_INVALIDATE_ARTIFACT
489                | Self::CACHE_INVALIDATE_NOTE
490                | Self::MEMORY_COMMIT_CREATED
491                | Self::CONTEXT_COMMIT
492                | Self::WORKING_SET_UPDATED
493                | Self::WORKING_SET_DELETED
494                | Self::BASH_EXECUTION_STARTED
495                | Self::BASH_EXECUTION_COMPLETED
496                | Self::BASH_FILE_WRITTEN
497                | Self::BROWSER_ACTION_COMPLETED
498                | Self::WEB_MCP_TOOL_EXECUTED
499                | Self::WEB_MCP_DISCOVERY_SNAPSHOT
500                | Self::REASONING_TRACE
501                | Self::AG_UI_RUN_STARTED
502                | Self::AG_UI_STEP_STARTED
503                | Self::AG_UI_STEP_FINISHED
504                | Self::AG_UI_RUN_FINISHED
505                | Self::AG_UI_RUN_ERROR
506                | Self::AG_UI_INTERRUPT
507                | Self::AG_UI_STATE_SNAPSHOT
508                | Self::AG_UI_STATE_DELTA
509                | Self::INVARIANT_VIOLATION
510                | Self::PII_EGRESS_BLOCKED
511                | Self::PII_REDACTION_APPLIED
512                | Self::PII_VAULT_WRITE_FAILED
513                | Self::SESSION_CREATED
514                | Self::SESSION_ACTIVATED
515                | Self::SESSION_DELTA
516                | Self::SESSION_CLOSED
517                | Self::SESSION_EXPIRED
518                | Self::SESSION_FALLBACK
519                | Self::INSTANCE_REGISTERED
520                | Self::INSTANCE_HEARTBEAT
521                | Self::INSTANCE_DEREGISTERED
522                | Self::INSTANCE_STALE
523                | Self::COMPONENT_PATCH
524                | Self::COMPONENT_SNAPSHOT
525        )
526    }
527}
528
529/// Validate that an event kind is a recognized, defined variant.
530///
531/// This standalone function is intended to be called before storage operations
532/// to reject events with undefined (unregistered) kinds at the boundary,
533/// preventing garbage data from entering the DAG.
534pub fn validate_event_kind(kind: EventKind) -> Result<(), &'static str> {
535    if !kind.is_defined() {
536        return Err("undefined event kind");
537    }
538    Ok(())
539}
540
541impl fmt::Display for EventKind {
542    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
543        write!(f, "{:#06X}", self.0)
544    }
545}
546
547// ============================================================================
548// EVENT FLAGS
549// ============================================================================
550
551bitflags! {
552    /// Flags for event processing hints.
553    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
554    pub struct EventFlags: u8 {
555        /// Event requires acknowledgment
556        const REQUIRES_ACK = 0b0000_0001;
557        /// Event is part of a transaction
558        const TRANSACTIONAL = 0b0000_0010;
559        /// Event payload is compressed
560        const COMPRESSED = 0b0000_0100;
561        /// Event is a replay (not original)
562        const REPLAY = 0b0000_1000;
563        /// Event has been acknowledged
564        const ACKNOWLEDGED = 0b0001_0000;
565        /// Event triggered compensation
566        const COMPENSATED = 0b0010_0000;
567        /// Event is critical (must be processed)
568        const CRITICAL = 0b0100_0000;
569        /// Event payload had PII scrubbed (DAG queries can filter)
570        const PII_SCRUBBED = 0b1000_0000;
571    }
572}
573
574impl Default for EventFlags {
575    fn default() -> Self {
576        Self::empty()
577    }
578}
579
580// Manual serde implementation for EventFlags (bitflags 2.x + serde)
581impl Serialize for EventFlags {
582    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
583        self.bits().serialize(serializer)
584    }
585}
586
587impl<'de> Deserialize<'de> for EventFlags {
588    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
589        let bits = u8::deserialize(deserializer)?;
590        Self::from_bits(bits).ok_or_else(|| {
591            serde::de::Error::custom(format!("invalid EventFlags bits: {:#04x}", bits))
592        })
593    }
594}
595
596#[cfg(feature = "openapi")]
597impl utoipa::ToSchema for EventFlags {
598    fn name() -> std::borrow::Cow<'static, str> {
599        std::borrow::Cow::Borrowed("EventFlags")
600    }
601}
602
603#[cfg(feature = "openapi")]
604impl utoipa::PartialSchema for EventFlags {
605    fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
606        utoipa::openapi::ObjectBuilder::new()
607            .schema_type(utoipa::openapi::schema::SchemaType::Type(
608                utoipa::openapi::schema::Type::Integer,
609            ))
610            .description(Some("Event processing flags as a u8 bitfield (0-255)"))
611            .minimum(Some(0.0))
612            .maximum(Some(255.0))
613            .into()
614    }
615}
616
617// ============================================================================
618// EVENT HEADER (64-byte cache-aligned)
619// ============================================================================
620
621/// Event header with all metadata needed for processing.
622///
623/// This structure is 64 bytes and cache-line aligned for optimal performance.
624/// The payload is stored separately and referenced by the header.
625///
626/// Layout (64 bytes total, ordered by alignment to minimize padding):
627/// - event_id: 16 bytes (UUIDv7)
628/// - correlation_id: 16 bytes (UUIDv7)
629/// - timestamp: 8 bytes (microseconds since epoch)
630/// - position: 12 bytes (DagPosition)
631/// - payload_size: 4 bytes (u32)
632/// - event_kind: 2 bytes (EventKind)
633/// - flags: 1 byte (EventFlags)
634/// - _reserved: 5 bytes (padding to 64)
635#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
636#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
637#[repr(C, align(64))]
638pub struct EventHeader {
639    /// Unique event identifier (UUIDv7 for timestamp-sortable IDs)
640    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
641    pub event_id: EventId,
642    /// Correlation ID for tracing related events
643    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
644    pub correlation_id: EventId,
645    /// Timestamp in microseconds since Unix epoch
646    pub timestamp: i64,
647    /// Position in the event DAG
648    pub position: DagPosition,
649    /// Size of the payload in bytes
650    pub payload_size: u32,
651    /// Event kind (category + type)
652    pub event_kind: EventKind,
653    /// Processing flags
654    pub flags: EventFlags,
655    /// Random seed captured at emit time for deterministic replay
656    pub random_seed: Option<u64>,
657    /// Optional causality context for distributed tracing.
658    ///
659    /// When present, links this event to an OpenTelemetry trace via W3C
660    /// Trace Context. Populated by the `CausalitySpanLinker` in the
661    /// telemetry layer; omitted from serialization when absent.
662    #[serde(default, skip_serializing_if = "Option::is_none")]
663    pub causality: Option<Causality>,
664    /// Reserved for future use (padding to alignment)
665    #[serde(skip)]
666    _reserved: [u8; 5],
667}
668
669// Compile-time check that EventHeader alignment
670// Note: Size increased from 64 to accommodate random_seed field
671const _: () = assert!(std::mem::align_of::<EventHeader>() == 64);
672
673impl EventHeader {
674    /// Create a new event header with default (empty) flags and no random seed.
675    pub fn new(
676        event_id: EventId,
677        correlation_id: EventId,
678        timestamp: i64,
679        position: DagPosition,
680        payload_size: u32,
681        event_kind: EventKind,
682    ) -> Self {
683        Self {
684            event_id,
685            correlation_id,
686            timestamp,
687            position,
688            payload_size,
689            event_kind,
690            flags: EventFlags::empty(),
691            random_seed: None,
692            causality: None,
693            _reserved: [0; 5],
694        }
695    }
696
697    /// Set event flags on this header.
698    pub fn with_flags(mut self, flags: EventFlags) -> Self {
699        self.flags = flags;
700        self
701    }
702
703    /// Attach a deterministic random seed for replay.
704    pub fn with_random_seed(mut self, seed: u64) -> Self {
705        self.random_seed = Some(seed);
706        self
707    }
708
709    /// Attach a causality context to this header for distributed tracing.
710    pub fn with_causality(mut self, causality: Causality) -> Self {
711        self.causality = Some(causality);
712        self
713    }
714
715    /// Check if this event requires acknowledgment.
716    pub fn requires_ack(&self) -> bool {
717        self.flags.contains(EventFlags::REQUIRES_ACK)
718    }
719
720    /// Check if this event is part of a transaction.
721    pub fn is_transactional(&self) -> bool {
722        self.flags.contains(EventFlags::TRANSACTIONAL)
723    }
724
725    /// Check if this event is a replay.
726    pub fn is_replay(&self) -> bool {
727        self.flags.contains(EventFlags::REPLAY)
728    }
729
730    /// Check if this event has been acknowledged.
731    pub fn is_acknowledged(&self) -> bool {
732        self.flags.contains(EventFlags::ACKNOWLEDGED)
733    }
734
735    /// Check if this event is critical.
736    pub fn is_critical(&self) -> bool {
737        self.flags.contains(EventFlags::CRITICAL)
738    }
739
740    /// Mark this event as acknowledged.
741    pub fn acknowledge(&mut self) {
742        self.flags |= EventFlags::ACKNOWLEDGED;
743    }
744
745    /// Get the event age in microseconds from a reference time.
746    pub fn age_micros(&self, now_micros: i64) -> i64 {
747        now_micros - self.timestamp
748    }
749}
750
751// ============================================================================
752// FULL EVENT (Header + Payload)
753// ============================================================================
754
755/// A complete event with header and payload.
756///
757/// The payload is generic to allow different event types to have different
758/// payload structures while sharing the same header format.
759#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
760#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
761pub struct Event<P> {
762    pub header: EventHeader,
763    pub payload: P,
764    /// Hash chain for tamper-evident audit trail (Blake3 hash of parent + self).
765    /// None for genesis events.
766    #[serde(skip_serializing_if = "Option::is_none")]
767    pub hash_chain: Option<HashChain>,
768}
769
770impl<P> Event<P> {
771    /// Create a new event with the given header and payload (genesis event, no hash chain).
772    pub fn new(header: EventHeader, payload: P) -> Self {
773        Self {
774            header,
775            payload,
776            hash_chain: None,
777        }
778    }
779
780    /// Create a new event with the given header, payload, and hash chain.
781    pub fn with_hash_chain(header: EventHeader, payload: P, hash_chain: HashChain) -> Self {
782        Self {
783            header,
784            payload,
785            hash_chain: Some(hash_chain),
786        }
787    }
788
789    /// Get the event ID.
790    pub fn event_id(&self) -> EventId {
791        self.header.event_id
792    }
793
794    /// Get the event kind.
795    pub fn event_kind(&self) -> EventKind {
796        self.header.event_kind
797    }
798
799    /// Get the correlation ID.
800    pub fn correlation_id(&self) -> EventId {
801        self.header.correlation_id
802    }
803
804    /// Get the DAG position.
805    pub fn position(&self) -> DagPosition {
806        self.header.position
807    }
808
809    /// Map the payload to a different type.
810    pub fn map_payload<Q, F: FnOnce(P) -> Q>(self, f: F) -> Event<Q> {
811        Event {
812            header: self.header,
813            payload: f(self.payload),
814            hash_chain: self.hash_chain,
815        }
816    }
817}
818
819/// Borrows header + payload for content-only hashing.
820///
821/// The hash_chain field is intentionally excluded: `event_hash` is the hash
822/// of the event *content*, so including it would create a circular dependency
823/// (the hash would need to contain itself).
824#[derive(Serialize)]
825struct EventContentRef<'a, P: Serialize> {
826    header: &'a EventHeader,
827    payload: &'a P,
828}
829
830fn serialize_event_content<P: Serialize>(
831    header: &EventHeader,
832    payload: &P,
833) -> CellstateResult<Vec<u8>> {
834    serde_json::to_vec(&EventContentRef { header, payload }).map_err(|err| {
835        OperationalError::SerializationError {
836            message: format!("failed to serialize event content for hashing: {err}"),
837        }
838        .into()
839    })
840}
841
842impl<P: Serialize> Event<P> {
843    /// Compute Blake3 hash of this event's content (header + payload).
844    ///
845    /// Returns an error instead of silently hashing empty bytes when
846    /// serialization fails.
847    pub fn try_compute_hash(&self) -> CellstateResult<[u8; 32]> {
848        let canonical = serialize_event_content(&self.header, &self.payload)?;
849        Ok(blake3::hash(&canonical).into())
850    }
851
852    /// Compute Blake3 hash of this event's content (header + payload).
853    ///
854    /// Hashes only header and payload — the hash_chain field is excluded to
855    /// avoid a circular dependency (event_hash cannot include itself).
856    pub fn compute_hash(&self) -> [u8; 32] {
857        self.try_compute_hash()
858            .expect("event hashing requires serializable event content")
859    }
860
861    /// Verify this event against a parent hash using Blake3.
862    /// Returns an error when the event cannot be serialized for verification.
863    pub fn verify_checked(&self, parent_hash: &[u8; 32]) -> CellstateResult<bool> {
864        Blake3Verifier.try_verify_chain(self, parent_hash)
865    }
866
867    /// Verify this event against a parent hash using Blake3.
868    /// Returns true for genesis events (no hash chain).
869    pub fn verify(&self, parent_hash: &[u8; 32]) -> bool {
870        self.verify_checked(parent_hash).unwrap_or(false)
871    }
872
873    /// Check if this is a genesis event (no parent).
874    pub fn is_genesis(&self) -> bool {
875        self.hash_chain.is_none()
876    }
877}
878
879// ============================================================================
880// UPSTREAM SIGNALS
881// ============================================================================
882
883/// Signals that can be sent upstream in the DAG ("tram car tracks").
884///
885/// These signals flow in the opposite direction to events, allowing
886/// downstream processors to communicate back to upstream producers.
887#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
888#[serde(rename_all = "snake_case")]
889#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
890pub enum UpstreamSignal {
891    /// Acknowledge receipt/processing of an event
892    Ack {
893        #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
894        event_id: EventId,
895    },
896    /// Request backpressure (slow down event production)
897    Backpressure {
898        /// Resume timestamp in microseconds
899        until: i64,
900    },
901    /// Cancel a correlation chain
902    Cancel {
903        #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
904        correlation_id: EventId,
905        reason: String,
906    },
907    /// Signal that compensation is complete
908    CompensationComplete {
909        #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
910        event_id: EventId,
911    },
912    /// Propagate an error upstream
913    ErrorPropagation {
914        #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
915        source_event_id: EventId,
916        error_code: String,
917        message: String,
918    },
919    /// Conflict detected between two concurrent branches in the DAG.
920    /// This is a coordination signal, not a failure — the handler decides resolution.
921    ConflictDetected {
922        #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
923        branch_a: EventId,
924        #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
925        branch_b: EventId,
926    },
927}
928
929// ============================================================================
930// HASH CHAINING FOR AUDIT INTEGRITY (Phase 1.1)
931// ============================================================================
932
933/// Hash algorithm for event integrity.
934#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
935#[serde(rename_all = "snake_case")]
936#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
937pub enum HashAlgorithm {
938    /// SHA-256 hash algorithm
939    Sha256,
940    /// Blake3 hash algorithm (faster, recommended)
941    #[default]
942    Blake3,
943}
944
945/// Hash chain for tamper-evident event log.
946///
947/// Each event in the chain contains a hash of the previous event,
948/// creating an immutable, verifiable audit trail.
949#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
950#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
951pub struct HashChain {
952    /// Hash of previous event (creates chain). Genesis event has zero prev_hash.
953    #[cfg_attr(feature = "openapi", schema(value_type = String))]
954    pub prev_hash: [u8; 32],
955    /// Hash of this event (canonical serialization)
956    #[cfg_attr(feature = "openapi", schema(value_type = String))]
957    pub event_hash: [u8; 32],
958    /// Algorithm used for hashing
959    pub algorithm: HashAlgorithm,
960}
961
962impl Default for HashChain {
963    fn default() -> Self {
964        Self {
965            prev_hash: [0u8; 32], // Genesis event has zero prev_hash
966            event_hash: [0u8; 32],
967            algorithm: HashAlgorithm::Blake3,
968        }
969    }
970}
971
972// ============================================================================
973// CAUSALITY TRACKING (Phase 1.2)
974// ============================================================================
975
976/// W3C Trace Context compatible distributed tracing.
977///
978/// Provides full causality tracking for events across distributed systems.
979/// Compatible with OpenTelemetry and other distributed tracing systems.
980#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
981#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
982pub struct Causality {
983    /// Stable trace ID across request lifecycle (W3C compatible)
984    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
985    pub trace_id: Uuid,
986    /// Current operation span ID
987    pub span_id: u64,
988    /// Parent span (if not root)
989    pub parent_span_id: Option<u64>,
990    /// Parent event IDs for causality fan-in (multiple parents)
991    #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
992    pub parent_event_ids: Vec<EventId>,
993    /// Root event of this trace tree
994    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
995    pub root_event_id: EventId,
996}
997
998impl Default for Causality {
999    fn default() -> Self {
1000        let root_id = EventId::now_v7();
1001        Self {
1002            trace_id: Uuid::now_v7(),
1003            span_id: 0,
1004            parent_span_id: None,
1005            parent_event_ids: Vec::new(),
1006            root_event_id: root_id,
1007        }
1008    }
1009}
1010
1011impl Causality {
1012    /// Create a new causality context with a fresh trace.
1013    pub fn new() -> Self {
1014        Self::default()
1015    }
1016
1017    /// Create a child span from this causality context.
1018    pub fn child(&self, new_span_id: u64) -> Self {
1019        Self {
1020            trace_id: self.trace_id,
1021            span_id: new_span_id,
1022            parent_span_id: Some(self.span_id),
1023            parent_event_ids: vec![self.root_event_id],
1024            root_event_id: self.root_event_id,
1025        }
1026    }
1027
1028    /// Create a causality context with multiple parents (fan-in).
1029    pub fn merge(parents: &[&Causality], new_span_id: u64) -> Option<Self> {
1030        let first = parents.first()?;
1031        Some(Self {
1032            trace_id: first.trace_id,
1033            span_id: new_span_id,
1034            parent_span_id: Some(first.span_id),
1035            parent_event_ids: parents.iter().map(|p| p.root_event_id).collect(),
1036            root_event_id: first.root_event_id,
1037        })
1038    }
1039}
1040
1041// ============================================================================
1042// RICH EVIDENCE REFERENCES (Phase 1.3)
1043// ============================================================================
1044
1045use crate::{AgentId, ArtifactId, ExtractionMethod, NoteId, Timestamp};
1046
1047/// Evidence reference types for provenance tracking.
1048///
1049/// Rich references to external sources that support claims or data in an event.
1050#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1051#[serde(rename_all = "snake_case")]
1052#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1053pub enum EvidenceRef {
1054    /// Reference to a document chunk
1055    DocChunk {
1056        doc_id: String,
1057        chunk_id: String,
1058        offset: u32,
1059        length: u32,
1060    },
1061    /// Reference to another event
1062    Event {
1063        #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
1064        event_id: EventId,
1065        timestamp: i64,
1066    },
1067    /// Reference to a tool call result
1068    ToolResult {
1069        call_id: String,
1070        tool_name: String,
1071        result_index: u32,
1072    },
1073    /// Reference to external URL
1074    Url {
1075        url: String,
1076        accessed_at: i64,
1077        #[cfg_attr(feature = "openapi", schema(value_type = Option<String>))]
1078        hash: Option<[u8; 32]>,
1079    },
1080    /// Reference to knowledge pack section
1081    KnowledgePack { pack_id: String, section: String },
1082    /// Reference to artifact
1083    Artifact { artifact_id: ArtifactId },
1084    /// Reference to note
1085    Note { note_id: NoteId },
1086    /// Manual user-provided evidence
1087    Manual {
1088        user_id: String,
1089        timestamp: i64,
1090        description: String,
1091    },
1092}
1093
1094/// Verification status for evidence.
1095#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1096#[serde(rename_all = "snake_case")]
1097#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1098pub enum VerificationStatus {
1099    /// Evidence has not been verified
1100    #[default]
1101    Unverified,
1102    /// Evidence has been verified
1103    Verified,
1104    /// Evidence has been partially verified
1105    PartiallyVerified,
1106    /// Evidence verification failed
1107    Invalid,
1108    /// Evidence has expired
1109    Expired,
1110}
1111
1112/// Enhanced provenance with evidence chains.
1113///
1114/// Extends basic provenance with rich evidence references, chain of custody,
1115/// and verification status for robust audit trails.
1116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1117#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1118pub struct EnhancedProvenance {
1119    /// Source turn number
1120    pub source_turn: i32,
1121    /// How this data was extracted
1122    pub extraction_method: ExtractionMethod,
1123    /// Confidence score (0.0 to 1.0)
1124    pub confidence: Option<f32>,
1125    /// Rich evidence references
1126    pub evidence_refs: Vec<EvidenceRef>,
1127    /// Chain of custody (agent trail) - tuples of (AgentId, Timestamp)
1128    #[cfg_attr(feature = "openapi", schema(value_type = Vec<serde_json::Value>))]
1129    pub chain_of_custody: Vec<(AgentId, Timestamp)>,
1130    /// Verification status
1131    pub verification_status: VerificationStatus,
1132}
1133
1134impl Default for EnhancedProvenance {
1135    fn default() -> Self {
1136        Self {
1137            source_turn: 0,
1138            extraction_method: ExtractionMethod::Unknown,
1139            confidence: None,
1140            evidence_refs: Vec::new(),
1141            chain_of_custody: Vec::new(),
1142            verification_status: VerificationStatus::Unverified,
1143        }
1144    }
1145}
1146
1147impl EnhancedProvenance {
1148    /// Create a new enhanced provenance.
1149    pub fn new(source_turn: i32, extraction_method: ExtractionMethod) -> Self {
1150        Self {
1151            source_turn,
1152            extraction_method,
1153            ..Default::default()
1154        }
1155    }
1156
1157    /// Add an evidence reference.
1158    pub fn with_evidence(mut self, evidence: EvidenceRef) -> Self {
1159        self.evidence_refs.push(evidence);
1160        self
1161    }
1162
1163    /// Add a custody entry.
1164    pub fn with_custody(mut self, agent_id: AgentId, timestamp: Timestamp) -> Self {
1165        self.chain_of_custody.push((agent_id, timestamp));
1166        self
1167    }
1168
1169    /// Set confidence score.
1170    pub fn with_confidence(mut self, confidence: f32) -> Self {
1171        self.confidence = Some(confidence.clamp(0.0, 1.0));
1172        self
1173    }
1174
1175    /// Set verification status.
1176    pub fn with_verification(mut self, status: VerificationStatus) -> Self {
1177        self.verification_status = status;
1178        self
1179    }
1180}
1181
1182// ============================================================================
1183// EVENT VERIFIER TRAIT (Phase 1.4)
1184// ============================================================================
1185
1186/// Trait for cryptographic event verification.
1187///
1188/// Implementations of this trait provide hash computation and verification
1189/// for events, enabling tamper-evident audit logs.
1190pub trait EventVerifier: Send + Sync {
1191    /// Compute hash for an event.
1192    fn compute_hash<P: Serialize>(&self, event: &Event<P>) -> [u8; 32];
1193
1194    /// Compute hash for an event, returning a structured error if serialization fails.
1195    fn try_compute_hash<P: Serialize>(&self, event: &Event<P>) -> CellstateResult<[u8; 32]>;
1196
1197    /// Verify event integrity using its hash.
1198    fn verify_hash<P: Serialize>(&self, event: &Event<P>, expected: &[u8; 32]) -> bool {
1199        &self.compute_hash(event) == expected
1200    }
1201
1202    /// Verify hash chain (current depends on previous).
1203    fn verify_chain<P: Serialize>(&self, current: &Event<P>, previous_hash: &[u8; 32]) -> bool;
1204
1205    /// Verify hash chain (current depends on previous) with structured errors.
1206    fn try_verify_chain<P: Serialize>(
1207        &self,
1208        current: &Event<P>,
1209        previous_hash: &[u8; 32],
1210    ) -> CellstateResult<bool>;
1211
1212    /// Get the algorithm used by this verifier.
1213    fn algorithm(&self) -> HashAlgorithm;
1214}
1215
1216/// Blake3-based event verifier (default, recommended for performance).
1217#[derive(Debug, Clone, Copy, Default)]
1218pub struct Blake3Verifier;
1219
1220impl EventVerifier for Blake3Verifier {
1221    fn compute_hash<P: Serialize>(&self, event: &Event<P>) -> [u8; 32] {
1222        self.try_compute_hash(event)
1223            .expect("event hashing requires serializable event content")
1224    }
1225
1226    fn try_compute_hash<P: Serialize>(&self, event: &Event<P>) -> CellstateResult<[u8; 32]> {
1227        let canonical = serialize_event_content(&event.header, &event.payload)?;
1228        Ok(blake3::hash(&canonical).into())
1229    }
1230
1231    fn verify_chain<P: Serialize>(&self, current: &Event<P>, previous_hash: &[u8; 32]) -> bool {
1232        self.try_verify_chain(current, previous_hash)
1233            .unwrap_or(false)
1234    }
1235
1236    fn try_verify_chain<P: Serialize>(
1237        &self,
1238        current: &Event<P>,
1239        previous_hash: &[u8; 32],
1240    ) -> CellstateResult<bool> {
1241        let Some(hash_chain) = &current.hash_chain else {
1242            return Ok(true); // Genesis events are always valid
1243        };
1244
1245        // Reject events whose hash chain claims a different algorithm.
1246        if hash_chain.algorithm != HashAlgorithm::Blake3 {
1247            return Ok(false);
1248        }
1249
1250        if hash_chain.prev_hash != *previous_hash {
1251            return Ok(false);
1252        }
1253
1254        let canonical = serialize_event_content(&current.header, &current.payload)?;
1255        let computed_hash = blake3::hash(&canonical);
1256        Ok(computed_hash.as_bytes() == &hash_chain.event_hash)
1257    }
1258
1259    fn algorithm(&self) -> HashAlgorithm {
1260        HashAlgorithm::Blake3
1261    }
1262}
1263
1264/// SHA-256-based event verifier (for compatibility).
1265#[derive(Debug, Clone, Copy, Default)]
1266pub struct Sha256Verifier;
1267
1268impl EventVerifier for Sha256Verifier {
1269    fn compute_hash<P: Serialize>(&self, event: &Event<P>) -> [u8; 32] {
1270        self.try_compute_hash(event)
1271            .expect("event hashing requires serializable event content")
1272    }
1273
1274    fn try_compute_hash<P: Serialize>(&self, event: &Event<P>) -> CellstateResult<[u8; 32]> {
1275        use sha2::{Digest, Sha256};
1276        let canonical = serialize_event_content(&event.header, &event.payload)?;
1277        let result = Sha256::digest(&canonical);
1278        let mut hash = [0u8; 32];
1279        hash.copy_from_slice(&result);
1280        Ok(hash)
1281    }
1282
1283    fn verify_chain<P: Serialize>(&self, current: &Event<P>, previous_hash: &[u8; 32]) -> bool {
1284        self.try_verify_chain(current, previous_hash)
1285            .unwrap_or(false)
1286    }
1287
1288    fn try_verify_chain<P: Serialize>(
1289        &self,
1290        current: &Event<P>,
1291        previous_hash: &[u8; 32],
1292    ) -> CellstateResult<bool> {
1293        let Some(hash_chain) = &current.hash_chain else {
1294            return Ok(true); // Genesis events are always valid
1295        };
1296
1297        if hash_chain.prev_hash != *previous_hash {
1298            return Ok(false);
1299        }
1300
1301        use sha2::{Digest, Sha256};
1302        let canonical = serialize_event_content(&current.header, &current.payload)?;
1303        let computed = Sha256::digest(&canonical);
1304        Ok(computed[..] == hash_chain.event_hash)
1305    }
1306
1307    fn algorithm(&self) -> HashAlgorithm {
1308        HashAlgorithm::Sha256
1309    }
1310}
1311
1312// ============================================================================
1313// EVENT DAG TRAIT (Async for PG async I/O + LMDB hot path)
1314// ============================================================================
1315
1316use crate::Effect;
1317
1318/// Trait for Event DAG operations.
1319///
1320/// Implementations of this trait provide persistent storage and traversal
1321/// of the event graph. The DAG supports:
1322///
1323/// - Appending new events with automatic position calculation
1324/// - Reading events by ID
1325/// - Walking ancestor chains for context reconstruction
1326/// - Sending upstream signals for coordination
1327/// - Correlation chain traversal for related event discovery
1328///
1329/// # Async Design
1330///
1331/// All methods are async to support:
1332/// - LMDB hot cache (sync but fast - microseconds, safe in async context)
1333/// - PostgreSQL fallback (truly async - milliseconds, non-blocking)
1334///
1335/// # Payload Type
1336///
1337/// The `Payload` associated type determines what data is stored with each event.
1338/// This is typically a serializable enum of all possible event payloads.
1339#[async_trait::async_trait]
1340pub trait EventDag: Send + Sync {
1341    /// The payload type stored with events.
1342    type Payload: Clone + Send + Sync + 'static;
1343
1344    /// Append a new event to the DAG.
1345    ///
1346    /// The event's position should be set by the caller based on the parent event.
1347    /// Returns the assigned event ID on success.
1348    async fn append(&self, event: Event<Self::Payload>) -> Effect<EventId>;
1349
1350    /// Read an event by its ID.
1351    async fn read(&self, event_id: EventId) -> Effect<Event<Self::Payload>>;
1352
1353    /// Walk the ancestor chain from a given event.
1354    ///
1355    /// Returns events from `from` toward the root, limited by `limit`.
1356    /// The events are returned in order from most recent to oldest.
1357    async fn walk_ancestors(
1358        &self,
1359        from: EventId,
1360        limit: usize,
1361    ) -> Effect<Vec<Event<Self::Payload>>>;
1362
1363    /// Walk descendants from a given event.
1364    async fn walk_descendants(
1365        &self,
1366        from: EventId,
1367        limit: usize,
1368    ) -> Effect<Vec<Event<Self::Payload>>>;
1369
1370    /// Send an upstream signal from a downstream event.
1371    ///
1372    /// Signals propagate backward through the DAG to notify upstream
1373    /// producers of acknowledgments, backpressure, or errors.
1374    async fn signal_upstream(&self, from: EventId, signal: UpstreamSignal) -> Effect<()>;
1375
1376    /// Find all events in a correlation chain.
1377    async fn find_correlation_chain(
1378        &self,
1379        tenant_id: uuid::Uuid,
1380        correlation_id: EventId,
1381    ) -> Effect<Vec<Event<Self::Payload>>>;
1382
1383    /// Get the current position for appending a new event.
1384    async fn next_position(&self, parent: Option<EventId>, lane: u32) -> Effect<DagPosition>;
1385
1386    /// Get events by kind within a position range.
1387    async fn find_by_kind(
1388        &self,
1389        kind: EventKind,
1390        min_depth: u32,
1391        max_depth: u32,
1392        limit: usize,
1393    ) -> Effect<Vec<Event<Self::Payload>>>;
1394
1395    /// Get events by kind within a position range for a tenant.
1396    ///
1397    /// Default behavior falls back to `find_by_kind` for implementations that
1398    /// do not support tenant-scoped querying at the storage boundary.
1399    async fn find_by_kind_for_tenant(
1400        &self,
1401        tenant_id: uuid::Uuid,
1402        kind: EventKind,
1403        min_depth: u32,
1404        max_depth: u32,
1405        limit: usize,
1406    ) -> Effect<Vec<Event<Self::Payload>>> {
1407        let _ = tenant_id;
1408        self.find_by_kind(kind, min_depth, max_depth, limit).await
1409    }
1410
1411    /// Acknowledge an event.
1412    async fn acknowledge(&self, event_id: EventId, send_upstream: bool) -> Effect<()>;
1413
1414    /// Get unacknowledged events that require acknowledgment.
1415    async fn unacknowledged(&self, limit: usize) -> Effect<Vec<Event<Self::Payload>>>;
1416}
1417
1418/// Extension trait for EventDag with convenience methods.
1419#[async_trait::async_trait]
1420pub trait EventDagExt: EventDag {
1421    /// Append a new root event.
1422    async fn append_root(&self, payload: Self::Payload) -> Effect<EventId> {
1423        let position = match self.next_position(None, 0).await {
1424            Effect::Ok(pos) => pos,
1425            Effect::Err(e) => return Effect::Err(e),
1426            other => return other.map(|_| unreachable!()),
1427        };
1428
1429        let event_id = EventId::now_v7();
1430        let event = Event {
1431            header: EventHeader::new(
1432                event_id,
1433                event_id,
1434                chrono::Utc::now().timestamp_micros(),
1435                position,
1436                0,
1437                EventKind::DATA,
1438            ),
1439            payload,
1440            hash_chain: None,
1441        };
1442        self.append(event).await
1443    }
1444
1445    /// Append a child event to an existing event.
1446    async fn append_child(&self, parent: EventId, payload: Self::Payload) -> Effect<EventId> {
1447        let parent_event = match self.read(parent).await {
1448            Effect::Ok(e) => e,
1449            Effect::Err(e) => return Effect::Err(e),
1450            other => return other.map(|_| unreachable!()),
1451        };
1452
1453        let position = match self
1454            .next_position(Some(parent), parent_event.header.position.lane)
1455            .await
1456        {
1457            Effect::Ok(pos) => pos,
1458            Effect::Err(e) => return Effect::Err(e),
1459            other => return other.map(|_| unreachable!()),
1460        };
1461
1462        let event_id = EventId::now_v7();
1463        let event = Event {
1464            header: EventHeader::new(
1465                event_id,
1466                parent_event.header.correlation_id,
1467                chrono::Utc::now().timestamp_micros(),
1468                position,
1469                0,
1470                EventKind::DATA,
1471            ),
1472            payload,
1473            hash_chain: None,
1474        };
1475        self.append(event).await
1476    }
1477
1478    /// Fork a new lane from an existing event.
1479    async fn fork(
1480        &self,
1481        parent: EventId,
1482        new_lane: u32,
1483        payload: Self::Payload,
1484    ) -> Effect<EventId> {
1485        let parent_event = match self.read(parent).await {
1486            Effect::Ok(e) => e,
1487            Effect::Err(e) => return Effect::Err(e),
1488            other => return other.map(|_| unreachable!()),
1489        };
1490
1491        let position = match self.next_position(Some(parent), new_lane).await {
1492            Effect::Ok(pos) => pos,
1493            Effect::Err(e) => return Effect::Err(e),
1494            other => return other.map(|_| unreachable!()),
1495        };
1496
1497        let event_id = EventId::now_v7();
1498        let event = Event {
1499            header: EventHeader::new(
1500                event_id,
1501                parent_event.header.correlation_id,
1502                chrono::Utc::now().timestamp_micros(),
1503                position,
1504                0,
1505                EventKind::DATA,
1506            ),
1507            payload,
1508            hash_chain: None,
1509        };
1510        self.append(event).await
1511    }
1512
1513    /// Get the depth of an event in the DAG.
1514    async fn depth(&self, event_id: EventId) -> Effect<u32> {
1515        match self.read(event_id).await {
1516            Effect::Ok(event) => Effect::Ok(event.header.position.depth),
1517            Effect::Err(e) => Effect::Err(e),
1518            other => other.map(|_| unreachable!()),
1519        }
1520    }
1521
1522    /// Check if one event is an ancestor of another.
1523    async fn is_ancestor(&self, ancestor: EventId, descendant: EventId) -> Effect<bool> {
1524        let ancestors = match self.walk_ancestors(descendant, 1000).await {
1525            Effect::Ok(events) => events,
1526            Effect::Err(e) => return Effect::Err(e),
1527            other => return other.map(|_| unreachable!()),
1528        };
1529
1530        for event in ancestors {
1531            if event.header.event_id == ancestor {
1532                return Effect::Ok(true);
1533            }
1534        }
1535        Effect::Ok(false)
1536    }
1537}
1538
1539// Blanket implementation: any type implementing EventDag automatically gets EventDagExt
1540impl<T: EventDag> EventDagExt for T {}
1541
1542/// Builder for creating events with proper positioning.
1543#[derive(Debug, Clone)]
1544pub struct EventBuilder<P> {
1545    parent: Option<EventId>,
1546    lane: u32,
1547    correlation_id: Option<EventId>,
1548    payload: P,
1549    flags: EventFlags,
1550}
1551
1552impl<P> EventBuilder<P> {
1553    /// Create a new event builder with the given payload.
1554    pub fn new(payload: P) -> Self {
1555        Self {
1556            parent: None,
1557            lane: 0,
1558            correlation_id: None,
1559            payload,
1560            flags: EventFlags::empty(),
1561        }
1562    }
1563
1564    /// Set the parent event.
1565    pub fn parent(mut self, parent: EventId) -> Self {
1566        self.parent = Some(parent);
1567        self
1568    }
1569
1570    /// Set the lane.
1571    pub fn lane(mut self, lane: u32) -> Self {
1572        self.lane = lane;
1573        self
1574    }
1575
1576    /// Set the correlation ID.
1577    pub fn correlation(mut self, correlation_id: EventId) -> Self {
1578        self.correlation_id = Some(correlation_id);
1579        self
1580    }
1581
1582    /// Mark this event as requiring acknowledgment.
1583    pub fn requires_ack(mut self) -> Self {
1584        self.flags |= EventFlags::REQUIRES_ACK;
1585        self
1586    }
1587
1588    /// Mark this event as critical.
1589    pub fn critical(mut self) -> Self {
1590        self.flags |= EventFlags::CRITICAL;
1591        self
1592    }
1593
1594    /// Mark this event as transactional.
1595    pub fn transactional(mut self) -> Self {
1596        self.flags |= EventFlags::TRANSACTIONAL;
1597        self
1598    }
1599
1600    /// Get the configured parent.
1601    pub fn get_parent(&self) -> Option<EventId> {
1602        self.parent
1603    }
1604
1605    /// Get the configured lane.
1606    pub fn get_lane(&self) -> u32 {
1607        self.lane
1608    }
1609
1610    /// Get the configured flags.
1611    pub fn get_flags(&self) -> EventFlags {
1612        self.flags
1613    }
1614
1615    /// Get the correlation ID (or None).
1616    pub fn get_correlation_id(&self) -> Option<EventId> {
1617        self.correlation_id
1618    }
1619
1620    /// Consume the builder and return the payload.
1621    pub fn into_payload(self) -> P {
1622        self.payload
1623    }
1624}
1625
1626#[cfg(test)]
1627mod tests {
1628    use super::*;
1629
1630    #[test]
1631    fn test_event_header_size() {
1632        // Note: Size increased from 64 bytes after adding random_seed field
1633        // Size is now larger but still cache-aligned
1634        assert_eq!(std::mem::align_of::<EventHeader>(), 64);
1635    }
1636
1637    #[test]
1638    fn test_dag_position() {
1639        let root = DagPosition::root();
1640        assert!(root.is_root());
1641        assert_eq!(root.depth, 0);
1642
1643        let child = root.child(1);
1644        assert!(!child.is_root());
1645        assert_eq!(child.depth, 1);
1646        assert!(root.is_ancestor_of(&child));
1647
1648        let fork = root.fork(1, 0);
1649        assert_eq!(fork.lane, 1);
1650        assert!(!root.is_ancestor_of(&fork)); // Different lane
1651    }
1652
1653    #[test]
1654    fn test_event_kind_categories() {
1655        assert!(EventKind::SYSTEM_INIT.is_system());
1656        assert!(!EventKind::TRAJECTORY_CREATED.is_system());
1657        assert!(EventKind::EFFECT_ERROR.is_effect());
1658
1659        let custom = EventKind::custom(0xF, 0x123);
1660        assert_eq!(custom.category(), 0xF);
1661        assert_eq!(custom.type_id(), 0x123);
1662    }
1663
1664    #[test]
1665    fn test_event_flags() {
1666        let mut flags = EventFlags::REQUIRES_ACK | EventFlags::CRITICAL;
1667        assert!(flags.contains(EventFlags::REQUIRES_ACK));
1668        assert!(flags.contains(EventFlags::CRITICAL));
1669        assert!(!flags.contains(EventFlags::REPLAY));
1670
1671        flags |= EventFlags::ACKNOWLEDGED;
1672        assert!(flags.contains(EventFlags::ACKNOWLEDGED));
1673    }
1674
1675    #[test]
1676    fn test_event_header_creation() {
1677        let event_id = EventId::now_v7();
1678        let correlation_id = EventId::now_v7();
1679        let header = EventHeader::new(
1680            event_id,
1681            correlation_id,
1682            1234567890,
1683            DagPosition::root(),
1684            100,
1685            EventKind::TRAJECTORY_CREATED,
1686        )
1687        .with_flags(EventFlags::REQUIRES_ACK);
1688
1689        assert_eq!(header.event_id, event_id);
1690        assert!(header.requires_ack());
1691        assert!(!header.is_acknowledged());
1692    }
1693
1694    #[test]
1695    fn test_hash_chain_genesis_event() {
1696        use serde_json::json;
1697
1698        let event = Event::new(
1699            EventHeader::new(
1700                EventId::now_v7(),
1701                EventId::now_v7(),
1702                chrono::Utc::now().timestamp_micros(),
1703                DagPosition::root(),
1704                0,
1705                EventKind::DATA,
1706            ),
1707            json!({"type": "genesis"}),
1708        );
1709
1710        assert!(event.is_genesis());
1711        assert!(event.verify(&[0u8; 32]));
1712    }
1713
1714    #[test]
1715    fn test_hash_chain_tamper_detection() {
1716        use serde_json::json;
1717
1718        let parent = Event::new(
1719            EventHeader::new(
1720                EventId::now_v7(),
1721                EventId::now_v7(),
1722                chrono::Utc::now().timestamp_micros(),
1723                DagPosition::root(),
1724                0,
1725                EventKind::DATA,
1726            ),
1727            json!({"data": "original"}),
1728        );
1729
1730        let parent_hash = parent.compute_hash();
1731        let wrong_hash = [0xFF; 32];
1732
1733        let child_hash = {
1734            let temp = Event::new(
1735                EventHeader::new(
1736                    EventId::now_v7(),
1737                    EventId::now_v7(),
1738                    chrono::Utc::now().timestamp_micros(),
1739                    DagPosition::new(1, 0, 0),
1740                    0,
1741                    EventKind::DATA,
1742                ),
1743                json!({"data": "child"}),
1744            );
1745            temp.compute_hash()
1746        };
1747
1748        let hash_chain = HashChain {
1749            prev_hash: wrong_hash, // Wrong!
1750            event_hash: child_hash,
1751            algorithm: HashAlgorithm::Blake3,
1752        };
1753
1754        let tampered = Event::with_hash_chain(
1755            EventHeader::new(
1756                EventId::now_v7(),
1757                EventId::now_v7(),
1758                chrono::Utc::now().timestamp_micros(),
1759                DagPosition::new(1, 0, 0),
1760                0,
1761                EventKind::DATA,
1762            ),
1763            json!({"data": "child"}),
1764            hash_chain,
1765        );
1766
1767        assert!(!tampered.verify(&parent_hash)); // Should fail
1768    }
1769
1770    #[test]
1771    fn upstream_signal_conflict_detected_roundtrip() {
1772        let signal = UpstreamSignal::ConflictDetected {
1773            branch_a: EventId::now_v7(),
1774            branch_b: EventId::now_v7(),
1775        };
1776        let json = serde_json::to_string(&signal).unwrap();
1777        let deserialized: UpstreamSignal = serde_json::from_str(&json).unwrap();
1778        assert!(matches!(
1779            deserialized,
1780            UpstreamSignal::ConflictDetected { .. }
1781        ));
1782    }
1783
1784    #[test]
1785    fn effect_conflict_event_kind() {
1786        assert!(EventKind::EFFECT_CONFLICT.is_effect());
1787        assert_eq!(EventKind::EFFECT_CONFLICT.0, 0xD007);
1788    }
1789
1790    #[test]
1791    fn is_defined_accepts_known_kinds() {
1792        assert!(EventKind::SCOPE_CREATED.is_defined());
1793        assert!(EventKind::AGENT_STATUS_CHANGED.is_defined());
1794        assert!(EventKind::EFFECT_ERROR.is_defined());
1795        assert!(EventKind::SCOPE_CONTEXT_PAGED.is_defined());
1796        assert!(EventKind::SYNC_PULSE_EMITTED.is_defined());
1797        assert!(EventKind::SYNC_PULSE_RECONCILED.is_defined());
1798    }
1799
1800    #[test]
1801    fn is_defined_rejects_unknown_kinds() {
1802        assert!(!EventKind(0x0FFF).is_defined());
1803        assert!(!EventKind(0xFFFF).is_defined());
1804        assert!(!EventKind(0x1FFF).is_defined());
1805    }
1806
1807    #[test]
1808    fn test_undefined_event_kind_fails_validation() {
1809        assert!(validate_event_kind(EventKind(0x9999)).is_err());
1810    }
1811
1812    #[test]
1813    fn test_defined_event_kinds_pass_validation() {
1814        assert!(validate_event_kind(EventKind::DATA).is_ok());
1815    }
1816
1817    #[test]
1818    fn try_compute_hash_rejects_unserializable_payload() {
1819        struct FailingPayload;
1820
1821        impl Serialize for FailingPayload {
1822            fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
1823            where
1824                S: serde::Serializer,
1825            {
1826                Err(serde::ser::Error::custom("nope"))
1827            }
1828        }
1829
1830        let event = Event::new(
1831            EventHeader::new(
1832                EventId::now_v7(),
1833                EventId::now_v7(),
1834                chrono::Utc::now().timestamp_micros(),
1835                DagPosition::root(),
1836                0,
1837                EventKind::DATA,
1838            ),
1839            FailingPayload,
1840        );
1841
1842        let err = event.try_compute_hash().expect_err("hashing should fail");
1843        assert!(err.to_string().contains("serialize event content"));
1844    }
1845}