Skip to content

Releases: cipherstash/stack

stash@1.0.0-rc.4

stash@1.0.0-rc.4 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Jul 12:31
98a9c09

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<T>('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 <pkg> / 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 equa...

Read more

@cipherstash/wizard@1.0.0-rc.4

Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Jul 12:31
98a9c09

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.

@cipherstash/stack@1.0.0-rc.4

Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Jul 12:31
98a9c09

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.

    // 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

    // 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/stack-supabase@1.0.0-rc.4

Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Jul 12:31
98a9c09

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

@cipherstash/stack-drizzle@1.0.0-rc.4

Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Jul 12:31
98a9c09

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

@cipherstash/prisma-next@1.0.0-rc.4

Pre-release

Choose a tag to compare

@github-actions github-actions released this 21 Jul 12:31
98a9c09

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

stash@1.0.0-rc.3

stash@1.0.0-rc.3 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 19 Jul 11:21
4888995

Minor Changes

  • 0811330: Add stash eql migration — generate an EQL v3 install migration for your ORM
    instead of running the SQL directly against the database (stash eql install).
    Migration-first is the preferred path: the install lands in your migration history
    and ships to every environment through the ORM's own migrate step.

    stash eql migration --drizzle              # Drizzle custom migration
    stash eql migration --drizzle --supabase   # also grants eql_v3 to anon/authenticated/service_role

    The migration carries the CLI's bundled v3 install SQL (one source of truth) plus
    the cs_migrations tracking schema, so a single drizzle-kit migrate covers
    everything stash encrypt … needs. --supabase appends the eql_v3 +
    eql_v3_internal role grants for PostgREST/RLS access.

    --prisma is registered but not available yet — the Prisma Next migration
    emitter is a follow-up (tracked in #690) that will let
    prisma-next drop its baked install baseline. It fails with a pointer for now.

  • d20e48a: stash init is honest non-interactively — it no longer reports success for a
    setup that didn't fully complete.

    • Fails on version skew. A non-interactive run can't reconcile an
      already-installed @cipherstash/* package that's older than this CLI
      expects (it won't mutate an install without consent), so instead of warning
      and proceeding — scaffolding against mismatched packages and then claiming
      success — it now refuses with a non-zero exit and the exact align command.
      Interactive runs still offer to align. A newer install stays a warn (the
      install is likely fine; update the CLI instead).
    • No false "Setup complete". If the EQL extension isn't installed at the
      end — and the integration isn't one that installs it out-of-band — the
      summary reads "Setup incomplete" and init exits non-zero, pointing at
      stash eql install. Integrations that install EQL via a migration are
      reported honestly rather than as failures: Prisma Next (installs it via
      migration apply) and the Drizzle flow, which generates an EQL migration
      and now says "EQL migration generated — apply it with drizzle-kit migrate"
      instead of claiming the extension is already installed.
    • Honest checkmarks. The summary no longer claims "Database connection
      verified" (init resolves a URL but doesn't open a connection) — it now says
      "Database URL resolved" — and only shows "Encryption client scaffolded" when
      a client was actually written (skipped for Prisma Next).
    • No false "skills loaded". The agent handoff prompt only points at the
      skills directory when skills were actually copied (a stripped build installs
      none), instead of telling the agent to read files that aren't there.
  • 3a86939: EQL v3 support for the encryption rollout lifecycle (#648). The stash encrypt * commands (and @cipherstash/migrate underneath) now resolve a
    column's EQL version and its encrypted counterpart from the Postgres domain
    types
    — the EQL v3 types are self-describing, so the <col>_encrypted
    naming is a convention only, never enforced or relied upon — and follow the
    right lifecycle, no new flags:

    • encrypt backfill works on v3 columns unchanged (the engine was always
      version-agnostic; pass an EncryptionV3 client and real v3 envelopes land
      in the concrete eql_v3_* domain column — verified live against a real
      database, including the domain CHECK and a decrypt round-trip). The
      manifest records the detected version, the encrypted column's name, and the
      v3 target phase, and the command prints v3-appropriate next steps.
    • encrypt cutover on a backfilled v3 column reports "not applicable"
      (exit 0) with guidance: v3 has no rename cut-over — the application
      switches to the encrypted column by name. Before backfill completes it
      exits 1 and says to finish the backfill instead of instructing the switch.
      On a database with no eql_v2_configuration table (a v3-only install) the
      v2 path now explains that instead of surfacing a raw Postgres error.
    • encrypt drop is version-aware: v3 runs from the backfilled phase,
      verifies live coverage (refuses to generate the migration while any row
      still has the plaintext set and the encrypted column NULL — the
      countUnencrypted check), and drops the ORIGINAL plaintext column (there
      is no <col>_plaintext under v3); v2 behaviour is unchanged. The generated
      v3 migration re-verifies coverage at apply time — it locks the table,
      re-counts, and aborts without dropping if plaintext-only rows appeared
      after generation. And because dropping is the one irreversible step, it
      requires a positively asserted plaintext↔ciphertext pairing (the
      manifest's recorded encryptedColumn or the naming convention): a match
      found only by being the table's sole EQL column is refused with
      instructions, and an ambiguous table (several EQL columns, none
      identifiable) fails closed listing the candidates — as does cutover.
    • encrypt status classifies each column from the observed domain type
      (manifest as fallback), shows v3 in the EQL column, and no longer raises
      the v2-only not-registered / plaintext-col-missing drift flags for v3
      columns. stash status's quest ladder and the stash init agent handoff
      prompt teach the version-appropriate next step (no more "run cutover" on
      v3 columns).
    • New @cipherstash/migrate exports: classifyEqlDomain,
      resolveEncryptedColumn, pickEncryptedColumn, listEncryptedColumns
      (domain-type resolution — case-exact for quoted/mixed-case table names),
      countEncrypted / countUnencrypted (coverage counts), and manifest
      eqlVersion + encryptedColumn fields. EqlVersion is numeric (2 | 3),
      matching the manifest and the installer. Resolved columns carry via: 'hint' | 'convention' | 'sole' so callers can tell a positively asserted
      pairing from a by-elimination guess.
    • Fixed: encrypt cutover/encrypt drop precondition failures now actually
      exit 1 — the early-return guards previously skipped the exit-code path
      entirely, so failed preconditions exited 0. (This also applies to v2
      preconditions: scripted pipelines that relied on the erroneous exit 0 will
      now see the documented exit 1.)

    The stash-cli and stash-encryption skills and the @cipherstash/migrate
    README document the two lifecycles (v2: backfill → cutover → drop;
    v3: backfill → switch-by-name → drop).

  • b0634df: stash plan --complete-rollout is now automatable and has an honest exit code.
    It skips the production-deploy gate, so it needs explicit consent — previously
    that was an interactive prompt with no bypass, so a non-interactive run
    auto-cancelled (default-no) and exited 0 without drafting a plan, leaving
    automation to assume a plan existed.

    • New --yes flag confirms the gate-skip without a prompt (for CI/agents).
    • Without --yes, a non-interactive --complete-rollout run now refuses
      with a non-zero exit
      and points at --yes, instead of silently succeeding.
    • Interactive behaviour is unchanged (default-no confirm).
  • f188c7a: stash env now works: it mints deployment credentials from your device-code
    session and prints them as env vars — no dashboard copy-paste. The command
    creates a fresh ZeroKMS client and a member-role CipherStash access key (named
    via --name; the role is pinned in the request and verified on the response —
    the CLI deliberately cannot mint admin keys), then emits CS_WORKSPACE_CRN,
    CS_CLIENT_ID, CS_CLIENT_KEY, and CS_CLIENT_ACCESS_KEY.

    Output goes to stdout by default — and stdout is pipe-clean (progress UI is on
    stderr), so stash env --name x > prod.env and pipes into secret stores are
    safe. --write [path] writes a file instead (default .env.production.local,
    enforced mode 0600 even when overwriting), confirming before overwriting and
    refusing non-interactively — always before anything is minted, so a refusal
    never discards the shown-exactly-once access key. --json emits NDJSON; with
    --write the confirmation event is deliberately secret-free. API responses
    are schema-validated so a service change can never print undefined into a
    credentials file. Creating access keys requires the admin role in the
    workspace.

    This is also the supported credential path for WASM/edge local development
    (Supabase Edge Functions, Cloudflare Workers, Deno), where the runtime cannot
    read the ~/.cipherstash device profile: mint a key and feed it via
    supabase functions serve --env-file or the platform's secret store.

    The STASH_EXPERIMENTAL_ENV_CMD gate is removed.

  • 8872d1e: stash init, stash plan, and stash impl no longer crash on a Prisma Next
    project. SKILL_MAP was missing a prisma-next entry, so the skills-install
    and AGENTS.md-builder steps hit SKILL_MAP[integration]undefined and threw
    "not iterable" for any repo the CLI detected as Prisma Next. The entry is added
    and both consumers now resolve skills through a skillsFor() helper that
    degrades an unmapped integration to the base skill set instead of crashing
    (tsup ships without type-checking, so the Record<Integration> type alone
    didn't protect the build).

    Ships a new stash-prisma-next agent skill documenting the EQL v3 Prisma
    Next surface — the domain-named encrypted column types (EncryptedTextSearch,
    EncryptedDoubleOrd, …), cipherstashFromStackV3 wiring, the runtime value
    envelopes, the eql* query operators, and EQL installation via
    prisma-next migration apply. It is installed for Prisma Next projects and
    inlined into AGENTS.md for editor agents.

    stash eql install now refuses to run in a Prisma Next project (pointing you
    at `prisma-next migration app...

Read more

@cipherstash/wizard@1.0.0-rc.3

Pre-release

Choose a tag to compare

@github-actions github-actions released this 19 Jul 11:21
4888995

Patch Changes

  • 8b2551a: Fix "Failed to load native binding" on project-local installs of the CLI/SDK
    (npm). @cipherstash/auth was pinned at 0.41.0 while the six
    @cipherstash/auth-* platform bindings declared in stack/stash/wizard's
    optionalDependencies were pinned at 0.42.0. Because auth pins its bindings as
    exact-version optional peer dependencies, the skew made npm nest per-consumer
    binding copies that the hoisted auth package could not resolve — any command
    or import touching auth then died at startup. All seven packages now move in
    lockstep at 0.42.0, Dependabot is barred from bumping any of them
    independently, and a supply-chain CI test fails on any future skew.

@cipherstash/stack@1.0.0-rc.3

Pre-release

Choose a tag to compare

@github-actions github-actions released this 19 Jul 11:21
4888995

Patch Changes

  • 8b2551a: Fix "Failed to load native binding" on project-local installs of the CLI/SDK
    (npm). @cipherstash/auth was pinned at 0.41.0 while the six
    @cipherstash/auth-* platform bindings declared in stack/stash/wizard's
    optionalDependencies were pinned at 0.42.0. Because auth pins its bindings as
    exact-version optional peer dependencies, the skew made npm nest per-consumer
    binding copies that the hoisted auth package could not resolve — any command
    or import touching auth then died at startup. All seven packages now move in
    lockstep at 0.42.0, Dependabot is barred from bumping any of them
    independently, and a supply-chain CI test fails on any future skew.

@cipherstash/stack-supabase@1.0.0-rc.3

Pre-release

Choose a tag to compare

@github-actions github-actions released this 19 Jul 11:21
4888995

Patch Changes

  • Updated dependencies [8b2551a]
    • @cipherstash/stack@1.0.0-rc.3