diff --git a/.changeset/pre.json b/.changeset/pre.json index a303b3249..cdaa6fd7c 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -26,6 +26,7 @@ "auth-binding-lockstep", "cli-anonymous-telemetry", "cli-eql-v3-single-bundle", + "codex-skills-eperm", "drizzle-kit-eql-v3-ddl", "eql-migration-command", "eql-v3-adapter-type-robustness", @@ -51,7 +52,11 @@ "eql-v3-typed-schema", "eql-v3-wasm-inline", "honest-noninteractive-init", + "hungry-spoons-shake", + "init-drizzle-eql-v3", "init-pins-runtime-versions", + "init-placeholder-eql-v3", + "khaki-pugs-play", "migrate-eql-v3", "plan-complete-rollout-yes", "prisma-example-eql-v3", @@ -59,11 +64,14 @@ "prisma-next-drop-encrypted-prefix", "prisma-next-eql-runtime-source", "prisma-next-joins-release-train", + "prisma-next-v3-only-install", + "protect-ffi-030-json-selectors", "release-train-coupling", "remove-legacy-drizzle-package", "remove-secrets-leftovers", "rename-db-install-to-eql-install", "schema-stevec-standard-pin", + "skills-eql-v3-accuracy", "skills-identity-docs-refresh", "stack-1-0-0-rc", "stack-adapter-kit", @@ -71,6 +79,7 @@ "stash-cli-skill-refresh", "stash-drizzle-skill-encrypt-query", "stash-env-mint-credentials", + "stash-prisma-next-skill-v3-only", "stash-prisma-next-skill", "stash-skills-contains-to-matches", "stash-supabase-contains-substrings", @@ -82,6 +91,7 @@ "supabase-v3-order-by-ope-term", "v3-supabase-needle-lockcontext-errors", "wasm-encrypt-query", + "wasm-inline-bulk-ops", "wizard-allow-env-templates", "wizard-analytics-privacy", "wizard-joins-release-train" diff --git a/e2e/CHANGELOG.md b/e2e/CHANGELOG.md index d8b61b3d8..fbe4d3001 100644 --- a/e2e/CHANGELOG.md +++ b/e2e/CHANGELOG.md @@ -1,5 +1,22 @@ # @cipherstash/e2e +## 0.0.3-rc.4 + +### Patch Changes + +- Updated dependencies [98156ac] +- Updated dependencies [0e2ce93] +- Updated dependencies [c8726cd] +- Updated dependencies [04f5a13] +- Updated dependencies [46dde37] +- Updated dependencies [cf2c57c] +- Updated dependencies [524903c] +- Updated dependencies [2e6f032] +- Updated dependencies [508f1d5] + - stash@1.0.0-rc.4 + - @cipherstash/wizard@1.0.0-rc.4 + - @cipherstash/stack@1.0.0-rc.4 + ## 0.0.3-rc.3 ### Patch Changes diff --git a/e2e/package.json b/e2e/package.json index 72b91ba7e..7fd7205eb 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/e2e", - "version": "0.0.3-rc.3", + "version": "0.0.3-rc.4", "private": true, "description": "End-to-end tests that exercise built CipherStash binaries and cross-package behaviour.", "type": "module", diff --git a/examples/basic/CHANGELOG.md b/examples/basic/CHANGELOG.md index 8496f1897..8b590e678 100644 --- a/examples/basic/CHANGELOG.md +++ b/examples/basic/CHANGELOG.md @@ -1,5 +1,15 @@ # @cipherstash/basic-example +## 1.2.14-rc.4 + +### Patch Changes + +- Updated dependencies [cf2c57c] +- Updated dependencies [508f1d5] + - @cipherstash/stack@1.0.0-rc.4 + - @cipherstash/stack-drizzle@1.0.0-rc.4 + - @cipherstash/stack-supabase@1.0.0-rc.4 + ## 1.2.14-rc.3 ### Patch Changes diff --git a/examples/basic/package.json b/examples/basic/package.json index 734001f7b..1c1de60b3 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/basic-example", "private": true, - "version": "1.2.14-rc.3", + "version": "1.2.14-rc.4", "type": "module", "scripts": { "start": "tsx index.ts" diff --git a/examples/prisma/CHANGELOG.md b/examples/prisma/CHANGELOG.md index 7360cfda5..54f5e51b2 100644 --- a/examples/prisma/CHANGELOG.md +++ b/examples/prisma/CHANGELOG.md @@ -1,5 +1,15 @@ # @cipherstash/prisma-next-example +## 0.1.0-rc.4 + +### Patch Changes + +- Updated dependencies [2e6f032] +- Updated dependencies [cf2c57c] +- Updated dependencies [508f1d5] + - @cipherstash/prisma-next@1.0.0-rc.4 + - @cipherstash/stack@1.0.0-rc.4 + ## 0.1.0-rc.3 ### Minor Changes diff --git a/examples/prisma/package.json b/examples/prisma/package.json index ea8f3843a..12e424b77 100644 --- a/examples/prisma/package.json +++ b/examples/prisma/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/prisma-next-example", "private": true, - "version": "0.1.0-rc.3", + "version": "0.1.0-rc.4", "description": "End-to-end example of @cipherstash/prisma-next: searchable application-layer encryption for Postgres with Prisma Next, using @cipherstash/stack as the SDK.", "type": "module", "scripts": { diff --git a/packages/bench/CHANGELOG.md b/packages/bench/CHANGELOG.md index f78d55b47..7ebbbed0c 100644 --- a/packages/bench/CHANGELOG.md +++ b/packages/bench/CHANGELOG.md @@ -1,5 +1,14 @@ # @cipherstash/bench +## 0.0.5-rc.4 + +### Patch Changes + +- Updated dependencies [cf2c57c] +- Updated dependencies [508f1d5] + - @cipherstash/stack@1.0.0-rc.4 + - @cipherstash/stack-drizzle@1.0.0-rc.4 + ## 0.0.5-rc.3 ### Patch Changes diff --git a/packages/bench/package.json b/packages/bench/package.json index 4cd8be524..1aa5b847a 100644 --- a/packages/bench/package.json +++ b/packages/bench/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/bench", - "version": "0.0.5-rc.3", + "version": "0.0.5-rc.4", "private": true, "description": "Performance / index-engagement benchmarks for stack integrations (Drizzle, encryptedSupabase, Prisma).", "type": "module", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index b723ee0a7..0df96276a 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,212 @@ # @cipherstash/cli +## 1.0.0-rc.4 + +### Patch Changes + +- 98156ac: Fix the Codex handoff installing zero skills — and losing `AGENTS.md` and `.cipherstash/` with them — when `.codex/` is not writable. + + Codex sandboxes deny writes under `.codex/`. `installSkills` created its destination with an unguarded `mkdirSync`, sitting directly above a per-skill copy loop that _was_ guarded — so the failure threw past that fallback and past the caller, aborting the whole handoff step. Because the skills install runs first, nothing after it ran either: no `AGENTS.md`, no `.cipherstash/context.json`, no `.cipherstash/setup-prompt.md`. All five Codex runs of the rc.3 skilltester matrix landed here, and it was identified in that report as the primary driver of the Claude→Codex quality gap. + + The fix, hardened by a follow-up review of the first cut: + + - **`installSkills` never throws, and reports what happened.** It returns `{ copied, failed }` instead of a flat list, so callers can tell "unwritable destination" from "stripped build" from "partial copy" without re-deriving it — every filesystem failure degrades to a warning plus a `failed` entry. + - **The Codex handoff inlines exactly the skills that failed.** Whatever could not be copied into `.codex/skills/` — all of them under a sandbox, or a subset after a partial failure — has its body inlined into `AGENTS.md` via the same `doctrine-plus-skills` path the editor-agent handoff uses. The launch prompt points at wherever each skill actually ended up, including both locations after a partial copy. A stripped build that ships no skills stays `doctrine-only` and says nothing. + - **The doctrine now ships where the published CLI can find it.** The bundled AGENTS.md doctrine was copied to `dist/commands/init/doctrine`, but the compiled resolver probes ancestor directories of the chunk in `dist/bin/` — so every published build silently wrote the minimal `AGENTS.md` stub instead of the doctrine (and the inline fallback would have inlined nothing). It now lands at `dist/doctrine`, like the skills bundle. `buildAgentsMdBody` also honours `doctrine-plus-skills` even when the doctrine fragment is missing, so inlined skills are never dropped with it. + - **The generated artifacts describe the fallback honestly.** `context.json` gains an `inlinedSkills` field, and `setup-prompt.md` distinguishes installed / inlined / failed skills instead of mislabelling an unwritable destination as a "stripped build". The Claude handoff now warns when skills exist but could not be installed, and the AGENTS.md handoff records what it inlined. + - **The rest of the handoff is guarded too.** The `AGENTS.md` upsert (which refuses malformed sentinel pairs) and the bundled-file reads degrade to warnings instead of aborting the step before `.cipherstash/` is written. + + `@cipherstash/wizard` carries its own copy of `installSkills` with the same unguarded `mkdirSync` above the same guarded copy loop. It targets `.claude/skills` rather than `.codex/skills`, so the Codex sandbox case does not apply, but an unwritable destination crashed it identically — now guarded the same way, with a confirmed-then-failed install recorded in the wizard changelog instead of vanishing with the terminal output. + +- 0e2ce93: Fix `stash impl` and `stash init` hanging on CI runners that allocate a TTY. + + Four prompts decided whether to run interactively without going through the + shared TTY helper, so on a CI runner with an allocated TTY they rendered a clack + prompt and blocked forever on `/dev/tty` — a silent hang with no error and no + timeout: + + - `stash impl` gated on an inline `process.env.CI !== 'true'`, which only + recognised the exact lowercase spelling. Runners that set `CI=1` or `CI=TRUE` + blocked on the plan-summary confirmation or the agent-target picker. + - `stash init`'s offer to chain into `stash plan`, and its Proxy-vs-SDK + question, gated on `process.stdout.isTTY` and did not consult `CI` at all — + so they hung on any CI runner with a TTY, whatever the spelling. Gating on + stdout was also the wrong stream: a redirected stdin still hangs a prompt. + - `stash impl --continue-without-plan` confirmed the flag with a second prompt + that was not gated at all, so a CI run with no plan on disk blocked there even + though the flag had already granted consent. The flag is now taken as consent + in non-interactive runs and only re-confirmed interactively. + + All four now use the shared `isInteractive()` helper (stdin is a TTY and `CI` + is not set to `1`/`true` in any case), matching `stash plan`. Non-interactive + runs take the path they always should have: `stash init` skips the chain offer + and prints the `plan --target` hint, the Proxy-vs-SDK question defaults to + SDK-only, and `stash impl` proceeds without prompting. + +- c8726cd: `stash init --drizzle` now installs EQL v3 instead of v2. + + The Drizzle init flow pinned `--eql-version 2`, because `stash eql install +--drizzle` (the only migration-generating install path at the time) was + v2-only. That made `stash init --drizzle` the single flow that provisioned a v2 + database — a bare `stash eql install`, and init for every other integration, + already defaulted to v3. It also contradicted the `stash-drizzle` skill init + copies into the same project, which documents the v3 `@cipherstash/stack-drizzle/v3` + surface (`types.*` domains, `EncryptionV3`) and would have the user's agent + author v3 code against a v2 database. + + Init's Drizzle flow now routes through `stash eql migration --drizzle`, so it + stays migration-first (the install lands in your Drizzle migration history and + ships to every environment via `drizzle-kit migrate`) while emitting v3 SQL. + The generated migration also carries the `cs_migrations` tracking schema, so one + `drizzle-kit migrate` covers everything `stash encrypt …` needs. If `drizzle-kit` + isn't installed or configured, init now reports EQL as not installed and points + at `stash eql migration --drizzle` rather than aborting the run. + + The v2 Drizzle path remains available for existing deployments via an explicit + `stash eql install --drizzle --eql-version 2`; that command's error message now + points at the v3 alternative instead of only suggesting `--eql-version 2`. + +- 04f5a13: `stash init` now scaffolds an EQL **v3** encryption client, matching the EQL v3 + database it installs. + + The placeholder client (`DRIZZLE_PLACEHOLDER` / `GENERIC_PLACEHOLDER`) and the + introspection-driven client generator previously emitted EQL v2 authoring + patterns — `Encryption({ schemas })`, `encryptedColumn(...).equality().freeTextSearch()`, + and `encryptedType('x', { equality: true })`. Since init installs a v3 + database, this handed the customer's coding agent v2 guidance against a v3 + schema (follow-up to #732 / #705). + + Scaffolds now teach the v3 surface: `EncryptionV3` from `@cipherstash/stack/v3`, + the concrete-domain `types.*` factories (`types.TextSearch`, `types.IntegerOrd`, + `types.Text`, `types.Json`, …), and the `@cipherstash/stack-drizzle/v3` entry + (`extractEncryptionSchemaV3`) for Drizzle. The `encryptionClient` export shape + and the empty-schema "no schemas yet" error path are unchanged. + +- 46dde37: Fix two defects in the Drizzle migration generator used by `stash eql install --drizzle` (EQL v2): + + - **`--name` is now validated and no longer reaches a shell.** The migration name was interpolated into a shell command string, so a name containing shell metacharacters (e.g. `--name 'x; rm -rf ~'`) was executed. `--name` is now restricted to letters, numbers, dashes, and underscores, and drizzle-kit is invoked with an argv array instead of a shell string. + - **`--out` is now actually passed to drizzle-kit.** The flag was used to search for the generated migration but never handed to `drizzle-kit generate`, so any project whose `drizzle.config.ts` writes migrations outside `drizzle/` had the file written in one place and searched for in another, failing with "migration file not found". + - **drizzle-kit now runs project-locally.** The generator invoked drizzle-kit through the download-and-run form (`pnpm dlx` / `npx ` / `bunx`), which could fetch a different drizzle-kit major into a temp store and resolve a different `drizzle.config.ts`/schema than the project's. It now uses the project-local form (`pnpm exec` / `npx --no-install`), so it resolves the project's own drizzle-kit and config and fails loudly if drizzle-kit isn't installed rather than surprise-downloading it. The "run your migrations" hint matches. This aligns v2 with the v3 generator's behaviour. + + `stash eql migration --drizzle` (EQL v3) already had all three fixes and is unchanged. + +- cf2c57c: Upgrade Stack to `@cipherstash/protect-ffi` 0.30 and EQL 3.0.2. + + Prisma Next includes a versioned EQL 3.0.2 upgrade migration, so databases + that have already recorded the original EQL v3 baseline still install the new + domains and functions. + + Encrypted JSON now uses the `public.eql_v3_json_search` storage domain and + `eql_v3.query_json` query domain. Drizzle selector equality uses exact, + GIN-indexable value-selector containment, while selector range comparisons use + a ciphertext-free path selector plus string/number query term. Prisma Next gains + the equivalent `eqlJsonPathEq`, `eqlJsonPathNeq`, `eqlJsonPathGt`, + `eqlJsonPathGte`, `eqlJsonPathLt`, and `eqlJsonPathLte` operators. Selector + Selector-based `ORDER BY` is available as + `ops.selector(column, path).asc()/desc()` in Drizzle + and `eqlJsonPathAsc(column, path)` / `eqlJsonPathDesc(column, path)` in Prisma + Next; both lower to `ORDER BY eql_v3.ord_term` over the selected entry. + + If you call `encryptQuery` with an explicit `queryType`, note that + `steVecTerm` now produces a scalar JSON ordering term. It no longer means + structural containment; use the recommended `searchableJson` query type with + an object or array for containment, or `steVecValueSelector` with + `{ path, value }` for exact equality at a path. + + The FFI now rejects free-text needles shorter than the configured n-gram size + at the core query-encryption boundary, including callers that bypass adapter + guards. + + This EQL release changes the SteVec storage format. Existing EQL v3 encrypted + JSON rows must be re-encrypted before they can be queried with the new domain. + Legacy EQL v2 `searchableJson()` schemas are rejected during client setup + because the old selector envelope can no longer be emitted; migrate them to the + v3 `types.Json` domain. + + EQL 3.0.2 requires typed query-domain operands for encrypted free-text and JSON + operators. PostgREST cannot express those casts, so Supabase v3 fails fast for + `matches()`, encrypted `contains()`, and `selectorEq()`/`selectorNe()` instead + of placing a decryptable storage envelope in a GET query string that the new + SQL surface will reject. Use the Drizzle or Prisma Next adapter, or a carefully + scoped direct SQL/RPC path. + +- 524903c: Correct stale EQL v3 guidance in the bundled agent skills. + + `@cipherstash/migrate` and the `stash encrypt *` commands gained EQL v3 support + (cipherstash/stack#648, now closed), but the shipped skills still told readers the + rollout tooling was v2-only. Since these skills are copied into customer repos, the + stale text steered users away from v3 and toward workarounds they no longer need. + + - **`stash-drizzle`, `stash-supabase`** — replaced the "v3 not supported end-to-end" + callouts with an accurate EQL version note: the tooling auto-detects a column's + generation from its Postgres domain type, and the two lifecycles differ at the end. + v3 is `backfill → switch the app to the encrypted column by name → drop` with no + cut-over rename; v2 keeps the `stash encrypt cutover` rename plus config promotion. + - **`stash-supabase`** — removed the "Interim path until #648: the v2 encrypted twin" + section; a v2 twin is no longer needed to get CLI-managed backfill. + - **`stash-drizzle`, `stash-supabase`** — the drop step now documents that + `stash encrypt drop` targets the _original_ column under v3 (there is no + `_plaintext`, since nothing was renamed) and `_plaintext` under v2. + - **`stash-cli`** — corrected the documented `EQLInstaller` default: `eqlVersion` + defaults to `3`, not `2`, matching the `--eql-version` CLI default. Also reworded + the v2 cut-over known-gap note, which cited cipherstash/stack#585 as open tracking + when it was resolved by making v3 the default. + +- 2e6f032: Update the bundled `stash-prisma-next` skill for the EQL v3-only + `@cipherstash/prisma-next`: drop the stale references to the removed EQL v2 + surface (`cipherstashFromStackV2`, the `cipherstash*` operators, the "legacy v2" + subpath note) so the guidance copied into customer repos matches the package. +- 508f1d5: **Breaking (`@cipherstash/stack/wasm-inline`):** every fallible method now returns a `Result` — `{ data } | { failure }` — instead of throwing. And `bulkEncrypt` / `bulkDecrypt` are added, so a list of encrypted rows costs **one** ZeroKMS round trip instead of one per row. + + ### Result alignment + + `encrypt`, `decrypt`, `encryptQuery` and `encryptQueryBulk` previously threw on failure, and returned bare values on success. They now return `{ data } | { failure }`, with `failure.type` drawn from `EncryptionErrorTypes` (`EncryptionError` for encrypt-side operations, `DecryptionError` for decrypt-side) and `failure.code` carrying the FFI error code where there is one. + + ```typescript + // before + const encrypted = await client.encrypt(plaintext, { + table: users, + column: users.email, + }); + + // after + const result = await client.encrypt(plaintext, { + table: users, + column: users.email, + }); + if (result.failure) throw new Error(result.failure.message); + const encrypted = result.data; + ``` + + This is the contract the native entry has always honoured, and the one `AGENTS.md` states outright: _"Operations return `{ data }` or `{ failure }`. Preserve this shape and error `type` values in `EncryptionErrorTypes`."_ The WASM entry never followed it. That was drift rather than a design decision — nothing about WASM prevents it (`@byteslice/result` is already bundled into `dist/wasm-inline.js`), and it meant edge code had to be written in a different shape from every other surface, with failures that were easy to miss. + + Fixed now because it is a breaking change and 1.0.0 has not shipped: `@cipherstash/stack@latest` is still `0.19.0`, so this surface has only ever been published under the `rc` tag. After GA it would have had to wait for a major. + + `isEncrypted` is unchanged — a pure predicate with nothing to fail at, exactly as on the native entry. + + ### Bulk operations + + ```typescript + // Write: several columns across many rows, one round trip + const encrypted = await client.bulkEncrypt([ + { plaintext: "alice@example.com", table: users, column: users.email }, + { plaintext: "hello", table: users, column: users.bio }, + ]); + + // Read: a whole page in one call + const emails = await client.bulkDecrypt(rows.map((r) => r.email)); + ``` + + The WASM entry previously exposed no bulk operations at all, so rendering an N-row list on Deno, Cloudflare Workers, or Supabase Edge Functions meant N sequential ZeroKMS calls. Combined with the WASM cold start, that made list endpoints impractical on the edge. + + Both are index-aligned with their input, and `null` / `undefined` entries yield `null` at the same index without reaching ZeroKMS (an all-null batch makes no call at all). Because each entry names its own table and column, a single `bulkEncrypt` can cover several columns across many rows — which is what makes the saving real, since a single-column batch would still cost one round trip per column. + + `bulkDecrypt` builds on the fallible FFI primitive, so when items fail the `failure.message` names **every** failing index with its reason, rather than surfacing the first and discarding the rest. + + The model helpers (`encryptModel` / `decryptModel` and their bulk forms) remain Node-only: the WASM entry has no single-model operation to build them on, so those need their own port. + + - @cipherstash/migrate@1.0.0-rc.1 + ## 1.0.0-rc.3 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index d07dfdb5b..5b95ae381 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "stash", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "description": "CipherStash CLI — the one stash command for auth, init, encryption schema, database setup, and secrets.", "repository": { "type": "git", diff --git a/packages/prisma-next/CHANGELOG.md b/packages/prisma-next/CHANGELOG.md index 8bd057d0a..b21637252 100644 --- a/packages/prisma-next/CHANGELOG.md +++ b/packages/prisma-next/CHANGELOG.md @@ -1,5 +1,102 @@ # @cipherstash/prisma-next +## 1.0.0-rc.4 + +### Minor Changes + +- 2e6f032: Make `@cipherstash/prisma-next` **EQL v3 only**. The EQL v2 surface is removed + entirely — install path, authoring constructors, runtime codecs, and the v2 + subpath exports. + + **Why:** the v2 and v3 baselines were chained — the v3 migration edge started + from the v2 baseline's `to` state and the head ref required both invariants — so + the only path to head ran the v2 install first. The v2 bundle's install fails on + managed Postgres (e.g. Supabase) where the connecting role is **not a + superuser**, which made the adapter unusable there even for v3-only apps. + Installing only EQL v3 (which applies fine as a non-superuser) fixes this. + + **Breaking — install path:** the EQL v2 baseline migration + (`20260601T0000_install_eql_bundle`) is removed, and the contract now models no + storage (the retired `eql_v2_configuration` table is gone). The v3 baseline + (`20260601T0100_install_eql_v3_bundle`) is re-rooted as the sole invariant-only + **genesis** edge (`from: null`); the head ref requires only + `cipherstash:install-eql-v3-bundle-v1`. `prisma-next migration apply` now + installs EQL v3 exclusively and works on Supabase as a non-superuser. + + **Breaking — API:** the EQL v2 authoring/runtime surface is removed: + + - `cipherstashFromStackV2`, `deriveStackSchemas`, and `createCipherstashSdk` + (from `./stack`) — use `cipherstashFromStack` (v3). + - The `encrypted*V2` TS column factories and the `cipherstash.Encrypted*V2` PSL + constructors (from `./column-types`) — use the v3 domain factories/constructors + (`text`/`textSearch`/`bigIntOrd`/… and `cipherstash.TextSearch()` etc.). + - The v2 runtime codecs, `createCipherstashRuntimeDescriptor`, the `cipherstash*` + query operators/helpers, and the `EncryptedDouble` envelope (from `./runtime`) + — use the v3 runtime (`createCipherstashV3RuntimeDescriptor`, + `bulkEncryptMiddlewareV3`, the `eql*` operators, `EncryptedNumber`). The + version-neutral envelopes (`EncryptedString`/`BigInt`/`Boolean`/`Date`/`Json`) + and `decryptAll` are unchanged. + - The `./middleware` and `./migration` subpath exports are removed (the v2 + bulk-encrypt middleware and call-classes). Use `bulkEncryptMiddlewareV3` from + `./runtime` / `./v3`. + + Apps still on the v2 surface must move to the v3 constructors and regenerate + their contract (`prisma-next contract emit`); there is no supported EQL v2 path + in this package anymore. + + **Also:** the "bulk-encrypt middleware not wired" diagnostic is now raised on the + v3 write path. Encoding an unencrypted value with an SDK that has no + `bulkEncryptMiddlewareV3(sdk)` registered against it fails fast with + `RUNTIME.ENCODE_FAILED` and a copy-pasteable wiring snippet, instead of surfacing + as an opaque pg-level serialise error. (The guard existed on the v2 codec; the v3 + codec had never wired it up.) + +- cf2c57c: Upgrade Stack to `@cipherstash/protect-ffi` 0.30 and EQL 3.0.2. + + Prisma Next includes a versioned EQL 3.0.2 upgrade migration, so databases + that have already recorded the original EQL v3 baseline still install the new + domains and functions. + + Encrypted JSON now uses the `public.eql_v3_json_search` storage domain and + `eql_v3.query_json` query domain. Drizzle selector equality uses exact, + GIN-indexable value-selector containment, while selector range comparisons use + a ciphertext-free path selector plus string/number query term. Prisma Next gains + the equivalent `eqlJsonPathEq`, `eqlJsonPathNeq`, `eqlJsonPathGt`, + `eqlJsonPathGte`, `eqlJsonPathLt`, and `eqlJsonPathLte` operators. Selector + Selector-based `ORDER BY` is available as + `ops.selector(column, path).asc()/desc()` in Drizzle + and `eqlJsonPathAsc(column, path)` / `eqlJsonPathDesc(column, path)` in Prisma + Next; both lower to `ORDER BY eql_v3.ord_term` over the selected entry. + + If you call `encryptQuery` with an explicit `queryType`, note that + `steVecTerm` now produces a scalar JSON ordering term. It no longer means + structural containment; use the recommended `searchableJson` query type with + an object or array for containment, or `steVecValueSelector` with + `{ path, value }` for exact equality at a path. + + The FFI now rejects free-text needles shorter than the configured n-gram size + at the core query-encryption boundary, including callers that bypass adapter + guards. + + This EQL release changes the SteVec storage format. Existing EQL v3 encrypted + JSON rows must be re-encrypted before they can be queried with the new domain. + Legacy EQL v2 `searchableJson()` schemas are rejected during client setup + because the old selector envelope can no longer be emitted; migrate them to the + v3 `types.Json` domain. + + EQL 3.0.2 requires typed query-domain operands for encrypted free-text and JSON + operators. PostgREST cannot express those casts, so Supabase v3 fails fast for + `matches()`, encrypted `contains()`, and `selectorEq()`/`selectorNe()` instead + of placing a decryptable storage envelope in a GET query string that the new + SQL surface will reject. Use the Drizzle or Prisma Next adapter, or a carefully + scoped direct SQL/RPC path. + +### Patch Changes + +- Updated dependencies [cf2c57c] +- Updated dependencies [508f1d5] + - @cipherstash/stack@1.0.0-rc.4 + ## 1.0.0-rc.3 ### Minor Changes diff --git a/packages/prisma-next/package.json b/packages/prisma-next/package.json index a4c7721c5..dc6c127a3 100644 --- a/packages/prisma-next/package.json +++ b/packages/prisma-next/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/prisma-next", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "license": "MIT", "author": "CipherStash ", "description": "CipherStash extension for Prisma Next: searchable application-layer field-level encryption for Postgres (EQL v3), with domain-typed encrypted columns, the eql* query operators, bulk encrypt/decrypt middleware, and a baseline migration that installs the EQL v3 bundle.", diff --git a/packages/stack-drizzle/CHANGELOG.md b/packages/stack-drizzle/CHANGELOG.md index 46e49bba3..6a2662e59 100644 --- a/packages/stack-drizzle/CHANGELOG.md +++ b/packages/stack-drizzle/CHANGELOG.md @@ -1,5 +1,55 @@ # @cipherstash/stack-drizzle +## 1.0.0-rc.4 + +### Minor Changes + +- cf2c57c: Upgrade Stack to `@cipherstash/protect-ffi` 0.30 and EQL 3.0.2. + + Prisma Next includes a versioned EQL 3.0.2 upgrade migration, so databases + that have already recorded the original EQL v3 baseline still install the new + domains and functions. + + Encrypted JSON now uses the `public.eql_v3_json_search` storage domain and + `eql_v3.query_json` query domain. Drizzle selector equality uses exact, + GIN-indexable value-selector containment, while selector range comparisons use + a ciphertext-free path selector plus string/number query term. Prisma Next gains + the equivalent `eqlJsonPathEq`, `eqlJsonPathNeq`, `eqlJsonPathGt`, + `eqlJsonPathGte`, `eqlJsonPathLt`, and `eqlJsonPathLte` operators. Selector + Selector-based `ORDER BY` is available as + `ops.selector(column, path).asc()/desc()` in Drizzle + and `eqlJsonPathAsc(column, path)` / `eqlJsonPathDesc(column, path)` in Prisma + Next; both lower to `ORDER BY eql_v3.ord_term` over the selected entry. + + If you call `encryptQuery` with an explicit `queryType`, note that + `steVecTerm` now produces a scalar JSON ordering term. It no longer means + structural containment; use the recommended `searchableJson` query type with + an object or array for containment, or `steVecValueSelector` with + `{ path, value }` for exact equality at a path. + + The FFI now rejects free-text needles shorter than the configured n-gram size + at the core query-encryption boundary, including callers that bypass adapter + guards. + + This EQL release changes the SteVec storage format. Existing EQL v3 encrypted + JSON rows must be re-encrypted before they can be queried with the new domain. + Legacy EQL v2 `searchableJson()` schemas are rejected during client setup + because the old selector envelope can no longer be emitted; migrate them to the + v3 `types.Json` domain. + + EQL 3.0.2 requires typed query-domain operands for encrypted free-text and JSON + operators. PostgREST cannot express those casts, so Supabase v3 fails fast for + `matches()`, encrypted `contains()`, and `selectorEq()`/`selectorNe()` instead + of placing a decryptable storage envelope in a GET query string that the new + SQL surface will reject. Use the Drizzle or Prisma Next adapter, or a carefully + scoped direct SQL/RPC path. + +### Patch Changes + +- Updated dependencies [cf2c57c] +- Updated dependencies [508f1d5] + - @cipherstash/stack@1.0.0-rc.4 + ## 1.0.0-rc.3 ### Patch Changes diff --git a/packages/stack-drizzle/package.json b/packages/stack-drizzle/package.json index a49931e78..dd004ad62 100644 --- a/packages/stack-drizzle/package.json +++ b/packages/stack-drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack-drizzle", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "description": "CipherStash Stack Drizzle ORM integration: searchable, application-layer field-level encryption for PostgreSQL.", "keywords": [ "encrypted", diff --git a/packages/stack-supabase/CHANGELOG.md b/packages/stack-supabase/CHANGELOG.md index 87121f008..034c19e96 100644 --- a/packages/stack-supabase/CHANGELOG.md +++ b/packages/stack-supabase/CHANGELOG.md @@ -1,5 +1,53 @@ # @cipherstash/stack-supabase +## 1.0.0-rc.4 + +### Patch Changes + +- cf2c57c: Upgrade Stack to `@cipherstash/protect-ffi` 0.30 and EQL 3.0.2. + + Prisma Next includes a versioned EQL 3.0.2 upgrade migration, so databases + that have already recorded the original EQL v3 baseline still install the new + domains and functions. + + Encrypted JSON now uses the `public.eql_v3_json_search` storage domain and + `eql_v3.query_json` query domain. Drizzle selector equality uses exact, + GIN-indexable value-selector containment, while selector range comparisons use + a ciphertext-free path selector plus string/number query term. Prisma Next gains + the equivalent `eqlJsonPathEq`, `eqlJsonPathNeq`, `eqlJsonPathGt`, + `eqlJsonPathGte`, `eqlJsonPathLt`, and `eqlJsonPathLte` operators. Selector + Selector-based `ORDER BY` is available as + `ops.selector(column, path).asc()/desc()` in Drizzle + and `eqlJsonPathAsc(column, path)` / `eqlJsonPathDesc(column, path)` in Prisma + Next; both lower to `ORDER BY eql_v3.ord_term` over the selected entry. + + If you call `encryptQuery` with an explicit `queryType`, note that + `steVecTerm` now produces a scalar JSON ordering term. It no longer means + structural containment; use the recommended `searchableJson` query type with + an object or array for containment, or `steVecValueSelector` with + `{ path, value }` for exact equality at a path. + + The FFI now rejects free-text needles shorter than the configured n-gram size + at the core query-encryption boundary, including callers that bypass adapter + guards. + + This EQL release changes the SteVec storage format. Existing EQL v3 encrypted + JSON rows must be re-encrypted before they can be queried with the new domain. + Legacy EQL v2 `searchableJson()` schemas are rejected during client setup + because the old selector envelope can no longer be emitted; migrate them to the + v3 `types.Json` domain. + + EQL 3.0.2 requires typed query-domain operands for encrypted free-text and JSON + operators. PostgREST cannot express those casts, so Supabase v3 fails fast for + `matches()`, encrypted `contains()`, and `selectorEq()`/`selectorNe()` instead + of placing a decryptable storage envelope in a GET query string that the new + SQL surface will reject. Use the Drizzle or Prisma Next adapter, or a carefully + scoped direct SQL/RPC path. + +- Updated dependencies [cf2c57c] +- Updated dependencies [508f1d5] + - @cipherstash/stack@1.0.0-rc.4 + ## 1.0.0-rc.3 ### Patch Changes diff --git a/packages/stack-supabase/package.json b/packages/stack-supabase/package.json index 4e4b09892..4b0a5bc46 100644 --- a/packages/stack-supabase/package.json +++ b/packages/stack-supabase/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack-supabase", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "description": "CipherStash Stack Supabase integration: transparent, searchable field-level encryption for Supabase.", "keywords": [ "encrypted", diff --git a/packages/stack/CHANGELOG.md b/packages/stack/CHANGELOG.md index b5cac25f6..a403f6dcc 100644 --- a/packages/stack/CHANGELOG.md +++ b/packages/stack/CHANGELOG.md @@ -1,5 +1,98 @@ # @cipherstash/stack +## 1.0.0-rc.4 + +### Minor Changes + +- cf2c57c: Upgrade Stack to `@cipherstash/protect-ffi` 0.30 and EQL 3.0.2. + + Prisma Next includes a versioned EQL 3.0.2 upgrade migration, so databases + that have already recorded the original EQL v3 baseline still install the new + domains and functions. + + Encrypted JSON now uses the `public.eql_v3_json_search` storage domain and + `eql_v3.query_json` query domain. Drizzle selector equality uses exact, + GIN-indexable value-selector containment, while selector range comparisons use + a ciphertext-free path selector plus string/number query term. Prisma Next gains + the equivalent `eqlJsonPathEq`, `eqlJsonPathNeq`, `eqlJsonPathGt`, + `eqlJsonPathGte`, `eqlJsonPathLt`, and `eqlJsonPathLte` operators. Selector + Selector-based `ORDER BY` is available as + `ops.selector(column, path).asc()/desc()` in Drizzle + and `eqlJsonPathAsc(column, path)` / `eqlJsonPathDesc(column, path)` in Prisma + Next; both lower to `ORDER BY eql_v3.ord_term` over the selected entry. + + If you call `encryptQuery` with an explicit `queryType`, note that + `steVecTerm` now produces a scalar JSON ordering term. It no longer means + structural containment; use the recommended `searchableJson` query type with + an object or array for containment, or `steVecValueSelector` with + `{ path, value }` for exact equality at a path. + + The FFI now rejects free-text needles shorter than the configured n-gram size + at the core query-encryption boundary, including callers that bypass adapter + guards. + + This EQL release changes the SteVec storage format. Existing EQL v3 encrypted + JSON rows must be re-encrypted before they can be queried with the new domain. + Legacy EQL v2 `searchableJson()` schemas are rejected during client setup + because the old selector envelope can no longer be emitted; migrate them to the + v3 `types.Json` domain. + + EQL 3.0.2 requires typed query-domain operands for encrypted free-text and JSON + operators. PostgREST cannot express those casts, so Supabase v3 fails fast for + `matches()`, encrypted `contains()`, and `selectorEq()`/`selectorNe()` instead + of placing a decryptable storage envelope in a GET query string that the new + SQL surface will reject. Use the Drizzle or Prisma Next adapter, or a carefully + scoped direct SQL/RPC path. + +- 508f1d5: **Breaking (`@cipherstash/stack/wasm-inline`):** every fallible method now returns a `Result` — `{ data } | { failure }` — instead of throwing. And `bulkEncrypt` / `bulkDecrypt` are added, so a list of encrypted rows costs **one** ZeroKMS round trip instead of one per row. + + ### Result alignment + + `encrypt`, `decrypt`, `encryptQuery` and `encryptQueryBulk` previously threw on failure, and returned bare values on success. They now return `{ data } | { failure }`, with `failure.type` drawn from `EncryptionErrorTypes` (`EncryptionError` for encrypt-side operations, `DecryptionError` for decrypt-side) and `failure.code` carrying the FFI error code where there is one. + + ```typescript + // before + const encrypted = await client.encrypt(plaintext, { + table: users, + column: users.email, + }); + + // after + const result = await client.encrypt(plaintext, { + table: users, + column: users.email, + }); + if (result.failure) throw new Error(result.failure.message); + const encrypted = result.data; + ``` + + This is the contract the native entry has always honoured, and the one `AGENTS.md` states outright: _"Operations return `{ data }` or `{ failure }`. Preserve this shape and error `type` values in `EncryptionErrorTypes`."_ The WASM entry never followed it. That was drift rather than a design decision — nothing about WASM prevents it (`@byteslice/result` is already bundled into `dist/wasm-inline.js`), and it meant edge code had to be written in a different shape from every other surface, with failures that were easy to miss. + + Fixed now because it is a breaking change and 1.0.0 has not shipped: `@cipherstash/stack@latest` is still `0.19.0`, so this surface has only ever been published under the `rc` tag. After GA it would have had to wait for a major. + + `isEncrypted` is unchanged — a pure predicate with nothing to fail at, exactly as on the native entry. + + ### Bulk operations + + ```typescript + // Write: several columns across many rows, one round trip + const encrypted = await client.bulkEncrypt([ + { plaintext: "alice@example.com", table: users, column: users.email }, + { plaintext: "hello", table: users, column: users.bio }, + ]); + + // Read: a whole page in one call + const emails = await client.bulkDecrypt(rows.map((r) => r.email)); + ``` + + The WASM entry previously exposed no bulk operations at all, so rendering an N-row list on Deno, Cloudflare Workers, or Supabase Edge Functions meant N sequential ZeroKMS calls. Combined with the WASM cold start, that made list endpoints impractical on the edge. + + Both are index-aligned with their input, and `null` / `undefined` entries yield `null` at the same index without reaching ZeroKMS (an all-null batch makes no call at all). Because each entry names its own table and column, a single `bulkEncrypt` can cover several columns across many rows — which is what makes the saving real, since a single-column batch would still cost one round trip per column. + + `bulkDecrypt` builds on the fallible FFI primitive, so when items fail the `failure.message` names **every** failing index with its reason, rather than surfacing the first and discarding the rest. + + The model helpers (`encryptModel` / `decryptModel` and their bulk forms) remain Node-only: the WASM entry has no single-model operation to build them on, so those need their own port. + ## 1.0.0-rc.3 ### Patch Changes diff --git a/packages/stack/package.json b/packages/stack/package.json index c8ee6f5e9..32a167b60 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "description": "CipherStash Stack for TypeScript and JavaScript", "keywords": [ "encrypted", diff --git a/packages/test-kit/CHANGELOG.md b/packages/test-kit/CHANGELOG.md index afdb8935f..22d82c4aa 100644 --- a/packages/test-kit/CHANGELOG.md +++ b/packages/test-kit/CHANGELOG.md @@ -1,5 +1,13 @@ # @cipherstash/test-kit +## 0.0.1-rc.4 + +### Patch Changes + +- Updated dependencies [cf2c57c] +- Updated dependencies [508f1d5] + - @cipherstash/stack@1.0.0-rc.4 + ## 0.0.1-rc.3 ### Patch Changes diff --git a/packages/test-kit/package.json b/packages/test-kit/package.json index 63dabe047..656ffc96c 100644 --- a/packages/test-kit/package.json +++ b/packages/test-kit/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/test-kit", - "version": "0.0.1-rc.3", + "version": "0.0.1-rc.4", "private": true, "description": "Shared EQL v3 test harness: the domain catalog, the plaintext oracle, and the integration-suite driver. Consumed as TypeScript source — no build step.", "type": "module", diff --git a/packages/wizard/CHANGELOG.md b/packages/wizard/CHANGELOG.md index 9fa7bc2dd..0c25c6ce9 100644 --- a/packages/wizard/CHANGELOG.md +++ b/packages/wizard/CHANGELOG.md @@ -1,5 +1,23 @@ # @cipherstash/wizard +## 1.0.0-rc.4 + +### Patch Changes + +- 98156ac: Fix the Codex handoff installing zero skills — and losing `AGENTS.md` and `.cipherstash/` with them — when `.codex/` is not writable. + + Codex sandboxes deny writes under `.codex/`. `installSkills` created its destination with an unguarded `mkdirSync`, sitting directly above a per-skill copy loop that _was_ guarded — so the failure threw past that fallback and past the caller, aborting the whole handoff step. Because the skills install runs first, nothing after it ran either: no `AGENTS.md`, no `.cipherstash/context.json`, no `.cipherstash/setup-prompt.md`. All five Codex runs of the rc.3 skilltester matrix landed here, and it was identified in that report as the primary driver of the Claude→Codex quality gap. + + The fix, hardened by a follow-up review of the first cut: + + - **`installSkills` never throws, and reports what happened.** It returns `{ copied, failed }` instead of a flat list, so callers can tell "unwritable destination" from "stripped build" from "partial copy" without re-deriving it — every filesystem failure degrades to a warning plus a `failed` entry. + - **The Codex handoff inlines exactly the skills that failed.** Whatever could not be copied into `.codex/skills/` — all of them under a sandbox, or a subset after a partial failure — has its body inlined into `AGENTS.md` via the same `doctrine-plus-skills` path the editor-agent handoff uses. The launch prompt points at wherever each skill actually ended up, including both locations after a partial copy. A stripped build that ships no skills stays `doctrine-only` and says nothing. + - **The doctrine now ships where the published CLI can find it.** The bundled AGENTS.md doctrine was copied to `dist/commands/init/doctrine`, but the compiled resolver probes ancestor directories of the chunk in `dist/bin/` — so every published build silently wrote the minimal `AGENTS.md` stub instead of the doctrine (and the inline fallback would have inlined nothing). It now lands at `dist/doctrine`, like the skills bundle. `buildAgentsMdBody` also honours `doctrine-plus-skills` even when the doctrine fragment is missing, so inlined skills are never dropped with it. + - **The generated artifacts describe the fallback honestly.** `context.json` gains an `inlinedSkills` field, and `setup-prompt.md` distinguishes installed / inlined / failed skills instead of mislabelling an unwritable destination as a "stripped build". The Claude handoff now warns when skills exist but could not be installed, and the AGENTS.md handoff records what it inlined. + - **The rest of the handoff is guarded too.** The `AGENTS.md` upsert (which refuses malformed sentinel pairs) and the bundled-file reads degrade to warnings instead of aborting the step before `.cipherstash/` is written. + + `@cipherstash/wizard` carries its own copy of `installSkills` with the same unguarded `mkdirSync` above the same guarded copy loop. It targets `.claude/skills` rather than `.codex/skills`, so the Codex sandbox case does not apply, but an unwritable destination crashed it identically — now guarded the same way, with a confirmed-then-failed install recorded in the wizard changelog instead of vanishing with the terminal output. + ## 1.0.0-rc.3 ### Patch Changes diff --git a/packages/wizard/package.json b/packages/wizard/package.json index cbeadfd64..e57cf8198 100644 --- a/packages/wizard/package.json +++ b/packages/wizard/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/wizard", - "version": "1.0.0-rc.3", + "version": "1.0.0-rc.4", "description": "AI-powered encryption setup for CipherStash. Reads your codebase, picks columns to encrypt, and wires everything up.", "repository": { "type": "git",