| stash | minor |
|---|
Add stash eql migration --supabase, so an EQL v3 install survives supabase db reset (#613).
Supabase projects previously had only stash eql install --supabase, which applies the SQL directly to a running database. supabase db reset — the ordinary local development loop — drops that database and replays supabase/migrations/, so the install was wiped and the next query failed with type "eql_v3_encrypted" does not exist. There was no supported way to get EQL into the migrations directory.
stash eql migration --supabase now writes supabase/migrations/<timestamp>_cipherstash_eql.sql, carrying the EQL v3 bundle, the anon / authenticated / service_role grants, and the cipherstash.cs_migrations tracking schema — so one supabase db reset provisions everything stash encrypt needs. The file is timestamped at generation time, so it sorts after everything already applied and pushes without --include-all. A second run exits rather than adding a duplicate install; --force regenerates the existing one in place.
The command now warns when the migrations directory already holds EQL-referencing migrations that sort before the install it is about to write. A project that ran stash eql install directly and then added public.eql_v3_* columns against the live database gets an install stamped today — after those migrations — and supabase db reset, which replays in version order with no dependency awareness, then fails with type "eql_v3_text_search" does not exist. The warning names the specific files and the remedy (rename the install below the earliest of them, then reconcile each remote — see below). It fires on --dry-run too, and nothing is renamed automatically — the ordering of someone else's deployed history is not ours to change silently.
That warning's remote guidance now requires you to verify the remote before writing to its ledger. It splits by whether the remote already has EQL: one where it does needs only the ledger row (supabase migration repair --status applied <version>, which runs no SQL — pushing the file instead re-runs a bundle opening with DROP SCHEMA IF EXISTS eql_v3 CASCADE), and one where it does not needs the SQL genuinely applied (supabase db push --include-all, the back-dated version being a gap in the middle of that history). Previously the first branch was recommended on an assumption the user was never asked to check, and it is the one operation here with no self-correcting failure: mark a version applied on a remote that never ran the SQL and EQL is permanently absent and permanently marked applied, so no future push installs it and the first migration referencing eql_v3 fails with nothing pointing at the cause. The warning now prints the check first — psql "$REMOTE_DATABASE_URL" -Atc "select eql_v3.version()" — and says never to mark applied when it errors. It asks for eql_v3.version() rather than the eql_v3 schema deliberately: that function is created by the bundle's closing statements, so it cannot resolve on an install that aborted partway, while the schema is created by its opening ones and survives one. The same correction lands in the stash-cli and stash-supabase skills and the CLI README, and a guard test now fails the build if a shipped skill recommends the ledger-only repair without that check above it.
--force's follow-up guidance was wrong and is now correct. It said to re-apply with supabase db reset (local) or supabase db push (remote), but a push never re-applies a rewritten migration: the Supabase CLI decides what is pending by comparing versions, never file content, so an in-place rewrite keeping its version is skipped and push reports Remote database is up to date. The remote recipe is now supabase migration repair --status reverted <version> (tracking table only — it applies no SQL) followed by supabase db push, with --include-all called out as a conditional: it is needed only when migrations sort after the install, which leaves the reverted version as a gap in the middle of remote history. Reverting the newest version leaves it at the tail, where a plain push applies it — and the flag applies every out-of-order migration you have, so recommending it unconditionally was itself a hazard. The warning also names the hazard it never mentioned: the EQL bundle opens with DROP SCHEMA IF EXISTS eql_v3 CASCADE, so re-applying drops every index, constraint, and RLS policy that references eql_v3 / eql_v3_internal — free on a fresh db reset, destructive on a populated remote.
--out on a bare --supabase now warns. The Supabase CLI's migrations directory is not configurable — supabase db reset and supabase db push read <project>/supabase/migrations and nothing else, config.toml has no key for it, and --workdir relocates the whole supabase/ directory rather than this subdirectory — so an install written elsewhere is never applied, which is the original bug relocated. The flag still writes the file (a project may apply that directory through its own tooling) but names the consequence, on --dry-run too. --out alongside --drizzle --supabase is unaffected: there it is drizzle-kit's output directory.
--supabase keeps its existing meaning alongside --drizzle (append the role grants to the Drizzle migration); only a bare --supabase selects the new emitter.
stash init --supabase now generates that migration instead of installing directly, when the project has local supabase/ scaffolding — a hosted project without it still installs directly. Re-running init over a project that already has an install migration reports it and moves on, rather than treating the duplicate refusal as a failed setup. Its next steps no longer tell you to run eql install --supabase and then supabase db reset, which was the exact sequence that destroyed the install.
stash init's EQL summary line now distinguishes the migration it wrote from one it merely found. A re-run over an existing install migration says "EQL migration already present" instead of "EQL migration generated" — same apply guidance, same successful exit, but no claim about work the run did not do.
stash init's EQL prompt now names the action for the route it is actually on. On the migration-first routes it asks whether to generate a migration (naming supabase/migrations/ or your Drizzle migrations folder) rather than whether to install into your database, which described the wrong action on both. Declining is fixed the same way: the retry hint is now stash eql migration --supabase / --drizzle on those routes instead of stash eql install, which on Supabase would reinstate the very bug above.
stash init now routes on the integration flags themselves rather than on the provider's display name, so combining them works. stash init --drizzle --supabase is accepted — and is the natural invocation for a Drizzle project on Supabase — but init joined the matched flags into a single provider name (drizzle-supabase) for referrer tracking and then compared that name against 'drizzle' and 'supabase' everywhere it had a decision to make. Every comparison went false. A local Supabase stack answers on 127.0.0.1:54322, so host detection reports plain Postgres and the flags are the only signal left: the run installed EQL directly instead of writing a migration — nothing in supabase/migrations/, no anon / authenticated / service_role grants — which is the #613 failure this release exists to fix, reached through a flag combination the CLI accepts. The same fall-through dropped the supabase status hint when resolving DATABASE_URL (the one lookup that finds a local stack's URL), lost the Prisma Next branch for --prisma --supabase — scaffolding a client Prisma Next never uses and running a duplicate EQL install that races prisma-next migrate's journal — and installed no integration adapter at all, where a combined run needs both @cipherstash/stack-drizzle and @cipherstash/stack-supabase. The provider now carries the matched flags alongside its name and every step reads those; the combined name is still exactly what gets recorded as the referrer, it is simply no longer what the CLI branches on. Drizzle still wins the migration route when both flags fire — it owns the migration history, and --supabase is the grants modifier there. Single-flag runs behave exactly as before.
Also corrects the remote apply command across the Supabase guidance: a bare supabase migration up targets the local database, so the instructions now say supabase db push.
Also corrects the eql install --migration removal message, which pointed every Supabase user at --drizzle.
The Supabase CLI behaviour all of the above depends on is now pinned by a live test rather than by reading the CLI's source. supabase-push.live.test.ts drives the real binary against a real Postgres — db push --db-url needs neither Docker nor a linked project — and covers: the generated install applying with no --include-all; anon reaching eql_v3 via SET ROLE through the grants carried in the emitted file (not just the ones eql install --direct applies); an out-of-order version aborting the whole push rather than being skipped; a --force-replaced file never re-applying; --include-all being needed only for the gap case; and a leaked .tmp file being ignored. Gated on STASH_TEST_SUPABASE_DB_URL + STASH_TEST_SUPABASE_CLI, so the default suite is unchanged.