14 plans in 6 waves covering all 68 requirements for the Tournament Engine phase. Includes research (go-libsql, NATS JetStream, Svelte 5 runes, ICM complexity), plan verification (2 iterations), and user feedback (hand-for-hand UX, SEAT-06 reword, re-entry semantics, integration test, DKK defaults, JWT 7-day expiry, clock tap safety). Wave structure: 1: A (scaffold), B (schema) 2: C (auth/audit), D (clock), E (templates), J (frontend scaffold) 3: F (financial), H (seating), M (layout shell) 4: G (player management) 5: I (tournament lifecycle) 6: K (overview/financials), L (players), N (tables/more) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.8 KiB
Plan A: Project Scaffold + Core Infrastructure
wave: 1 depends_on: [] files_modified:
- go.mod
- go.sum
- cmd/leaf/main.go
- internal/server/server.go
- internal/server/ws/hub.go
- internal/server/middleware/auth.go
- internal/server/middleware/role.go
- internal/nats/embedded.go
- internal/nats/publisher.go
- internal/store/db.go
- frontend/embed.go
- Makefile autonomous: true requirements: [ARCH-01, ARCH-04, ARCH-05, ARCH-06, ARCH-07]
Goal
A Go binary starts in the LXC container, embeds NATS JetStream (sync_interval: always) and LibSQL, serves HTTP via chi, hosts a WebSocket hub that broadcasts to connected clients within 100ms, and serves a SvelteKit SPA stub via go:embed. This is the skeleton that every subsequent plan builds on.
Context
- Greenfield project — no existing code
- Dev environment: x86_64 LXC container (Debian/Ubuntu), Go 1.23+
- go-libsql has no tagged releases — pin to commit hash in go.mod with comment
- NATS JetStream sync_interval: always is mandatory (Jepsen 2025 finding for single-node)
- coder/websocket v1.8.14 for WebSocket hub (not gorilla/websocket)
- See 01-RESEARCH.md Pattern 1 (Embedded NATS), Pattern 2 (WebSocket Hub), Pattern 3 (SvelteKit Embed)
User Decisions (from CONTEXT.md)
- The operator is the Tournament Director (TD) — use this term consistently
- Multi-tournament support means all state must be tournament-scoped from day one (MULTI-01)
- WebSocket hub must support tournament-scoped broadcasting
Tasks
Create the Go module at the project root. Install all core dependencies pinned to specific versions:github.com/tursodatabase/go-libsql— pin to commit hash (check latest stable commit, add comment in go.mod)github.com/nats-io/nats-server/v2@ v2.12.4github.com/nats-io/nats.go@ latestgithub.com/coder/websocket@ v1.8.14github.com/go-chi/chi/v5@ v5.2.5github.com/golang-jwt/jwt/v5@ latestgolang.org/x/crypto@ latestgithub.com/google/uuid@ latestgithub.com/skip2/go-qrcode@ latest
Create the directory structure per 01-RESEARCH.md Recommended Project Structure. Include empty .go files with package declarations where needed to make the module compile.
Create a Makefile with targets:
make build— buildscmd/leafbinarymake run— builds and runs with default data dirmake test— runs all testsmake frontend— builds the SvelteKit frontend (placeholder — just creates build dir with stub index.html)make all— frontend + build
The binary must compile and run (serving HTTP on :8080) with make run.
Verification: go build ./cmd/leaf/... succeeds. go vet ./... passes. Directory structure matches the recommended layout.
1. Embedded NATS Server (internal/nats/embedded.go):
- Start NATS server in-process with
DontListen: true(no TCP listener) - JetStream enabled with
JetStreamSyncInterval: 0(sync_interval: always — MANDATORY per Jepsen finding) - Memory limit: 64MB, Disk limit: 1GB
- Wait for ready with 5-second timeout
- Clean shutdown on context cancellation
- Create initial JetStream streams:
AUDIT(subject:tournament.*.audit),STATE(subject:tournament.*.state.*)
2. NATS Publisher (internal/nats/publisher.go):
- Publish function that writes to JetStream with subject routing
- Helper to publish tournament-scoped events:
Publish(ctx, tournamentID, subject, data)
3. LibSQL Database (internal/store/db.go):
- Open LibSQL with
sql.Open("libsql", "file:"+dbPath) - Enable WAL mode:
PRAGMA journal_mode=WAL - Enable foreign keys:
PRAGMA foreign_keys=ON - Configurable data directory (flag or env var)
- Close on context cancellation
4. WebSocket Hub (internal/server/ws/hub.go):
- Connection registry with mutex protection
- Client struct: conn, tournamentID (subscription scope), send channel (buffered 256)
Register(client)/Unregister(client)Broadcast(tournamentID, messageType, data)— sends to all clients subscribed to that tournament (or all if tournamentID is empty)- On connect: accept WebSocket, register client, send initial state snapshot (stub for now — just a "connected" message)
- Read pump and write pump goroutines per client
- Drop messages for slow consumers (non-blocking channel send)
- Graceful shutdown: close all connections
5. HTTP Server (internal/server/server.go):
- chi router with middleware: logger, recoverer, request ID, CORS (permissive for dev)
- Route groups:
/api/v1/— REST API endpoints (stub 200 responses for now)/ws— WebSocket upgrade endpoint → Hub.HandleConnect/*— SvelteKit SPA fallback handler
- Configurable listen address (default
:8080) - Graceful shutdown with context
6. SvelteKit Embed Stub (frontend/embed.go):
//go:embed all:builddirectiveHandler()function that serves static files with SPA fallback- For now, create a
frontend/build/index.htmlstub file with minimal HTML that says "Felt — Loading..."
7. Main (cmd/leaf/main.go):
- Parse flags:
--data-dir(default./data),--addr(default:8080) - Startup order: LibSQL → NATS → Hub → HTTP Server
- Signal handling (SIGINT, SIGTERM) with graceful shutdown in reverse order
- Log startup and shutdown events
Verification:
make runstarts the binary, prints startup log showing LibSQL opened, NATS ready, HTTP listening on :8080curl http://localhost:8080/returns the stub HTMLcurl http://localhost:8080/api/v1/healthreturns 200 with JSON showing all subsystems operational (LibSQL:SELECT 1succeeds, NATS server ready, WebSocket hub accepting connections)- A WebSocket client connecting to
ws://localhost:8080/wsreceives a connected message - CTRL+C triggers graceful shutdown with log messages
Verification Criteria
go build ./cmd/leaf/...succeeds with zero warningsgo vet ./...passesmake runstarts the binary and serves HTTP on :8080- LibSQL database file created in data directory with WAL mode
- NATS JetStream streams (AUDIT, STATE) exist after startup
- WebSocket connection to
/wssucceeds and receives initial message - SPA fallback serves index.html for unknown routes
- Graceful shutdown on SIGINT/SIGTERM completes without errors
Must-Haves (Goal-Backward)
- Go binary compiles and runs as a single process with all infrastructure embedded
- NATS JetStream uses sync_interval: always (data durability guarantee)
- WebSocket hub supports tournament-scoped broadcasting
- SvelteKit SPA served via go:embed with fallback routing
- All state is tournament-scoped from day one (no global singletons)