Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
a65856d
docs(stack): design spec for eql_v3 text_search schema DSL (increment 1)
tobyhede Jun 30, 2026
73c1ab3
docs(stack): implementation plan for eql_v3 text_search schema DSL
tobyhede Jun 30, 2026
71e0198
docs(stack): incorporate plan review feedback for eql_v3 text_search
tobyhede Jun 30, 2026
35bb46c
docs(stack): scope v3 to working client integration + apply batch-2 r…
tobyhede Jun 30, 2026
a822660
docs(stack): apply batch-3 plan review (widen internal consumers, sco…
tobyhede Jun 30, 2026
445be40
docs(stack): split query column contract so encryptQuery rejects non-…
tobyhede Jun 30, 2026
1713caf
docs(stack): pin bulk-encrypt widen-site + note WASM v3 boundary
tobyhede Jun 30, 2026
840dc66
docs(stack): pin concrete BuiltMatchIndexOpts type for v3 match opts
tobyhede Jun 30, 2026
d8d659e
docs(stack): fix @ts-expect-error placement in negative type tests
tobyhede Jun 30, 2026
2fac5c8
feat(stack): add eql_v3 text_search column builder
tobyhede Jun 30, 2026
aaa88c9
feat(stack): add eql_v3 encryptedTable and buildEncryptConfig
tobyhede Jun 30, 2026
888c170
feat(stack): wire @cipherstash/stack/schema/v3 export subpath
tobyhede Jun 30, 2026
7a32262
test(stack): type-level tests for eql_v3 schema DSL + scoped CI typec…
tobyhede Jun 30, 2026
8c221d5
feat(stack): widen public client types so v3 builders work with the c…
tobyhede Jun 30, 2026
1aa9a11
chore(stack): changeset for eql_v3 text_search DSL (minor)
tobyhede Jun 30, 2026
656841f
style(stack): biome format v3 tests + drop now-unused EncryptedTable …
tobyhede Jun 30, 2026
28b3cfc
fix(stack): guard v3 encryptedTable against reserved column names
tobyhede Jun 30, 2026
4fad589
fix(stack): resolve column name structurally in wasm-inline encrypt e…
tobyhede Jun 30, 2026
8721ff7
feat(stack): guard v3 buildEncryptConfig against duplicate table names
tobyhede Jun 30, 2026
4515807
feat(stack): extend v3 reserved-column guard to inherited prototype keys
tobyhede Jun 30, 2026
ece1b7e
docs(stack): note text_search defaults to equality, needs queryType:'…
tobyhede Jun 30, 2026
359dd55
test(stack): cover eql v3 typed schema domains
tobyhede Jul 1, 2026
cae3eb7
feat(stack): add eql v3 domain builders for all generated SQL domains
tobyhede Jul 1, 2026
7f646bb
feat(stack): support v3 tables and Date/bigint in client encryption
tobyhede Jul 1, 2026
282c462
test(stack): stabilize v3 client, pg, and wasm-inline coverage
tobyhede Jul 1, 2026
b8a3d20
chore(stack): add changeset for eql v3 typed schema
tobyhede Jul 1, 2026
db2840f
docs: add eql v3 typed schema plan and query API walkthrough
tobyhede Jul 1, 2026
d142d9e
fix(stack): address PR review feedback for eql v3 typed schema
tobyhede Jul 1, 2026
af2d04e
feat(stack): strongly-typed EQL v3 client surface (@cipherstash/stack…
tobyhede Jul 1, 2026
ba7f467
fix(stack): use string plaintext for eql v3 int8 domains
tobyhede Jul 1, 2026
430f487
fix(stack): address code review findings for eql v3 typed client
tobyhede Jul 1, 2026
b25bf4a
fix(stack): key v3 encrypt config by DB column name, not JS property
tobyhede Jul 1, 2026
ebf2c74
fix(stack): match v3 model fields by JS property, encrypt by DB name
tobyhede Jul 1, 2026
64a1def
ci(stack): add blocking FTA complexity gate for EQL v3
tobyhede Jul 1, 2026
39ac793
test(stack): type-driven v3 domain test matrix
tobyhede Jul 2, 2026
3b01be1
feat(stack): equality-via-ORE fix + live v3 domain coverage
tobyhede Jul 2, 2026
be6af18
test(stack): live Postgres SQL coverage for all 35 v3 domains
tobyhede Jul 2, 2026
b0f4f4d
fix(stack): address CodeRabbit review findings on eql v3 typed client
tobyhede Jul 2, 2026
45782f0
test(stack): harden v3 CJS export check and preserve run() transcript…
tobyhede Jul 2, 2026
dfb2890
test(stack): disable timestamptz and matrix-live-pg tests failing on CI
tobyhede Jul 2, 2026
53cf854
fix(stack): wrap ciphertext params with sql.json() in matrix-live-pg
tobyhede Jul 2, 2026
5ccf13d
Merge pull request #540 from cipherstash/feat/eql-v3-test-matrix
tobyhede Jul 3, 2026
0d9a107
docs(stack): design for Stryker v3 blocking CI gate
tobyhede Jul 2, 2026
5e228ce
docs(stack): confirm lean, single-workflow Stryker v3 gate
tobyhede Jul 2, 2026
c20cecd
refactor(stack): extract EQL v3 DSL into eql/v3 types module
tobyhede Jul 3, 2026
380be64
fix(stack): guard against duplicate v3 column DB names in build()
tobyhede Jul 3, 2026
684bdbc
fix(stack): export v3 Buildable* contracts from public types entrypoint
tobyhede Jul 3, 2026
a672451
test(stack): migrate v3-matrix suite to eql/v3 types namespace
tobyhede Jul 3, 2026
790f2f6
test(stack): cover lock-context encrypt guards and wasm newClient shape
tobyhede Jul 3, 2026
4f0982d
test(stack): re-enable matrix-live-pg live SQL coverage (stale skip)
tobyhede Jul 3, 2026
c627aba
fix(stack): emit hm index for text order domains (eql_v3 SQL domain r…
tobyhede Jul 3, 2026
c7690e3
test(stack): v3 lock-context coverage + text_match/no-op/empty-config…
tobyhede Jul 3, 2026
0eebebb
chore(stack): EQL v3 maintainability follow-ups from the #547 review
freshtonic Jul 4, 2026
beddbf0
test(stack): cover empty text order pg rejection
tobyhede Jul 5, 2026
b0b9789
test(stack): avoid empty text search pg seed
tobyhede Jul 5, 2026
dcd0fe7
test(stack): normalize date storage decrypt assertion
tobyhede Jul 5, 2026
348e4fa
Merge pull request #541 from cipherstash/feat/eql-v3-types-module
tobyhede Jul 6, 2026
35bd9a8
fix(stack): address EQL v3 types-module review follow-ups
tobyhede Jul 6, 2026
63fe076
Merge pull request #559 from cipherstash/fix/eql-v3-review-followups
tobyhede Jul 6, 2026
615e09c
docs(stack): design for EQL v3 Drizzle concrete-type support
tobyhede Jul 6, 2026
6469cc7
chore(stack): upgrade EQL v3 bundle — timestamptz -> timestamp
tobyhede Jul 3, 2026
c93b694
fix(stack): give v3 timestamp domains cast_as 'timestamp' (preserve t…
tobyhede Jul 3, 2026
893d6ed
test(stack): cover timestamp cast_as wasm mapping
tobyhede Jul 6, 2026
fc33d6b
test(stack): pin timestamp time-of-day preservation + dedupe date-lik…
tobyhede Jul 6, 2026
a26655f
docs(stack): design for EQL v3 Drizzle concrete-type support
tobyhede Jul 6, 2026
4d5bf63
Merge pull request #542 from cipherstash/feat/eql-v3-timestamp-bundle
tobyhede Jul 7, 2026
89b903f
chore(stack): EQL v3 concrete types via protect-ffi 0.28
tobyhede Jul 8, 2026
63ca540
chore(stack): re-vendor EQL v3 bundle; move type domains to public.*
tobyhede Jul 8, 2026
d1be1bc
ci(stack): fail loudly when CipherStash live-test secrets are missing
tobyhede Jul 8, 2026
9194df0
fix(stack): pass eqlVersion:3 to protect-ffi and upgrade v3 pg tests …
tobyhede Jul 8, 2026
6e6e932
test(stack): stabilise live timeouts + document v3 ord_term ordering …
tobyhede Jul 8, 2026
5eab5e5
test(stack): full ORE ordering matrix, CI silent-skip guard, reframe …
tobyhede Jul 8, 2026
d9fb482
Merge pull request #575 from cipherstash/chore/eql-v3-ffi-0-28
tobyhede Jul 9, 2026
7f001ab
test(stack): close v3 query-permutation coverage gaps (neq, contained…
tobyhede Jul 9, 2026
e55bb5e
fix(stack): bulk-model id split corrupts field names containing hyphens
tobyhede Jul 8, 2026
66a0e02
feat(stack): add EQL v3 bigint domain family (i64, native JS bigint o…
tobyhede Jul 8, 2026
0b9b192
feat(cli): EQL v3 install path (--eql-version) + rename db install to…
tobyhede Jul 8, 2026
079d323
docs(stack,cli): reconcile living docs to `stash eql install` + docum…
tobyhede Jul 8, 2026
4d54c27
test(stack): compare bigint ORE samples numerically, not lexicographi…
tobyhede Jul 9, 2026
dafcb02
docs(cli,migrate): reference #585 for v3 flag gaps, scope migrate REA…
tobyhede Jul 9, 2026
f14e2b5
fix(stack): reject out-of-range bigint on every encrypt path
tobyhede Jul 9, 2026
8f93fb3
feat(stack): accept bigint on the WASM entry point
tobyhede Jul 9, 2026
a72a113
fix(stack,drizzle): stop silently coercing bigint query values to str…
tobyhede Jul 9, 2026
c36954f
Merge pull request #583 from cipherstash/feat/eql-v3-bigint-restack
tobyhede Jul 9, 2026
419cc68
Merge pull request #587 from cipherstash/test/eql-v3-query-permutatio…
tobyhede Jul 9, 2026
d73a03c
feat(stack): EQL v3 Drizzle adapter on protect-ffi 0.28 (public.* dom…
tobyhede Jul 9, 2026
ba99c79
fix(stack): resolve v3 typedClient decrypt reconstructors by tableName
tobyhede Jul 9, 2026
8123839
refactor(stack): remove obsolete v3 freeTextSearch tuner
tobyhede Jul 9, 2026
f281167
feat(stack): strongly-typed v3 Drizzle adapter (envelope typing, docs…
tobyhede Jul 9, 2026
dcae4a2
test(stack): stage USER_JWT live-coverage guard (skipped pending secret)
tobyhede Jul 9, 2026
cfd46ee
chore(stack): source EQL v3 bundle from @cipherstash/eql
tobyhede Jul 9, 2026
5e9875e
fix(stack): correct stale slug in v3 drizzle tests (eql_v3. -> public.)
tobyhede Jul 9, 2026
70e9f19
fix(stack): probe eql_v3.version() in its own statement
tobyhede Jul 9, 2026
1be5b5e
fix(stack): parenthesise v3 range, batch inArray, restore bulk valida…
tobyhede Jul 9, 2026
9c82f50
fix(stack): compare bigint order domains in v3 drizzle operators oracle
tobyhede Jul 9, 2026
2decfb0
Merge pull request #565 from cipherstash/eql-v3-drizzle-concrete-types
tobyhede Jul 9, 2026
222be16
Merge pull request #590 from cipherstash/use-cipherstash-eql
tobyhede Jul 9, 2026
5d23e80
feat(stack): encryptedSupabaseV3 — EQL v3 dialect of the Supabase ada…
tobyhede Jul 8, 2026
2079d74
feat(stack): add v3 DOMAIN_REGISTRY keyed by unqualified domain name
tobyhede Jul 9, 2026
cb93504
feat(stack): expand select('*') from an introspected column list
tobyhede Jul 9, 2026
482bdde
feat(stack): introspect public columns + EQL v3 domains (by comment) …
tobyhede Jul 9, 2026
bbe0674
feat(stack): synthesize/merge v3 tables + guard unmodelled EQL domains
tobyhede Jul 9, 2026
722ca60
feat(stack): verify declared v3 schemas against introspected domains
tobyhede Jul 9, 2026
6769ca2
feat(stack): introspecting encryptedSupabaseV3 factory + optional sch…
tobyhede Jul 9, 2026
2354af4
test(stack): live Postgres introspection + unmodelled-domain partition
tobyhede Jul 9, 2026
dcb3745
fix(stack): select('*') must alias renamed v3 columns back to their p…
tobyhede Jul 9, 2026
98f657a
fix(stack): adapt supabase merge test to the removed v3 freeTextSearc…
tobyhede Jul 9, 2026
f785339
fix(stack): resolve v3 column renames in order() and onConflict
tobyhede Jul 9, 2026
3048d88
refactor(stack): translate supabase column names once, at a phase bou…
tobyhede Jul 9, 2026
34622f1
fix(stack): actionable error when the optional `pg` peer is missing
tobyhede Jul 9, 2026
3fe3c0c
fix(stack): null-prototype the synthesized/merged v3 column maps
tobyhede Jul 9, 2026
5fdfd87
docs(stack): show the SQL a v3 column is detected from; add introspec…
tobyhede Jul 9, 2026
3ecece0
fix(stack): null-prototype the built encrypt config; cover the __prot…
tobyhede Jul 9, 2026
fd33aad
fix(stack): never encrypt `is` or null filter operands in the supabas…
tobyhede Jul 9, 2026
24784e2
test(stack): cover addJsonbCastsV3, single() dates, forced eqlVersion…
tobyhede Jul 9, 2026
d02717c
fix(stack): scope the unmodelled-domain guard to the tables the calle…
tobyhede Jul 9, 2026
50494d0
fix(stack): escape double quotes and backslashes in .or() filter oper…
tobyhede Jul 9, 2026
81d2eb2
fix(stack): encrypt each element of an in() list inside .or()
tobyhede Jul 9, 2026
9c20935
test(stack): drive all 39 v3 domains through the supabase adapter
tobyhede Jul 9, 2026
a54bd5b
fix(cli): grant eql_v3_internal to the supabase roles
tobyhede Jul 9, 2026
3d9f3d5
fix(stack): reject property/DB name collisions at construction; corre…
tobyhede Jul 9, 2026
1666655
test(stack): run the supabase v3 adapter against a real PostgREST as …
tobyhede Jul 9, 2026
0ebf57e
fix(stack): close v3 drizzle fail-open paths, fix false-negative tests
tobyhede Jul 9, 2026
cbf2da4
test(stack): close the audited v3 coverage gaps
tobyhede Jul 9, 2026
60fd960
feat(stack)!: replace like/ilike with contains on the v3 supabase sur…
tobyhede Jul 9, 2026
67d840f
fix(stack): count codepoints in the free-text needle floor
tobyhede Jul 9, 2026
7a76041
fix(stack): preserve parse error cause in v3 drizzle codec
tobyhede Jul 9, 2026
04290f5
Merge pull request #588 from cipherstash/feat/eql-v3-supabase-adapter
tobyhede Jul 9, 2026
99f8b0a
fix(stack): escape encrypted in-list operands; widen is/contains
tobyhede Jul 9, 2026
8cd485d
fix(stack): correct the supabase .or() string parser
tobyhede Jul 9, 2026
ecf273d
fix(stack): reject empty SteVec, pin ops.and positive path
tobyhede Jul 9, 2026
14d0e78
test(stack): name the missing fixture in lock-context denial tests
tobyhede Jul 9, 2026
2bc4872
test(stack): couple negative live assertions to internal controls
tobyhede Jul 9, 2026
39e6b70
Merge pull request #607 from cipherstash/drizzle-test-coverage
tobyhede Jul 9, 2026
55d9646
fix(stack): close the audited v3 supabase review findings
tobyhede Jul 9, 2026
4b27b7a
fix(stack): count only structural braces when splitting an or-string
tobyhede Jul 9, 2026
bf3e1ea
Merge pull request #609 from cipherstash/feat/eql-v3-supabase-adapter…
tobyhede Jul 9, 2026
cd521a0
Merge branch 'main' into feat/eql-v3-text-search-schema
calvinbrewer Jul 9, 2026
229ce59
feat(stack,cli)!: re-baseline EQL v3 on eql-3.0.0 GA and protect-ffi …
calvinbrewer Jul 9, 2026
e1a004a
fix(stack): emit the OPE ordering index on _ord domains (eql-3.0.0 te…
calvinbrewer Jul 9, 2026
8f57880
fix(stack): declare the ope index on the v2 builder's indexes shape
calvinbrewer Jul 9, 2026
9229bad
fix(stack): finish the OPE re-pin — ord_term_ore, op assertions, '' s…
calvinbrewer Jul 9, 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
16 changes: 16 additions & 0 deletions .changeset/cli-eql-v3-single-bundle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'stash': minor
---

`stash eql install --eql-version 3` now installs the eql-3.0.0 GA bundle,
vendored from the pinned `@cipherstash/eql` package (sha256-verified).

Since eql-3.0.0 one artifact installs everywhere: the operator-class
statements self-skip when the role lacks superuser (managed Postgres,
Supabase) and the bundle disables the ORE-backed encrypted domains it cannot
support. The separate v3 Supabase bundle variant is gone — `--supabase` and
`--exclude-operator-family` no longer select a different v3 file (the role
GRANTs for `eql_v3` / `eql_v3_internal` still apply with `--supabase`).

The bundled skills are also refreshed for the eql-3.0.0 naming convention
(`public.eql_v3_<name>` column domains) and the EQL v3 typed-schema surface.
11 changes: 11 additions & 0 deletions .changeset/eql-v3-bigint-domains.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@cipherstash/stack': minor
---

Add the EQL v3 bigint domain family to the public DSL: `types.Bigint`,
`types.BigintEq`, `types.BigintOrdOre`, and `types.BigintOrd`, backed by the
`public.bigint*` concrete domains. Plaintext is a JS `bigint`, round-tripped
losslessly across the protect-ffi 0.28 boundary (i64 bounds enforced at the
FFI — out-of-range values surface as encryption errors). Index emission follows
the numeric rule: `bigint_eq` → unique (hm); `bigint_ord`/`bigint_ord_ore` →
ore (equality answered via ob).
9 changes: 9 additions & 0 deletions .changeset/eql-v3-bundle-from-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@cipherstash/stack': patch
---

Source the EQL v3 install bundle from `@cipherstash/eql@3.0.0-alpha.3` instead of a hand-vendored 43k-line SQL fixture committed to the test tree. The package publishes its SQL and its TypeScript wire types from the same `eql-bindings` commit, so the bundle is now pinned to a released EQL version rather than tracked by convention.

Test-and-tooling only — `@cipherstash/eql` is a `devDependency` and no public API changes.

The staleness check in the v3 install helper now compares `eql_v3.version()` against the pinned release instead of probing for a hand-picked sentinel domain. The previous sentinel (`public.timestamp`) exists in both the old and new bundles, so it would have reported a stale install as current and left the suite silently running the wrong SQL.
13 changes: 13 additions & 0 deletions .changeset/eql-v3-cli-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"stash": minor
---

Add an EQL v3 install path to `stash eql install` via a new `--eql-version <2|3>`
flag (default `2`). v3 installs the native concrete-domain schema (`public.*`
type domains, `eql_v3` operators, `eql_v3_internal` constructors) from bundles
vendored into `packages/cli/src/sql` by `scripts/build-eql-v3-sql.mjs` (full
bundle + a Supabase variant with the two superuser-only operator-class chunks
stripped). v3 currently supports the direct install path only —
`--drizzle`/`--migration`/`--migrations-dir`/`--latest` are rejected — and the
installer keys `isInstalled`/version checks and Supabase grants to the `eql_v3`
schema.
38 changes: 38 additions & 0 deletions .changeset/eql-v3-drizzle-fail-open-guards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'@cipherstash/stack': minor
---

Close two fail-open paths in the EQL v3 Drizzle adapter.

`ops.contains()` now throws `EncryptionOperatorError` for a search term that
tokenizes to nothing: the empty string, or a term shorter than the match index
tokenizer's `token_length` (3 by default). Such a term produces an empty bloom
filter, and `stored_bf @> '{}'` is true for every row — so a user searching
`"ad"` silently received the entire table. Measured live, the terms `"ad"`,
`"a"` and `"x"` each returned every seeded row, including one in which `"x"`
did not appear.

The floor counts Unicode codepoints, matching the tokenizer. A UTF-16 length
check would wave through an astral-plane term — `"👍👍"` is 4 code units but
only 2 codepoints, yields no trigram, and matched every row.

**Breaking for callers passing short terms:** `contains()` calls that previously
returned every row now throw. Terms of 3+ codepoints are unaffected.

`v3FromDriver()` now throws the new `EqlV3CodecError` on a payload that is not
an EQL envelope, instead of surfacing a raw `SyntaxError` for malformed JSON and
passing a bare scalar through unchecked — `v3FromDriver('5')` previously returned
`5` typed as `Encrypted`, which then reached `decrypt` as garbage. The guard
accepts both scalar envelopes (ciphertext at `c`) and SteVec documents
(ciphertext at `sv[0].c`). A SteVec's `sv` must be a non-empty array: `sv[0]` is
the decryption root, so `sv: []` carries a ciphertext key but no ciphertext, and
is now rejected rather than passed to `decrypt`. `EqlV3CodecError` is exported
from `@cipherstash/stack/eql/v3/drizzle` so callers can catch it.

Also removes an unreachable branch in `inArray`/`notInArray`, whose empty-list
guard already throws before it.

Note: the v2 Drizzle adapter's `like`/`ilike` path builds the same bloom filters
and has the same short-term fail-open. It is **not** fixed here — v2 terms carry
SQL wildcards, so the floor must be measured against what its tokenizer actually
receives before the shared guard can be reused. Tracked separately.
17 changes: 17 additions & 0 deletions .changeset/eql-v3-drizzle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@cipherstash/stack": minor
---

Add EQL v3 Drizzle support at `@cipherstash/stack/eql/v3/drizzle`. A Drizzle-native
`types` namespace (same PascalCase names as `@cipherstash/stack/eql/v3`) declares
encrypted columns whose Postgres type is the semantic `public.<domain>`; the concrete
type drives the legal query operators. `createEncryptionOperatorsV3` provides
capability-checked `eq`/`ne`/`gt`/`gte`/`lt`/`lte`/`between`/`contains`/`inArray`/
`asc`/`desc`/`and`/`or` that emit the latest two-argument `eql_v3` SQL functions with
full-envelope operands, and
`extractEncryptionSchemaV3` rebuilds the schema for `EncryptionV3`. The existing v2
`@cipherstash/stack/drizzle` integration is unchanged.

The v3 text-search helper is `contains`; obsolete `like`/`ilike` helpers are not
exposed because v3 free-text search is token containment rather than SQL wildcard
matching.
5 changes: 5 additions & 0 deletions .changeset/eql-v3-ffi-0-28-concrete-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cipherstash/stack': minor
---

Upgrade `@cipherstash/protect-ffi` to 0.28.0 and update EQL v3 concrete Postgres domain names to match the SQL fixture (`integer*`, `smallint*`, `bool`, `real*`, and `double*`). The public factories remain semantic (`Integer`, `Smallint`, `Boolean`, `Real`, `Double`) while their concrete domains change, so this is a minor release.
23 changes: 23 additions & 0 deletions .changeset/eql-v3-ga-rebaseline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'@cipherstash/stack': minor
---

Re-baseline EQL v3 on the eql-3.0.0 GA release and protect-ffi 0.29.

- **Breaking (v3 preview surface):** the EQL v3 column domains follow the
eql-3.0.0 naming convention — flat, prefixed names in `public`
(`public.eql_v3_text_search`, `public.eql_v3_integer_ord`, …) instead of the
alpha-era bare names. Databases installed from an alpha bundle must be
re-installed (`stash eql install --eql-version 3` replaces the schema).
- `encryptQuery` under `eqlVersion: 3` now returns EQL v3 query operands
(protect-ffi 0.29): term-only scalar operands for the `eql_v3.query_<name>`
domains, the `eql_v3.query_jsonb` containment needle, and bare selector
hashes for JSON path queries — v3 scalar and selector queries no longer
throw `EQL_V3_QUERY_UNSUPPORTED` (the code is gone).
- v2 `searchableJson()` columns now pin the SteVec encoding to `standard`
explicitly. protect-ffi 0.29 flipped the library default to `compat`
(EQL v3's encoding); without the pin, v2 JSON containment queries would
silently match nothing and newly written rows would not be comparable with
existing ones.
- The EQL v3 test/install SQL is sourced from the pinned `@cipherstash/eql`
package (3.0.0) instead of a hand-vendored fixture.
5 changes: 5 additions & 0 deletions .changeset/eql-v3-public-domains.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cipherstash/stack': patch
---

Re-vendor the EQL v3 SQL bundle and align the v3 DSL to it: encrypted type domains now live in the `public` schema (`public.text`, `public.integer`, …) rather than `eql_v3`, and the boolean domain is `public.boolean` (was `eql_v3.bool`). The `eql_v3` schema now holds only the operator-backing functions, and the index-term constructors (`hmac_256`, `ore_block_256`, `bloom_filter`) moved to `eql_v3_internal`. This keeps the SDK's emitted domain names byte-matched to the installed bundle so `CREATE TABLE`/cast resolution succeeds.
65 changes: 65 additions & 0 deletions .changeset/eql-v3-supabase-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
'@cipherstash/stack': minor
---

Add `encryptedSupabaseV3` — the EQL v3 dialect of the Supabase adapter. It is
now a connect-time-async factory: `await encryptedSupabaseV3(url, key)` (or
`(client)`) introspects the database over `DATABASE_URL`, detects EQL v3 columns
by their Postgres domain (`information_schema.columns.domain_name`), and derives
each column's encryption config from its domain — callers no longer pass a
schema to `from()`. `select('*')` is supported (expanded from the introspected
column list, and aliased back to each declared column's JS property name so a
property→DB rename round-trips). A column using an EQL v3 domain this SDK version does not model
(e.g. `public.json`, `*_ord_ope`) throws at construction rather than silently
passing through. Supplying `schemas` remains optional and adds compile-time
types plus startup verification of the declared tables against the database.
Requires a Postgres connection for introspection (`pg` is a new optional peer),
so it cannot run in a Worker or the browser.

Every column name a query carries — filters, `match`, `not`, raw `filter`,
`or()`, `order()`, and the `onConflict` option — is now resolved from its JS
property name to its DB column name in a single pass before the query is built,
so a declared rename round-trips everywhere rather than only on the paths that
remembered to translate.

`order()` on ANY encrypted v3 column is now rejected — at compile time when
`schemas` is supplied, and at runtime otherwise. The EQL v3 domains are
`DOMAIN … AS jsonb` and the bundle declares no btree operator class on them, so
`ORDER BY col` resolves through jsonb's default `jsonb_cmp` and sorts by the
envelope's byte structure: a stable, plausible-looking, meaningless row order,
with no error. Correct ordering is `ORDER BY eql_v3.ord_term(col)`, which
PostgREST's `order=` cannot express. Order by a plaintext column, expose
`eql_v3.ord_term()` as a generated column or view, or use the EQL v3 Drizzle
integration, which emits `ord_term` directly. Note `gte`/`lte` filters remain
correct: the comparison operators *are* declared on the ord domains, and only
sorting resolves through the missing operator class.

`.or()` now understands PostgREST's `column.not.<op>.<value>` negation. It was
previously parsed as `{ op: 'not', value: '<op>.<value>' }`, so on an encrypted
column `or('nickname.not.in.(ada,grace)')` encrypted the literal string
`in.(ada,grace)` as a single plaintext and produced a filter that silently
matched nothing.

Free-text search on the v3 builder is `contains(column, value)`. `like`/`ilike`
are not exposed, because EQL v3 free-text search is token containment over a
bloom filter (`@>`, backed by `eql_v3.contains`) rather than SQL wildcard
matching — `%` is tokenized like any other character, so a `like` pattern is a
category error. This matches the v3 Drizzle integration, which omits them for
the same reason. On an encrypted column `like`/`ilike` now throw and name
`contains`; on a plaintext column they remain ordinary PostgREST filters.

`contains` is narrowed at compile time to columns whose domain carries the
`freeTextSearch` capability (`public.text_match`, `public.text_search`), and
guarded at runtime for the untyped surface. A raw `filter(column, operator, …)`
on an encrypted v3 column now derives its query type from the operator instead
of always encrypting an equality term, so `filter('bio', 'cs', …)` on a
`public.text_match` column works rather than being rejected, and an unsupported
operator throws instead of silently encrypting the wrong term.

Substring `contains` still matches only when the needle equals the stored value
or is exactly the tokenizer's window (3 characters): the operand is a storage
envelope whose bloom carries the whole needle as an `include_original` token.
This is shared with v3 Drizzle's `contains` and tracked upstream in EQL.

v2 (`encryptedSupabase`) is unchanged: it keeps `like`/`ilike` (`eql_v2.like`,
`~~`) and its raw-`filter` query-type mapping, so no v2 ciphertext moves.
18 changes: 18 additions & 0 deletions .changeset/eql-v3-text-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"@cipherstash/stack": minor
---

Add the EQL v3 `text_search` authoring DSL on a new `@cipherstash/stack/eql/v3`
subpath (`types.TextSearch`, v3 `encryptedTable` / `buildEncryptConfig`). The v3
builders emit the existing `EncryptConfig` shape, so encryption, payloads, and
query paths are unchanged at runtime.

Also widens the public client types (`EncryptionClientConfig.schemas`,
`EncryptOptions`, `SearchTerm`/`EncryptQueryOptions`) to a structural contract so
both v2 and v3 builders are accepted by `Encryption` / `encrypt` / `decrypt` /
`encryptQuery`. This is a backward-compatible widening — existing v2 usage is
unaffected. The structural contracts themselves (`BuildableColumn`,
`BuildableQueryColumn`, `BuildableV3QueryableColumn`, `BuildableTable`,
`BuildableTableColumns`) and the `encryptModel` return-type mapper
(`EncryptedFromBuildableTable`) are exported from `@cipherstash/stack/types` so
consumers can name them.
29 changes: 29 additions & 0 deletions .changeset/eql-v3-typed-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"@cipherstash/stack": minor
---

Add a strongly-typed EQL v3 client surface on a new `@cipherstash/stack/v3`
subpath (`EncryptionV3`, `typedClient`, `TypedEncryptionClient`). It re-exports
the v3 `types` namespace and table API (from `@cipherstash/stack/eql/v3`), so a
single import provides everything needed to author and use a v3 schema.

Every method derives its types from the concrete `table` / `column` builder
arguments:

- `encrypt` / `encryptQuery` pin the plaintext to the column's domain type
(`text → string`, `timestamp → Date`, …).
- `encryptQuery` constrains `queryType` to the column's capabilities and rejects
storage-only columns at compile time.
- `encryptModel` / `bulkEncryptModels` validate schema-column fields against their
inferred plaintext type (passthrough fields are untouched) and return a precise
encrypted model.
- `decryptModel` / `bulkDecryptModels` return the precise plaintext model,
reconstructing `Date` values from the encrypt-config `cast_as`.

Because the typed methods bind to the concrete branded v3 classes, a hand-rolled
structural table/column is rejected — closing the soundness gap where a non-branded
table could be encrypted at runtime while typed as plaintext.

Runtime behaviour is unchanged: the encrypt/query paths return the same operations
as the base client; only the model-decrypt paths add a per-column `Date`
reconstruction step. The v2 client surface (`Encryption`) is untouched.
7 changes: 7 additions & 0 deletions .changeset/eql-v3-typed-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@cipherstash/stack': minor
---

Add EQL v3 schema builders for supported generated SQL domains under `@cipherstash/stack/eql/v3`, exposed as the `types` namespace (one member per supported EQL v3 domain, e.g. `types.TextEq` / `types.IntegerOrd` / `types.Timestamp`), including explicit query capability metadata (`getQueryCapabilities()` / `isQueryable()`) and v3 table support in model encryption helpers (`encryptModel` / `bulkEncryptModels`).

Also widen the accepted plaintext input type for `encrypt` / `encryptQuery` to include `Date` (via the new `Plaintext` type), so v3 `date` / `timestamp` domains can be encrypted and queried with their natural JavaScript values.
11 changes: 11 additions & 0 deletions .changeset/rename-db-install-to-eql-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"stash": minor
"@cipherstash/wizard": minor
---

Rename `stash db install` to `stash eql install`. The command scaffolds
`stash.config.ts` and installs the EQL extensions, so it now lives under a
dedicated `eql` command group. `stash db install` keeps working as a
deprecated alias that prints a warning pointing at the new name. All help
text, hints, generated migration headers, and wizard steps now reference
`stash eql install`.
8 changes: 8 additions & 0 deletions .changeset/schema-stevec-standard-pin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@cipherstash/schema': patch
---

`searchableJson()` now pins the SteVec encoding mode to `standard` explicitly.
protect-ffi 0.29 flipped the library default to `compat` (the EQL v3
encoding); pinning keeps the v2 wire format byte-stable so existing encrypted
JSON columns stay queryable and comparable.
Loading
Loading