Fugue

A Fugue sequence CRDT: an ordered list with a provable maximal non-interleaving guarantee for concurrent insertions.

Vs. Rga. Rga tracks only a left-origin per element; concurrent runs inserted at the same position can interleave character-by-character. Fugue builds an explicit tree where each element's FugueOp.Insert.parent and FugueOp.Insert.side determine its tree placement. Depth-first traversal of this tree produces the canonical sequence. Because consecutive inserts from the same replica each become children of the previous element, they form a contiguous chain that cannot interleave with concurrent inserts from other replicas. This is the only sequence CRDT with a formal maximal-non-interleaving proof (Weidner et al., "The Art of the Fugue", arXiv:2305.00583, 2023).

Tree structure.

  • The virtual root is FugueId.HEAD.

  • Every element N is either a left child or right child of its FugueOp.Insert.parent.

  • Left children are traversed before the parent; ordered by FugueId ascending (sender-id order).

  • Right children are traversed after the parent; ordered in reverse FugueNode.rightOrigin sequence order (the right child whose rightOrigin appears later in the sequence comes first among right siblings — it claims the space closer to the parent).

  • Traversal is depth-first: leftChildren..., parent, rightChildren...

Convergence. The "state" is the set of FugueOps. piece is idempotent set-union — it satisfies the three lattice laws. Any two replicas that have absorbed the same set of ops produce the same toList regardless of delivery order.

Causal-stability GC. causalDots exposes the delivered Dots so the us.tractat.kuilt.quilter.Quilter causal-stability machinery can drive compaction. compact garbage-collects causally-stable tombstones whose id is no longer a live tree parent. This bounds op-log growth under long-running replication. See docs/op-log-crdt-compaction.md for the safety argument.

Performance. Local insertAt/removeAt maintain the materialized tree and sequence incrementally (FugueSeqState, #1211): an append is O(1) tree work instead of the pre-#1211 O(n log n) full tree rebuild per insert (quadratic overall for append-heavy sequences). Remote apply, piece, compact, and deserialization don't thread the incremental state — the next local edit or read rebuilds it once and resumes incremental maintenance.

Serialization. Use wireSerializer rather than the compiler-generated serializer to correctly thread the element-type serializer through the op-log.

Type Parameters

V

the element type. Must be serializable for wire transport.

Samples

val a = ReplicaId("A")
val b = ReplicaId("B")

// Replica A builds a run: "a1", "a2", "a3" (each prepended before the prior front).
val (fA1, opA1) = Fugue.empty<String>().insertAt(a, 0, "a1")
val (fA2, opA2) = fA1.insertAt(a, 0, "a2")
val (fA3, opA3) = fA2.insertAt(a, 0, "a3")

// Replica B independently builds "b1", "b2" at the same position.
val (fB1, opB1) = Fugue.empty<String>().insertAt(b, 0, "b1")
val (fB2, opB2) = fB1.insertAt(b, 0, "b2")

// Merge all ops into both replicas.
val mergedByA = fA3.apply(opB1).apply(opB2)
val mergedByB = fB2.apply(opA1).apply(opA2).apply(opA3)

// Both converge to the same order.
check(mergedByA.toList() == mergedByB.toList()) { "Convergence: both must agree" }

val merged = mergedByA.toList()
// The A-run and B-run each form a contiguous block — no interleaving.
val aIndices = merged.mapIndexedNotNull { i, v -> if (v.startsWith("a")) i else null }
val bIndices = merged.mapIndexedNotNull { i, v -> if (v.startsWith("b")) i else null }
check(aIndices == (aIndices.first()..aIndices.last()).toList()) { "A run is contiguous: $merged" }
check(bIndices == (bIndices.first()..bIndices.last()).toList()) { "B run is contiguous: $merged" }

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

All ids that have been garbage-collected by any FugueOp.Compact in this op-log.

Link copied to clipboard

This replica's current Lamport timestamp (max seen + 1 after any op).

Link copied to clipboard
val size: Int

The number of visible elements.

Functions

Link copied to clipboard
fun apply(op: FugueOp<V>): Fugue<V>

Apply an op received from a remote replica, advancing the Lamport clock.

Link copied to clipboard
open override fun causalDots(): Set<Dot>

The causal Dots this op-log has delivered — one per FugueOp.Insert plus one per id in every FugueOp.Compact.

Link copied to clipboard

The per-author high-water of dots this state delivered and has since compacted away without retaining their identities.

Link copied to clipboard
open override fun classify(op: FugueOp<V>): LogOp<FugueId>

Classify one FugueOp — see OpLogCrdt.classify. Delegates to the sole classifier.

Link copied to clipboard
fun compact(stableCut: VersionVector, frontierMax: VersionVector, delivered: VersionVector): Pair<Fugue<V>, FugueOp.Compact>?

Garbage-collect tombstoned elements that are causally stable under the eviction-safe causal-stability barrier (same design as Rga.compact, docs/op-log-crdt-compaction.md).

Link copied to clipboard
open override fun dotOf(id: FugueId): Dot

The causal dot id belongs to — see OpLogCrdt.dotOf.

Link copied to clipboard
open operator override fun equals(other: Any?): Boolean

Two Fugue instances are equal when their op-sets are equal — i.e. they represent the same CRDT state. The lamport high-water mark is a clock convenience, not part of the value: two converged replicas may differ in lamport if one advanced its clock by observing a duplicate op, so including it in equality would make a.piece(a) != a in that case.

Link copied to clipboard
open override fun hashCode(): Int
Link copied to clipboard
fun insertAt(replica: ReplicaId, index: Int, value: V): Pair<Fugue<V>, FugueOp.Insert<V>>

Insert value at visible position index (0 = prepend before first visible element).

Link copied to clipboard
open override fun operations(): Sequence<FugueOp<V>>

The ops this replica currently holds — see OpLogCrdt.operations.

Link copied to clipboard
open override fun opSerializer(vSerializer: KSerializer<V>): KSerializer<FugueOp<V>>

The canonical FugueOpSerializer — see OpLogCrdt.opSerializer.

Link copied to clipboard
open override fun piece(other: Fugue<V>): Fugue<V>

Merge two replicas' op-logs. The result is idempotent set-union — both replicas converge to the same toList after piece.

Link copied to clipboard
fun removeAt(index: Int): Pair<Fugue<V>, FugueOp.Remove<V>>?

Remove the visible element at index.

Link copied to clipboard
fun toList(): List<V>

The current visible (non-tombstoned) elements, in sequence order.

Link copied to clipboard
open override fun toString(): String