Tracking issue for sub-phase 1.f of RFC-001. Largest sub-phase by surface area.
Implement the multi-device sync protocol from RFC §6.6 end-to-end: server-side ops log + watermark + cursor + compaction job, WebSocket fan-out, desktop client integration with a "Server mode" toggle.
Scope — waveflow-server
Schema (per RFC §6.6):
sync_op table with BIGSERIAL id, operation_id UUID, lamport_ts BIGINT, double UNIQUE on (user_id, device_id, operation_id) and (user_id, device_id, lamport_ts).
device_sync_cursor (user_id, device_id, last_seen_id, last_seen_at).
sync_compaction_watermark (user_id, compacted_up_to, updated_at).
- Composite index
device_sync_cursor (user_id, last_seen_at, last_seen_id) for the compaction MIN query.
Endpoints:
POST /sync/ops — push batch. Idempotency check on operation_id first (returns 200 + existing row on dup), then per-device monotonicity check (409 with stored max on violation), then BIGSERIAL assignment in a transaction, then WebSocket broadcast.
GET /sync/ops?since=N — pull. Resurrected-device guard: since > 0 AND since < compacted_up_to → 410 Gone {"type":"resync_required","compacted_up_to":N}. No cursor write here.
POST /sync/ack {"last_seen_id": N} — only path that advances device_sync_cursor.
GET /sync/ws — WebSocket. Server pushes new ops; client sends {"ack": N} frames.
Background:
- ACK debouncing: in-memory latest per
(user_id, device_id), flush every 5s or on disconnect. Compaction MIN reads the in-memory value, never the stale Postgres row.
- Nightly compaction job: reads
MIN(last_seen_id) excluding devices stale > 90 days (configurable), collapses ops with id < min per (entity_id, field), updates sync_compaction_watermark.compacted_up_to in the same transaction as the delete.
Scope — waveflow (desktop)
- Settings → "Server mode" toggle (Local / Server-connected / Hybrid).
- Repository swap at runtime: existing Tauri commands route to either local SQLite or HTTP/WebSocket axum based on mode.
- Device-side Lamport clock (persisted in
app_setting), increment-on-emit, max+1 on receive.
- Pending-ops queue persisted locally so offline edits sync when the connection comes back.
- Login flow: in-app webview to the Better Auth magic-link page, JWT stored in OS keyring via secure-storage plugin.
Critical invariants (must hold or compaction is unsafe)
- Compaction job's delete + watermark update happen in the same Postgres transaction.
- Compaction MIN computation includes any unflushed in-memory ACK values.
- Server never writes
device_sync_cursor outside of an explicit ACK path.
Dependencies
- Blocks on: 1.d (auth) + 1.e (HTTP fundamentals). Web integration may stay minimal in this sub-phase — focus is server + desktop.
Acceptance criteria
Estimate
~4-5 weeks. Most complex sub-phase.
Tracking issue for sub-phase 1.f of RFC-001. Largest sub-phase by surface area.
Implement the multi-device sync protocol from RFC §6.6 end-to-end: server-side ops log + watermark + cursor + compaction job, WebSocket fan-out, desktop client integration with a "Server mode" toggle.
Scope —
waveflow-serverSchema (per RFC §6.6):
sync_optable withBIGSERIAL id,operation_id UUID,lamport_ts BIGINT, double UNIQUE on(user_id, device_id, operation_id)and(user_id, device_id, lamport_ts).device_sync_cursor (user_id, device_id, last_seen_id, last_seen_at).sync_compaction_watermark (user_id, compacted_up_to, updated_at).device_sync_cursor (user_id, last_seen_at, last_seen_id)for the compaction MIN query.Endpoints:
POST /sync/ops— push batch. Idempotency check onoperation_idfirst (returns 200 + existing row on dup), then per-device monotonicity check (409 with stored max on violation), then BIGSERIAL assignment in a transaction, then WebSocket broadcast.GET /sync/ops?since=N— pull. Resurrected-device guard:since > 0 AND since < compacted_up_to→410 Gone {"type":"resync_required","compacted_up_to":N}. No cursor write here.POST /sync/ack {"last_seen_id": N}— only path that advancesdevice_sync_cursor.GET /sync/ws— WebSocket. Server pushes new ops; client sends{"ack": N}frames.Background:
(user_id, device_id), flush every 5s or on disconnect. Compaction MIN reads the in-memory value, never the stale Postgres row.MIN(last_seen_id)excluding devices stale > 90 days (configurable), collapses ops withid < minper(entity_id, field), updatessync_compaction_watermark.compacted_up_toin the same transaction as the delete.Scope —
waveflow(desktop)app_setting), increment-on-emit, max+1 on receive.Critical invariants (must hold or compaction is unsafe)
device_sync_cursoroutside of an explicit ACK path.Dependencies
Acceptance criteria
operation_idis idempotent.Estimate
~4-5 weeks. Most complex sub-phase.