Skip to content

feat(control-plane): per-app pg16→pg17 cutover command#14

Merged
valvesss merged 4 commits into
mainfrom
feat/pg17-cutover-command
Jul 2, 2026
Merged

feat(control-plane): per-app pg16→pg17 cutover command#14
valvesss merged 4 commits into
mainfrom
feat/pg17-cutover-command

Conversation

@valvesss

@valvesss valvesss commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

A repeatable, durable per-app v16→v17 cutover command for the Hauldr fleet (ADR 0007 / the pg17 "Fase 5" plan in docs/pg-supabase-image-migration.md). Migrates one project's data from the old hauldr-db (postgres:16) to the new hauldr-db-v17 (supabase/postgres 17, superuser supabase_admin) and repoints that project's services — gated on a stored projects.cluster binding so re-provision / reconcile never revert it.

Cut from feat/pg17-fase3-compat-baseline (reuses its v17 compat baseline: migrations/0003_extensions.sql + the GoTrue auth-helper / search_path work).

Changes

  • control/0012_cluster.sqlprojects.cluster text not null default 'pg16' + check in ('pg16','pg17').
  • config.ts — v17 data-cluster target: v17AdminUrl (HAULDR_DB_V17_ADMIN_URL ?? cronAdminUrl ?? …), v17Host (hauldr-db-v17), v17Port (5432).
  • clusters.ts (new) — clusterOf(name) + adminUrlForCluster / dbHostForCluster / dbPortForCluster / adminClientForCluster / dbClientForCluster / adminDbUrlForCluster. pg16 resolves to the EXACT current values so existing apps are byte-for-byte unchanged.
  • gotrue.ts / postgrest.ts / realtime.ts — cluster-aware, defaulting to pg16 (every new branch gated on cluster === 'pg17'). Realtime tenant db_host/db_port/db_user/db_password and its schema+RLS dbClient calls now follow the project's cluster.
  • cutover.ts (new) — cutover(name, {rollback?, stage?}):
    • stageA: global roles on v17 (as supabase_admin) → authenticator role w/ stored password + ADR-0007 governance (conn limit 20, statement/idle timeouts) → db_<name> from template0 → pg_dump -Fc -N realtime | pg_restore → compat baseline (extensions + auth helpers reused verbatim from gotrue.ts + ALTER ROLE postgres IN DATABASE … search_path) → reindex databasepublic+auth row-count parity check (throws on mismatch).
    • stageB: update projects set cluster='pg17' → re-provision auth/rest/realtime (repoint + redeploy) → notify pgrst on v17.
    • rollback: re-flip to pg16 + re-provision. Old pg16 db never dropped.
  • Dockerfilepostgresql17-client for pg_dump/pg_restore.
  • cli.tscutover <name> [--rollback] [--stage a|b|all] (default all).

Safety

  • Reviewable only — nothing run against live DBs/clusters, no deploy. Typecheck clean (pre-existing jobs.ts pg-boss error unrelated).
  • pg16 path behaviour identical to today.

🤖 Generated with Claude Code

valvesss and others added 4 commits July 2, 2026 06:53
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bake the compat baseline into every NEW project so it is born correct on the
supabase/postgres (v17) substrate — turning v17 from pilot into the official
target for anything new, with zero live-data movement.

- migrations/0003_extensions.sql: pgcrypto + uuid-ossp + pg_trgm on every new DB
  (created from template0, no supabase seed → must install). Idempotent.
- gotrue.ts prepareAuth: create Supabase-dialect auth.uid()/role()/jwt() helpers
  in each project's auth schema (read request.jwt.claims; grant anon+authenticated)
  so RLS ported from Supabase works unchanged.
- gotrue.ts prepareAuth: bake the §5e GoTrue-on-v17 fix into provisioning — pin the
  postgres role-in-database search_path (auth, public, extensions), which OVERRIDES
  the role default the supabase image sets on `postgres` (the crash-loop cause).
  New v17 projects boot GoTrue clean with no manual step. Harmless on the PG16 image.

Cluster-agnostic and idempotent → safe whether the target is the legacy PG16 image
or v17. Validated end-to-end on hauldr-db-v17 in a throwaway template0 DB (cluster
untouched): 0001+0002+0003 apply clean; role-in-database search_path effective for a
fresh postgres session; auth.uid() resolves the sub; authenticated INSERT respects
RLS (owner = auth.uid()). See docs/pg-supabase-image-migration.md §Fase 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… Fase 3 checklist tick

Records the design for making new projects born on v17 (§7): registry-driven
per-project cluster binding + resolver in db.ts, not a global adminUrl repoint
(control DB stays on PG16). Names the touch-list, the supabase_admin superuser
requirement, a reduced first-cut scope (no pooler on v17 yet), rollback, and DoD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a repeatable, durable per-app v16→v17 cutover (ADR 0007 / Fase 5).
Migrates one project's data from the legacy postgres:16 cluster to the
supabase/postgres 17 substrate (hauldr-db-v17, superuser supabase_admin)
and repoints its sidecars, gating the flip on a stored projects.cluster
binding so re-provision/reconcile never revert it.

- control/0012_cluster.sql: projects.cluster (pg16|pg17), default pg16.
- config.ts: v17AdminUrl/v17Host/v17Port (v17 data-cluster target).
- clusters.ts: Cluster resolver — clusterOf + per-cluster admin/db clients
  and host/port. pg16 resolves to the exact current values (no drift).
- gotrue/postgrest/realtime: cluster-aware, defaulting to pg16 (identical
  behaviour for existing apps); resolve cluster via clusterOf(name).
- cutover.ts: stageA (roles + db + dump/restore excluding realtime +
  compat baseline + reindex + row-count parity) / stageB (flip + repoint
  + notify pgrst) / rollback. Idempotent, verbose, throws on mismatch.
- Dockerfile: postgresql17-client for pg_dump/pg_restore.
- cli.ts: `cutover <name> [--rollback] [--stage a|b|all]`.

Typecheck clean (pre-existing jobs.ts pg-boss error unrelated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@valvesss valvesss marked this pull request as ready for review July 2, 2026 23:33
@valvesss valvesss merged commit 35ecada into main Jul 2, 2026
3 checks passed
@valvesss valvesss deleted the feat/pg17-cutover-command branch July 2, 2026 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant