Skip to content

[claude] Sync: duplicated ClientId causes silent, permanent divergence — add detection and reconciliation #79

Description

@myieye

[Claude-drafted]

Sync exchanges commits purely on (ClientId → latest commit timestamp) (SyncState, QueryHelpers.GetMissingCommits). This assumes each ClientId is a single writer with append-only history. If two replicas ever write under the same ClientId (copied SQLite file, restored device backup), their histories fork and commits are permanently stranded in both directions — each side believes the other already has everything below its head. Nothing detects this: commit hashes never cross the wire ([JsonIgnore]), hash only id + parentHash, and are rewritten locally.

Repro: copy a project DB to a second client, edit + sync on both. The project eventually becomes unsyncable: an edit arrives for an entity whose creating commit is stranded, and SnapshotWorker throws on every subsequent sync.

Proposal (layered):

  1. Tripwire: in AddRangeFromSync, receiving a commit authored by the local ClientId that isn't already in the local DB proves the ID is duplicated → surface loudly; the app should switch to a fresh ClientId so the fork stops growing.
  2. Detect: extend SyncState entries with a commit count + order-independent digest of that client's commit IDs, compared over the shared range (≤ the lower head). Mismatch ⇒ divergence, even when heads differ. Must stay compatible with timestamp-only clients.
  3. Repair: on divergence for a ClientId, exchange that client's full commit-ID list, diff, send missing commits both ways. AddRangeFromSync already handles past-insertion (dedup, hash rewrite, snapshot replay), so the merge converges. Open question: auto-repair with loud logging vs. requiring user attention.

Prevention (keeping writer identity out of the copyable DB) is the app's job: sillsdev/languageforge-lexbox#2431.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions