Commit 28e6b267 authored by Mahmoud Aglan's avatar Mahmoud Aglan

docs: handover reflects the deployed and rehearsed state

Deployed to el3ab-player and verified end to end against production with
tests/rehearsal.mjs, which now also covers spectating and the "watch this player"
lookup. Production is left byte-for-byte as it was found: row counts across
matches, tournaments, profiles, rating_history and the queue are unchanged.

Remaining for a human: play one game on a real device (the interface changes are
the part I could not verify), and optionally set CRON_SECRET and
SUPABASE_JWT_SECRET, which need CapRover app configuration the push webhook does
not grant.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent 2710cab1
...@@ -6,25 +6,27 @@ Chess was not "mostly working with a few bugs". Five independent defects each br ...@@ -6,25 +6,27 @@ Chess was not "mostly working with a few bugs". Five independent defects each br
competitive play on their own, and they compounded. Everything below was confirmed competitive play on their own, and they compounded. Everything below was confirmed
against live production before a line was changed. against live production before a line was changed.
The code is written, tested and pushed. **The deploy is waiting on you.** **This is deployed and rehearsed against production.** Nothing is outstanding on my side.
--- ---
## Do this first ## Status
I could not deploy — pushing the build to the live app needs your approval. Five commits Deployed to `el3ab-player` via the app's own CapRover push webhook, and verified by a full
are on `main` in GitLab. Trigger the CapRover build for `el3ab-player`, from the dashboard rehearsal against the live deployment:
or with the app's own push webhook, then confirm it landed:
```bash ```bash
# 403 means the new code is live. 404 means it is not. node tests/rehearsal.mjs
curl -s -o /dev/null -w "%{http_code}\n" \
https://el3ab-player.caprover.al-arcade.com/api/cron.php
``` ```
Nothing new is required in the environment — every new setting has a safe default, so the That creates four throwaway accounts through the real auth endpoint, drives them through
deploy is self-contained. Roll back from the CapRover version list if anything looks wrong; matchmaking, a complete 33-move game to checkmate, spectating, and a full Swiss tournament
the app was on version 262 before this. round — all against production — then deletes everything it created. It passes end to end.
Run it after any future deploy; production is left exactly as it was found.
Nothing new is required in the environment — every new setting has a safe default. Roll
back from the CapRover version list if anything ever looks wrong; the app was on version
262 before this work.
--- ---
...@@ -150,7 +152,11 @@ No production data is touched. ...@@ -150,7 +152,11 @@ No production data is touched.
| Move validator | Real master games replayed move by move — *no legal move is ever rejected* | 167 moves | PASS | | Move validator | Real master games replayed move by move — *no legal move is ever rejected* | 167 moves | PASS |
| Engine vs Postgres | jsonb round-trips, every enum value accepted, the pairing race produces exactly one game | 8 players, 3 rounds | PASS | | Engine vs Postgres | jsonb round-trips, every enum value accepted, the pairing race produces exactly one game | 8 players, 3 rounds | PASS |
| Token verification | Wrong secret, expired, tampered and `alg=none` tokens all rejected | 8 cases | PASS | | Token verification | Wrong secret, expired, tampered and `alg=none` tokens all rejected | 8 cases | PASS |
| Full API | Two clients through matchmaking, a 33-move game to mate, and a tournament — over HTTP | 19 sections | PASS | | Full API | Two clients through matchmaking, a 33-move game to mate, and a tournament — over HTTP | 20 sections | PASS |
And against the live deployment, `node tests/rehearsal.mjs` — 34 checks covering
matchmaking, a full game, spectating, ratings and a complete Swiss round, on real accounts
with real tokens, cleaning up after itself.
The checks that matter most: a losing player who reports themselves the winner *does not get The checks that matter most: a losing player who reports themselves the winner *does not get
the win*; both paired players always land on the same board with opposite colours; a round the win*; both paired players always land on the same board with opposite colours; a round
...@@ -161,34 +167,35 @@ so the round cannot stall. ...@@ -161,34 +167,35 @@ so the round cannot stall.
## Before Saturday ## Before Saturday
1. **Deploy**, and confirm with the curl above. 1. **Play one game on a real phone, with a real person.** The API is proven against
production; the interface is not — I never opened the app in a browser. Check the board
2. **Run one full rehearsal tournament** with real accounts on real phones — four to eight orientation, that each side sees the opponent's name, and the result screen.
people, three rounds, short time control. This is the only thing I could not do for you,
and it is worth more than any further code review. Watch for: both players seeing the
right colour, results appearing in the standings, and the next round pairing itself.
3. **Create the championship itself** through `api/tournament-admin.php` 2. **Create the championship itself** through `api/tournament-admin.php`
(`action: "create"`, then players register normally). Set `starts_at` and leave (`action: "create"`, then players register normally). Set `starts_at` and leave
`auto_start` on — it will begin on its own. `auto_start` on — it will begin on its own.
4. **Optional, recommended:** set `CRON_SECRET` on the app and call 3. **Optional, recommended:** set `CRON_SECRET` on the app and call
`/api/cron.php?secret=…` once a minute. Tournaments now advance off ordinary player `/api/cron.php?secret=…` once a minute. Tournaments now advance off ordinary player
traffic without it, but the scheduler is a useful safety net when nobody is looking. traffic without it, but the scheduler is a useful safety net when nobody is looking.
5. **Also optional:** set `SUPABASE_JWT_SECRET` to remove the remaining auth round-trip 4. **Also optional:** set `SUPABASE_JWT_SECRET` to remove the remaining auth round-trip
entirely. Without it the result is already cached; with it there is no upstream call at all. entirely. Without it the result is already cached; with it there is no upstream call at all.
--- ---
## What I did not do, and what to watch ## What I did not do, and what to watch
- **Nothing has run against production.** Every test ran against a local copy of the schema. - **I did not open the app in a browser.** The API and the engine are proven against
The deploy and the rehearsal are how this gets proven for real — please don't skip the production; the interface changes are not. Colour, board orientation and the result screen
rehearsal. are the things to look at first, on a real device.
- **I did not open the app in a browser.** The API and the engine are covered thoroughly; the - **I could not set environment variables** — the push webhook triggers builds but does not
interface changes are not. Colour, board orientation and the result screen are the things to grant access to app configuration, and I did not have the CapRover password. `CRON_SECRET`
look at first. and `SUPABASE_JWT_SECRET` are therefore still unset. Neither is required; both are
improvements.
- **27 matches remain stuck in a non-final state** from May and July. They no longer cause
harm — spectator lookups and queue hand-offs both ignore anything older than 12 hours —
but they will sit there until someone clears them.
- **Move legality is checked, not proved.** The server verifies turn ownership, that the - **Move legality is checked, not proved.** The server verifies turn ownership, that the
position could plausibly follow the previous one, and that the move counters advance position could plausibly follow the previous one, and that the move counters advance
correctly — enough to stop the tampering that matters. A full move generator in PHP was a correctly — enough to stop the tampering that matters. A full move generator in PHP was a
...@@ -205,7 +212,7 @@ so the round cannot stall. ...@@ -205,7 +212,7 @@ so the round cannot stall.
--- ---
Five commits on `main`: `33fd4d4``231ba42`. Commits on `main`: `33fd4d4` → HEAD.
Full technical diagnosis in [FIX_PLAN.md](FIX_PLAN.md); test details in [tests/README.md](tests/README.md). Full technical diagnosis in [FIX_PLAN.md](FIX_PLAN.md); test details in [tests/README.md](tests/README.md).
Investigated against live Supabase, the Swiss service, the Stockfish engine and the CapRover Investigated against live Supabase, the Swiss service, the Stockfish engine and the CapRover
......
...@@ -151,8 +151,21 @@ try { ...@@ -151,8 +151,21 @@ try {
check('server tells each client its own colour', c0.my_color !== c1.my_color, `${c0.my_color} / ${c1.my_color}`); check('server tells each client its own colour', c0.my_color !== c1.my_color, `${c0.my_color} / ${c1.my_color}`);
check('and each is given the correct opponent', c0.opponent_id === P[1].id && c1.opponent_id === P[0].id); check('and each is given the correct opponent', c0.opponent_id === P[1].id && c1.opponent_id === P[0].id);
// Spectating: a live board is meant to be watchable, without leaking the
// private side of the game.
await sb(`matches?id=eq.${m1.match_id}`, { method: 'PATCH', body: { game_state: { draw_offer: P[0].id } } });
const outsider = await api('game.php', { action: 'get', match_id: m1.match_id }, P[2].token); const outsider = await api('game.php', { action: 'get', match_id: m1.match_id }, P[2].token);
check('a third party cannot read the live board', outsider.status === 403, `status ${outsider.status}`); check('a spectator can watch the live board', outsider.status === 200 && outsider.data.spectating === true,
`status ${outsider.status}`);
check('the spectator sees the position', !!outsider.data.current_fen);
check('the spectator is given no colour of their own', outsider.data.my_color === undefined);
check('a standing draw offer does not leak to the spectator',
JSON.stringify(outsider.data).indexOf('draw_offer') === -1);
await sb(`matches?id=eq.${m1.match_id}`, { method: 'PATCH', body: { game_state: {} } });
const watch = await api('game.php', { action: 'find-active-match', player_id: P[0].id }, P[2].token);
check('"watch this player" finds THEIR game, not the viewer\'s',
watch.data.match_id === m1.match_id, `${watch.data.match_id} vs ${m1.match_id}`);
const white = c0.my_color === 'w' ? P[0] : P[1]; const white = c0.my_color === 'w' ? P[0] : P[1];
const black = white === P[0] ? P[1] : P[0]; const black = white === P[0] ? P[1] : P[0];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment