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// The durable make-remote queue's read shapes, which a host renders.
128pub use db::{ExternalBlob, MakeRemoteProgress, QueuedDelete, QueuedUpload};
129
130/// The exact `rusqlite` coven owns the connection through. The host runs its app
131/// SQL via [`CovenHandle::sql`] / [`CovenHandle::write`] against this same crate
132/// — use `coven::rusqlite::{params, Row, …}` rather than depending on `rusqlite`
133/// directly, so the host can never drift onto a `libsqlite3-sys` version that
134/// conflicts with coven's.
135pub use rusqlite;
136
137// Blob descriptors, errors, the host-implemented observer.
138pub use blob::cache::{BlobCacheError, BlobStream};
139pub use blob::{
140    BlobRef, BlobReplacement, BlobScope, BlobTransitionObserver, CacheFill, Provenance,
141    RowBlobAuthority, RowBlobRef,
142};
143
144// Applied-sync change notification (the host reacts to these).
145pub use changeset::{ChangeOp, RowChange};
146
147// Host schema declaration: the synced-table set plus the synced-schema migration
148// ladder the host registers with the builder.
149pub use migration::{Migration, MigrationStep};
150pub use sync::session::{BlobDecl, RowIdentity, SyncedTable};
151
152// Config.
153pub use config::{
154    CloudHomeConfig, CloudProvider, Config, ConfigError, CustomS3ExactSlots, HomeStorage,
155};
156
157// Keys / oauth / keyring bootstrap. The keyring service name has a setter and the
158// two getters that pair with it; the OAuth registration takes/returns its creds
159// and tokens.
160pub use keys::{CloudHomeCredentials, KeyError, MasterKeyCustody, UserKeypair};
161
162// At-rest crypto the host configures (the host sizes cloud stream reads from
163// `CHUNK_SIZE`), and the store directory the host points coven at. `MasterKeyring`
164// is the master-key custody value type; `EncryptionService` is the cipher coven
165// builds from it internally. `SealError` is what the handle's app-data sealing
166// returns.
167pub use encryption::{
168    EncryptionError, EncryptionService, KeyFingerprint, MasterKeyring, SealError, CHUNK_SIZE,
169};
170pub use store_dir::{StoreDir, StoreLayout};
171
172// Sync vocabulary exposed through the public handle.
173pub use sync::circle::{
174    Audience, Circle, CircleCloseParticipant, CircleCloseSettlement, CircleCloseStatus,
175    CircleControlCoord, CircleEpochCloseId, CircleId, CircleInfo, CircleMemberInfo,
176    CircleOperationBlock, CircleOperationId, CircleOperationInfo, CircleOperationKind,
177    CircleOperationState, CircleRole, CircleState,
178};
179pub use sync::hlc::{Hlc, Timestamp, UpdatedAtStamper};
180pub use sync::membership::{
181    MemberInfo, MemberRole, MembershipConflictChoice, MembershipConflictInfo,
182};
183pub use sync::store::{
184    DeviceJoinAbandonment, DeviceJoinAction, DeviceJoinActivation, DeviceJoinCancellation,
185    DeviceJoinCleanupActivation, DeviceJoinCleanupProgress, DeviceJoinCleanupReceipt,
186    DeviceJoinError, DeviceJoinOffer, DeviceJoinProducer, DeviceJoinProducerWriteRevocation,
187    DeviceJoinReadiness, DeviceJoinRole, DeviceJoinStatus, DeviceJoinWriteRevocationExecutor,
188    DeviceProviderAccessAdministrator, DeviceProviderAccessRequest, DeviceProviderAdmission,
189    DeviceProviderAdmissionApproval, DeviceProviderAdmissionCompletion, DeviceProviderReadiness,
190    DeviceRegistrationRequest, JoinedStore, JoinerJoinClosure, JoinerJoinTerminal,
191    ProviderAdminJoinClosure, ProviderAdminJoinTerminal, ProviderReadyDeviceBootstrap,
192    ProviderWriteAuthorityRef, ProvisionalDeviceBootstrap,
193};
194
195// Sync setup / restore / join bootstrap.
196pub use join_code::{decode_invite_code_info, decode_join_request, JoinCodeError};
197pub use sync::restore_code::{
198    decode_restore_code_info, ActivatedContinuation, OwnerRecoveryAuthority, RestoreAuthority,
199};
200
201// Cloud at-rest cipher (host configures / tests inject).
202pub use sync::cloud_storage::CloudCipher;
203
204// --- Host-facing surface with no internal coven caller: each is a public-API-only
205//     item the host reaches (named by a public signature, or constructed/read by
206//     the host), cross-checked against the host's usage and coven's module docs. ---
207
208// Clock + id abstractions the host injects: real impls plus the deterministic
209// test fakes coven shares so the host tests against the same ones.
210pub use clock::{Clock, ClockRef, SystemClock};
211#[cfg(any(test, feature = "test-utils"))]
212pub use clock::{FixedClock, SteppingClock};
213#[cfg(any(test, feature = "test-utils"))]
214pub use id_provider::SequentialIdProvider;
215pub use id_provider::{IdProvider, IdRef, UuidProvider};
216pub use write::{
217    AffectedRow, PendingWrite, PublishedPosition, WriteBlock, WriteId, WriteReceipt,
218    WriteResolution, WriteRetractionWitness, WriteStatus,
219};
220
221// Managed local blob store: the host constructs it; coven never does.
222pub use storage::cloud::{
223    BlobBody, BoxPartSink, CloudAccessOutcome, CloudAccessState, CloudHome, CloudHomeError,
224    CloudHomeJoinInfo, CloudObjectVersion, CloudVersionedObject, PartSink, UploadProgress,
225};
226
227// Mobile OAuth: hosts whose OS captures the redirect drive the flow through
228// these instead of the desktop browser-callback `sign_in_*` above.
229pub use sync::store::{HeldStoreCoordinate, HeldStorePosition, HeldStorePositionReason};
230
231// Sync-status surface: the completed-cycle success payload, the per-cycle alert
232// bundle it carries, and the per-device activity a host renders "which devices
233// synced, and when" from.
234pub use sync::loop_policy::{SyncLoopAlerts, SyncLoopSuccess};
235pub use sync::status::DeviceActivity;
236pub use sync::store_commit::{
237    CommitFrontier, ObjectHash, StoreBatchCommitRef, StoreCommitCoord, StoreCommitOrder,
238    StoreDeviceId,
239};
240
241// In-memory cloud home for host integration tests.
242#[cfg(any(test, feature = "test-utils"))]
243pub use storage::cloud::test_utils::InMemoryCloudHome;