Skip to content

fix(sync): refuse a replica whose clock cannot be real - #108

Merged
florianmousseau merged 8 commits into
mainfrom
fix/hlc-clock-drift
Jul 31, 2026
Merged

fix(sync): refuse a replica whose clock cannot be real#108
florianmousseau merged 8 commits into
mainfrom
fix/hlc-clock-drift

Conversation

@florianmousseau

Copy link
Copy Markdown
Contributor

A hybrid logical clock absorbs the highest wall time it sees. One replica
carrying an impossible value therefore did three things at once:

  1. it won every last-writer-wins comparison from then on - the resolution is a
    clock comparison and nothing else;
  2. it raised the clock of every replica that merged it (merge() keeps
    max(a.meta.clock, b.meta.clock)), and of every replica those merged with;
  3. it kept doing so after its author was removed from the group, because by
    then the value lived in everyone else's meta.

merge() now refuses an INCOMING replica carrying any clock - meta, record,
tombstone or field - more than five minutes ahead of local wall time, with the
new CLOCK_DRIFT code.

Three decisions worth stating, since each closes a door:

  • Only the incoming side is checked. merge(local, remote, ...) is the
    documented signature and both call sites honour it. A replica that already
    absorbed a bad clock has to keep merging, or the guard would strand exactly
    the stores it exists to protect.
  • Refused, not clamped. Silently lowering a clock leaves two replicas each
    convinced they hold the later write; a refusal is one visible failure with a
    stable code the host can word.
  • The past is never refused. A wall time behind local cannot be told apart
    from a device that was offline for a week.

Second fix, same file: createNode() fell back to Math.random() when
crypto.randomUUID was missing - which is precisely a non-secure context,
since that is the only place randomUUID is absent. It now falls back to
crypto.getRandomValues, and a platform with no WebCrypto at all is refused
rather than served a weak id. A replica id is not a secret, but two replicas
sharing one lose the tiebreak that makes the order total. The repo already
states it takes its randomness from one source; this was the last exception.

Tests: the bound at the millisecond, the past left alone, a drifted tombstone
caught (not just the meta clock), a poisoned LOCAL side that must still merge,
and the id fallback with randomUUID removed.

@sonarqubecloud

Copy link
Copy Markdown

@florianmousseau
florianmousseau merged commit 265bda2 into main Jul 31, 2026
7 checks passed
@florianmousseau
florianmousseau deleted the fix/hlc-clock-drift branch July 31, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant