Skip to main content

coven_core/
lib.rs

1//! coven — end-to-end encrypted, multi-writer, bring-your-own-storage SQLite
2//! sync, with an encrypted blob store and a cryptographic membership model.
3//!
4//! The host app owns its SQLite schema and domain. coven owns the sync layer:
5//! changesets captured via the SQLite session extension, HLC-stamped
6//! and signed per author, encrypted and pushed/pulled through a pluggable
7//! `CloudHome`, conflict-resolved per row by `_updated_at` arbitration with a
8//! column-level premerge so concurrent edits to different columns of a row survive.
9//! An append-only Ed25519-signed membership chain wraps the per-store
10//! symmetric key to each member.
11//!
12//! Integration contract for the host:
13//! - coven OWNS the SQLite connection. The host opens it once with
14//!   [`Coven::builder(config)`], declares synced tables and its synced-schema
15//!   [`Migration`] ladder, and calls `open`: coven installs or verifies its one
16//!   current internal schema, then runs the host's migration ladder over
17//!   `PRAGMA user_version` for the app's tables and seeds the register clock off
18//!   the rows on disk. The host runs its SQL through [`CovenHandle::sql`] or
19//!   [`CovenHandle::write`]; coven captures each write into the pending-changeset
20//!   journal as it commits, inside its own journaled transaction.
21//! - Every synced table has an `id` text primary key at column 0 and an
22//!   `_updated_at TEXT NOT NULL` column, and is declared as a
23//!   [`SyncedTable`] in the builder's `synced_tables` set with a required
24//!   [`RowIdentity`]. `(table, id)` is one logical row across every device.
25//!   Independently created rows use canonical UUIDv4 or UUIDv7 ids; shared-key
26//!   tables intentionally merge equal application keys. A primary-key change
27//!   removes the old identity and inserts the new validated identity. A plain
28//!   [`sync::session::SyncedTable::new`] table syncs unconditionally;
29//!   [`sync::session::SyncedTable::remote_root`] also syncs the whole table and
30//!   makes blobs on the row or its FK-descendants always Remote;
31//!   [`sync::session::SyncedTable::gated_by`] marks a *gated root* whose boolean
32//!   gate column decides, per row, whether that row and its declared
33//!   FK-descendants are shared. See [`sync::gate`] for the gating semantics.
34//! - `_updated_at` is coven's last-writer-wins register, an opaque Hybrid Logical
35//!   Clock stamp the host mints with [`SqlContext::stamp`] and binds into every
36//!   synced-row write. The host must not parse or compare the stamp as a wall-clock
37//!   time; coven advances the clock past pulled rows so a later local write always
38//!   sorts causally after them, which a wall clock cannot guarantee under skew.
39//! - When a cloud provider is connected, the host calls
40//!   [`CovenHandle::connect_sync`]. Which rows carry blobs is declared per table
41//!   via [`sync::session::SyncedTable::carries_blob`]; the host also supplies an
42//!   optional [`blob::BlobTransitionObserver`] to the builder. A local-only store
43//!   that never connects a provider still reads and writes through the handle.
44//!
45//! # Blob storage model
46//!
47//! A blob is opaque bytes a synced row references — a photo, an audio file, a
48//! cover image. Each blob declares two orthogonal properties and has one runtime
49//! state. [`blob`] holds the full concept tree; the summary:
50//!
51//! - **Provenance** ([`blob::Provenance`]) — where the bytes live while the blob is
52//!   *Local*. *User-provided*: the user's own file at a path coven references (the
53//!   Remote→Local restore writes back to a user path, so it needs one).
54//!   *Host-provided*: data the host hands coven, which coven keeps in its own local
55//!   store at `storage/local/<namespace>/<id>` (no path needed to restore).
56//! - **Cache fill** ([`blob::CacheFill`]) — how a device gets the bytes while the
57//!   blob is *Remote*. [`CacheEager`](blob::CacheFill::CacheEager) fetches into the
58//!   cache on pull (cover art, so a grid renders from local bytes);
59//!   [`CacheLazy`](blob::CacheFill::CacheLazy) fetches on first read (audio).
60//! - **Locality** — the state: *Local* (bytes on-device, in the user's file or
61//!   coven's local store) or *Remote* (bytes in the cloud, each device's copy a
62//!   cache copy). `make_remote` uploads the bytes and flips a gated root's gate on;
63//!   `make_local` brings them back to a local file and flips it off (see
64//!   [`blob::transition`]). A remote root starts Remote and rejects those
65//!   transitions.
66//!
67//! The **cache** ([`blob::cache`]) is a Remote-only mechanism: it holds
68//! re-fetchable copies of Remote blobs under `storage/cache/<namespace>/…`
69//! (evictable, against a per-namespace size budget — [`CovenHandle::set_cache_budget`])
70//! and `storage/pinned/<namespace>/…` (kept). A Local blob is never in the cache,
71//! and `CacheEager`/`CacheLazy`/pin/budget describe a blob only while it is Remote.
72//! An *asset* (a cover, an artist image — [`sync::session::SyncedTable::asset`])
73//! rides its subject's gate but never keeps the subject alive.
74
75// coven-core's documented public API is exactly the crate-root re-exports below.
76// The implementation modules are `#[doc(hidden)] pub`: reachable by `coven`,
77// which needs the internals to build the public engine, but excluded from the
78// documented surface and not part of the host API. A host
79// depends on `coven`, whose own modules are `pub(crate)`, so it reaches the
80// engine only through the curated re-exports, never through `coven_core::sync::…`
81// or `coven::sync::…`.
82//
83// The blob engine: the vocabulary types plus the two lifecycle halves —
84// `blob::cache` (bytes on disk, folder-truth retention) and `blob::upload` (drain
85// the durable upload queue to the cloud).
86#[doc(hidden)]
87pub mod blob;
88#[doc(hidden)]
89pub mod changeset;
90#[doc(hidden)]
91pub mod clock;
92mod write;
93// Shared wire format for pasted codes (invite, restore): prefix + base64url(json).
94mod code_envelope;
95#[doc(hidden)]
96pub mod config;
97#[doc(hidden)]
98pub mod database;
99#[doc(hidden)]
100pub mod db;
101#[doc(hidden)]
102pub mod encryption;
103#[doc(hidden)]
104pub mod id_provider;
105#[doc(hidden)]
106pub mod join_code;
107#[doc(hidden)]
108pub mod keys;
109#[doc(hidden)]
110pub mod store_dir;
111// The host's synced-schema ladder: ordered migrations tracked in `PRAGMA
112// user_version`, which doubles as the wire `schema_version`.
113#[doc(hidden)]
114pub mod migration;
115// The device-local plaintext file behind each `BlobRef`: read on push, written on
116// pull, backed directly by the local filesystem.
117#[doc(hidden)]
118pub mod local_blob;
119#[doc(hidden)]
120pub mod storage;
121#[doc(hidden)]
122pub mod sync;
123
124// The curated public API — the only names a host is meant to touch.
125
126pub use database::DbError;
127
128/// The exact `rusqlite` coven owns the connection through. The host runs its app
129/// SQL via [`CovenHandle::sql`] / [`CovenHandle::write`] against this same crate
130/// — use `coven::rusqlite::{params, Row, …}` rather than depending on `rusqlite`
131/// directly, so the host can never drift onto a `libsqlite3-sys` version that
132/// conflicts with coven's.
133pub use rusqlite;
134
135// Blob descriptors, errors, the host-implemented observer.
136pub use blob::cache::BlobCacheError;
137pub use blob::{
138    BlobRef, BlobReplacement, BlobScope, BlobTransitionObserver, CacheFill, Provenance,
139    RowBlobAuthority, RowBlobRef,
140};
141
142// Applied-sync change notification (the host reacts to these).
143pub use changeset::{ChangeOp, RowChange};
144
145// Host schema declaration: the synced-table set plus the synced-schema migration
146// ladder the host registers with the builder.
147pub use migration::{Migration, MigrationStep};
148pub use sync::session::{BlobDecl, RowIdentity, SyncedTable};
149
150// Config.
151pub use config::{
152    CloudHomeConfig, CloudProvider, Config, ConfigError, CustomS3ExactSlots, HomeStorage,
153};
154
155// Keys / oauth / keyring bootstrap. The keyring service name has a setter and the
156// two getters that pair with it; the OAuth registration takes/returns its creds
157// and tokens.
158pub use keys::{CloudHomeCredentials, KeyError, MasterKeyCustody, UserKeypair};
159
160// At-rest crypto the host configures (the host sizes cloud stream reads from
161// `CHUNK_SIZE`), and the store directory the host points coven at. `MasterKeyring`
162// is the master-key custody value type; `EncryptionService` is the cipher coven
163// builds from it internally. `SealError` is what the handle's app-data sealing
164// returns.
165pub use encryption::{
166    EncryptionError, EncryptionService, KeyFingerprint, MasterKeyring, SealError, CHUNK_SIZE,
167};
168pub use store_dir::{StoreDir, StoreLayout};
169
170// Sync vocabulary exposed through the public handle.
171pub use sync::circle::{
172    Audience, Circle, CircleCloseParticipant, CircleCloseSettlement, CircleCloseStatus,
173    CircleControlCoord, CircleEpochCloseId, CircleId, CircleInfo, CircleMemberInfo,
174    CircleOperationBlock, CircleOperationId, CircleOperationInfo, CircleOperationKind,
175    CircleOperationState, CircleRole, CircleState,
176};
177pub use sync::hlc::{Hlc, Timestamp, UpdatedAtStamper};
178pub use sync::membership::{
179    MemberInfo, MemberRole, MembershipConflictChoice, MembershipConflictInfo,
180};
181pub use sync::store::{
182    DeviceJoinAbandonment, DeviceJoinAction, DeviceJoinActivation, DeviceJoinCancellation,
183    DeviceJoinCleanupActivation, DeviceJoinCleanupProgress, DeviceJoinCleanupReceipt,
184    DeviceJoinError, DeviceJoinOffer, DeviceJoinProducer, DeviceJoinProducerWriteRevocation,
185    DeviceJoinReadiness, DeviceJoinRole, DeviceJoinStatus, DeviceJoinWriteRevocationExecutor,
186    DeviceProviderAccessAdministrator, DeviceProviderAccessRequest, DeviceProviderAdmission,
187    DeviceProviderAdmissionApproval, DeviceProviderAdmissionCompletion, DeviceProviderReadiness,
188    DeviceRegistrationRequest, JoinedStore, JoinerJoinClosure, JoinerJoinTerminal,
189    ProviderAdminJoinClosure, ProviderAdminJoinTerminal, ProviderReadyDeviceBootstrap,
190    ProviderWriteAuthorityRef, ProvisionalDeviceBootstrap,
191};
192
193// Sync setup / restore / join bootstrap.
194pub use join_code::{decode_invite_code_info, decode_join_request, JoinCodeError};
195pub use sync::restore_code::{
196    decode_restore_code_info, ActivatedContinuation, OwnerRecoveryAuthority, RestoreAuthority,
197};
198
199// Cloud at-rest cipher (host configures / tests inject).
200pub use sync::cloud_storage::CloudCipher;
201
202// --- Host-facing surface with no internal coven caller: each is a public-API-only
203//     item the host reaches (named by a public signature, or constructed/read by
204//     the host), cross-checked against the host's usage and coven's module docs. ---
205
206// Clock + id abstractions the host injects: real impls plus the deterministic
207// test fakes coven shares so the host tests against the same ones.
208pub use clock::{Clock, ClockRef, SystemClock};
209#[cfg(any(test, feature = "test-utils"))]
210pub use clock::{FixedClock, SteppingClock};
211#[cfg(any(test, feature = "test-utils"))]
212pub use id_provider::SequentialIdProvider;
213pub use id_provider::{IdProvider, IdRef, UuidProvider};
214pub use write::{
215    AffectedRow, PendingWrite, PublishedPosition, WriteBlock, WriteId, WriteReceipt,
216    WriteResolution, WriteRetractionWitness, WriteStatus,
217};
218
219// Managed local blob store: the host constructs it; coven never does.
220pub use storage::cloud::{
221    BlobBody, BoxPartSink, CloudAccessOutcome, CloudAccessState, CloudHome, CloudHomeError,
222    CloudHomeJoinInfo, CloudObjectVersion, CloudVersionedObject, PartSink, UploadProgress,
223};
224
225// Mobile OAuth: hosts whose OS captures the redirect drive the flow through
226// these instead of the desktop browser-callback `sign_in_*` above.
227pub use sync::store::{HeldStoreCoordinate, HeldStorePosition, HeldStorePositionReason};
228
229// Sync-status surface: the completed-cycle success payload, the per-cycle alert
230// bundle it carries, and the per-device activity a host renders "which devices
231// synced, and when" from.
232pub use sync::loop_policy::{SyncLoopAlerts, SyncLoopSuccess};
233pub use sync::status::DeviceActivity;
234pub use sync::store_commit::{
235    CommitFrontier, ObjectHash, StoreBatchCommitRef, StoreCommitCoord, StoreCommitOrder,
236    StoreDeviceId,
237};
238
239// In-memory cloud home for host integration tests.
240#[cfg(any(test, feature = "test-utils"))]
241pub use storage::cloud::test_utils::InMemoryCloudHome;