You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: skills/stash-cli/SKILL.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -508,7 +508,7 @@ In one transaction it renames `<col>` → `<col>_plaintext` and `<col>_encrypted
508
508
509
509
> **After cutover, `<col>` holds ciphertext — the read path is not automatic.** Wire reads through the encryption client (`decryptModel(row, table)` for Drizzle, the `encryptedSupabaseV3` wrapper for Supabase — `encryptedSupabase` on the legacy v2 surface, otherwise `decrypt` / `bulkDecryptModels`) before returning values to callers. Skip this and your read paths hand raw EQL payloads to end users. The integration skill has the exact API. **CipherStash Proxy is the one exception** — it decrypts on the wire, so Proxy users need no application change. The cutover plan written by `stash plan` includes this read-path switch as an explicit step.
510
510
>
511
-
> **Known gap (v2).** The pending-configuration precondition is satisfied by `stash db push`. SDK-only users (who otherwise never need `db push`) must therefore run it once before `encrypt cutover`. (EQL v3 columns sidestep this entirely — no configuration table, no cutover; see above.) Tracked in [issue #585](https://github.com/cipherstash/stack/issues/585).
511
+
> **Known gap (v2).** The pending-configuration precondition is satisfied by `stash db push`. SDK-only users (who otherwise never need `db push`) must therefore run it once before `encrypt cutover`. This gap is specific to the legacy v2 path and is not being decoupled — EQL v3 columns sidestep it entirely (no configuration table, no cut-over; see above), which is how [issue #585](https://github.com/cipherstash/stack/issues/585) was resolved when v3 became the default.
awaitinstaller.install({ supabase: true }) // executes in a transaction
605
605
```
606
606
607
-
`isInstalled`, `getInstalledVersion`, and `install` all accept `eqlVersion?: 2 | 3` (default `2`), selecting the `eql_v2` or `eql_v3` schema. `install` also takes `excludeOperatorFamily`, `supabase`, and `latest` (v2 only).
607
+
`isInstalled`, `getInstalledVersion`, and `install` all accept `eqlVersion?: 2 | 3` (default `3`, matching the CLI's `--eql-version` default), selecting the `eql_v3` or `eql_v2` schema. `install` also takes `excludeOperatorFamily`, `supabase`, and `latest` (v2 only).
Copy file name to clipboardExpand all lines: skills/stash-drizzle/SKILL.md
+22-10Lines changed: 22 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -365,9 +365,9 @@ if (!decrypted.failure) {
365
365
366
366
The hard case: a Drizzle table that already exists in production with live data in a plaintext column you want to encrypt. You can't just change the column type — that would drop the data and break NOT NULL constraints.
367
367
368
-
CipherStash splits this into two named steps with a hard production-deploy gate between them: an **encryption rollout** (schema-add + dual-write code) and an**encryption cutover** (backfill + rename + drop). (If using CipherStash Proxy, the rollout also includes `stash db push` to register the encryption config with EQL.) The `stash-encryption` skill is the canonical reference for the lifecycle; this section walks the Drizzle-specific shape.
368
+
CipherStash splits this into two named steps with a hard production-deploy gate between them: an **encryption rollout** (schema-add + dual-write code) and a**cutover step** (backfill + switch reads + drop — under EQL v2 the switch is a rename, under v3 it is an application-side change). (If using CipherStash Proxy, the rollout also includes `stash db push` to register the encryption config with EQL.) The `stash-encryption` skill is the canonical reference for the lifecycle; this section walks the Drizzle-specific shape.
369
369
370
-
> **⚠️ v3 backfill tooling status.** The CLI backfill/cutover tooling (`stash encrypt backfill`, `stash encrypt cutover`, and the underlying `@cipherstash/migrate`) currently targets**EQL v2 columns**. v3 compatibility is tracked in [cipherstash/stack#648](https://github.com/cipherstash/stack/issues/648). The lifecycle below (schema-add → dual-write → deploy gate → backfill → cutover → drop) is the correct shape for v3 either way — until #648 lands, run the backfill/rename steps with your own scripts (encrypt with `bulkEncryptModels`, write in chunks) instead of the `stash encrypt` commands.
370
+
> **EQL version note.** The CLI rollout tooling (`stash encrypt *`, and the underlying `@cipherstash/migrate`) works with**both EQL versions** and auto-detects a column's version from its Postgres domain type — there is no flag. The lifecycles differ at the end: **v3** (the default, and what the schema below uses) is `schema-add → dual-write → deploy gate → backfill → switch the app to the encrypted column by name → drop`, with **no cut-over rename**; **v2** finishes with `stash encrypt cutover` (a rename swap plus an `eql_v2_configuration` promotion) before the drop. Running `stash encrypt cutover` on a **backfilled** v3 column reports "not applicable" and exits 0 (it exits 1 if the backfill hasn't finished).
371
371
372
372
> **Where am I?** Run `stash status` first (substitute the runner per the note above). It shows you which Drizzle tables/columns are mid-rollout, which are post-deploy, and what the next move is. Re-run after every transition.
373
373
@@ -474,8 +474,6 @@ Once dual-writes are live in production and `cs_migrations` records `dual_writin
474
474
475
475
#### Backfill: encrypt the historical rows
476
476
477
-
> Until [#648](https://github.com/cipherstash/stack/issues/648) lands, the commands in this step target v2 columns — for v3 columns, replicate the same shape with a script (chunked `bulkEncryptModels` + UPDATE inside transactions, resumable and idempotent).
# (Interactive: answer 'yes' to the dual-write confirmation prompt.)
@@ -486,9 +484,18 @@ Resumable, idempotent, chunked. The CLI walks the table in keyset-pagination ord
486
484
487
485
If something goes wrong (e.g. you discover the dual-write code wasn't actually live when backfill ran), re-run with `--force` to re-encrypt every row regardless of current state.
488
486
489
-
>**SDK-only note:**`stash encrypt cutover` currently requires a pending EQL configuration set by `stash db push`. If you're using the SDK without Proxy, you'll hit a "No pending EQL configuration" error from cutover. **Workaround:** run `stash db push` once before `stash encrypt cutover`.
487
+
>**SDK-only note (EQL v2 only):**`stash encrypt cutover` requires a pending EQL configuration set by `stash db push`. If you're using the SDK without Proxy, you'll hit a "No pending EQL configuration" error from cutover. **Workaround:** run `stash db push` once before `stash encrypt cutover`. EQL v3 columns never hit this — cut-over doesn't apply to them.
488
+
489
+
#### Switch reads to the encrypted column
490
+
491
+
**EQL v3 (the schema above): there is no cut-over.** The encrypted column keeps
492
+
its own name — you switch the application to it by name, verify reads, then drop
493
+
the plaintext column. Point your queries at `email_encrypted`, deploy, and
494
+
confirm reads decrypt correctly; then skip ahead to the drop step. Running
495
+
`stash encrypt cutover` on a **backfilled** v3 column reports "not applicable" and exits 0 (it exits 1 if the backfill hasn't finished).
490
496
491
-
#### Cutover: rename swap and activate
497
+
The rest of this subsection is the **EQL v2** path (a `eql_v2_encrypted` twin),
498
+
kept for existing v2 deployments.
492
499
493
500
First, update the Drizzle schema to the post-cutover shape — switch `email` to the encrypted type and remove the `email_encrypted` column.
494
501
@@ -547,17 +554,22 @@ Once read paths are updated and you're confident reads are decrypting correctly,
547
554
stash encrypt drop --table users --column email
548
555
```
549
556
550
-
The CLI emits a Drizzle migration file with `ALTER TABLE users DROP COLUMN email_plaintext;`. Review and apply with `drizzle-kit migrate`. Update the schema to remove `email_plaintext`:
557
+
The CLI emits a Drizzle migration file with the drop. **Which column it drops depends on the EQL version**, which the CLI auto-detects:
558
+
559
+
- **v3** — drops the original plaintext column, `ALTER TABLE users DROP COLUMN email;`. There was no rename, so no `email_plaintext` exists. Requires the column to be in the `backfilled` phase, plus a live coverage check.
560
+
- **v2** — drops the post-rename leftover, `ALTER TABLE users DROP COLUMN email_plaintext;`. Requires the `cut-over` phase.
561
+
562
+
Review and apply with `drizzle-kit migrate`, then update the schema to its final shape — the encrypted column is the only one left:
Copy file name to clipboardExpand all lines: skills/stash-supabase/SKILL.md
+26-55Lines changed: 26 additions & 55 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -627,58 +627,14 @@ The hard case: a Supabase table that already exists with live data in a plaintex
627
627
628
628
CipherStash splits this into two named steps with a hard production-deploy gate between them: an **encryption rollout** (schema-add + dual-write code) and an **encryption cutover** (backfill + rename + drop). The `stash-encryption` skill is the canonical reference for the lifecycle; this section walks the Supabase-specific shape.
629
629
630
-
> **Known gap (EQL v3 backfill).** The `stashencryptbackfill` / `stashencryptcutover` tooling currently targets EQL v2 (`eql_v2_encrypted`) columns — EQL v3 domain columns are not yet supported end-to-end. Tracked in [cipherstash/stack#648](https://github.com/cipherstash/stack/issues/648). The lifecycle below (rollout → deploy gate → cutover) is the correct shape either way; until #648 lands, run the CLI backfill/cutover commands against a v2 encrypted twin (see "Interim path until #648" just below), or backfill v3 columns with your own script.
630
+
> **EQL version note.** The `stashencrypt*` tooling works with **both EQL versions** and auto-detects a column's version from its Postgres domain type — there is no flag. The lifecycles differ at the end: **v3** (the default, and what this section's schema uses) is `rollout → deploygate → backfill → switchtheapptotheencryptedcolumnbyname → drop`, with **no cut-over rename**; **v2** finishes with `stashencryptcutover` (a rename swap plus an `eql_v2_configuration` promotion) before the drop. Running `stashencryptcutover` on a **backfilled** v3 column reports "not applicable" and exits 0 (it exits 1 if the backfill hasn't finished).
631
631
632
632
> **Using CipherStash Proxy?** If you query encrypted data through [CipherStash Proxy](https://github.com/cipherstash/proxy) instead of the SDK, also run `stashdbpush` after schema-add and again before cutover to register the encrypted column shape with EQL.
633
633
634
634
> **Runner note.** `stashinit` adds `stash` to the project as a dev dependency, so `stash <command>` runs through whichever package manager the project uses (Bun, pnpm, Yarn, or npm) — examples below show this bare form. Before init has run, prefix with your package manager's one-shot runner: `bunx`, `pnpmdlx`, `yarndlx`, or `npx`. The CLI's behaviour is identical across all of them.
635
635
636
636
> **Where am I?** Run `stashstatus` first (substitute the runner per the note above). It shows you which tables/columns are mid-rollout, which are post-deploy, and what the next move is. Re-run after every transition.
637
637
638
-
### Interim path until #648: the v2 encrypted twin
639
-
640
-
**This is the interim EQL v2 path, distinct from the v3 surface the rest of
641
-
this skill documents.** `stashencryptbackfill` / `stashencryptcutover`
642
-
only operate on `eql_v2_encrypted` columns today, so to get the CLI-managed
643
-
backfill/cutover, make the twin a **v2** column and dual-write it through the
644
-
legacy `encryptedSupabase` (v2) wrapper. The lifecycle below (rollout → deploy
645
-
gate → cutover) is unchanged — only the twin's column type and the dual-write
Resumable, idempotent, chunked. The CLI walks the table in keyset-pagination order, encrypts each chunk via the encryption client, and writes the ciphertext into `email_encrypted` inside transactions that also checkpoint to `cs_migrations`. SIGINT-safe. (Remember the known-gap callout above: today this command targets EQL v2 columns — see #648.)
745
+
Resumable, idempotent, chunked. The CLI walks the table in keyset-pagination order, encrypts each chunk via the encryption client, and writes the ciphertext into `email_encrypted` inside transactions that also checkpoint to `cs_migrations`. SIGINT-safe. It auto-detects whether the column is EQL v2 or v3 and records that in`cs_migrations`.
790
746
791
747
If something goes wrong (e.g. you discover the dual-write code wasn't actually live when backfill ran), re-run with `--force` to re-encrypt every row regardless of current state.
792
748
793
-
#### Cutover: rename swap and activate
749
+
#### Switch reads to the encrypted column
750
+
751
+
**EQL v3 (the schema above): there is no cut-over.** The encrypted column keeps
752
+
its own name — point your application at `email_encrypted` through the
753
+
`encryptedSupabaseV3` wrapper, deploy, verify reads decrypt correctly, then skip
754
+
ahead to the drop step. Running `stash encrypt cutover` on a **backfilled** v3
755
+
column reports "not applicable" and exits 0 (it exits 1 if the backfill hasn't
756
+
finished).
757
+
758
+
The rest of this subsection is the **EQL v2** path (an `eql_v2_encrypted` twin
759
+
queried through the legacy `encryptedSupabase` wrapper), kept for existing v2
760
+
deployments.
794
761
795
762
First, if you use declared `schemas`, update them to the post-cutover shape — the encrypted column will live under the original column name:
(Without declared schemas, introspection picks up the renamed column at the next client startup.)
805
772
806
-
> **Known gap (SDK-only users):** `stash encrypt cutover` currently requires a pending EQL configuration, which is set by `stash db push`. If you're using the SDK without Proxy, you'll hit a "No pending EQL configuration" error from cutover. **Workaround:** run `stash db push` once before `stash encrypt cutover`. This will be decoupled in a future release (tracked separately).
773
+
>**Known gap (EQL v2, SDK-only users):**`stash encrypt cutover` requires a pending EQL configuration, which is set by `stash db push`. If you're using the SDK without Proxy, you'll hit a "No pending EQL configuration" error from cutover. **Workaround:** run `stash db push` once before `stash encrypt cutover`. EQL v3 columns never hit this — cut-over doesn't apply to them.
807
774
>
808
775
> **Using CipherStash Proxy?** Re-push the encryption config so EQL has a pending row that points at `email` (no `_encrypted` suffix):
809
776
>
@@ -843,7 +810,12 @@ Once read paths are routing through the wrapper and you're confident reads are d
843
810
stash encrypt drop --table users --column email
844
811
```
845
812
846
-
The CLI emits a Supabase migration file with `ALTER TABLE users DROP COLUMN email_plaintext;`. Review and apply with `supabase migration up` (or `supabase db reset` locally). Then remove the dual-write code from app paths — `email_plaintext` is gone; only `email` (encrypted) is written now, through the wrapper.
813
+
The CLI emits a Supabase migration file with the drop. **Which column it drops depends on the EQL version**, which the CLI auto-detects:
814
+
815
+
- **v3** — drops the original plaintext column, `ALTER TABLE users DROP COLUMN email;`. There was no rename, so no `email_plaintext` exists. Requires the `backfilled` phase plus a live coverage check.
816
+
- **v2** — drops the post-rename leftover, `ALTER TABLE users DROP COLUMN email_plaintext;`. Requires the `cut-over` phase.
817
+
818
+
Review and apply with `supabase migration up` (or `supabase db reset` locally). Then remove the dual-write code from app paths — the plaintext column is gone; only the encrypted column is written now, through the wrapper.
847
819
848
820
### Inspecting progress at any time
849
821
@@ -864,9 +836,8 @@ supabaseClient, encryptionClient })` factory, which takes a hand-written
864
836
client-side schema and a two-argument `from(tableName, schema)`. That surface
865
837
still ships in `@cipherstash/stack-supabase` and is unchanged — keep using it
866
838
for existing v2 deployments — but it is not the recommended path for new
867
-
projects: use `encryptedSupabaseV3`. One current exception: the v2 wrapper is
868
-
also the interim dual-write path for CLI-managed `stash encrypt backfill` /
869
-
`stash encrypt cutover` until [cipherstash/stack#648](https://github.com/cipherstash/stack/issues/648)
870
-
lands — see "Interim path until #648: the v2 encrypted twin" in the migration
871
-
section above for a minimal recipe. For the v2 wrapper's full API and
872
-
semantics, see the docs at https://cipherstash.com/docs.
839
+
projects: use `encryptedSupabaseV3`. The CLI rollout tooling (`stash encrypt
840
+
backfill` / `cutover` / `drop`) supports both generations and auto-detects which
841
+
one a column uses, so a v2 twin is no longer needed to get CLI-managed
842
+
backfill — see the EQL version note in the migration section above. For the v2
843
+
wrapper's full API and semantics, see the docs at https://cipherstash.com/docs.
0 commit comments