Skip to content

Feat/1767 season rollover leaderboard snapshot - #1781

Merged
Olowodarey merged 4 commits into
Arena1X:mainfrom
OsagieCynthia:feat/1767-season-rollover-leaderboard-snapshot
Aug 29, 2026
Merged

Olowodarey merged 4 commits into
Arena1X:mainfrom
OsagieCynthia:feat/1767-season-rollover-leaderboard-snapshot

Conversation

@OsagieCynthia

Copy link
Copy Markdown
Contributor

Atomic season rollover with leaderboard snapshot

season-rollover.scheduler.ts finalized a season and saved
rollover_processed_at as separate steps, with no immutable record of the
closed season's final standings. A crash mid-write could leave standings
finalized without the idempotency marker, and there was nothing to pay
rewards or audit against.

Changes

  • Add SeasonLeaderboardSnapshot entity + migration for an immutable
    season_leaderboard_snapshots table (unique on season, user).
  • processSeasonRollover now runs freeze → snapshot → reset points → open
    next season as one DB transaction — commits or rolls back together.
  • Idempotent: rollover_processed_at is re-checked inside the transaction,
    and the snapshot step is skipped if one already exists for the season, so
    a retry can never duplicate rows.
  • Unit tests for exactly-one-snapshot, no-duplicate-on-retry, and full
    rollback when the snapshot write fails.

Testing

  • npm test (backend) — 1490/1490 passing
  • npm run lint (backend) — 0 errors
  • npm run build (backend) — clean
  • npm run migration:check-timestamps — passed

Closes #1767

Adds the SeasonLeaderboardSnapshot entity that will hold one immutable
row per (season, user) captured at rollover time, per Arena1X#1767.
Table with FKs to seasons/users, unique (season_id, user_id), and an
index on (season_id, rank) for ordered reads.
processSeasonRollover previously finalized the ending season (its own
transaction) and only afterwards saved rollover_processed_at, with no
leaderboard snapshot at all — a crash between those steps could leave
standings finalized without ever recording rollover_processed_at, and
there was no immutable record of final standings for reward payout or
audit.

Fold freeze + snapshot + open-next into one DB transaction: finalize
the ending season, write its final standings into the new
season_leaderboard_snapshots table, reset season_points, then activate
the next season — all committed together or rolled back together.

Idempotent: rollover_processed_at is re-checked inside the transaction
(closing the race between two concurrent ticks), and snapshotLeaderboard
skips if a snapshot for the season already exists, so a retry can never
duplicate rows past the unique (season, user) constraint.
Covers exactly-one-snapshot-per-season, no duplicate snapshot when the
rollover transaction is retried, and a full rollback (season standings
untouched) when the snapshot write fails mid-transaction.
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
insight-arena-4rll Ready Ready Preview Aug 29, 2026 4:52pm

@Olowodarey
Olowodarey merged commit 9a35be5 into Arena1X:main Aug 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Backend] seasons: Atomic season rollover with leaderboard snapshot

2 participants