Files
Dune/docs/SEAT_HANDOVER.md
T
Quintine 7dfd6b3048 Prototype voluntary seat handover with durable private retries
Let a current owner issue, cancel or replace a private 24-hour one-time offer.
Recipients accept the same faction and saved game while old browser sessions,
recovery keys and entry receipts lose authority. Preserve exact claim retries
across refresh, restart and later offers, with private tab-scoped continuation.

Fence every claim mutation to its winning transaction. Add durable receipts
and additive backfill for already-issued offers without changing game JSON or
existing seat authority. Block competing navigation during unresolved owner
operations; remove invalidated owner kits on confirmed recovery.

Record the pending CHOAM Nexus after-victory secrecy/window decision without
inventing the unresolved gameplay or opening mode/publication gates.

Validation: types/lint and 4,364 offline tests passed; production build passed;
43 HTTP tests passed. Added 19 offline and 3 HTTP regressions. Independent
privacy/persistence review passed, including deterministic CAS interleavings,
rollback, legacy migration and exact receipt restoration. Browser room 2EMYG4CY
retained private setup choices after handover/refresh and continued into Storm;
follow-up recovery removed its invalid offer with cards/forces/spice unchanged.
All 218 opening rooms preserved (291 after QA and both broad runs). The opening
456 seat, 49 recovery-key, 56 recovery-receipt and 106 entry-receipt rows were
also verified unchanged. Server reused; no restart or recurring automation.

Final private report: ~/.local/share/dune/checkpoints/20260913-seat-handover/
verification-final/report.json. Source fingerprint:
8949cdf58eb8bc8bd33f2657c6b21352b3ee5e17e2a556c684a89073015632f4.
Check/build/HTTP duration: 140565/6689/24835 ms. The earlier broad run passed;
a final recovery-UI review fix required the second source-bound run.
Goal aggregate counter 64,301,976 -> 65,031,751 before Git (729,775) for this
bounded capability and its concurrency/recovery review; full goal remains active.
2026-09-13 15:28:14 +10:00

4.5 KiB

Voluntary seat handover prototype

13 September 2026. This is an operational multiplayer feature, independent of board-game rules and mode gates. The current human owner can hand their own seat to another browser with a private one-time kit. No host takeover, inactivity inference or delegation by another player is authorized by this feature.

Player flow

  • In the table, Pass your seat to another player creates a private offer. Share its complete kit only with the intended recipient. It expires 24 hours after server creation. Ordinary play can continue until acceptance.
  • Cancel handover invalidates the current offer. A confirmed replacement invalidates an older kit. If an owner operation is uncertain, retry it exactly; replacing uncertain work requires an explicit acknowledgement.
  • The recipient chooses Accept a seat handover on the home page, reads the kit and confirms the target room and seat. An active cookie for another seat in that room is rejected; use a separate browser profile.
  • Acceptance preserves the player ID, faction, name, cards, resources, private commitments, AI-control setting and game history. It revokes all previous browser sessions and recovery authority for that seat. The new owner should create their own recovery kit after acceptance.

Offers and exact recipient retry proof remain in private tab-scoped browser storage across refresh. Claim proof is written and read back before any request is sent. A dedicated screen retains it while the outcome is unknown and excludes competing create/join/recovery controls. An exact receipt response and a matching cookie-authorized read must both confirm the seat. The room URL is anchored before the proof is removed. Closing the tab or clearing its storage can lose this proof; explicit abandonment explains that it cannot undo a completed claim.

Server boundary

The existing /api/rooms/:code/control endpoint supports createSeatHandover, revokeSeatHandover and claimSeatHandover. Only an authenticated seat may issue or revoke its own offer. The server uses 256-bit client secrets, hashed storage, a server expiry and room-version comparisons with active-session checks inside the atomic write. Recovery invalidates an unclaimed offer by revoking its issuing session. Plaintext secrets never enter game JSON, projected views, URLs or history.

The additive offer and receipt tables preserve existing games and credentials. The second additive migration also copies existing claimed-offer receipts without changing game or seat authority. Claims alter custody and room version while keeping stored game JSON byte-identical. Exact claim retries reissue the same session; creating a later offer does not consume that receipt. A later successful claim or recovery revokes the session and prevents replay. Invalid proofs reveal no private view. Transport retains origin checks, bounded bodies, no-store and room-scoped HttpOnly cookies.

Evidence and remaining acceptance

tests/seat-handover-client.test.ts covers kit/proof validation, refresh identity, storage failures and receipt/read confirmation. tests/seat-handover-store.test.ts executes production SQL against in-memory SQLite for custody, private saved state, rollback and concurrent ownership changes. tests/seat-handover-http.test.ts exercises the real HTTP boundary, exact retries, revoked cookies/recovery, cross-origin rejection and competing-seat protection.

Required checkpoint results belong in the verified commit and compact report. Browser QA in new room 2EMYG4CY confirmed offer restoration, acceptance with the same four private traitor choices, refresh, and continued play through starting placement and the initial card deal. The recipient reached Storm with Crysknife, Lady Jessica, ten forces in Arrakeen and ten spice. A follow-up created a recovery key and new offer, recovered the same seat, and verified that the invalidated offer was removed from the owner controls while cards, forces and spice stayed intact. This was a bounded desktop journey; existing human game authority was not used. All 218 opening rooms and their 456 seat, 49 recovery-key, 56 recovery-receipt and 106 entry-receipt records remained unchanged after migration and targeted QA. Full disconnection acceptance across all decisions, unattended continuation, unprepared abandoned-seat recovery and comprehensive hosted acceptance remain unfinished. The historical generation/delegation design in MULTIPLAYER_AUDIT remains a broader proposal; current handover uses exact active-token fencing instead of a separate generation field.