4.2 KiB
Leader/Follower Tournament Sync
Backend supports one-way tournament replication:
- leader tournament stays writable
- follower tournament stays read only
- leader pushes full snapshots asynchronously
- follower never pulls
- follower never needs to reach leader
This fits live topology:
- leader = local laptop / Raspberry Pi / LAN host without public IP
- follower = internet VM with public HTTPS endpoint
- only leader needs outbound access to follower
Live Setup
1. Create follower tournament on remote backend
Create empty tournament with:
read_only_mode: truesync_auth_token: <shared secret>
Do not send teams payload for follower. Backend allows empty read only follower tournament creation.
2. Create normal leader tournament on local backend
Create tournament as usual. This remains normal writable tournament.
3. Configure leader to push to follower
Update leader tournament with:
sync_target_url: https://remote.example/tournaments/:id/sync_statesync_auth_token: <same shared secret as follower>
Leader becomes push source. Follower accepts only authenticated snapshot pushes.
4. Run tournament normally on leader
These leader-side actions enqueue async snapshot push:
- tournament updates
- timer updates
- team renames
- match state changes
- match score changes
- stage finish / playoff generation
Queue retries with backoff until follower accepts snapshot.
Takeover
If remote must become writable after sync phase:
- disable
read_only_modeon follower - backend clears follower token when no longer needed
- follower becomes normal standalone tournament again
After takeover, leader should no longer push to old follower URL.
API Contract
Leader-side writable config
Tournament push mode active when:
sync_target_urlpresentsync_auth_tokenpresentread_only_modefalse
Follower-side push receiver
Tournament accepts push when:
read_only_modetruesync_auth_tokenpresent
Push endpoint:
PATCH /tournaments/:id/sync_state
Auth:
Authorization: Bearer <token>
Payload:
{ snapshot: ... }
Snapshot import replaces tournament graph using sync_source_id mapping, not local DB ids.
Frontend Integration Notes
Frontend does not need separate "follower UI mode" for normal viewers. Remote tournament should look same for non-owner users.
Owner-facing frontend can expose:
read_only_modetogglesync_target_urlinputsync_auth_tokeninput- sync health display:
sync_last_pushed_atsync_last_push_error
Recommended owner UX:
- create remote follower first
- copy remote
sync_auth_token - paste remote
sync_stateURL into leader - save leader sync config
- show last push time / error state
- allow explicit follower takeover by disabling
read_only_mode
Frontend should block owner edits on follower when read_only_mode true. Backend already enforces this with 423 Locked.
Future Feature Rule
Any new persisted tournament state must be handled in three places:
- leader write path
- snapshot export/import path
- follower/read-only behavior
Guardrail now exists:
TournamentSyncSchemalists every synced and ignored model column- spec fails when new DB column appears without explicit sync decision
This does not auto-implement sync for new features. It makes missing sync work fail loudly in tests instead of silently drifting.
For every new tournament feature, add:
- schema decision in
TournamentSyncSchema - snapshot builder/importer support if state must replicate
- one follower lifecycle test or roundtrip spec that proves behavior survives sync
Coverage Today
Current backend coverage includes:
- async push queue and retry behavior
- follower auth and read-only lock
- full follower lifecycle HTTP E2E with unrelated tournaments and mismatched ids
- team renames
- multiple decider matches
- playoff generation blocked before deciders finish
- playoff generation after deciders finish
- follower takeover
- large roundtrip graph coverage for 32 groups, 4 teams each, 64-team playoffs
Still not magic:
- if new feature adds new state and nobody updates sync contract, tests will fail
- if new feature changes behavior but not schema, add behavior test too