Diagnosing Multi-Session State Divergence Behind a Completeness Guard

Reusable engineering pattern · Vex dev-training · 2026-09-06 · abstracted from a live production diagnosis (no app/customer specifics)

The symptom

Two clients editing the same shared draft show different completion counts for the same records ("Session A: 38 done / 1 pending" vs "Session B: 31 done / 8 pending"). A record can display fully-computed values yet read as pending. A newly-added server-side "completeness guard" then refuses to finalize ("the saved set is incomplete or changed"), and edits appear frozen.

Why exact-match guards surface latent divergence

A finalization guard that requires set equality across four representations — client-expected == canonical-source == persisted-eligible == persisted-entry-keys == snapshot-count — is correct, but it converts a previously-silent inconsistency into a hard block. The guard is not the bug; it is the smoke detector.

if (!exactExpectedSet || !exactEligibleSet || !exactEntrySet || !exactSnapshotCount) {
  return { incomplete: true, /* existing report NOT replaced */ };
}

The two failure classes (bifurcate FIRST)

A — Client staleness. Server state IS complete; one client holds a cached/never-refetched view and submits a stale expected-set. Real-time sync is absent, so completions made in Session A never reach Session B. The guard correctly rejects the stale submission.

B — Persistence loss. Server state is genuinely incomplete: a per-record partial-merge write dropped or never persisted some records' completion. Then the data itself must be reconciled.

Do not propose a fix until you know A vs B. They have opposite remedies (client refetch/sync vs data reconciliation). One DB read of the persisted row resolves it.

Two anti-patterns that cause class A/B

  1. State-in-payload: storing completion as a boolean inside each record entry rather than as an independently-persisted field. Completion then rides the same partial-merge path and is lost/overwritten with it.
  2. Client-authored canonical sets: a navigation autosave that rewrites the authoritative eligible-set from the client's in-memory map (eligibleIds = Object.keys(localEntries)). A stale/partial client can shrink or diverge the persisted canonical set — exactly what an exact-match guard then rejects.

The frozen-edit trap (in-progress refs)

Concurrency guards like if (opInProgressRef.current) return; on every edit handler are safe only if the ref is guaranteed to reset. If it is set true before an await that can hang (an unsettled queue promise), the finally that resets it never runs → every handler silently no-ops → "the UI won't let me edit." Always reset in finally AND bound the awaited promise (timeout / guaranteed-settle).

Diagnostic checklist

Vex engineering pattern library · deploy_ready · 2026-09-06