DealSession
Drives a cryptographically fair card deal over a Seam, as an op-based CRDT.
Each card is a CardState; players sequentially encrypt the deck (SRA layers compose because the cipher is commutative), then non-quorum players strip their layer so only the visibility quorum can read each card. State converges via the CardOp stream broadcast over the seam.
Single-writer assumption. The shuffle and strip helpers read the base ciphertext from state outside the _state.update block, run crypto, then commit. If the incoming-op collector races to apply a remote op to the same card in that window, the commit can overwrite converged state with a stale-base ciphertext (canApply checks GSet membership, not ciphertext provenance). This is safe under the current model where each session has exactly one local writer; concurrent local shuffle/strip while remote ops mutate the same card is a production hazard that needs an "await my turn" gate (deferred).
Constructors
Functions
Set visibility quorums for each card (local state — no broadcast).
Optionally escrow my key to a trusted server for liveness recovery.
Encrypt every card with my key. Seeds the deck from deck on first call (encoding each plaintext once via encodePlaintext). If remote ops have already grown the deck with placeholder cards (the synchronous test harness delivers the first shuffler's ops before the second shuffler calls this), the deck is used as-is so the second shuffler's key composes on top.