Review of the conflict surface (ConflictEventCallback, replicant_register_conflict_callback) from integrating it into Entonal Studio's TonalDB. The 'Dropbox conflicted-copy' model is the right family of solution, but the current API is a weaker version of it than it looks. Ranked by severity:
1. The losing version is not durable (correctness hole)
ConflictDetected is a fire-once, in-memory event delivering winning_content + losing_content. If no callback is registered — true of every client today — or the client crashes between the event and its rescue write, the losing version is gone forever. Dropbox does not delegate the rescue: the conflicted copy is created centrally and unconditionally.
Proposal: persist the losing version durably in the engine (a conflicts table in the local replica, or server-side) and expose a pull API — enumerate / inspect / resolve — with the callback demoted to a "new conflict recorded" nudge. The local schema already has sync_status IN ('synced','pending','conflict') and local_changes JSON, so the storage layer looks half-ready for this; the API just doesn't surface it.
2. Rescue is racy across replicas (correctness hole)
The callback presumably fires on every replica that observes the conflict. If two devices both implement rescue-as-copy, both rescues sync and the user gets two "conflicted copy" documents. Client-side session dedupe can't catch a cross-device duplicate. Either the server should own the rescue, or the API needs deterministic single-rescuer semantics (e.g. only the replica whose write lost performs it). This part likely needs replicant-server work — flagging here since the contract lives in the client API.
3. No metadata on either side
The callback delivers two content blobs and nothing else — no sync_revision, timestamp, author, or device for either version. A client can't render "changed on your laptop 2h ago vs your iPad just now", or even order the versions except by trusting winning/losing. Document callbacks already carry an envelope (user_id, author_name); the conflict callback should too, plus per-side updated_at / sync_revision.
4. LWW is a lowball for structured JSON (enhancement)
Dropbox's model fits opaque files; Replicant documents are structured JSON and the engine already ships JSON patches (sync_queue.patch). Auto-merging non-overlapping field edits (title edited on one device, another field on the other → no conflict at all) would eliminate most real-world conflicts before any UX is needed. A full client resolver hook ("return the merged content") is the max version; patch-level auto-merge is cheaper and captures most of the value.
5. Document winner determinism
Migration 4 ("remove version vector") suggests conflict detection is revision-counter based, so the "winner" may effectively be arrival order at the server. That's acceptable, but it should be documented and stable — rescue logic and any future merge both build on it.
Suggested priority
1 and 2 are contract-level correctness holes and worth fixing before clients build rescue logic on the current API; 3 is cheap and unlocks reasonable conflict UX. 4–5 are genuine improvements but optional for current workloads (small documents, rare conflicts).
Context: Entonal Studio issue DEV-941 tracks wiring the client callback; its rescue-as-copy design hits limitations 1–3 directly.
Review of the conflict surface (
ConflictEventCallback,replicant_register_conflict_callback) from integrating it into Entonal Studio's TonalDB. The 'Dropbox conflicted-copy' model is the right family of solution, but the current API is a weaker version of it than it looks. Ranked by severity:1. The losing version is not durable (correctness hole)
ConflictDetectedis a fire-once, in-memory event deliveringwinning_content+losing_content. If no callback is registered — true of every client today — or the client crashes between the event and its rescue write, the losing version is gone forever. Dropbox does not delegate the rescue: the conflicted copy is created centrally and unconditionally.Proposal: persist the losing version durably in the engine (a
conflictstable in the local replica, or server-side) and expose a pull API — enumerate / inspect / resolve — with the callback demoted to a "new conflict recorded" nudge. The local schema already hassync_status IN ('synced','pending','conflict')andlocal_changes JSON, so the storage layer looks half-ready for this; the API just doesn't surface it.2. Rescue is racy across replicas (correctness hole)
The callback presumably fires on every replica that observes the conflict. If two devices both implement rescue-as-copy, both rescues sync and the user gets two "conflicted copy" documents. Client-side session dedupe can't catch a cross-device duplicate. Either the server should own the rescue, or the API needs deterministic single-rescuer semantics (e.g. only the replica whose write lost performs it). This part likely needs replicant-server work — flagging here since the contract lives in the client API.
3. No metadata on either side
The callback delivers two content blobs and nothing else — no
sync_revision, timestamp, author, or device for either version. A client can't render "changed on your laptop 2h ago vs your iPad just now", or even order the versions except by trusting winning/losing. Document callbacks already carry an envelope (user_id,author_name); the conflict callback should too, plus per-sideupdated_at/sync_revision.4. LWW is a lowball for structured JSON (enhancement)
Dropbox's model fits opaque files; Replicant documents are structured JSON and the engine already ships JSON patches (
sync_queue.patch). Auto-merging non-overlapping field edits (title edited on one device, another field on the other → no conflict at all) would eliminate most real-world conflicts before any UX is needed. A full client resolver hook ("return the merged content") is the max version; patch-level auto-merge is cheaper and captures most of the value.5. Document winner determinism
Migration 4 ("remove version vector") suggests conflict detection is revision-counter based, so the "winner" may effectively be arrival order at the server. That's acceptable, but it should be documented and stable — rescue logic and any future merge both build on it.
Suggested priority
1 and 2 are contract-level correctness holes and worth fixing before clients build rescue logic on the current API; 3 is cheap and unlocks reasonable conflict UX. 4–5 are genuine improvements but optional for current workloads (small documents, rare conflicts).
Context: Entonal Studio issue DEV-941 tracks wiring the client callback; its rescue-as-copy design hits limitations 1–3 directly.