Releases: cipherstash/stack
Release list
stash@1.0.0-rc.4
Patch Changes
-
98156ac: Fix the Codex handoff installing zero skills — and losing
AGENTS.mdand.cipherstash/with them — when.codex/is not writable.Codex sandboxes deny writes under
.codex/.installSkillscreated its destination with an unguardedmkdirSync, 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: noAGENTS.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:
installSkillsnever 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 afailedentry.- 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 intoAGENTS.mdvia the samedoctrine-plus-skillspath 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 staysdoctrine-onlyand 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 indist/bin/— so every published build silently wrote the minimalAGENTS.mdstub instead of the doctrine (and the inline fallback would have inlined nothing). It now lands atdist/doctrine, like the skills bundle.buildAgentsMdBodyalso honoursdoctrine-plus-skillseven when the doctrine fragment is missing, so inlined skills are never dropped with it. - The generated artifacts describe the fallback honestly.
context.jsongains aninlinedSkillsfield, andsetup-prompt.mddistinguishes 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.mdupsert (which refuses malformed sentinel pairs) and the bundled-file reads degrade to warnings instead of aborting the step before.cipherstash/is written.
@cipherstash/wizardcarries its own copy ofinstallSkillswith the same unguardedmkdirSyncabove the same guarded copy loop. It targets.claude/skillsrather 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 implandstash inithanging 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 implgated on an inlineprocess.env.CI !== 'true', which only
recognised the exact lowercase spelling. Runners that setCI=1orCI=TRUE
blocked on the plan-summary confirmation or the agent-target picker.stash init's offer to chain intostash plan, and its Proxy-vs-SDK
question, gated onprocess.stdout.isTTYand did not consultCIat 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-planconfirmed 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 andCI
is not set to1/truein any case), matchingstash plan. Non-interactive
runs take the path they always should have:stash initskips the chain offer
and prints theplan --targethint, the Proxy-vs-SDK question defaults to
SDK-only, andstash implproceeds without prompting. -
c8726cd:
stash init --drizzlenow installs EQL v3 instead of v2.The Drizzle init flow pinned
--eql-version 2, becausestash eql install --drizzle(the only migration-generating install path at the time) was
v2-only. That madestash init --drizzlethe single flow that provisioned a v2
database — a barestash eql install, and init for every other integration,
already defaulted to v3. It also contradicted thestash-drizzleskill 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 viadrizzle-kit migrate) while emitting v3 SQL.
The generated migration also carries thecs_migrationstracking schema, so one
drizzle-kit migratecovers everythingstash encrypt …needs. Ifdrizzle-kit
isn't installed or configured, init now reports EQL as not installed and points
atstash eql migration --drizzlerather 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 initnow 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(),
andencryptedType<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:
EncryptionV3from@cipherstash/stack/v3,
the concrete-domaintypes.*factories (types.TextSearch,types.IntegerOrd,
types.Text,types.Json, …), and the@cipherstash/stack-drizzle/v3entry
(extractEncryptionSchemaV3) for Drizzle. TheencryptionClientexport 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):--nameis 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.--nameis now restricted to letters, numbers, dashes, and underscores, and drizzle-kit is invoked with an argv array instead of a shell string.--outis now actually passed to drizzle-kit. The flag was used to search for the generated migration but never handed todrizzle-kit generate, so any project whosedrizzle.config.tswrites migrations outsidedrizzle/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 differentdrizzle.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-ffi0.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_searchstorage domain and
eql_v3.query_jsonquery 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 equivalenteqlJsonPathEq,eqlJsonPathNeq,eqlJsonPathGt,
eqlJsonPathGte,eqlJsonPathLt, andeqlJsonPathLteoperators. Selector
Selector-basedORDER BYis available as
ops.selector(column, path).asc()/desc()in Drizzle
andeqlJsonPathAsc(column, path)/eqlJsonPathDesc(column, path)in Prisma
Next; both lower toORDER BY eql_v3.ord_termover the selected entry.If you call
encryptQuerywith an explicitqueryType, note that
steVecTermnow produces a scalar JSON ordering term. It no longer means
structural containment; use the recommendedsearchableJsonquery type with
an object or array for containment, orsteVecValueSelectorwith
{ path, value }for exact equa...
@cipherstash/wizard@1.0.0-rc.4
Patch Changes
-
98156ac: Fix the Codex handoff installing zero skills — and losing
AGENTS.mdand.cipherstash/with them — when.codex/is not writable.Codex sandboxes deny writes under
.codex/.installSkillscreated its destination with an unguardedmkdirSync, 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: noAGENTS.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:
installSkillsnever 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 afailedentry.- 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 intoAGENTS.mdvia the samedoctrine-plus-skillspath 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 staysdoctrine-onlyand 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 indist/bin/— so every published build silently wrote the minimalAGENTS.mdstub instead of the doctrine (and the inline fallback would have inlined nothing). It now lands atdist/doctrine, like the skills bundle.buildAgentsMdBodyalso honoursdoctrine-plus-skillseven when the doctrine fragment is missing, so inlined skills are never dropped with it. - The generated artifacts describe the fallback honestly.
context.jsongains aninlinedSkillsfield, andsetup-prompt.mddistinguishes 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.mdupsert (which refuses malformed sentinel pairs) and the bundled-file reads degrade to warnings instead of aborting the step before.cipherstash/is written.
@cipherstash/wizardcarries its own copy ofinstallSkillswith the same unguardedmkdirSyncabove the same guarded copy loop. It targets.claude/skillsrather 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
Minor Changes
-
cf2c57c: Upgrade Stack to
@cipherstash/protect-ffi0.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_searchstorage domain and
eql_v3.query_jsonquery 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 equivalenteqlJsonPathEq,eqlJsonPathNeq,eqlJsonPathGt,
eqlJsonPathGte,eqlJsonPathLt, andeqlJsonPathLteoperators. Selector
Selector-basedORDER BYis available as
ops.selector(column, path).asc()/desc()in Drizzle
andeqlJsonPathAsc(column, path)/eqlJsonPathDesc(column, path)in Prisma
Next; both lower toORDER BY eql_v3.ord_termover the selected entry.If you call
encryptQuerywith an explicitqueryType, note that
steVecTermnow produces a scalar JSON ordering term. It no longer means
structural containment; use the recommendedsearchableJsonquery type with
an object or array for containment, orsteVecValueSelectorwith
{ 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 v2searchableJson()schemas are rejected during client setup
because the old selector envelope can no longer be emitted; migrate them to the
v3types.Jsondomain.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(), encryptedcontains(), andselectorEq()/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 aResult—{ data } | { failure }— instead of throwing. AndbulkEncrypt/bulkDecryptare added, so a list of encrypted rows costs one ZeroKMS round trip instead of one per row.Result alignment
encrypt,decrypt,encryptQueryandencryptQueryBulkpreviously threw on failure, and returned bare values on success. They now return{ data } | { failure }, withfailure.typedrawn fromEncryptionErrorTypes(EncryptionErrorfor encrypt-side operations,DecryptionErrorfor decrypt-side) andfailure.codecarrying 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.mdstates outright: "Operations return{ data }or{ failure }. Preserve this shape and errortypevalues inEncryptionErrorTypes." The WASM entry never followed it. That was drift rather than a design decision — nothing about WASM prevents it (@byteslice/resultis already bundled intodist/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@latestis still0.19.0, so this surface has only ever been published under therctag. After GA it would have had to wait for a major.isEncryptedis 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/undefinedentries yieldnullat 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 singlebulkEncryptcan 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.bulkDecryptbuilds on the fallible FFI primitive, so when items fail thefailure.messagenames every failing index with its reason, rather than surfacing the first and discarding the rest.The model helpers (
encryptModel/decryptModeland 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
Patch Changes
-
cf2c57c: Upgrade Stack to
@cipherstash/protect-ffi0.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_searchstorage domain and
eql_v3.query_jsonquery 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 equivalenteqlJsonPathEq,eqlJsonPathNeq,eqlJsonPathGt,
eqlJsonPathGte,eqlJsonPathLt, andeqlJsonPathLteoperators. Selector
Selector-basedORDER BYis available as
ops.selector(column, path).asc()/desc()in Drizzle
andeqlJsonPathAsc(column, path)/eqlJsonPathDesc(column, path)in Prisma
Next; both lower toORDER BY eql_v3.ord_termover the selected entry.If you call
encryptQuerywith an explicitqueryType, note that
steVecTermnow produces a scalar JSON ordering term. It no longer means
structural containment; use the recommendedsearchableJsonquery type with
an object or array for containment, orsteVecValueSelectorwith
{ 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 v2searchableJson()schemas are rejected during client setup
because the old selector envelope can no longer be emitted; migrate them to the
v3types.Jsondomain.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(), encryptedcontains(), andselectorEq()/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
Minor Changes
-
cf2c57c: Upgrade Stack to
@cipherstash/protect-ffi0.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_searchstorage domain and
eql_v3.query_jsonquery 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 equivalenteqlJsonPathEq,eqlJsonPathNeq,eqlJsonPathGt,
eqlJsonPathGte,eqlJsonPathLt, andeqlJsonPathLteoperators. Selector
Selector-basedORDER BYis available as
ops.selector(column, path).asc()/desc()in Drizzle
andeqlJsonPathAsc(column, path)/eqlJsonPathDesc(column, path)in Prisma
Next; both lower toORDER BY eql_v3.ord_termover the selected entry.If you call
encryptQuerywith an explicitqueryType, note that
steVecTermnow produces a scalar JSON ordering term. It no longer means
structural containment; use the recommendedsearchableJsonquery type with
an object or array for containment, orsteVecValueSelectorwith
{ 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 v2searchableJson()schemas are rejected during client setup
because the old selector envelope can no longer be emitted; migrate them to the
v3types.Jsondomain.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(), encryptedcontains(), andselectorEq()/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
@cipherstash/prisma-next@1.0.0-rc.4
Minor Changes
-
2e6f032: Make
@cipherstash/prisma-nextEQL 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'stostate 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 retiredeql_v2_configurationtable 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 applynow
installs EQL v3 exclusively and works on Supabase as a non-superuser.Breaking — API: the EQL v2 authoring/runtime surface is removed:
cipherstashFromStackV2,deriveStackSchemas, andcreateCipherstashSdk
(from./stack) — usecipherstashFromStack(v3).- The
encrypted*V2TS column factories and thecipherstash.Encrypted*V2PSL
constructors (from./column-types) — use the v3 domain factories/constructors
(text/textSearch/bigIntOrd/… andcipherstash.TextSearch()etc.). - The v2 runtime codecs,
createCipherstashRuntimeDescriptor, thecipherstash*
query operators/helpers, and theEncryptedDoubleenvelope (from./runtime)
— use the v3 runtime (createCipherstashV3RuntimeDescriptor,
bulkEncryptMiddlewareV3, theeql*operators,EncryptedNumber). The
version-neutral envelopes (EncryptedString/BigInt/Boolean/Date/Json)
anddecryptAllare unchanged. - The
./middlewareand./migrationsubpath exports are removed (the v2
bulk-encrypt middleware and call-classes). UsebulkEncryptMiddlewareV3from
./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_FAILEDand 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-ffi0.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_searchstorage domain and
eql_v3.query_jsonquery 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 equivalenteqlJsonPathEq,eqlJsonPathNeq,eqlJsonPathGt,
eqlJsonPathGte,eqlJsonPathLt, andeqlJsonPathLteoperators. Selector
Selector-basedORDER BYis available as
ops.selector(column, path).asc()/desc()in Drizzle
andeqlJsonPathAsc(column, path)/eqlJsonPathDesc(column, path)in Prisma
Next; both lower toORDER BY eql_v3.ord_termover the selected entry.If you call
encryptQuerywith an explicitqueryType, note that
steVecTermnow produces a scalar JSON ordering term. It no longer means
structural containment; use the recommendedsearchableJsonquery type with
an object or array for containment, orsteVecValueSelectorwith
{ 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 v2searchableJson()schemas are rejected during client setup
because the old selector envelope can no longer be emitted; migrate them to the
v3types.Jsondomain.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(), encryptedcontains(), andselectorEq()/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
stash@1.0.0-rc.3
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
thecs_migrationstracking schema, so a singledrizzle-kit migratecovers
everythingstash encrypt …needs.--supabaseappends theeql_v3+
eql_v3_internalrole grants for PostgREST/RLS access.--prismais 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 initis 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 withdrizzle-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.
- Fails on version skew. A non-interactive run can't reconcile an
-
3a86939: EQL v3 support for the encryption rollout lifecycle (#648). The
stash encrypt *commands (and@cipherstash/migrateunderneath) 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 backfillworks on v3 columns unchanged (the engine was always
version-agnostic; pass anEncryptionV3client and real v3 envelopes land
in the concreteeql_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 cutoveron 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 noeql_v2_configurationtable (a v3-only install) the
v2 path now explains that instead of surfacing a raw Postgres error.encrypt dropis version-aware: v3 runs from thebackfilledphase,
verifies live coverage (refuses to generate the migration while any row
still has the plaintext set and the encrypted column NULL — the
countUnencryptedcheck), and drops the ORIGINAL plaintext column (there
is no<col>_plaintextunder 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 recordedencryptedColumnor 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 doescutover.encrypt statusclassifies each column from the observed domain type
(manifest as fallback), showsv3in the EQL column, and no longer raises
the v2-onlynot-registered/plaintext-col-missingdrift flags for v3
columns.stash status's quest ladder and thestash initagent handoff
prompt teach the version-appropriate next step (no more "run cutover" on
v3 columns).- New
@cipherstash/migrateexports:classifyEqlDomain,
resolveEncryptedColumn,pickEncryptedColumn,listEncryptedColumns
(domain-type resolution — case-exact for quoted/mixed-case table names),
countEncrypted/countUnencrypted(coverage counts), and manifest
eqlVersion+encryptedColumnfields.EqlVersionis numeric (2 | 3),
matching the manifest and the installer. Resolved columns carryvia: 'hint' | 'convention' | 'sole'so callers can tell a positively asserted
pairing from a by-elimination guess. - Fixed:
encrypt cutover/encrypt dropprecondition 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-cliandstash-encryptionskills and the@cipherstash/migrate
README document the two lifecycles (v2: backfill → cutover → drop;
v3: backfill → switch-by-name → drop). -
b0634df:
stash plan --complete-rolloutis 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
--yesflag confirms the gate-skip without a prompt (for CI/agents). - Without
--yes, a non-interactive--complete-rolloutrun now refuses
with a non-zero exit and points at--yes, instead of silently succeeding. - Interactive behaviour is unchanged (default-no confirm).
- New
-
f188c7a:
stash envnow 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 emitsCS_WORKSPACE_CRN,
CS_CLIENT_ID,CS_CLIENT_KEY, andCS_CLIENT_ACCESS_KEY.Output goes to stdout by default — and stdout is pipe-clean (progress UI is on
stderr), sostash env --name x > prod.envand 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.--jsonemits NDJSON; with
--writethe confirmation event is deliberately secret-free. API responses
are schema-validated so a service change can never printundefinedinto 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~/.cipherstashdevice profile: mint a key and feed it via
supabase functions serve --env-fileor the platform's secret store.The
STASH_EXPERIMENTAL_ENV_CMDgate is removed. -
8872d1e:
stash init,stash plan, andstash implno longer crash on a Prisma Next
project.SKILL_MAPwas missing aprisma-nextentry, so the skills-install
and AGENTS.md-builder steps hitSKILL_MAP[integration]→undefinedand threw
"not iterable" for any repo the CLI detected as Prisma Next. The entry is added
and both consumers now resolve skills through askillsFor()helper that
degrades an unmapped integration to the base skill set instead of crashing
(tsupships without type-checking, so theRecord<Integration>type alone
didn't protect the build).Ships a new
stash-prisma-nextagent skill documenting the EQL v3 Prisma
Next surface — the domain-named encrypted column types (EncryptedTextSearch,
EncryptedDoubleOrd, …),cipherstashFromStackV3wiring, the runtime value
envelopes, theeql*query operators, and EQL installation via
prisma-next migration apply. It is installed for Prisma Next projects and
inlined intoAGENTS.mdfor editor agents.stash eql installnow refuses to run in a Prisma Next project (pointing you
at `prisma-next migration app...
@cipherstash/wizard@1.0.0-rc.3
Patch Changes
- 8b2551a: Fix "Failed to load native binding" on project-local installs of the CLI/SDK
(npm).@cipherstash/authwas 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 hoistedauthpackage 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
Patch Changes
- 8b2551a: Fix "Failed to load native binding" on project-local installs of the CLI/SDK
(npm).@cipherstash/authwas 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 hoistedauthpackage 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.