Skip to content

Update EQL v3 concrete type names via FFI 0.28#575

Open
tobyhede wants to merge 5 commits into
feat/eql-v3-text-search-schemafrom
chore/eql-v3-ffi-0-28
Open

Update EQL v3 concrete type names via FFI 0.28#575
tobyhede wants to merge 5 commits into
feat/eql-v3-text-search-schemafrom
chore/eql-v3-ffi-0-28

Conversation

@tobyhede

@tobyhede tobyhede commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Upgrade @cipherstash/protect-ffi to 0.27.0.
  • Rename EQL v3 concrete type factories/classes/domains to the new names: Integer, Smallint, Boolean, Real, Double, and Bigint.
  • Widen the stack FFI-facing encrypted payload/query types for the new v3 query shape and update v3 schema/matrix tests.

Validation

  • pnpm --filter @cipherstash/stack build
  • pnpm exec vitest run __tests__/schema-v3.test.ts __tests__/v3-matrix/matrix.test.ts
  • pnpm exec vitest --run --typecheck.only __tests__/schema-v3.test-d.ts __tests__/typed-client-v3.test-d.ts __tests__/v3-matrix/matrix.test-d.ts

Notes

  • Live credential/Postgres suites were not run locally.

Summary by CodeRabbit

  • New Features

    • Added a strongly typed EQL v3 schema and client surface with domain-specific builders for integer, smallint, boolean, real, double, and existing text/date/timestamp types.
    • Added support for Date values in v3 encryption and query encryption flows.
    • Expanded v3 query handling to support the new typed query shapes.
  • Bug Fixes

    • Updated v3 naming and examples to use the new domain names consistently.
    • Bulk decryption now reports malformed items as per-item errors instead of failing the entire request.

@tobyhede tobyhede requested a review from a team as a code owner July 8, 2026 04:09
@changeset-bot

changeset-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6e6e932

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@cipherstash/stack Minor
@cipherstash/bench Patch
@cipherstash/prisma-next Patch
@cipherstash/basic-example Patch
@cipherstash/prisma-next-example Patch
@cipherstash/e2e Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e3331f08-de82-40df-ad18-45b23e53f3c6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/eql-v3-ffi-0-28

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Squashes the concrete-type domain work and the protect-ffi 0.24 -> 0.28
bump into a single commit, removing the intermediate 0.27 -> 0.28 rename
and re-bump churn (package.json, pnpm-lock.yaml, changeset).
@tobyhede tobyhede force-pushed the chore/eql-v3-ffi-0-28 branch from fcbe7da to 89b903f Compare July 8, 2026 04:24
Adopt the latest EQL v3 SQL bundle and realign the v3 DSL so the SDK's
emitted domain names byte-match the installed bundle (a mismatch fails
CREATE TABLE/cast resolution at runtime, only caught by live-PG tests).

- Re-vendor packages/stack/__tests__/fixtures/eql-v3/cipherstash-encrypt-v3.sql
- Type domains eql_v3.* -> public.* (user-column domains now live in public
  so app tables survive EQL uninstall); eql_v3.bool -> public.boolean
- Index-term constructors hmac_256/ore_block_256/bloom_filter moved to
  eql_v3_internal in live-test SQL; operator extractors (eq_term/ord_term/
  match_term) stay in eql_v3
- Remap eqlType strings + catalog keys + type-level/unit assertions

Verified: test:types 60/60, schema-v3 unit 162/162, no regressions.
Live-PG contract suite runs in CI.
tobyhede added 3 commits July 8, 2026 21:27
The live suites gate themselves on CS_WORKSPACE_CRN / CS_CLIENT_ID /
CS_CLIENT_KEY / CS_CLIENT_ACCESS_KEY (vitest `describe.skip`, Deno
`test.ignore`). If any secret is unset, rotated, or absent (e.g. a fork
PR), those suites silently skip and CI goes green with ZERO live
encryption coverage — no signal that the whole v3 live matrix never ran.

Add a `require-cs-secrets` local composite action that asserts all four
vars are present and fails the job otherwise, and wire it into every
credential-dependent job:
  - tests.yml: run-tests, e2e-tests, wasm-e2e-tests
  - prisma-next-e2e.yml
  - prisma-example-readme-e2e.yml

The wasm-e2e-tests job already had an inline version of this guard; it's
now folded into the shared action so the check lives in one place.

Note: run-tests-bun is intentionally left unguarded — it is
`continue-on-error: true` (and runs vitest with `|| true`), so a guard
there could not fail CI regardless.
…to operand query API

Root cause of the red v3 live suites: the SDK never passed `eqlVersion` to
protect-ffi's `newClient`, so v3 clients defaulted to `eqlVersion: 2`. A v2-mode
client cannot resolve v3 concrete-type columns and the native addon throws
"Cannot convert undefined or null to object" for every encrypt.

Source fix (thread the version through the existing config seam):
- types.ts: add `eqlVersion?: 2 | 3` to `ClientConfig`.
- encryption/index.ts: `init()` forwards `eqlVersion` to `newClient`.
- encryption/v3.ts: `EncryptionV3` forces `eqlVersion: 3` (v3-only invariant).
  v2 clients leave it unset -> protect-ffi default 2, so no v2 change.

Test fixes:
- matrix-live{,-pg}.test.ts: `slug` stripped the stale `eql_v3.` prefix, but the
  domains were renamed to `public.*`, producing invalid dotted column names
  (`public.boolean`) that also fail FFI identifier resolution. Strip `public.`.
- matrix-live-pg + schema-v3-pg: protect-ffi 0.28 has no v3 scalar query wire
  shape (`encryptQuery` throws EQL_V3_QUERY_UNSUPPORTED). Upgrade both suites to
  the supported API: encrypt a FULL operand with `client.encrypt` and compare
  with the public two-arg functions `eql_v3.eq/gte/lte/contains(col, $::jsonb)`.
  - text_ord/text_ord_ore carry both `hm` and `ob`; `eql_v3.eq` would compare
    `hm`, so the ORE proof uses a degenerate `gte AND lte` range. Pure-ORE
    numeric/date domains keep `eql_v3.eq` (resolves to `ord_term`).
  - schema-v3-pg used the base `Encryption` factory with v3 tables; pass
    `config: { eqlVersion: 3 }` so its storage encrypts succeed.
  - Rebuild the query-only-rejection test without `encryptQuery` (strip the
    ciphertext from a full operand).
  - Range test asserts membership via `ORDER BY label`: `eql_v3.gte/lte` order
    correctly (selects grace+zora, excludes ada/alan), but `ORDER BY
    eql_v3.ord_term` sorts by the raw term, not ORE order, for text_search.
- Both pg suites: `DROP TABLE IF EXISTS` before create, so a stale local table
  from before the `public.*` rename can't mask the run (no-op in CI).

Verified live: matrix-live 176/176, matrix-live-pg 40/40, schema-v3-pg 8/8,
type tests 60/60.
…defect

Two follow-ups surfaced while making the v3 suites green.

1. Live-test timeout flakiness. vitest.config.ts set no global testTimeout, so
   it used Vitest's 5000ms default. 300+ live tests already pass an explicit
   `, 30000)`; the few without one inherited 5000ms and intermittently timed out
   under concurrent ZeroKMS load (they pass in isolation). Set testTimeout and
   hookTimeout to 30000 to match the dominant explicit value — high enough for
   real network latency, low enough to still surface a genuine hang. Full live
   suite now 1036 passed / 0 timeouts.

2. `ORDER BY eql_v3.ord_term(col)` does not sort in ORE order. Root cause: the
   ORE-correct btree opclass for the composite `ore_block_256` type is created
   in a superuser-gated DO block in the vendored bundle and is skipped on
   managed/non-superuser Postgres, so ORDER BY silently falls back to record
   (raw-byte) comparison. The `eql_v3.gte/lte/lt/gt` predicates are ORE-correct
   regardless (they don't use sort/index machinery). This is an upstream bundle
   limitation, so the vendored .sql is NOT patched; instead:
   - docs/eql-v3-ord-term-ordering-defect.md records the defect, live evidence,
     a minimal repro, and the recommended upstream fix, and warns that a future
     v3 orderBy must not lower to a bare `ORDER BY ord_term(...)`.
   - schema-v3-pg.test.ts corrects the range-test comment and adds a positive
     ORE-total-order proof reconstructed from pairwise `eql_v3.lt` (the only
     ORE-correct path), independent of the superuser-gated opclass.

Verified live: schema-v3-pg 9/9; full package suite 1036 passed / 3 skipped.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant