RgaGcCoordinator
Coordinator that drives tombstone GC (and optional history windowing) for an Rga CRDT replicated via Quilter, gated by the eviction-safe causal-stability barrier (ADR-003 addendum v3, #262).
Role
The Rga op-log grows without bound unless tombstones are periodically garbage-collected, but a tombstoned Insert(I, …) cannot be purged while a concurrent Insert(J, after=I) minted by a different author may still exist undelivered — purging I would orphan J everywhere (#262). The sound condition is causal stability, not a scalar watermark.
Quilter publishes the two version-vector quantities the barrier needs:
Quilter.cutFrontier — an atomically-published CutFrontier carrying both the stable cut
S(minover live peers — every peer has delivered everything at-or-below it) and the frontierF = max(F_live, retainedFrontier)(the highest dot any peer, live or evicted-but-retained, has told us exists). Published together (wiring invariant W1) so a compactor never observes a half-update whereFhas fallen below a known-to-exist dot.Quilter.deliveredLocal — this replica's own contiguous delivered version vector.
This coordinator observes cutFrontier and, on each emission, hands S, F, and the fresh delivered value to Rga.compact(stableCut, frontierMax, delivered). That method refuses GC unless the frontier is complete (delivered.dominates(F) — this replica has delivered every known-to-exist dot, so any concurrent successor of a tombstone is already visible), the tombstone is causally stable (S.contains(I.dot)), and it has no surviving local successor.
cutFrontier is republished whenever deliveredLocal or the matrix clock changes, and _deliveredLocal is updated before cutFrontier is published (see recomputeCut), so reading delivered.value on each emission is consistent with the cut that triggered it.
Loop-until-stable
On each cut emission the coordinator calls Rga.compact in a loop until it returns null. This handles the two-pass chain case: removing a tombstone may unblock a structural predecessor, making it eligible on the next pass. Quilter.apply updates state synchronously via StateFlow.update, so state.value reflects each applyCompaction before the next loop iteration — loop-until-null is therefore safe.
Each resulting RgaOp.Compact is bridged into a Patch<V>> via Rga.empty<V>().apply(compactOp) — a minimal single-op delta that any peer merges via Rga.piece, triggering the same purge on the remote op-log.
Window policy
windowPolicy may return additional ids to truncate from the visible prefix (history windowing). The default WindowPolicy.never does nothing beyond causal-stability GC.
Parameters
the atomically-published causal-stability cut + frontier from Quilter.cutFrontier.
this replica's contiguous delivered VV from Quilter.deliveredLocal.
called with each compaction Patch; the caller wires this to Quilter.apply so the delta propagates to all peers.
optional history-windowing policy (default WindowPolicy.never).
the CoroutineScope for background coroutines.