Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 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
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
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.
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.
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.
5 changes: 5 additions & 0 deletions .changeset/skip-v3-bigint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cipherstash/stack': patch
---

Omit EQL v3 bigint factories from the public DSL and test matrix until native bigint round-tripping is supported.
43 changes: 43 additions & 0 deletions .github/actions/require-cs-secrets/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Require CipherStash secrets
description: >-
Fail the job when any CS_* credential is missing. The live test suites gate
themselves on these vars (vitest `describe.skip`, Deno `test.ignore`), so a
rotated / cleared / fork-PR-absent secret would make them silently skip and
let CI go green with ZERO live coverage. This turns that silent skip into a
loud failure. Requires the repo to be checked out first (local action path).

inputs:
workspace-crn:
description: secrets.CS_WORKSPACE_CRN
required: true
client-id:
description: secrets.CS_CLIENT_ID
required: true
client-key:
description: secrets.CS_CLIENT_KEY
required: true
client-access-key:
description: secrets.CS_CLIENT_ACCESS_KEY
required: true

runs:
using: composite
steps:
- name: Assert CS_* secrets are present
shell: bash
env:
CS_WORKSPACE_CRN: ${{ inputs.workspace-crn }}
CS_CLIENT_ID: ${{ inputs.client-id }}
CS_CLIENT_KEY: ${{ inputs.client-key }}
CS_CLIENT_ACCESS_KEY: ${{ inputs.client-access-key }}
run: |
missing=0
for v in CS_WORKSPACE_CRN CS_CLIENT_ID CS_CLIENT_KEY CS_CLIENT_ACCESS_KEY; do
if [ -z "${!v}" ]; then
echo "::error::Required secret $v is not set on this runner — live test suites would silently skip."
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
exit 1
fi
65 changes: 65 additions & 0 deletions .github/workflows/fta-v3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: "FTA Complexity (EQL v3)"

# Blocking complexity gate for the EQL v3 text-search schema. Runs the Fast
# TypeScript Analyzer (fta-cli) against the v3 source directory only and fails
# the check when any file exceeds the score cap (`--score-cap` in the
# `analyze:complexity` script). FTA is pure static source analysis, so this job
# needs no build step, database, or credentials.

on:
push:
branches:
- 'main'
paths:
- 'packages/stack/src/eql/v3/**'
# Shared match-index defaults live outside src/eql/v3 but shape every
# emitted v3 match block (load-bearing `k`/`m` ciphertext params), so edits
# here must trigger the v3 gate too.
- 'packages/stack/src/schema/match-defaults.ts'
- 'packages/stack/package.json'
- '.github/workflows/fta-v3.yml'
pull_request:
branches:
- "**"
paths:
- 'packages/stack/src/eql/v3/**'
- 'packages/stack/src/schema/match-defaults.ts'
- 'packages/stack/package.json'
- '.github/workflows/fta-v3.yml'

permissions:
contents: read

jobs:
fta:
name: Analyze v3 complexity
runs-on: blacksmith-4vcpu-ubuntu-2404

steps:
- name: Checkout Repo
uses: actions/checkout@v6

- uses: pnpm/action-setup@v6.0.8
name: Install pnpm
with:
run_install: false

- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: 'pnpm'

# node-pty's install hook falls back to `node-gyp rebuild` when no
# linux-x64 prebuild matches. pnpm/action-setup v6 no longer ships
# node-gyp on PATH, so install it explicitly.
- name: Install node-gyp
run: npm install -g node-gyp

- name: Install dependencies
run: pnpm install --frozen-lockfile

# Non-zero exit (score above the cap) fails the check — this is the
# blocking gate. No `continue-on-error`.
- name: Analyze v3 complexity
run: pnpm --filter @cipherstash/stack run analyze:complexity
10 changes: 10 additions & 0 deletions .github/workflows/prisma-example-readme-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# A missing / rotated / fork-PR-absent secret makes the walkthrough skip
# its live steps silently, hiding regressions behind a green job. Fail loud.
- name: Require CipherStash secrets
uses: ./.github/actions/require-cs-secrets
with:
workspace-crn: ${{ secrets.CS_WORKSPACE_CRN }}
client-id: ${{ secrets.CS_CLIENT_ID }}
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}

# Build via turbo so `^build` on `@cipherstash/prisma-next` and
# its `@cipherstash/stack` peer is honoured. The test's
# `pnpm install` subprocess inside `examples/prisma/` is a no-op
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/prisma-next-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# The global-setup hook hard-errors without CS_WORKSPACE_CRN, but a
# missing sibling secret could still degrade coverage silently — assert
# all four up front so a rotated / fork-PR-absent secret fails loudly.
- name: Require CipherStash secrets
uses: ./.github/actions/require-cs-secrets
with:
workspace-crn: ${{ secrets.CS_WORKSPACE_CRN }}
client-id: ${{ secrets.CS_CLIENT_ID }}
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}

# Write the CS_* credentials and the harness DATABASE_URL into the
# example app's .env so the runtime + the `prisma-next migration
# apply` invocation in global-setup both pick them up. The harness
Expand Down
49 changes: 35 additions & 14 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# Fail loudly if any live-test credential is missing, so the v3 live
# matrix (and every other `describeLive` suite) can't silently skip.
- name: Require CipherStash secrets
uses: ./.github/actions/require-cs-secrets
with:
workspace-crn: ${{ secrets.CS_WORKSPACE_CRN }}
client-id: ${{ secrets.CS_CLIENT_ID }}
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}

- name: Type tests (stack)
run: pnpm --filter @cipherstash/stack run test:types

- name: Lint — no hardcoded package-manager runners
run: pnpm run lint:runners

Expand Down Expand Up @@ -149,6 +162,16 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# Auth-dependent `e2e/` suites skip themselves without these vars — a
# silent skip would hide regressions behind a green job. Fail loudly.
- name: Require CipherStash secrets
uses: ./.github/actions/require-cs-secrets
with:
workspace-crn: ${{ secrets.CS_WORKSPACE_CRN }}
client-id: ${{ secrets.CS_CLIENT_ID }}
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}

# Run the standalone `e2e/` workspace via turbo so the `^build`
# dep on the `test:e2e` task builds cli + wizard first. CLI's own
# E2E (`packages/cli/tests/e2e/**`) is covered by the `run-tests`
Expand All @@ -157,7 +180,7 @@ jobs:
run: pnpm exec turbo run test:e2e --filter @cipherstash/e2e

# Verifies @cipherstash/stack/wasm-inline works under Deno — i.e. the
# WASM build of protect-ffi 0.25+ and auth 0.38+ can round-trip an
# WASM build of protect-ffi 0.26+ and auth 0.40+ can round-trip an
# encryption against ZeroKMS / CTS in a runtime with no native
# bindings available. The deno.json deliberately omits --allow-ffi so
# a silent fallback to the NAPI module is impossible.
Expand Down Expand Up @@ -216,19 +239,17 @@ jobs:
- name: Build stack
run: pnpm exec turbo run build --filter @cipherstash/stack

# roundtrip.test.ts skips itself (Deno.test.ignore) when any of
# the four CS_* env vars is missing — that's the right behaviour
# for local runs, but in CI a silent skip would mean a rotated /
# cleared secret hides a real WASM regression behind a green job.
# Fail loudly instead.
- name: Assert CS_* secrets are present
run: |
for v in CS_WORKSPACE_CRN CS_CLIENT_ID CS_CLIENT_KEY CS_CLIENT_ACCESS_KEY; do
if [ -z "${!v}" ]; then
echo "::error::Required secret $v is not set on this runner — the WASM smoke test would silently skip."
exit 1
fi
done
# roundtrip.test.ts skips itself (Deno.test.ignore) when any of the four
# CS_* env vars is missing — fine locally, but in CI a silent skip would
# let a rotated / cleared secret hide a real WASM regression behind a
# green job. Fail loudly instead.
- name: Require CipherStash secrets
uses: ./.github/actions/require-cs-secrets
with:
workspace-crn: ${{ secrets.CS_WORKSPACE_CRN }}
client-id: ${{ secrets.CS_CLIENT_ID }}
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}

- name: Run Deno WASM smoke test
working-directory: e2e/wasm
Expand Down
Loading