Skip to content

v0.94.0 — Dev environment rename: next.purplecat.games

Shipped 2026-09-19 — merged to main as 06f65ff9, tagged v0.94.0. Built on version/0.94.0, six commits. Not one of the releases ROADMAP.md sequences from the heuristic evaluation; slotted ahead of them, which moved every planned release down one number (Roles and the Gatekeeper is v0.95.0, and so on through v0.105.0). The roadmap page is dev-environment-rename.

dev.purplecat.games is now next.purplecat.games, and api-dev is api-next, so that the dev name is free for something else. Nothing about the environment other than its hostnames changed: it is still named dev in every resource — cluster, bucket, task family, secret, image tag, workflow — because the infrastructure module takes environment and app_subdomain separately, and changing the first would have recreated a bucket and a cluster to alter a label nobody types into a browser.

Why it was not a DNS change

The ask was "can I just change the Route 53 entry?", and the answer was no, four times over. The hostname is derived once in Terraform and used in four places a DNS edit does not reach:

  • The CloudFront alias. A distribution answers 403 to any host it does not list, before the origin is consulted.
  • FRONTEND_URL in the task definition, which the server turns into its CORS allowlist (since v0.56.x) and into every campaign and character invite link. A SPA on an unlisted origin has every request and the socket handshake rejected — with a console indistinguishable from a dead server.
  • The assets bucket's CORS rule, which presigned browser uploads depend on, independently of the API's.
  • The ALB's host-header rule for the API.

And two in CI: the deploy workflow finds the distribution to invalidate by alias, so a stale APP_HOST is a deploy that silently serves the previous bundle; and API_BASE_URL is baked into the bundle at build time.

What changed

  • N.1 — Terraform. module.dev: app_subdomain = "next", api_subdomain = "api-next". The plan was the contract: 3 to add, 3 to change, 3 to destroy — alias, assets CORS rule and listener-rule condition in place; the two Route 53 records replaced because their name is their identity; a task-definition revision whose only real change was FRONTEND_URL. Applied 2026-09-19; terraform plan is "No changes" after.
  • N.2 — Deploy Dev, and a guard. APP_HOST / API_BASE_URL follow, and deployHostParity.test.ts (server-core, beside the other CI-file guards) reads both sides — Terraform's subdomains for dev and alpha, each workflow's two variables — and fails when they disagree. It also pins that the invalidation step still keys on the alias, which is why the parity matters.
  • N.3 — The mentions in cors.ts and its test.
  • N.4 — The cutover. Apply, then Deploy Dev — not apply-then-roll: the bundle in the bucket had api-dev baked in and had to be rebuilt, and the workflow derives its task definition from the family's latest revision, which after the apply was Terraform's. Verification is below.

Verification

Run 2026-09-19 after terraform apply -target=module.dev and Deploy Dev run 35474803633 (all four jobs green; service rolled to task-definition revision 9, rolloutState: COMPLETED; invalidation ICTRB3YM0R0UZPJ5DO82HSJ92Y on E3OOWUJUTXE95K found by alias and Completed — no ::warning::).

Breakage Check Result
CloudFront alias curl -sI https://next.purplecat.games HTTP/2 200, served by CloudFront; the bundle it serves names api-next three times and api-dev zero
API CORS OPTIONS /api/campaigns with Origin: https://next.purplecat.games 204, access-control-allow-origin: https://next.purplecat.games, credentials true; a GET answers 401 with the header (auth, not CORS); the Socket.IO polling handshake 200 with the header; Origin: https://evil.example gets no header
Assets bucket CORS S3 preflight for PUT from the new origin — what the browser sends before a presigned upload 200, Access-Control-Allow-Origin: https://next.purplecat.games, methods POST, GET, PUT; the same from the old origin is 403
ALB host rule https://api-next.purplecat.games/health 200
Sign-in Load https://next.purplecat.games/ in a browser The SPA boots, Clerk initialises and hands off to its hosted sign-in (right-ibex-13.accounts.dev, a development instance — so there is no per-host allowed-origins list to update; the page's worry about the production instance was wrong). Verified up to the credential form; credentials are not something the release process types
Old hosts dig against the zone's own nameserver api-dev.purplecat.games → NXDOMAIN. dev.purplecat.games already answers again — a plain A record to 75.58.11.253, created outside Terraform between the apply and this check, which is the reuse the rename was for
Terraform terraform plan -target=module.dev after the apply No changes

Found on the way

  • Deploy Dev could not start. Its bundle-modules job checks the platform out under platform/ so the module repos can sit beside it, and pnpm/action-setup@v4 reads package.json from the workspace root by default — so the first run after the push failed at "Setup pnpm" with No pnpm version is specified, before anything was built. The same workflow had passed on 2026-09-09; the v4 tag floats and that run's logs have expired, so what changed underneath is not recorded. The step now names package_json_file: platform/package.json, and ciActionVersions.test.ts fails any job whose checkout has a path: and whose pnpm setup does not point at it.
  • scripts/ is still not a workspace on main. The page first put the guard there; the v0.84.0 branch that made that directory a workspace was never merged, so a test there does not run. The guard lives in server-core.