Walks the local store and maps it onto the cloud object model per the
design doc: captures, generations (+ version wavs), profiles (+ samples,
avatar), and the settings singletons. Records carry the full row (paths
storage-relative so restores re-anchor under the destination data dir);
audio travels as per-asset ciphertext blobs.
Envelopes are randomized, so a new cloud_sync_state table remembers both
the plaintext fingerprint (did content actually change?) and the
ciphertext hash the server holds (re-declare unchanged blobs without
re-encrypting or re-uploading). updated_at is excluded from fingerprints
- its onupdate trigger fires as a side effect of applying pulls and
would bounce synced objects otherwise. Push runs before pull; conflicts
are last-writer-wins per object. Own pushes echoing back through the
feed are recognized by ciphertext hash and skipped.
Tests simulate two machines (own DB, data dir, keychain) syncing through
an in-process fake cloud: full backup/restore with byte-identical audio,
incremental edit (only the record blob re-uploads), delete propagation,
LWW convergence, and the blindness check that server storage only ever
holds VBX1 envelopes.
cloud_keys: device private key + master key live in the OS keychain
(keyring), namespaced per cloud account — never in the local DB. Headless
installs without a keychain get a hard error, not a plaintext fallback.
cloud_api: async httpx client for the bearer-key surface (devices,
account-key escrow, object push/commit, sync pull). Presigned blob
transfers use a separate unauthenticated client so the bearer key never
reaches the storage host.
cloud_account: the design-doc §9 flows — first-device setup mints MK +
recovery phrase (returned for one-time display), later devices register
and either adopt a wrapped MK provisioned by an existing device or
restore from the phrase. sync_device_id lands on cloud_settings with an
idempotent migration.
Tests run the real flows against an in-process fake cloud and assert the
master key and phrase never appear in any request body. A full round-trip
integration test (encrypt -> push -> pull -> decrypt -> tombstone) runs
against a live dev server when VOICEBOX_CLOUD_TEST_API/KEY are set.