v0.13.1 concession management and record, clients update, version report
test / test (push) Canceled after 0s
test / test (push) Canceled after 0s
This commit is contained in:
1 parent
f1ac4cbbe7
commit
6e8d80c788
30 files changed
+704
-81
No files matched your search
+60
-8
@@ -95,17 +95,23 @@ Authorization: Bearer <token>
|
||||
### Health Check
|
||||
`GET /health`
|
||||
|
||||
Returns server and storage status.
|
||||
Returns server and storage status, and the build the server runs.
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"time": 1699123456,
|
||||
"storage": "ok"
|
||||
"storage": "ok",
|
||||
"version": "v0.14.0-3-g1a2b3c4"
|
||||
}
|
||||
```
|
||||
|
||||
`version` is `git describe` of the source the binary was built from (`make`
|
||||
sets it), or `dev` for a plain `go build`. Compare it after a deployment: a
|
||||
client newer than the server gets 404 `NOT_FOUND` for endpoints the server
|
||||
predates.
|
||||
|
||||
Storage states:
|
||||
- `"disabled"` - No database configured (`-dsn` or `CHESS_DSN`)
|
||||
- `"ok"` - Database operational with auth enabled
|
||||
@@ -225,6 +231,11 @@ carries its SAN, derived on read from the stored position before it.
|
||||
}
|
||||
```
|
||||
|
||||
`concession` (present only when there was one) is the game's first
|
||||
resignation or agreed draw, in stored-result form (`"result": "black_wins"`);
|
||||
see [Continuing after a resignation or agreed
|
||||
draw](#continuing-after-a-resignation-or-agreed-draw).
|
||||
|
||||
`result` is omitted while a game is ongoing. Persisted terminal values are
|
||||
`white_wins`, `black_wins`, `draw`, and `stalemate`. `pgnResult` is the PGN
|
||||
token for the same outcome (`1-0`, `0-1`, `1/2-1/2`, or `*` while ongoing),
|
||||
@@ -235,7 +246,7 @@ rules core cannot notate, which the server logs. A claimed player's `name` is
|
||||
their username when the claim was recorded; it survives later renames and
|
||||
account deletion, and is omitted for anonymous and computer players.
|
||||
|
||||
Undo stays available after a result (a finished game can be rewound and
|
||||
Undo stays available after most results (a finished game can be rewound and
|
||||
played on), so a stored game is never final. Responses carry a strong `ETag`
|
||||
and `Cache-Control: private, no-cache`: send `If-None-Match` to get 304 while
|
||||
nothing changed.
|
||||
@@ -366,6 +377,7 @@ A live game (`GET /games/{gameId}` and every game response) carries:
|
||||
| `termination` | How a finished game ended: `checkmate`, `resignation`, `stalemate`, `insufficient_material`, `threefold_repetition`, `fifty_move_rule`, or `agreement`; omitted while unfinished |
|
||||
| `drawOffer` | `w` or `b`: that side's draw offer awaits an answer; omitted when none |
|
||||
| `drawOutcome` | Only on responses to `POST .../draw`: `offered`, `accepted`, or `declined` |
|
||||
| `concession` | The game's first resignation or agreed draw: `{"result": "black wins", "termination": "resignation", "ply": 24}`, `ply` being the moves played when it was made; kept when an undo continues play |
|
||||
|
||||
**Automatic draws.** After every move the game is drawn, without a claim, as
|
||||
on most online servers, when:
|
||||
@@ -421,16 +433,53 @@ Other errors are 400: `GAME_OVER` for a finished game, `INVALID_REQUEST` for
|
||||
an ambiguous or computer side, a missing offer, or a game that is not
|
||||
ongoing. Both return the game.
|
||||
|
||||
#### Continuing after a resignation or agreed draw
|
||||
|
||||
A resignation or agreed draw is a *concession*: a result the players chose.
|
||||
|
||||
- **Both sides human:** it is final. `undo` returns 400 `GAME_OVER`, for a
|
||||
hot-seat game too.
|
||||
- **Against the computer:** the claimant may `undo` it and play on. The live
|
||||
`state` and `termination` (and the stored `result` and `termination`)
|
||||
follow the game; `concession` keeps the first one, in memory and in the
|
||||
database (`concession_*` columns), through later undos, moves, and results.
|
||||
History reports it, and the full PGN notes it in the final comment, such as
|
||||
`{ White resigned at ply 24; play continued. }`.
|
||||
|
||||
### Claims and control
|
||||
|
||||
Anyone holding the game ID may move for, undo, reconfigure, or unload an
|
||||
unclaimed side; a claim is what protects a player. Moves, resignation, and
|
||||
draws act for one side and need that side's claim (or none). The requests
|
||||
below affect both sides, so:
|
||||
|
||||
| Request | Allowed when |
|
||||
|---|---|
|
||||
| undo, configure players | no side is claimed by another user: the caller holds every claim, or nothing is claimed |
|
||||
| delete (unload) | the caller holds a claim, or nothing is claimed |
|
||||
|
||||
Otherwise they return 403 `UNAUTHORIZED`. Two players who each claimed a
|
||||
side therefore cannot take back each other's moves or swap the opponent for
|
||||
the computer: takebacks between players are not supported. Send the bearer
|
||||
token (optional authentication) with these requests.
|
||||
|
||||
### Undo Moves
|
||||
`POST /games/{gameId}/undo`
|
||||
|
||||
Reverts moves from history. Undo stays available after a result, including a
|
||||
resignation or an agreed draw: it rewinds the result and any draw offer.
|
||||
```json
|
||||
{"count": 2}
|
||||
```
|
||||
|
||||
Reverts `count` plies (default 1). Allowed after a result: it rewinds the
|
||||
result and clears draw offers. Refused while the computer is thinking, after
|
||||
a resignation or agreed draw between two humans (400 `GAME_OVER`), and by
|
||||
another user's claim (403, see [Claims and control](#claims-and-control)).
|
||||
|
||||
### Configure Players
|
||||
`PUT /games/{gameId}/players`
|
||||
|
||||
Changes player configuration mid-game.
|
||||
Changes player configuration mid-game; subject to [Claims and
|
||||
control](#claims-and-control).
|
||||
|
||||
### Get Board
|
||||
`GET /games/{gameId}/board`
|
||||
@@ -441,7 +490,8 @@ Returns ASCII board visualization.
|
||||
`DELETE /games/{gameId}`
|
||||
|
||||
Unloads the live game from memory. Its persisted game and move history remain
|
||||
available through the history endpoint. Returns 204 on success.
|
||||
available through the history endpoint. Returns 204 on success, 403 to a
|
||||
caller holding none of the game's claims.
|
||||
|
||||
## Error Format
|
||||
```json
|
||||
@@ -453,7 +503,9 @@ available through the history endpoint. Returns 204 on success.
|
||||
```
|
||||
|
||||
Error codes:
|
||||
- `GAME_NOT_FOUND` - Invalid game ID
|
||||
- `GAME_NOT_FOUND` - No live game with that ID
|
||||
- `NOT_FOUND` - No such endpoint (for example a server older than the client)
|
||||
- `UNAUTHORIZED` - A side or the game is claimed by another user
|
||||
- `INVALID_MOVE` - Illegal chess move
|
||||
- `NOT_HUMAN_TURN` - Wrong player type for turn
|
||||
- `GAME_OVER` - Game already ended
|
||||
|
||||
+8
-2
@@ -237,7 +237,7 @@ Commands encapsulate operations with type, arguments, and optional user ID for a
|
||||
### Player Configuration
|
||||
Players identified by UUID (authenticated users) or generated IDs (anonymous), configured with type (human/computer), skill level, and search time.
|
||||
|
||||
### Storage Schema (version 2)
|
||||
### Storage Schema (version 3)
|
||||
```sql
|
||||
users (
|
||||
user_id uuid PRIMARY KEY,
|
||||
@@ -271,6 +271,10 @@ games (
|
||||
-- insufficient_material, threefold_repetition,
|
||||
-- fifty_move_rule, agreement
|
||||
end_time_utc timestamptz, -- set exactly when result is set
|
||||
concession_result text, -- first resignation (win) or agreed draw,
|
||||
concession_termination text, -- kept when an undo continues play
|
||||
concession_ply integer, -- against the computer; all four set
|
||||
concession_time_utc timestamptz -- together or all NULL
|
||||
)
|
||||
|
||||
moves (
|
||||
@@ -286,7 +290,9 @@ moves (
|
||||
|
||||
`schema_version` records the applied version; version 2 added
|
||||
`termination`, constrained to be set exactly with `result` and to fit it
|
||||
(wins by checkmate or resignation, draws by a draw rule or agreement). Indexes cover session expiry,
|
||||
(wins by checkmate or resignation, draws by a draw rule or agreement).
|
||||
Version 3 added the `concession_*` columns: a rewind clears `result` and
|
||||
`termination` but keeps them, and the first concession is never overwritten. Indexes cover session expiry,
|
||||
each claim column ordered by `(start_time_utc DESC, game_id DESC)` for the game
|
||||
listing, and the start time of unclaimed games for the anonymous-game purge.
|
||||
Claims and player IDs deliberately have no foreign key to `users`: history
|
||||
|
||||
+10
-3
@@ -119,14 +119,18 @@ chess > computer
|
||||
```
|
||||
|
||||
#### `undo` / `u`
|
||||
Undo one or more moves.
|
||||
Undo one or more moves. Refused when another user claimed a side (players
|
||||
cannot take back each other's moves) and after a resignation or agreed draw
|
||||
between two humans. Against the computer a resignation can be undone; the
|
||||
client notes that it stays on record.
|
||||
```
|
||||
chess > undo # Undo last move
|
||||
chess > undo 3 # Undo last 3 moves
|
||||
```
|
||||
|
||||
#### `show` / `h`
|
||||
Display board and game state with colored pieces.
|
||||
Display board and game state with colored pieces, the result, and any
|
||||
pending draw offer.
|
||||
```
|
||||
chess > show
|
||||
```
|
||||
@@ -145,7 +149,10 @@ chess > delete <gameId> # Unload specific live game
|
||||
```
|
||||
|
||||
#### `poll` / `p`
|
||||
Long-poll for game updates (waits up to 30 seconds).
|
||||
Long-poll for game updates (waits up to 30 seconds). Returns on a move, an
|
||||
undo, a result (a resignation or an accepted draw), or a draw offer, and
|
||||
prints the outcome or offer. In a two-player game, `poll` is how you learn
|
||||
that the opponent moved, offered a draw, or resigned.
|
||||
```
|
||||
chess > poll
|
||||
```
|
||||
|
||||
+16
-4
@@ -62,13 +62,25 @@ The tables:
|
||||
| `chess.schema_version` | One row: the applied migration |
|
||||
| `chess.users` | Accounts: lowercase username and email, Argon2id password hash |
|
||||
| `chess.sessions` | One active session per user; deleting it signs the user out |
|
||||
| `chess.games` | Players, claims (`*_claimed_by`), name snapshots, `result`, `termination`, times |
|
||||
| `chess.games` | Players, claims (`*_claimed_by`), name snapshots, `result`, `termination`, `concession_*`, times |
|
||||
| `chess.moves` | Ply `move_number` 1..n, UCI move, FEN after the move |
|
||||
|
||||
`result` is `white_wins`, `black_wins`, `stalemate`, or `draw`, and
|
||||
`termination` says how: `checkmate`, `resignation`, `stalemate`,
|
||||
`insufficient_material`, `threefold_repetition`, `fifty_move_rule`, or
|
||||
`agreement`. Both are empty while a game is unfinished.
|
||||
`agreement`. Both are empty while a game is unfinished, and an undo empties
|
||||
them again. `concession_result`, `concession_termination`, and
|
||||
`concession_ply` keep the first resignation or agreed draw even when an undo
|
||||
against the computer continued play:
|
||||
|
||||
```sql
|
||||
SELECT game_id, concession_result, concession_ply,
|
||||
coalesce(result, 'unfinished') AS now
|
||||
FROM chess.games
|
||||
WHERE concession_result IS NOT NULL
|
||||
AND (result IS DISTINCT FROM concession_result
|
||||
OR termination IS DISTINCT FROM concession_termination); -- continued after conceding
|
||||
```
|
||||
|
||||
`\set g '<game-id>'` stores a game ID for the `:'g'` references below.
|
||||
|
||||
@@ -276,7 +288,7 @@ su -m chess -c '/home/chess/bin/chess-server db delete -confirm -dsn "postgres:/
|
||||
su -m chess -c '/home/chess/bin/chess-server db init -dsn "postgres:///chess?host=/tmp"'
|
||||
```
|
||||
|
||||
`db init` prints `Database schema ready (version 2)`. In split mode the
|
||||
`db init` prints `Database schema ready (version 3)`. In split mode the
|
||||
`chess` role owns no tables and cannot drop or create them; run both commands
|
||||
as `postgres` acting as the owner role instead, e.g. on Linux:
|
||||
|
||||
@@ -304,6 +316,6 @@ Verify any of them:
|
||||
|
||||
```sql
|
||||
\dt chess.* -- games, moves, schema_version, sessions, users
|
||||
SELECT version FROM chess.schema_version; -- 2
|
||||
SELECT version FROM chess.schema_version; -- 3
|
||||
\drds -- chess: search_path and timeouts in database chess
|
||||
```
|
||||
@@ -137,12 +137,14 @@ systemctl status chessd
|
||||
journalctl -u chessd -f # server log
|
||||
sudo chess-db user add -username <name> # accounts; see database.md
|
||||
sudo chess-db verify # check stored games; run after upgrades
|
||||
curl -s http://127.0.0.1:8080/health # "storage":"ok"
|
||||
curl -s http://127.0.0.1:8080/health # "storage":"ok", "version" = the build
|
||||
systemctl list-timers chess-backup.timer # with ENABLE_BACKUP=yes
|
||||
```
|
||||
|
||||
**Upgrade:** build, then rerun `setup.sh` with the new `CHESS_BINARY`.
|
||||
Schema migrations run before the service starts.
|
||||
Schema migrations run before the service starts. `/health` reports the
|
||||
running build as `version` (`git describe` of the source); check it matches,
|
||||
and upgrade the server before or with a web client served from elsewhere.
|
||||
|
||||
**Rollback:** until you delete it, the previous binary is kept:
|
||||
|
||||
|
||||
+6
-1
@@ -302,10 +302,15 @@ Restore the previous web client files on the host as well.
|
||||
|
||||
```sh
|
||||
jail# service chessd status
|
||||
jail# fetch -qo - http://127.0.0.1:8080/health # "storage":"ok"
|
||||
jail# fetch -qo - http://127.0.0.1:8080/health # "storage":"ok", "version" = the build
|
||||
jail# sockstat -4 -6 -l | grep postgres # no TCP listener
|
||||
jail# sockstat -4 -c | grep ':8080' # peer = nginx address
|
||||
jail# su -m chess -c 'psql -X -d chess -Atc "SHOW search_path"' # chess
|
||||
jail# su -m chess -c 'psql -X -d postgres -c "select 1"' # rejected by pg_hba
|
||||
host# curl -s https://<site>/chess/health # through nginx
|
||||
```
|
||||
|
||||
`version` in `/health` is `git describe` of the checkout the binary was built
|
||||
from. After an upgrade it must match the new build; a web client copied to a
|
||||
static site ahead of the server gets 404 `NOT_FOUND` (shown as "Game Not
|
||||
Found" by older clients) for endpoints the running server predates.
|
||||
+2
-1
@@ -41,7 +41,8 @@ into phases that each ship and test on their own.
|
||||
| D3 | Where SAN/PGN is produced | **Decided: server-side Go** (R1), derived on read from the initial FEN and the UCI line: no schema change, one implementation for web and CLI, no third-party script under the CSP. |
|
||||
| D4 | Player names | **Done.** `white_name`/`black_name` snapshot the claimant's username in the claim transaction and survive renames and deletions; exposed as `players.*.name`. |
|
||||
| D5 | Draw rules | **Done.** Draws apply automatically after every move, as on most online servers: dead material, threefold repetition, and the fifty-move rule (FIDE's claimable draws, and so also its 75-move and fivefold rules). Draw offers (a computer answers from its evaluation) and resignation exist; the stored `termination` says how a game ended. |
|
||||
| D6 | Undo after a result | **Decided: keep allowed**, so a player can step back from a finished game and play a line again. Consequence: a finished game's stored history, result, and end time are rewritten by undo; replay shows the line as it stands now. |
|
||||
| D6 | Undo after a result | **Decided: keep allowed**, so a player can step back from a finished game and play a line again. Consequence: a finished game's stored history, result, and end time are rewritten by undo; replay shows the line as it stands now. **Refined (v3 schema):** a resignation or agreed draw between two humans is final, and undo, player changes, and unload respect other users' claims (no takebacks between players). Against the computer a concession can be undone, but the first one stays on record (`concession_*`) and in history and PGN. |
|
||||
| D8 | Human-vs-human in the web client | **Open.** The web client plays only against the computer; two-player games run through the API and terminal client (`join`, `poll`). A web mode would need join-by-link, a long-poll for the opponent's moves, and an offer indicator on the Draw button (accept by pressing it). |
|
||||
| D7 | List pagination | **Done.** Opaque keyset `cursor` on `(start_time_utc, game_id)`; `offset` kept for existing clients and exclusive with `cursor`. |
|
||||
|
||||
## Phase R1 — Notation Core (server) — done
|
||||
|
||||
Reference in new issue
Block a user