You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The MQTT sync path exposes no client-driven compare-and-set, so an atomic reclaim of an existing row (fence on an expected version/holder, then take it) cannot be expressed as a single operation. A caller must do delete(stale) → create(new), which is non-atomic and racy.
Why the create-conflict fix does not cover this
The Store::create rollback fix (stitch-sync 0.5.0) closes the fresh-claim-on-a-free-key race: two creates on a unique key → the loser now rejects with Conflict. But reclaiming a key already held by a departed/stale owner is a different problem: the client must atomically assert "the current holder is still X at version N" and replace it. There is no primitive for that.
Concrete downstream shape (ticket-live seat reclaim): acquireHold does store.delete(staleHold) then store.create(newHold) — two ops, no fence. Between them another client can claim the seat. This is the non-atomic-reclaim behavior Presence.tla breaks in a handful of steps.
What exists server-side vs. what the client can drive
mqdb has _version optimistic concurrency, and stitch already maps update/delete CAS conflicts to Error::Conflict.
BUT Store::update / Store::delete take no expected-version precondition from the client (update(entity, id, fields, origin) / delete(entity, id, origin)), and the agent's field-level LWW retry-on-conflict merges rather than rejects — the opposite of a fence. So the building block is present in the store but not surfaced as a client-controllable CAS.
Proposal (options to design between)
Conditional write: an update/delete variant that takes an expected_version (or expected field values) and returns Error::Conflict when the precondition fails, disabling the LWW auto-merge for that call.
Claim-transfer op: a dedicated "take ownership of id if holder == X and version == N" operation, returning Conflict otherwise — closer to a presence-lease.
Either lets a reclaim be a single fenced op instead of delete-then-create.
Notes
Overlaps the presence-lease / reclaim work in stitch-p2p (multi-leader LWW + HLC, signed reclaim), but that's the P2P crate — this request is for the MQTT/mqdb sync path (stitch-sync).
Architecture item, not a one-liner; worth designing deliberately (and probably a TLA+ model of the fenced reclaim before implementing).
Summary
The MQTT sync path exposes no client-driven compare-and-set, so an atomic reclaim of an existing row (fence on an expected version/holder, then take it) cannot be expressed as a single operation. A caller must do
delete(stale) → create(new), which is non-atomic and racy.Why the create-conflict fix does not cover this
The
Store::createrollback fix (stitch-sync 0.5.0) closes the fresh-claim-on-a-free-key race: two creates on a unique key → the loser now rejects withConflict. But reclaiming a key already held by a departed/stale owner is a different problem: the client must atomically assert "the current holder is still X at version N" and replace it. There is no primitive for that.Concrete downstream shape (ticket-live seat reclaim):
acquireHolddoesstore.delete(staleHold)thenstore.create(newHold)— two ops, no fence. Between them another client can claim the seat. This is the non-atomic-reclaim behaviorPresence.tlabreaks in a handful of steps.What exists server-side vs. what the client can drive
_versionoptimistic concurrency, and stitch already maps update/delete CAS conflicts toError::Conflict.Store::update/Store::deletetake no expected-version precondition from the client (update(entity, id, fields, origin)/delete(entity, id, origin)), and the agent's field-level LWW retry-on-conflict merges rather than rejects — the opposite of a fence. So the building block is present in the store but not surfaced as a client-controllable CAS.Proposal (options to design between)
update/deletevariant that takes anexpected_version(or expected field values) and returnsError::Conflictwhen the precondition fails, disabling the LWW auto-merge for that call.idif holder == X and version == N" operation, returningConflictotherwise — closer to a presence-lease.Either lets a reclaim be a single fenced op instead of delete-then-create.
Notes
stitch-p2p(multi-leader LWW + HLC, signed reclaim), but that's the P2P crate — this request is for the MQTT/mqdb sync path (stitch-sync).