Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 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
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
50 changes: 42 additions & 8 deletions .changeset/eql-3-0-5-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,45 @@
---

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
destroys every grant on every object in `eql_v3` / `eql_v3_internal`, along
with anything that depended on them (this is the same mechanism that drops
functional indexes on encrypted columns). **Re-run your grant script after
upgrading.** 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. **With the rename made non-breaking by
the alias, this is the only part of the upgrade that needs action.**

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 @@ -23,11 +50,18 @@ Two artefacts carry the new bundle:
**Action required.** The baseline's bytes — and so its `migrationHash` — have
changed. If your project already has a `migrations/cipherstash/` directory
generated against `@cipherstash/stack-prisma@1.0.0`, delete that directory and
re-run `prisma-next migration plan` (or `migrate`); the seed phase regenerates
it byte-identical to the shipped artefacts. Your database keeps its markers, so
re-run `prisma-next migration plan`; the seed phase regenerates 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
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.
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.
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
48 changes: 40 additions & 8 deletions .github/workflows/bench-eql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,39 @@ on:
branches:
- main
# Repo-root relative, so every entry gained the subtree prefix. Unprefixed,
# `src/**/*.sql` and `tests/sqlx/**/*` match nothing under this repository's
# root and the bench would simply stop running on pushes — silently, since
# a workflow that never triggers reports nothing at all.
# `src/**` and `tests/**` match nothing under this repository's root and the
# bench would simply stop running on pushes — silently, since a workflow
# that never triggers reports nothing at all.
#
# IDENTICAL to `test-eql.yml`'s list, modulo each file naming itself, and
# held that way by `scripts/__tests__/eql-workflow-filters.test.mjs`. The
# bench is a strict subset of that suite's inputs, so a bespoke narrower
# list here is possible and was what the port carried — and it had drifted
# three ways, each of them a change that alters what the bench measures
# while never starting it:
#
# * `src/**/*.sql` missed `src/v3/version.template`, which `tasks/build.sh`
# names in its own `#MISE sources` and `sed`s into `version.sql`.
# * `tests/sqlx/**/*` missed `tests/docker-compose.yml` — the file
# `postgres:up` actually runs, one level above the glob.
# * `mise.toml` was absent entirely, and it defines every `mise run` task
# below plus the toolchain that compiles them.
#
# Two lists that answer the same question are worth more kept equal than
# kept minimal: the cost of over-triggering is one nightly-budget bench run,
# and the cost of under-triggering is a regression that lands and is
# attributed to whatever merges next.
paths:
- ".github/workflows/bench-eql.yml"
- "packages/eql/src/**/*.sql"
- "packages/eql/tests/sqlx/**/*"
- "packages/eql/tasks/**/*"
- ".github/actions/require-cs-secrets/**"
- "packages/eql/src/**"
- "packages/eql/tests/**"
- "packages/eql/tasks/**"
- "packages/eql/crates/**"
- "packages/eql/packages/**"
- "packages/eql/Cargo.toml"
- "packages/eql/Cargo.lock"
- "packages/eql/mise.toml"

schedule:
# 02:00 UTC daily
Expand Down Expand Up @@ -113,7 +135,17 @@ jobs:
CS_WORKSPACE_CRN: ${{ vars.CS_WORKSPACE_CRN }}
CS_CLIENT_ID: ${{ vars.CS_CLIENT_ID }}
CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }}
# `set -euo pipefail` because the workflow-wide `shell: bash {0}` drops
# GitHub's implicit `-eo pipefail`: without it a failing `rustup
# component add` is discarded and only `test:bench` decides the step,
# which then fails several minutes later for a reason that reads as a
# bench regression. Enforced by
# `scripts/__tests__/workflow-run-fail-fast.test.mjs`.
#
# Assignment on its own rather than `export x=$(…)`: errexit ignores
# the substitution's status when the line starts with a command word.
run: |
export active_rust_toolchain=$(rustup show active-toolchain | cut -d' ' -f1)
rustup component add --toolchain ${active_rust_toolchain} rustfmt clippy
set -euo pipefail
active_rust_toolchain=$(rustup show active-toolchain | cut -d' ' -f1)
rustup component add --toolchain "${active_rust_toolchain}" rustfmt clippy
mise run --output prefix test:bench --postgres ${POSTGRES_VERSION}
20 changes: 20 additions & 0 deletions .github/workflows/integration-drizzle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ on:
# Pinned by scripts/__tests__/wasm-build-inputs-paths-filter.test.mjs.
- 'packages/protect-ffi/tsconfig.wasm-errors.json'
- 'packages/protect-ffi/scripts/inline-wasm.mjs'
# Out of that package, and compiled into the same two artifacts: the
# cdylib crate carries
# `eql-bindings = { path = "../../../eql/crates/eql-bindings" }`, so the
# EQL wire types are linked into `index.node` and into
# `protect_ffi_inline.js`. Both cache keys in
# `.github/actions/build-ffi-binding` hash them, so an edit here misses
# that cache and rebuilds the bundle the `integration/wasm/**` suites
# load. Same argument as the two entries above, same guard.
- 'packages/eql/crates/**'
- 'packages/eql/Cargo.toml'
pull_request:
branches: ['**']
# Repeated verbatim: GitHub Actions does not support YAML anchors/aliases.
Expand Down Expand Up @@ -160,6 +170,16 @@ on:
# Pinned by scripts/__tests__/wasm-build-inputs-paths-filter.test.mjs.
- 'packages/protect-ffi/tsconfig.wasm-errors.json'
- 'packages/protect-ffi/scripts/inline-wasm.mjs'
# Out of that package, and compiled into the same two artifacts: the
# cdylib crate carries
# `eql-bindings = { path = "../../../eql/crates/eql-bindings" }`, so the
# EQL wire types are linked into `index.node` and into
# `protect_ffi_inline.js`. Both cache keys in
# `.github/actions/build-ffi-binding` hash them, so an edit here misses
# that cache and rebuilds the bundle the `integration/wasm/**` suites
# load. Same argument as the two entries above, same guard.
- 'packages/eql/crates/**'
- 'packages/eql/Cargo.toml'

jobs:
integration:
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/integration-protect-ffi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ on:
# toolchain pins, and the PG* connection env.
- 'packages/protect-ffi/package.json'
- 'packages/protect-ffi/mise.toml'
# Out of this package, and a real compile input: the cdylib crate carries
# `eql-bindings = { path = "../../../eql/crates/eql-bindings" }`, so the
# EQL wire types are linked into `index.node` AND into the wasm32 build
# this suite's `wasm-round-trip` test loads. Both cache keys in
# `.github/actions/build-ffi-binding` hash these, so an edit misses the
# cache and rebuilds the binding — without these entries it would be
# rebuilt and no suite that loads it would start. Pinned by
# scripts/__tests__/wasm-build-inputs-paths-filter.test.mjs.
- 'packages/eql/crates/**'
- 'packages/eql/Cargo.toml'
- '.github/workflows/integration-protect-ffi.yml'
- '.github/actions/build-ffi-binding/**'
- '.github/actions/require-cs-secrets/**'
Expand All @@ -79,6 +89,16 @@ on:
- 'packages/protect-ffi/tsconfig.wasm-errors.json'
- 'packages/protect-ffi/package.json'
- 'packages/protect-ffi/mise.toml'
# Out of this package, and a real compile input: the cdylib crate carries
# `eql-bindings = { path = "../../../eql/crates/eql-bindings" }`, so the
# EQL wire types are linked into `index.node` AND into the wasm32 build
# this suite's `wasm-round-trip` test loads. Both cache keys in
# `.github/actions/build-ffi-binding` hash these, so an edit misses the
# cache and rebuilds the binding — without these entries it would be
# rebuilt and no suite that loads it would start. Pinned by
# scripts/__tests__/wasm-build-inputs-paths-filter.test.mjs.
- 'packages/eql/crates/**'
- 'packages/eql/Cargo.toml'
- '.github/workflows/integration-protect-ffi.yml'
- '.github/actions/build-ffi-binding/**'
- '.github/actions/require-cs-secrets/**'
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/macro-expand-eql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,31 @@ jobs:
# an unpinned version could drift the snapshot even with a frozen macro +
# nightly. The snapshot then only moves when the macro moves.
- name: Install pinned nightly toolchain
# `set -eu`, deliberately WITHOUT `pipefail`: the workflow-wide
# `shell: bash {0}` drops GitHub's implicit `-eo pipefail`, so errexit
# has to be asked for (see
# `scripts/__tests__/workflow-run-fail-fast.test.mjs`) — but `head -1`
# closes the pipe as soon as it has its line, so under `pipefail` a
# grep that still had output to write reports 141 and fails the step on
# a successful match. Same SIGPIPE hazard as
# `scripts/__tests__/workflow-grep-q-pipelines.test.mjs`. The `test -n`
# below already covers the case this pipeline can genuinely fail in.
run: |
set -eu
NIGHTLY=$(grep -oE 'nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}' mise.toml | head -1)
test -n "$NIGHTLY" || { echo "could not find pinned nightly in mise.toml"; exit 1; }
rustup toolchain install "$NIGHTLY" --profile minimal --component rustfmt

- name: Regenerate and verify the matrix expansion snapshots
# `set -euo pipefail` is load-bearing here, more than anywhere else in
# this file. Without it the workflow-wide `shell: bash {0}` discards the
# regeneration's exit code and the `git diff` below decides the step —
# so when `test:matrix:expand` BREAKS, the snapshots are untouched, the
# diff is clean, and this job reports success having verified that a
# build which never ran produced no drift. Enforced by
# `scripts/__tests__/workflow-run-fail-fast.test.mjs`.
run: |
set -euo pipefail
mise run test:matrix:expand
git diff --exit-code -- \
tests/sqlx/snapshots/integer_expanded.rs \
Expand Down
Loading
Loading