Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

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}