Add the cloud sync engine

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.
This commit is contained in:
Jamie Pine
2026-07-01 15:16:04 -07:00
parent 9b0e024d3b
commit 25602555b1
7 changed files with 1042 additions and 56 deletions
+2
View File
@@ -12,6 +12,7 @@ from .models import (
CaptureSettings,
ChannelDeviceMapping,
CloudSettings,
CloudSyncState,
EffectPreset,
Generation,
GenerationSettings,
@@ -34,6 +35,7 @@ __all__ = [
"CaptureSettings",
"ChannelDeviceMapping",
"CloudSettings",
"CloudSyncState",
"EffectPreset",
"Generation",
"GenerationSettings",