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

eidetica/database/
mod.rs

1//! Database module provides functionality for managing collections of related entries.
2//!
3//! A `Database` represents a hierarchical structure of entries, like a traditional database
4//! or a branch in a version control system. Each database has a root entry and maintains
5//! the history and relationships between entries. Database holds a weak reference to its
6//! parent Instance, accessing storage and coordination services through that handle.
7
8use rand::{Rng, RngCore, distributions::Alphanumeric};
9use serde_json;
10
11use crate::{
12    Error, Instance, Result, Transaction, WeakInstance,
13    auth::{
14        crypto::{PrivateKey, PublicKey},
15        errors::AuthError,
16        settings::AuthSettings,
17        types::{AuthKey, Permission, SigKey},
18        validation::AuthValidator,
19    },
20    backend::VerificationStatus,
21    constants::{ROOT, SETTINGS},
22    crdt::Doc,
23    entry::{Entry, ID},
24    instance::{WriteSource, backend::Backend, errors::InstanceError},
25    store::{SettingsStore, Store},
26};
27
28#[cfg(test)]
29mod tests;
30
31/// A signing key bound to its identity in a database's auth settings.
32///
33/// Pairs the cryptographic signing key with information about how to look up
34/// permissions in the database's auth configuration. The identity determines
35/// which entry in `_settings.auth` this key maps to.
36#[derive(Clone, Debug)]
37pub struct DatabaseKey {
38    signing_key: Box<PrivateKey>,
39    identity: SigKey,
40}
41
42impl DatabaseKey {
43    /// Identity = pubkey derived from signing key. Most common case.
44    pub fn new(signing_key: PrivateKey) -> Self {
45        let pubkey = signing_key.public_key();
46        Self {
47            signing_key: Box::new(signing_key),
48            identity: SigKey::from_pubkey(&pubkey),
49        }
50    }
51
52    /// Identity = explicit SigKey (name, global, delegation, etc.)
53    pub fn with_identity(signing_key: PrivateKey, identity: SigKey) -> Self {
54        Self {
55            signing_key: Box::new(signing_key),
56            identity,
57        }
58    }
59
60    /// Identity = global permission with actual pubkey embedded for verification.
61    pub fn global(signing_key: PrivateKey) -> Self {
62        let pubkey = signing_key.public_key();
63        Self {
64            signing_key: Box::new(signing_key),
65            identity: SigKey::global(&pubkey),
66        }
67    }
68
69    /// Identity = key name lookup.
70    pub fn with_name(signing_key: PrivateKey, name: impl Into<String>) -> Self {
71        Self {
72            signing_key: Box::new(signing_key),
73            identity: SigKey::from_name(name),
74        }
75    }
76
77    /// Get the signing key.
78    pub fn signing_key(&self) -> &PrivateKey {
79        &self.signing_key
80    }
81
82    /// Get the public key.
83    pub fn public_key(&self) -> PublicKey {
84        self.signing_key.public_key()
85    }
86
87    /// Get the identity used for auth settings lookup.
88    pub fn identity(&self) -> &SigKey {
89        &self.identity
90    }
91
92    /// Consume self and return the parts.
93    pub fn into_parts(self) -> (PrivateKey, SigKey) {
94        (*self.signing_key, self.identity)
95    }
96}
97
98/// Represents a collection of related entries, like a traditional database or a branch in a version control system.
99///
100/// Each `Database` is identified by the ID of its root `Entry` and manages the history of data
101/// associated with that root. It interacts with the underlying storage through the Instance handle.
102#[derive(Clone, Debug)]
103pub struct Database {
104    root: ID,
105    instance: WeakInstance,
106    /// Signing key bound to its auth identity for this database
107    key: Option<DatabaseKey>,
108}
109
110impl Database {
111    /// Creates a new `Database` instance with a user-provided signing key.
112    ///
113    /// This constructor creates a new database using a signing key that's already in memory
114    /// (e.g., from UserKeyManager), without requiring the key to be stored in the backend.
115    /// This is the preferred method for creating databases in a User context where keys
116    /// are managed separately from the backend.
117    ///
118    /// The created database will use a `DatabaseKey` for all subsequent operations,
119    /// meaning transactions will use the provided key directly rather than looking it up
120    /// from backend storage.
121    ///
122    /// # Auth Bootstrapping
123    ///
124    /// Auth is always bootstrapped with the signing key as `Admin(0)`. Passing auth
125    /// configuration in `initial_settings` is an error — additional keys must be added
126    /// via follow-up transactions after creation.
127    ///
128    /// # Arguments
129    /// * `instance` - Instance handle for storage and coordination
130    /// * `signing_key` - The signing key to use for the initial commit and subsequent operations.
131    ///   This key should already be decrypted and ready to use. The public key is derived
132    ///   automatically and used as the key identifier in auth settings.
133    /// * `initial_settings` - `Doc` CRDT containing the initial settings for the database.
134    ///   Use `Doc::new()` for an empty settings document.
135    ///
136    /// # Returns
137    /// A `Result` containing the new `Database` instance configured with a `DatabaseKey`.
138    ///
139    /// # Example
140    /// ```rust,no_run
141    /// # use eidetica::*;
142    /// # use eidetica::backend::database::InMemory;
143    /// # use eidetica::auth::crypto::generate_keypair;
144    /// # use eidetica::crdt::Doc;
145    /// # #[tokio::main]
146    /// # async fn main() -> Result<()> {
147    /// let instance = Instance::open(Box::new(InMemory::new())).await?;
148    /// let (signing_key, _public_key) = generate_keypair();
149    ///
150    /// let mut settings = Doc::new();
151    /// settings.set("name", "my_database");
152    ///
153    /// // Create database with user-managed key (no backend storage needed)
154    /// let database = Database::create(&instance, signing_key, settings).await?;
155    ///
156    /// // All transactions automatically use the provided key
157    /// let tx = database.new_transaction().await?;
158    /// # Ok(())
159    /// # }
160    /// ```
161    pub async fn create(
162        instance: &Instance,
163        signing_key: PrivateKey,
164        initial_settings: Doc,
165    ) -> Result<Self> {
166        let mut initial_settings = initial_settings;
167        let pubkey = signing_key.public_key();
168
169        // Reject preconfigured auth — Database::create owns auth bootstrapping entirely.
170        if initial_settings.get("auth").is_some() {
171            return Err(Error::Auth(AuthError::InvalidAuthConfiguration {
172                reason: "initial_settings must not contain auth configuration; \
173                         Database::create bootstraps auth with the signing key as Admin(0)"
174                    .to_string(),
175            }));
176        }
177
178        // Bootstrap auth with the signing key as Admin(0)
179        let mut auth_settings = AuthSettings::new();
180        auth_settings.add_key(&pubkey, AuthKey::active(None, Permission::Admin(0)))?;
181        initial_settings.set("auth", auth_settings.as_doc().clone());
182
183        // Create the initial root entry using a temporary Database and Transaction.
184        // This placeholder ID should not exist in the backend, so get_tips will be empty.
185        let bootstrap_placeholder_id = format!(
186            "bootstrap_root_{}",
187            rand::thread_rng()
188                .sample_iter(&Alphanumeric)
189                .take(10)
190                .map(char::from)
191                .collect::<String>()
192        );
193
194        // Create temporary database for bootstrap with DatabaseKey.
195        // This allows the bootstrap transaction to use the provided key directly.
196        let temp_database_for_bootstrap = Database {
197            root: bootstrap_placeholder_id.clone().into(),
198            instance: instance.downgrade(),
199            key: Some(DatabaseKey::new(signing_key.clone())),
200        };
201
202        // Create the transaction - it will use the provided key automatically
203        let txn = temp_database_for_bootstrap.new_transaction().await?;
204
205        // IMPORTANT: For the root entry, we need to set the database root to empty string
206        // so that is_root() returns true and all_roots() can find it
207        txn.set_entry_root("")?;
208
209        // Populate the SETTINGS and ROOT subtrees for the very first entry
210        txn.update_subtree(SETTINGS, &serde_json::to_string(&initial_settings)?)
211            .await?;
212        txn.update_subtree(ROOT, &serde_json::to_string(&"".to_string())?)
213            .await?; // Standard practice for root entry's _root
214
215        // Add entropy to the entry metadata to ensure unique database IDs even with identical settings
216        txn.set_metadata_entropy(rand::thread_rng().next_u64())?;
217
218        // Commit the initial entry
219        let new_root_id = txn.commit().await?;
220
221        // Now create the real database with the new_root_id and DatabaseKey
222        Ok(Self {
223            root: new_root_id,
224            instance: instance.downgrade(),
225            key: Some(DatabaseKey::new(signing_key)),
226        })
227    }
228
229    /// Opens a database for read-only access, bypassing authentication validation.
230    ///
231    /// # Internal Use Only
232    ///
233    /// This method bypasses authentication validation and is intended for internal
234    /// operations that require reading database state (loading settings, checking
235    /// permissions, resolving delegations, etc.).
236    ///
237    /// These operations should only be performed by the server/instance administrator,
238    /// but we don't verify that yet. Future versions may add admin permission checks.
239    ///
240    /// # Behavior
241    ///
242    /// - No authentication validation is performed
243    /// - The resulting database has no key source, so commits will fail
244    /// - Used internally for system operations that need read access
245    ///
246    /// # Arguments
247    /// * `id` - The `ID` of the root entry.
248    /// * `instance` - Instance handle for storage and coordination
249    ///
250    /// # Returns
251    /// A `Result` containing the new `Database` instance or an error.
252    pub(crate) fn open_unauthenticated(id: ID, instance: &Instance) -> Result<Self> {
253        // TODO: Audit all usages of this function
254        Ok(Self {
255            root: id,
256            instance: instance.downgrade(),
257            key: None,
258        })
259    }
260
261    /// Opens an existing `Database` with a `DatabaseKey`.
262    ///
263    /// This constructor opens an existing database by its root ID and configures it to use
264    /// a `DatabaseKey` for all subsequent operations. This is used in the User
265    /// context where keys are managed by UserKeyManager and already decrypted in memory.
266    ///
267    /// # Key Management
268    ///
269    /// This constructor uses **user-managed keys**:
270    /// - The key is provided directly (e.g., from UserKeyManager)
271    /// - Uses `DatabaseKey` for all subsequent operations
272    /// - No backend key storage needed
273    ///
274    /// Note: To **create** a new database with user-managed keys, use `create()`.
275    /// This method is for **opening existing** databases.
276    ///
277    /// To discover which SigKey to use for a given public key, use `Database::find_sigkeys()`.
278    ///
279    /// # Arguments
280    /// * `instance` - Instance handle for storage and coordination
281    /// * `root_id` - The root entry ID of the existing database to open
282    /// * `key` - A `DatabaseKey` combining signing key and auth identity
283    ///
284    /// # Returns
285    /// A `Result` containing the `Database` instance configured with the `DatabaseKey`
286    ///
287    /// # Example
288    /// ```rust,no_run
289    /// # use eidetica::*;
290    /// # use eidetica::database::DatabaseKey;
291    /// # use eidetica::backend::database::InMemory;
292    /// # use eidetica::auth::crypto::generate_keypair;
293    /// # #[tokio::main]
294    /// # async fn main() -> Result<()> {
295    /// # let instance = Instance::open(Box::new(InMemory::new())).await?;
296    /// # let (signing_key, _verifying_key) = generate_keypair();
297    /// # let root_id = "existing_database_root_id".into();
298    /// // Open database with pubkey identity (most common)
299    /// let key = DatabaseKey::new(signing_key);
300    /// let database = Database::open(instance, &root_id, key).await?;
301    ///
302    /// // All transactions automatically use the provided key
303    /// let tx = database.new_transaction().await?;
304    /// # Ok(())
305    /// # }
306    /// ```
307    pub async fn open(instance: Instance, root_id: &ID, key: DatabaseKey) -> Result<Self> {
308        let temp_db = Self::open_unauthenticated(root_id.clone(), &instance)?;
309        temp_db.validate_key(&key).await?;
310
311        Ok(Self {
312            root: root_id.clone(),
313            instance: instance.downgrade(),
314            key: Some(key),
315        })
316    }
317
318    /// Validate a `DatabaseKey` against this database's auth settings.
319    ///
320    /// Checks that:
321    /// 1. The signing key derives to the public key claimed by the identity
322    /// 2. The identity exists in the database's auth settings
323    ///
324    /// Returns the effective permission for the validated key.
325    ///
326    /// Used by `Database::open` (on an unauthenticated temp db) to fail fast
327    /// on invalid keys, and by `current_permission` to look up permissions.
328    async fn validate_key(&self, key: &DatabaseKey) -> Result<Permission> {
329        let settings_store = self.get_settings().await?;
330        let auth_settings = settings_store.auth_snapshot().await?;
331
332        // Derive actual pubkey from the signing key
333        let actual_pubkey = key.public_key();
334
335        match key.identity() {
336            SigKey::Direct(hint) if hint.is_global() => {
337                // Verify the embedded pubkey matches the actual signing key
338                if let Some(embedded_pubkey) = &hint.pubkey
339                    && *embedded_pubkey != actual_pubkey
340                {
341                    return Err(Error::Auth(AuthError::SigningKeyMismatch {
342                        reason: format!(
343                            "signing key derives pubkey '{actual_pubkey}' \
344                                 but global identity claims '{embedded_pubkey}'"
345                        ),
346                    }));
347                }
348                auth_settings.get_global_permission().ok_or_else(|| {
349                    Error::Auth(AuthError::InvalidAuthConfiguration {
350                        reason: "Global '*' permission not configured".to_string(),
351                    })
352                })
353            }
354            SigKey::Direct(hint) => match (&hint.pubkey, &hint.name) {
355                (Some(pubkey), _) => {
356                    // Verify the claimed pubkey matches the actual signing key
357                    if *pubkey != actual_pubkey {
358                        return Err(Error::Auth(AuthError::SigningKeyMismatch {
359                            reason: format!(
360                                "signing key derives pubkey '{actual_pubkey}' \
361                                 but identity claims '{pubkey}'"
362                            ),
363                        }));
364                    }
365                    let auth_key = auth_settings.get_key_by_pubkey(pubkey)?;
366                    Ok(*auth_key.permissions())
367                }
368                (_, Some(name)) => {
369                    let matches = auth_settings.find_keys_by_name(name);
370                    if matches.is_empty() {
371                        return Err(Error::Auth(AuthError::KeyNotFound {
372                            key_name: name.clone(),
373                        }));
374                    }
375                    // Find the named key whose pubkey matches our actual signing key
376                    let actual_pubkey_str = actual_pubkey.to_string();
377                    let (_, auth_key) = matches
378                        .iter()
379                        .find(|(pk, _)| *pk == actual_pubkey_str)
380                        .ok_or_else(|| {
381                            Error::Auth(AuthError::SigningKeyMismatch {
382                                reason: format!(
383                                    "signing key derives pubkey '{actual_pubkey}' \
384                                     but no key named '{name}' has that pubkey"
385                                ),
386                            })
387                        })?;
388                    Ok(*auth_key.permissions())
389                }
390                _ => Err(Error::Auth(AuthError::InvalidAuthConfiguration {
391                    reason: "DatabaseKey has empty identity hint".to_string(),
392                })),
393            },
394            SigKey::Delegation { .. } => {
395                // Resolve delegation path through AuthValidator
396                let instance = self.instance()?;
397                let mut validator = AuthValidator::new();
398                let resolved_auths = validator
399                    .resolve_sig_key(key.identity(), &auth_settings, Some(&instance))
400                    .await
401                    .map_err(|e| {
402                        Error::Auth(AuthError::InvalidAuthConfiguration {
403                            reason: format!("Delegation resolution failed: {e}"),
404                        })
405                    })?;
406
407                // Find a resolved auth whose pubkey matches our signing key
408                resolved_auths
409                    .into_iter()
410                    .find(|ra| ra.public_key == actual_pubkey)
411                    .map(|ra| ra.effective_permission)
412                    .ok_or_else(|| {
413                        Error::Auth(AuthError::SigningKeyMismatch {
414                            reason: format!(
415                                "signing key derives pubkey '{actual_pubkey}' \
416                                 but no resolved delegation key matches"
417                            ),
418                        })
419                    })
420            }
421        }
422    }
423
424    /// Find all SigKeys that a public key can use to access a database.
425    ///
426    /// This static helper method loads a database's authentication settings and returns
427    /// all possible SigKeys that can be used with the given public key. This is useful for
428    /// discovering authentication options before opening a database.
429    ///
430    /// Returns all matching SigKeys including:
431    /// - Specific key names where the pubkey matches
432    /// - Global permission if available
433    /// - Single-hop delegation paths (pubkey found in a directly delegated tree)
434    ///
435    /// The results are **sorted by permission level, highest first**, making it easy to
436    /// select the most privileged access available.
437    ///
438    /// # Arguments
439    /// * `instance` - Instance handle for storage and coordination
440    /// * `root_id` - Root entry ID of the database to check
441    /// * `pubkey` - Public key string (e.g., "Ed25519:abc123...") to look up
442    ///
443    /// # Returns
444    /// A vector of (SigKey, Permission) tuples, sorted by permission (highest first).
445    /// Returns empty vector if no valid access methods are found.
446    ///
447    /// # Errors
448    /// Returns an error if:
449    /// - Database cannot be loaded
450    /// - Auth settings cannot be parsed
451    ///
452    /// # Example
453    /// ```rust,no_run
454    /// # use eidetica::*;
455    /// # use eidetica::database::DatabaseKey;
456    /// # use eidetica::backend::database::InMemory;
457    /// # use eidetica::auth::crypto::generate_keypair;
458    /// # use eidetica::auth::types::SigKey;
459    /// # #[tokio::main]
460    /// # async fn main() -> Result<()> {
461    /// # let instance = Instance::open(Box::new(InMemory::new())).await?;
462    /// # let (signing_key, pubkey) = generate_keypair();
463    /// # let root_id = "database_root_id".into();
464    /// // Find all SigKeys this pubkey can use (sorted highest permission first)
465    /// let sigkeys = Database::find_sigkeys(&instance, &root_id, &pubkey).await?;
466    ///
467    /// // Use the first available SigKey (highest permission)
468    /// if let Some((sigkey, _permission)) = sigkeys.first() {
469    ///     let key = DatabaseKey::with_identity(signing_key, sigkey.clone());
470    ///     let database = Database::open(instance, &root_id, key).await?;
471    /// }
472    /// # Ok(())
473    /// # }
474    /// ```
475    pub async fn find_sigkeys(
476        instance: &Instance,
477        root_id: &ID,
478        pubkey: &PublicKey,
479    ) -> Result<Vec<(SigKey, Permission)>> {
480        use crate::auth::{permission::clamp_permission, types::DelegationStep};
481
482        // Create temporary database to load settings (no key source needed for reading)
483        let temp_db = Self::open_unauthenticated(root_id.clone(), instance)?;
484
485        // Load auth settings
486        let settings_store = temp_db.get_settings().await?;
487        let auth_settings = settings_store.auth_snapshot().await?;
488
489        // Find direct SigKeys for this pubkey
490        let mut results = auth_settings.find_all_sigkeys_for_pubkey(pubkey);
491
492        // Scan single-hop delegation paths
493        // FIXME: deep nested delegations can't use this
494        if let Ok(delegated_trees) = auth_settings.get_all_delegated_trees() {
495            for (delegated_root_id, delegated_tree_ref) in &delegated_trees {
496                // Load the delegated tree's auth settings
497                let delegated_db =
498                    match Self::open_unauthenticated(delegated_root_id.clone(), instance) {
499                        Ok(db) => db,
500                        Err(_) => continue,
501                    };
502                let delegated_settings = match delegated_db.get_settings().await {
503                    Ok(s) => s,
504                    Err(_) => continue,
505                };
506                let delegated_auth = match delegated_settings.auth_snapshot().await {
507                    Ok(a) => a,
508                    Err(_) => continue,
509                };
510
511                // Check if pubkey exists in the delegated tree
512                let delegated_sigkeys = delegated_auth.find_all_sigkeys_for_pubkey(pubkey);
513                if delegated_sigkeys.is_empty() {
514                    continue;
515                }
516
517                // Get current tips for the delegated tree
518                let tips = match instance.backend().get_tips(delegated_root_id).await {
519                    Ok(t) => t,
520                    Err(_) => continue,
521                };
522
523                // For each matching key in the delegated tree, construct a delegation SigKey
524                for (delegated_sk, delegated_perm) in delegated_sigkeys {
525                    // Clamp the delegated permission through the bounds
526                    let effective_perm =
527                        clamp_permission(delegated_perm, &delegated_tree_ref.permission_bounds);
528
529                    // Construct the delegation SigKey using the hint from the direct key
530                    let delegation_sigkey = SigKey::Delegation {
531                        path: vec![DelegationStep {
532                            tree: delegated_root_id.to_string(),
533                            tips: tips.clone(),
534                        }],
535                        hint: delegated_sk.hint().clone(),
536                    };
537
538                    results.push((delegation_sigkey, effective_perm));
539                }
540            }
541        }
542
543        // Sort by permission, highest first
544        results.sort_by(|a, b| b.1.cmp(&a.1));
545        Ok(results)
546    }
547
548    /// Get the auth identity for this database's configured key.
549    pub fn auth_identity(&self) -> Option<&SigKey> {
550        self.key.as_ref().map(|k| &k.identity)
551    }
552
553    /// Register an Instance-wide callback to be invoked when entries are written locally to this database.
554    ///
555    /// Local writes are those originating from transaction commits in the current Instance.
556    /// The callback receives the entry, database, and instance as parameters, providing
557    /// full context for any coordination or side effects needed.
558    ///
559    /// **Important:** This callback is registered at the Instance level and will fire for all local
560    /// writes to the database tree (identified by root ID), regardless of which Database handle
561    /// performed the write. Multiple Database handles pointing to the same root ID share the same
562    /// set of callbacks.
563    ///
564    /// # Arguments
565    /// * `callback` - Function to invoke on local writes to this database tree
566    ///
567    /// # Returns
568    /// A Result indicating success or failure
569    ///
570    /// # Example
571    /// ```rust,no_run
572    /// # use eidetica::*;
573    /// # use eidetica::crdt::Doc;
574    /// # use eidetica::backend::database::InMemory;
575    /// # use eidetica::auth::crypto::PrivateKey;
576    /// # #[tokio::main]
577    /// # async fn main() -> Result<()> {
578    /// let instance = Instance::open(Box::new(InMemory::new())).await?;
579    /// # let settings = eidetica::crdt::Doc::new();
580    /// # let signing_key = PrivateKey::generate();
581    /// # let database = Database::create(&instance, signing_key, Doc::new()).await?;
582    ///
583    /// database.on_local_write(|entry, db, _instance| {
584    ///     let entry_id = entry.id().clone();
585    ///     let db_id = db.root_id().clone();
586    ///     Box::pin(async move {
587    ///         println!("Entry {} written to database {}", entry_id, db_id);
588    ///         Ok(())
589    ///     })
590    /// })?;
591    /// # Ok(())
592    /// # }
593    /// ```
594    pub fn on_local_write<F, Fut>(&self, callback: F) -> Result<()>
595    where
596        F: for<'a> Fn(&'a Entry, &'a Database, &'a Instance) -> Fut
597            + Send
598            + std::marker::Sync
599            + 'static,
600        Fut: std::future::Future<Output = Result<()>> + Send + 'static,
601    {
602        let instance = self.instance()?;
603        instance.register_write_callback(WriteSource::Local, self.root_id().clone(), callback)
604    }
605
606    /// Register an Instance-wide callback to be invoked when entries are written remotely to this database.
607    ///
608    /// Remote writes are those originating from sync or replication from other nodes.
609    /// The callback receives the entry, database, and instance as parameters.
610    ///
611    /// **Important:** This callback is registered at the Instance level and will fire for all remote
612    /// writes to the database tree (identified by root ID), regardless of which Database handle
613    /// registered the callback. Multiple Database handles pointing to the same root ID share the same
614    /// set of callbacks.
615    ///
616    /// # Arguments
617    /// * `callback` - Function to invoke on remote writes to this database tree
618    ///
619    /// # Returns
620    /// A Result indicating success or failure
621    ///
622    /// # Example
623    /// ```rust,no_run
624    /// # use eidetica::*;
625    /// # use eidetica::crdt::Doc;
626    /// # use eidetica::backend::database::InMemory;
627    /// # use eidetica::auth::crypto::PrivateKey;
628    /// # #[tokio::main]
629    /// # async fn main() -> Result<()> {
630    /// let instance = Instance::open(Box::new(InMemory::new())).await?;
631    /// # let settings = eidetica::crdt::Doc::new();
632    /// # let signing_key = PrivateKey::generate();
633    /// # let database = Database::create(&instance, signing_key, Doc::new()).await?;
634    ///
635    /// database.on_remote_write(|entry, db, _instance| {
636    ///     let entry_id = entry.id().clone();
637    ///     let db_id = db.root_id().clone();
638    ///     Box::pin(async move {
639    ///         println!("Remote entry {} synced to database {}", entry_id, db_id);
640    ///         Ok(())
641    ///     })
642    /// })?;
643    /// # Ok(())
644    /// # }
645    /// ```
646    pub fn on_remote_write<F, Fut>(&self, callback: F) -> Result<()>
647    where
648        F: for<'a> Fn(&'a Entry, &'a Database, &'a Instance) -> Fut
649            + Send
650            + std::marker::Sync
651            + 'static,
652        Fut: std::future::Future<Output = Result<()>> + Send + 'static,
653    {
654        let instance = self.instance()?;
655        instance.register_write_callback(WriteSource::Remote, self.root_id().clone(), callback)
656    }
657
658    /// Get the ID of the root entry
659    pub fn root_id(&self) -> &ID {
660        &self.root
661    }
662
663    /// Upgrade the weak instance reference to a strong reference.
664    ///
665    /// # Returns
666    /// A `Result` containing the Instance or an error if the Instance has been dropped.
667    pub(crate) fn instance(&self) -> Result<Instance> {
668        self.instance
669            .upgrade()
670            .ok_or_else(|| Error::Instance(InstanceError::InstanceDropped))
671    }
672
673    /// Get a reference to the backend
674    pub fn backend(&self) -> Result<Backend> {
675        Ok(self.instance()?.backend().clone())
676    }
677
678    /// Retrieve the root entry from the backend
679    pub async fn get_root(&self) -> Result<Entry> {
680        let instance = self.instance()?;
681        instance.get(&self.root).await
682    }
683
684    /// Get a read-only settings store for the database.
685    ///
686    /// Returns a SettingsStore that provides access to the database's settings.
687    /// Since this creates an internal transaction that is never committed, any
688    /// modifications made through the returned store will not persist.
689    ///
690    /// For making persistent changes to settings, create a transaction and use
691    /// `Transaction::get_settings()` instead.
692    ///
693    /// # Returns
694    /// A `Result` containing the `SettingsStore` for settings or an error.
695    ///
696    /// # Example
697    /// ```rust,no_run
698    /// # use eidetica::Database;
699    /// # async fn example(database: Database) -> eidetica::Result<()> {
700    /// // Read-only access
701    /// let settings = database.get_settings().await?;
702    /// let name = settings.get_name().await?;
703    ///
704    /// // For modifications, use a transaction:
705    /// let txn = database.new_transaction().await?;
706    /// let settings = txn.get_settings()?;
707    /// settings.set_name("new_name").await?;
708    /// txn.commit().await?;
709    /// # Ok(())
710    /// # }
711    /// ```
712    pub async fn get_settings(&self) -> Result<SettingsStore> {
713        let txn = self.new_transaction().await?;
714        txn.get_settings()
715    }
716
717    /// Get the name of the database from its settings store
718    pub async fn get_name(&self) -> Result<String> {
719        let settings = self.get_settings().await?;
720        settings.get_name().await
721    }
722
723    /// Create a new atomic transaction on this database
724    ///
725    /// This creates a new atomic transaction containing a new Entry.
726    /// The atomic transaction will be initialized with the current state of the database.
727    /// If a default authentication key is set, the transaction will use it for signing.
728    ///
729    /// # Returns
730    /// A `Result<Transaction>` containing the new atomic transaction
731    pub async fn new_transaction(&self) -> Result<Transaction> {
732        let tips = self.get_tips().await?;
733        self.new_transaction_with_tips(&tips).await
734    }
735
736    /// Create a new atomic transaction on this database with specific parent tips
737    ///
738    /// This creates a new atomic transaction that will have the specified entries as parents
739    /// instead of using the current database tips. This allows creating complex DAG structures
740    /// like diamond patterns for testing and advanced use cases.
741    ///
742    /// # Arguments
743    /// * `tips` - The specific parent tips to use for this transaction
744    ///
745    /// # Returns
746    /// A `Result<Transaction>` containing the new atomic transaction
747    pub async fn new_transaction_with_tips(&self, tips: impl AsRef<[ID]>) -> Result<Transaction> {
748        let mut txn = Transaction::new_with_tips(self, tips.as_ref()).await?;
749
750        // Set provided signing key from DatabaseKey
751        if let Some(key) = &self.key {
752            txn.set_provided_key(*key.signing_key.clone(), key.identity.clone());
753        }
754
755        Ok(txn)
756    }
757
758    /// Insert an entry into the database without modifying it.
759    /// This is primarily for testing purposes or when you need full control over the entry.
760    /// Note: This method assumes the entry is already properly signed and verified.
761    pub async fn insert_raw(&self, entry: Entry) -> Result<ID> {
762        let instance = self.instance()?;
763        let id = entry.id();
764
765        instance.put(VerificationStatus::Verified, entry).await?;
766
767        Ok(id)
768    }
769
770    /// Get a Store type that will handle accesses to the Store
771    /// This will return a Store initialized to point at the current state of the database.
772    ///
773    /// The returned store should NOT be used to modify the database, as it intentionally does not
774    /// expose the Transaction. Since the Transaction is never committed, it does not have any
775    /// effect on the database.
776    pub async fn get_store_viewer<T>(&self, name: impl Into<String>) -> Result<T>
777    where
778        T: Store,
779    {
780        let txn = self.new_transaction().await?;
781        T::new(&txn, name.into()).await
782    }
783
784    /// Get the current tips (leaf entries) of the main database branch.
785    ///
786    /// Tips represent the latest entries in the database's main history, forming the heads of the DAG.
787    ///
788    /// # Returns
789    /// A `Result` containing a vector of `ID`s for the tip entries or an error.
790    pub async fn get_tips(&self) -> Result<Vec<ID>> {
791        let instance = self.instance()?;
792        instance.get_tips(&self.root).await
793    }
794
795    /// Get the full `Entry` objects for the current tips of the main database branch.
796    ///
797    /// # Returns
798    /// A `Result` containing a vector of the tip `Entry` objects or an error.
799    pub async fn get_tip_entries(&self) -> Result<Vec<Entry>> {
800        let instance = self.instance()?;
801        let tips = instance.get_tips(&self.root).await?;
802        let mut entries = Vec::new();
803        for id in &tips {
804            entries.push(instance.get(id).await?);
805        }
806        Ok(entries)
807    }
808
809    /// Get a single entry by ID from this database.
810    ///
811    /// This is the primary method for retrieving entries after commit operations.
812    /// It provides safe, high-level access to entry data without exposing backend details.
813    ///
814    /// The method verifies that the entry belongs to this database by checking its root ID.
815    /// If the entry exists but belongs to a different database, an error is returned.
816    ///
817    /// # Arguments
818    /// * `entry_id` - The ID of the entry to retrieve (accepts anything that converts to ID/String)
819    ///
820    /// # Returns
821    /// A `Result` containing the `Entry` or an error if not found or not part of this database
822    ///
823    /// # Example
824    /// ```rust,no_run
825    /// # use eidetica::*;
826    /// # use eidetica::Instance;
827    /// # use eidetica::backend::database::InMemory;
828    /// # use eidetica::crdt::Doc;
829    /// # #[tokio::main]
830    /// # async fn main() -> Result<()> {
831    /// # let backend = Box::new(InMemory::new());
832    /// # let instance = Instance::open(backend).await?;
833    /// # instance.create_user("test", None).await?;
834    /// # let mut user = instance.login_user("test", None).await?;
835    /// # let key_id = user.add_private_key(None).await?;
836    /// # let tree = user.create_database(Doc::new(), &key_id).await?;
837    /// # let txn = tree.new_transaction().await?;
838    /// let entry_id = txn.commit().await?;
839    /// let entry = tree.get_entry(&entry_id).await?;           // Using &String
840    /// let entry = tree.get_entry("some_entry_id").await?;     // Using &str
841    /// let entry = tree.get_entry(entry_id.clone()).await?;    // Using String
842    /// println!("Entry signature: {:?}", entry.sig);
843    /// # Ok(())
844    /// # }
845    /// ```
846    pub async fn get_entry<I: Into<ID>>(&self, entry_id: I) -> Result<Entry> {
847        let instance = self.instance()?;
848        let id = entry_id.into();
849        let entry = instance.get(&id).await?;
850
851        // Check if the entry belongs to this database
852        if !entry.in_tree(&self.root) {
853            return Err(InstanceError::EntryNotInDatabase {
854                entry_id: id,
855                database_id: self.root.clone(),
856            }
857            .into());
858        }
859
860        Ok(entry)
861    }
862
863    /// Get multiple entries by ID efficiently.
864    ///
865    /// This method retrieves multiple entries more efficiently than multiple `get_entry()` calls
866    /// by minimizing conversion overhead and pre-allocating the result vector.
867    ///
868    /// The method verifies that all entries belong to this database by checking their root IDs.
869    /// If any entry exists but belongs to a different database, an error is returned.
870    ///
871    /// # Parameters
872    /// * `entry_ids` - An iterable of entry IDs to retrieve. Accepts any string or ID types
873    ///   that can be converted to `ID` (`&str`, `String`, `&ID`, etc.)
874    ///
875    /// # Returns
876    /// A `Result` containing a vector of `Entry` objects or an error if any entry is not found or not part of this database
877    ///
878    /// # Example
879    /// ```rust,no_run
880    /// # use eidetica::*;
881    /// # use eidetica::Instance;
882    /// # use eidetica::backend::database::InMemory;
883    /// # use eidetica::crdt::Doc;
884    /// # #[tokio::main]
885    /// # async fn main() -> Result<()> {
886    /// # let backend = Box::new(InMemory::new());
887    /// # let instance = Instance::open(backend).await?;
888    /// # instance.create_user("test", None).await?;
889    /// # let mut user = instance.login_user("test", None).await?;
890    /// # let key_id = user.add_private_key(None).await?;
891    /// # let tree = user.create_database(Doc::new(), &key_id).await?;
892    /// let entry_ids = vec!["id1", "id2", "id3"];
893    /// let entries = tree.get_entries(entry_ids).await?;
894    /// # Ok(())
895    /// # }
896    /// ```
897    pub async fn get_entries<I, T>(&self, entry_ids: I) -> Result<Vec<Entry>>
898    where
899        I: IntoIterator<Item = T>,
900        T: Into<ID>,
901    {
902        // Collect IDs first to minimize conversions and avoid repeat work in iterator chain
903        let ids: Vec<ID> = entry_ids.into_iter().map(Into::into).collect();
904        let instance = self.instance()?;
905        let mut entries = Vec::with_capacity(ids.len());
906
907        for id in ids {
908            let entry = instance.get(&id).await?;
909
910            // Check if the entry belongs to this database
911            if !entry.in_tree(&self.root) {
912                return Err(InstanceError::EntryNotInDatabase {
913                    entry_id: id,
914                    database_id: self.root.clone(),
915                }
916                .into());
917            }
918
919            entries.push(entry);
920        }
921
922        Ok(entries)
923    }
924
925    // === AUTHENTICATION HELPERS ===
926
927    /// Verify an entry's signature and authentication against the database's configuration that was valid at the time of entry creation.
928    ///
929    /// This method validates that:
930    /// 1. The entry belongs to this database
931    /// 2. The entry is properly signed with a key that was authorized in the database's authentication settings at the time the entry was created
932    /// 3. The signature is cryptographically valid
933    ///
934    /// The method uses the entry's metadata to determine which authentication settings were active when the entry was signed,
935    /// ensuring that entries remain valid even if keys are later revoked or settings change.
936    ///
937    /// # Arguments
938    /// * `entry_id` - The ID of the entry to verify (accepts anything that converts to ID/String)
939    ///
940    /// # Returns
941    /// A `Result` containing `true` if the entry is valid and properly authenticated, `false` if authentication fails
942    ///
943    /// # Errors
944    /// Returns an error if:
945    /// - The entry is not found
946    /// - The entry does not belong to this database
947    /// - The entry's metadata cannot be parsed
948    /// - The historical authentication settings cannot be retrieved
949    pub async fn verify_entry_signature<I: Into<ID>>(&self, entry_id: I) -> Result<bool> {
950        let entry = self.get_entry(entry_id).await?;
951
952        // Get the authentication settings that were valid at the time this entry was created
953        let historical_settings = self.get_historical_settings_for_entry(&entry).await?;
954
955        // Use the authentication validator with historical settings
956        let instance = self.instance()?;
957        let mut validator = AuthValidator::new();
958        validator
959            .validate_entry(&entry, &historical_settings, Some(&instance))
960            .await
961    }
962
963    /// Get the permission level for this database's configured signing key.
964    ///
965    /// Returns the effective permission for the key that was configured when opening
966    /// or creating this database. This uses the already-resolved identity stored in
967    /// the database's `DatabaseKey`, ensuring consistency with `Database::open`.
968    ///
969    /// # Returns
970    /// The effective Permission for the configured signing key.
971    ///
972    /// # Errors
973    /// Returns an error if:
974    /// - No signing key is configured (database opened without authentication)
975    /// - The database settings cannot be retrieved
976    /// - The key is no longer valid in the current auth settings
977    ///
978    /// # Example
979    /// ```rust,no_run
980    /// # use eidetica::*;
981    /// # use eidetica::crdt::Doc;
982    /// # use eidetica::backend::database::InMemory;
983    /// # use eidetica::auth::crypto::generate_keypair;
984    /// # #[tokio::main]
985    /// # async fn main() -> Result<()> {
986    /// # let instance = Instance::open(Box::new(InMemory::new())).await?;
987    /// # let (signing_key, _public_key) = generate_keypair();
988    /// # let database = Database::create(&instance, signing_key, Doc::new()).await?;
989    /// // Check if the current key has Admin permission
990    /// let permission = database.current_permission().await?;
991    /// if permission.can_admin() {
992    ///     println!("Current key has Admin permission!");
993    /// }
994    /// # Ok(())
995    /// # }
996    /// ```
997    pub async fn current_permission(&self) -> Result<Permission> {
998        let key = self
999            .key
1000            .as_ref()
1001            .ok_or(AuthError::InvalidAuthConfiguration {
1002                reason: "No signing key configured for this database".to_string(),
1003            })?;
1004        self.validate_key(key).await
1005    }
1006
1007    /// Get the authentication settings that were valid when a specific entry was created.
1008    ///
1009    /// This method examines the entry's metadata to find the settings tips that were active
1010    /// at the time of entry creation, then reconstructs the historical settings state.
1011    ///
1012    /// # Arguments
1013    /// * `entry` - The entry to get historical settings for
1014    ///
1015    /// # Returns
1016    /// A `Result` containing the historical authentication settings
1017    async fn get_historical_settings_for_entry(&self, _entry: &Entry) -> Result<AuthSettings> {
1018        // TODO: Implement full historical settings reconstruction from entry metadata
1019        // For now, use current settings for simplicity and backward compatibility
1020        //
1021        // The complete implementation would:
1022        // 1. Parse entry metadata to get settings tips active at entry creation time
1023        // 2. Reconstruct the CRDT state from those historical tips
1024        // 3. Validate against that historical state
1025        //
1026        // This ensures entries remain valid even if keys are later revoked,
1027        // but requires more complex CRDT state reconstruction logic.
1028
1029        let settings = self.get_settings().await?;
1030        settings.auth_snapshot().await
1031    }
1032
1033    // === DATABASE QUERIES ===
1034
1035    /// Get all entries in this database.
1036    ///
1037    /// ⚠️ **Warning**: This method loads all entries into memory. Use with caution on large databases.
1038    /// Consider using `get_tips()` or `get_tip_entries()` for more efficient access patterns.
1039    ///
1040    /// # Returns
1041    /// A `Result` containing a vector of all `Entry` objects in the database
1042    pub async fn get_all_entries(&self) -> Result<Vec<Entry>> {
1043        let instance = self.instance()?;
1044        instance.backend().get_tree(&self.root).await
1045    }
1046}