Skip to content

roll back optimistic create on remote conflict (0.5.0 / 0.4.0) - #22

Merged
fabracht merged 4 commits into
mainfrom
fix-create-conflict-rollback
Aug 29, 2026
Merged

fabracht merged 4 commits into
mainfrom
fix-create-conflict-rollback

Conversation

@fabracht

Copy link
Copy Markdown
Contributor

Problem

Store::create optimistically wrote to memory + persistence + the offline queue, then on a live connection called sync_create. A broker 409 Conflict (e.g. a unique-constraint collision) fell into a catch-all that only logged a warning and returned Ok(id); a 403 Ownership removed the queued insert but kept the local row and also returned Ok(id). Either way a client racing for an exclusive key (a seat/hold) got Ok plus a phantom local row — and because resync skips and re-creates ids with a pending queue entry, the phantom survived reconnect. The offline-queue flush additionally converted a losing insert into a blind sync_update (a latent overwrite of the winner).

Fix

  • Store::create rolls back memory + persistence + the queued Insert and returns Err on Conflict/Ownership; transient failures still queue and return Ok(id). Only Origin::Local reaches this path, so mirror/resync/load writes are untouched.
  • The offline-queue flush no longer converts a losing insert to an update — it drops it and reports it (FlushSummary.conflicted_inserts). flush_loop/on_connected then roll the losing local row back, so a create that returned Ok(id) after a transient error and later lost the race converges to not-holding while continuously connected (previously it healed only on the next reconnect).
  • Error::is_permanent_mutation now classifies mqdb UniqueViolation as permanent.
  • stitch-harness gains .unique_constraint(entity, fields), registering a broker Schema + unique constraint so tests exercise a real 409.

Versioning

Minor bump — OfflineQueue::flush is public (pub mod queue) and its signature changed from Result<usize> to Result<FlushSummary>, and create now returns Err where it returned Ok:

  • stitch-sync 0.4.0 → 0.5.0
  • stitch-wasm 0.3.0 → 0.4.0

Verification

  • TLA+: exclusivity + convergence invariants hold exhaustively at 2 and 3 racing clients; the pre-fix design reproduces the exact double-sell (both clients believe they won).
  • Adversarial multi-agent review (two passes): the first surfaced a transient-then-conflict phantom edge case, now fixed; the re-review over that delta was clean.
  • 112 stitch tests pass (incl. a real-409 end-to-end test against the broker); clippy clean on native + wasm; workspace + wasm32 compile clean.

Consumer note

A 409 only arises when the broker enforces the uniqueness. mqdb-agent has no primary-key collision on id (a duplicate id is a silent last-writer-wins overwrite), so an exclusive key must be a UNIQUE constraint on a non-id field (e.g. a hold keyed by a random id with unique(seatId)).

@fabracht
fabracht merged commit 3331dbf into main Aug 29, 2026
6 checks passed
@fabracht
fabracht deleted the fix-create-conflict-rollback branch August 29, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant