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

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 std::{future::Future, sync::Arc};
9
10use rand::{Rng, RngCore, distributions::Alphanumeric};
11use serde_json;
12
13#[cfg(all(unix, feature = "service"))]
14use crate::instance::backend::RemoteBackend;
15#[cfg(all(unix, feature = "service"))]
16use crate::service::client::RemoteConnection;
17use crate::{
18    Error, Instance, Result, Snapshot, Transaction, WeakInstance,
19    auth::{
20        crypto::{PrivateKey, PublicKey},
21        errors::AuthError,
22        settings::AuthSettings,
23        types::{AuthKey, Permission, SigKey},
24        validation::AuthValidator,
25    },
26    backend::VerificationStatus,
27    constants::{ROOT, SETTINGS},
28    crdt::{CRDT, Doc},
29    entry::{Entry, ID},
30    instance::{WriteCallback, WriteEvent, backend::Backend, errors::InstanceError},
31    store::{SettingsStore, Store},
32};
33
34#[cfg(test)]
35mod tests;
36
37tokio::task_local! {
38    /// Set while a `verify()`/validation pass is on the call stack.
39    ///
40    /// Verification reads the database (delegation resolution opens trees,
41    /// reads settings → tips), and the access-time auto-verify hook in
42    /// [`Database::snapshot`] would otherwise re-enter verification
43    /// unboundedly. While this is set, the hook is suppressed and reads
44    /// return raw (still `Failed`-filtered) tips.
45    static IN_VERIFY: bool;
46}
47
48fn auto_verify_suppressed() -> bool {
49    IN_VERIFY.try_with(|v| *v).unwrap_or(false)
50}
51
52/// Outcome of reconstructing the `_settings` state an entry pins.
53///
54/// An entry records, in its signed metadata, the `_settings` tips its
55/// signature must be validated against. We can only verify it if this node
56/// holds that full pinned `_settings` ancestor set.
57enum PinnedSettings {
58    /// The pinned `_settings` set is fully present; here is its auth config.
59    Complete(AuthSettings),
60    /// This node does not yet hold the full pinned `_settings` set, so the
61    /// entry cannot be verified yet (it stays `Unverified`).
62    Incomplete,
63}
64
65/// Summary of a [`Database::verify`] pass.
66#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
67pub struct VerifyReport {
68    /// Entries promoted `Unverified` → `Verified` this pass.
69    pub verified: usize,
70    /// Entries marked `Unverified` → `Failed` (definitively bad) this pass.
71    pub failed: usize,
72    /// Entries left `Unverified` (pinned `_settings` not yet held locally).
73    pub still_unverified: usize,
74}
75
76/// A signing key bound to its identity in a database's auth settings.
77///
78/// Pairs the cryptographic signing key with information about how to look up
79/// permissions in the database's auth configuration. The identity determines
80/// which entry in `_settings.auth` this key maps to.
81#[derive(Clone, Debug)]
82pub struct DatabaseKey {
83    signing_key: Box<PrivateKey>,
84    identity: SigKey,
85}
86
87impl DatabaseKey {
88    /// Identity = pubkey derived from signing key. Most common case.
89    pub fn new(signing_key: PrivateKey) -> Self {
90        let pubkey = signing_key.public_key();
91        Self {
92            signing_key: Box::new(signing_key),
93            identity: SigKey::from_pubkey(&pubkey),
94        }
95    }
96
97    /// Identity = explicit SigKey (name, global, delegation, etc.)
98    pub fn with_identity(signing_key: PrivateKey, identity: SigKey) -> Self {
99        Self {
100            signing_key: Box::new(signing_key),
101            identity,
102        }
103    }
104
105    /// Identity = global permission with actual pubkey embedded for verification.
106    pub fn global(signing_key: PrivateKey) -> Self {
107        let pubkey = signing_key.public_key();
108        Self {
109            signing_key: Box::new(signing_key),
110            identity: SigKey::global(&pubkey),
111        }
112    }
113
114    /// Identity = key name lookup.
115    pub fn with_name(signing_key: PrivateKey, name: impl Into<String>) -> Self {
116        Self {
117            signing_key: Box::new(signing_key),
118            identity: SigKey::from_name(name),
119        }
120    }
121
122    /// Get the signing key.
123    pub fn signing_key(&self) -> &PrivateKey {
124        &self.signing_key
125    }
126
127    /// Get the public key.
128    pub fn public_key(&self) -> PublicKey {
129        self.signing_key.public_key()
130    }
131
132    /// Get the identity used for auth settings lookup.
133    pub fn identity(&self) -> &SigKey {
134        &self.identity
135    }
136
137    /// Consume self and return the parts.
138    pub fn into_parts(self) -> (PrivateKey, SigKey) {
139        (*self.signing_key, self.identity)
140    }
141}
142
143impl From<PrivateKey> for DatabaseKey {
144    /// Convert a `PrivateKey` into a `DatabaseKey` with pubkey-derived identity.
145    ///
146    /// This is equivalent to [`DatabaseKey::new`] and covers the most common case
147    /// where the key's identity in auth settings is its own public key.
148    fn from(signing_key: PrivateKey) -> Self {
149        Self::new(signing_key)
150    }
151}
152
153/// Represents a collection of related entries, like a traditional database or a branch in a version control system.
154///
155/// Each `Database` is identified by the ID of its root `Entry` and manages the history of data
156/// associated with that root. It interacts with the underlying storage through the Instance handle.
157#[derive(Clone, Debug)]
158pub struct Database {
159    root: ID,
160    instance: WeakInstance,
161    /// Storage seam `Transaction`/`Store` reads flow through. On a local
162    /// instance this is a clone of the instance's own [`Backend`] (forwarding
163    /// to the backing engine); on a connected instance a per-handle
164    /// [`RemoteBackend`] bound to this database's acting identity. Derived from
165    /// `instance`/construction only — carrying it across
166    /// `with_key`/`allow_unverified` (`..self`) rebuilds is correct.
167    ops: Arc<dyn Backend>,
168    /// Signing key bound to its auth identity for this database
169    key: Option<DatabaseKey>,
170    /// When `false` (default), reads expose only the maximal all-`Verified`
171    /// prefix of the DAG (the "Verified frontier"). When `true`, reads also
172    /// include `Unverified` entries. `Failed` entries are dropped regardless.
173    /// Set via [`Database::allow_unverified`].
174    allow_unverified: bool,
175}
176
177impl Database {
178    /// Creates a new `Database` instance with a user-provided signing key.
179    ///
180    /// This constructor creates a new database using a signing key that's already in memory
181    /// (e.g., from UserKeyManager), without requiring the key to be stored in the backend.
182    /// This is the preferred method for creating databases in a User context where keys
183    /// are managed separately from the backend.
184    ///
185    /// The created database will use a `DatabaseKey` for all subsequent operations,
186    /// meaning transactions will use the provided key directly rather than looking it up
187    /// from backend storage.
188    ///
189    /// # Auth Bootstrapping
190    ///
191    /// Auth is always bootstrapped with the signing key as `Admin(0)`. Passing auth
192    /// configuration in `initial_settings` is an error — additional keys must be added
193    /// via follow-up transactions after creation.
194    ///
195    /// # Arguments
196    /// * `instance` - Instance handle for storage and coordination
197    /// * `signing_key` - The signing key to use for the initial commit and subsequent operations.
198    ///   This key should already be decrypted and ready to use. The public key is derived
199    ///   automatically and used as the key identifier in auth settings.
200    /// * `initial_settings` - `Doc` CRDT containing the initial settings for the database.
201    ///   Use `Doc::new()` for an empty settings document.
202    ///
203    /// # Returns
204    /// A `Result` containing the new `Database` instance configured with a `DatabaseKey`.
205    ///
206    /// # Example
207    /// ```rust,no_run
208    /// # use eidetica::*;
209    /// # use eidetica::backend::database::InMemory;
210    /// # use eidetica::auth::crypto::generate_keypair;
211    /// # use eidetica::crdt::Doc;
212    /// # #[tokio::main]
213    /// # async fn main() -> Result<()> {
214    /// let instance = Instance::open_backend(Box::new(InMemory::new())).await?;
215    /// let (signing_key, _public_key) = generate_keypair();
216    ///
217    /// let mut settings = Doc::new();
218    /// settings.set("name", "my_database");
219    ///
220    /// // Create database with user-managed key (no backend storage needed)
221    /// let database = Database::create(&instance, signing_key, settings).await?;
222    ///
223    /// // All transactions automatically use the provided key
224    /// let tx = database.new_transaction().await?;
225    /// # Ok(())
226    /// # }
227    /// ```
228    pub async fn create(
229        instance: &Instance,
230        signing_key: PrivateKey,
231        initial_settings: Doc,
232    ) -> Result<Self> {
233        Self::create_with_init(instance, signing_key, initial_settings, async |_| Ok(())).await
234    }
235
236    /// Creates a new database with an initialization callback that runs inside
237    /// the genesis transaction.
238    ///
239    /// This is the underlying constructor used by [`Self::create`] and by
240    /// `User::new_database()` (the builder API). The callback receives the
241    /// genesis transaction after `_settings` and `_root` have been staged but
242    /// before commit, allowing additional subtrees — stores, initial doc data,
243    /// records — to be written into the same entry that establishes the
244    /// database root.
245    ///
246    /// All writes performed in the callback become part of the single genesis
247    /// entry: one signed entry, one backend write, atomic. If the callback
248    /// returns an error, the transaction is dropped without committing and no
249    /// database is created.
250    ///
251    /// # Arguments
252    /// * `instance` - Instance handle for storage and coordination
253    /// * `signing_key` - The private key for this database (becomes Admin(0))
254    /// * `initial_settings` - Initial settings document (must not contain auth)
255    /// * `init` - Callback run against the genesis transaction before commit
256    pub async fn create_with_init<F>(
257        instance: &Instance,
258        signing_key: PrivateKey,
259        initial_settings: Doc,
260        init: F,
261    ) -> Result<Self>
262    where
263        F: AsyncFnOnce(&Transaction) -> Result<()>,
264    {
265        let mut initial_settings = initial_settings;
266        let pubkey = signing_key.public_key();
267
268        // Reject preconfigured auth — Database::create owns auth bootstrapping entirely.
269        if initial_settings.get("auth").is_some() {
270            return Err(Error::Auth(Box::new(AuthError::InvalidAuthConfiguration {
271                reason: "initial_settings must not contain auth configuration; \
272                         Database::create bootstraps auth with the signing key as Admin(0)"
273                    .to_string(),
274            })));
275        }
276
277        // Bootstrap auth with the signing key as Admin(0)
278        let mut auth_settings = AuthSettings::new();
279        auth_settings.add_key(&pubkey, AuthKey::active(None, Permission::Admin(0)))?;
280        initial_settings.set("auth", auth_settings.as_doc().clone());
281
282        // Create the initial root entry using a temporary Database and Transaction.
283        // This placeholder ID should not exist in the backend, so snapshot will be empty.
284        let bootstrap_placeholder_id = format!(
285            "bootstrap_root_{}",
286            rand::thread_rng()
287                .sample_iter(&Alphanumeric)
288                .take(10)
289                .map(char::from)
290                .collect::<String>()
291        );
292
293        // Create temporary database for bootstrap with DatabaseKey.
294        // This allows the bootstrap transaction to use the provided key directly.
295        let temp_database_for_bootstrap = Database {
296            root: ID::from_bytes(bootstrap_placeholder_id.as_bytes()),
297            instance: instance.downgrade(),
298            ops: instance.backend().clone(),
299            key: Some(DatabaseKey::new(signing_key.clone())),
300            allow_unverified: false,
301        };
302
303        // Create the transaction - it will use the provided key automatically
304        let txn = temp_database_for_bootstrap.new_transaction().await?;
305
306        // IMPORTANT: For the root entry, we need to set the database root to empty/default
307        // so that is_root() returns true and all_roots() can find it
308        txn.set_entry_root(ID::default())?;
309
310        // Populate the SETTINGS and ROOT subtrees for the very first entry
311        txn.update_subtree(SETTINGS, serde_json::to_vec(&initial_settings)?)
312            .await?;
313        txn.update_subtree(ROOT, serde_json::to_vec("")?).await?; // Standard practice for root entry's _root
314
315        // Add entropy to the entry metadata to ensure unique database IDs even with identical settings
316        txn.set_metadata_entropy(rand::thread_rng().next_u64())?;
317
318        // Lock system subtrees (`_settings`, `_root`, `_index`) for the
319        // duration of the init callback. The callback gets a `&Transaction`
320        // that rejects `_*` opens via `get_store` / `Store::open`, so callers
321        // can't accidentally clobber subtrees that `create_with_init` itself
322        // manages. Internal paths (Registry, Store::register's `_index`
323        // writes) bypass `get_store` and remain functional. The guard releases
324        // on drop, so the lock lifts on both early-return and panic-unwind.
325        {
326            let _lock = txn.lock_system_subtrees();
327            init(&txn).await?;
328        }
329
330        // Commit the initial entry
331        let new_root_id = txn.commit().await?;
332
333        // Construct the returned Database, wiring its `ops` to match the
334        // instance's flavour:
335        //
336        // - **Connected (remote) instance** — bind a `RemoteBackend` to the
337        //   *new database's* identity (the signing-key's pubkey, self-signed
338        //   as `Admin(0)` by the genesis). The connection's login pubkey is
339        //   the *caller's* (e.g. the registering admin), which is **not** a
340        //   member of the new tree's auth. If we cloned the instance's
341        //   session backend here, every read `Transaction::commit` performs
342        //   on this database would carry the connection's login identity, and
343        //   the server's per-tree gate would deny it. A per-database identity
344        //   makes all reads use the tree's own member key — but the server's
345        //   gate also requires that key to be in the connection's *session
346        //   keyset*, so we `register_session_key(signing_key)` first to do the
347        //   proof-of-possession handshake that adds it.
348        // - **Local instance** — clone the instance's backend, unchanged.
349        #[cfg(all(unix, feature = "service"))]
350        if let Some(conn) = instance.remote_connection() {
351            let pubkey_for_identity = signing_key.public_key();
352            conn.register_session_key(&signing_key).await?;
353            return Ok(Self {
354                root: new_root_id.clone(),
355                instance: instance.downgrade(),
356                ops: Arc::new(RemoteBackend::new(
357                    conn,
358                    Some(SigKey::from_pubkey(&pubkey_for_identity)),
359                )),
360                key: Some(DatabaseKey::new(signing_key)),
361                allow_unverified: false,
362            });
363        }
364
365        Ok(Self {
366            root: new_root_id,
367            instance: instance.downgrade(),
368            ops: instance.backend().clone(),
369            key: Some(DatabaseKey::new(signing_key)),
370            allow_unverified: false,
371        })
372    }
373
374    /// Opens an existing database by its root ID.
375    ///
376    /// Verifies the root entry exists in the backend, then returns a handle
377    /// for read-only access. To perform authenticated writes, chain
378    /// `.with_key(key)` after opening.
379    ///
380    /// # Arguments
381    /// * `instance` - Instance handle for storage and coordination
382    /// * `root_id` - The root entry ID of the database to open
383    ///
384    /// # Errors
385    /// Returns an error if the root entry does not exist in the backend.
386    ///
387    /// # Example
388    /// ```rust,no_run
389    /// # use eidetica::*;
390    /// # use eidetica::backend::database::InMemory;
391    /// # use eidetica::auth::crypto::generate_keypair;
392    /// # #[tokio::main]
393    /// # async fn main() -> Result<()> {
394    /// # let instance = Instance::open_backend(Box::new(InMemory::new())).await?;
395    /// # let (signing_key, _verifying_key) = generate_keypair();
396    /// # let root_id = ID::from_bytes(b"existing_database_root_id");
397    /// // Open database for reading
398    /// let db = Database::open(&instance, &root_id).await?;
399    ///
400    /// // Open database with a signing key for writes
401    /// let db = Database::open(&instance, &root_id).await?.with_key(signing_key);
402    /// let tx = db.new_transaction().await?;
403    /// # Ok(())
404    /// # }
405    /// ```
406    pub async fn open(instance: &Instance, root_id: &ID) -> Result<Self> {
407        // Verify the root entry exists. Surfaces "database doesn't exist"
408        // at open time instead of at first read/write.
409        instance.backend().get(root_id).await?;
410
411        Ok(Self {
412            root: root_id.clone(),
413            instance: instance.downgrade(),
414            ops: instance.backend().clone(),
415            key: None,
416            allow_unverified: false,
417        })
418    }
419
420    /// Open a database for remote access through a service connection.
421    ///
422    /// Constructs a [`Database`] handle whose backing
423    /// [`Backend`](crate::instance::backend::Backend) is a
424    /// [`RemoteBackend`](crate::instance::backend::RemoteBackend) bound to
425    /// `identity`, so every [`Transaction`]/[`Store`] read and write travels
426    /// over the connection as a `DatabaseOp` under that identity. The
427    /// `identity` must match the database's auth settings for the caller's
428    /// key. `Instance::connect` must be used to create the instance.
429    #[cfg(all(unix, feature = "service"))]
430    pub async fn open_remote(
431        instance: &Instance,
432        conn: RemoteConnection,
433        root_id: &ID,
434        identity: SigKey,
435    ) -> Result<Self> {
436        instance.backend().get(root_id).await?;
437        Ok(Self {
438            root: root_id.clone(),
439            instance: instance.downgrade(),
440            ops: Arc::new(RemoteBackend::new(conn, Some(identity))),
441            key: None,
442            allow_unverified: false,
443        })
444    }
445
446    /// Attach a signing key to this database handle.
447    ///
448    /// The key is stored for use by future transactions. No validation is
449    /// performed; invalid keys will cause errors at commit time or when
450    /// calling [`current_permission`](Self::current_permission).
451    ///
452    /// Calling `with_key` again replaces any previously-attached key — the
453    /// most recent call wins.
454    ///
455    /// To discover which `SigKey` identity to use for a given public key,
456    /// use [`Database::find_sigkeys`].
457    pub fn with_key(self, key: impl Into<DatabaseKey>) -> Self {
458        Self {
459            key: Some(key.into()),
460            ..self
461        }
462    }
463
464    /// Include `Unverified` entries in this handle's reads.
465    ///
466    /// By default a `Database` exposes only the **Verified frontier**: the
467    /// maximal prefix of the DAG (an ancestor-closed set, starting at the
468    /// root) in which every entry is `Verified`. Tips that are still
469    /// `Unverified` — and everything reachable only through them — are hidden,
470    /// so a default read never reflects state this node could not authenticate.
471    ///
472    /// Calling `allow_unverified` opts this handle into the looser view that
473    /// also includes `Unverified` entries (everything except `Failed`, which
474    /// is always dropped). Use it when you explicitly want to observe
475    /// not-yet-verified data — e.g. freshly synced entries whose pinned
476    /// `_settings` this node does not hold yet.
477    ///
478    /// This is a per-handle setting and composes with [`with_key`](Self::with_key):
479    ///
480    /// ```rust,no_run
481    /// # use eidetica::*;
482    /// # use eidetica::auth::crypto::generate_keypair;
483    /// # async fn example(instance: Instance, root_id: ID) -> Result<()> {
484    /// # let (signing_key, _) = generate_keypair();
485    /// let db = Database::open(&instance, &root_id)
486    ///     .await?
487    ///     .with_key(signing_key)
488    ///     .allow_unverified();
489    /// # Ok(())
490    /// # }
491    /// ```
492    ///
493    /// # Note on CRDT coherence
494    ///
495    /// The Verified frontier is a *prefix* cut, not a per-value filter. An
496    /// interior `Unverified` entry hides all of its descendants from the
497    /// default view even if those descendants are themselves `Verified`,
498    /// because exposing them without their unverifiable ancestor would yield
499    /// an incoherent CRDT state. Run [`verify`](Self::verify) to promote the
500    /// blocking entry, or `allow_unverified` to read past it.
501    pub fn allow_unverified(self) -> Self {
502        Self {
503            allow_unverified: true,
504            ..self
505        }
506    }
507
508    /// Validate a `DatabaseKey` against this database's auth settings.
509    ///
510    /// Checks that:
511    /// 1. The signing key derives to the public key claimed by the identity
512    /// 2. The identity exists in the database's auth settings
513    ///
514    /// Returns the effective permission for the validated key. Callers wanting
515    /// to fail fast on an invalid key should call
516    /// [`current_permission`](Self::current_permission), which wraps this.
517    async fn validate_key(&self, key: &DatabaseKey) -> Result<Permission> {
518        let settings_store = self.get_settings().await?;
519        let auth_settings = settings_store.auth_snapshot().await?;
520        let actual_pubkey = key.public_key();
521        let instance = match key.identity() {
522            // Delegation resolution needs an Instance for cross-tree lookups;
523            // direct identities resolve from `auth_settings` alone.
524            SigKey::Delegation { .. } => Some(self.instance()?),
525            _ => None,
526        };
527        crate::auth::validation::permissions::resolve_identity_permission(
528            &actual_pubkey,
529            key.identity(),
530            &auth_settings,
531            instance.as_ref(),
532        )
533        .await
534    }
535
536    /// Find all SigKeys that a public key can use to access a database.
537    ///
538    /// This static helper method loads a database's authentication settings and returns
539    /// all possible SigKeys that can be used with the given public key. This is useful for
540    /// discovering authentication options before opening a database.
541    ///
542    /// Returns all matching SigKeys including:
543    /// - Specific key names where the pubkey matches
544    /// - Global permission if available
545    /// - Single-hop delegation paths (pubkey found in a directly delegated tree)
546    ///
547    /// The results are **sorted by permission level, highest first**, making it easy to
548    /// select the most privileged access available.
549    ///
550    /// # Arguments
551    /// * `instance` - Instance handle for storage and coordination
552    /// * `root_id` - Root entry ID of the database to check
553    /// * `pubkey` - Public key string (e.g., "Ed25519:abc123...") to look up
554    ///
555    /// # Returns
556    /// A vector of (SigKey, Permission) tuples, sorted by permission (highest first).
557    /// Returns empty vector if no valid access methods are found.
558    ///
559    /// # Errors
560    /// Returns an error if:
561    /// - Database cannot be loaded
562    /// - Auth settings cannot be parsed
563    ///
564    /// # Example
565    /// ```rust,no_run
566    /// # use eidetica::*;
567    /// # use eidetica::database::DatabaseKey;
568    /// # use eidetica::backend::database::InMemory;
569    /// # use eidetica::auth::crypto::generate_keypair;
570    /// # use eidetica::auth::types::SigKey;
571    /// # #[tokio::main]
572    /// # async fn main() -> Result<()> {
573    /// # let instance = Instance::open_backend(Box::new(InMemory::new())).await?;
574    /// # let (signing_key, pubkey) = generate_keypair();
575    /// # let root_id = ID::from_bytes(b"database_root_id");
576    /// // Find all SigKeys this pubkey can use (sorted highest permission first)
577    /// let sigkeys = Database::find_sigkeys(&instance, &root_id, &pubkey).await?;
578    ///
579    /// // Use the first available SigKey (highest permission)
580    /// if let Some((sigkey, _permission)) = sigkeys.first() {
581    ///     let key = DatabaseKey::with_identity(signing_key, sigkey.clone());
582    ///     let database = Database::open(&instance, &root_id).await?.with_key(key);
583    /// }
584    /// # Ok(())
585    /// # }
586    /// ```
587    pub async fn find_sigkeys(
588        instance: &Instance,
589        root_id: &ID,
590        pubkey: &PublicKey,
591    ) -> Result<Vec<(SigKey, Permission)>> {
592        use crate::auth::{permission::clamp_permission, types::DelegationStep};
593
594        // Create temporary database to load settings (no key source needed for reading)
595        let temp_db = Self::open(instance, root_id).await?;
596
597        // Load auth settings
598        let settings_store = temp_db.get_settings().await?;
599        let auth_settings = settings_store.auth_snapshot().await?;
600
601        // Find direct SigKeys for this pubkey
602        let mut results = auth_settings.find_all_sigkeys_for_pubkey(pubkey);
603
604        // Scan single-hop delegation paths
605        // FIXME: deep nested delegations can't use this
606        if let Ok(delegated_trees) = auth_settings.get_all_delegated_trees() {
607            for (delegated_root_id, delegated_tree_ref) in &delegated_trees {
608                // Load the delegated tree's auth settings
609                let delegated_db = match Self::open(instance, delegated_root_id).await {
610                    Ok(db) => db,
611                    Err(_) => continue,
612                };
613                let delegated_settings = match delegated_db.get_settings().await {
614                    Ok(s) => s,
615                    Err(_) => continue,
616                };
617                let delegated_auth = match delegated_settings.auth_snapshot().await {
618                    Ok(a) => a,
619                    Err(_) => continue,
620                };
621
622                // Check if pubkey exists in the delegated tree
623                let delegated_sigkeys = delegated_auth.find_all_sigkeys_for_pubkey(pubkey);
624                if delegated_sigkeys.is_empty() {
625                    continue;
626                }
627
628                // Get current tips for the delegated tree
629                let tips = match instance.backend().snapshot(delegated_root_id).await {
630                    Ok(snap) => snap.into_tips(),
631                    Err(_) => continue,
632                };
633
634                // For each matching key in the delegated tree, construct a delegation SigKey
635                for (delegated_sk, delegated_perm) in delegated_sigkeys {
636                    // Clamp the delegated permission through the bounds
637                    let effective_perm =
638                        clamp_permission(delegated_perm, &delegated_tree_ref.permission_bounds);
639
640                    // Construct the delegation SigKey using the hint from the direct key
641                    let delegation_sigkey = SigKey::Delegation {
642                        path: vec![DelegationStep {
643                            tree: delegated_root_id.clone(),
644                            tips: tips.clone(),
645                        }],
646                        hint: delegated_sk.hint().clone(),
647                    };
648
649                    results.push((delegation_sigkey, effective_perm));
650                }
651            }
652        }
653
654        // Sort by permission, highest first
655        results.sort_by_key(|b| std::cmp::Reverse(b.1));
656        Ok(results)
657    }
658
659    /// Get the auth identity for this database's configured key.
660    pub fn auth_identity(&self) -> Option<&SigKey> {
661        self.key.as_ref().map(|k| &k.identity)
662    }
663
664    /// Register a callback to be invoked when entries are written to this database.
665    ///
666    /// The callback fires for **both** local writes (transaction commits) and remote
667    /// writes (sync). Branch on [`WriteEvent::source`](crate::WriteEvent::source) inside
668    /// the closure if you only care about one.
669    ///
670    /// Returns a [`WriteCallback`] handle. **Drop it to unregister.** Call
671    /// [`WriteCallback::detach`] to leave the callback registered for the life
672    /// of the [`Instance`] without holding the handle.
673    ///
674    /// **Important:** Callbacks are registered at the Instance level and fire for all
675    /// writes to the database tree (identified by root ID), regardless of which
676    /// `Database` handle performed the write or registered the callback.
677    ///
678    /// # ⚠️ Limited semantics on a connected (remote) [`Instance`]
679    ///
680    /// On a daemon-backed [`Instance`], `on_write` is **best-effort and partial**.
681    /// It only observes writes whose commit ran through this client's
682    /// [`Instance::put_entry`]. The following writes are **not** observed:
683    ///
684    /// - Commits made by other client processes connected to the same daemon.
685    /// - Entries the daemon receives via sync from peers.
686    /// - Anything the daemon writes outside this client's commit path.
687    ///
688    /// In addition, [`WriteEvent::previous_tips`] is **always empty** for writes
689    /// that go through a remote backend — the canonical DAG lives on the daemon
690    /// and the client has nothing local to read tips from. Callbacks that diff
691    /// `previous_tips` against `event.entries()` will see "the world was empty"
692    /// on every event.
693    ///
694    /// If you need full cross-client / cross-sync notification semantics, use a
695    /// local [`Instance`] for now. A server-push subscription path is planned —
696    /// when it lands, this method will gain the same contract on remote.
697    ///
698    /// # Callback contract (local [`Instance`])
699    ///
700    /// - **Local writes**: fires once per transaction commit; the [`WriteEvent`]
701    ///   contains exactly one entry.
702    /// - **Remote writes**: fires once per sync batch (not per entry); the
703    ///   [`WriteEvent`] may contain multiple entries received together.
704    /// - All entries in the event are fully persisted before the callback fires.
705    /// - [`WriteEvent::previous_tips`] contains the DAG tips from before the
706    ///   write, so consumers can determine exactly what changed.
707    /// - Errors are logged but do not prevent other callbacks from running.
708    /// - The `db` argument is a **read-only** [`Database`] handle (no
709    ///   [`DatabaseKey`] configured): you can read settings, entries, and
710    ///   metadata, but cannot commit transactions through it. To write from
711    ///   inside a callback, resolve through `db.instance()?` and open the
712    ///   database with the appropriate key.
713    /// - **Reentrance**: writes are serialized per-tree via an async lock
714    ///   that is held while callbacks run. A callback must not commit a
715    ///   transaction on the same tree it was invoked for — that would
716    ///   deadlock. Spawn a task or write to a different tree instead.
717    ///
718    /// # Example
719    /// ```rust,no_run
720    /// # use eidetica::*;
721    /// # use eidetica::crdt::Doc;
722    /// # use eidetica::backend::database::InMemory;
723    /// # use eidetica::auth::crypto::PrivateKey;
724    /// # #[tokio::main]
725    /// # async fn main() -> Result<()> {
726    /// let instance = Instance::open_backend(Box::new(InMemory::new())).await?;
727    /// # let signing_key = PrivateKey::generate();
728    /// # let database = Database::create(&instance, signing_key, Doc::new()).await?;
729    ///
730    /// let cb = database.on_write(|event, db| {
731    ///     let count = event.entries().len();
732    ///     let source = event.source();
733    ///     let db_id = db.root_id().clone();
734    ///     async move {
735    ///         println!("{count} entries written to {db_id} ({source:?})");
736    ///         Ok(())
737    ///     }
738    /// })?;
739    ///
740    /// // Drop `cb` to unregister, or:
741    /// cb.detach(); // keep registered for the life of the Instance
742    /// # Ok(())
743    /// # }
744    /// ```
745    ///
746    /// If a callback needs the [`Instance`], call [`Database::instance`] on
747    /// the `db` argument.
748    pub fn on_write<F, Fut>(&self, callback: F) -> Result<WriteCallback>
749    where
750        F: for<'a> Fn(&'a WriteEvent, &'a Database) -> Fut + Send + std::marker::Sync + 'static,
751        Fut: std::future::Future<Output = Result<()>> + Send + 'static,
752    {
753        let instance = self.instance()?;
754        let tree_id = self.root_id().clone();
755        let id = instance.register_write_callback(tree_id.clone(), callback);
756        Ok(WriteCallback::new_per_database(
757            instance.downgrade(),
758            tree_id,
759            id,
760        ))
761    }
762
763    /// Get the ID of the root entry
764    pub fn root_id(&self) -> &ID {
765        &self.root
766    }
767
768    /// Upgrade the weak instance reference to a strong reference.
769    ///
770    /// `Database` holds a [`WeakInstance`](crate::WeakInstance), so this can
771    /// fail if the owning [`Instance`] has already been dropped.
772    pub fn instance(&self) -> Result<Instance> {
773        self.instance
774            .upgrade()
775            .ok_or_else(|| Error::Instance(Box::new(InstanceError::InstanceDropped)))
776    }
777
778    /// Get a clone of the backend seam.
779    pub fn backend(&self) -> Result<Arc<dyn Backend>> {
780        Ok(self.instance()?.backend().clone())
781    }
782
783    /// The storage seam this handle's `Transaction`/`Store` reads/writes flow
784    /// through (a [`LocalBackend`](crate::instance::backend::LocalBackend) clone
785    /// of the instance's backend, or a per-handle
786    /// [`RemoteBackend`](crate::instance::backend::RemoteBackend)).
787    pub(crate) fn ops(&self) -> &dyn Backend {
788        self.ops.as_ref()
789    }
790
791    /// Retrieve the root entry from the backend
792    pub async fn get_root(&self) -> Result<Entry> {
793        let instance = self.instance()?;
794        instance.get(&self.root).await
795    }
796
797    /// Get a read-only settings store for the database.
798    ///
799    /// Returns a SettingsStore that provides access to the database's settings.
800    /// Since this creates an internal transaction that is never committed, any
801    /// modifications made through the returned store will not persist.
802    ///
803    /// For making persistent changes to settings, create a transaction and use
804    /// `Transaction::get_settings()` instead.
805    ///
806    /// # Returns
807    /// A `Result` containing the `SettingsStore` for settings or an error.
808    ///
809    /// # Example
810    /// ```rust,no_run
811    /// # use eidetica::Database;
812    /// # async fn example(database: Database) -> eidetica::Result<()> {
813    /// // Read-only access
814    /// let settings = database.get_settings().await?;
815    /// let name = settings.get_name().await?;
816    ///
817    /// // For modifications, use a transaction:
818    /// let txn = database.new_transaction().await?;
819    /// let settings = txn.get_settings()?;
820    /// settings.set_name("new_name").await?;
821    /// txn.commit().await?;
822    /// # Ok(())
823    /// # }
824    /// ```
825    pub async fn get_settings(&self) -> Result<SettingsStore> {
826        let txn = self.new_transaction().await?;
827        txn.get_settings()
828    }
829
830    /// Get the name of the database from its settings store
831    pub async fn get_name(&self) -> Result<String> {
832        let settings = self.get_settings().await?;
833        settings.get_name().await
834    }
835
836    /// Create a new atomic transaction on this database
837    ///
838    /// This creates a new atomic transaction containing a new Entry.
839    /// The atomic transaction will be initialized with the current state of the database.
840    /// If a default authentication key is set, the transaction will use it for signing.
841    ///
842    /// # Returns
843    /// A `Result<Transaction>` containing the new atomic transaction
844    pub async fn new_transaction(&self) -> Result<Transaction> {
845        let snapshot = self.snapshot().await?;
846        self.new_transaction_at(&snapshot).await
847    }
848
849    /// Create a new atomic transaction on this database anchored at a specific snapshot.
850    ///
851    /// The transaction's parents are taken from the provided snapshot's tips instead of
852    /// the database's current state. This allows creating complex DAG structures
853    /// like diamond patterns for testing and advanced use cases.
854    ///
855    /// # Arguments
856    /// * `snapshot` - The snapshot to anchor the transaction at
857    ///
858    /// # Returns
859    /// A `Result<Transaction>` containing the new atomic transaction
860    pub async fn new_transaction_at(&self, snapshot: &Snapshot) -> Result<Transaction> {
861        let mut txn = Transaction::new_at(self, snapshot).await?;
862
863        // Set provided signing key from DatabaseKey
864        if let Some(key) = &self.key {
865            txn.set_provided_key(*key.signing_key.clone(), key.identity.clone());
866        }
867
868        Ok(txn)
869    }
870
871    /// Gather everything a client needs to build and sign a transaction
872    /// locally for the given stores, with parents drawn from `scope`'s
873    /// projection.
874    ///
875    /// This is **single-sourced**: both the server's `BeginTransaction`
876    /// handler and the Phase-3 remote seam call it, so
877    /// `Transaction::commit`'s build-sign path has one source of truth for
878    /// context gathering.
879    ///
880    /// `scope=AllowUnverified` opens against the raw DAG (only `Failed`
881    /// dropped); the default `Verified` scope uses the Verified frontier.
882    /// The returned [`TransactionContext`] carries everything needed for
883    /// one round-trip transaction build: main parents + heights, per-store
884    /// subtree parents + heights, settings tips, and the merged `_settings`
885    /// CRDT state this entry is authored against.
886    #[cfg(all(unix, feature = "service"))]
887    pub async fn transaction_context(
888        &self,
889        stores: &[String],
890        scope: crate::service::protocol::ReadScope,
891    ) -> Result<crate::service::protocol::TransactionContext> {
892        use crate::service::protocol::{ReadScope, TransactionContext};
893
894        // -- scope-sensitive main tips --------------------------------
895        let db_for_tips = Database {
896            allow_unverified: matches!(scope, ReadScope::AllowUnverified),
897            ..self.clone()
898        };
899        let main_snapshot = db_for_tips.snapshot().await?;
900        let main_tips = main_snapshot.tips();
901
902        // -- main parents: (tip, height) ------------------------------
903        let mut main_parents = Vec::with_capacity(main_tips.len());
904        for tip in main_tips {
905            let entry = self.ops().get(tip).await?;
906            main_parents.push((tip.clone(), entry.height()));
907        }
908
909        // -- per-store subtree parents: (tip, subtree_height) ---------
910        let mut subtree_parents = std::collections::BTreeMap::new();
911        for store in stores {
912            let child_snap = self
913                .ops()
914                .store_snapshot_at(self.root_id(), store, &main_snapshot)
915                .await?;
916            let mut pairs = Vec::with_capacity(child_snap.len());
917            for tip in child_snap.tips() {
918                let entry = self.ops().get(tip).await?;
919                let height = entry.subtree_height(store).unwrap_or(0);
920                pairs.push((tip.clone(), height));
921            }
922            subtree_parents.insert(store.clone(), pairs);
923        }
924
925        // -- settings tips (pinned in entry metadata) -----------------
926        let settings_tips = self
927            .ops()
928            .store_snapshot_at(self.root_id(), SETTINGS, &main_snapshot)
929            .await?
930            .into_tips();
931
932        // -- merged _settings state as serde_json::Value --------------
933        let txn = Transaction::new_at(self, &main_snapshot).await?;
934        let settings_doc: Doc = txn.get_full_state(SETTINGS).await?;
935        let settings_value = serde_json::to_value(&settings_doc)?;
936
937        Ok(TransactionContext {
938            main_parents,
939            subtree_parents,
940            settings_tips,
941            settings_value,
942        })
943    }
944
945    /// Server-materialized merged state of an **unencrypted** store, as a
946    /// `serde_json::Value` against the database's Verified frontier.
947    ///
948    /// Creates an ephemeral transaction, deserializes every entry's
949    /// store data as [`Doc`], and merges them via Doc's LWW merge —
950    /// the same merge `Store<T>` would perform client-side. All current
951    /// store types (DocStore, Table, Settings) serialize their data as
952    /// JSON, so `Doc`-typed deserialization works universally.
953    ///
954    /// # Encrypted stores
955    ///
956    /// Encrypted stores cannot be materialized this way (the ephemeral
957    /// transaction has no encryptor, so `serde_json::from_slice::<Doc>`
958    /// would fail on ciphertext). The caller must use
959    /// [`get_store_entries`](Self::get_store_entries) for encrypted
960    /// stores and decrypt+merge client-side.
961    pub async fn get_store_state(&self, store: &str) -> Result<serde_json::Value> {
962        let txn = self.new_transaction().await?;
963        let state: Doc = txn.get_full_state(store).await?;
964        Ok(serde_json::to_value(&state)?)
965    }
966
967    /// Ordered (by subtree height), verifiable, opaque store entries
968    /// reachable from `tips` within `scope`.
969    ///
970    /// This is the **universal** primitive — works for encrypted and
971    /// unencrypted stores alike because it returns raw [`Entry`] records
972    /// with opaque [`RawData`](crate::entry::RawData); no deserialization
973    /// or merge runs server-side. The per-subtree-height ordering
974    /// (ascending, then by ID for tiebreaking) is exactly the canonical
975    /// CRDT replay order produced by
976    /// [`sort_entries_by_subtree_height`](crate::backend::database::in_memory::cache::sort_entries_by_subtree_height).
977    ///
978    /// When `scope` is [`ReadScope::Verified`] and `tips` are the
979    /// Verified-frontier tips from [`snapshot`](Self::snapshot),
980    /// every returned entry is guaranteed `Verified` (the frontier is
981    /// ancestor-closed). For [`ReadScope::AllowUnverified`], entries
982    /// reachable from unverified tips are included.
983    #[cfg(all(unix, feature = "service"))]
984    pub async fn get_store_entries(
985        &self,
986        store: &str,
987        tips: &[ID],
988        _scope: crate::service::protocol::ReadScope,
989    ) -> Result<Vec<Entry>> {
990        let snapshot = Snapshot::from(tips.to_vec());
991        self.ops().store_at(self.root_id(), store, &snapshot).await
992    }
993
994    /// Execute a closure within a transaction and commit the result.
995    ///
996    /// This is a convenience wrapper for the common pattern of creating a transaction,
997    /// performing store operations, and committing. The transaction is committed after
998    /// the closure returns `Ok`. If the closure returns `Err`, the transaction is
999    /// dropped without committing.
1000    ///
1001    /// For read-only access, use [`get_store_viewer`](Self::get_store_viewer) instead.
1002    ///
1003    /// # Arguments
1004    /// * `f` - A closure that receives the [`Transaction`] and performs store operations.
1005    ///   The closure should return `Ok(R)` on success.
1006    ///
1007    /// # Returns
1008    /// On success, returns the value produced by the closure after committing.
1009    /// The commit ID is not returned; use [`new_transaction`](Self::new_transaction)
1010    /// directly if you need it.
1011    ///
1012    /// # Errors
1013    /// Returns an error if transaction creation, the closure, or commit fails.
1014    /// If the closure fails, the transaction is not committed.
1015    ///
1016    /// # Example
1017    /// ```rust,no_run
1018    /// # use eidetica::*;
1019    /// # use eidetica::store::Table;
1020    /// # use serde::{Serialize, Deserialize};
1021    /// # #[derive(Clone, Serialize, Deserialize)]
1022    /// # struct Todo { title: String }
1023    /// # async fn example(db: Database) -> Result<()> {
1024    /// // Insert a record and get its generated key
1025    /// let key = db.with_transaction(|txn| async move {
1026    ///     let store = txn.get_store::<Table<Todo>>("todos").await?;
1027    ///     store.insert(Todo { title: "Buy milk".into() }).await
1028    /// }).await?;
1029    ///
1030    /// // Multiple operations in one atomic transaction
1031    /// db.with_transaction(|txn| async move {
1032    ///     let store = txn.get_store::<Table<Todo>>("todos").await?;
1033    ///     store.insert(Todo { title: "First".into() }).await?;
1034    ///     store.insert(Todo { title: "Second".into() }).await?;
1035    ///     Ok(())
1036    /// }).await?;
1037    /// # Ok(())
1038    /// # }
1039    /// ```
1040    pub async fn with_transaction<F, Fut, R>(&self, f: F) -> Result<R>
1041    where
1042        F: FnOnce(Transaction) -> Fut + Send,
1043        Fut: Future<Output = Result<R>> + Send,
1044    {
1045        let txn = self.new_transaction().await?;
1046        let commit_handle = txn.clone();
1047        let result = f(txn).await?;
1048        commit_handle.commit().await?;
1049        Ok(result)
1050    }
1051
1052    /// Insert an entry into the database without modifying or validating it.
1053    /// Primarily for testing / full control over raw entry storage.
1054    ///
1055    /// The entry is stored `Unverified`: this path runs no validation, so it
1056    /// cannot honestly claim the entry is verified, and the storage API no
1057    /// longer accepts a caller-asserted status. Only the local validation
1058    /// pass promotes entries to `Verified`.
1059    pub async fn insert_raw(&self, entry: Entry) -> Result<ID> {
1060        let instance = self.instance()?;
1061        let id = entry.id();
1062
1063        instance.put(entry).await?;
1064
1065        Ok(id)
1066    }
1067
1068    /// Get a Store type that will handle accesses to the Store
1069    /// This will return a Store initialized to point at the current state of the database.
1070    ///
1071    /// The returned store should NOT be used to modify the database, as it intentionally does not
1072    /// expose the Transaction. Since the Transaction is never committed, it does not have any
1073    /// effect on the database.
1074    pub async fn get_store_viewer<T>(&self, name: impl Into<String>) -> Result<T>
1075    where
1076        T: Store,
1077    {
1078        let txn = self.new_transaction().await?;
1079        T::load(&txn, name.into()).await
1080    }
1081
1082    /// Get the current tips (leaf entries) of the main database branch.
1083    ///
1084    /// Tips represent the latest entries in the database's main history, forming the heads of the DAG.
1085    ///
1086    /// If any raw tip is `Unverified`, an opportunistic [`Self::verify`] pass
1087    /// runs first (entries arrive `Unverified` from sync; this promotes the
1088    /// ones whose pinned `_settings` are now held).
1089    ///
1090    /// The returned tips then depend on the handle's view:
1091    ///
1092    /// - **default** — the **Verified frontier**: the tips of the maximal
1093    ///   ancestor-closed, all-`Verified` prefix of the DAG. A still-`Unverified`
1094    ///   tip is replaced by its nearest `Verified` ancestors; anything reachable
1095    ///   only through an `Unverified` entry is excluded.
1096    /// - **[`allow_unverified`](Self::allow_unverified)** — the raw tips with
1097    ///   only `Failed` entries dropped (`Unverified` tips are kept).
1098    ///
1099    /// `Failed` entries are dropped in both cases. While a [`verify`](Self::verify)
1100    /// pass is on the stack the frontier is bypassed (its own reads must see the
1101    /// raw DAG to reconstruct pinned `_settings`); a remote backend returns its
1102    /// raw tips unchanged (the server owns verification).
1103    ///
1104    /// # Returns
1105    /// A `Result` containing the [`Snapshot`] of tip entries or an error.
1106    pub async fn snapshot(&self) -> Result<Snapshot> {
1107        let instance = self.instance()?;
1108
1109        // On a remote instance the server owns verification: `snapshot`
1110        // already returns the server-side Verified frontier (or empty for a
1111        // not-yet-propagated tree, e.g. `Database::create`'s bootstrap
1112        // placeholder root — `EntryNotFound` is mapped to empty to match
1113        // `Backend::snapshot`'s contract). Return it directly: the local
1114        // verification machinery below (status probe, auto-verify,
1115        // `verified_frontier`) is local-only and would fail on a remote
1116        // backend anyway (e.g. `verified_frontier`'s `backend.get_tree(...)`).
1117        //
1118        // Delegate to `self.ops()` rather than calling the connection
1119        // directly: when this handle was built via `Database::create` or
1120        // `Database::open_remote` its `ops` is a `RemoteBackend` carrying the
1121        // *per-database* identity (the new tree's own member key, or the
1122        // caller's chosen identity), which the server's per-tree gate accepts.
1123        // Routing through `conn.session_identity()` here would instead use the
1124        // connection's (caller's) session pubkey, which is not a member of a
1125        // freshly-created tree and gets denied. A handle from `Database::open`
1126        // on a connected instance instead clones the instance's session
1127        // backend, keeping the session-identity semantics for that path.
1128        #[cfg(all(unix, feature = "service"))]
1129        if instance.remote_connection().is_some() {
1130            return match self.ops().snapshot(&self.root).await {
1131                Ok(snap) => Ok(snap),
1132                Err(e) if e.is_not_found() => Ok(Snapshot::EMPTY),
1133                Err(e) => Err(e),
1134            };
1135        }
1136
1137        // Local path: verification-status probing needs the concrete engine.
1138        let backend = instance.require_local_engine()?;
1139        let tips = self.ops().snapshot(&self.root).await?.into_tips();
1140
1141        // Verification status ops are local-only. On a remote backend the
1142        // server owns verification (and stores everything Unverified until
1143        // it verifies); the client returns verified tips unchanged.
1144        if let Some(first) = tips.first()
1145            && backend.get_verification_status(first).await.is_err()
1146        {
1147            return Ok(Snapshot::new(tips));
1148        }
1149
1150        // Access-time opportunistic verification: if any tip is still
1151        // Unverified, attempt to resolve it now. Best-effort — a failure or a
1152        // still-incomplete pin must not block the read. Suppressed while a
1153        // verify pass is already on the stack (its own reads land here).
1154        let tips = if auto_verify_suppressed() {
1155            tips
1156        } else {
1157            let mut any_unverified = false;
1158            for t in &tips {
1159                if backend
1160                    .get_verification_status(t)
1161                    .await
1162                    .unwrap_or(VerificationStatus::Unverified)
1163                    == VerificationStatus::Unverified
1164                {
1165                    any_unverified = true;
1166                    break;
1167                }
1168            }
1169            if any_unverified {
1170                // Boxed: this call closes a snapshot → verify →
1171                // validate_entry → delegation → get_settings → snapshot
1172                // async cycle; the box gives it a finite future size.
1173                let _ = Box::pin(self.verify()).await;
1174                self.ops().snapshot(&self.root).await?.into_tips()
1175            } else {
1176                tips
1177            }
1178        };
1179
1180        // Default view: cut to the Verified frontier. Suppressed while a
1181        // verify pass is on the stack — its reads must see the raw DAG to
1182        // reconstruct pinned `_settings` (the frontier filter itself depends
1183        // on verification status, which is exactly what verify is computing).
1184        if !self.allow_unverified && !auto_verify_suppressed() {
1185            return self.verified_frontier().await.map(Snapshot::new);
1186        }
1187
1188        // `allow_unverified` view: keep Unverified tips, drop only Failed.
1189        let mut visible = Vec::with_capacity(tips.len());
1190        for t in tips {
1191            if backend.get_verification_status(&t).await? != VerificationStatus::Failed {
1192                visible.push(t);
1193            }
1194        }
1195        Ok(Snapshot::new(visible))
1196    }
1197
1198    /// Compute the tips of the maximal all-`Verified` prefix of the DAG.
1199    ///
1200    /// An entry is in the prefix iff it is `Verified` **and** every one of its
1201    /// parents is in the prefix (the prefix is ancestor-closed). The frontier
1202    /// is the set of prefix entries that are not the parent of any other
1203    /// prefix entry — i.e. the tips of the verified subgraph.
1204    ///
1205    /// Returns an empty vector if the root itself is not `Verified` (nothing
1206    /// is observable in the default view until verification reaches the root).
1207    async fn verified_frontier(&self) -> Result<Vec<ID>> {
1208        let instance = self.instance()?;
1209        let backend = instance.require_local_engine()?;
1210
1211        // Topologically sorted (height then ID): every parent precedes its
1212        // children, so a single forward pass can decide prefix membership.
1213        let entries = backend.get_tree(self.root_id()).await?;
1214
1215        let mut in_prefix: std::collections::HashSet<ID> = std::collections::HashSet::new();
1216        let mut covered: std::collections::HashSet<ID> = std::collections::HashSet::new();
1217
1218        for e in &entries {
1219            let id = e.id();
1220            if backend.get_verification_status(&id).await? != VerificationStatus::Verified {
1221                continue;
1222            }
1223            let parents = e.parents().unwrap_or_default();
1224            if parents.iter().all(|p| in_prefix.contains(p)) {
1225                in_prefix.insert(id);
1226                // Every parent now has a verified child, so it is interior to
1227                // the prefix and cannot itself be a frontier tip.
1228                for p in parents {
1229                    covered.insert(p);
1230                }
1231            }
1232        }
1233
1234        let frontier: Vec<ID> = entries
1235            .into_iter()
1236            .map(|e| e.id())
1237            .filter(|id| in_prefix.contains(id) && !covered.contains(id))
1238            .collect();
1239        Ok(frontier)
1240    }
1241
1242    /// Get the full `Entry` objects for the current tips of the main database branch.
1243    ///
1244    /// # Returns
1245    /// A `Result` containing a vector of the tip `Entry` objects or an error.
1246    pub async fn get_tip_entries(&self) -> Result<Vec<Entry>> {
1247        let instance = self.instance()?;
1248        let snapshot = self.snapshot().await?;
1249        let mut entries = Vec::new();
1250        for id in snapshot.tips() {
1251            entries.push(instance.get(id).await?);
1252        }
1253        Ok(entries)
1254    }
1255
1256    /// Get a single entry by ID from this database.
1257    ///
1258    /// This is the primary method for retrieving entries after commit operations.
1259    /// It provides safe, high-level access to entry data without exposing backend details.
1260    ///
1261    /// The method verifies that the entry belongs to this database by checking its root ID.
1262    /// If the entry exists but belongs to a different database, an error is returned.
1263    ///
1264    /// # Arguments
1265    /// * `entry_id` - The ID of the entry to retrieve (accepts anything that converts to ID/String)
1266    ///
1267    /// # Returns
1268    /// A `Result` containing the `Entry` or an error if not found or not part of this database
1269    ///
1270    /// # Example
1271    /// ```rust,no_run
1272    /// # use eidetica::*;
1273    /// # use eidetica::Instance;
1274    /// # use eidetica::backend::database::InMemory;
1275    /// # use eidetica::crdt::Doc;
1276    /// # #[tokio::main]
1277    /// # async fn main() -> Result<()> {
1278    /// # let (_instance, mut user) = Instance::create_backend(
1279    /// #     Box::new(InMemory::new()),
1280    /// #     NewUser::passwordless("test"),
1281    /// # ).await?;
1282    /// # let key_id = user.add_private_key(None).await?;
1283    /// # let tree = user.create_database(Doc::new(), &key_id).await?;
1284    /// # let txn = tree.new_transaction().await?;
1285    /// let entry_id = txn.commit().await?;
1286    /// let entry = tree.get_entry(&entry_id).await?;           // Using &ID
1287    /// let entry = tree.get_entry(entry_id.clone()).await?;    // Using ID
1288    /// println!("Entry signature: {:?}", entry.sig);
1289    /// # Ok(())
1290    /// # }
1291    /// ```
1292    pub async fn get_entry<I: Into<ID>>(&self, entry_id: I) -> Result<Entry> {
1293        let id = entry_id.into();
1294        // Route through `self.ops()` so handles built via `Database::create`
1295        // or `Database::open_remote` read with the per-DB identity from
1296        // their `RemoteBackend`. Going through `instance.get(id)` would
1297        // use the connection's login pubkey, which on a remote instance is
1298        // denied by the per-tree gate when the login key isn't a member of
1299        // this tree (e.g. user-tree key created via `User::add_private_key`
1300        // and used to author a database that doesn't grant the root key).
1301        let entry = self.ops().get(&id).await?;
1302
1303        // Check if the entry belongs to this database
1304        if !entry.in_tree(&self.root) {
1305            return Err(InstanceError::EntryNotInDatabase {
1306                entry_id: id,
1307                database_id: self.root.clone(),
1308            }
1309            .into());
1310        }
1311
1312        Ok(entry)
1313    }
1314
1315    /// Get multiple entries by ID efficiently.
1316    ///
1317    /// This method retrieves multiple entries more efficiently than multiple `get_entry()` calls
1318    /// by minimizing conversion overhead and pre-allocating the result vector.
1319    ///
1320    /// The method verifies that all entries belong to this database by checking their root IDs.
1321    /// If any entry exists but belongs to a different database, an error is returned.
1322    ///
1323    /// # Parameters
1324    /// * `entry_ids` - An iterable of entry IDs to retrieve
1325    ///
1326    /// # Returns
1327    /// A `Result` containing a vector of `Entry` objects or an error if any entry is not found or not part of this database
1328    ///
1329    /// # Example
1330    /// ```rust,no_run
1331    /// # use eidetica::*;
1332    /// # use eidetica::Instance;
1333    /// # use eidetica::backend::database::InMemory;
1334    /// # use eidetica::crdt::Doc;
1335    /// # #[tokio::main]
1336    /// # async fn main() -> Result<()> {
1337    /// # let (_instance, mut user) = Instance::create_backend(
1338    /// #     Box::new(InMemory::new()),
1339    /// #     NewUser::passwordless("test"),
1340    /// # ).await?;
1341    /// # let key_id = user.add_private_key(None).await?;
1342    /// # let tree = user.create_database(Doc::new(), &key_id).await?;
1343    /// let entry_ids = vec![ID::from_bytes("id1"), ID::from_bytes("id2")];
1344    /// let entries = tree.get_entries(entry_ids).await?;
1345    /// # Ok(())
1346    /// # }
1347    /// ```
1348    pub async fn get_entries<I, T>(&self, entry_ids: I) -> Result<Vec<Entry>>
1349    where
1350        I: IntoIterator<Item = T>,
1351        T: std::borrow::Borrow<ID>,
1352    {
1353        let ids: Vec<ID> = entry_ids.into_iter().map(|t| t.borrow().clone()).collect();
1354        let instance = self.instance()?;
1355        let mut entries = Vec::with_capacity(ids.len());
1356
1357        for id in ids {
1358            let entry = instance.get(&id).await?;
1359
1360            // Check if the entry belongs to this database
1361            if !entry.in_tree(&self.root) {
1362                return Err(InstanceError::EntryNotInDatabase {
1363                    entry_id: id,
1364                    database_id: self.root.clone(),
1365                }
1366                .into());
1367            }
1368
1369            entries.push(entry);
1370        }
1371
1372        Ok(entries)
1373    }
1374
1375    // === AUTHENTICATION HELPERS ===
1376
1377    /// Verify an entry's signature and authentication against the database's configuration that was valid at the time of entry creation.
1378    ///
1379    /// This method validates that:
1380    /// 1. The entry belongs to this database
1381    /// 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
1382    /// 3. The signature is cryptographically valid
1383    ///
1384    /// The method uses the entry's metadata to determine which authentication settings were active when the entry was signed,
1385    /// ensuring that entries remain valid even if keys are later revoked or settings change.
1386    ///
1387    /// # Arguments
1388    /// * `entry_id` - The ID of the entry to verify (accepts anything that converts to ID/String)
1389    ///
1390    /// # Returns
1391    /// A `Result` containing `true` if the entry is valid and properly authenticated, `false` if authentication fails
1392    ///
1393    /// # Errors
1394    /// Returns an error if:
1395    /// - The entry is not found
1396    /// - The entry does not belong to this database
1397    /// - The entry's metadata cannot be parsed
1398    /// - The historical authentication settings cannot be retrieved
1399    pub async fn verify_entry_signature<I: Into<ID>>(&self, entry_id: I) -> Result<bool> {
1400        let entry = self.get_entry(entry_id).await?;
1401
1402        // Validate against the `_settings` the entry pins, not current
1403        // settings — so a later key revocation cannot retroactively
1404        // invalidate (or validate) historical entries.
1405        match self.get_historical_settings_for_entry(&entry).await? {
1406            // We do not hold the pinned `_settings` set, so we cannot make a
1407            // verification decision: report not-verified rather than guess.
1408            PinnedSettings::Incomplete => Ok(false),
1409            PinnedSettings::Complete(auth_settings) => {
1410                let instance = self.instance()?;
1411                let mut validator = AuthValidator::new();
1412                validator
1413                    .validate_entry(&entry, &auth_settings, Some(&instance))
1414                    .await
1415            }
1416        }
1417    }
1418
1419    /// Get the permission level for this database's configured signing key.
1420    ///
1421    /// Returns the effective permission for the key that was configured when opening
1422    /// or creating this database. This uses the already-resolved identity stored in
1423    /// the database's `DatabaseKey`.
1424    ///
1425    /// # Returns
1426    /// The effective Permission for the configured signing key.
1427    ///
1428    /// # Errors
1429    /// Returns an error if:
1430    /// - No signing key is configured (database opened without authentication)
1431    /// - The database settings cannot be retrieved
1432    /// - The key is no longer valid in the current auth settings
1433    ///
1434    /// # Example
1435    /// ```rust,no_run
1436    /// # use eidetica::*;
1437    /// # use eidetica::crdt::Doc;
1438    /// # use eidetica::backend::database::InMemory;
1439    /// # use eidetica::auth::crypto::generate_keypair;
1440    /// # #[tokio::main]
1441    /// # async fn main() -> Result<()> {
1442    /// # let instance = Instance::open_backend(Box::new(InMemory::new())).await?;
1443    /// # let (signing_key, _public_key) = generate_keypair();
1444    /// # let database = Database::create(&instance, signing_key, Doc::new()).await?;
1445    /// // Check if the current key has Admin permission
1446    /// let permission = database.current_permission().await?;
1447    /// if permission.can_admin() {
1448    ///     println!("Current key has Admin permission!");
1449    /// }
1450    /// # Ok(())
1451    /// # }
1452    /// ```
1453    pub async fn current_permission(&self) -> Result<Permission> {
1454        let key = self
1455            .key
1456            .as_ref()
1457            .ok_or(AuthError::InvalidAuthConfiguration {
1458                reason: "No signing key configured for this database".to_string(),
1459            })?;
1460        self.validate_key(key).await
1461    }
1462
1463    /// Reconstruct the `_settings` auth config an entry's signature is pinned
1464    /// to, from the `settings_snapshot` recorded in its signed metadata.
1465    ///
1466    /// Validation must run against the settings the entry pinned — not the
1467    /// current settings — so granting authority later cannot retroactively
1468    /// invalidate an entry that pinned less, and (once revocation lands)
1469    /// removals are handled on a separate, current-settings path.
1470    ///
1471    /// Returns [`PinnedSettings::Incomplete`] when this node does not hold the
1472    /// full pinned `_settings` ancestor set; the caller must then leave the
1473    /// entry `Unverified` rather than guess against whatever it does hold.
1474    async fn get_historical_settings_for_entry(&self, entry: &Entry) -> Result<PinnedSettings> {
1475        let instance = self.instance()?;
1476        let backend = instance.backend();
1477
1478        // The pin: `_settings` snapshot recorded in the entry's signed metadata.
1479        let settings_tips: Vec<ID> = match entry.metadata() {
1480            Some(raw) => match serde_json::from_slice::<crate::transaction::EntryMetadata>(raw) {
1481                Ok(md) => md.settings_snapshot.into_tips(),
1482                // Unparsable metadata ⇒ we cannot establish the pin.
1483                Err(_) => return Ok(PinnedSettings::Incomplete),
1484            },
1485            None => Vec::new(),
1486        };
1487
1488        // Resolve the effective `_settings` tips to validate against.
1489        let effective_tips: Vec<ID> = if settings_tips.is_empty() {
1490            if entry.in_subtree(SETTINGS) {
1491                // Genesis / bootstrap: no prior `_settings` exists, so the
1492                // entry is self-authorising — validate against the auth it
1493                // itself establishes (TOFU), mirroring how the transaction
1494                // validates initial database creation. Seeding the
1495                // reconstruction with the entry itself folds in its own
1496                // `_settings` contribution.
1497                vec![entry.id()]
1498            } else {
1499                // No auth context at all (no settings ever configured) —
1500                // mirrors the transaction path's "auth never configured" case.
1501                return Ok(PinnedSettings::Complete(AuthSettings::new()));
1502            }
1503        } else {
1504            settings_tips
1505        };
1506
1507        // Completeness: every pinned tip and its full `_settings` ancestor
1508        // closure must be present locally. `store_at` silently
1509        // skips absent ancestors, so an explicit walk is required — a missing
1510        // ancestor would otherwise yield a wrong (partial) auth config.
1511        let mut stack: Vec<ID> = effective_tips.clone();
1512        let mut seen: std::collections::HashSet<ID> = std::collections::HashSet::new();
1513        while let Some(id) = stack.pop() {
1514            if !seen.insert(id.clone()) {
1515                continue;
1516            }
1517            let Ok(e) = backend.get(&id).await else {
1518                return Ok(PinnedSettings::Incomplete);
1519            };
1520            // Walk both the `_settings` subtree DAG and the main parents that
1521            // carry it, so the closure can't be short-circuited.
1522            for p in e.subtree_parents(SETTINGS).unwrap_or_default() {
1523                stack.push(p);
1524            }
1525            for p in e.parents().unwrap_or_default() {
1526                stack.push(p);
1527            }
1528        }
1529
1530        // Reconstruct the merged `_settings` Doc as of the pinned tips.
1531        // Entries come back root-first; `_settings` is a system subtree and
1532        // is never encrypted, so deserialize directly.
1533        let effective_snapshot = Snapshot::from(effective_tips.clone());
1534        let entries = backend
1535            .store_at(self.root_id(), SETTINGS, &effective_snapshot)
1536            .await?;
1537        let mut settings_doc = Doc::default();
1538        for e in &entries {
1539            if let Ok(data) = e.data(SETTINGS) {
1540                let part: Doc = serde_json::from_slice(data)?;
1541                settings_doc = settings_doc.merge(&part)?;
1542            }
1543        }
1544
1545        let auth_settings = match settings_doc.get("auth") {
1546            Some(crate::crdt::doc::Value::Doc(auth_doc)) => auth_doc.clone().into(),
1547            _ => AuthSettings::new(),
1548        };
1549        Ok(PinnedSettings::Complete(auth_settings))
1550    }
1551
1552    /// Attempt to verify every `Unverified` entry in this database.
1553    ///
1554    /// For each `Unverified` entry, reconstruct the `_settings` it pins
1555    /// (see [`Self::get_historical_settings_for_entry`]) and validate its
1556    /// signature + permissions against that:
1557    ///
1558    /// - an ancestor is `Failed` → this entry is `Failed` too (quarantine
1559    ///   propagates down the branch);
1560    /// - an ancestor is still `Unverified`, or not held locally yet (partial
1561    ///   sync) → left `Unverified` (retried once the ancestor verifies / the
1562    ///   missing entry arrives);
1563    /// - pinned `_settings` not fully held locally → left `Unverified`
1564    ///   (a later pass retries once the set syncs in);
1565    /// - signature + permissions valid → promoted to `Verified`;
1566    /// - definitively invalid → marked `Failed` (dropped from reads).
1567    ///
1568    /// Verification is **prefix-closed**: an entry is `Verified` only if its
1569    /// entire ancestor history is `Verified`. It is therefore impossible for a
1570    /// tip to be `Verified` while one of its ancestors is not, which is what
1571    /// makes the Verified set ancestor-closed (see [`Self::allow_unverified`]).
1572    ///
1573    /// Already-`Verified` entries are never demoted here; that is a separate,
1574    /// not-yet-built path. Local-only — verification is a per-node decision
1575    /// and is never delegated to a peer.
1576    pub async fn verify(&self) -> Result<VerifyReport> {
1577        // Suppress the access-time auto-verify hook for the whole pass:
1578        // validation reads the database (delegation → settings → tips) and
1579        // must not recurse back into verification.
1580        IN_VERIFY
1581            .scope(true, async move {
1582                let instance = self.instance()?;
1583                let backend = instance.require_local_engine()?;
1584
1585                // Raw tree walk (not `snapshot`) so traversal itself never
1586                // re-enters the hook even before the guard is observed.
1587                let entries = backend.get_tree(self.root_id()).await?;
1588                let mut report = VerifyReport::default();
1589
1590                for entry in &entries {
1591                    let id = entry.id();
1592                    if backend.get_verification_status(&id).await? != VerificationStatus::Unverified
1593                    {
1594                        continue;
1595                    }
1596
1597                    // Verification is prefix-closed: an entry's trust rests on
1598                    // its entire history, so it can only be `Verified` if every
1599                    // ancestor is. `get_tree` is topo-sorted (parents before
1600                    // children), so each parent's status is already final for
1601                    // this pass.
1602                    //
1603                    // - any ancestor `Failed` → the branch is tainted; this
1604                    //   entry is `Failed` too (quarantine propagates forward);
1605                    // - any ancestor still `Unverified` → cannot trust this
1606                    //   entry yet; leave it for a later pass.
1607                    let parents = entry.parents().unwrap_or_default();
1608                    let mut compromised = false;
1609                    let mut blocked = false;
1610                    for p in &parents {
1611                        match backend.get_verification_status(p).await {
1612                            Ok(VerificationStatus::Verified) => {}
1613                            Ok(VerificationStatus::Failed) => compromised = true,
1614                            Ok(VerificationStatus::Unverified) => blocked = true,
1615                            // Parent not held locally yet (partial sync): we
1616                            // cannot establish the history, so this entry is
1617                            // blocked until the parent arrives — not an error.
1618                            Err(e) if e.is_not_found() => blocked = true,
1619                            Err(e) => return Err(e),
1620                        }
1621                    }
1622                    if compromised {
1623                        backend
1624                            .update_verification_status(&id, VerificationStatus::Failed)
1625                            .await?;
1626                        report.failed += 1;
1627                        continue;
1628                    }
1629                    if blocked {
1630                        report.still_unverified += 1;
1631                        continue;
1632                    }
1633
1634                    match self.get_historical_settings_for_entry(entry).await? {
1635                        PinnedSettings::Incomplete => report.still_unverified += 1,
1636                        PinnedSettings::Complete(auth_settings) => {
1637                            let mut validator = AuthValidator::new();
1638                            let valid = validator
1639                                .validate_entry(entry, &auth_settings, Some(&instance))
1640                                .await
1641                                .unwrap_or(false);
1642                            let new_status = if valid {
1643                                report.verified += 1;
1644                                VerificationStatus::Verified
1645                            } else {
1646                                report.failed += 1;
1647                                VerificationStatus::Failed
1648                            };
1649                            backend.update_verification_status(&id, new_status).await?;
1650                        }
1651                    }
1652                }
1653                Ok(report)
1654            })
1655            .await
1656    }
1657
1658    // === DATABASE QUERIES ===
1659
1660    /// Get all entries in this database.
1661    ///
1662    /// ⚠️ **Warning**: This method loads all entries into memory. Use with caution on large databases.
1663    /// Consider using `snapshot()` or `get_tip_entries()` for more efficient access patterns.
1664    ///
1665    /// # Returns
1666    /// A `Result` containing a vector of all `Entry` objects in the database
1667    pub async fn get_all_entries(&self) -> Result<Vec<Entry>> {
1668        let instance = self.instance()?;
1669        instance.require_local_engine()?.get_tree(&self.root).await
1670    }
1671}