aura_core/effects/ledger.rs
1//! Effect API interface
2//!
3//! Pure trait definitions for event sourcing and audit operations used by protocols.
4//!
5//! Note: This trait works with serialized event data to avoid circular dependencies
6//! with aura-journal. Implementations can internally use richer types like AccountState.
7//!
8//! # Effect Classification
9//!
10//! - **Category**: Application Effect
11//! - **Implementation**: `aura-journal` or `aura-protocol` (Layer 2 or Layer 4)
12//! - **Usage**: Event sourcing, audit trails, journal graph operations
13//!
14//! This is an application effect for event sourcing and journal graph operations.
15//! Combines traditional event sourcing (events, devices, authorization) with journal
16//! graph operations (nodes, edges, CRDT merging) for Aura's graph-based threshold
17//! identity structure. Handlers in `aura-journal` or `aura-protocol`.
18
19use crate::types::identifiers::DeviceId;
20use async_trait::async_trait;
21
22/// Effect API effects for event sourcing and audit trails
23///
24/// This trait combines traditional event sourcing operations (events, devices, authorization)
25/// with journal graph operations (nodes, edges, CRDT merging) needed for the journal's
26/// graph-based threshold identity structure.
27#[async_trait]
28pub trait EffectApiEffects: Send + Sync {
29 // Traditional Effect API Operations
30
31 /// Append an event to the effect_api
32 async fn append_event(&self, event: Vec<u8>) -> Result<(), EffectApiError>;
33
34 /// Get the current epoch/sequence number
35 async fn current_epoch(&self) -> Result<u64, EffectApiError>;
36
37 /// Get events since a specific epoch
38 async fn events_since(&self, epoch: u64) -> Result<Vec<Vec<u8>>, EffectApiError>;
39
40 /// Check if a device is authorized for an operation
41 async fn is_device_authorized(
42 &self,
43 device_id: DeviceId,
44 operation: &str,
45 ) -> Result<bool, EffectApiError>;
46
47 /// Update device last seen timestamp
48 async fn update_device_activity(&self, device_id: DeviceId) -> Result<(), EffectApiError>;
49
50 /// Subscribe to effect_api events
51 async fn subscribe_to_events(&self) -> Result<EffectApiEventStream, EffectApiError>;
52
53 // Journal Graph Operations
54 // Operations for managing the journal's graph-based threshold identity structure
55
56 /// Check if adding an edge would create a cycle in the journal graph
57 async fn would_create_cycle(
58 &self,
59 edges: &[(Vec<u8>, Vec<u8>)],
60 new_edge: (Vec<u8>, Vec<u8>),
61 ) -> Result<bool, EffectApiError>;
62
63 /// Find strongly connected components in the journal graph
64 async fn find_connected_components(
65 &self,
66 edges: &[(Vec<u8>, Vec<u8>)],
67 ) -> Result<Vec<Vec<Vec<u8>>>, EffectApiError>;
68
69 /// Find topological ordering of nodes in the journal graph
70 async fn topological_sort(
71 &self,
72 edges: &[(Vec<u8>, Vec<u8>)],
73 ) -> Result<Vec<Vec<u8>>, EffectApiError>;
74
75 /// Calculate shortest path between two nodes in the journal graph
76 async fn shortest_path(
77 &self,
78 edges: &[(Vec<u8>, Vec<u8>)],
79 start: Vec<u8>,
80 end: Vec<u8>,
81 ) -> Result<Option<Vec<Vec<u8>>>, EffectApiError>;
82
83 /// Generate a random secret for cryptographic operations
84 async fn generate_secret(&self, length: usize) -> Result<Vec<u8>, EffectApiError>;
85
86 /// Hash data with cryptographic hash function
87 async fn hash_data(&self, data: &[u8]) -> Result<[u8; 32], EffectApiError>;
88
89 /// Get current timestamp (seconds since Unix epoch)
90 async fn current_timestamp(&self) -> Result<u64, EffectApiError>;
91
92 /// Get device ID for this effect_api instance
93 async fn effect_api_device_id(&self) -> Result<DeviceId, EffectApiError>;
94
95 /// Generate a new UUID
96 async fn new_uuid(&self) -> Result<uuid::Uuid, EffectApiError>;
97}
98
99/// Effect API-related errors
100#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)]
101pub enum EffectApiError {
102 /// Effect API is not available
103 #[error("Effect API not available")]
104 NotAvailable,
105
106 /// Access denied for the requested operation
107 #[error("Access denied for operation: {operation}")]
108 AccessDenied {
109 /// The operation that was denied
110 operation: String,
111 },
112
113 /// Device not found in effect_api
114 #[error("Device not found: {device_id}")]
115 DeviceNotFound {
116 /// The device ID that was not found
117 device_id: DeviceId,
118 },
119
120 /// Event has invalid format
121 #[error("Invalid event format")]
122 InvalidEvent,
123
124 /// Requested epoch is out of range
125 #[error("Epoch out of range: {epoch}")]
126 EpochOutOfRange {
127 /// The invalid epoch number
128 epoch: u64,
129 },
130
131 /// Effect API data is corrupted
132 #[error("Effect API corrupted: {reason}")]
133 Corrupted {
134 /// Reason for corruption
135 reason: String,
136 },
137
138 /// Concurrent access conflict detected
139 #[error("Concurrent access conflict")]
140 ConcurrentAccess,
141
142 /// Backend storage error
143 #[error("Backend error: {error}")]
144 Backend {
145 /// Backend error message
146 error: String,
147 },
148
149 /// Journal graph operation failed
150 #[error("Graph operation failed: {message}")]
151 GraphOperationFailed {
152 /// Description of the failure
153 message: String,
154 },
155
156 /// Cryptographic operation failed
157 #[error("Cryptographic operation failed: {message}")]
158 CryptoOperationFailed {
159 /// Description of the failure
160 message: String,
161 },
162
163 /// Invalid node or edge data
164 #[error("Invalid graph data: {message}")]
165 InvalidGraphData {
166 /// Description of invalid data
167 message: String,
168 },
169}
170
171/// Effect API events
172#[derive(Debug, Clone)]
173pub enum EffectApiEvent {
174 /// New event appended to effect_api
175 EventAppended {
176 /// Epoch number of the new event
177 epoch: u64,
178 /// The event data
179 event: Vec<u8>,
180 },
181 /// Device activity timestamp updated
182 DeviceActivity {
183 /// The device that was active
184 device_id: DeviceId,
185 /// Updated last seen timestamp
186 last_seen: u64,
187 },
188 /// Account state has changed
189 StateChanged,
190}
191
192/// Stream of effect_api events
193pub type EffectApiEventStream = Box<dyn futures::Stream<Item = EffectApiEvent> + Send + Unpin>;