diff --git a/tests/Tests.FSharp/Properties/Math.Invariants.Tests.fs b/tests/Tests.FSharp/Properties/Math.Invariants.Tests.fs index aa2d52e3e..5558df479 100644 --- a/tests/Tests.FSharp/Properties/Math.Invariants.Tests.fs +++ b/tests/Tests.FSharp/Properties/Math.Invariants.Tests.fs @@ -152,6 +152,63 @@ let ``OR-set merge value is associative`` (xs: int list) (ys: int list) (zs: int orSetValueEqual lhs rhs +// ─── LWW-Register — last-writer-wins merge laws ──────────────────── +// LwwRegister.Merge picks the write with larger timestamp; ties +// broken by replica-id (lexicographic ordinal) for determinism. The +// merge is left-biased on FULL key equality (a.Timestamp = b.Timestamp +// AND a.Replica = b.Replica), so commutativity only holds over the +// LWW invariant: each (Timestamp, Replica) pair is a unique write +// identity. We pin that invariant in these properties by hard-coding +// distinct replica ids ("r1", "r2", "r3") across the per-property +// arguments — that guarantees full-key equality cannot occur, which +// makes the (Timestamp, Replica) total order strict and the merge +// commutative / idempotent / associative on the test domain. +// +// A separate property below exercises the deliberate tie-breaking +// (equal timestamps + equal replicas) to lock the deterministic +// left-bias contract; that one uses lwwEqual on the whole state. + +let private lwwEqual (a: LwwRegister) (b: LwwRegister) = + a.Value = b.Value && a.Timestamp = b.Timestamp && a.Replica = b.Replica + +[] +let ``LWW-register merge is commutative under unique replica ids`` + (vA: int) (tA: int) (vB: int) (tB: int) = + // Distinct replica ids make full-key equality impossible. + let a = LwwRegister.Create(vA, int64 tA, "r1") + let b = LwwRegister.Create(vB, int64 tB, "r2") + lwwEqual (LwwRegister.Merge a b) (LwwRegister.Merge b a) + +[] +let ``LWW-register merge is idempotent`` (v: int) (t: int) (r: NonEmptyString) = + let a = LwwRegister.Create(v, int64 t, r.Get) + lwwEqual (LwwRegister.Merge a a) a + +[] +let ``LWW-register merge is associative under unique replica ids`` + (vA: int) (tA: int) (vB: int) (tB: int) (vC: int) (tC: int) = + // Distinct replica ids make full-key equality impossible across + // the three inputs. + let a = LwwRegister.Create(vA, int64 tA, "r1") + let b = LwwRegister.Create(vB, int64 tB, "r2") + let c = LwwRegister.Create(vC, int64 tC, "r3") + let lhs = LwwRegister.Merge (LwwRegister.Merge a b) c + let rhs = LwwRegister.Merge a (LwwRegister.Merge b c) + lwwEqual lhs rhs + +[] +let ``LWW-register merge tie-breaks left on equal (timestamp, replica)`` () = + // When a.Timestamp = b.Timestamp AND a.Replica = b.Replica, the + // merge is intentionally left-biased — picks the left argument. + // This is the documented determinism contract; commutativity + // does NOT hold in this corner. Pinned as a [] (not + // []) because the law being checked is asymmetric. + let a = LwwRegister.Create(1, 100L, "r1") + let b = LwwRegister.Create(2, 100L, "r1") + (LwwRegister.Merge a b).Value |> should equal 1 + (LwwRegister.Merge b a).Value |> should equal 2 + + // ─── HyperMinHash — Jaccard monotonicity ─────────────────────────── // Reference: Yu & Weber arXiv:1710.08436.