Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9022e16
build(deps-dev): bump js-yaml from 4.3.0 to 4.3.1
dependabot[bot] Aug 10, 2026
142f41d
fix(json): restore deprecated ste_vec_contains aliases
freshtonic Aug 17, 2026
600a9b2
test(json): use encrypted fixture for legacy aliases
freshtonic Aug 17, 2026
35bce9e
fix(ci): register restored alias coverage
freshtonic Aug 17, 2026
74106c7
Merge pull request #435 from cipherstash/dependabot/npm_and_yarn/js-y…
tobyhede Aug 17, 2026
313eac0
Merge pull request #437 from cipherstash/fix/restore-ste-vec-contains…
freshtonic Aug 17, 2026
c1057af
fix(deps): patch postcss to 8.5.26
tobyhede Aug 17, 2026
4b50483
fix(deps): bump jsonwebtoken to 10.4.0 via stack-auth 0.42.2
tobyhede Aug 18, 2026
6ba124f
fix(deps): keep winapi-util on windows-sys 0.61.2
tobyhede Aug 18, 2026
51732f3
Merge pull request #438 from cipherstash/fix/cip-3807-postcss-patch
tobyhede Aug 18, 2026
2759a17
Merge pull request #439 from cipherstash/fix/cip-3809-jsonwebtoken-patch
tobyhede Aug 18, 2026
4b2d859
Version Packages
github-actions[bot] Aug 18, 2026
91ebf28
Merge pull request #434 from cipherstash/changeset-release/main
tobyhede Aug 19, 2026
4098a1f
fix(release): make the version hook runnable, and refuse to half-publish
tobyhede Aug 19, 2026
ca2b0a7
ci(eql): hash the new compile input, and give the PG matrix a trigger…
tobyhede Aug 19, 2026
fd8c40b
test(eql): the cargo-task guard was reading 32 of 73 tasks
tobyhede Aug 19, 2026
3b38d88
fix(eql): close three holes in the registry-pin linter, and record th…
tobyhede Aug 19, 2026
810b069
docs(eql): correct the 3.0.5 blast radius, and cover the stale vendor…
tobyhede Aug 19, 2026
a4cadd2
fix(deps): four security overrides had been overtaken by their own fixes
tobyhede Aug 19, 2026
88c21c9
chore(eql): sync subtree to upstream 91ebf28d (3.0.5 release)
tobyhede Aug 19, 2026
10fca72
fix(biome): align the config schema with the version actually installed
tobyhede Aug 19, 2026
2a140e8
fix(eql): re-emit against the published 3.0.5, which kept the old name
tobyhede Aug 19, 2026
e266775
fix(release): drop --offline, which the release job's cold registry c…
tobyhede Aug 19, 2026
93f971a
fix(eql): the doxygen-leak guard could only ever report OK
tobyhede Aug 19, 2026
9cec92e
docs: stop telling agents the release gate is currently blocking
tobyhede Aug 19, 2026
00d685f
fix(skills): stash-prisma never carried the post-upgrade re-plan
tobyhede Aug 19, 2026
2e98298
fix(release): close the audit gaps from the #885 and #915 reviews
tobyhede Aug 20, 2026
0d98727
Merge feat/eql-1-vendor into feat/eql-2-review-fixes
tobyhede Aug 20, 2026
e4ddc2d
fix(release): the gate could not tell our 3.0.5 from npm's 3.0.5
tobyhede Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 58 additions & 6 deletions .changeset/eql-3-0-5-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,63 @@
---

Move the bundled EQL v3 migrations to **eql-3.0.5**, which renames the SQL
function `eql_v3.ste_vec_contains` to `eql_v3.jsonb_document_contains`. The
operators are unchanged (`@>` / `<@` on `public.eql_v3_json_search` behave
exactly as before) — only callers that invoke the function **by name** are
affected, which in practice means Supabase / PostgREST, since PostgREST calls
functions rather than operators.
function `eql_v3.ste_vec_contains` to `eql_v3.jsonb_document_contains`.

**The blast radius is narrower than a renamed public function suggests.** The
`@>` / `<@` operators on `public.eql_v3_json_search` behave exactly as before,
and so do the two function-form entry points that exist for platforms without
operator support — `eql_v3.jsonb_contains(jsonb, jsonb)` and
`eql_v3.jsonb_contained_by(jsonb, jsonb)` are byte-identical to 3.0.4. Those
are what a PostgREST caller invokes, so PostgREST callers on the documented
surface are **not** affected. The renamed function is the typed implementation
those operators dispatch into.

**And the old name still works.** eql-3.0.5 ships `eql_v3.ste_vec_contains` as
a deprecated delegating alias for both overloads, so hand-written SQL naming it
— an application query, a view, an RLS policy, or a per-function
`GRANT EXECUTE ON FUNCTION eql_v3.ste_vec_contains(…)` — keeps resolving. The
typed overload stays inlinable, so a function-form query through the alias
still matches the same functional GIN index. Migrate to
`jsonb_document_contains` when convenient; nothing forces it at upgrade time.

**Separately — and true of every EQL upgrade, not just this one:** the install
bundle opens with `DROP SCHEMA IF EXISTS eql_v3 CASCADE`, so applying it drops
every object in `eql_v3` / `eql_v3_internal` and everything that depended on
them. **Encrypted data and column types are not affected** — the storage
domains are `public.eql_v3_*`, deliberately outside both dropped schemas, and
their CHECK functions are re-created rather than dropped. What does not survive
is everything else pointing into the schema, which is two actions, neither of
them to do with the rename:

1. **Re-run your grant script.** Every grant on every `eql_v3` /
`eql_v3_internal` object is gone. The schema-wide form EQL documents —
`GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA eql_v3 TO app_role` — picks up
both the new name and the alias on its own.
2. **Recreate your functional indexes, then `ANALYZE`.** Indexes over
`eql_v3.eq_term(…)` / `ord_term` / `match_term` / `to_ste_vec_query(…)`
depend on the dropped schema and go with it. Nothing errors afterwards:
encrypted predicates keep working and silently fall back to sequential
scans. A migration runner will not redo an already-applied migration, so
this has to be a *new* one. The `stash-indexing` skill documents the
mechanism ("These indexes do not survive an EQL reinstall or upgrade") and
the `EXPLAIN` check that confirms recovery; capturing and restoring them
automatically is tracked in
[cipherstash/stack#918](https://github.com/cipherstash/stack/issues/918).

Any RLS policy, view, or constraint that calls an `eql_v3` function is dropped
by the same CASCADE and needs recreating too. **The rename itself needs no
action — the alias makes it non-breaking.**

Two artefacts carry the new bundle:

- A new upgrade edge, `20260814T0000_upgrade_eql_v3_3_0_5`, carrying the
invariant `cipherstash:upgrade-eql-v3-bundle-3.0.5-v1`. Databases already
running an earlier bundle re-install through this edge on the next
`prisma-next migrate`, exactly as they did for 3.0.2 and 3.0.4.
`prisma-next migration plan` followed by `prisma-next migrate`, exactly as
they did for 3.0.2 and 3.0.4. **`migrate` alone is not enough** — the seed
phase that copies a new migration package into your repo runs only from
`migration plan`, so without it the 3.0.5 directory never reaches disk and
`migrate` is a silent no-op that leaves the database on the older bundle.
- The baseline install migration `20260601T0100_install_eql_v3_bundle`, whose
baked bundle moves to 3.0.5 and which gains a fourth no-SQL carrier op for
the new invariant. Fresh databases therefore land on 3.0.5 from the single
Expand All @@ -30,6 +75,13 @@ it byte-identical to the shipped artefacts. Your database keeps its markers, so
already-applied invariants are not re-run — the only new work is the 3.0.5
upgrade edge.

If you skip the delete, nothing warns you: a vendored baseline is stale but
internally intact, so it passes every integrity check. On an existing database
the upgrade still applies correctly; on a **fresh** one, `db init` refuses with
`Operation cipherstash.upgrade-eql-v3-bundle-3.0.5 has class "data" which is
not allowed by policy.` — an error that names neither the directory nor the
remedy. See "Upgrading from 1.0.0" in the package README.

**Why the baseline was re-emitted rather than left frozen.** These artefacts are
content-addressed and normally append-only: an EQL bump ships as a new upgrade
directory and published directories are never rewritten. That rule cannot be
Expand Down
41 changes: 41 additions & 0 deletions .changeset/eql-exact-runtime-pin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
'@cipherstash/stack-prisma': patch
'stash': patch
---

Pin the packed `@cipherstash/eql` dependency to an exact version, closing a
route by which an installed EQL bundle could drift ahead of the code built
against it.

Both packages declared `"@cipherstash/eql": "workspace:^"` under
`dependencies`. In this workspace that resolves in-tree either way, so nothing
in development or CI could see a difference — but the two specifiers do not
pack the same. pnpm rewrites the protocol when it builds the tarball a customer
actually installs:

"workspace:^" packs as "^3.0.5"
"workspace:*" packs as "3.0.5"

The caret is the problem. `@cipherstash/eql` is still published from
`cipherstash/encrypt-query-language` until the publisher repoint, so a 3.0.x can
reach npm without passing through this repository at all — and `^3.0.5` accepts
it. A customer installing `stash` or `@cipherstash/stack-prisma` would then get
SQL that STORES and queries encrypted payloads at one version, while
`@cipherstash/stack`'s v3 domain types (which EMIT those payloads) and
`stack-prisma`'s baked migrations stayed frozen at the version this repo built
and tested against. The two halves of EQL are released in lockstep precisely
because that skew does not fail at install or in CI — it fails in a database.

`workspace:*` is the only form that closes it. A literal `"3.0.5"` would be an
exact pin too, but it is a registry pin: `pnpm run lint:eql-pins` rejects it,
because resolving EQL from a registry rather than from this repo is the same
drift one layer up.

No API, behaviour or SQL changes. What changes is the dependency range in the
published tarballs, and only in the narrowing direction — the version resolved
today is the version that was already being resolved. Nothing needs to be done
on upgrade.

`@cipherstash/stack` declares the same dependency under `devDependencies` and
is deliberately left alone: pnpm rewrites that range too, but no consumer of the
package ever resolves it.
16 changes: 16 additions & 0 deletions .changeset/prisma-skill-upgrade-replan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'stash': patch
---

`skills/stash-prisma` now documents the re-plan step that follows an
`@cipherstash/stack-prisma` upgrade: `rm -rf migrations/cipherstash && npx
prisma-next migration plan`, why only `migration plan` vendors new migration
packages, and the exact `db init` refusal a stale vendored directory produces on
a fresh database (`Operation cipherstash.upgrade-eql-v3-bundle-3.0.5 has class
"data" which is not allowed by policy.`).

The package README already carried this; the skill did not — and the skill is
what ships inside the `stash` tarball and gets copied into a user's
`.claude/skills/`, so an agent driving the upgrade hit the refusal with no route
out of it. `packages/stack-prisma/test/v3/stale-vendored-space.test.ts` now pins
both files to the planner's real message so they cannot drift apart again.
35 changes: 35 additions & 0 deletions .changeset/protect-ffi-eql-bindings-in-tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
'@cipherstash/protect-ffi': patch
---

Compile `eql-bindings` from this repository rather than from crates.io.

The native binding pinned `eql-bindings = "=3.0.2"` from the registry. It now
resolves by path from `packages/eql/crates/eql-bindings`, which ships at 3.0.5
alongside the `@cipherstash/eql` SQL bundle.

**No behaviour change.** `eql-bindings` is the Rust half of EQL — it EMITS the
encrypted payloads that the SQL half STORES and queries — and its Rust source is
byte-identical across 3.0.2, 3.0.4 and 3.0.5 (`src/`, `bindings/` and `schema/`
compared directly). What 3.0.3 through 3.0.5 changed was SQL, carried on the
shared lockstep version number. So the payloads this binding produces are the
same bytes before and after; what moves is the version stamped on the crate
compiled into `index.node`, from 3.0.2 to 3.0.5.

**Why it is worth a release anyway.** A registry pin let the two halves of EQL
drift apart silently. Nothing asserted they agreed: a mismatched pair compiles,
passes every suite, and fails in a database — because the failure is a payload
the installed SQL cannot read, which no unit test holds both sides of. Resolving
from the tree makes the skew unrepresentable: the emitter and the SQL are now
the same commit, and `pnpm run lint:eql-pins` fails any change that reintroduces
a registry pin on either.

The flip was taken while it was a no-op deliberately. Waiting for the first
release where the two halves genuinely diverge would have turned a provenance
change into a behaviour change that had to be argued under credentialed test.

Verified without credentials: `cargo build -p protect-ffi` clean, the crate test
suite green (310 passed) with `cargo fmt --check` clean, and a
`wasm32-unknown-unknown` build clean — the last of those being the target where a
cross-workspace path dependency would break first, since the EQL workspace never
otherwise builds for wasm32.
37 changes: 37 additions & 0 deletions .changeset/skills-eql-source-and-index-cascade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
'stash': patch
---

Correct two things the bundled agent skills were telling customers wrongly
about EQL.

**`skills/stash-postgres` pointed at the wrong repository.** EQL's source now
lives in `cipherstash/stack` under `packages/eql/`, and that is where operator
gaps and domain-level bugs are filed; only *publishing* still happens from
`cipherstash/encrypt-query-language`, which the skill continues to say. The
skill also cited "the EQL skill" as a source of truth that "ships from
`encrypt-query-language` alongside the bundle" — no such skill ships from
either repository, so the reference is gone and the remaining three sources
(the generated types, the install SQL, and `SELECT eql_v3.version()`) are
renumbered.

**And it claimed the CLI pins an exact `@cipherstash/eql` version, "so a
database is only ever on one bundle."** Neither half holds: the CLI depends on
the workspace package rather than a pinned literal, and a database is on
whatever bundle was last applied to it — the Prisma Next adapter installs and
upgrades the bundle through its own migrations without involving the CLI at
all. Replaced with the guarantee that does hold: one `stash` release carries
one resolved bundle, and the database is the authority on which bundle it has.

**`skills/stash-prisma` hands out the functional-index recipe without saying an
EQL upgrade destroys it.** Installing a bundle begins with `DROP SCHEMA IF
EXISTS eql_v3 CASCADE`, which cascade-drops every index over an `eql_v3.*`
extractor — the PSL expression indexes Prisma Next 0.17 introduced and any
`rawSql` index DDL alike; queries keep working and silently sequential-scan.
Because an applied migration is never replayed, recovery is a NEW one: a PSL
expression index has to change its `name:` (the physical name carries a content
hash of the expression, so re-declaring the same one plans no work), and a
`rawSql` recovery op needs a new `id`. Said where the recipe is given, pointing
at `stash-indexing` for the mechanism and at
[cipherstash/stack#918](https://github.com/cipherstash/stack/issues/918) for
capturing and restoring them automatically.
16 changes: 16 additions & 0 deletions .changeset/stack-prisma-upgrade-from-1-0-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@cipherstash/stack-prisma': patch
---

Document the 1.0.0 → 3.0.5 upgrade in the package README: why
`migrations/cipherstash/` must be deleted and regenerated, what each Prisma Next
command does if it is not, and the exact `db init` refusal
(`Operation cipherstash.upgrade-eql-v3-bundle-3.0.5 has class "data" which is
not allowed by policy.`) that a stale vendored directory produces on a fresh
database.

The behaviour worth knowing regardless of version: only `prisma-next migration
plan` copies new migration packages into your repo. Running `migrate` or
`db init` after upgrading this package without planning first silently leaves
the database on the older EQL bundle — a stale vendored directory is internally
intact, so it passes every integrity check and nothing reports a problem.
6 changes: 4 additions & 2 deletions .changeset/supabase-skill-eql-305.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
Update the bundled agent skills for eql-3.0.5. `skills/stash-supabase`
re-states the PostgREST query-domain limitations against 3.0.5 (unchanged in
substance — the typed `eql_v3.query_*` operand requirement still stands), and
`skills/stash-postgres` drops a claim that the CLI pins `@cipherstash/eql` to
an exact version, which stopped being true when EQL moved in-tree.
`skills/stash-postgres` drops one of the two places it claimed the CLI pins
`@cipherstash/eql` to an exact version — a claim that stopped being true when
EQL moved in-tree. The second copy goes in the same release, with the rest of
that skill's EQL source and issue pointers.
39 changes: 38 additions & 1 deletion .github/actions/build-ffi-binding/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,41 @@ runs:
# package.json, and mise.toml pins the toolchain that compiles it.
# Edit either and the Rust hash is unchanged, so the cache hit skips
# the build step below and the job proceeds on a stale index.node.
#
# The last two are NOT in this package. `crates/protect-ffi/Cargo.toml`
# carries `eql-bindings = { path = "../../../eql/crates/eql-bindings" }`
# — an in-tree path dependency that compiles into `index.node` and into
# the wasm build below. A path dep has no registry checksum, so a
# source-only edit there moves NOTHING this key would otherwise hash:
# not `crates/**`, not either manifest, and not `Cargo.lock`, which
# records the crate by name and version and only moves when the version
# does. Every glob hashes identically, the restore hits, the build below
# is skipped, and every credentialed job runs a stale binding. (This is
# observed, not theoretical: the crate went 3.0.4 -> 4.0.0 -> 3.0.5
# across three commits and neither key here changed by a byte.)
#
# `crates/**` rather than the one crate, deliberately. `eql-bindings`
# has no path deps of its own TODAY, so the narrower glob would be exact
# — and would silently stop being exact the moment it gains one on a
# sibling in that workspace, which is the same hole one level down. The
# cost of the wider glob is a spurious miss when an unrelated EQL crate
# changes; the cost of the narrower one is a stale binary. Guarded by
# `scripts/__tests__/ffi-binding-action.test.mjs`, which discovers the
# path deps from the manifest rather than trusting this comment.
#
# The EQL workspace root manifest comes with it: cargo reads it for the
# crate's workspace context, so a `<key>.workspace = true` added to
# eql-bindings (three of its four siblings already inherit `[lints]`
# that way) would make it a compile input with no other trace.
key: >-
ffi-native-${{ runner.os }}-${{ runner.arch }}-${{
hashFiles('packages/protect-ffi/crates/**',
'packages/protect-ffi/Cargo.toml',
'packages/protect-ffi/Cargo.lock',
'packages/protect-ffi/package.json',
'packages/protect-ffi/mise.toml') }}
'packages/protect-ffi/mise.toml',
'packages/eql/crates/**',
'packages/eql/Cargo.toml') }}

# Always, hit or miss: `lib/` is `tsc` over `src/`, which is seconds, and
# it tracks a different input set than the cached binary.
Expand Down Expand Up @@ -136,11 +164,20 @@ runs:
# under src/ are the eql-v3-types declarations, which that tsconfig
# never reads. ffi-binding-action.test.mjs walks the tsconfig's imports
# and fails if this stops being the complete input set.
#
# `packages/eql/**` is here for the reason spelled out on the native key
# above, and it is NOT redundant with it: wasm-pack compiles the same
# `eql-bindings` path dependency for wasm32 into
# `protect_ffi_inline.js`, which is the bundle
# `@cipherstash/stack/wasm-inline` imports. Two keys, two archives, the
# same missing input.
key: >-
ffi-wasm-${{ runner.os }}-${{
hashFiles('packages/protect-ffi/crates/**',
'packages/protect-ffi/Cargo.toml',
'packages/protect-ffi/Cargo.lock',
'packages/eql/crates/**',
'packages/eql/Cargo.toml',
'packages/protect-ffi/dist/wasm/*.d.ts',
'packages/protect-ffi/src/errors.ts',
'packages/protect-ffi/package.json',
Expand Down
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ updates:
update-types:
- version-update:semver-major

# ── Cargo (packages/protect-ffi — the only Rust in the repo) ────
# ── Cargo (packages/protect-ffi — one of two Rust workspaces) ───
# Absorbing protect-ffi brought a 494-crate Cargo.lock in-tree. osv-scanner
# already sees it — `--recursive ./` walks the tree and extracts every
# lockfile it recognises — so known advisories were visible from day one, but
Expand Down
Loading
Loading