# Issue #310 — Host transition idempotency and error catalog ## Scope This artifact hardens the two host-owned scoreboard exits in the canonical gameplay flow: - `POST /lobby/sessions/{code}/rounds/next` - `POST /lobby/sessions/{code}/finish` The goal is retry-safe host behavior when the scoreboard transition already succeeded server-side but the client retries because of a duplicate click, timeout, or lost response. ## Transition contract | Endpoint | First valid transition | Idempotent replay state | Replay result | Broadcast behavior | Still-invalid states | |---|---|---|---|---|---| | `POST /lobby/sessions/{code}/rounds/next` | `scoreboard -> lie` | `lie` with persisted current-round bootstrap (`RoundConfig` + `RoundQuestion`) | `200 OK` with the same canonical next-round payload shape | `phase.lie_started` fires only on the first transition | `lobby`, `guess`, `reveal`, `finished` → `next_round_invalid_phase` | | `POST /lobby/sessions/{code}/finish` | `scoreboard -> finished` | `finished` | `200 OK` with the same final leaderboard payload shape | `phase.game_over` fires only on the first transition | `lobby`, `lie`, `guess`, `reveal` → `finish_game_invalid_phase` | ## Error catalog notes No new backend error codes were introduced for this slice. The contract change is behavioral: - `next_round_invalid_phase` now means the session is in a phase where the scoreboard → next-round transition has **not** already been completed, or the expected bootstrap artifact for the already-started round is missing. - `finish_game_invalid_phase` now means the session is in a phase where the scoreboard → finish transition has **not** already been completed. - Successful replays are returned as normal `200 OK` canonical responses instead of phase errors. ## Acceptance evidence - Repeated `rounds/next` calls after a successful scoreboard exit return the same canonical lie/bootstrap payload without incrementing the round twice. - Repeated `finish` calls after a successful scoreboard exit return the same finished leaderboard payload without rebroadcasting game-over. - Wrong-phase calls outside those replay states still return the existing shared error codes.