|
1 | 1 | # @cipherstash/cli |
2 | 2 |
|
| 3 | +## 1.0.0-rc.3 |
| 4 | + |
| 5 | +### Minor Changes |
| 6 | + |
| 7 | +- 3a86939: EQL v3 support for the encryption rollout lifecycle (#648). The `stash |
| 8 | +encrypt *` commands (and `@cipherstash/migrate` underneath) now resolve a |
| 9 | + column's EQL version and its encrypted counterpart from the **Postgres domain |
| 10 | + types** — the EQL v3 types are self-describing, so the `<col>_encrypted` |
| 11 | + naming is a convention only, never enforced or relied upon — and follow the |
| 12 | + right lifecycle, no new flags: |
| 13 | + |
| 14 | + - **`encrypt backfill`** works on v3 columns unchanged (the engine was always |
| 15 | + version-agnostic; pass an `EncryptionV3` client and real v3 envelopes land |
| 16 | + in the concrete `eql_v3_*` domain column — verified live against a real |
| 17 | + database, including the domain CHECK and a decrypt round-trip). The |
| 18 | + manifest records the detected version, the encrypted column's name, and the |
| 19 | + v3 target phase, and the command prints v3-appropriate next steps. |
| 20 | + - **`encrypt cutover`** on a backfilled v3 column reports "not applicable" |
| 21 | + (exit 0) with guidance: v3 has no rename cut-over — the application |
| 22 | + switches to the encrypted column by name. Before backfill completes it |
| 23 | + exits 1 and says to finish the backfill instead of instructing the switch. |
| 24 | + On a database with no `eql_v2_configuration` table (a v3-only install) the |
| 25 | + v2 path now explains that instead of surfacing a raw Postgres error. |
| 26 | + - **`encrypt drop`** is version-aware: v3 runs from the `backfilled` phase, |
| 27 | + **verifies live coverage** (refuses to generate the migration while any row |
| 28 | + still has the plaintext set and the encrypted column NULL — the |
| 29 | + `countUnencrypted` check), and drops the ORIGINAL plaintext column (there |
| 30 | + is no `<col>_plaintext` under v3); v2 behaviour is unchanged. The generated |
| 31 | + v3 migration **re-verifies coverage at apply time** — it locks the table, |
| 32 | + re-counts, and aborts without dropping if plaintext-only rows appeared |
| 33 | + after generation. And because dropping is the one irreversible step, it |
| 34 | + requires a positively asserted plaintext↔ciphertext pairing (the |
| 35 | + manifest's recorded `encryptedColumn` or the naming convention): a match |
| 36 | + found only by being the table's sole EQL column is refused with |
| 37 | + instructions, and an ambiguous table (several EQL columns, none |
| 38 | + identifiable) fails closed listing the candidates — as does `cutover`. |
| 39 | + - **`encrypt status`** classifies each column from the observed domain type |
| 40 | + (manifest as fallback), shows `v3` in the EQL column, and no longer raises |
| 41 | + the v2-only `not-registered` / `plaintext-col-missing` drift flags for v3 |
| 42 | + columns. `stash status`'s quest ladder and the `stash init` agent handoff |
| 43 | + prompt teach the version-appropriate next step (no more "run cutover" on |
| 44 | + v3 columns). |
| 45 | + - New `@cipherstash/migrate` exports: `classifyEqlDomain`, |
| 46 | + `resolveEncryptedColumn`, `pickEncryptedColumn`, `listEncryptedColumns` |
| 47 | + (domain-type resolution — case-exact for quoted/mixed-case table names), |
| 48 | + `countEncrypted` / `countUnencrypted` (coverage counts), and manifest |
| 49 | + `eqlVersion` + `encryptedColumn` fields. `EqlVersion` is numeric (`2 | 3`), |
| 50 | + matching the manifest and the installer. Resolved columns carry `via: |
| 51 | +'hint' | 'convention' | 'sole'` so callers can tell a positively asserted |
| 52 | + pairing from a by-elimination guess. |
| 53 | + - Fixed: `encrypt cutover`/`encrypt drop` precondition failures now actually |
| 54 | + exit 1 — the early-return guards previously skipped the exit-code path |
| 55 | + entirely, so failed preconditions exited 0. (This also applies to v2 |
| 56 | + preconditions: scripted pipelines that relied on the erroneous exit 0 will |
| 57 | + now see the documented exit 1.) |
| 58 | + |
| 59 | + The `stash-cli` and `stash-encryption` skills and the `@cipherstash/migrate` |
| 60 | + README document the two lifecycles (v2: backfill → cutover → drop; |
| 61 | + v3: backfill → switch-by-name → drop). |
| 62 | + |
| 63 | +- b0634df: `stash plan --complete-rollout` is now automatable and has an honest exit code. |
| 64 | + It skips the production-deploy gate, so it needs explicit consent — previously |
| 65 | + that was an interactive prompt with no bypass, so a non-interactive run |
| 66 | + auto-cancelled (default-no) and exited **0** without drafting a plan, leaving |
| 67 | + automation to assume a plan existed. |
| 68 | + |
| 69 | + - New `--yes` flag confirms the gate-skip without a prompt (for CI/agents). |
| 70 | + - Without `--yes`, a non-interactive `--complete-rollout` run now **refuses |
| 71 | + with a non-zero exit** and points at `--yes`, instead of silently succeeding. |
| 72 | + - Interactive behaviour is unchanged (default-no confirm). |
| 73 | + |
| 74 | +- f188c7a: `stash env` now works: it mints deployment credentials from your device-code |
| 75 | + session and prints them as env vars — no dashboard copy-paste. The command |
| 76 | + creates a fresh ZeroKMS client and a member-role CipherStash access key (named |
| 77 | + via `--name`; the role is pinned in the request and verified on the response — |
| 78 | + the CLI deliberately cannot mint admin keys), then emits `CS_WORKSPACE_CRN`, |
| 79 | + `CS_CLIENT_ID`, `CS_CLIENT_KEY`, and `CS_CLIENT_ACCESS_KEY`. |
| 80 | + |
| 81 | + Output goes to stdout by default — and stdout is pipe-clean (progress UI is on |
| 82 | + stderr), so `stash env --name x > prod.env` and pipes into secret stores are |
| 83 | + safe. `--write [path]` writes a file instead (default `.env.production.local`, |
| 84 | + enforced mode 0600 even when overwriting), confirming before overwriting and |
| 85 | + refusing non-interactively — always _before_ anything is minted, so a refusal |
| 86 | + never discards the shown-exactly-once access key. `--json` emits NDJSON; with |
| 87 | + `--write` the confirmation event is deliberately secret-free. API responses |
| 88 | + are schema-validated so a service change can never print `undefined` into a |
| 89 | + credentials file. Creating access keys requires the admin role in the |
| 90 | + workspace. |
| 91 | + |
| 92 | + This is also the supported credential path for WASM/edge local development |
| 93 | + (Supabase Edge Functions, Cloudflare Workers, Deno), where the runtime cannot |
| 94 | + read the `~/.cipherstash` device profile: mint a key and feed it via |
| 95 | + `supabase functions serve --env-file` or the platform's secret store. |
| 96 | + |
| 97 | + The `STASH_EXPERIMENTAL_ENV_CMD` gate is removed. |
| 98 | + |
| 99 | +- 8872d1e: `stash init`, `stash plan`, and `stash impl` no longer crash on a Prisma Next |
| 100 | + project. `SKILL_MAP` was missing a `prisma-next` entry, so the skills-install |
| 101 | + and AGENTS.md-builder steps hit `SKILL_MAP[integration]` → `undefined` and threw |
| 102 | + "not iterable" for any repo the CLI detected as Prisma Next. The entry is added |
| 103 | + and both consumers now resolve skills through a `skillsFor()` helper that |
| 104 | + degrades an unmapped integration to the base skill set instead of crashing |
| 105 | + (`tsup` ships without type-checking, so the `Record<Integration>` type alone |
| 106 | + didn't protect the build). |
| 107 | + |
| 108 | + Ships a new **`stash-prisma-next`** agent skill documenting the EQL v3 Prisma |
| 109 | + Next surface — the domain-named encrypted column types (`EncryptedTextSearch`, |
| 110 | + `EncryptedDoubleOrd`, …), `cipherstashFromStackV3` wiring, the runtime value |
| 111 | + envelopes, the `eql*` query operators, and EQL installation via |
| 112 | + `prisma-next migration apply`. It is installed for Prisma Next projects and |
| 113 | + inlined into `AGENTS.md` for editor agents. |
| 114 | + |
| 115 | + `stash eql install` now refuses to run in a Prisma Next project (pointing you |
| 116 | + at `prisma-next migration apply`, which owns EQL installation) unless you pass |
| 117 | + `--force` — closing the manual-invocation hole that `stash init --prisma-next` |
| 118 | + already avoided. |
| 119 | + |
| 120 | +### Patch Changes |
| 121 | + |
| 122 | +- 8b2551a: Fix "Failed to load native binding" on project-local installs of the CLI/SDK |
| 123 | + (npm). `@cipherstash/auth` was pinned at 0.41.0 while the six |
| 124 | + `@cipherstash/auth-*` platform bindings declared in stack/stash/wizard's |
| 125 | + optionalDependencies were pinned at 0.42.0. Because auth pins its bindings as |
| 126 | + exact-version optional peer dependencies, the skew made npm nest per-consumer |
| 127 | + binding copies that the hoisted `auth` package could not resolve — any command |
| 128 | + or import touching auth then died at startup. All seven packages now move in |
| 129 | + lockstep at 0.42.0, Dependabot is barred from bumping any of them |
| 130 | + independently, and a supply-chain CI test fails on any future skew. |
| 131 | +- b8cb599: Fix invalid DDL from `drizzle-kit generate`/`push` for EQL v3 encrypted columns. |
| 132 | + A v3 column declared its SQL type as the schema-qualified domain |
| 133 | + (`public.eql_v3_text_search`), but drizzle-kit wraps a custom type's whole name |
| 134 | + in a single pair of double quotes — emitting `"public.eql_v3_text_search"`, which |
| 135 | + Postgres reads as one dotted identifier and rejects with `type |
| 136 | +"public.eql_v3_text_search" does not exist`. Generated migrations had to be |
| 137 | + hand-repaired. |
| 138 | + |
| 139 | + The v3 column now emits the **unqualified** domain (`eql_v3_text_search`), which |
| 140 | + drizzle-kit renders as the valid `"eql_v3_text_search"` and which resolves via the |
| 141 | + search path (the domains live in `public`). This matches how the v2 |
| 142 | + `encryptedType` surface already declares its type, and how drizzle-kit reads the |
| 143 | + type back during a `push` introspection diff, so the two sides no longer disagree. |
| 144 | + Builder recovery still yields the canonical `public.eql_v3_*` identity, so |
| 145 | + operators and schema extraction are unchanged. |
| 146 | + |
| 147 | + The bundled `stash-drizzle` skill is updated to describe the unqualified generated |
| 148 | + type and the search-path requirement (hence the `stash` bump — the skill ships in |
| 149 | + its tarball). |
| 150 | + |
| 151 | +- 175eeb7: The EQL **v3** install SQL is now read from the `@cipherstash/eql` package at |
| 152 | + runtime instead of a copy vendored into this repo. `@cipherstash/eql` becomes a |
| 153 | + runtime dependency of `stash`, and a version bump now flows straight through — no |
| 154 | + re-vendor step, no drift between the pin and the shipped bundle. |
| 155 | + |
| 156 | + This removes ~44k lines of generated plpgsql from the repository (which had made |
| 157 | + GitHub classify the whole repo as plpgsql — CIP-3518) along with the |
| 158 | + `gen:eql-v3-sql` vendor script and its CI drift-check. |
| 159 | + |
| 160 | + No behaviour change: v3 installs the same one-artifact bundle (which self-adapts to |
| 161 | + non-superuser environments like Supabase), and the v2 path is unchanged. |
| 162 | + |
| 163 | +- 4923c0a: **Breaking (v3 authoring surface):** the EQL v3 PSL column constructors drop |
| 164 | + the `Encrypted` prefix to line up with the stack / Drizzle `types.*` catalog — |
| 165 | + the `cipherstash.` namespace already disambiguates. So |
| 166 | + `cipherstash.EncryptedTextSearch()` → `cipherstash.TextSearch()`, |
| 167 | + `cipherstash.EncryptedDoubleOrd()` → `cipherstash.DoubleOrd()`, |
| 168 | + `cipherstash.EncryptedBoolean()` → `cipherstash.Boolean()`, etc. |
| 169 | + |
| 170 | + The v3 one-call setup function is renamed `cipherstashFromStackV3` → |
| 171 | + `cipherstashFromStack` (v3 is the default), and the existing v2 setup function |
| 172 | + becomes `cipherstashFromStackV2`. |
| 173 | + |
| 174 | + The camelCase TS-authoring factory exports move in lockstep: |
| 175 | + `encryptedTextSearch` → `textSearch`, `encryptedDoubleOrd` → `doubleOrd`, etc. |
| 176 | + (a property test enforces the PSL and TS names agree modulo first-letter case). |
| 177 | + |
| 178 | + Unchanged: the runtime value envelopes (`EncryptedString`, `EncryptedNumber`, |
| 179 | + `EncryptedBoolean`, …), the `cipherstash.*V2` legacy column constructors, the |
| 180 | + generated `contract.json` / codec ids, and the `eql*` query operators. |
| 181 | + |
| 182 | + The `stash-prisma-next` skill is updated to the new names (skills ship in the |
| 183 | + `stash` tarball). |
| 184 | + |
| 185 | +- Updated dependencies [3a86939] |
| 186 | + - @cipherstash/migrate@1.0.0-rc.1 |
| 187 | + |
3 | 188 | ## 1.0.0-rc.2 |
4 | 189 |
|
5 | 190 | ### Patch Changes |
|
0 commit comments