Files
scrabble-game/PLAN.md
T
Ilia Denisov eeaad62b10
Tests · Go / test (push) Successful in 11s
Tests · Integration / integration (push) Successful in 8s
Stage 1: backend foundation (Postgres, sessions, accounts, OTel)
- internal/postgres: pgx-over-database/sql pool (otelsql), embedded goose
  migrations into schema 'backend', committed go-jet code + cmd/jetgen tool.
- internal/account: durable accounts + unified telegram/email identities
  (UUIDv7 keys), find-or-create provisioning with unique-conflict handling.
- internal/session: opaque 256-bit tokens stored as a SHA-256 hash, revoke-only
  (no TTL); write-through cache gating /readyz; store + service.
- internal/telemetry: OTel tracer/meter providers (none/stdout) + request-timing
  middleware; internal/config gains Postgres + OTel env loading.
- internal/server: /api/v1 {public,user,internal,admin} skeleton + X-User-ID
  middleware; /readyz checks DB ping + cache; main wires
  telemetry -> db+migrate -> warm cache -> server.
- Tests: unit + integration (build tag 'integration', testcontainers
  postgres:17) for migrations, accounts, sessions, readyz; new integration.yaml.
- Docs: ARCHITECTURE, TESTING, PLAN refinements, root + backend READMEs.

Session/account REST handlers deferred to Stage 6 (gateway); OTLP + dashboards
to Stage 11.
2026-06-02 13:52:26 +02:00

9.3 KiB
Raw Blame History

Scrabble Game — implementation plan

Living plan and stage tracker. Each stage is implemented in its own session; the rules for starting and finishing a stage are in CLAUDE.md. The architecture/decision record is docs/ARCHITECTURE.md; behaviour is docs/FUNCTIONAL.md. When a stage produces a decision, bake it back here and into the affected docs/code in the same PR.

Context

Greenfield multiplatform Scrabble. Players arrive from a platform (Telegram first; later VK/MAX/iOS/Android) or standalone web (email / guest). Three executables — gateway, backend, ui — plus per-platform side-services. Deliberately simpler than the sibling ../galaxy-game (idea donor, not a template). The ../scrabble-solver engine is embedded in-process as a library.

Locked decisions (recap — full record in docs/ARCHITECTURE.md)

Stack: go.work monorepo, modules scrabble/<name>, Go 1.26.x, backend gin+pgx+Postgres(schema backend)+goose+zap+OTel (deps added when first used). Wire: Connect-RPC + FlatBuffers (client↔gateway), REST/JSON + X-User-ID (gateway↔backend), gRPC server-stream for live events. Auth: platform-native, thin opaque session token, no Ed25519/signing, likely no Redis. UI: pure HTML5/CSS, plain Svelte + Vite, Capacitor for native. MVP surfaces: Telegram + web (email + ephemeral guest) + link/merge. Variants: ru/en/Эрудит. Legality: validate-at-submit. End: empty bag+rack / 6 scoreless / 24h timeout. Hint: top-1. Word-check: unlimited + complaint. Robot: P(win)≈0.40, margin targeting, [2,90]min skewed timing, sleep 00:0007:00 opp-tz, nudge logic. Dictionary: pin per game. History: structured + GCG export, dictionary- independent (see ARCHITECTURE §9.1).

Stage tracker

# Stage Status
0 Scaffolding (go.work, backend skeleton, docs, CI) done
1 Backend foundation (config, server, Postgres+goose, sessions, accounts) todo
2 Engine package over scrabble-solver todo
3 Game domain (lifecycle, rules, hint, word-check, history+GCG, stats) todo
4 Lobby & social (matchmaking, friends, block, chat, profile, nudge) todo
5 Robot opponent todo
6 Gateway edge (Connect/FB, platform auth, sessions, push bridge, admin) todo
7 UI (plain Svelte + Vite, board, lobby, chat, i18n) todo
8 Telegram integration (bot side-service, deep-link, push) todo
9 Admin & dictionary ops (complaint review, version reload) todo
10 Account linking & merge todo
11 Polish (observability, perf with evidence, deploy) todo

Scaffolding is incremental: go.work lists only existing modules; each stage adds the modules it needs.

Stages

Each stage: read this plan + relevant docs, interview the owner on the open details below, implement within scope, then update plan/docs/code and get CI green before marking done.

Stage 0 — Scaffolding (done)

Scope: go.work (Go 1.26.3, use ./backend); minimal runnable backend (gin, zap, /healthz, /readyz, env config); docs skeleton; PLAN.md; CLAUDE.md; .gitea/workflows/go-unit.yaml; README; .gitignore. Acceptance: go build ./backend/... + go vet + gofmt clean + go test ./backend/... green; CI green on push.

Stage 1 — Backend foundation

Scope: config/server route groups (/api/v1/{public,user,internal,admin}, probes), Postgres (pgx) + embedded goose migrations + schema backend, telemetry (OTel) wiring, in-memory cache scaffolding, thin sessions + accounts + platform identities. Open details: Postgres version + DSN/search_path convention; jet vs sqlc/sqlx (default jet); migration naming; exact session-token shape (opaque random length, TTL, revocation); account/identity table shape; whether the admin bootstrap lands here or in Stage 9.

Stage 2 — Engine package

Scope: backend/internal/engine over scrabble-solver — versioned DAWG load/registry, GenerateMoves/ValidatePlay/ScorePlay wrappers, bag/rack, the dictionary-independent game-state model + decode helpers. Add replace scrabble-solver => ../scrabble-solver to go.work here and solve the CI sibling-checkout (clone gitea.iliadenisov.ru/.../scrabble-solver). Open details: how CI obtains the solver (clone sibling vs publish/tag the solver module); in-memory game-state representation; how blanks and exchanges are modelled; Эрудит specifics to verify against the solver.

Stage 3 — Game domain

Scope: create/join, turn order, submit play/pass/exchange/resign, validate-at-submit, scoring, end-conditions, 24h timeout/auto-resign, hint, word-check + complaint capture, structured history + GCG writer, stats on finish. Open details: GCG dialect details (blanks, exchanges, notation); exact stats edge cases; turn-timeout scheduler mechanism (cron vs per-game timer); complaint payload shape.

Stage 4 — Lobby & social

Scope: matchmaking pool, friends, block, per-game chat, profile + email confirm-code, nudge. Open details: pool fairness/keying confirmation; deep-link format per platform; chat length limit + retention; friend-request lifecycle; email-code provider (SMTP relay choice).

Stage 5 — Robot opponent

Scope: human-like player — balance ~0.40, margin targeting, skewed [2,90]min timing + sleep + nudge logic, friend/DM blocking, name pool. Open details: exact delay distribution + parameters; margin band; name pool source; how the scheduler drives robot moves; metrics for tuning balance.

Stage 6 — Gateway edge

Scope: Connect/gRPC-Web (h2c), Telegram initData validation → session → X-User-ID, in-memory rate-limit, admin Basic-Auth passthrough, FlatBuffers transcoding, in-app push stream bridging backend push gRPC stream, email + ephemeral-guest paths. Open details: FlatBuffers schema layout + message_type catalog; rate-limit classes/limits; admin surface routing; session cache shape at the gateway.

Stage 7 — UI

Scope: plain Svelte + Vite static; Connect-web + FlatBuffers client; lobby (my games, profile tabs); board (HTML5/CSS grid, drag-n-drop, no assets); chat; hint/word-check; in-app stream; i18n en/ru; in-memory session (+IndexedDB if available); Capacitor-ready structure. Open details: detailed game-board UX (deferred by the owner to this stage); client routing; offline/refresh behaviour; design system / theming.

Stage 8 — Telegram integration

Scope: bot side-service, deep-link invites, platform push (your-turn / nudge), Mini App launch/auth; backend↔platform internal API. Open details: bot framework/library; deep-link scheme; push message templates; internal API contract; Mini App hosting/origin.

Stage 9 — Admin & dictionary ops

Scope: admin endpoints (users, games, complaint review queue, dictionary versions + reload), complaint→dictionary update pipeline. Open details: whether a server-rendered console is wanted or JSON-only; the dictionary rebuild/deploy pipeline; complaint resolution workflow.

Stage 10 — Account linking & merge

Scope: link-via-confirm; merge-into-A (stats sum, transfer games/friends, dedupe). High blast-radius — focused regression tests. Open details: conflict resolution (active games on both, duplicate friends, display-name collisions); irreversibility/audit; confirm-flow per platform.

Stage 11 — Polish

Scope: observability dashboards, evidence-based performance work, prod build/deploy. Open details: deployment target/host; dashboards; load expectations.

Refinements logged during implementation

  • Stage 0: solver replace deferred to Stage 2 (nothing imports it yet; adding the path now would break CI, which checks out only this repo). Docker / compose deferred to a stage that has something to deploy. Trunk is master (owner preference); feature/* + PR from Stage 1; the genesis commit lands on master by necessity.
  • Stage 1 (interview + implementation):
    • Query layer: go-jet over database/sql (pgx stdlib) + otelsql; a cmd/jetgen tool regenerates the committed code from a throwaway container. Postgres 17 pinned for jetgen, tests and prod.
    • Sessions: opaque token stored only as a SHA-256 hash (kept as hex text, not bytea — avoids jet bytea-literal friction), revoke-only (no TTL); revocation-audit table deferred. Backend keeps a warmed write-through session cache that gates /readyz.
    • Data model: UUIDv7 PKs; one unified identities table (kind ∈ telegram|email, widen to vk/max later); no soft-delete / actor-audit columns yet.
    • HTTP surface: service/store/cache layer only. /api/v1/{public,user, internal,admin} groups + X-User-ID middleware are scaffolding (exposed via Server group accessors); the session/account REST handlers land with the gateway in Stage 6. Admin bootstrap deferred to Stage 9.
    • Telemetry: providers + request-timing middleware + otelsql; exporters none (default) / stdout; OTLP + dashboards deferred to Stage 11.
    • Tests/CI: integration tests behind the integration build tag in backend/internal/inttest + new integration.yaml (testcontainers, Ryuk off, serial), firing on push and PR. Backend now hard-depends on Postgres at boot (migrations at startup) — a deliberate contract change from Stage 0, documented in both READMEs. All code stays in the existing backend module under internal/ (+ cmd/jetgen); go.work untouched.