WI-035: Who was at the table
WI-035: Who was at the table
players and session_participants have existed since WI-010 and been unused. WI-032 left them out on purpose, because the Player↔User distinction is the delicate part of the schema and deserved more than a footnote.
Logging who was there is what makes a session a memory rather than a checkbox — the second tier of ADR-0007, and the last part of it unbuilt.
In scope
GET/POST /me/players— your roster.PUT/GET /me/sessions/:id/participants— who played, who won, scores.
Out of scope
- The claim flow.
players.user_idexists and stays null. Letting somebody claim a player record means merging identities and deciding who may — real work, and dangerous to improvise. Its own item. - Editing or deleting a player. Removing someone who appears in sessions is a question about history, not a CRUD gap.
- UI. Routes first, as with WI-030 before WI-031.
A player is not a user (ADR-0006)
Most people’s game groups will never all sign up. A player is a lightweight local record holding a display name and an optional link to an account.
These describe real people who did not consent to an account, so they carry a name and nothing else (security-and-secrets.md). No email, no avatar, nothing to leak.
Two people called Dave
Players are not deduplicated by name. Two people with the same name is an ordinary situation, and merging them would attribute one person’s wins to another — a quiet corruption of exactly the data this table exists for.
The boundary this item is really about
Two checks, for different reasons:
- the session must be the caller’s to modify
- the players must be the caller’s to attach
Without the second, a stranger’s name lands in your history and your session lands in their counts. Both are seeded, and both fail loudly when removed.
Definition of done
- A roster starts empty, accepts names, and is not visible to another user.
- Two players with the same name stay distinct.
- Participants record winners and scores.
- Setting participants replaces the set — who was at the table is one fact.
- An empty set is valid and means nobody was recorded.
- The same player listed twice is tolerated, not a 500.
- Attaching another user’s player is a 403, and changes nothing.
- Touching or reading another user’s session is a 404.
- A score that would not fit
numeric(10,2)is a 400. - Each check proven able to fail.
- Gate green.
Verification
pnpm --filter @tabletop/api testpnpm gateNotes
The roster is ordered by most recently played with, not alphabetically, because ADR-0007 wants the last table offered as a one-tap default and the same people play the same games on the same night.
score is numeric(10,2) and crosses the wire as a string. The contract validates the string shape rather than accepting a float, which would silently lose the scale the column declares — the same reasoning as weight in WI-020.