fix(core): one rank owns a sequence id, so two ops at one stamp do not resolve by arrival order (C40)
Two ListInsert ops under one ClientId at the identical Stamp are both admissible (dedup is on OpId; the id-space record bounds only an honest mint), and List::insert_at was idempotent on the id — so the first to land kept the slot with its value and its Fugue anchor, and the two delivery orders encoded different snapshots (the node carried 2 on one replica, 99 on the other).
- One rank over
(kind tag, position, encoded value), inListso it reachesTextruns on the same seam. Smaller rank takes the id with its value and its anchor; a tie changes nothing (replay stays inert, two claims on one node take the meet of their positions). - Every seat path runs it — a claim owns the key it named, not the id. Plain
ListInsert, every codepoint of aTextrun, a children-list birth whose key was free, the document-ranked eviction and join (which now yield to a live scalar on the id), andList::merge, which was answering by which side received. - Tag leads, read as the number it is — mixed scalar/composite falls out of the same comparator, no semantic "composite beats scalar" to re-decide per new kind. Two composites are still ranked at the
(list, stamp)key (C24, birth over move then smaller element id) before the sequence is touched. - Position outranks the value: it is the only key a delete leaves behind, and all a tombstone encodes. Value-first would have let a delete landing between two claims decide the winner by which one it buried; measured, all four orders of
[insert, insert, delete]now agree. - Fixes
[ListInsert, tagged, tagless]replacing the scalar while[ListInsert, tagless, tagged]kept it — one op set, two trees.
Spec crates/core/tests/list_stamp_collision.rs (14) folds every delivery order per shape and compares encode_state bytes.
Residue filed as C133: a tombstone holds no kind, so a mixed pair a delete lands between is still ordered by arrival, and List::merge — whose tombstone arm can rank nothing — is left non-associative on the same shape (104/3000 collision pools, 0 without a delete; C40 closes the commutativity half, 2638/3000 before → 0 after). Narrowed here (the mixed split no longer keys on which claim arrived first), not closed — closing it needs a rank persisted per dead id, which the tombstone run compression forbids. C134 files the detached deep clone the winning merge arm installs (pre-dates C40 in the vacant arm, widened here, unreachable from Document).
ARCHITECTURE §List + DECISIONS updated.
Reviewer note: Copilot's quota is spent (a review request does not stick) and cubic produces a summary only, so the local gate stands in: full workspace (226 suites), targeted Miri on the ten touched suites, a 22-mutant statement-deletion sweep over the comparator and every call site (5 survivors on the first pass, each closed by a new test or by deleting the unreachable branch; second pass: no survivors), and an adversarial falsification pass in a separate worktree.
The falsification pass broke two claims, both now fixed in prose or filed: "every seat path runs the one rank" was overstated (a document-ranked eviction/join reads only the leading tag; a claim meeting a tombstone reads only the position — ARCHITECTURE/DECISIONS/spec now say which seam reads what), and List::merge lost associativity on collision+delete (filed on C133). It verified the anchor-travels claim, C24-unchanged-for-composites, replay inertness, Miri, and the main-vs-branch split measurement the prose asserts; 4000 random scalar-only op sets folded in every permutation give 0 divergences here against 1345/4000 on the parent commit.