A live connection can disappear between two events. Transport delivery order on one connection does not prove that a client received every durable case change across reconnects, workers, or regions. A message needs a stable event identity and ordering scope, such as a per-case version. The client records the last applied version, drops older or duplicate events, and detects gaps. A reconnect cursor works only while the server retains matching history and the actor remains authorized. If history has expired or the cursor is invalid, fetch an authoritative snapshot and resume from a new boundary. Presence or typing hints do not need durable replay; case status and comments often do.
Event Sequence, Replay, and Gap Reconciliation
Working case
Case 29 moves from open at version 47 to held at 48, then to closed at 49. Reviewer 47 disconnects after receiving version 47. On reconnect, a worker sends version 49 first and version 48 later. A naive client applies both and ends on held. The revised client detects a missing version 48, pauses case mutation rendering, and requests a fresh authorized snapshot or replay from 47. It applies 48 then 49, or accepts a snapshot already at 49. A duplicate 49 is ignored. If the reviewer lost access during the gap, the server denies replay rather than returning old private events.
Implementation boundary
function eventStep(currentVersion, incomingVersion) {
if (incomingVersion <= currentVersion) return "duplicate-or-old";
return incomingVersion === currentVersion + 1 ? "apply" : "reconcile-gap";
}
console.log(eventStep(47, 49));
// Output: reconcile-gapDefine sequence scope explicitly: per case, per tenant stream, or one global log. Do not compare two unrelated case versions as one total order. Persist an event ID with the committed write, ideally in the same transaction or reliable outbox that changes the case. Bound replay retention and cursor lifetime. On reconnect, authenticate again, authorize each requested stream, and fetch events after the last applied cursor. If the cursor is too old, return a gap result that causes a snapshot load. Include a snapshot version so events arriving during the fetch can be buffered or replayed after it. On the client, apply an event only if it is the expected next version for that scope; ignore duplicates and reconcile gaps. Do not treat a timestamp as a unique ordered cursor.
Cost and boundaries
Durable replay uses O(E) storage for E retained events and indexed range reads per reconnect. A per-case version check is O(1) for each applied message, while a snapshot fetch costs the size of current state. Keeping longer history improves offline recovery but raises storage and privacy retention costs. A global sequence simplifies one cursor but can increase contention and force clients to scan unrelated events. Measure reconnect count, replay depth, gap frequency, snapshot load rate, duplicate delivery, and time from reconnect to consistent UI. Define an expiry period that matches actual client offline behavior.
Failure trace
Deliver version 49 before 48 and confirm the page never ends on held. Repeat version 49 twice and ensure no duplicate audit or notification action occurs. Reconnect with a cursor older than retained history and require a snapshot rather than a fabricated empty replay. Revoke access during downtime; the old cursor cannot retrieve case events. Fetch a snapshot at 49 while event 50 arrives; the client must buffer or replay it, not erase it with the older response. Restart the event worker after commit and ensure its outbox still emits the committed event once in logical state.
Verification
- Sequence scope and replay retention are explicit.
- Duplicates are ignored and gaps trigger replay or snapshot.
- Replay rechecks current permission after reconnect.
Practice drill
Produce case 29 versions 47 through 63. Drop the connection after 47, reorder 48 and 49, duplicate 50, then reconnect after the replay window expires. Record every applied version and visible final state. Add a revoked reviewer and confirm both replay and snapshot routes deny private data. Run the same sequence after an event worker crash. State whether the system guarantees at-least-once delivery with deduplication or a weaker refresh-based outcome; do not claim that a socket by itself guarantees exactly-once effects.
Decision note
Durable case truth comes from committed versions and snapshots; socket arrival is only a delivery attempt.
Common Mistakes
- Sorting business events by browser arrival time.
- Treating a timestamp as a unique gap-free cursor.
- Returning private replay after access revocation.
Related lessons
Realtime Connection and Event Delivery; WebSocket Handshake, Session, and Channel Authorization; Socket Fanout, Backpressure, and Slow Consumers; Presence Expiry, Heartbeats, and Connection Drain; Live Update Reconnect and Event Ordering; Snapshot, Delta Cursor, and Gap Recovery.
Apply and check
Build Project: live permit case board and review Web Development: realtime delivery contracts quiz.
