Skip to main content

a3s_code_core/state_graph/
event.rs

1use super::{GraphPatch, ObjectId, RelationId};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5/// Current record schema. Version 2 uses incremental structural state hashes;
6/// strict replay remains compatible with version 1 records.
7pub const GRAPH_EVENT_SCHEMA_VERSION: u32 = 2;
8pub(crate) const MIN_GRAPH_EVENT_SCHEMA_VERSION: u32 = 1;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(tag = "type", rename_all = "snake_case")]
12pub enum GraphEvent {
13    GoalCreated {
14        goal: String,
15    },
16    ObjectCreated {
17        id: ObjectId,
18        object_type: String,
19        data: Value,
20    },
21    ObjectUpdated {
22        id: ObjectId,
23        version: u64,
24        data: Value,
25    },
26    ObjectRemoved {
27        id: ObjectId,
28        version: u64,
29    },
30    RelationCreated {
31        id: RelationId,
32        relation_type: String,
33        source: ObjectId,
34        target: ObjectId,
35        data: Value,
36    },
37    RelationUpdated {
38        id: RelationId,
39        version: u64,
40        data: Value,
41    },
42    RelationRemoved {
43        id: RelationId,
44        version: u64,
45    },
46    PatchProposed {
47        patch_id: String,
48        patch: GraphPatch,
49    },
50    PatchApplied {
51        patch_id: String,
52    },
53    PatchRejected {
54        patch_id: String,
55        reason: String,
56    },
57    BehaviorStarted {
58        name: String,
59    },
60    BehaviorCompleted {
61        name: String,
62    },
63    BehaviorFailed {
64        name: String,
65        error: String,
66    },
67    BranchForked {
68        parent_branch_id: String,
69        fork_sequence: u64,
70    },
71    /// Audit fact imported from another authoritative event stream.
72    ExternalEventObserved {
73        source: String,
74        stream_id: String,
75        sequence: u64,
76        event_id: String,
77        name: String,
78        payload: Value,
79    },
80    Custom {
81        name: String,
82        payload: Value,
83    },
84}
85
86impl GraphEvent {
87    pub fn event_type(&self) -> &str {
88        match self {
89            Self::GoalCreated { .. } => "goal.created",
90            Self::ObjectCreated { .. } => "object.created",
91            Self::ObjectUpdated { .. } => "object.updated",
92            Self::ObjectRemoved { .. } => "object.removed",
93            Self::RelationCreated { .. } => "relation.created",
94            Self::RelationUpdated { .. } => "relation.updated",
95            Self::RelationRemoved { .. } => "relation.removed",
96            Self::PatchProposed { .. } => "patch.proposed",
97            Self::PatchApplied { .. } => "patch.applied",
98            Self::PatchRejected { .. } => "patch.rejected",
99            Self::BehaviorStarted { .. } => "behavior.started",
100            Self::BehaviorCompleted { .. } => "behavior.completed",
101            Self::BehaviorFailed { .. } => "behavior.failed",
102            Self::BranchForked { .. } => "branch.forked",
103            Self::ExternalEventObserved { name, .. } => name,
104            Self::Custom { name, .. } => name,
105        }
106    }
107}
108
109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110pub struct GraphEventRecord {
111    pub schema_version: u32,
112    pub id: String,
113    pub sequence: u64,
114    pub timestamp_ms: u64,
115    pub branch_id: String,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub causation_id: Option<String>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub correlation_id: Option<String>,
120    pub state_version_before: u64,
121    pub state_version_after: u64,
122    /// SHA-256 of the deterministic graph projection after this event.
123    pub state_hash_after: String,
124    /// Hash of the preceding record, forming a tamper-evident event chain.
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub previous_record_hash: Option<String>,
127    /// SHA-256 over this record's immutable fields and `previous_record_hash`.
128    pub record_hash: String,
129    pub event: GraphEvent,
130}