# Online records and deployment

Movement remains local at the same fixed Source interval. Accounts and ranked
records are optional: when online preparation is unavailable, players can explicitly
choose local play. New ranked runs wait at spawn until online saving is ready.

## Architecture

- Vercel CDN serves the Vite build, visuals and immutable gzip collision packs.
  Compression changes transfer size, never geometry coordinates or physics.
- Supabase Auth handles Google/X OAuth with PKCE. Only the project URL and
  publishable key reach the browser. The Node API authorizes private requests
  using Auth `getUser`, never by trusting a decoded client token.
- Vercel Node functions run in Frankfurt beside Supabase. `api/surf.ts` handles
  profiles, leaderboards, attempt tickets and submissions. Private replay uploads
  go directly to Storage, avoiding function request-body limits.
- The durable `surf-verify` queue invokes `api/verify.ts`, which re-simulates
  commands against pinned server geometry and movement settings.
- Postgres transactions enforce quotas, worker leases, atomic PB replacement,
  duplicate prevention and deletion. Surf tables have RLS enabled with no browser
  grants or policies. Only the trusted service role accesses them. Supabase's
  informational [RLS-without-policy notice](https://supabase.com/docs/guides/database/database-linter?lint=0008_rls_enabled_no_policy)
  is intentional here; adding a permissive client policy would weaken this design.

The server derives time and ordered splits from fractional simulation events,
stored as integer microseconds. It requires the canonical normal spawn, no inherited
momentum, exact configuration, bounded commands, every checkpoint and a finish
without resetting. Browser-supplied elapsed times are never accepted.

Practice, restores, demonstrations, hidden tabs or frame stalls during a timed run, and changed rules
are excluded in the UI. Server validation independently checks the complete run.
This is **server-validated movement**, not proof of human input: valid command
sequences can be synthesized. Exact duplicate digests are rejected, but this is
not comprehensive anti-cheat. Keep the public leaderboard labelled beta.

## Version identity

Visible in-game menus keep simulation, timing and command recording running.
Pointer release clears controls but does not invalidate the run or its attempt.
Recapture preserves the fixed-clock remainder and elapsed time. FOV, sensitivity
and binding changes remain ordinary presentation/input operations. Hidden tabs
and frame gaps over 250 ms still interrupt ranked runs. Menu finishes are queued
once without dismissing the open panel; stage recovery uses the same simulation.
No physics source, board identity, verifier, replay format or local PB key changes.
The 40,000-command recording limit includes time spent in the start area or menus;
the HUD/menu reports when a restart is needed instead of silently trimming it.

The October 9 ranked-saving fix changes client preparation and feedback only;
it does not change physics, timer rules, board IDs or server verification. An unused
attempt survives canonical restarts/full falls for the same account, map and rules.
Restart replaces tickets nearing expiry with enough lifetime for a bounded replay.
The first simulation command waits for preparation; a late ticket still cannot
retroactively qualify a run. Stage returns keep the original recording and timer.
Opening a menu before the timer starts, or loading a nonexistent practice position,
does not discard a prepared attempt. HUD and finish messages distinguish ranked,
waiting, local-only, queued and rejected runs. Completed uploads still use the
existing durable queue and survive subsequent restarts.

Regression coverage: `tests/ranked-saving.test.ts` and
`tests/ranked-saving.browser.ts`, including real Kitsune geometry with two stage-9
falls, Mesa restart/fall recovery, delayed preparation, outages, and upload bytes
checked by `verifyReplayBytes`. Browser APIs/rooms are isolated fixtures; these
checks create no production records. See `test-results/ranked-saving/` for local
reports. Local validation is not evidence of deployment.

`scripts/build-online.ts` hashes collision JSON bytes and movement/session/timer
sources, normalizing source line endings for consistent Windows/Linux identities.
Board IDs include map version, both SHA-256 hashes, all movement settings, protocol
and canonical-spawn policy. The browser refuses ranking against a different build.
Rendering/audio changes do not create new boards. Verification-rule changes need
deliberate protocol review even when the movement source remains unchanged.

The small generated `src/online/catalog.generated.json` is tracked. The server
packs in `.online-build/` and public packs in `public/packed/` are generated by
`npm run prepare:online`, automatically run before development, tests and builds.
New movement/map identities need new board rows, not changes to an old board.

## Configuration and deployment

The existing Vercel project is `surfd`. Node 22 is pinned by `package.json`.
`vercel.json` specifies Frankfurt, queue delivery, hourly maintenance and server
map inclusion. GitHub checks run tests, build and the production dependency audit.

Configure these server variables for each deployment environment:

| Variable | Purpose |
| --- | --- |
| `SUPABASE_URL` | Project HTTPS URL |
| `SUPABASE_PUBLISHABLE_KEY` | Public `sb_publishable_…` key |
| `SUPABASE_SECRET_KEY` | Private `sb_secret_…` key, marked sensitive |
| `SURF_AUTH_PROVIDERS` | Actually enabled providers, e.g. `google,x` |
| `CRON_SECRET` | Random private maintenance secret |

Never prefix private credentials with `VITE_`. `.env.local`, `.vercel/` and test
outputs are ignored. The config endpoint returns only explicitly allowed public
values. Missing cloud settings produce an honest local-only state.

Apply the migrations under `supabase/migrations/` in order. Seed the generated
catalog into `surf_boards`: `boardId` becomes `id`, `id` becomes `map_id`, `version`
becomes `map_version`; include both hashes and config, and set `active=true`.
Deactivate retired boards instead of silently changing their rules.

Google/X client credentials belong in Supabase Auth. Provider callback:
`https://wzyozuhomwufeuqakuai.supabase.co/auth/v1/callback`. Supabase's separate Site
URL/redirect allowlist must allow the actual app origin (and specific previews
used to test login). The browser returns to `/`. Google testing mode requires
listed test users; public login needs the appropriate audience setting.

Deploy and validate a preview before promoting. Deployment protection must allow
the intended audience before describing the beta as public. Release previews
must use the isolated staging Supabase project and staging room Worker for release
tests. Verify both URLs in the hosted config before creating a test account. OAuth
provider configuration is distinct from local browser fixture tests.

## Resource bounds and recovery

Restart tickets expire after 45 minutes and grant no Storage access. An existing
attempt can recover its upload for 24 hours after registration; it keeps its original
owner, board, issue clock and verifier checks. This does not extend the time in which
the browser starts a run, change movement rules, or create replacement attempts.
Only finishing requests signed upload permission. A database reservation covers outstanding tokens
and existing objects. Capacity is capped at **512 MiB per account and 10 GiB globally**,
independently of the Supabase billing plan. Each signed upload reserves its full 1 MiB limit
for **125 minutes**, even if its current object is smaller. Retained replay bodies
also count toward the allowance. At most **120 new upload permissions per rolling hour**
are granted to an account. Reauthorizing a still-live permission renews its existing
reservation without consuming another rate-limit slot or moving its original rate window.
Restarts consume no storage allowance. Accounting uses
the larger of the live reservation and actual object size for each path, in one
database snapshot; account deletion cannot release still-live token capacity.

Uploads are capped at 1 MiB gzip, 8 MiB after expansion and 40,000 commands (about
ten minutes including preparation). Per-account request quotas allow 1,200 attempt
tickets and 120 submissions per hour. These are beta bounds, not unlimited hosting.

Upload limits return a bounded `Retry-After` delay. The saved-run queue shares an upload
cooldown across that account's waiting recordings while continuing to check runs already
sent for verification. Manual retry respects the server delay. Completed recordings remain
in browser storage through map changes and reloads, subject to the existing recovery window.

Signed uploads cannot overwrite existing objects. A lost upload response recovers
through idempotent submission; reloading between upload and submission also retries
submission. Database leases and transactional completion tolerate duplicate queue
delivery. Operational errors are retried rather than labelled invalid movement.

Completed replays are kept in IndexedDB independently of map changes. Pending
verification polls start at three seconds, slow to ten seconds after 30 seconds and
30 seconds after two minutes, with positive jitter. Transport errors back off to
60–75 seconds. These schedules are per run, so a new finish does not make older jobs
poll early. A terminal missing/expired attempt stops automatic polling but retains
the replay for download in Account. Removal is explicit and enabled after export;
active queued replays cannot be removed by that control. Six retained copies block
a new ranked start until space is freed. Local play remains available.

## Read efficiency

The public top-50 leaderboard is credential-free and cached at Vercel for 30 seconds.
The player's own best is fetched separately through an authenticated, non-public
endpoint. The client combines concurrent reads and caches successful results for at
most 30 seconds, shortened by the public response's CDN age. The board refreshes
while visible; closed menus and hidden tabs do not poll. A confirmed finish
invalidates the affected board and uses one private fresh read, so the player's new
record does not wait for the public cache. Other players' boards catch up on refresh.
Failures are never cached and account identities never share private cache entries.

Account and room identity reads use `surf_profile_summary`, calculating only the
player's points, title, completions and records. The full Ranks page retains exact
global positions and reuses its map-score calculation. Existing points formulas,
ties, latest-active-board selection and visibility rules are unchanged. Normal
profile refreshes share an in-flight request; each verified result is handled once.

Run `npm run benchmark:online` for a repeatable local PostgreSQL benchmark with
1,200 synthetic players and about 6,000 records. It asserts unchanged standings.
Run `npm run test:online:efficiency` for browser polling, freshness and recovery checks.
These are not production throughput or monthly cost predictions.

Authenticated `/api/maintenance` previews eligible cleanup by default. The database
switch `surf_cleanup_control.enabled` starts false. Once explicitly enabled, the
hourly production cron (17 minutes past each hour, UTC; current Vercel Pro plan)
prunes only abandoned, unsubmitted tickets older than 48 hours with no record,
file, live upload permission, recovery window, verification reference or hold.
Current PB replay bodies on every board, hidden records and moderation holds stay.
Other successful bodies expire after seven days; rejected bodies after seven days;
unclaimed orphan bodies after three hours. Issued uploads keep their full 24-hour
recovery window. Verified times, splits, history, visibility, PB links and points
are not removed by cleanup. `?dry-run=1` always returns a read-only bounded manifest.

Each invocation has a two-minute database lease and at most four batches of 2,000
tickets and 100 replay files, with a work deadline and bounded network requests.
Replay deletion uses durable claims and the Storage API; availability changes only
after confirmed file absence. Interrupted claims resume next time. Bounded queue
recovery follows cleanup while time remains; normal queue and browser recovery
remain independent. Previews require manual authenticated invocation. See
`SURFD-ONLINE-OPERATIONS.md` for activation, holds and rollback.

Maintenance checks `CRON_SECRET` before any work, with bounded batches and a work
deadline below its function limit. Account deletion hides records, removes replay
bodies, revokes sessions and deletes the Auth user. Outstanding token reservations
outlive deletion until expiry to prevent a storage quota bypass.

Watch database size, Storage use, egress, verifier errors and queue age. Upload
allowances do not cap every infrastructure bill or prevent every denial of service.
When capacity is exhausted, ranked uploads fail gracefully and local play continues.

`supabase/diagnostics/online-health.sql` provides read-only operator snapshots for
queue age, expired verification leases, quota headroom and cumulative query work.
Compare two snapshots for rates; lifetime query counts are not current traffic.
Investigate a waiting run older than five minutes, any sustained API/Auth failures,
or charged Storage capacity above 80%. These are suggested operating thresholds,
not automatically configured alerts. Keep Supabase CPU/memory and disk-IO budget
graphs alongside them: upgrading the billing plan does not remove compute limits.

The read-efficiency migration is backward-compatible with the older application.
Release it before the new app; verify with a disposable staging account and then
stage a production candidate for promotion. An app rollback can retain the migration.
Do not roll back the 24-hour recovery window while users have recoverable queued runs.
No map identity, physics hash, recorded time or stored numeric leaderboard is rewritten.

## Validation

`npm test` includes movement regressions, strict replay validation, gzip loading,
API authorization, retry/crash recovery, actual PostgreSQL through PGlite, quotas,
atomic PBs, rank ties, account deletion, maintenance guards and client lifecycle.
`npx tsx tests/online-client.browser.ts` exercises browser flows with explicit
service fixtures; these do not establish live OAuth or queue operation.

`fixtures/online-canonical-replay.json.gz` completes Utopia from its unchanged
production spawn. The first 664 ticks walk across the start deck using ordinary
commands before joining the established route. The production verifier reproduces
**53.349069 seconds** and all three checkpoints. Its 4,531 total commands take
about 67.965 seconds including preparation; cloud testing must allow that wall time
between issuing the ticket and submitting. Automated QA accounts/records must be
removed from public standings after testing.

## Privacy and limitations

Name changes have a database-enforced 60-day (1,440-hour) cooldown. The first
change after rollout is available immediately; each actual change, including a
moderator rename, starts a new wait. Moderator accounts bypass the cooldown in
Account and can override another player's name through Name moderation. Name
locks still apply to the normal Account form; unlocking does not reset the wait.
Saving the same name does not create a history entry or restart the wait.

Account → Name moderation shows recent changes and supports exact, case-insensitive
current/previous-name or account-ID searches, with older-page loading. Reused names
show separate accounts for explicit selection. Only server-allowlisted moderators
can access this endpoint. `surf_name_history` records previous/new names, timestamps,
actors and moderator reasons; it is private to the service and cascades on account
deletion. Existing moderator rename records are imported, but missing historical
player renames cannot be reconstructed. The migration does not infer cooldown dates
from the delivery queue. Client-supplied bypass flags are never trusted.

Supabase Auth stores provider account identity. Public records expose username,
account UUID, time, splits, date and available verified command replays, not email
addresses or OAuth credentials. No analytics or advertising SDK is added.

Original CSS map assets retain their authors' rights; hosting does not grant a new
licence. Existing uncertainties remain in `THIRD_PARTY_NOTICES.md`. Exact universal
CSS/KSF parity, human-only anti-cheat and unlimited free operation are not claimed.

## Verified deployment evidence (3 October 2026)

The full 232-test suite and production dependency audit passed (zero vulnerabilities).
Replay/database checks also passed on Node 22. Native Node ESM loading is tested
without TypeScript hooks: `scripts/build-server.ts` bundles internal server and
simulation imports into explicit `.mjs` entrypoints before Vercel packages them.
This fixes the startup failure found in the first real preview; local TypeScript
tests alone did not detect that packaging difference.

`fixtures/online-cloud-validation.json` records a successful real preview run:
Auth account creation/sign-in, profile update, ordinary attempt/upload APIs, private
immutable Storage upload, queue verification, exact time/splits, atomic repeated
submission, public leaderboard, intact replay download and account deletion.
A subsequent direct database check confirmed zero remaining QA users, records or
replay objects. Both temporary QA deployments and the QA secret were removed.
The helper is preserved only as an opt-in test fixture under `tests/support/`;
no test bootstrap endpoint is present in the normal deployed API.

The deployed browser loaded Boreas and its leaderboard with no console errors.
Google login reached Google's real sign-in page. Completing a human Google/X login
and verifying the final return remains a user check; this is separate from the
successful real Auth-token/backend integration test.

## UI review branch — 3 October 2026

The UI pass retains the Source/VGUI panel style with a shared visual system for
the main menu, Settings, map picker, accounts, leaderboards and finish screen.
The visual pass itself leaves movement and ranked-board identities unchanged (the
subsequent capped-start rule change is documented below). Google uses its official
current sign-in mark; provider actions remain optional. The later guest-claim
flow below supersedes this pass's initial next-run-only sign-in prompt.

`GET /api/surf?action=run&runId=UUID` exposes the public name, map, server time and
splits of a visible verified replay on a current active board. It excludes private
upload paths and account data. `/?map=boreas&run=UUID` opens a run card with replay
playback. Sharing opens an editable X draft; it never posts automatically. Local,
practice and watched-route shares explicitly say what they are and link to the
map, rather than presenting a verified replay link. All displayed times round to
milliseconds consistently, including across minute boundaries.

For local review, run `npm run dev -- --port 4190` and open `/ui-review.html` for
sample finish, sign-in, leaderboard and sharing states. This gallery uses an
isolated fixture client; no accounts or records are created. It is not a production
build entry. The real playable preview remains `/`. Production deployment was
held for review; the user approved this release on 3 October 2026.

Validation for this pass: all 243 automated tests passed, as did 14 isolated
browser integration contexts and 22 UI checks. `npm run test:online:browser`
exercises auth handoff, submission, replay, cancellation and sharing against
fixture APIs; it does not complete a real Google/X login or publish a record.
With the dev server on port 4190, `npm run test:ui` checks the playable menu and
the isolated gallery, including keyboard focus and 390×740 / 800×480 layouts.
Its report and seven screenshots are written to `test-results/`. Override
`UI_BASE_URL` to use a different local server.

The production build's normal Boreas command replay completed in 00:39.495,
with checkpoint splits 00:16.594 and 00:32.681. Browser inspection found no
console errors. The embedded review browser cannot capture the mouse, so use
Chrome or Edge for manual surfing. New online UI flows were checked with fixture
APIs; their deployed integration still needs a preview check before promotion.
The review gallery is excluded from the production build.

### Watching replays while in a server — 8 October 2026

The local client now keeps the room connection open for leaderboard Watch,
shared-run playback, and Watch route. Previously both replay entry points
explicitly called `multiplayer.leave()`. Playback now uses a separate session
while retaining the original room player, position, stage, practice state, and
commands. Escape offers **Return to server**; replay completion offers the same
action. Restart run deliberately restarts the original server map.

Only the retained player's stationary pose is sent to the room. Replay movement,
practice flags, timer, and finish do not become the viewer's multiplayer state
or ranked result. Chat and votes remain connected. Active viewing counts as
activity for the existing full-room idle policy; paused/background viewers remain
idle. Other-map playback hides avatars from the room's different map. Opening
Records keeps the current run moving and ranked. Actually starting Watch explicitly
interrupts the player's ranked attempt while parking their body for the replay.

Room changes cancel playback and stale downloads. A reconnect to the same room
epoch preserves the retained player. Canceled or failed replay loads restore the
room player, and Escape during return loading does not recapture the mouse.
Explicit Leave server and choosing a solo map retain their normal behavior.

`npm run test:replay:rooms` runs the real browser controls and complete Boreas
replay with isolated API/WebSocket fixtures. Set `UI_BASE_URL` to a local dev or
built-preview server, and `UI_REPORT_TAG` to name the JSON report/screenshots under
`test-results/replay-room/`. It checks decoded outgoing poses, chat/voting,
same-map and other-map returns, Summer stage 11, replay switching, download and
asset failures, cancellation, room rotation, reconnect, natural completion,
restart, and solo playback. It does not create live players or publish records.
This fix is local pending release; these checks are not production verification.

## Capped ranked starts — 3 October 2026

The normal client profile, build catalog and trusted verifier now use
`RANKED_CONFIG` (`css-surf-capped-1`): auto bunnyhop enabled, unrestricted start
speed disabled. The existing Boreas-derived end-tick XY start cap is enabled;
see [the movement reference](REFERENCE.md) for its exact semantics and evidence
limits. Old `SURF_CONFIG` open-start witnesses remain replayable and unchanged.
They are rejected by the new ranked verifier. New local record keys and generated
board IDs keep the categories separate; old records must never be relabelled.

Preferences v3 imports v2 controls, sensitivity, view, audio and map preferences,
but resets the old default unrestricted-start flag to false. A fresh explicit
unranked opt-in persists. Settings and leaderboard panels explain the capped rule.
Guest messaging distinguishes eligible prepared runs from local-only finishes;
the guest-claim flow below permits authentication after an eligible finish.

Before deployment, seed the three new catalog board rows as described above and
retire the old boards without deleting their records. The approved release has
seeded the new boards without relabelling older records. Check the new boards
and a real submission on a preview before promoting the UI and rule changes.
Drain old-profile pending verification jobs before cutover; the new verifier
cannot finish those jobs under different rules. Replays and published times keep
their original identities. The unranked open-start option also preserves access
to existing local PBs by using the legacy configuration version.
The canonical Utopia fixture has a newly verified envelope/report for the capped
profile; its normal command sequence is unchanged. The prior cloud validation
file remains historical evidence for the previous deployment, not a cloud test
of these new board IDs. `tsx scripts/validate-classics.ts --ranked` regenerates
`fixtures/ranked-playability-results.json` for all three capped command routes
and exact 30/60/144/240 FPS schedule comparisons.

Local validation passed: 263 automated tests, 14 isolated online browser contexts,
23 UI checks, and the production build. All three capped routes complete, with
exact per-tick results at 30/60/144/240 FPS. No real account or database record was
created during this follow-up.

## Finish first, sign in to save — local review

The normal default-rules guest flow now requests a signed guest ticket before the
first movement command. As of the October 9 client fix, new runs wait for online
preparation, with an explicit local-play option if the player prefers not to wait.
Finishing an eligible run stores its exact command replay and ticket in IndexedDB.
Google/X buttons commit an explicit save intent before leaving for OAuth. On return,
the finish screen and splits are restored, the ticket is claimed by the signed-in
account, and the existing immutable upload and server verification flow runs.
Only a verified server response upgrades sharing to a public replay link.

`POST guest-attempt` allocates no Auth account, database row or Storage object.
Its 24-hour HMAC proof binds a random attempt UUID, board identity and server issue
time. The signing key is domain-separated from `SUPABASE_SECRET_KEY`; no new secret
is needed. Treat the proof as a private bearer capability: it never belongs in a
URL, share link or log. Rotating the server secret invalidates unclaimed proofs.
`POST guest-claim` requires a real Auth user and matching `expectedOwnerId` before
claiming. The atomic database ledger binds that nonce to exactly one account,
including after account deletion, until the proof expires. Same-owner retries
return the same attempt; the upload window is not extended. A new claim grants
the ordinary 45-minute window. Registration time, separately from original issue
time, enforces the shared 1,200-attempt/hour quota.

The browser keeps one completed guest replay, bounded by the existing 8 MiB /
40,000-command limits. A newer finish can replace an unclaimed draft only before
the player has requested saving it. Authorized, owned and claimed drafts are
protected until saved or explicitly dismissed. IndexedDB transactions bind owner,
claim progress and conditional deletion to the attempt UUID so stale tabs cannot
overwrite another run. Unclaimed drafts expire with their 24-hour ticket; claimed
drafts remain recoverable for seven days, including when only verification remains
after the upload window closes. Expired entries are removed on the next access.
Storage failure stops OAuth navigation and offers a retry instead of silently
losing the replay. Cancelling login keeps the run available in Account.

Practice, open starts, changed movement rules, interrupted runs, expired/unprepared
tickets and watched replays do not become ranked through login. The browser's time
and splits are presentation only; server simulation computes the accepted result.
This is not evidence of human-only input. The original server verification and
anti-automation limitations still apply.

Release prerequisites: apply `20261003155331_signed_guest_attempt_claims.sql`, seed
the new capped-start boards, and validate before promotion. The schema and board
setup were applied after the user's release approval on 3 October 2026. The
browser integration suites use explicitly routed fixture services, not real Google
or X identities, and the SQL tests apply all migrations to local PGlite.

Local validation passed: 283 automated tests, 14 existing online browser contexts,
15 new guest-save browser contexts, 11 real IndexedDB scenarios, two actual-game
restored-finish scenarios, 26 UI checks, TypeScript and the production build.
The guest flow exercises the real Supabase PKCE client against intercepted fixture
Auth responses, asserts the uploaded gzip replay matches the retained commands,
and tests account changes, login cancellation, duplicate returns, lost responses,
expired upload windows, unavailable storage, and both protected-slot and retry
recovery. This does not claim a completed real Google/X account login on the new
deployment. `npm run test:guest:browser` runs the guest suites; its actual-game
checks need the dev server at `UI_BASE_URL` (default port 4190). Test reports and
screenshots are written under ignored `test-results/`.

A domain change does not migrate local site data: OAuth must return to the same
origin that stored the replay. Keep the existing origin allowed during a domain
transition and avoid redirecting an in-progress old-origin callback to the new one.

## Custom domain — 3 October 2026

`https://surfd.net/` is attached to the existing Vercel production project;
`www.surfd.net` redirects to it with HTTP 308, preserving paths and queries.
The original `surfd-five.vercel.app` origin remains available. HTTPS, all three
collision packs, the scripts, legal pages and records API were checked successfully.
The production deployment was not changed by attaching the domain. The UI,
capped-start and guest-save release was subsequently approved separately.

The user confirmed saving Supabase Site URL `https://surfd.net` and adding
`https://surfd.net/` to the redirect allowlist while preserving existing entries.
The agent could not independently read this setting: the connected database tools
do not manage Auth configuration and the browser dashboard was signed out.
Google and X still use the existing Supabase callback URL above. Public provider
website/privacy/terms links can use the new domain. Online accounts and times use
the same backend; browser-local PBs, settings and login sessions do not move across
origins automatically.

## Approved release checks — 3 October 2026

The guest-claim migration is applied, with RLS enabled and no anonymous or ordinary
authenticated table/RPC privileges. Only the trusted service can consume claims.
All three new board identities were inserted alongside the original boards; the
old boards are retired at cutover without deleting the existing historical record.
There were no pending/verifying jobs at the pre-release check.

Security advisors report the intentional server-only RLS configuration described
above, plus an existing [leaked-password protection advisory](https://supabase.com/docs/guides/auth/password-security#password-strength-and-leaked-password-protection).
The game's sign-in UI uses Google/X, not password registration. No authentication
settings were weakened for this release.

Creating a temporary real-cloud QA account and preview guard secret was rejected
by automatic approval review as outside the deployment request. Neither was
created, and no temporary helper is included in the release. Validation instead
uses the completed local/browser suites, live public endpoints, schema/permission
checks, and production runtime/browser inspection. The optional `cloud-smoke.ts
--guest` fixture is available for a separately authorized cloud-account test; this
release does not claim that new authenticated end-to-end cloud test passed.
