feat(cli): use stack EphemeralPostgres for schema-tooling shadows - #6540
feat(cli): use stack EphemeralPostgres for schema-tooling shadows#6540avallete wants to merge 19 commits into
Conversation
Supabase CLI previewnpx --yes https://pkg.pr.new/supabase/cli/supabase@bcc62728d19c93a1cd748e7dd832c2a3c9c05f25Preview package for commit |
bcc6272 to
d34d089
Compare
Coly010
left a comment
There was a problem hiding this comment.
Reviewed at ae7a323 (review agents across architecture, engineering, security and DX, plus manual dogfooding on the cli-staging bench: macOS, Docker Desktop 29.7, container runtime). Requesting changes. The ephemeral-shadow core is solid and I could not make it leak, but the PR's own db start path does not work on a real stack, three command families silently stay on the legacy backend with the flag on, and the snapshot cache dropped the integrity checks the legacy cache had.
What I dogfooded
| Scenario | Result |
|---|---|
db diff --linked, db pull, migration list --linked |
✅ ephemeral shadow, ~5s warm |
migration squash with two migrations |
✅ cold shadow + Docker pg_dump one-shot, 8s |
| Cold → warm shadow cache | ✅ stack-shadow-baseline-*.tar 0600 in a 0700 dir, mtime touched on warm hit, 7.6s → 2.5s |
| Ctrl-C 3.5s into a cold shadow (ephemeral Postgres + storage schema-init up) | ✅ no containers, volumes, networks or .partial left |
| Ephemeral cluster publish binding | ✅ 127.0.0.1:<port> only |
--use-migra / --use-pgadmin / db start --from-backup / invalid env or config values |
✅ rejected as documented |
db start |
❌ failed to start local database: Persisted API gateway material is incomplete; leaves a registered stack with desired lifecycle stopped; --debug adds nothing |
gen types --local with the stack running |
❌ supabase start is not running. from the legacy Compose check — routes to legacy under the flag |
db reset --local |
❌ wiped and re-bootstrapped the database, then failed on the overlay connect; status kept reporting database ready |
every other --local command |
⛔ blocked on my machine by a pre-existing stack gateway bug (below), worked around with --db-url on the private port |
Blockers
db startcannot work on a real stack. The postgres-only overlay excludesauth, andpackages/stack/src/supervisor/Ingress.ts:276gates API-key material ondefinition.listeners.api.enabledrather than on auth/rest being enabled, so the four auth key slots are missing and the supervisor fails.stack start -x authfails identically on develop, so the bug is pre-existing, but this PR is its first mainstream caller and thedb startintegration tests mockStackApi, so nothing exercises it. Fix the gate (or make the API listener follow the auth/rest exclusion) here or in a prerequisite PR, and add one real container-runtimedb starttest.- Routing allow-list misses shared consumers (
pull,gen,inspect). Inline. - Catalog overlay re-runs
roles.sqlon already-running stacks. Inline. - Snapshot tar restored without a key-bound marker, extracted with default
tarflags. Inline.
Should fix
- failed
resetDatabaseleaves the stack claimingrunningwith a dead database and a stale activation cache (inline) - no reaper for orphaned
supabase-eph-*containers/volumes/PGDATA after a hard kill (inline) - loopback port allocation TOCTOU, matters once declarative sync provisions two shadows (inline)
- ephemeral Postgres env file created world-readable then chmod'd with the failure ignored (inline)
- cache directory mode only applied at creation (inline)
--runtime dockeradvice is unreachable for an existing native stack (inline)db dump --linked/--db-urldropSUPABASE_NETWORK_IDwith the flag on (inline)- stack
db resetskips bucket seeding whenever storage is still bouncing (inline) db pull's seeded-from-dump branch still needs a Dockerpg_dumpcontainer (inline)- engine-flag rejection applies to
--linked/--db-urltoo, contradicting "linked targets are unchanged" (inline)
Docs and contract
SIDE_EFFECTS.mdis stale for five commands whose behaviour changed under the flag:db reset,db start,db pull(and top-levelpull),migration squash,db schema declarative generate/sync. Nothing documents the new~/.supabase/ephemeral-postgres/<identity>/state location either.- ADR 0025 says "Top-level
statusis not switched";root.tsaliases it. stack-commands.mddescribesdb startas "starts the database without persisting--exclude", butdb starthas no--excludeflag and in practice resumes the full persisted stack when one exists. Say that plainly.packages/config/src/experimental.tsdescription omitstest dband should say--localtargets only (inline).(or Git Bash)in the Windows dump/squash suggestion is wrong: Git Bash ships nopg_dump.test dbon the same path has no suggestion at all.WARNING: skipped analytics schema init: analytics schema init requires the activated analytics processreached me duringdb reset. "Schema init" is ADR vocabulary; users need "auth/storage/analytics database setup was skipped because the service is not running".StackCatalogSetupErrorhas no[ErrorActionabilityId](inline).
Pre-existing stack bugs surfaced by this dogfood (not this PR's fault, but they block it)
- Database TCP gateway never proxies on macOS. With develop's supervisor as well as this PR's, Postgres answers directly on its published private port in ~17ms, but the stack's own
127.0.0.1:54322listener times out for hostpgclients and returns "server closed the connection unexpectedly" for thepg_dump/pg_provecontainers viahost.docker.internal. The HTTP gateway (REST, auth health) works. Since every--localcommand in this PR goes throughcredentials().database.url, none of them work on a Mac today. macOS CI only runs the port tests, which would explain why this is green. stack start -x authfails as described in blocker 1.
Done well
ADR decisions (a)–(e) each map to code; the auth/storage/realtime policy lives in the CLI and SchemaInit stays generic; runtime.kind + engine + artifact identity in the cache key with the separate stack-shadow-baseline- namespace means legacy and stack tars cannot collide; addFinalizer(destroyCluster) registered before acquisition and uninterruptibleMask scoped to the handoff, which the Ctrl-C test bore out; the ephemeral-postgres and reset-database integration tests run real Postgres rather than mocks; db diff's SIDE_EFFECTS.md update is exemplary.
jgoux
left a comment
There was a problem hiding this comment.
Requesting changes for the six runtime defects detailed inline. The ADR routing correction is nonblocking.
The findings were independently cross-checked and supported by code traces and targeted reproductions. Build, type checks, changed-file lint/format checks, and 951 targeted tests passed (one platform-specific skip), but those checks do not cover the failures identified here. Full E2E and the complete platform matrix were not run.
ae7a323 to
5953ccf
Compare
When [experimental].stack is on, db diff/pull, declarative generate/sync, and migration squash provision throwaway shadows through @supabase/stack instead of legacy Docker SQL-template containers.
The stack backend resolved --local through stack credentials, but only pg-delta and squash runtimes provided the API, so migration up failed as not running. Move shared stack helpers out of the experimental command family, fail closed on local reset, skip Docker image inspect, and use stack credentials for declarative --local.
When [experimental].stack is on, db start/reset/dump/test and squash use stack credentials and resetDatabase instead of Compose. Native engines use PATH pg_dump/pg_prove; docker engines keep the tool container.
…Postgres Restored snapshots skip bootstrap, so the cache key now uses the same internal database constants. Interrupt owns the cluster on the start fiber so Postgres cannot stay bound, and remaining oxfmt drift is formatted.
…urces Reuse the shadow-cache JSON helper, snapshot new error tags, and mask container create-to-ID so Ctrl-C cannot orphan Docker/Podman objects.
db/migration and db-bootstrap cannot import experimental command internals, and knip flagged an unused local-running probe.
Omitted runtime now probes Docker. Declarative local ensure uses the postgres-only start helper so it cannot undo db start's overlay.
A failed first db start leaves an unconfigured identity; the retry must not compile the full default stack.
Include the new command-internal stack modules in Effect lint, make ephemeral Postgres start/stop Effect properties, and report PATH pg_dump/pg_prove failures instead of a container exit.
Give local and shadow databases the catalog Compose would after auth/storage/realtime migrate, without activating lazy services.
Schema-init compiles only database plus requested one-shots, Studio no longer requires analytics, and one-shots join the cluster network so Linux docker catalog SQL can match Compose.
…hema-init seeds Native Realtime prepare must migrate only. Shadow cache keys now use the same default runtime as create, and remotes *EnabledForSetup flags overlay onto ephemeral catalog config.
Compose-faithful start/reset, host-network rewrite, native-only rpc, and snapshot hardening so leftover review and dogfood stay green.
Restart tests must provide leftover EffectStack.resetDatabase and the start-handler services so they typecheck on current develop.
5953ccf to
5fee157
Compare
Legacy typegen still probes the Compose db hostname and config port. Also keep effect lint clean on the Promise ephemeral facade.
…ct .env experimental
Summary
When
[experimental].stack/SUPABASE_EXPERIMENTAL_STACK=1is on:supabase stackis gated on the flag, including help and completion. Top-levelstart/stop/statusbecome stack aliases. Invalid project config leaves the feature off; invalid env overrides are still errors. Flag off keeps legacy Compose start/stop/status and Docker SQL-template shadows.--localuses the project stack.db,migration,test db,gen types, andinspectresolve--localthrough stack credentials instead of Compose names (supabase_db_*,db:5432).gen types --localandinspect db … --localdo not start a stack. Linked and explicit--db-urlstay URL / Management API targets (a URL that happens to matchconfig.tomlports is still a URL). Functions, storage, and top-levelpullstay on their existing backends.EphemeralPostgres.db diff,db pull, declarative generate/sync, andmigration squashprovision throwaway shadows through@supabase/stack. The cluster is a scoped second instance (native host PGDATA or a named volume). Snapshots are runtime-kind specific (stack-shadow-baseline-*.tar, same keep-3 / 2-day TTL as the legacy namespace). The runtime marker can carrysnapshotKey; keyless tars still restore. Archive flags are portable (GNU tar and Darwin bsdtar). Ephemeral env files are0600; the cache dir is0700.--use-migra,--use-pgadmin,--use-pg-schema, and--diff-engine migraare rejected for every stack runtime because the shadow is alwaysEphemeralPostgres, including--linked/--db-url.unconfigured): schema init, overlay (webhooks, grants, vault,roles.sql), then migrate-and-seed. Existing cluster: webhooks only — no second overlay or migrate-and-seed. A failed first setup leaves the engine up and does not retry on the next start; recover withdb reset. Disabled capabilities keep nested pins (version, settings).db startis postgres-only on first create. Ingress follows listener intents, so a database-only plan does not require API-key material. An existing stack resumes its persisted services.--from-backupis unsupported.db reset --localand declarative--applywipe throughresetDatabase(a failed wipe stays start-eligible), then migrate or seed on stack credentials. Bucket seed talks to the stack API endpoint and waits only while storage is enabled and bouncing.db dump --local,db test/test db, andmigration squashuse PATHpg_dump/pg_proveon native engines (must match the stack Postgres major). Docker/Podman keeps the one-shot tool container against published credentials, neverPGHOST=db. On Linux, omit / empty /host--network-idkeeps loopback instead of rewriting tohost.docker.internal.rpc/GEN_RPC_*binds a unique host port on native runtimes only. Docker publications omitrpc.--excludedoes not skip that catalog set. Docker catalog SQL matches Compose; native schema-init is migrate-only (Realtimebin/prepareomitsSEED_SELF_HOST).*EnabledForSetupoverlay onto ephemeral catalog config sodb pulldoes not schema-init services remotes turned off. Interrupted Postgres is torn down with the start fiber; in-flight Docker/Podman create IDs are registered so Ctrl-C cannot orphan them. Native spawn drains pipes.Docs: ADR 0025, stack-commands, and the
SIDE_EFFECTS.mdpages for the switched commands (including~/.supabase/ephemeral-postgres/<identity>/).