1use bitflags::bitflags;
12use serde::{Deserialize, Serialize};
13use std::fmt;
14use uuid::Uuid;
15
16use crate::identity::EntityIdType;
17use crate::{CellstateResult, EventId, OperationalError};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
35#[repr(C)]
36pub struct DagPosition {
37 pub depth: u32,
39 pub lane: u32,
41 pub sequence: u32,
43}
44
45impl DagPosition {
46 pub const fn new(depth: u32, lane: u32, sequence: u32) -> Self {
48 Self {
49 depth,
50 lane,
51 sequence,
52 }
53 }
54
55 pub const fn root() -> Self {
57 Self::new(0, 0, 0)
58 }
59
60 pub const fn child(&self, sequence: u32) -> Self {
62 Self::new(self.depth + 1, self.lane, sequence)
63 }
64
65 pub const fn fork(&self, new_lane: u32, sequence: u32) -> Self {
67 Self::new(self.depth + 1, new_lane, sequence)
68 }
69
70 pub const fn is_ancestor_of(&self, other: &Self) -> bool {
76 self.depth < other.depth && self.lane == other.lane
77 }
78
79 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#[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 pub const DATA: Self = Self(0x0000);
131
132 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 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 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 pub const SCOPE_CONTEXT_PAGED: Self = Self(0x2005);
154
155 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 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 pub const TURN_CREATED: Self = Self(0x5001);
170
171 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 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 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 pub const STEP_COMPLETED: Self = Self(0x6030);
191 pub const STEP_FAILED: Self = Self(0x6031);
192
193 pub const BELIEF_CREATED: Self = Self(0x6040);
195 pub const BELIEF_SUPERSEDED: Self = Self(0x6041);
196
197 pub const DELIBERATION_COMPLETED: Self = Self(0x6050);
199 pub const ENGINE_STATE_PERSISTED: Self = Self(0x6051);
200
201 pub const SYNC_PULSE_EMITTED: Self = Self(0x6060);
204 pub const SYNC_PULSE_RECONCILED: Self = Self(0x6061);
206
207 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 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 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 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 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 pub const EVOLUTION_SNAPSHOT_CREATED: Self = Self(0xC001);
241 pub const EVOLUTION_PHASE_CHANGED: Self = Self(0xC002);
242
243 pub const SAGA_DELEGATION_TIMED_OUT: Self = Self(0xC010);
246 pub const SAGA_HANDOFF_TIMED_OUT: Self = Self(0xC011);
248 pub const TTL_ARTIFACT_EXPIRED: Self = Self(0xC020);
250 pub const TTL_NOTE_EXPIRED: Self = Self(0xC021);
252 pub const MEMORY_ENTITY_ARCHIVED: Self = Self(0xC030);
254 pub const SCOPE_TURNS_REAPED: Self = Self(0xC040);
256 pub const HASH_CHAIN_VIOLATION: Self = Self(0xC050);
258 pub const EFFECT_EXECUTED: Self = Self(0xC060);
260
261 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 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 pub const MEMORY_COMMIT_CREATED: Self = Self(0xE010);
278 pub const CONTEXT_COMMIT: Self = Self(0xE020);
280
281 pub const WORKING_SET_UPDATED: Self = Self(0xF101);
283 pub const WORKING_SET_DELETED: Self = Self(0xF102);
284
285 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 pub const BROWSER_ACTION_COMPLETED: Self = Self(0xF401);
301
302 pub const WEB_MCP_TOOL_EXECUTED: Self = Self(0xF501);
304 pub const WEB_MCP_DISCOVERY_SNAPSHOT: Self = Self(0xF502);
305
306 pub const REASONING_TRACE: Self = Self(0xF301);
313
314 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 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 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 pub const COMPONENT_PATCH: Self = Self(0x0020);
360 pub const COMPONENT_SNAPSHOT: Self = Self(0x0021);
361
362 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 pub const fn category(&self) -> u8 {
370 (self.0 >> 12) as u8
371 }
372
373 pub const fn type_id(&self) -> u16 {
375 self.0 & 0x0FFF
376 }
377
378 pub const fn custom(category: u8, type_id: u16) -> Self {
380 Self(((category as u16) << 12) | (type_id & 0x0FFF))
381 }
382
383 pub const fn is_system(&self) -> bool {
385 self.category() == 0
386 }
387
388 pub const fn is_effect(&self) -> bool {
390 self.category() == 0xD
391 }
392
393 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
529pub 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
547bitflags! {
552 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
554 pub struct EventFlags: u8 {
555 const REQUIRES_ACK = 0b0000_0001;
557 const TRANSACTIONAL = 0b0000_0010;
559 const COMPRESSED = 0b0000_0100;
561 const REPLAY = 0b0000_1000;
563 const ACKNOWLEDGED = 0b0001_0000;
565 const COMPENSATED = 0b0010_0000;
567 const CRITICAL = 0b0100_0000;
569 const PII_SCRUBBED = 0b1000_0000;
571 }
572}
573
574impl Default for EventFlags {
575 fn default() -> Self {
576 Self::empty()
577 }
578}
579
580impl 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
636#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
637#[repr(C, align(64))]
638pub struct EventHeader {
639 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
641 pub event_id: EventId,
642 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
644 pub correlation_id: EventId,
645 pub timestamp: i64,
647 pub position: DagPosition,
649 pub payload_size: u32,
651 pub event_kind: EventKind,
653 pub flags: EventFlags,
655 pub random_seed: Option<u64>,
657 #[serde(default, skip_serializing_if = "Option::is_none")]
663 pub causality: Option<Causality>,
664 #[serde(skip)]
666 _reserved: [u8; 5],
667}
668
669const _: () = assert!(std::mem::align_of::<EventHeader>() == 64);
672
673impl EventHeader {
674 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 pub fn with_flags(mut self, flags: EventFlags) -> Self {
699 self.flags = flags;
700 self
701 }
702
703 pub fn with_random_seed(mut self, seed: u64) -> Self {
705 self.random_seed = Some(seed);
706 self
707 }
708
709 pub fn with_causality(mut self, causality: Causality) -> Self {
711 self.causality = Some(causality);
712 self
713 }
714
715 pub fn requires_ack(&self) -> bool {
717 self.flags.contains(EventFlags::REQUIRES_ACK)
718 }
719
720 pub fn is_transactional(&self) -> bool {
722 self.flags.contains(EventFlags::TRANSACTIONAL)
723 }
724
725 pub fn is_replay(&self) -> bool {
727 self.flags.contains(EventFlags::REPLAY)
728 }
729
730 pub fn is_acknowledged(&self) -> bool {
732 self.flags.contains(EventFlags::ACKNOWLEDGED)
733 }
734
735 pub fn is_critical(&self) -> bool {
737 self.flags.contains(EventFlags::CRITICAL)
738 }
739
740 pub fn acknowledge(&mut self) {
742 self.flags |= EventFlags::ACKNOWLEDGED;
743 }
744
745 pub fn age_micros(&self, now_micros: i64) -> i64 {
747 now_micros - self.timestamp
748 }
749}
750
751#[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 #[serde(skip_serializing_if = "Option::is_none")]
767 pub hash_chain: Option<HashChain>,
768}
769
770impl<P> Event<P> {
771 pub fn new(header: EventHeader, payload: P) -> Self {
773 Self {
774 header,
775 payload,
776 hash_chain: None,
777 }
778 }
779
780 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 pub fn event_id(&self) -> EventId {
791 self.header.event_id
792 }
793
794 pub fn event_kind(&self) -> EventKind {
796 self.header.event_kind
797 }
798
799 pub fn correlation_id(&self) -> EventId {
801 self.header.correlation_id
802 }
803
804 pub fn position(&self) -> DagPosition {
806 self.header.position
807 }
808
809 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#[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 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 pub fn compute_hash(&self) -> [u8; 32] {
857 self.try_compute_hash()
858 .expect("event hashing requires serializable event content")
859 }
860
861 pub fn verify_checked(&self, parent_hash: &[u8; 32]) -> CellstateResult<bool> {
864 Blake3Verifier.try_verify_chain(self, parent_hash)
865 }
866
867 pub fn verify(&self, parent_hash: &[u8; 32]) -> bool {
870 self.verify_checked(parent_hash).unwrap_or(false)
871 }
872
873 pub fn is_genesis(&self) -> bool {
875 self.hash_chain.is_none()
876 }
877}
878
879#[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 Ack {
893 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
894 event_id: EventId,
895 },
896 Backpressure {
898 until: i64,
900 },
901 Cancel {
903 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
904 correlation_id: EventId,
905 reason: String,
906 },
907 CompensationComplete {
909 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
910 event_id: EventId,
911 },
912 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 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#[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 Sha256,
940 #[default]
942 Blake3,
943}
944
945#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
950#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
951pub struct HashChain {
952 #[cfg_attr(feature = "openapi", schema(value_type = String))]
954 pub prev_hash: [u8; 32],
955 #[cfg_attr(feature = "openapi", schema(value_type = String))]
957 pub event_hash: [u8; 32],
958 pub algorithm: HashAlgorithm,
960}
961
962impl Default for HashChain {
963 fn default() -> Self {
964 Self {
965 prev_hash: [0u8; 32], event_hash: [0u8; 32],
967 algorithm: HashAlgorithm::Blake3,
968 }
969 }
970}
971
972#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
981#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
982pub struct Causality {
983 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
985 pub trace_id: Uuid,
986 pub span_id: u64,
988 pub parent_span_id: Option<u64>,
990 #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>))]
992 pub parent_event_ids: Vec<EventId>,
993 #[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 pub fn new() -> Self {
1014 Self::default()
1015 }
1016
1017 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 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
1041use crate::{AgentId, ArtifactId, ExtractionMethod, NoteId, Timestamp};
1046
1047#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1051#[serde(rename_all = "snake_case")]
1052#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1053pub enum EvidenceRef {
1054 DocChunk {
1056 doc_id: String,
1057 chunk_id: String,
1058 offset: u32,
1059 length: u32,
1060 },
1061 Event {
1063 #[cfg_attr(feature = "openapi", schema(value_type = String, format = "uuid"))]
1064 event_id: EventId,
1065 timestamp: i64,
1066 },
1067 ToolResult {
1069 call_id: String,
1070 tool_name: String,
1071 result_index: u32,
1072 },
1073 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 KnowledgePack { pack_id: String, section: String },
1082 Artifact { artifact_id: ArtifactId },
1084 Note { note_id: NoteId },
1086 Manual {
1088 user_id: String,
1089 timestamp: i64,
1090 description: String,
1091 },
1092}
1093
1094#[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 #[default]
1101 Unverified,
1102 Verified,
1104 PartiallyVerified,
1106 Invalid,
1108 Expired,
1110}
1111
1112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1117#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1118pub struct EnhancedProvenance {
1119 pub source_turn: i32,
1121 pub extraction_method: ExtractionMethod,
1123 pub confidence: Option<f32>,
1125 pub evidence_refs: Vec<EvidenceRef>,
1127 #[cfg_attr(feature = "openapi", schema(value_type = Vec<serde_json::Value>))]
1129 pub chain_of_custody: Vec<(AgentId, Timestamp)>,
1130 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 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 pub fn with_evidence(mut self, evidence: EvidenceRef) -> Self {
1159 self.evidence_refs.push(evidence);
1160 self
1161 }
1162
1163 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 pub fn with_confidence(mut self, confidence: f32) -> Self {
1171 self.confidence = Some(confidence.clamp(0.0, 1.0));
1172 self
1173 }
1174
1175 pub fn with_verification(mut self, status: VerificationStatus) -> Self {
1177 self.verification_status = status;
1178 self
1179 }
1180}
1181
1182pub trait EventVerifier: Send + Sync {
1191 fn compute_hash<P: Serialize>(&self, event: &Event<P>) -> [u8; 32];
1193
1194 fn try_compute_hash<P: Serialize>(&self, event: &Event<P>) -> CellstateResult<[u8; 32]>;
1196
1197 fn verify_hash<P: Serialize>(&self, event: &Event<P>, expected: &[u8; 32]) -> bool {
1199 &self.compute_hash(event) == expected
1200 }
1201
1202 fn verify_chain<P: Serialize>(&self, current: &Event<P>, previous_hash: &[u8; 32]) -> bool;
1204
1205 fn try_verify_chain<P: Serialize>(
1207 &self,
1208 current: &Event<P>,
1209 previous_hash: &[u8; 32],
1210 ) -> CellstateResult<bool>;
1211
1212 fn algorithm(&self) -> HashAlgorithm;
1214}
1215
1216#[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) = ¤t.hash_chain else {
1242 return Ok(true); };
1244
1245 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(¤t.header, ¤t.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#[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) = ¤t.hash_chain else {
1294 return Ok(true); };
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(¤t.header, ¤t.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
1312use crate::Effect;
1317
1318#[async_trait::async_trait]
1340pub trait EventDag: Send + Sync {
1341 type Payload: Clone + Send + Sync + 'static;
1343
1344 async fn append(&self, event: Event<Self::Payload>) -> Effect<EventId>;
1349
1350 async fn read(&self, event_id: EventId) -> Effect<Event<Self::Payload>>;
1352
1353 async fn walk_ancestors(
1358 &self,
1359 from: EventId,
1360 limit: usize,
1361 ) -> Effect<Vec<Event<Self::Payload>>>;
1362
1363 async fn walk_descendants(
1365 &self,
1366 from: EventId,
1367 limit: usize,
1368 ) -> Effect<Vec<Event<Self::Payload>>>;
1369
1370 async fn signal_upstream(&self, from: EventId, signal: UpstreamSignal) -> Effect<()>;
1375
1376 async fn find_correlation_chain(
1378 &self,
1379 tenant_id: uuid::Uuid,
1380 correlation_id: EventId,
1381 ) -> Effect<Vec<Event<Self::Payload>>>;
1382
1383 async fn next_position(&self, parent: Option<EventId>, lane: u32) -> Effect<DagPosition>;
1385
1386 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 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 async fn acknowledge(&self, event_id: EventId, send_upstream: bool) -> Effect<()>;
1413
1414 async fn unacknowledged(&self, limit: usize) -> Effect<Vec<Event<Self::Payload>>>;
1416}
1417
1418#[async_trait::async_trait]
1420pub trait EventDagExt: EventDag {
1421 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 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 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 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 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
1539impl<T: EventDag> EventDagExt for T {}
1541
1542#[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 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 pub fn parent(mut self, parent: EventId) -> Self {
1566 self.parent = Some(parent);
1567 self
1568 }
1569
1570 pub fn lane(mut self, lane: u32) -> Self {
1572 self.lane = lane;
1573 self
1574 }
1575
1576 pub fn correlation(mut self, correlation_id: EventId) -> Self {
1578 self.correlation_id = Some(correlation_id);
1579 self
1580 }
1581
1582 pub fn requires_ack(mut self) -> Self {
1584 self.flags |= EventFlags::REQUIRES_ACK;
1585 self
1586 }
1587
1588 pub fn critical(mut self) -> Self {
1590 self.flags |= EventFlags::CRITICAL;
1591 self
1592 }
1593
1594 pub fn transactional(mut self) -> Self {
1596 self.flags |= EventFlags::TRANSACTIONAL;
1597 self
1598 }
1599
1600 pub fn get_parent(&self) -> Option<EventId> {
1602 self.parent
1603 }
1604
1605 pub fn get_lane(&self) -> u32 {
1607 self.lane
1608 }
1609
1610 pub fn get_flags(&self) -> EventFlags {
1612 self.flags
1613 }
1614
1615 pub fn get_correlation_id(&self) -> Option<EventId> {
1617 self.correlation_id
1618 }
1619
1620 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 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)); }
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, 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)); }
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}