fix(sync): replicate deletions and keep photos off the wire

Reusing the backup snapshot for sync had two bugs that would bite real use:

1. Deletions didn't propagate. exportInventory() filters isDeleted=false
   (user backups are tombstone-free by design) and the JSON codec dropped
   isDeleted (omitted on encode, hardcoded false on decode) — so deleting a
   variety on one device never reached the others. Now the codec round-trips
   isDeleted (emitted only when true, so backups stay byte-identical and it's
   backward compatible), and a new exportForSync() includes tombstones. The
   importer already merges them LWW-correctly.

2. Photos bloated events. The snapshot embeds photo bytes as base64, so a
   real inventory would blow past relay event-size limits and sync would
   silently fail. exportForSync() omits attachments entirely (photos stay
   device-local; media sync is a separate, deferred concern).

buildSnapshot() now uses exportForSync(); backups keep using exportInventory
(photos in, tombstones out) unchanged.

Tests: a deletion replicates as a tombstone; the sync snapshot carries no
photo bytes while a sealed backup still does.
This commit is contained in:
vjrj 2026-07-11 00:17:08 +02:00
parent 225880fc64
commit 21e4357970
4 changed files with 83 additions and 11 deletions

View file

@ -50,11 +50,12 @@ class ExportImportService implements InventorySnapshotIO {
/// The backup file extension. The content is the sealed interchange JSON.
static const backupExtension = 'tanemaki';
/// Raw (unsealed) interchange snapshot for device-to-device sync the same
/// JSON as a backup, but the sync transport (not this) does the encryption.
/// Raw (unsealed) interchange snapshot for device-to-device sync: includes
/// tombstones (so deletions replicate) and omits photo bytes (kept device-
/// local). The sync transport (not this) does the encryption.
@override
Future<List<int>> buildSnapshot() async =>
utf8.encode(_jsonCodec.encode(await _repository.exportInventory()));
utf8.encode(_jsonCodec.encode(await _repository.exportForSync()));
/// Merges a raw interchange snapshot from another device (LWW, idempotent).
@override