feat(registry): Postgres, accounts with player codes, worlds and characters as snapshots #9

Merged
kblack0610 merged 1 commit from feat/registry-worlds into main 2026-10-25 18:37:15 +00:00
Owner

Moves the registry from SQLite to Postgres and adds accounts, characters and shared worlds stored as snapshots. Design: plan section "1. Registry: Postgres, accounts, worlds".

  • Postgres through pgx. Plain SQL migrations (migrations/) are applied at boot under an advisory lock. Every v1.7.0 table and query is ported.
  • registry import-sqlite <registry.db> copies a v1.7.0 database once, in one transaction. A marker row makes a re-run a no-op, and a column Postgres lacks fails the whole import.
  • Accounts are reached by a player code: 3 EFF words plus 4 digits, about 52 bits, returned once. Only HMAC-SHA256(pepper, code) is stored. The header is Authorization: Account <code>. Create can adopt an existing profileId. Account create and wrong codes (player or invite) are rate limited per IP. account_identities exists and is empty.
  • Characters belong to one account. Worlds have an owner, members who join by invite code, a 30-day soft delete, and fork. A 90 s lease means only one writer at a time: a SERVER_TOKEN server or a member hosting.
  • Snapshots are gzipped JSON in the saves bucket under saves/<id>/<snapshot>.json.gz, up to 8 MiB. The SHA-256 is checked on upload and again by the bucket. A write whose parent is not the head gets 409, checked under a row lock.
  • Restore writes a new head. An hourly retention sweep keeps manual, restore and fork snapshots, every head, the last 20 autosaves, and one autosave a day for 30 days.
  • Postgres runs writes concurrently, where SQLite serialised them. Register's session seq and ELO updates now take advisory locks; the session-seq test caught this.

Tests: go test ./... runs against throwaway postgres:17.6-alpine and bitnamilegacy/minio:2025.4.22 containers.

  • 70 top-level tests passed, three runs in a row.
  • New coverage: every new route, lease conflict and expiry, the 409 on a stale parent, restore, fork, GC, stranger vs member, the import, bucket checksum rejection, and 16 concurrent uploads where exactly one wins.
  • Negative control: with the row lock removed, the concurrent-upload test fails.

The image was built locally with docker build.

https://claude.ai/code/session_01MHKGfpCFTRu5C8K961mGdG

Moves the registry from SQLite to Postgres and adds accounts, characters and shared worlds stored as snapshots. Design: plan section "1. Registry: Postgres, accounts, worlds". - Postgres through pgx. Plain SQL migrations (`migrations/`) are applied at boot under an advisory lock. Every v1.7.0 table and query is ported. - `registry import-sqlite <registry.db>` copies a v1.7.0 database once, in one transaction. A marker row makes a re-run a no-op, and a column Postgres lacks fails the whole import. - Accounts are reached by a player code: 3 EFF words plus 4 digits, about 52 bits, returned once. Only HMAC-SHA256(pepper, code) is stored. The header is `Authorization: Account <code>`. Create can adopt an existing `profileId`. Account create and wrong codes (player or invite) are rate limited per IP. `account_identities` exists and is empty. - Characters belong to one account. Worlds have an owner, members who join by invite code, a 30-day soft delete, and fork. A 90 s lease means only one writer at a time: a SERVER_TOKEN server or a member hosting. - Snapshots are gzipped JSON in the saves bucket under `saves/<id>/<snapshot>.json.gz`, up to 8 MiB. The SHA-256 is checked on upload and again by the bucket. A write whose parent is not the head gets 409, checked under a row lock. - Restore writes a new head. An hourly retention sweep keeps manual, restore and fork snapshots, every head, the last 20 autosaves, and one autosave a day for 30 days. - Postgres runs writes concurrently, where SQLite serialised them. Register's session seq and ELO updates now take advisory locks; the session-seq test caught this. Tests: `go test ./...` runs against throwaway postgres:17.6-alpine and bitnamilegacy/minio:2025.4.22 containers. - 70 top-level tests passed, three runs in a row. - New coverage: every new route, lease conflict and expiry, the 409 on a stale parent, restore, fork, GC, stranger vs member, the import, bucket checksum rejection, and 16 concurrent uploads where exactly one wins. - Negative control: with the row lock removed, the concurrent-upload test fails. The image was built locally with `docker build`. https://claude.ai/code/session_01MHKGfpCFTRu5C8K961mGdG
The registry moves from SQLite to Postgres (pgx, plain SQL migrations applied
at boot) and gains accounts, characters and shared worlds:

- Accounts are reached by a player code (3 EFF words + 4 digits, ~52 bits),
  shown once; only HMAC-SHA256(ACCOUNT_PEPPER, code) is stored. Create can
  adopt an existing profileId. Create and wrong codes are rate limited per IP.
- Characters belong to one account. Worlds have an owner, members by invite
  code, soft delete for 30 days, fork into a new world, and a 90 s lease so
  only one server (SERVER_TOKEN or a member hosting) writes at a time.
- Snapshots are immutable gzipped JSON in the saves bucket; every write names
  its parent and is refused with 409 unless it is the head. Restore writes a
  new head. An hourly sweep keeps manual/restore/fork snapshots, heads, the
  newest 20 autosaves and one a day for 30 days.
- registry import-sqlite copies a v1.7.0 registry.db once, in one transaction.
- Register's session seq and ELO updates take advisory locks: Postgres runs
  them concurrently where SQLite serialised every write.

Tests run against throwaway postgres:17.6-alpine and MinIO containers.

Claude-Session: https://claude.ai/code/session_01MHKGfpCFTRu5C8K961mGdG
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kblack0610/bnb-game-servers!9
No description provided.