pub struct CovenHandle { /* private fields */ }Expand description
The cipher a store’s app-data sealing runs under, resolved from custody.
A store whose custody unlocks None has no key to seal under or open with,
which is SealError::Locked — the same discipline the sync engine’s cipher
resolution keeps, where an opaque home with no established key refuses to
start rather than inventing one.
Shared by CovenHandle and CovenReadHandle so
both resolve the identical keyring the identical way; a payload one seals, the
other opens.
The handle over one coven store.
Open it once with Coven::builder, then call methods. Cheap to
clone — every field is shared (an Arc, a Clone handle, or a
reference-counted lock), so a clone drives the same retained owners as the
original.
§Using the handle
The host builds the handle once at startup and then only calls methods on it — it never assembles coven’s internals by hand or hands them back to coven on every call. Rows go through the connection coven owns; blobs go through the handle’s read/store methods; sync is optional.
// Rows: run app SQL on the connection coven owns.
let note_count: i64 = handle
.read(|sql| {
sql.query_row("SELECT count(*) FROM notes", [], |row| row.get(0))
.map_err(coven::CovenError::from)
})
.await?;
// Blobs: read an exact row version. coven resolves locality — the user's own
// file, its local store, the cache, or a cloud fetch — and returns plaintext.
let bytes: Vec<u8> = handle.read_blob(cover).await?;
// Sync is optional. Connect a provider, then drive it; a store with no
// cloud home never calls these and stays fully usable on-device.
handle.connect_sync().await?;
handle.sync_now();Implementations§
Source§impl CovenHandle
impl CovenHandle
pub async fn write<F, R>(&self, sql: F) -> CovenResult<WriteReceipt<R>>where
F: for<'context, 'connection> FnOnce(SqlContext<'context, 'connection>) -> CovenResult<R> + Send + 'static,
R: Send + 'static,
Sourcepub fn read<F, R>(&self, read: F) -> Read<'_, F>where
F: for<'connection> FnOnce(SqlReadContext<'connection>) -> CovenResult<R> + Send + 'static,
R: Send + 'static,
pub fn read<F, R>(&self, read: F) -> Read<'_, F>where
F: for<'connection> FnOnce(SqlReadContext<'connection>) -> CovenResult<R> + Send + 'static,
R: Send + 'static,
Read one consistent snapshot when awaited. Attach process to compute
a result on separate workers after releasing the connection.
Sourcepub fn subscribe<F, R>(&self, query: F) -> LiveQuery<R>where
F: for<'connection> Fn(SqlReadContext<'connection>) -> CovenResult<R> + Send + Sync + 'static,
R: Send + 'static,
pub fn subscribe<F, R>(&self, query: F) -> LiveQuery<R>where
F: for<'connection> Fn(SqlReadContext<'connection>) -> CovenResult<R> + Send + Sync + 'static,
R: Send + 'static,
Create a query that returns its initial value and runs again when a committed database change can affect it.
The query uses the same crate::SqlReadContext as read.
Coven records the tables and columns SQLite reads, and narrows supported
single-table primary-key predicates to their bound values. Other
predicates retain safe table-and-column invalidation.
Attach process to move result processing
off the read connection. Only the final delivered value needs Clone
and PartialEq.
Sourcepub fn subscribe_reconfigurable<Request, F, R>(
&self,
initial_request: Request,
query: F,
) -> ReconfigurableLiveQuery<Request, R>
pub fn subscribe_reconfigurable<Request, F, R>( &self, initial_request: Request, query: F, ) -> ReconfigurableLiveQuery<Request, R>
Create a tracked query whose absolute request can be replaced while the
subscription remains active.
Attach process to process
each result together with the request that produced it.
pub async fn write_with_blobs<F, S, R>(
&self,
build: F,
sql: S,
) -> CovenResult<WriteReceipt<R>>where
F: FnOnce(&mut WriteBatch) -> CovenResult<()> + Send + 'static,
S: for<'context, 'connection> FnOnce(SqlContext<'context, 'connection>) -> CovenResult<R> + Send + 'static,
R: Send + 'static,
Sourcepub fn subscribe_sync_status(&self) -> Receiver<SyncLoopStatus>
pub fn subscribe_sync_status(&self) -> Receiver<SyncLoopStatus>
Subscribe to the sync loop’s SyncLoopStatus stream. The channel is
owned by this handle, not the loop, so the receiver keeps working across a
reconnect and may be created before any provider is connected (it starts
receiving once a loop runs). Infallible for that reason — there is no loop
state to check.
The receiver immediately contains the current value. Intermediate values
may be coalesced; Synchronized.row_changes is a refresh hint rather than a
complete change stream.
Sourcepub fn subscribe_eager_cache_fill_status(
&self,
) -> Receiver<EagerCacheFillStatus>
pub fn subscribe_eager_cache_fill_status( &self, ) -> Receiver<EagerCacheFillStatus>
Subscribe to the post-open CacheEager fill. Enrollment installs rows and returns without artwork; the connected library then reports discovery, bounded-cadence download progress, completion, cancellation, or failure.
Sourcepub fn cancel_eager_cache_fill(&self)
pub fn cancel_eager_cache_fill(&self)
Stop post-open CacheEager downloads without stopping cloud sync.
Sourcepub async fn pending_writes(&self) -> Result<Vec<PendingWrite>, CovenError>
pub async fn pending_writes(&self) -> Result<Vec<PendingWrite>, CovenError>
Writes that have shared rows and have not reached a published position.
Sourcepub async fn blocked_writes(&self) -> Result<Vec<PendingWrite>, CovenError>
pub async fn blocked_writes(&self) -> Result<Vec<PendingWrite>, CovenError>
Writes stopped by a semantic publication fault and awaiting an explicit retry or discard decision.
Sourcepub async fn retry_blocked_write(
&self,
write_id: &WriteId,
) -> Result<Vec<WriteId>, CovenError>
pub async fn retry_blocked_write( &self, write_id: &WriteId, ) -> Result<Vec<WriteId>, CovenError>
Requeue one blocked write for full production validation. A connected sync loop is woken after the durable transition.
Sourcepub async fn retry_blocked_operation(
&self,
operation: BlockedOperationId,
) -> Result<(), RetryBlockedOperationError>
pub async fn retry_blocked_operation( &self, operation: BlockedOperationId, ) -> Result<(), RetryBlockedOperationError>
Hand one blocked operation back to the sync loop, whichever kind it is.
The host renders SyncLoopStatus::Blocked’s operations as one list
with one button, so it retries them through one call; the id says which
path the retry takes. Each kind revalidates from scratch, so an
operation whose cause still stands simply blocks again.
Sourcepub async fn discard_blocked_write(
&self,
write_id: &WriteId,
) -> Result<Vec<WriteId>, CovenError>
pub async fn discard_blocked_write( &self, write_id: &WriteId, ) -> Result<Vec<WriteId>, CovenError>
Atomically discard a blocked write and reverse every later unpublished shared write whose working-row state depends on it.
Sourcepub async fn write_status(
&self,
write_id: &WriteId,
) -> Result<WriteStatus, CovenError>
pub async fn write_status( &self, write_id: &WriteId, ) -> Result<WriteStatus, CovenError>
Read the current durable status of one write.
Sourcepub async fn subscribe_write_status(
&self,
write_id: &WriteId,
) -> Result<Receiver<WriteStatus>, CovenError>
pub async fn subscribe_write_status( &self, write_id: &WriteId, ) -> Result<Receiver<WriteStatus>, CovenError>
Subscribe to one write’s current durable status. The initial value is reconstructed from SQLite before the receiver is returned.
Sourcepub async fn connect_sync(&self) -> Result<(), SyncError>
pub async fn connect_sync(&self) -> Result<(), SyncError>
Build the connected cloud storage, start its sync loop, and install the connection. If the cloud home fails to build, no connection is installed.
The at-rest cipher is resolved from the handle’s custody per start: an
opaque home unlocks the master keyring (failing with
SyncError::MasterKeyNotEstablished if none is established), a
browsable one never consults custody. Reconnecting a provider replaces
the cloud home and loop while retaining the Store database and clock.
Sourcepub async fn probe_cloud_home(&self, config: &Config) -> Result<(), SyncError>
pub async fn probe_cloud_home(&self, config: &Config) -> Result<(), SyncError>
Build and probe the cloud home described by config without installing
it as this handle’s sync connection. Hosts use this to validate proposed
provider settings before committing them to their config source.
Sourcepub async fn setup_s3_cloud_home(
&self,
cloud_home: CloudHomeConfig,
access_key: String,
secret_key: String,
) -> Result<ConnectedCloudHome, CloudHomeSetupError>
pub async fn setup_s3_cloud_home( &self, cloud_home: CloudHomeConfig, access_key: String, secret_key: String, ) -> Result<ConnectedCloudHome, CloudHomeSetupError>
Connect a new S3 cloud home and commit its credentials and any generated opaque-home master key only after the replacement connection is ready.
Sourcepub async fn setup_cloudkit_cloud_home(
&self,
cloud_home: CloudHomeConfig,
cloudkit_ops: Arc<dyn CloudKitOps>,
) -> Result<ConnectedCloudHome, CloudHomeSetupError>
pub async fn setup_cloudkit_cloud_home( &self, cloud_home: CloudHomeConfig, cloudkit_ops: Arc<dyn CloudKitOps>, ) -> Result<ConnectedCloudHome, CloudHomeSetupError>
Connect a new CloudKit cloud home and commit any generated opaque-home master key only after the replacement connection is ready.
Sourcepub async fn setup_oauth_cloud_home(
&self,
cloud_home: CloudHomeConfig,
cancel: Receiver<bool>,
) -> Result<ConnectedCloudHome, CloudHomeSetupError>
pub async fn setup_oauth_cloud_home( &self, cloud_home: CloudHomeConfig, cancel: Receiver<bool>, ) -> Result<ConnectedCloudHome, CloudHomeSetupError>
Authorize and connect a new Google Drive, Dropbox, or OneDrive home. Tokens remain proposed until the replacement connection is ready.
Sourcepub fn cloud_home_key_state(
&self,
storage: HomeStorage,
) -> Result<CloudHomeKeyState, KeyError>
pub fn cloud_home_key_state( &self, storage: HomeStorage, ) -> Result<CloudHomeKeyState, KeyError>
Whether a home with this storage policy needs and can unlock its key.
Sourcepub async fn unlock_cloud_home(
&self,
serialized_master_key: &str,
) -> Result<ConnectedCloudHome, CloudHomeUnlockError>
pub async fn unlock_cloud_home( &self, serialized_master_key: &str, ) -> Result<ConnectedCloudHome, CloudHomeUnlockError>
Import the master key for this returning opaque cloud home, verify it against the signed Store root, and connect without retaining a rejected key.
pub async fn connect_sync_with_cloudkit( &self, cloudkit_ops: Arc<dyn CloudKitOps>, ) -> Result<(), SyncError>
Sourcepub fn connect_sync_with_test_home(
&self,
home: Arc<dyn ExactCloudHome>,
cipher: CloudCipher,
) -> impl Future<Output = Result<(), CloudHomeSetupError>> + Send + '_
pub fn connect_sync_with_test_home( &self, home: Arc<dyn ExactCloudHome>, cipher: CloudCipher, ) -> impl Future<Output = Result<(), CloudHomeSetupError>> + Send + '_
Test-only: connect a started sync loop over an injected ExactCloudHome
instead of one built from crate::Config, so a host’s integration tests drive
the real make-Remote / make-Local / upload-drain and read paths over a mock
cloud with no live provider.
The test counterpart of connect_sync: it builds
storage over home/cipher, prepares the configured home’s master key,
starts the loop, and commits a newly generated key and the connection
together only after startup succeeds. The explicit cipher protects the
injected storage; the master key separately protects Store routing data.
The read path needs no separate hook: blob_storage
serves reads from the connected loop’s own CloudSyncConnection, which here
wraps the injected home, so read_blob /
pin resolve a Remote miss against the same test home the
drain writes to.
Sourcepub async fn setup_cloud_home_with_test_home(
&self,
cloud_home: CloudHomeConfig,
home: Arc<dyn ExactCloudHome>,
credentials: Option<CloudHomeCredentials>,
) -> Result<ConnectedCloudHome, CloudHomeSetupError>
pub async fn setup_cloud_home_with_test_home( &self, cloud_home: CloudHomeConfig, home: Arc<dyn ExactCloudHome>, credentials: Option<CloudHomeCredentials>, ) -> Result<ConnectedCloudHome, CloudHomeSetupError>
Test-only: atomically set up a proposed cloud home over an injected provider while exercising the production key and connection transaction.
Sourcepub async fn unlock_cloud_home_with_test_home(
&self,
serialized_master_key: &str,
home: Arc<dyn ExactCloudHome>,
) -> Result<ConnectedCloudHome, CloudHomeUnlockError>
pub async fn unlock_cloud_home_with_test_home( &self, serialized_master_key: &str, home: Arc<dyn ExactCloudHome>, ) -> Result<ConnectedCloudHome, CloudHomeUnlockError>
Test-only: unlock a returning opaque home over an injected provider while exercising the production key-and-connection transaction.
Sourcepub fn connect_sync_with_test_home_caller_driven(
&self,
home: Arc<dyn ExactCloudHome>,
cipher: CloudCipher,
) -> impl Future<Output = Result<(), CloudHomeSetupError>> + Send + '_
pub fn connect_sync_with_test_home_caller_driven( &self, home: Arc<dyn ExactCloudHome>, cipher: CloudCipher, ) -> impl Future<Output = Result<(), CloudHomeSetupError>> + Send + '_
Test-only: connect over an injected ExactCloudHome exactly as
connect_sync_with_test_home does,
but start no background loop — the caller drives sync itself.
The loop-started connect and an explicit
drain_uploads are two drainers of one queue. They
take turns rather than overlap, so whichever runs second drains only what
the first left — a host asserting on its own drain’s count reads that as
“nothing was queued” and fails intermittently. Here no cycle exists to
share the queue with: the host’s drain_uploads is the only drain, its
count is the whole truth, and is_syncing stays
false for the connection’s whole life.
Everything a connected store can do is available — make_remote,
make_local, the drain, membership — because none of it needs the loop
thread. Circle writes are the exception: they are dispatched to that
thread, so they refuse with
CircleError::LoopNotRunning here.
Sourcepub async fn connect_sync_with_test_home_custody(
&self,
home: Arc<dyn ExactCloudHome>,
) -> Result<(), SyncError>
pub async fn connect_sync_with_test_home_custody( &self, home: Arc<dyn ExactCloudHome>, ) -> Result<(), SyncError>
Test-only: connect over an injected ExactCloudHome while resolving the
at-rest cipher from custody the way production
connect_sync does, instead of taking an explicit
cipher like connect_sync_with_test_home.
Where that method prepares a missing master key as part of its connection
transaction, this requires an existing key and drives the connection path
used by production, which unlocks the master keyring through the store’s
custody exactly as start_sync would — so a
test can establish a key, connect over a mock home, and prove the traffic
is sealed under that key. An opaque home with no key established fails
SyncError::MasterKeyNotEstablished before the loop starts.
Sourcepub async fn start_sync(&self) -> Result<(), SyncError>
pub async fn start_sync(&self) -> Result<(), SyncError>
Start (or restart) the sync loop of the installed connection. A no-op when no provider is connected — a home-less store has nothing to start. Errors if the connected cloud home fails to build.
Sourcepub fn stop_sync(&self)
pub fn stop_sync(&self)
Stop the sync loop after the in-flight cycle while keeping the provider
connected so start_sync can resume it. A no-op when
no provider is connected.
The material a running loop resolved from custody (the master keyring,
the device signing identity) is cached only inside that loop for as
long as it runs — nowhere else in the handle — and this is where it is
purged. A subsequent start_sync/
connect_sync re-resolves fresh from whatever
custody now serves, so a host’s lock flow that stops sync as part of
locking, then later reconnects, never resumes on stale material.
Sourcepub fn disconnect_sync(&self)
pub fn disconnect_sync(&self)
Disconnect the provider entirely: stop the loop and drop the connection.
The store becomes home-less until the next
connect_sync.
Carries the same purge as stop_sync, so nothing about
the previous connection — including which custody it resolved material
from — survives into the next connect.
Sourcepub async fn disconnect_cloud_home(&self) -> Result<(), SyncError>
pub async fn disconnect_cloud_home(&self) -> Result<(), SyncError>
Disconnect the configured cloud home and remove its provider credentials. If credential removal fails, the installed connection is preserved.
Sourcepub fn sync_now(&self)
pub fn sync_now(&self)
Wake the sync loop to run a cycle now rather than at the next idle tick. A no-op when no provider is connected.
Sourcepub fn is_syncing(&self) -> bool
pub fn is_syncing(&self) -> bool
Whether the sync loop is running. false for a home-less store.
Sourcepub fn is_connected(&self) -> bool
pub fn is_connected(&self) -> bool
Whether a provider connection is installed. Distinct
from is_syncing, which additionally requires the loop
to be running: this is the predicate a host uses for “has a cloud home”
without the loop-ready condition.
Sourcepub async fn import_master_key(
&self,
serialized: &str,
) -> Result<(), MasterKeyError>
pub async fn import_master_key( &self, serialized: &str, ) -> Result<(), MasterKeyError>
Import a serialized master keyring a host already holds and establish it under the handle’s custody, replacing whatever custody already holds.
Sourcepub async fn forget_master_key(&self) -> Result<(), SyncError>
pub async fn forget_master_key(&self) -> Result<(), SyncError>
Remove the master key from custody and disconnect any operation retaining its unlocked value. If custody cannot remove the key, the connection is preserved and the error is returned.
Sourcepub fn initialize_identity(&self) -> Result<String, IdentityError>
pub fn initialize_identity(&self) -> Result<String, IdentityError>
Generate this store’s signing identity and establish it under the
handle’s identity custody. Errors with
IdentityError::AlreadyEstablished if custody already unlocks one —
coven never generates over an existing identity. This is the identity
counterpart of cloud-home setup’s master-key transaction for a store a
host is creating fresh (not joining or restoring, which each establish
their own identity as part of what they do). Returns the established
public key, hex-encoded.
Sourcepub fn transfer_limits(&self) -> TransferLimits
pub fn transfer_limits(&self) -> TransferLimits
Set a host’s own store-scoped secret — an API token, a service
credential — under the same platform keyring, and the same access
policy, as coven’s own key material. name identifies the secret
within the store; coven owns the account rendering and the entry’s
protection class. KeyError::InvalidSecretName if name collides
with one of coven’s own reserved slot names, is empty, or contains
:.
The concurrent blob-transfer limits in force: how many uploads an
upload-drain pass runs at once and how many downloads a pin fetches at
once.
Sourcepub fn set_transfer_limits(&self, limits: TransferLimits)
pub fn set_transfer_limits(&self, limits: TransferLimits)
Replace the transfer limits while the store is open. Every later
upload-drain pass and pin call runs under the new limits; a pass
already running keeps the limit it admitted under. The builder’s
max_concurrent_uploads / max_concurrent_downloads set the initial
values.
pub fn set_host_secret(&self, name: &str, value: &str) -> Result<(), KeyError>
Sourcepub fn host_secret(&self, name: &str) -> Result<Option<String>, KeyError>
pub fn host_secret(&self, name: &str) -> Result<Option<String>, KeyError>
Read a host secret set by set_host_secret,
None if never set. A present-but-empty entry is corrupt, not
absent — the same discipline coven’s own key reads apply.
Sourcepub fn delete_host_secret(&self, name: &str) -> Result<(), KeyError>
pub fn delete_host_secret(&self, name: &str) -> Result<(), KeyError>
Remove a host secret. Ok whether or not one was set.
Sourcepub fn seal_app_data(
&self,
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, SealError>
pub fn seal_app_data( &self, plaintext: &[u8], aad: &[u8], ) -> Result<Vec<u8>, SealError>
Seal plaintext under the store’s current master-key generation, for a
host to store in its own rows — a password entry’s payload, an API token.
coven’s at-rest encryption is cloud-side; the local database is plaintext
SQLite, so a host with a secret to keep in a row seals it here first.
The output records the generation it was sealed under, so it stays
openable after any number of key rotations. aad binds the ciphertext to
its context — the owning row’s primary key, say — and
open_app_data with a different aad fails, so a
payload moved to another row does not silently open there.
SealError::Locked if the store has no established master key, the same
gate connect_sync applies before it seals cloud
traffic.
Sourcepub fn open_app_data(
&self,
sealed: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, SealError>
pub fn open_app_data( &self, sealed: &[u8], aad: &[u8], ) -> Result<Vec<u8>, SealError>
Open a payload seal_app_data produced, under
whichever generation it names — a rotated keyring still opens everything
it sealed before rotating.
SealError::Locked if the store is locked; a wrong aad, a tampered
payload, an unreadable version, or a generation this store’s keyring lacks
each surface their own typed error.
Sourcepub async fn row_blob_ref(
&self,
table: &str,
row_id: &str,
) -> Result<RowBlobRef, DbError>
pub async fn row_blob_ref( &self, table: &str, row_id: &str, ) -> Result<RowBlobRef, DbError>
Capture the exact current blob-bearing row version. Blob operations use this row-bound value so a later row replacement cannot redirect a read.
Sourcepub async fn read_blob(
&self,
blob: &RowBlobRef,
) -> Result<Vec<u8>, BlobCacheError>
pub async fn read_blob( &self, blob: &RowBlobRef, ) -> Result<Vec<u8>, BlobCacheError>
Read a blob’s whole plaintext through coven’s locality-aware read: served
from the user’s file (Local user-provided), coven’s local store (Local
host-provided), the pinned/evictable cache on a Remote hit, or fetched
from the cloud (into the cache) on a Remote miss. The host passes the
RowBlobRef captured from row_blob_ref; coven
holds the database, directory, and storage.
Sourcepub async fn materialize_row_blob(
&self,
blob: &RowBlobRef,
) -> Result<(), BlobCacheError>
pub async fn materialize_row_blob( &self, blob: &RowBlobRef, ) -> Result<(), BlobCacheError>
Ensure the exact current row blob plaintext is durable on this device. Remote blobs materialize into their locator-keyed cache path; Local and pending-remote blobs exact-verify their authoritative local source.
Sourcepub async fn open_blob_stream(
&self,
blob: &RowBlobRef,
) -> Result<BlobStream, BlobCacheError>
pub async fn open_blob_stream( &self, blob: &RowBlobRef, ) -> Result<BlobStream, BlobCacheError>
Open an exact row blob’s plaintext for ranged reading, for streaming or
seeking without loading the whole file. The ranged sibling of
read_blob, which stays the one-shot whole read.
Opening resolves the blob’s locality, proves the plaintext’s size and
content hash against the row, and holds the open file; every
BlobStream::read_at then costs only the bytes it returns. Hold the
stream for as long as the host is reading that blob — a stream per opened
file, not per range — since re-opening re-proves the whole blob.
Sourcepub async fn pin(&self, blobs: &[RowBlobRef]) -> Result<(), BlobCacheError>
pub async fn pin(&self, blobs: &[RowBlobRef]) -> Result<(), BlobCacheError>
Pin a Remote blob set for offline: coven fetches each into the protected
cache (storage/pinned/) — from the evictable cache if already there, else
the cloud — exempt from the size budget. Idempotent.
Sourcepub async fn unpin(&self, blobs: &[RowBlobRef]) -> Result<(), BlobCacheError>
pub async fn unpin(&self, blobs: &[RowBlobRef]) -> Result<(), BlobCacheError>
Unpin a Remote blob set: coven moves each from storage/pinned/ to the
evictable storage/cache/ (still readable, now droppable). No cloud read.
Sourcepub fn blob_cloud_key(&self, blob: &BlobRef) -> Result<String, StorageError>
pub fn blob_cloud_key(&self, blob: &BlobRef) -> Result<String, StorageError>
The cloud object key a blob’s bytes live at, derived under the connected
home’s path scheme (Hashed → {namespace}/{ab}/{cd}/{id}, Plain →
{namespace}/{cloud_path}).
Read-only: coven owns this derivation and every operation that needs a key derives its own (a delete resolves it from the stored ref), so nothing a host calls takes one back. It exists so a host can observe the key coven would use — asserting an upload landed where a read looks for it, or naming an object in a diagnostic — without reimplementing the layout and drifting from it.
A Plain home whose cloud_path is absent, or does not name the blob it
carries, is a surfaced error — see CloudSyncConnection::blob_key.
Sourcepub async fn is_pinned(
&self,
blobs: &[RowBlobRef],
) -> Result<bool, BlobCacheError>
pub async fn is_pinned( &self, blobs: &[RowBlobRef], ) -> Result<bool, BlobCacheError>
Whether every blob in blobs is pinned for offline — present in coven’s
kept cache folder (storage/pinned/). The host answers “is this release
kept offline” through this instead of stat-ing coven’s cache layout itself.
An empty set is vacuously pinned. A blob not pinned (in the evictable cache
or absent) makes the whole set unpinned; an existence-check failure is
surfaced, never read as “not pinned”.
Sourcepub async fn rows_pinned(
&self,
table: &str,
row_ids: Vec<String>,
) -> Result<Vec<Option<bool>>, BlobCacheError>
pub async fn rows_pinned( &self, table: &str, row_ids: Vec<String>, ) -> Result<Vec<Option<bool>>, BlobCacheError>
Whether each of table’s row_ids is pinned for offline, one answer per
id in the order given. None where an id names no live blob-bearing row.
is_pinned answers over blobs that together make up
one thing — every blob of a release, pinned and unpinned together. This
answers for many independent rows at once: a host drawing a “kept
offline” marker per row of a page resolves and answers the whole page in
one call, instead of a row_blob_ref and an
is_pinned per row.
A row whose blob has no committed cloud object — one still Local, or one whose upload has not landed — has no kept copy to hold and reads as not pinned. An existence-check failure is still surfaced, never read as “not pinned”.
Sourcepub async fn evict_blob(&self, blob: &RowBlobRef) -> Result<(), BlobCacheError>
pub async fn evict_blob(&self, blob: &RowBlobRef) -> Result<(), BlobCacheError>
Remove one Remote blob’s re-fetchable on-device cache copies from both
storage/pinned/ and storage/cache/. This never touches the local store,
whose bytes may be the only usable copy owned by an unpublished write.
It does not delete the cloud blob or its carrying row; a later read can
fetch the bytes again.
Sourcepub async fn make_remote(
&self,
root_table: &str,
root_id: &str,
root_label: &str,
pin: bool,
refs: Vec<RowBlobRef>,
) -> Result<(), MakeRemoteError>
pub async fn make_remote( &self, root_table: &str, root_id: &str, root_label: &str, pin: bool, refs: Vec<RowBlobRef>, ) -> Result<(), MakeRemoteError>
Make (root_table, root_id) Remote (Local → Remote): enqueue an upload per
user-provided blob from its external file and record the make_remote
intent, then return. The drain uploads each and flips the gate true on the
last; the gate flip re-emits the subtree and the cycle’s inline push
uploads host-provided blobs. pin keeps the uploaded blobs in the cache as
pinned offline copies. Errors with MakeRemoteError::SyncNotReady when no
provider is connected.
refs is the root’s complete current blob set in the order the host wants
uploads admitted. coven validates the set atomically before enqueueing it.
root_label is what the host calls this root, snapshotted onto the queue
rows and the intent. The queue outlives the root row on purpose — a
cancelled or deleted root still has cloud objects to unwind — so an entry
that had to read the row to name itself could not be rendered at exactly
the moment a person most needs to see it.
pub async fn make_remote_batch( &self, root_table: &str, roots: Vec<MakeRemoteRoot>, pin: bool, ) -> Result<(), MakeRemoteError>
Sourcepub async fn cancel_make_remote(
&self,
root_table: &str,
root_id: &str,
) -> Result<(), MakeRemoteError>
pub async fn cancel_make_remote( &self, root_table: &str, root_id: &str, ) -> Result<(), MakeRemoteError>
Cancel an in-flight make_remote of (root_table, root_id): clear its intent
and pending uploads and tombstone any blob already in the cloud. The gate
never flips, so the root stays Local. Errors with
MakeRemoteError::SyncNotReady when no provider is connected.
Sourcepub async fn make_local(
&self,
root_table: &str,
root_id: &str,
dest: &HashMap<String, PathBuf>,
cancel: &Receiver<bool>,
) -> Result<(), MakeLocalError>
pub async fn make_local( &self, root_table: &str, root_id: &str, dest: &HashMap<String, PathBuf>, cancel: &Receiver<bool>, ) -> Result<(), MakeLocalError>
Make (root_table, root_id) Local (Remote → Local): bring each blob back to
a local file durability-first — a user-provided blob to the path named in
dest (blob id → destination path), a host-provided blob to coven’s local
store (no dest) — then flip the gate false, register the external refs, and
enqueue the cloud deletes in one atomic commit. cancel aborts before the
commit (the root stays Remote). Errors with MakeLocalError::SyncNotReady
when no provider is connected.
Sourcepub async fn queued_uploads(&self) -> Result<Vec<QueuedUpload>, DbError>
pub async fn queued_uploads(&self) -> Result<Vec<QueuedUpload>, DbError>
Every upload the durable queue is holding, oldest first.
An upload appears here the moment make_remote
enqueues it — before any transfer is attempted, and whether or not sync
is connected — and stays until its publication activates or its
cancellation clears it. The queue is a table in the store database, so
this survives restarts: a host can render “waiting to upload” without
having observed the transfer that will do it.
This is a read; nothing here starts or advances a transfer. Compare
drain_uploads, which does the work.
To ask whether a root still has a transition running, prefer
make_remote_progress: the queue empties
before the transition ends.
Sourcepub fn subscribe_cloud_outbox(&self) -> CloudOutboxLiveQuery
pub fn subscribe_cloud_outbox(&self) -> CloudOutboxLiveQuery
Subscribe to the durable upload queue and make-remote intents as one
committed snapshot. The first crate::CloudOutboxLiveQuery::next returns
immediately; later calls wake from the same committed-change stream as
row live queries.
Sourcepub async fn cloud_outbox_snapshot(
&self,
) -> Result<CloudOutboxSnapshot, DbError>
pub async fn cloud_outbox_snapshot( &self, ) -> Result<CloudOutboxSnapshot, DbError>
Read the same committed durable state the cloud-outbox subscription emits, without waiting for a change.
Sourcepub async fn queued_uploads_for_root(
&self,
root_table: &str,
root_id: &str,
) -> Result<Vec<QueuedUpload>, DbError>
pub async fn queued_uploads_for_root( &self, root_table: &str, root_id: &str, ) -> Result<Vec<QueuedUpload>, DbError>
The queued uploads belonging to one gated root.
The filter runs in SQL, so asking about one root does not decode every
other queued upload in the store. A host answers “is anything still
waiting to upload for this row?” from whether this is empty — but see
make_remote_progress for whether the
transition itself has finished, which outlasts its uploads.
Sourcepub async fn external_blob(
&self,
table: &str,
row_id: &str,
) -> Result<Option<ExternalBlob>, DbError>
pub async fn external_blob( &self, table: &str, row_id: &str, ) -> Result<Option<ExternalBlob>, DbError>
Where the user’s own file for a row’s blob lives on disk, or None
when the row has no external registration.
This is the read that mirrors
SqlContext::register_external_blob:
a host that needs the original file itself — to re-read its tags, to
find an artifact it produced — asks here rather than reading coven’s
copy, because for a user-provided blob there is no copy.
None means no registration, which is an ordinary answer: a row whose
blobs coven copies, or one whose registration was cleared, has no user
file to name. A registration that disagrees with the row it belongs to
is an error, not a None.
Sourcepub async fn queued_deletes(&self) -> Result<Vec<QueuedDelete>, DbError>
pub async fn queued_deletes(&self) -> Result<Vec<QueuedDelete>, DbError>
Every cloud tombstone the durable queue is holding, oldest first.
A tombstone is queued by
SqlContext::enqueue_blob_delete
and stays until a sync cycle carries the removal out, so this reports
removals still owed to the cloud across restarts.
Sourcepub async fn make_remote_progress(
&self,
root_table: &str,
root_id: &str,
) -> Result<Option<MakeRemoteProgress>, DbError>
pub async fn make_remote_progress( &self, root_table: &str, root_id: &str, ) -> Result<Option<MakeRemoteProgress>, DbError>
How far the make-remote for one gated root has got, or None when that
root has none running.
This outlasts the root’s queued uploads. Once the last upload lands its
queue rows are consumed, but the transition is not finished until the
Store write publishing it activates — so a root can have no queued
uploads and still be mid-transition, reported here as
MakeRemoteProgress::Publishing.
Sourcepub async fn drain_uploads(&self) -> Result<DrainOutcome, SyncError>
pub async fn drain_uploads(&self) -> Result<DrainOutcome, SyncError>
Drain pending blob uploads now: read each local file, seal it under its
scope, write it to the cloud, and keep a retain_pinned entry’s plaintext
in the protected cache.
The sync loop drains each cycle; this drives a drain directly off the connected home, against coven’s own register clock and the handle’s observer. Errors when no provider is connected (there is no cloud to write to).
The DrainOutcome says what the pass found, not just how much it moved:
an empty queue, a queue held entirely in retry backoff, and a paused one
are each their own answer rather than a zero count.
A host that connects with a running loop shares the queue with the
cycle’s drain. The two never run at once — the queue is drained under an
exclusive turn, so one entry is never in two uploads — but they do divide
the work: this call may wait for a cycle’s drain and then find the
entries it wanted already uploaded, and answer QueueEmpty. The outcome
describes this pass, never the queue’s whole history, so a host that
needs the latter should watch
subscribe_cloud_outbox rather than
count one drain’s return. connect_sync_with_test_home_caller_driven
(test builds only) connects without a loop, so this call is the only
drain and its count is the whole truth.
Sourcepub async fn retry_uploads_now(&self) -> Result<DrainOutcome, SyncError>
pub async fn retry_uploads_now(&self) -> Result<DrainOutcome, SyncError>
Retry every failed upload now, without waiting for its automatic retry delay. This clears the durable delay only after confirming that a cloud connection can run the drain, then attempts the queue immediately.
Provider failures remain in the returned DrainOutcome, with their
updated attempt records available through
subscribe_cloud_outbox. Connection or
database failures are returned as SyncError.
pub async fn get_cache_budget( &self, namespace: &str, ) -> Result<Option<u64>, DbError>
pub async fn set_cache_budget( &self, namespace: &str, max_bytes: u64, ) -> Result<(), DbError>
Sourcepub async fn generate_restore_code(&self) -> Result<String, SyncError>
pub async fn generate_restore_code(&self) -> Result<String, SyncError>
Generate a restore code, seeded with the store’s current membership-head floor read from the cloud. Requires a connected provider because minting a trustworthy floor is a network read, not a pure function of local config and keyring state — a restore code minted without one would carry no protection against a storage provider replaying an older, otherwise validly signed membership state to the device that redeems it.
pub async fn get_members(&self) -> Result<Vec<MemberInfo>, SyncError>
pub async fn membership_conflict( &self, ) -> Result<Option<MembershipConflictInfo>, SyncError>
pub async fn start_device_pairing( &self, ) -> Result<DevicePairingHost, StartDevicePairingError>
pub async fn approve_device_pairing( &self, host: &DevicePairingHost, request: &DevicePairingRequest, role: MemberRole, policy: DeviceJoinApprovalPolicy<'_>, access_administrator: Option<&dyn DeviceProviderAccessAdministrator>, on_progress: &(dyn Fn(AdmittingDeviceJoinProgress) + Send + Sync), cancel: Receiver<bool>, ) -> Result<DeviceJoinDriveOutcome, ApproveDevicePairingError>
pub async fn cancel_device_pairing( &self, host: &DevicePairingHost, ) -> Result<(), ApproveDevicePairingError>
pub async fn begin_device_join( &self, member_pubkey: &str, ) -> Result<DeviceJoinOffer, SyncError>
pub async fn abandon_device_join( &self, offer: DeviceJoinOffer, ) -> Result<DeviceJoinAbandonment, SyncError>
pub async fn accept_device_registration_request( &self, request: DeviceRegistrationRequest, ) -> Result<ProvisionalDeviceBootstrap, SyncError>
pub async fn publish_device_provider_challenge( &self, bootstrap: ProvisionalDeviceBootstrap, ) -> Result<ProviderReadyDeviceBootstrap, SyncError>
pub async fn complete_device_provider_admission( &self, readiness: DeviceJoinReadiness, ) -> Result<DeviceProviderAdmissionCompletion, SyncError>
pub async fn finalize_device_join( &self, completion: DeviceProviderAdmissionCompletion, ) -> Result<DeviceJoinActivation, SyncError>
pub async fn device_join_status( &self, attempt_id: DeviceJoinAttemptId, role: DeviceJoinRole, ) -> Result<Option<DeviceJoinStatus>, SyncError>
pub async fn resume_device_joins( &self, ) -> Result<Vec<DeviceJoinAction>, SyncError>
pub async fn remove_member(&self, public_key_hex: &str) -> Result<(), SyncError>
pub async fn resolve_membership_conflict( &self, choice: &MembershipConflictChoice, ) -> Result<(), SyncError>
Sourcepub async fn propose_device_exclusion(
&self,
device_id: StoreDeviceId,
) -> Result<String, SyncError>
pub async fn propose_device_exclusion( &self, device_id: StoreDeviceId, ) -> Result<String, SyncError>
Propose excluding one Store device and return the code that identifies the exact activated proposal.
Sourcepub async fn cancel_device_exclusion(
&self,
proposal_code: &str,
) -> Result<(), SyncError>
pub async fn cancel_device_exclusion( &self, proposal_code: &str, ) -> Result<(), SyncError>
Cancel the exact Store-device exclusion proposal carried by proposal_code.
Sourcepub async fn finalize_device_exclusion(
&self,
proposal_code: &str,
) -> Result<(), SyncError>
pub async fn finalize_device_exclusion( &self, proposal_code: &str, ) -> Result<(), SyncError>
Finalize the exact Store-device exclusion proposal carried by proposal_code.
Sourcepub async fn begin_owner_promotion(
&self,
device_id: StoreDeviceId,
) -> Result<String, SyncError>
pub async fn begin_owner_promotion( &self, device_id: StoreDeviceId, ) -> Result<String, SyncError>
Begin transferring Store ownership to an active device and return the request code that device must accept.
Sourcepub async fn accept_owner_promotion(
&self,
request_code: &str,
) -> Result<String, SyncError>
pub async fn accept_owner_promotion( &self, request_code: &str, ) -> Result<String, SyncError>
Accept an Owner-promotion request and return the acceptance code the existing Owner must finalize.
Sourcepub async fn finalize_owner_promotion(
&self,
acceptance_code: &str,
) -> Result<(), SyncError>
pub async fn finalize_owner_promotion( &self, acceptance_code: &str, ) -> Result<(), SyncError>
Finalize the Owner-promotion acceptance carried by acceptance_code.
Sourcepub fn circles(&self) -> Circles<'_>
pub fn circles(&self) -> Circles<'_>
The Circle application surface: create, lifecycle, inspection, and typed
CircleError. A borrowed namespace with no state of
its own.
Trait Implementations§
Source§impl Clone for CovenHandle
impl Clone for CovenHandle
Source§fn clone(&self) -> CovenHandle
fn clone(&self) -> CovenHandle
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for CovenHandle
impl !RefUnwindSafe for CovenHandle
impl Send for CovenHandle
impl Sync for CovenHandle
impl Unpin for CovenHandle
impl UnsafeUnpin for CovenHandle
impl !UnwindSafe for CovenHandle
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more