Claim a Guest on the wire, and write its later Games to the Account #385

Closed
opened 2026-10-11 16:27:27 +00:00 by root · 14 comments
root commented 2026-10-11 16:27:27 +00:00 (Migrated from gitlab.core-project.space)

Imported from GitHub amaurycoudr/chess-project#385, opened by @amaurycoudr on 2026-10-11 16:27 UTC.


Parent

#365

What to build

A signed-in Player can claim the Guest their browser holds, through the API alone. The claim links the Guest to their Account for good and moves every Game the Guest owns to the Account (ADR 0035). "My games" as the Account then lists them on any signed-in browser. A Game decided later for a Seat the claimed Guest still holds is written as the Account. A claimed Guest keeps its Seats but takes no new one. Deleting the Account deletes the Guests it claimed, and the Account export lists them.

  • Schema, one migration on top of the Guest history and Accounts v1 schemas:
    • the Guest record gains the claiming Account and the claim date, set together or not at all and never cleared;
    • the Account reference cascades on delete.
  • Contract: a new guest router.
    • guest.get reads the caller's Guest as none, unclaimed with its count of Games (zero for a Guest with no record), claimed-by-you or claimed-by-other. It is a read, so it needs no Origin.
    • guest.claim needs a Session and answers how many Games moved. It refuses with GUEST_NOT_FOUND (404) without a Guest header and GUEST_ALREADY_CLAIMED (409) when another Account holds the claim. Both go in the refusal status table.
    • The Guest comes from the RPC context, never an input (ADR 0034).
  • The claim, in the games feature module, in one transaction:
    1. upsert the Guest record by hash and lock it;
    2. refuse a claim by another Account;
    3. answer zero moved for a repeat by the same Account;
    4. otherwise set the link and rewrite White and Black ownership, keeping at most one owner per Color.
    • Claiming a Guest that owns no Games is allowed, and links it.
  • A Game decided later: when a Game is written, a claimed Guest on either Color is written as that Color's Account owner. The write reads the claim under the same row lock the claim takes, so a race cannot leave a Game on a claimed Guest. The Room is unchanged.
  • Taking Seats:
    • without a Session, a claimed Guest's header counts as no header on create and take, so a fresh Guest is minted;
    • one exception: a take that hands back a Seat the claimed Guest already holds;
    • a header with no Guest record is an ordinary Guest, never re-minted;
    • the lookup happens in the service, never in the RPC context;
    • every other procedure still accepts a claimed Guest's header as proof of its Seats.
  • Export: the Account export gains the Guests it claimed, each with its claim date only, never the hash.
  • Self-games are rewritten like any other Game. Their shape in the listing, the Game read and the export belongs to #386: this ticket does not widen the Color shapes.

Seam: The RPC wire

Acceptance criteria

  • guest.get answers each of its four values, including unclaimed with zero for a header with no record, and claimed-by-other for a claimed Guest read with a different Account's Session or with none.
  • A claim moves every Game the Guest owns, as White and as Black, to the Account, leaves each opponent's side untouched, and answers the count; game.list as the Account then lists them from another signed-in client.
  • A claim without a Session is refused as unauthenticated; without a Guest header it is GUEST_NOT_FOUND.
  • A claim of a Guest claimed by another Account is GUEST_ALREADY_CLAIMED and moves nothing; a repeat by the same Account succeeds with zero moved.
  • Two Accounts claiming one Guest at once: exactly one wins.
  • A claim of a Guest with no record creates and links the record, and a Game it decides afterwards goes to the Account.
  • A Game decided after the claim for a Seat the claimed Guest holds is written as the Account, both with and without a Session on the deciding Move.
  • A claim racing a Game write for that Guest leaves the Game on the Account.
  • A self-game, from two claimed Guests and from a Guest against its own Account, ends with both Colors owned by the Account.
  • Without a Session, create and take with a claimed Guest's header mint a fresh Guest. A take that hands back a Seat the claimed Guest holds does not. A header with no record mints nothing. With a Session, create and take act as the Account.
  • Deleting the Account deletes its claimed Guests. Their header then reads unclaimed with zero, and their Games stay readable by link with no owner on that side.
  • The Account export lists each claimed Guest with its claim date and no hash.
> _Imported from GitHub [amaurycoudr/chess-project#385](https://github.com/amaurycoudr/chess-project/issues/385), opened by [@amaurycoudr](https://github.com/amaurycoudr) on 2026-10-11 16:27 UTC._ <!-- github-issue:385 --> --- ## Parent #365 ## What to build A signed-in **Player** can claim the **Guest** their browser holds, through the API alone. The claim links the **Guest** to their **Account** for good and moves every **Game** the **Guest** owns to the **Account** (ADR 0035). "My games" as the **Account** then lists them on any signed-in browser. A **Game** decided later for a **Seat** the claimed **Guest** still holds is written as the **Account**. A claimed **Guest** keeps its **Seats** but takes no new one. Deleting the **Account** deletes the **Guests** it claimed, and the **Account** export lists them. - **Schema**, one migration on top of the Guest history and Accounts v1 schemas: - the **Guest** record gains the claiming **Account** and the claim date, set together or not at all and never cleared; - the **Account** reference cascades on delete. - **Contract**: a new `guest` router. - `guest.get` reads the caller's **Guest** as `none`, `unclaimed` with its count of **Games** (zero for a **Guest** with no record), `claimed-by-you` or `claimed-by-other`. It is a read, so it needs no Origin. - `guest.claim` needs a **Session** and answers how many **Games** moved. It refuses with `GUEST_NOT_FOUND` (404) without a **Guest** header and `GUEST_ALREADY_CLAIMED` (409) when another **Account** holds the claim. Both go in the refusal status table. - The **Guest** comes from the RPC context, never an input (ADR 0034). - **The claim**, in the games feature module, in one transaction: 1. upsert the **Guest** record by hash and lock it; 2. refuse a claim by another **Account**; 3. answer zero moved for a repeat by the same **Account**; 4. otherwise set the link and rewrite White and Black ownership, keeping at most one owner per **Color**. - Claiming a **Guest** that owns no **Games** is allowed, and links it. - **A Game decided later**: when a **Game** is written, a claimed **Guest** on either **Color** is written as that **Color**'s **Account** owner. The write reads the claim under the same row lock the claim takes, so a race cannot leave a **Game** on a claimed **Guest**. The **Room** is unchanged. - **Taking Seats**: - without a **Session**, a claimed **Guest**'s header counts as no header on create and take, so a fresh **Guest** is minted; - one exception: a take that hands back a **Seat** the claimed **Guest** already holds; - a header with no **Guest** record is an ordinary **Guest**, never re-minted; - the lookup happens in the service, never in the RPC context; - every other procedure still accepts a claimed **Guest**'s header as proof of its **Seats**. - **Export**: the **Account** export gains the **Guests** it claimed, each with its claim date only, never the hash. - Self-games are rewritten like any other **Game**. Their shape in the listing, the **Game** read and the export belongs to #386: this ticket does not widen the **Color** shapes. Seam: The RPC wire ## Acceptance criteria - [ ] `guest.get` answers each of its four values, including `unclaimed` with zero for a header with no record, and `claimed-by-other` for a claimed **Guest** read with a different **Account**'s **Session** or with none. - [ ] A claim moves every **Game** the **Guest** owns, as White and as Black, to the **Account**, leaves each opponent's side untouched, and answers the count; `game.list` as the **Account** then lists them from another signed-in client. - [ ] A claim without a **Session** is refused as unauthenticated; without a **Guest** header it is `GUEST_NOT_FOUND`. - [ ] A claim of a **Guest** claimed by another **Account** is `GUEST_ALREADY_CLAIMED` and moves nothing; a repeat by the same **Account** succeeds with zero moved. - [ ] Two **Accounts** claiming one **Guest** at once: exactly one wins. - [ ] A claim of a **Guest** with no record creates and links the record, and a **Game** it decides afterwards goes to the **Account**. - [ ] A **Game** decided after the claim for a **Seat** the claimed **Guest** holds is written as the **Account**, both with and without a **Session** on the deciding **Move**. - [ ] A claim racing a **Game** write for that **Guest** leaves the **Game** on the **Account**. - [ ] A self-game, from two claimed **Guests** and from a **Guest** against its own **Account**, ends with both **Colors** owned by the **Account**. - [ ] Without a **Session**, create and take with a claimed **Guest**'s header mint a fresh **Guest**. A take that hands back a **Seat** the claimed **Guest** holds does not. A header with no record mints nothing. With a **Session**, create and take act as the **Account**. - [ ] Deleting the **Account** deletes its claimed **Guests**. Their header then reads `unclaimed` with zero, and their **Games** stay readable by link with no owner on that side. - [ ] The **Account** export lists each claimed **Guest** with its claim date and no hash.
root commented 2026-10-11 16:27:27 +00:00 (Migrated from gitlab.core-project.space)

GitHub issue relations, imported as "related" links (GitLab CE has no sub-issues or blocking links).

> _GitHub issue relations, imported as "related" links (GitLab CE has no sub-issues or blocking links)._ <!-- github-relations --> - Parent (sub-issue of): #365 - Blocked by: #384, #383, #381 - Blocking: #387, #386
root commented 2026-10-13 08:39:50 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #386

mentioned in issue #386
root commented 2026-10-13 08:39:51 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #387

mentioned in issue #387
root commented 2026-10-13 08:41:22 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #365

mentioned in issue #365
root commented 2026-10-13 08:41:28 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #381

mentioned in issue #381
root commented 2026-10-13 08:41:28 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #383

mentioned in issue #383
root commented 2026-10-13 08:41:29 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #384

mentioned in issue #384
root commented 2026-10-13 08:42:30 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #365

marked this issue as related to #365
root commented 2026-10-13 08:42:39 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #381

marked this issue as related to #381
root commented 2026-10-13 08:42:41 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #383

marked this issue as related to #383
root commented 2026-10-13 08:42:41 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #384

marked this issue as related to #384
root commented 2026-10-13 08:42:41 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #387

marked this issue as related to #387
root commented 2026-10-13 08:42:42 +00:00 (Migrated from gitlab.core-project.space)

marked this issue as related to #386

marked this issue as related to #386
root commented 2026-10-14 16:34:11 +00:00 (Migrated from gitlab.core-project.space)

mentioned in merge request !529

mentioned in merge request !529
root (Migrated from gitlab.core-project.space) closed this issue 2026-10-15 08:20:24 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
amaurycoudr/chess-project#385
No description provided.