Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Authentication

Ed25519 signatures and causal settings determine whether an entry is authorized. The authentication design is the canonical explanation of delegated floors, trust boundaries and examples. The verification model owns status and visibility; the CLI guide owns the offline reset and dependency-first recovery procedure.

Authentication States

State_settings.authUnsigned entrySigned entry
Unauthenticated historyMissing or emptyAccepted by the low-level validatorNo authority, except genuine genesis bootstrap
Authenticated historyKeys or global permissionRejectedSignature and permission checks required
Corrupt or deleted authWrong type or tombstoneRejectedRejected

Normal transaction APIs require a signing key. A non-genesis write cannot use newly staged settings to authorize itself. Transactions prevent auth corruption before storage; remote verification also rejects corrupt historical auth.

Permission and Key Resolution

PermissionSettings/keysWriteReadPriority
AdminYesYesYesLower number has higher privilege
WriteNoYesYesLower number has higher privilege
ReadNoNoYesNone

Direct keys live in _settings.auth.keys, indexed by public key; a name is a hint, not an identity. The wildcard supplies global permission for an actual signer. Delegations live in _settings.auth.delegations, indexed by database root ID. At each delegation step permission is clamped to its configured minimum/maximum; the final hint must resolve a concrete key, not the wildcard. Priority authorizes administrative actions; it does not change Doc merge ordering.

Historical signatures versus live access

Entry validation resolves delegation at the signed snapshots, subject to the configured and inherited causal floors. Later membership removal or downgrade does not by itself change that historical signature predicate.

Live service access, Database::current_permission, and sync’s Database::can_access instead use current accepted authority at every path step. The target’s current accepted declarations and bounds apply, even on a diagnostic allow_unverified handle or while verification uses raw history internally. Claimed tips remain bounded hints; they cannot select older authority or trigger identity fetching. The resolver reads the retained Verified frontier and proves its complete ancestry before folding settings at that immutable boundary. It does not recursively verify delegated databases while the caller holds a verification lock.

A public wildcard Read grant exposes identity metadata, not delegated membership. An active Read member can still be promoted by PermissionBounds.min. Independent direct target grants remain valid; sync’s pubkey-only discovery is still single-hop, while a supplied identity may name a bounded nested path.

This is observer-local authorization. A peer that has not accepted a removal can still use older state. There is no globally latest-state guarantee, wall-clock revocation bound during partitions, or new concurrent branch-validity policy. The live_delegation tests cover retained local and service identities, sync disclosure, reductions, bounds, path tampering and unaccepted identity state.

Validation Seams

  • database/mod.rs: derive and match the signed causal pre-write settings pin; check main parents before remote promotion; retain undecidable entries.
  • transaction/mod.rs: fixed main-parent settings/subtree reads and metadata; genuine-genesis-only staged-auth bootstrap; shared signature validator.
  • auth/validation/floors.rs: join Verified parent projections, derive effective resulting settings, preserve separate direct/nested root observations, prove complete delegated ancestry, and rebuild cold derived state.
  • auth/validation/delegation.rs: declaration lookup at historical snapshots, coverage of configured/inherited floors, bounds and key resolution.
  • service/server.rs: store submitted entries Unverified and verify server-side; clients cannot assert a verification label. Historical signature queries also run the native validator server-side, gated by the handle’s acting identity and Read permission on the target and claimed delegated trees.
  • backend/database/{in_memory/storage,sql/verified}.rs: atomic same-tree parent guards, status/Verified-tip publication, cascading demotion and conservative ancestry repair. These do not rerun signature or delegation validation; see the backend contract.
  • backend/database/{in_memory,sql}/traversal.rs: anchored store boundaries; SQL reads both current tip sets in one statement, InMemory uses its lock.

A projection can be published after authentication succeeds but before the Entry is stored/promoted. Consumers must still check the parent’s local status before using it. Unsupported derived-state storage rebuilds from Entries; cache I/O failure is not an empty state. No recursive cross-tree verification belongs in these seams while a per-tree verification lock is held.

Invariant-to-Regression Map

Names below are runnable test filters, not a test-count target. floor_tests refers to crates/lib/src/auth/validation/floor_tests.rs; validation::tests to its sibling tests.rs. Integration paths are relative to crates/lib/tests/it/.

InvariantRegressions
Equal/old causal snapshots remain valid; observed reductions cannot regressfloor_allows_equal_snapshot, floor_allows_stale_snapshot_when_ancestry_is_older, floor_blocks_removed_member_below_acknowledged_removal (floor_tests)
Join every parent, including incomparable snapshots and observations carried through other signersfloor_joins_all_parents_over_incomparable_frontiers, floor_carries_through_direct_key_intermediate, floor_carries_through_other_identity_intermediate, floor_is_keyed_by_tree_not_signer (floor_tests)
Direct Admin rewind allowed; descendants retain floors; same-entry delegated pointer write covers new pointerpointer_add_delegated_tree_allows_admin_rewind, pointer_raw_settings_write_allows_admin_rewind, pointer_advance_raises_committed_floor_for_descendants, same_entry_delegated_pointer_write_must_cover_new_pointer (floor_tests)
Effective removal resets direct floors, not a losing delta; merged absence clears active-parent observationspointer_effective_removal_resets_floor, losing_removal_does_not_clear_active_branch_floor, merged_absence_clears_even_an_active_branch_claim, delegated_removal_cannot_evade_inherited_revocation (floor_tests)
All path roots are tracked; overlapping direct removal cannot erase nested observationsfloor_applies_to_every_step_of_a_nested_path, nested_floor_survives_first_hop_removal_and_readd, overlapping_direct_and_nested_root_removal_keeps_nested_floor (floor_tests)
Empty/foreign tips invalid; Verified tip alone is not complete proof; unsettled ancestry can recovernested_empty_configured_pointer_is_invalid, floor_check_still_rejects_wrong_tree_tips, delegated_present_unverified_tip_is_retryable, delegated_verified_tip_requires_every_ancestor_verified (floor_tests); missing-root/tip/intermediate rejection-and-retry tests in database/tests.rs
Bounds apply before path/tip amplificationtest_delegation_path_length_capped, test_delegation_tips_count_capped (validation::tests)
Cache miss cannot erase a floor; published state is not a status promotionverified_parent_cache_miss_rebuilds_floor, published_projection_cannot_promote_unverified_parent (floor_tests)
Main-parent frontier matches signed pre-write pin and settings subtree parents; revocation does not reject old siblingssettings_parent_frontier_omission_and_extraneous_edge_fail_remote, non_settings_pin_cannot_promote_unsigned_child, revoked_signer_cannot_pin_old_settings_below_revocation, missing_main_ancestor_defers_forged_pin_decision_until_retry (floor_tests)
Local historical pin agrees with later verification; bootstrap is genesis-onlytest_historical_transaction_pins_main_parent_settings (database/settings_metadata.rs); test_non_genesis_first_auth_cannot_authorize_its_signer_locally, test_genesis_first_auth_survives_reverification, test_local_historical_commit_rejects_unverified_parent_without_storage (database/tests.rs)
Fixed boundary survives concurrent writes/grantsfixed_parent_subtree_read_ignores_concurrent_live_write, fixed_parent_auth_settings_read_ignores_concurrent_grant (transaction/tests.rs); fixed_parent_store_snapshot_ignores_concurrent_live_write, current_tips_query_returns_its_own_snapshot_after_concurrent_write (SQL traversal tests). These use forced interleavings, not timing sleeps
Historical backend traversal rejects partial/foreign ancestry; empty store name denotes main treehistorical_store_snapshot_rejects_missing_or_foreign_ancestry, historical_tree_tip_store_returns_boundary_tips (backend/subtree_operations.rs); test_backend_get_tree_from_tips_rejects_missing_intermediate, test_backend_get_tree_from_tips_rejects_foreign_ancestor (backend/tree_operations.rs)
Service retains undecidable entries; proven regressions fail; nonempty NotFound is not EMPTYtest_submit_missing_delegated_history_stays_retryable_and_invisible, test_submit_delegated_snapshot_regression_is_failed, remote_snapshot_at_preserves_missing_boundary_error (service.rs)
Offline reset retains Entries, clears terminal labels/caches, and dependency-first retry rebuilds usable dataexplicit_reset_keeps_entries_and_forgets_both_terminal_statuses, sqlite_reset_failure_rolls_back_cache_and_status_then_retries (backend/verification.rs); test_delegated_entry_synced_unverified_then_verified (auth/delegated_trees.rs, including reset/rebuild)
Signed wire/IDs unchanged, including nested paths; Snapshot remains Vec-compatibletest_dagcbor_wire_format_is_pinned, test_delegated_dagcbor_wire_format_is_pinned (entry/tests.rs); serializes_as_bare_id_array (snapshot.rs)

Floor unit tests use InMemory. Backend conformance tests and the delegated reset/rebuild receiver run on the local InMemory/SQLite/Postgres matrix; the latter uses InMemory when the service matrix is selected because verification is server-local. service.rs separately exercises the real Unix-socket boundary. Do not interpret a service matrix count as every raw backend test running over RPC.

For iteration, select these filters with cargo nextest run --all-features -E '...' inside the Nix dev shell. The completion gate is nix develop -c nix run .#fix followed by nix develop -c just nix full, including all backend matrices and integration VMs. A green suite is evidence for these invariants, not a proof of arbitrary security properties or automatic dependency acquisition.