server-2 is the TypeScript backend source of truth for Solid Stats. It owns API contracts, persistence, job orchestration, moderation workflows, and operational visibility. OCAP parsing stays in replay-parser-2, raw replay discovery stays in replays-fetcher, and browser UI work stays in web.
Project development uses only AI agents plus GSD workflow.
Agents keep README and planning docs current when scope, commands, architecture, validation data, or workflow changes. .planning/config.json keeps workflow-critical GSD settings aligned with replay-parser-2; agent_skills are intentionally stack-aware for this TypeScript/Fastify/API service. Requests that conflict with architecture, quality, maintainability, or proportional scope should be challenged with safer alternatives or a GSD plan before risky overrides.
Project-changing work must be captured in GSD planning, phase execution, or quick-task artifacts. Ask the user when change ownership, commit intent, or cross-project compatibility is unclear. Local-only backend work can rely on this repo's planning docs; parser contract mapping, ingest staging/source identity, RabbitMQ/S3 messages, API/data, canonical identity, auth, moderation, or UI-visible behavior requires adjacent app evidence or a user question.
Project planning lives in .planning/: PROJECT.md for product context and decisions, REQUIREMENTS.md for current milestone requirements, ROADMAP.md for phase sequence, STATE.md for current GSD state, and research/SUMMARY.md for architecture rationale.
The current milestone is v2.0 Backend Parity and Full-Run Readiness. Phase 13 is complete, and the milestone is ready for audit and completion.
Phase 3 delivered ingest promotion and parser job lifecycle: staging rows from replays-fetcher become canonical replays and durable parse jobs, RabbitMQ parse requests use the parser worker contract, parser terminal results are recorded idempotently, and read-only operator lifecycle APIs are exposed.
Phase 4 delivered parser artifact normalization and deterministic recalculation for player stats, squad stats, commander-side outcomes, bounty points, and the shared parser-result recalculation orchestration path. Phase 09 updates aggregate death semantics to use parser compact player counters; see docs/parser-counter-semantics.md. Phase 10 added full-run coverage and recalculation commands; see docs/full-run-recalculation.md. Phase 11 added rotation and no-SteamID identity readiness reporting; see docs/rotation-identity-readiness.md. Phase 12 added the legacy public export command; see docs/legacy-public-export.md. Phase 13 added the old-vs-new diff contract and app workflow boundary guard; see docs/diff-harness-contract.md.
- Node 25
- pnpm 11
- Docker Compose for local dependencies
pnpm install
cp .env.example .env
docker compose up -d postgres rabbitmq minio minio-create-bucket
pnpm run devPostgreSQL maps container port 5432 to host port 15432. RabbitMQ maps container port 5672 to host port 5673 and management port 15672 to host port 15673. These host ports avoid common local service conflicts.
pnpm run format
pnpm run lint
pnpm run typecheck
pnpm test
pnpm run test:coverage
pnpm run test:integration
pnpm run db:migrate
pnpm run test:schema
pnpm run openapi:export
pnpm run openapi:check
pnpm run ops:backup:check
pnpm run ops:boundary:check
pnpm run ops:stats:readiness
pnpm run ops:stats:legacy-export
pnpm run ops:stats:coverage
pnpm run ops:stats:recalculate
pnpm run verifyPhase 8 adds a single-VPS production Compose path:
cp .env.production.example .env.production
docker compose --env-file .env.production -f docker-compose.prod.yml build
docker compose --env-file .env.production -f docker-compose.prod.yml up -dSee docs/deployment.md for the deployment runbook and docs/backup-restore.md for PostgreSQL/S3-compatible backup and restore. The production image runs compiled JavaScript with pnpm run build output, and the migrate Compose service applies database migrations before the API starts.
GET /live- process liveness, no dependency checks.GET /ready- PostgreSQL, RabbitMQ, S3-compatible storage, and parser-integration readiness.GET /metrics- Prometheus-compatible process metrics plus parser job outcomes, parser job duration, parser worker failures, and observed queue depth.GET /openapi.json- generated OpenAPI 3.x document.GET /docs- local Swagger UI.GET /operations/ingest-staging- moderator/admin ingest staging lifecycle list with filters and pagination.GET /operations/ingest-staging/:id- moderator/admin staging detail and evidence summary.GET /operations/parse-jobs- moderator/admin parse job lifecycle list with filters and pagination.GET /operations/parse-jobs/:id- moderator/admin parse job detail and error summary.GET /operations/parse-jobs/:id/history- moderator/admin parse job lifecycle history.POST /operations/parse-jobs/:id/retry- admin-only retry for failed or retryable parse jobs.POST /operations/replays/:id/reparse- admin-only manual reparse that creates a new durable parse job.GET /stats/overview- anonymous public stats overview with optionalrotationIdfilter.GET /stats/players- anonymous player stats list with pagination, search, and optionalrotationIdfilter.GET /stats/players/:id- anonymous player stats profile with optionalrotationIdfilter.GET /stats/squads- anonymous squad stats list with pagination, search, and optionalrotationIdfilter.GET /stats/squads/:id- anonymous squad stats profile with optionalrotationIdfilter.GET /stats/rotations- anonymous rotation list.GET /stats/commander-sides- anonymous commander-side stats with optionalrotationIdfilter.GET /stats/bounty- anonymous bounty points list with pagination and optionalrotationIdfilter.GET /stats/leaderboards- anonymous player, squad, and bounty leaderboards with optionalrotationIdfilter.
The generated contract artifact is openapi/server-2.openapi.json.
See docs/api-compatibility.md for OpenAPI drift checks and web type-generation expectations.
Read-only operation APIs expose lifecycle state for authenticated moderators and admins. Mutating operation APIs require an authenticated admin session.
Phase 4 persists parser artifacts and recalculates rotation-scoped aggregate rows for player stats, squad stats, commander-side outcomes, and bounty points. Phase 09 preserves parser compact counter evidence and uses compact death counters for public aggregate death totals; see docs/parser-counter-semantics.md. Phase 10 exposes pnpm run ops:stats:coverage and pnpm run ops:stats:recalculate for full-run coverage, stale-current-result detection, and current parser result backfill; see docs/full-run-recalculation.md. Phase 11 exposes pnpm run ops:stats:readiness for rotation and no-SteamID identity readiness; see docs/rotation-identity-readiness.md. Phase 12 exposes pnpm run ops:stats:legacy-export for deterministic legacy-comparable public export JSON; see docs/legacy-public-export.md. Phase 13 defines old-vs-new-diff.v1 with review_required diff output and a workflow guard for app/infrastructure boundaries; see docs/diff-harness-contract.md. The v1 bounty formula is documented in docs/bounty-formula.md; teamkills and non-enemy kills award zero bounty points, and missing previous-rotation evidence uses zero effectiveness factors.
Phase 5 exposes anonymous public statistics routes for overview, players, squads, rotations, commander-side outcomes, bounty points, and leaderboards. Public API schemas are emitted through the generated OpenAPI contract for web.
Phase 6 uses Steam OpenID for browser sign-in. Configure PUBLIC_BASE_URL so Steam can return users to GET /auth/steam/callback. Session cookies are HttpOnly, SameSite=Lax, and configurable through SESSION_COOKIE_NAME and SESSION_TTL_SECONDS.
Set BOOTSTRAP_ADMIN_STEAM_ID to recognize the initial admin account when that Steam user signs in.
GET /auth/steam/login- redirect to Steam OpenID login, with optional relativeredirectTo.GET /auth/steam/callback- verify Steam OpenID callback, create or update the user, set a session cookie, and redirect.GET /auth/session- return the current authenticated user or{ authenticated: false }.POST /auth/logout- delete the current session and expire the session cookie.GET /admin/users- list users and roles for role management.PUT /admin/users/:id/roles- replace a user'sadmin/moderatorroles.POST /requests- create an authenticated player request.GET /requests- list the authenticated user's requests.GET /requests/:id- read status/detail for one authenticated user's request.POST /requests/:id/attachments- create a request attachment upload ticket for the request owner.GET /requests/:id/attachments- list attachment metadata for the request owner.GET /moderation/requests- list requests for moderator/admin review.GET /moderation/requests/:id- read moderation request detail and decision history.POST /moderation/requests/:id/decision- approve or reject a request with a moderator/admin comment.POST /moderation/requests/:id/audit-patches- create an audit patch for an approved stats correction request.GET /moderation/requests/:id/audit-patches- list audit patches for a moderated request.POST /moderation/requests/:id/workflows- record approved identity, Steam link, or legacy winner fix workflow actions.GET /moderation/requests/:id/workflows- list workflow actions for a moderated request.
Role management routes require an authenticated user with the admin role. Anonymous users receive 401, and authenticated users without admin receive 403.
Phase 7 starts with authenticated request creation and status APIs. Players can submit stats_correction, identity_correction, merge_split, and steam_link requests with a text description and an optional replay/player/squad/stat reference. References are validated through an injected validator before a request is accepted. Request list/detail routes are scoped to the current session user.
Request owners can reserve S3-backed attachment uploads. The API records attachment metadata and returns a presigned PUT upload URL plus required headers. Attachment object keys use the attachments/{requestId}/ prefix.
Moderators and admins can review the request queue, inspect request history, and approve or reject requests with comments. Approved stats correction requests can receive audit patches that record the affected entity, JSON patch payload, reason, and recalculation status through an injected recalculation hook. Approved requests can also record workflow actions for player merge, player split, Steam linking, and manual legacy winner fixes.
Phase 2 uses explicit PostgreSQL SQL migrations under src/infra/db/migrations/.
docker compose up -d postgres
pnpm run db:migrate
pnpm run test:schemaThe migration ledger table is schema_migrations. The initial schema migration creates v1 lifecycle tables for users, roles, canonical player identity history, squads, rotations, replay ingest evidence, parser jobs/results/events, aggregates, requests, attachments, moderation actions, and audit patches.