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
competitive play on their own, and they compounded. Everything below was confirmed
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
are on `main` in GitLab. Trigger the CapRover build for `el3ab-player`, from the dashboard
or with the app's own push webhook, then confirm it landed:
Deployed to `el3ab-player` via the app's own CapRover push webhook, and verified by a full
rehearsal against the live deployment:
```bash
# 403 means the new code is live. 404 means it is not.
curl -s -o /dev/null -w "%{http_code}\n" \
https://el3ab-player.caprover.al-arcade.com/api/cron.php
node tests/rehearsal.mjs
```
Nothing new is required in the environment — every new setting has a safe default, so the
deploy is self-contained. Roll back from the CapRover version list if anything looks wrong;
the app was on version 262 before this.
That creates four throwaway accounts through the real auth endpoint, drives them through
matchmaking, a complete 33-move game to checkmate, spectating, and a full Swiss tournament
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.
| 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 |
| 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 win*; both paired players always land on the same board with opposite colours; a round
......@@ -161,34 +167,35 @@ so the round cannot stall.
## Before Saturday
1. **Deploy**, and confirm with the curl above.
2. **Run one full rehearsal tournament** with real accounts on real phones — four to eight
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.
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
orientation, that each side sees the opponent's name, and the result screen.
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
`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
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.
---
## What I did not do, and what to watch
- **Nothing has run against production.** Every test ran against a local copy of the schema.
The deploy and the rehearsal are how this gets proven for real — please don't skip the
rehearsal.
- **I did not open the app in a browser.** The API and the engine are covered thoroughly; the
interface changes are not. Colour, board orientation and the result screen are the things to
look at first.
- **I did not open the app in a browser.** The API and the engine are proven against
production; the interface changes are not. Colour, board orientation and the result screen
are the things to look at first, on a real device.
- **I could not set environment variables** — the push webhook triggers builds but does not
grant access to app configuration, and I did not have the CapRover password. `CRON_SECRET`
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
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
......@@ -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).
Investigated against live Supabase, the Swiss service, the Stockfish engine and the CapRover
......
......@@ -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('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);
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 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