diff --git a/README.md b/README.md index 6a63805..1f513d9 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,34 @@ Players apply for a slot, click-accept the NDA, get a code (Steam key or AGS Campaign), play, and fill out a survey. Admins curate signups, manage the code pool, and review structured feedback from inside the AGS Admin Portal. -Built for indie and mid-size studios that already use AGS and need tenant-isolated playtest tooling they can own, audit, and self-host inside their own namespace — without rolling the same signup → NDA → key → feedback plumbing every release. - -![playtesthub end-to-end walkthrough — admin creates a playtest, player signs up, admin approves, code is granted, player submits a survey response, admin reviews the audit log](docs/images/walkthrough.gif) +Built for indie and mid-size studios that already use AGS and need tenant-isolated playtest tooling they can own, audit, and self-host inside their own namespace, without rebuilding the same signup → NDA → key → feedback plumbing every release. + +![playtesthub ADT walkthrough — admin creates a playtest with ADT distribution and auto-approve enabled, player signs up, is approved instantly, and receives the build download link from ADT](docs/images/walkthrough.gif) + +- [What's in the box](#whats-in-the-box) +- [Quick start](#quick-start) +- [Try the full flow](#try-the-full-flow) +- [Feature deep-dives](#feature-deep-dives) +- [Architecture at a glance](#architecture-at-a-glance) +- [Deploy](#deploy) +- [Integrating with your game](#integrating-with-your-game) +- [Documentation](#documentation) +- [Development workflow](#development-workflow) +- [Contributing](#contributing) +- [License](#license) ## What's in the box -- **Two distribution models** per playtest — `STEAM_KEYS` (CSV passthrough, manual Steam redemption) and `AGS_CAMPAIGN` (in-game redemption via the AGS Platform Campaign API). One internal code pool and state machine for both. -- **Discord-federated player identity** via AGS IAM's platform-token grant. Players sign in with Discord; the backend receives a real AGS user. -- **NDA versioning with forced re-acceptance** — edit the NDA mid-playtest and approved players must re-accept before submitting a survey response. -- **Discord DM delivery** of granted codes — FIFO worker queue with circuit breaker, manual retry, and restart-sweep semantics. Approval succeeds even if the DM fails; the code is also visible in the player UI. Requires a Discord server the bot and applicants both join (Discord blocks bot DMs without a mutual guild) — see [`docs/runbooks/setup-ags-discord.md` § 7 "Discord bot + server"](docs/runbooks/setup-ags-discord.md#7-discord-bot--server-required-for-dm-delivery). -- **Versioned typed surveys** (text, 1–5 rating, multi-choice) with per-version response splits. -- **Per-action audit log** for every admin mutation, stable JSONB shapes. -- **TDD-first** — unit, integration (testcontainers Postgres), e2e golden flow, and smoke harness (`pth` CLI). CI enforces every gate on every PR. +- **Three distribution models.** Every playtest picks one: `STEAM_KEYS` (CSV passthrough, manual Steam redemption), `AGS_CAMPAIGN` (in-game redemption via the AGS Platform Campaign API), or `ADT` (ships an AccelByte Development Toolkit build as a download URL, see [ADT distribution](#adt-distribution-m5b)). The two code-based models share one internal code pool and state machine. +- **Discord-federated player identity.** Players sign in with Discord through AGS IAM's platform-token grant, and the backend receives a real AGS user. +- **NDA versioning with forced re-acceptance.** Edit the NDA mid-playtest and approved players must accept the new version before they can submit a survey response. +- **Discord DM delivery of granted codes.** A FIFO worker queue with a circuit breaker, manual retry, and restart-sweep semantics. Approval succeeds even if the DM fails, and the code stays visible in the player UI. Note that Discord blocks bot DMs unless the bot and the applicant share a server, so you need a Discord server both join; see [`docs/runbooks/setup-ags-discord.md` § 7 "Discord bot + server"](docs/runbooks/setup-ags-discord.md#7-discord-bot--server-required-for-dm-delivery). +- **Playtest window enforcement.** A background worker automatically opens the playtest at `startsAt` and closes it at `endsAt`. See [Window enforcement](#window-enforcement-m4). +- **Auto-approve.** The first N signups get approved instantly, skipping manual triage. See [Auto-approve](#auto-approve-m5a). +- **Versioned typed surveys** (text, 1–5 rating, multi-choice), with responses split per survey version. +- **Per-action audit log.** Every admin mutation is recorded, with stable JSONB payload shapes. +- **Admin detail page + bulk announcements.** A list + detail-page-with-tabs admin UI, plus admin-authored broadcast DMs. See [UX revamp](#ux-revamp-m5c). +- **TDD-first.** Unit, integration (testcontainers Postgres), e2e golden flow, and a smoke harness (the `pth` CLI). CI enforces every gate on every PR. ```mermaid flowchart LR @@ -36,24 +51,6 @@ flowchart LR SV <--> AGS ``` -> **Admin authorization.** Every admin RPC is gated on the built-in AGS IAM permission `ADMIN:NAMESPACE:{namespace}:EXTEND:APPUI` (the AppUI-admin perm) at a per-RPC action bit (CREATE / READ / UPDATE / DELETE) — held by namespace-admin roles like **Game Admin** and **Studio Admin** that studios already assign to admin staff. Studios authorize playtest admins by assigning one of those roles in the AGS Admin Portal; **no custom role creation required**, which is what makes this work on Shared Cloud (game admins cannot assign `CUSTOM:*` perms there). The `AuditLog` provides per-action attribution. See [PRD §6 AuthZ](docs/PRD.md#security) and [PRD §9 R8](docs/PRD.md). - -## Status - -**v1.0.0 shipped (MIT).** Track progress in [`docs/STATUS.md`](docs/STATUS.md). Sources of truth, in order: - -| Doc | What it owns | -| --- | --- | -| [`docs/PRD.md`](docs/PRD.md) | Behavior. Authoritative if anything else disagrees. | -| [`docs/schema.md`](docs/schema.md) | DB schemas, audit-log enum + JSONB shapes, fenced-finalize SQL. | -| [`docs/errors.md`](docs/errors.md) | Byte-exact gRPC error codes / messages. | -| [`docs/architecture.md`](docs/architecture.md) | Stack + external dependency detail. | -| [`docs/engineering.md`](docs/engineering.md) | Repo layout, test strategy, TDD workflow, CI gates. | -| [`docs/cli.md`](docs/cli.md) | `pth` CLI spec — surface for humans + AI to drive the app end-to-end. | -| [`docs/dm-queue.md`](docs/dm-queue.md) | DM worker FIFO, circuit breaker, restart sweep. | -| [`docs/ags-failure-modes.md`](docs/ags-failure-modes.md) | AGS retry policy, cleanup matrix, M2 sub-cap rules. | -| [`docs/game-integration.md`](docs/game-integration.md) | Bridging playtesthub's Discord-headless AGS user to the game's Steam-headless AGS user. | - ## Quick start ### Prerequisites @@ -80,9 +77,20 @@ Smoke check: ./scripts/smoke/boot.sh # ephemeral PG + backend boot + reflection probe ``` -### Reproduce the golden flow +## Try the full flow -The `pth` CLI is the canonical end-to-end harness — same surface a human or AI uses to drive the system, and the same path the e2e test exercises. The composite command **`pth flow golden-m3`** runs the full M3 golden flow (admin creates an NDA-required STEAM_KEYS playtest → publishes → player signs up → accepts the NDA → admin uploads keys → admin approves → player retrieves the granted code → admin authors a survey → player submits a response → admin lists responses) and emits one NDJSON line per step. +The `pth` CLI is the canonical end-to-end harness. It is the same surface a human (or an AI agent) uses to drive the system, and the same path the e2e tests exercise. The composite command **`pth flow golden-m3`** runs the whole golden flow and emits one NDJSON line per step: + +1. Admin creates an NDA-required `STEAM_KEYS` playtest +2. Admin publishes it +3. Player signs up +4. Player accepts the NDA +5. Admin uploads Steam keys +6. Admin approves the player +7. Player retrieves the granted code +8. Admin authors a survey +9. Player submits a response +10. Admin lists the responses ```bash go build -o pth ./cmd/pth @@ -110,7 +118,7 @@ echo "$PASSWORD" | ./pth --profile "player-$USER_ID" user login-as \ --player-profile "player-$USER_ID" ``` -Expected output — ten NDJSON lines, all `status=OK`: +Expected output is ten NDJSON lines, all `status=OK`: ```json {"step":"create-playtest","status":"OK","response":{"playtest":{"id":"…","status":"PLAYTEST_STATUS_DRAFT","nda_required":true}}} @@ -132,25 +140,124 @@ Tear down: ./pth user delete --user-id "$USER_ID" --yes ``` -The tests under [`e2e/golden_m1_test.go`](e2e/golden_m1_test.go), [`e2e/golden_m2_test.go`](e2e/golden_m2_test.go), [`e2e/golden_m3_test.go`](e2e/golden_m3_test.go), and [`e2e/golden_m4_test.go`](e2e/golden_m4_test.go) wrap the same sequences behind `go test ./e2e/...` for CI / operator verification — see [`docs/cli.md` §7.4](docs/cli.md). +The tests under [`e2e/golden_m1_test.go`](e2e/golden_m1_test.go), [`e2e/golden_m2_test.go`](e2e/golden_m2_test.go), [`e2e/golden_m3_test.go`](e2e/golden_m3_test.go), and [`e2e/golden_m4_test.go`](e2e/golden_m4_test.go) wrap the same sequences behind `go test ./e2e/...` for CI and operator verification. See [`docs/cli.md` §7.4](docs/cli.md). + +## Feature deep-dives + +### Window enforcement (M4) + +`Playtest.startsAt` and `Playtest.endsAt` are enforced, not display-only: a background worker opens and closes the playtest on schedule. + +
+Details -**Window enforcement (M4)** — `Playtest.startsAt` / `Playtest.endsAt` are no longer display-only. A background `internal/window/` worker auto-transitions `DRAFT → OPEN` at `startsAt` and `OPEN → CLOSED` at `endsAt` (PRD §5.1, [`docs/STATUS_M4.md`](docs/STATUS_M4.md)). `pth flow golden-m4 --slug e2e-m4 --admin-profile admin` exercises the path: create with a window, await the auto-open + auto-close, assert two system-attributed `playtest.status_transition` audit rows. +- A background `internal/window/` worker auto-transitions `DRAFT → OPEN` at `startsAt` and `OPEN → CLOSED` at `endsAt` (PRD §5.1, [`docs/STATUS_M4.md`](docs/STATUS_M4.md)). +- `pth flow golden-m4 --slug e2e-m4 --admin-profile admin` exercises the path. It creates a playtest with a window, waits for the auto-open and auto-close, then asserts that two system-attributed `playtest.status_transition` audit rows exist. -**Auto-approve (M5.A)** — set `autoApprove=true` + `autoApproveLimit=N` (1..100,000) on a playtest and the first `N` signups land `APPROVED` straight from the signup handler, skipping the manual triage queue (PRD §5.4, [`docs/STATUS_M5.md`](docs/STATUS_M5.md)). Distribution-model-agnostic — works for STEAM_KEYS, AGS_CAMPAIGN, and ADT (Track B). The cap bounds **auto-approvals only**; manual `ApproveApplicant` against PENDING applicants stays uncapped. Pool-empty during a burst is a silent PENDING fallback — the signup still succeeds and the operator restocks (or manually approves) at leisure; no applicant-visible error. `pth flow golden-m2 --slug "auto-$(date +%s)" --admin-profile admin --player-profile "player-$USER_ID" --auto-approve --auto-approve-limit 5` runs the variant: same seven NDJSON lines, but `upload-codes` is hoisted before `signup` (auto-approve consumes from the pool inside the signup tx) and the manual `approve` step is replaced by `assert-applicant-auto-approved`, which calls `ListApplicants` and pins `status=APPROVED` + `auto_approved=true` on the just-signed-up row. +
-**ADT distribution (M5.B)** — a third `distributionModel` (`ADT`) that ships an AccelByte Development Toolkit build via a download URL instead of a redemption code (PRD §4.8, [`docs/STATUS_M5.md`](docs/STATUS_M5.md), runbook in [`docs/runbooks/adt-linking.md`](docs/runbooks/adt-linking.md)). One-time per studio: an admin links the studio's ADT namespace via the **Link new ADT Namespace** button on the Playtests list page (state-bearing redirect to ADT — no credential is exchanged; subsequent ADT API calls authenticate via playtesthub's AGS service IAM JWT). Each playtest then picks an ADT namespace + game + build at create time. Approve resolves a download URL via `adt.Client.IssueDownloadURL` (per-build per the 2026-05-20 ADT spec — fixed 24h CDN TTL) and falls back to the playtest's static `adtFallbackDownloadUrl` when ADT is unavailable; the DM body embeds the URL. The player UI's Pending page renders a download card instead of a code panel for ADT playtests, backed by the new `GetADTDownloadInfo` RPC. CLI surface: `pth adt linkage {list,start,complete,unlink}` + `pth adt build list` + `pth flow golden-m5 --slug … --dry-run` for the 11-step request-shape catalogue. +### Auto-approve (M5.A) -**UX revamp (M5.C)** — the admin shell shifts from list+modal to a list + detail-page-with-tabs layout (PRD §5.7 M5.C restructure + [`docs/STATUS_M5.md`](docs/STATUS_M5.md), tour in [`docs/runbooks/admin-shell-tour.md`](docs/runbooks/admin-shell-tour.md)). Clicking a row's **View** button navigates to `/playtest/` whose header carries the breadcrumb + title + date range + status pill + **Publish** / **Stop Playtest** verbs (pure copy renames over M4's existing state machine — no PRD §5.1 change). Below sit four tabs: *Playtest Info* (read-only summary + Edit), *Distribution* (per-model rendering with shared empty-state scaffold), *Participants* (6-column table with Code Sent Date derived from `applicant.last_dm_attempt_at`; the four ADT telemetry cache columns ship dormant for M6), and *Discord Bot Tools* (admin-authored bulk DM broadcast — subject + message are PII-sensitive and never logged; runbook in [`docs/runbooks/announcement-broadcast.md`](docs/runbooks/announcement-broadcast.md)). CLI surface: `pth announcement {create,list}` ships the broadcast tooling outside the admin UI. +Set `autoApprove=true` and `autoApproveLimit=N` (1..100,000) on a playtest, and the first `N` signups are approved straight from the signup handler instead of waiting in the manual triage queue (PRD §5.4, [`docs/STATUS_M5.md`](docs/STATUS_M5.md)). + +
+Details + +- Works with every distribution model: STEAM_KEYS, AGS_CAMPAIGN, and ADT. +- The cap bounds auto-approvals only. Manual `ApproveApplicant` against PENDING applicants stays uncapped. +- If the code pool runs empty during a signup burst, the signup still succeeds and the applicant quietly lands in PENDING. The operator restocks the pool (or approves manually) later; the applicant never sees an error. +- `pth flow golden-m2 --slug "auto-$(date +%s)" --admin-profile admin --player-profile "player-$USER_ID" --auto-approve --auto-approve-limit 5` runs the variant. It prints the same seven NDJSON lines, with two differences: `upload-codes` moves before `signup` (auto-approve consumes from the pool inside the signup transaction), and the manual `approve` step is replaced by `assert-applicant-auto-approved`, which calls `ListApplicants` and checks that the just-signed-up row has `status=APPROVED` and `auto_approved=true`. + +
+ +### ADT distribution (M5.B) + +A third `distributionModel`, `ADT`, ships an AccelByte Development Toolkit build as a download URL instead of a redemption code (PRD §4.8, [`docs/STATUS_M5.md`](docs/STATUS_M5.md), runbook in [`docs/runbooks/adt-linking.md`](docs/runbooks/adt-linking.md)). + +
+Details + +- One-time setup per studio: an admin links the studio's ADT namespace via the **Link new ADT Namespace** button on the Playtests list page. The link is a state-bearing redirect to ADT; no credential is exchanged, and subsequent ADT API calls authenticate with playtesthub's AGS service IAM JWT. +- Each playtest then picks an ADT namespace, game, and build at create time. +- On approve, the backend resolves a download URL via `adt.Client.IssueDownloadURL` (per build, with a fixed 24-hour CDN TTL per the 2026-05-20 ADT spec). If ADT is unavailable, it falls back to the playtest's static `adtFallbackDownloadUrl`. The DM body embeds the URL. +- On the player side, the Pending page renders a download card instead of a code panel for ADT playtests, backed by the `GetADTDownloadInfo` RPC. +- CLI surface: `pth adt linkage {list,start,complete,unlink}`, `pth adt build list`, and `pth flow golden-m5 --slug … --dry-run` for the 11-step request-shape catalogue. + +
+ +### UX revamp (M5.C) + +The admin shell moves from a list+modal layout to a list plus a detail page with tabs (PRD §5.7 M5.C restructure, [`docs/STATUS_M5.md`](docs/STATUS_M5.md), tour in [`docs/runbooks/admin-shell-tour.md`](docs/runbooks/admin-shell-tour.md)). + +
+Details + +- Clicking a row's **View** button navigates to `/playtest/`. The header carries the breadcrumb, title, date range, status pill, and the **Publish** / **Stop Playtest** verbs. Those verbs are pure copy renames over M4's existing state machine; PRD §5.1 is unchanged. +- Four tabs sit below the header: + - *Playtest Info*: read-only summary plus Edit. + - *Distribution*: per-model rendering with a shared empty-state scaffold. + - *Participants*: a 6-column table. Code Sent Date is derived from `applicant.last_dm_attempt_at`; four ADT telemetry cache columns ship dormant until M6. + - *Discord Bot Tools*: admin-authored bulk DM broadcast. Subject and message are PII-sensitive and never logged. Runbook in [`docs/runbooks/announcement-broadcast.md`](docs/runbooks/announcement-broadcast.md). +- CLI surface: `pth announcement {create,list}` ships the broadcast tooling outside the admin UI. + +
## Architecture at a glance -- **Backend** — Go, gRPC + grpc-gateway in-process, Postgres (Extend-managed), `pgx` driver, `golang-migrate` migrations, `accelbyte-go-sdk` for IAM + Platform. -- **Player frontend** — Svelte 5 + Vite + Tailwind v4, static bundle, hash router. Discord login via AGS's platform-token grant ([`docs/engineering.md` §"Discord federation via platform-token grant"](docs/engineering.md)). -- **Admin frontend** — React 19 + TypeScript Extend App UI bundled as a Module Federation remote, hosted by AccelByte and rendered inside the AGS Admin Portal. Currently Internal-Shared-Cloud only ([PRD §9 R11](docs/PRD.md)). -- **CLI (`pth`)** — Go binary, talks gRPC directly on `:6565`. Authoritative end-to-end harness. Spec in [`docs/cli.md`](docs/cli.md). +- **Backend**: Go, gRPC + grpc-gateway in-process, Postgres (Extend-managed), `pgx` driver, `golang-migrate` migrations, `accelbyte-go-sdk` for IAM + Platform. +- **Player frontend**: Svelte 5 + Vite + Tailwind v4, static bundle, hash router. Discord login via AGS's platform-token grant ([`docs/engineering.md` §"Discord federation via platform-token grant"](docs/engineering.md)). +- **Admin frontend**: React 19 + TypeScript Extend App UI bundled as a Module Federation remote, hosted by AccelByte and rendered inside the AGS Admin Portal. Currently Internal-Shared-Cloud only ([PRD §9 R11](docs/PRD.md)). +- **CLI (`pth`)**: Go binary, talks gRPC directly on `:6565`. Authoritative end-to-end harness. Spec in [`docs/cli.md`](docs/cli.md). Repo layout is documented in [`docs/engineering.md` §2](docs/engineering.md#2-repo-layout). +### Admin authorization + +Every admin RPC is gated on the built-in AGS IAM permission `ADMIN:NAMESPACE:{namespace}:EXTEND:APPUI` (the App UI admin permission), checked at a per-RPC action bit (CREATE / READ / UPDATE / DELETE). + +- That permission is held by namespace-admin roles like **Game Admin** and **Studio Admin**, which studios already assign to their admin staff. To authorize a playtest admin, assign one of those roles in the AGS Admin Portal. +- No custom role creation is required. This is what makes the app work on Shared Cloud, where game admins cannot assign `CUSTOM:*` permissions. +- The `AuditLog` provides per-action attribution. + +See [PRD §6 AuthZ](docs/PRD.md#security) and [PRD §9 R8](docs/PRD.md). + +## Deploy + +Three deployable surfaces. Each has its own host and its own runbook. + +1. **Backend (Extend Service Extension)**: Go binary + Postgres on AGS Extend. + 1. Create the Extend Service Extension app in the AGS Admin Portal. Set the env vars and secrets per [PRD §5.9](docs/PRD.md#59-runtime-configuration-go-backend), including `CORS_ALLOWED_ORIGINS` if the player will be hosted off-origin. + 2. Build + push with [`extend-helper-cli`](https://github.com/AccelByte/extend-helper-cli): + ```bash + extend-helper-cli image-upload --login \ + --namespace --app --image-tag v0.0.1 + ``` + 3. Deploy the pushed image from **App Detail → Image Version History → Deploy**, or via `extend-helper-cli deploy-app --wait`. +2. **Player frontend (Svelte → GitHub Pages)**: static bundle, hash-routed, Discord-federated. Auto-deploys on push to `main` via [`.github/workflows/pages.yml`](.github/workflows/pages.yml). Setup is one-time per fork: enable Pages with the workflow build source, set three repo Variables, allowlist the Pages origin in the backend's `CORS_ALLOWED_ORIGINS`, and register the Pages callback URL with Discord + AGS. Walk-through in [`docs/runbooks/deploy-player-pages.md`](docs/runbooks/deploy-player-pages.md). Vercel + custom-domain variants are noted in that runbook's § Out of scope. +3. **Admin UI (Extend App UI)**: React Module Federation remote hosted by AccelByte. `extend-helper-cli appui create` + `appui upload` (Internal Shared Cloud only today, see [`docs/engineering.md` §8](docs/engineering.md#8-temporary-ags-platform-workarounds)). + +For first-time AGS + Discord setup (IAM client, platform credential, redirect URIs), follow [`docs/runbooks/setup-ags-discord.md`](docs/runbooks/setup-ags-discord.md) before any of the above. + +## Integrating with your game + +playtesthub identifies players by their **Discord-federated** AGS user; the game probably identifies them by their **Steam-federated** AGS user. AGS IAM treats those as two separate headless accounts, so the same human ends up with two different AGS userIds unless the integration explicitly bridges them. [`docs/game-integration.md`](docs/game-integration.md) covers the four patterns a game team can pick from. The recommended path is a one-time Discord-OAuth gate on first launch that links Steam onto the playtesthub-side account. + +## Documentation + +**v1.0.0 shipped (MIT).** Track progress in [`docs/STATUS.md`](docs/STATUS.md). Sources of truth, in order: + +| Doc | What it owns | +| --- | --- | +| [`docs/PRD.md`](docs/PRD.md) | Behavior. Authoritative if anything else disagrees. | +| [`docs/schema.md`](docs/schema.md) | DB schemas, audit-log enum + JSONB shapes, fenced-finalize SQL. | +| [`docs/errors.md`](docs/errors.md) | Byte-exact gRPC error codes / messages. | +| [`docs/architecture.md`](docs/architecture.md) | Stack + external dependency detail. | +| [`docs/engineering.md`](docs/engineering.md) | Repo layout, test strategy, TDD workflow, CI gates. | +| [`docs/cli.md`](docs/cli.md) | `pth` CLI spec — surface for humans + AI to drive the app end-to-end. | +| [`docs/dm-queue.md`](docs/dm-queue.md) | DM worker FIFO, circuit breaker, restart sweep. | +| [`docs/ags-failure-modes.md`](docs/ags-failure-modes.md) | AGS retry policy, cleanup matrix, M2 sub-cap rules. | +| [`docs/game-integration.md`](docs/game-integration.md) | Bridging playtesthub's Discord-headless AGS user to the game's Steam-headless AGS user. | + ## Development workflow This repo is **TDD-first**. Every production change follows red → green → refactor: @@ -159,7 +266,7 @@ This repo is **TDD-first**. Every production change follows red → green → re 2. Write the minimum code to pass. 3. Refactor with tests green. -Smoke harness lands with the code that introduces it — see [`CLAUDE.md`](CLAUDE.md) and [`docs/engineering.md` §4](docs/engineering.md#4-redgreen-tdd-loop). +The smoke harness lands with the code that introduces it. See [`CLAUDE.md`](CLAUDE.md) and [`docs/engineering.md` §4](docs/engineering.md#4-redgreen-tdd-loop). ### Verification before committing @@ -176,36 +283,15 @@ buf lint ( cd admin && npm test && npm run build ) ``` -CI runs the same gates on every PR — see [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Browser-based a11y (`@axe-core/playwright` per [`docs/engineering.md` §5](docs/engineering.md#5-ci-gates)) is tracked under STATUS phase 12.1. - -## Deploy - -Three deployable surfaces. Each has its own host and its own runbook. - -1. **Backend (Extend Service Extension)** — Go binary + Postgres on AGS Extend. - 1. Create the Extend Service Extension app in the AGS Admin Portal. Set the env vars and secrets per [PRD §5.9](docs/PRD.md#59-runtime-configuration-go-backend) — including `CORS_ALLOWED_ORIGINS` if the player will be hosted off-origin. - 2. Build + push with [`extend-helper-cli`](https://github.com/AccelByte/extend-helper-cli): - ```bash - extend-helper-cli image-upload --login \ - --namespace --app --image-tag v0.0.1 - ``` - 3. Deploy the pushed image from **App Detail → Image Version History → Deploy**, or via `extend-helper-cli deploy-app --wait`. -2. **Player frontend (Svelte → GitHub Pages)** — static bundle, hash-routed, Discord-federated. Auto-deploys on push to `main` via [`.github/workflows/pages.yml`](.github/workflows/pages.yml). Setup is one-time per fork — enable Pages with the workflow build source, set three repo Variables, allowlist the Pages origin in the backend's `CORS_ALLOWED_ORIGINS`, register the Pages callback URL with Discord + AGS. Walk-through in [`docs/runbooks/deploy-player-pages.md`](docs/runbooks/deploy-player-pages.md). Vercel + custom-domain variants are noted in that runbook's § Out of scope. -3. **Admin UI (Extend App UI)** — React Module Federation remote hosted by AccelByte. `extend-helper-cli appui create` + `appui upload` (Internal Shared Cloud only today — see [`docs/engineering.md` §8](docs/engineering.md#8-temporary-ags-platform-workarounds)). - -For first-time AGS + Discord setup (IAM client, platform credential, redirect URIs), follow [`docs/runbooks/setup-ags-discord.md`](docs/runbooks/setup-ags-discord.md) before any of the above. - -## Integrating with your game - -playtesthub identifies players by their **Discord-federated** AGS user; the game probably identifies them by their **Steam-federated** AGS user. AGS IAM treats those as two separate headless accounts, so the same human ends up with two different AGS userIds unless the integration explicitly bridges them. [`docs/game-integration.md`](docs/game-integration.md) covers the four patterns a game team can pick from — recommended path is a one-time Discord-OAuth gate on first launch that links Steam onto the playtesthub-side account. +CI runs the same gates on every PR; see [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Browser-based a11y (`@axe-core/playwright` per [`docs/engineering.md` §5](docs/engineering.md#5-ci-gates)) is tracked under STATUS phase 12.1. ## Contributing Issues and PRs welcome. Before opening one: -- Read [`CLAUDE.md`](CLAUDE.md) and [`docs/engineering.md`](docs/engineering.md) — they encode the conventions CI enforces. +- Read [`CLAUDE.md`](CLAUDE.md) and [`docs/engineering.md`](docs/engineering.md); they encode the conventions CI enforces. - Add a failing test before the fix. PRs that change behavior without a test will be sent back. -- For PRD-shaping proposals, file an issue first; the PRD is authoritative and changes there gate everything else. +- For PRD-shaping proposals, file an issue first. The PRD is authoritative, and changes there gate everything else. ## License diff --git a/docs/images/walkthrough.gif b/docs/images/walkthrough.gif index 29fcc78..d2af06b 100644 Binary files a/docs/images/walkthrough.gif and b/docs/images/walkthrough.gif differ