eidetica/backend/mod.rs
1//! Backend implementations for Eidetica storage
2//!
3//! This module provides the core `BackendImpl` trait and various backend implementations
4//! organized by category (database, file, network, cloud).
5//!
6//! The `BackendImpl` trait defines the interface for storing and retrieving `Entry` objects.
7//! This allows the core database logic (`Instance`, `Database`) to be independent of the specific storage mechanism.
8//!
9//! Instance wraps BackendImpl in a `Backend` struct that provides a layer for future development.
10
11use std::any::Any;
12
13use async_trait::async_trait;
14use serde::{Deserialize, Serialize};
15
16use crate::{
17 Result,
18 auth::crypto::PrivateKey,
19 entry::{Entry, ID},
20};
21
22/// Persistent metadata for an Eidetica instance.
23///
24/// This struct consolidates all instance-level state that needs to persist across restarts:
25/// - The device signing key (cryptographic identity)
26/// - System database root IDs
27/// - Optional sync database root ID
28///
29/// The presence of `InstanceMetadata` in a backend indicates an initialized instance.
30/// A backend without metadata is treated as uninitialized and may trigger instance creation.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct InstanceMetadata {
33 /// Device signing key - the instance's cryptographic identity.
34 ///
35 /// This key is generated once during instance creation and persists for the lifetime
36 /// of the instance. It is used to sign system database entries and for sync identity.
37 pub device_key: PrivateKey,
38
39 /// Root ID of the _users system database.
40 ///
41 /// This database tracks user accounts and their associated data.
42 pub users_db: ID,
43
44 /// Root ID of the _databases system database.
45 ///
46 /// This database tracks metadata about all databases in the instance.
47 pub databases_db: ID,
48
49 /// Root ID of the _sync database (None until `enable_sync()` is called).
50 ///
51 /// This database stores all sync-related state.
52 pub sync_db: Option<ID>,
53}
54
55// Category modules
56pub mod database;
57pub mod errors;
58
59// Re-export main types for easier access
60pub use errors::BackendError;
61
62/// Verification status for entries in the backend.
63///
64/// This enum tracks whether an entry has been cryptographically verified
65/// by the higher-level authentication system. The backend stores this status
66/// but does not perform verification itself - that's handled by the Database/Transaction layers.
67///
68/// Currently all local entries must be authenticated (Verified), but this enum
69/// is designed to support future sync scenarios where entries may be received
70/// before verification is complete.
71#[derive(
72 Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default,
73)]
74pub enum VerificationStatus {
75 /// Entry has been cryptographically verified as authentic.
76 /// This is the default for all locally created entries.
77 #[default]
78 Verified,
79 /// Entry failed verification (invalid signature, revoked key, etc.).
80 /// Also used temporarily for entries awaiting verification during sync.
81 Failed,
82 // TODO: Add `Unverified` status and a re-verification pass for sync.
83 // Entries signed via delegation (SigKey::Delegation) require the delegated
84 // tree to be present locally for validation. During sync, the delegated tree
85 // may arrive after the entries that reference it. Currently those entries are
86 // stored as `Failed` with no retry. A re-verification pass should run when
87 // new trees become available, promoting `Unverified` entries whose delegation
88 // paths are now resolvable.
89 // The same applies to the non-delegation path if we try to verify without the
90 // full _settings tree.
91 // Unverified,
92}
93
94/// BackendImpl trait abstracting the underlying storage mechanism for Eidetica entries.
95///
96/// This trait defines the essential operations required for storing, retrieving,
97/// and querying entries and their relationships within databases and stores.
98/// Implementations of this trait handle the specifics of how data is persisted
99/// (e.g., in memory, on disk, in a remote database).
100///
101/// Much of the performance-critical logic, particularly concerning tree traversal
102/// and tip calculation, resides within `BackendImpl` implementations, as the optimal
103/// approach often depends heavily on the underlying storage characteristics.
104///
105/// All backend implementations must be `Send` and `Sync` to allow sharing across threads,
106/// and implement `Any` to allow for downcasting if needed.
107///
108/// Instance wraps BackendImpl in a `Backend` struct that provides additional coordination
109/// and will enable future development.
110///
111/// ## Verification Status
112///
113/// The backend stores a verification status for each entry, indicating whether
114/// the entry has been authenticated by the higher-level authentication system.
115/// The backend itself does not perform verification - it only stores the status
116/// set by the calling code (typically Database/Transaction implementations).
117#[async_trait]
118pub trait BackendImpl: Send + Sync + Any {
119 /// Retrieves an entry by its unique content-addressable ID.
120 ///
121 /// # Arguments
122 /// * `id` - The ID of the entry to retrieve.
123 ///
124 /// # Returns
125 /// A `Result` containing the `Entry` if found, or an `Error::NotFound` otherwise.
126 /// Returns an owned copy to support concurrent access with internal synchronization.
127 async fn get(&self, id: &ID) -> Result<Entry>;
128
129 /// Gets the verification status of an entry.
130 ///
131 /// # Arguments
132 /// * `id` - The ID of the entry to check.
133 ///
134 /// # Returns
135 /// A `Result` containing the `VerificationStatus` if the entry exists, or an `Error::NotFound` otherwise.
136 async fn get_verification_status(&self, id: &ID) -> Result<VerificationStatus>;
137
138 /// Stores an entry in the database with the specified verification status.
139 ///
140 /// If an entry with the same ID already exists, it may be overwritten,
141 /// although the content-addressable nature means the content will be identical.
142 /// The verification status will be updated to the provided value.
143 ///
144 /// # Arguments
145 /// * `verification_status` - The verification status to assign to this entry
146 /// * `entry` - The `Entry` to store.
147 ///
148 /// # Returns
149 /// A `Result` indicating success or an error during storage.
150 async fn put(&self, verification_status: VerificationStatus, entry: Entry) -> Result<()>;
151
152 /// Stores an entry with verified status (convenience method for local entries).
153 ///
154 /// This is a convenience method that calls `put(VerificationStatus::Verified, entry)`.
155 /// Use this for locally created and signed entries.
156 ///
157 /// # Arguments
158 /// * `entry` - The `Entry` to store as verified.
159 ///
160 /// # Returns
161 /// A `Result` indicating success or an error during storage.
162 async fn put_verified(&self, entry: Entry) -> Result<()> {
163 self.put(VerificationStatus::Verified, entry).await
164 }
165
166 /// Stores an entry with failed verification status (convenience method for sync scenarios).
167 ///
168 /// This is a convenience method that calls `put(VerificationStatus::Failed, entry)`.
169 /// Use this for entries that failed verification or are pending verification from sync.
170 /// In the future, this may use a dedicated `Unverified` status.
171 ///
172 /// # Arguments
173 /// * `entry` - The `Entry` to store as failed/unverified.
174 ///
175 /// # Returns
176 /// A `Result` indicating success or an error during storage.
177 async fn put_unverified(&self, entry: Entry) -> Result<()> {
178 self.put(VerificationStatus::Failed, entry).await
179 }
180
181 /// Updates the verification status of an existing entry.
182 ///
183 /// This allows the authentication system to mark entries as verified or failed
184 /// after they have been stored. Useful for batch verification operations.
185 ///
186 /// # Arguments
187 /// * `id` - The ID of the entry to update
188 /// * `verification_status` - The new verification status
189 ///
190 /// # Returns
191 /// A `Result` indicating success or `Error::NotFound` if the entry doesn't exist.
192 async fn update_verification_status(
193 &self,
194 id: &ID,
195 verification_status: VerificationStatus,
196 ) -> Result<()>;
197
198 /// Gets all entries with a specific verification status.
199 ///
200 /// This is useful for finding unverified entries that need authentication
201 /// or for security audits.
202 ///
203 /// # Arguments
204 /// * `status` - The verification status to filter by
205 ///
206 /// # Returns
207 /// A `Result` containing a vector of entry IDs with the specified status.
208 async fn get_entries_by_verification_status(
209 &self,
210 status: VerificationStatus,
211 ) -> Result<Vec<ID>>;
212
213 /// Retrieves the IDs of the tip entries for a given tree.
214 ///
215 /// Tips are defined as the set of entries within the specified tree
216 /// that have no children *within that same tree*. An entry is considered
217 /// a child of another if it lists the other entry in its `parents` list.
218 ///
219 /// # Arguments
220 /// * `tree` - The root ID of the tree for which to find tips.
221 ///
222 /// # Returns
223 /// A `Result` containing a vector of tip entry IDs or an error.
224 async fn get_tips(&self, tree: &ID) -> Result<Vec<ID>>;
225
226 /// Retrieves the IDs of the tip entries for a specific store within a given tree.
227 ///
228 /// Store tips are defined as the set of entries within the specified store
229 /// that have no children *within that same store*. An entry is considered
230 /// a child of another within a store if it lists the other entry in its
231 /// `store_parents` list for that specific store name.
232 ///
233 /// # Arguments
234 /// * `tree` - The root ID of the parent tree.
235 /// * `store` - The name of the store for which to find tips.
236 ///
237 /// # Returns
238 /// A `Result` containing a vector of tip entry IDs for the store or an error.
239 async fn get_store_tips(&self, tree: &ID, store: &str) -> Result<Vec<ID>>;
240
241 /// Gets the store tips that exist up to a specific set of main tree entries.
242 ///
243 /// This method finds all store entries that are reachable from the specified
244 /// main tree entries, then filters to find which of those are tips within the store.
245 ///
246 /// # Arguments
247 /// * `tree` - The root ID of the parent tree.
248 /// * `store` - The name of the store for which to find tips.
249 /// * `main_entries` - The main tree entry IDs to use as the boundary.
250 ///
251 /// # Returns
252 /// A `Result` containing a vector of store tip entry IDs up to the main entries.
253 async fn get_store_tips_up_to_entries(
254 &self,
255 tree: &ID,
256 store: &str,
257 main_entries: &[ID],
258 ) -> Result<Vec<ID>>;
259
260 /// Retrieves the IDs of all top-level root entries stored in the backend.
261 ///
262 /// Top-level roots are entries that are themselves roots of a tree
263 /// (i.e., `entry.is_root()` is true) and are not part of a larger tree structure
264 /// tracked by the backend (conceptually, their `tree.root` field is empty or refers to themselves,
265 /// though the implementation detail might vary). These represent the starting points
266 /// of distinct trees managed by the database.
267 ///
268 /// # Returns
269 /// A `Result` containing a vector of top-level root entry IDs or an error.
270 async fn all_roots(&self) -> Result<Vec<ID>>;
271
272 /// Finds the merge base (common dominator) of the given entry IDs within a store.
273 ///
274 /// The merge base is the lowest ancestor that ALL paths from ALL entries must pass through.
275 /// This is different from the traditional LCA - if there are parallel paths that bypass
276 /// a common ancestor, that ancestor is not the merge base. This is used to determine
277 /// optimal computation boundaries for CRDT state calculation.
278 ///
279 /// # Arguments
280 /// * `tree` - The root ID of the tree
281 /// * `store` - The name of the store context
282 /// * `entry_ids` - The entry IDs to find the merge base for
283 ///
284 /// # Returns
285 /// A `Result` containing the merge base entry ID, or an error if no common ancestor exists
286 async fn find_merge_base(&self, tree: &ID, store: &str, entry_ids: &[ID]) -> Result<ID>;
287
288 /// Collects all entries from the tree root down to the target entry within a store.
289 ///
290 /// This method performs a complete traversal from the tree root to the target entry,
291 /// collecting all entries that are ancestors of the target within the specified store.
292 /// The result includes the tree root and the target entry itself.
293 ///
294 /// # Arguments
295 /// * `tree` - The root ID of the tree
296 /// * `store` - The name of the store context
297 /// * `target_entry` - The target entry to collect ancestors for
298 ///
299 /// # Returns
300 /// A `Result` containing a vector of entry IDs from root to target, sorted by height
301 async fn collect_root_to_target(
302 &self,
303 tree: &ID,
304 store: &str,
305 target_entry: &ID,
306 ) -> Result<Vec<ID>>;
307
308 /// Returns a reference to the backend instance as a dynamic `Any` type.
309 ///
310 /// This allows for downcasting to a concrete backend implementation if necessary,
311 /// enabling access to implementation-specific methods. Use with caution.
312 fn as_any(&self) -> &dyn Any;
313
314 /// Retrieves all entries belonging to a specific tree, sorted topologically.
315 ///
316 /// The entries are sorted primarily by their height (distance from the root)
317 /// and secondarily by their ID to ensure a consistent, deterministic order suitable
318 /// for reconstructing the tree's history.
319 ///
320 /// **Note:** This potentially loads the entire history of the tree. Use cautiously,
321 /// especially with large trees, as it can be memory-intensive.
322 ///
323 /// # Arguments
324 /// * `tree` - The root ID of the tree to retrieve.
325 ///
326 /// # Returns
327 /// A `Result` containing a vector of all `Entry` objects in the tree,
328 /// sorted topologically, or an error.
329 async fn get_tree(&self, tree: &ID) -> Result<Vec<Entry>>;
330
331 /// Retrieves all entries belonging to a specific store within a tree, sorted topologically.
332 ///
333 /// Similar to `get_tree`, but limited to entries that are part of the specified store.
334 /// The entries are sorted primarily by their height within the store (distance
335 /// from the store's initial entry/entries) and secondarily by their ID.
336 ///
337 /// **Note:** This potentially loads the entire history of the store. Use with caution.
338 ///
339 /// # Arguments
340 /// * `tree` - The root ID of the parent tree.
341 /// * `store` - The name of the store to retrieve.
342 ///
343 /// # Returns
344 /// A `Result` containing a vector of all `Entry` objects in the store,
345 /// sorted topologically according to their position within the store, or an error.
346 async fn get_store(&self, tree: &ID, store: &str) -> Result<Vec<Entry>>;
347
348 /// Retrieves all entries belonging to a specific tree up to the given tips, sorted topologically.
349 ///
350 /// Similar to `get_tree`, but only includes entries that are ancestors of the provided tips.
351 /// This allows reading from a specific state of the tree defined by those tips.
352 ///
353 /// # Arguments
354 /// * `tree` - The root ID of the tree to retrieve.
355 /// * `tips` - The tip IDs defining the state to read from.
356 ///
357 /// # Returns
358 /// A `Result` containing a vector of `Entry` objects in the tree up to the given tips,
359 /// sorted topologically, or an error.
360 ///
361 /// # Errors
362 /// - `EntryNotFound` if any tip doesn't exist locally
363 /// - `EntryNotInTree` if any tip belongs to a different tree
364 async fn get_tree_from_tips(&self, tree: &ID, tips: &[ID]) -> Result<Vec<Entry>>;
365
366 /// Retrieves all entries belonging to a specific store within a tree up to the given tips, sorted topologically.
367 ///
368 /// Similar to `get_subtree`, but only includes entries that are ancestors of the provided store tips.
369 /// This allows reading from a specific state of the store defined by those tips.
370 ///
371 /// # Arguments
372 /// * `tree` - The root ID of the parent tree.
373 /// * `store` - The name of the store to retrieve.
374 /// * `tips` - The tip IDs defining the state to read from.
375 ///
376 /// # Returns
377 /// A `Result` containing a vector of `Entry` objects in the store up to the given tips,
378 /// sorted topologically, or an error.
379 async fn get_store_from_tips(&self, tree: &ID, store: &str, tips: &[ID]) -> Result<Vec<Entry>>;
380
381 // === CRDT State Cache Methods ===
382 //
383 // These methods provide caching for computed CRDT state at specific
384 // entry+store combinations. This optimizes repeated computations
385 // of the same store state from the same set of tip entries.
386
387 /// Get cached CRDT state for a store at a specific entry.
388 ///
389 /// # Arguments
390 /// * `entry_id` - The entry ID where the state is cached
391 /// * `store` - The name of the store
392 ///
393 /// # Returns
394 /// A `Result` containing an `Option<String>`. Returns `None` if not cached.
395 async fn get_cached_crdt_state(&self, entry_id: &ID, store: &str) -> Result<Option<String>>;
396
397 /// Cache CRDT state for a store at a specific entry.
398 ///
399 /// # Arguments
400 /// * `entry_id` - The entry ID where the state should be cached
401 /// * `store` - The name of the store
402 /// * `state` - The serialized CRDT state to cache
403 ///
404 /// # Returns
405 /// A `Result` indicating success or an error during storage.
406 async fn cache_crdt_state(&self, entry_id: &ID, store: &str, state: String) -> Result<()>;
407
408 /// Clear all cached CRDT states.
409 ///
410 /// This is used when the CRDT computation algorithm changes and existing
411 /// cached states may have been computed incorrectly.
412 ///
413 /// # Returns
414 /// A `Result` indicating success or an error during the clear operation.
415 async fn clear_crdt_cache(&self) -> Result<()>;
416
417 /// Get the store parent IDs for a specific entry and store, sorted by height then ID.
418 ///
419 /// This method retrieves the parent entry IDs for a given entry in a specific store
420 /// context, sorted using the same deterministic ordering used throughout the system
421 /// (height ascending, then ID ascending for ties).
422 ///
423 /// # Arguments
424 /// * `tree_id` - The ID of the tree containing the entry
425 /// * `entry_id` - The ID of the entry to get parents for
426 /// * `store` - The name of the store context
427 ///
428 /// # Returns
429 /// A `Result` containing a `Vec<ID>` of parent entry IDs sorted by (height, ID).
430 /// Returns empty vec if the entry has no parents in the store.
431 async fn get_sorted_store_parents(
432 &self,
433 tree_id: &ID,
434 entry_id: &ID,
435 store: &str,
436 ) -> Result<Vec<ID>>;
437
438 /// Gets all entries between one entry and multiple target entries (exclusive of start, inclusive of targets).
439 ///
440 /// This function correctly handles diamond patterns by finding ALL entries that are
441 /// reachable from any of the to_ids by following parents back to from_id, not just single paths.
442 /// The results are deduplicated and sorted by height then ID for deterministic CRDT merge ordering.
443 ///
444 /// # Arguments
445 /// * `tree_id` - The ID of the tree containing the entries
446 /// * `store` - The name of the store context
447 /// * `from_id` - The starting entry ID (not included in result)
448 /// * `to_ids` - The target entry IDs (all included in result)
449 ///
450 /// # Returns
451 /// A `Result<Vec<ID>>` containing all entry IDs between from and any of the targets, deduplicated and sorted by height then ID
452 async fn get_path_from_to(
453 &self,
454 tree_id: &ID,
455 store: &str,
456 from_id: &ID,
457 to_ids: &[ID],
458 ) -> Result<Vec<ID>>;
459
460 // === Instance Metadata Methods ===
461 //
462 // These methods manage persistent instance-level state including the device key
463 // and system database IDs. The presence of metadata indicates an initialized instance.
464
465 /// Get the instance metadata.
466 ///
467 /// Returns `None` for a fresh/uninitialized backend, `Some(metadata)` for an
468 /// initialized instance. This is used during `Instance::open()` to determine
469 /// whether to create a new instance or load an existing one.
470 ///
471 /// # Returns
472 /// A `Result` containing `Option<InstanceMetadata>`:
473 /// - `Some(metadata)` if the instance has been initialized
474 /// - `None` if the backend is fresh/uninitialized
475 async fn get_instance_metadata(&self) -> Result<Option<InstanceMetadata>>;
476
477 /// Set the instance metadata.
478 ///
479 /// This is called during instance creation to persist the device key and
480 /// system database IDs. It may also be called when enabling sync to update
481 /// the `sync_db` field.
482 ///
483 /// # Arguments
484 /// * `metadata` - The instance metadata to persist
485 ///
486 /// # Returns
487 /// A `Result` indicating success or an error during storage.
488 async fn set_instance_metadata(&self, metadata: &InstanceMetadata) -> Result<()>;
489}