# Bad Example: Edge Sync Loop (Livelock) > A two-node sync that oscillates divergent state forever. This is > the pre-specified `edge-sync-loop` chaos anti-pattern (ATELIER-110). > **Single-breach per D-068:** the principle breached is **Edge P4 > (Sync Conflicts are Bounded, Not Infinite)**. The prose explains > why this is a livelock, not eventual consistency. ## The Code ```typescript // Two edge nodes (a tablet and a dispatcher workstation) sync a // work-order status. The merge uses wall-clock timestamps with no // vector clock and no deterministic tiebreak. A clock skew flips // the winner on every merge pass; the state oscillates forever. interface WorkOrderState { orderId: string; status: string; wallClock: number; // P4 VIOLATION: wall time, not monotonic nodeId: string; } // P4 VIOLATION: the merge picks the later wall-clock write as the // winner. Wall time skews across nodes; a skew of even a few // milliseconds flips the winner. With no vector clock, concurrent // writes are not detected; with no deterministic tiebreak, equal // timestamps are resolved by whichever node's clock is ahead. function mergeBad(local: WorkOrderState, remote: WorkOrderState): WorkOrderState { // No vector clock. No monotonic logical clock. No deterministic // tiebreak by nodeId. This is wall-clock-only LWW on durable // state — the row in the CRDT-vs-LWW decision matrix that is a // P4 violation for anything but ephemeral state. if (local.wallClock >= remote.wallClock) return local; return remote; } // The sync loop: each node merges the other's state, writes the // result, and the next sync pass flips it back. The loop never // terminates. async function syncLoop(node: "tablet" | "dispatcher", peer: WorkOrderState) { let local = store.get(peer.orderId); while (true) { const merged = mergeBad(local, peer); store.set(merged.orderId, merged); await pushToPeer(merged); // peer receives, merges, pushes back peer = await pullFromPeer(); // peer's clock is now ahead — flips winner local = store.get(peer.orderId); // local re-merges; flips again // The loop runs forever. Convergence is never reached. This is // a livelock: the system is making progress (each pass writes) // but the state never converges. } } ``` The tablet sets `status: "completed"` at wall time `10:00:00.500`. The dispatcher's clock is 50ms ahead; it sets `status: "reassigned"` at wall time `10:00:00.550`. The first merge: dispatcher wins (`10:00:00.550 > 10:00:00.500`). The tablet receives `reassigned`, but its clock drifts ahead by 100ms during the next sync pass; it writes `completed` at `10:00:00.650`. The second merge: tablet wins. The dispatcher's clock drifts ahead again; it writes `reassigned` at `10:00:00.750`. The third merge: dispatcher wins. The state flips between `completed` and `reassigned` on every sync pass. The loop runs forever. ## Why It Violates ### Sync Conflicts are Bounded, Not Infinite (Edge P4, C1, C5) - **The breach:** the merge has no convergence bound. The CRDT-vs- LWW decision matrix in `domains/edge/sync.md` is explicit: wall-clock-only LWW on durable state carries an **unbounded failure mode** — clock skew = oscillation — and is a P4 violation for anything but ephemeral state. This code is the matrix's failure column made real. - **Why it is a livelock, not eventual consistency:** eventual consistency guarantees that, in the absence of new writes, all replicas eventually converge. This system never converges even with no new writes: the clock skew alone drives the oscillation. Each sync pass writes (so the system is "busy"), but the state never settles — the definition of a livelock. The P4 contract is that the merge terminates and converges; this merge does neither. - The missing pieces, per the decision matrix: - **No monotonic logical clock** (a hybrid logical clock or vector clock) — wall time skews, and the skew is unbounded. - **No vector clock** — concurrent writes are not detected, so the conflict is invisible; the merge silently flips instead of surfacing. - **No deterministic tiebreak** (e.g., `nodeId`) on equal timestamps — equal wall times are resolved by whichever node's clock is ahead, which is not a stable property. - The P4 bound requires that the merge function be associative, commutative, and idempotent, terminating in one pass. A wall-clock merge with clock skew satisfies none of these: it is not associative (order of merges flips the winner), not idempotent (a re-merge after a clock drift flips the result), and not terminating (the loop runs forever). ## The Fix ```typescript // Fix: replace wall-clock LWW with a CRDT register (state-based // CvRDT) using a monotonic logical clock and a deterministic // tiebreak by nodeId. The merge is now a total order — no // oscillation (P4 bounded). See the CRDT-vs-LWW decision matrix: // for a register on an unreliable network, a state-based CRDT is // the correct row. interface LWWRegister { value: T; timestamp: number; // monotonic logical clock (HLC), not wall time nodeId: string; // deterministic tiebreak } function mergeLWWRegister( local: LWWRegister, remote: LWWRegister, ): LWWRegister { // Associative, commutative, idempotent (P4 bound, P5 idempotent). // (timestamp, nodeId) is a total order — convergence in one pass. if (local.timestamp > remote.timestamp) return local; if (local.timestamp < remote.timestamp) return remote; return local.nodeId > remote.nodeId ? local : remote; } // The sync is now one pass: merge, write, done. No loop, no // oscillation. A re-merge of the same two replicas yields the same // result (P5 idempotent), so a retried sync is safe. async function syncOnce(local: LWWRegister, remote: LWWRegister) { const converged = mergeLWWRegister(local, remote); store.set(converged); await pushToPeer(converged); // Done. No while(true). The merge terminates. } ``` The fix selects the CRDT row of the decision matrix (a register on an unreliable network): a monotonic logical clock (HLC) eliminates clock skew; a deterministic tiebreak by `nodeId` eliminates the equal-timestamp flip. The merge is now a total order that converges in one pass — the P4 bound. See `examples/good/edge-offline-sync.md` for the full good-example version of this pattern. ## Cross-Domain Links - `domains/edge/sync.md` — the CRDT-vs-LWW decision matrix; the wall-clock-only LWW row is the failure mode this example instantiates; the CRDT row is the fix. - `domains/edge/first-principles.md` — P4 (Sync Conflicts are Bounded, Not Infinite) is the principle breached. - `review/anti-patterns.md` — the `edge-sync-loop` chaos anti-pattern (edge P4, C1, C5 — infinite oscillation is a correctness failure, not eventual consistency; the sync is a livelock). - `examples/good/edge-offline-sync.md` — the good-example version of this pattern: a CRDT register merge that converges in one pass.