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>
148 lines
6.8 KiB
Markdown
148 lines
6.8 KiB
Markdown
# 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
|
|
|
|
<task id="A1" title="Initialize Go module and dependency tree">
|
|
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.4
|
|
- `github.com/nats-io/nats.go` @ latest
|
|
- `github.com/coder/websocket` @ v1.8.14
|
|
- `github.com/go-chi/chi/v5` @ v5.2.5
|
|
- `github.com/golang-jwt/jwt/v5` @ latest
|
|
- `golang.org/x/crypto` @ latest
|
|
- `github.com/google/uuid` @ latest
|
|
- `github.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` — builds `cmd/leaf` binary
|
|
- `make run` — builds and runs with default data dir
|
|
- `make test` — runs all tests
|
|
- `make 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.
|
|
</task>
|
|
|
|
<task id="A2" title="Implement core infrastructure: NATS, LibSQL, WebSocket hub, HTTP server">
|
|
Wire up all core infrastructure in `cmd/leaf/main.go` and the `internal/` packages:
|
|
|
|
**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:build` directive
|
|
- `Handler()` function that serves static files with SPA fallback
|
|
- For now, create a `frontend/build/index.html` stub 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 run` starts the binary, prints startup log showing LibSQL opened, NATS ready, HTTP listening on :8080
|
|
- `curl http://localhost:8080/` returns the stub HTML
|
|
- `curl http://localhost:8080/api/v1/health` returns 200 with JSON showing all subsystems operational (LibSQL: `SELECT 1` succeeds, NATS server ready, WebSocket hub accepting connections)
|
|
- A WebSocket client connecting to `ws://localhost:8080/ws` receives a connected message
|
|
- CTRL+C triggers graceful shutdown with log messages
|
|
</task>
|
|
|
|
## Verification Criteria
|
|
|
|
1. `go build ./cmd/leaf/...` succeeds with zero warnings
|
|
2. `go vet ./...` passes
|
|
3. `make run` starts the binary and serves HTTP on :8080
|
|
4. LibSQL database file created in data directory with WAL mode
|
|
5. NATS JetStream streams (AUDIT, STATE) exist after startup
|
|
6. WebSocket connection to `/ws` succeeds and receives initial message
|
|
7. SPA fallback serves index.html for unknown routes
|
|
8. 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)
|