run

fun run(seed: Long): S

Run with a single seed; assert convergence. Returns the pre-compaction converged state.

Three phases when a compactor is bound, and only the first when one is not.

Phase 0 — fold every permutation, assert equal and byte-identical. Exactly what this method has always done, preserved character for character. That is not politeness: a new gate placed ahead of an older one is how an older guard's coverage silently drops to zero, and phase 0's byte assertion is #1957's coverage for every non-compaction field in the zoo. The phases below add assertions over additional states and never replace it.

Phase A — fold every permutation, then compact to stable. The tombstone set the compaction predicate walks was built by Set.plus in fold order; gcIds inherits that order and the minted Compact op's positions map inherits it from gcIds. So one Compact op's map order depends on the merge order, and a plain MapSerializer there writes two fold-equal states to different bytes (#1978).

Phase B — compact each replica alone, then fold every permutation. Each replica mints its Compact from its own single-author history, in an order fixed at mint time and identical under every later fold — so phase A's axis is gone here, and what varies instead is the merge of already-compacted states: compactedDots + other.compactedDots, and the position of each Compact op within the unioned op set. Those are the #1957 and #713 axes.

Neither phase is redundant, and this is measured rather than argued. Phase A cannot reach the merge-of-compacted-states path, because after it runs there is nothing left to merge; phase B cannot reach the fold-dependent-tombstone-set path, because each replica compacts a history only it authored. Reverting each mechanism in turn on main:

mechanismphase Aphase B
Rga/Fugue Compact.positions map order (#1978)REDgreen
MovableTree.compactedDots set order (#1957)greenRED
order between several Compact ops (#713)greenRED

MovableTree is the case worth naming, because the shape recurs: its compact selects droppable ops by filtering a log kept sorted by (ts, replicaId), so the freshly-minted droppedDots is already canonical and phase A's serializer has nothing to fix. Measured over seeds 0..31, its minted-Compact iteration order varies across the six folds on 0 of 32 seeds — against Rga's 32 and Fugue's 13. Reaching the code that writes a field is not the same as reaching the disagreement, which is why a post-merge hook alone — what #2019 originally proposed — would report compaction reached on 24 of 32 seeds while leaving compactedDots exactly as unpinned as it was.

Phase B's soundness rests on the replicas' histories being disjoint, and that is asserted, not assumed — see assertReplicaHistoriesAreDisjoint. Replica Rᵢ's history holds only Rᵢ's ops, so no peer can hold a concurrent op referencing one of them, and Rga's "no surviving successor" condition (and Fugue's "no surviving tree anchor") is evaluated over a set nobody can add to behind the compactor's back. A future generator that gave two replicas one author id would break that premise, so the harness reds instead.