Skip to content

feat(jsonb): storage-only public.eql_v3_json + rename searchable domain to _search (CIP-3512)#397

Open
freshtonic wants to merge 5 commits into
mainfrom
james/cip-3512-json-storage-only
Open

feat(jsonb): storage-only public.eql_v3_json + rename searchable domain to _search (CIP-3512)#397
freshtonic wants to merge 5 commits into
mainfrom
james/cip-3512-json-storage-only

Conversation

@freshtonic

@freshtonic freshtonic commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What & why

Every scalar EQL type shipped a storage-only / encryption-only variant under its bare name (public.eql_v3_integer etc.), but json had none — its only domain required a full searchable SteVec document, so there was no way to encrypt a JSON blob purely for storage. This closes that gap and aligns json with the scalar convention that the bare name is the storage-only variant.

  • public.eql_v3_json is now the storage-only / encryption-only domain: a plain {v, i, c} envelope with a native-jsonb operator firewall (raises on every operator, mirroring the scalar storage domains).
  • public.eql_v3_json_search is the searchable SteVec document — unchanged in behaviour, just renamed from public.eql_v3_json (all @>/<@/->/->> operators, the eql_v3.query_jsonb cast, and per-leaf hm/op terms move with it).
  • public.eql_v3_jsonb_entry and eql_v3.query_jsonb are unchanged.

Fixes CIP-3512.

How it threads through the stack

  • SQL (src/v3/jsonb/): new storage domain + storage_blockers.sql firewall; searchable surface renamed to _search.
  • Catalog (eql-domains): storage json modelled as a Shape::Scalar domain in the (still non-scalar) jsonb family; document domain renamed to json_search; full_name/rust_struct_name mappings updated.
  • Bindings (eql-bindings): new hand-written Json struct; SteVecDocument repointed to _search; regenerated inventory/payload/TS/JSON Schema; from_v2 conversion path updated (storage json resolves to a scalar target, SteVec doc to json_search).
  • Tests: renamed document references; new generated real-ciphertext storage fixture (v3_json_storage, via the .storage_only() path) + a dedicated SQLx suite; regenerated the public-surface golden snapshot (net change is exactly the 13 document ops moving to _search).

Versioning

Ships as a patch (3.0.1), not a major — EQL v3 has no production users yet. Changeset (patch on @cipherstash/eql) included; sync-lockstep-versions propagates 3.0.1 to eql-bindings and the bundled SQL at release time. Upgrade note U-007 added to docs/upgrading/v3.0.md (pre-release adopters change searchable JSON columns to public.eql_v3_json_search).

Test status

Rust crates (eql-domains, eql-codegen, eql-bindings) and the affected SQLx suites (surface, jsonb bindings/operators, payload schema, privilege, uninstall, and the new storage suite) are green. Verified behaviour on a live Postgres: both CHECKs are mutually exclusive by payload shape, and the firewall raises operator … is not supported for public.eql_v3_json.

Two caveats:

  • 8 v3_jsonb_tests fail locally — the known SteVec fixture selector-drift (pinned constants match CI workspace material, not local); deliberately not re-pinned. They use the v3_ste_vec document fixture and are independent of this change.
  • Uninstall tests hit "out of shared memory" under parallelism (the ~75 new firewall objects add lock pressure); they pass single-threaded, and CI partitions installs.

Follow-ups (tracked on CIP-3512)

Release EQL + eql-bindings, then update protectjs-ffi, stack, and the stack adapters (Drizzle / Prisma / Supabase) for the renamed JSON domains.

Summary by CodeRabbit

  • New Features

    • Added a storage-only encrypted JSON format using a {v, i, c} envelope.
    • Added a separate searchable encrypted JSON format supporting JSON querying and indexing.
  • Bug Fixes

    • Storage-only JSON now rejects unsupported JSON operators with clear errors.
    • Search operations continue working through the dedicated searchable format.
  • Documentation

    • Added upgrade guidance explaining the domain split, compatibility impact, validation examples, and rollback considerations.

…omain to _search

Every scalar EQL type shipped a storage-only / encryption-only variant under
its bare name, but json had none — its only domain required a full searchable
SteVec document. public.eql_v3_json is now the storage-only domain (a plain
{v,i,c} envelope with a native-jsonb operator firewall, the JSON analogue of
the scalar storage domains), and the searchable SteVec document moves to
public.eql_v3_json_search. public.eql_v3_jsonb_entry and eql_v3.query_jsonb
are unchanged.

Threaded through the catalog (storage json is a Shape::Scalar domain in the
jsonb family), codegen, hand-written bindings (new Json struct), regenerated
TS/JSON schemas, a generated real-ciphertext storage fixture with tests, and
the v2->v3 conversion path. Ships as a patch (3.0.1) with an upgrade note
(U-007); EQL v3 has no production users yet.

CIP-3512
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@freshtonic, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b10514da-0f74-4895-8c85-f79abc00fece

📥 Commits

Reviewing files that changed from the base of the PR and between 5892e28 and 0329659.

📒 Files selected for processing (17)
  • .changeset/json-storage-only-domain.md
  • crates/eql-bindings/tests/v3_conformance.rs
  • crates/eql-domains/src/spec.rs
  • docs/reference/adding-a-scalar-encrypted-domain-type.md
  • docs/reference/catalog-driven-architecture.md
  • docs/reference/database-indexes.md
  • docs/reference/eql-functions.md
  • docs/reference/json-support.md
  • docs/reference/permissions.md
  • docs/reference/query-performance.md
  • docs/reference/sql-support.md
  • docs/tutorials/proxy-configuration.md
  • tasks/test/clean_install_v3.sh
  • tasks/test/splinter.sh
  • tests/sqlx/tests/v3_json_storage_tests.rs
  • tests/sqlx/tests/v3_jsonb_operator_surface_tests.rs
  • tests/sqlx/tests/v3_jsonb_tests.rs
📝 Walkthrough

Walkthrough

The v3 JSONB model now separates storage-only {v,i,c} payloads under public.eql_v3_json from searchable SteVec documents under public.eql_v3_json_search. Bindings, SQL operators, conversion paths, fixtures, tests, and upgrade documentation were updated accordingly.

Changes

v3 JSONB domain split

Layer / File(s) Summary
Domain contracts and generated bindings
crates/eql-bindings/..., crates/eql-codegen/..., crates/eql-domains/...
Defines separate storage and searchable JSON schemas, bindings, catalog names, payload variants, and conversion targets.
SQL domains, operators, and firewalls
src/v3/jsonb/*
Makes eql_v3_json validate storage envelopes and reject JSONB operators, while routing searchable operators, casts, and SteVec functions through eql_v3_json_search.
Fixture generation and conversion paths
tests/sqlx/src/fixtures/*, tests/sqlx/tests/generate_all_fixtures.rs
Adds storage-only fixture generation and selects the storage or searchable domain according to SteVec indexing.
Validation, public surfaces, and upgrade documentation
tests/sqlx/tests/*, tests/sqlx/snapshots/*, docs/upgrading/v3.0.md, .changeset/*
Updates schema, operator, installation, uninstall, integration, public-surface, and upgrade coverage for the two domains.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: tobyhede, coderdan, auxesis

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PostgreSQL
  participant JsonSearchDomain
  participant JsonStorageDomain
  Client->>PostgreSQL: submit encrypted JSON expression
  PostgreSQL->>JsonSearchDomain: resolve searchable payload operators
  JsonSearchDomain-->>Client: return SteVec query or extracted value
  PostgreSQL->>JsonStorageDomain: resolve storage-only payload operator
  JsonStorageDomain-->>Client: raise operator not supported
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: splitting storage-only public.eql_v3_json from the renamed searchable _search domain.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch james/cip-3512-json-storage-only

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tests/sqlx/tests/v3_uninstall_tests.rs (1)

145-160: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Extend uninstall/install validation to the new storage-only domain.

The split introduces two distinct public JSON domains, but these tests only validate public.eql_v3_json_search.

  • tests/sqlx/tests/v3_uninstall_tests.rs#L145-L160: include both public.eql_v3_json and public.eql_v3_json_search in the installed public-domain expectations.
  • tests/sqlx/tests/v3_uninstall_tests.rs#L211-L228: add an application column typed as public.eql_v3_json and insert a valid {v,i,c} payload.
  • tests/sqlx/tests/v3_uninstall_tests.rs#L296-L298: assert that the storage-only column type, and its round-trip value, survive uninstall.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sqlx/tests/v3_uninstall_tests.rs` around lines 145 - 160, Extend
tests/sqlx/tests/v3_uninstall_tests.rs at lines 145-160 to expect both
public.eql_v3_json and public.eql_v3_json_search. At lines 211-228, add an
application column using public.eql_v3_json and insert a valid {v,i,c} payload.
At lines 296-298, assert that this storage-only column retains its type and
round-trip value after uninstall.
tests/sqlx/src/fixtures/validation.rs (1)

145-153: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test coverage gap: public.eql_v3_json no longer asserted as allowlisted.

The rename swapped the assertion for public.eql_v3_json_search in place of public.eql_v3_json, but public.eql_v3_json is still a distinct, live allowlist entry (storage-only fixtures use it per targets_for in v3_convert.rs). Add back the public.eql_v3_json assertion alongside the new one so both allowlisted JSONB tokens are covered.

✅ Proposed fix
     assert!(ColumnType::try_from("jsonb").is_ok());
+    assert!(ColumnType::try_from("public.eql_v3_json").is_ok());
     assert!(ColumnType::try_from("public.eql_v3_json_search").is_ok());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sqlx/src/fixtures/validation.rs` around lines 145 - 153, Update
column_type_accepts_allowlisted_tokens to assert that
ColumnType::try_from("public.eql_v3_json") succeeds, while retaining the
existing public.eql_v3_json_search assertion so both allowlisted JSONB tokens
are covered.
tests/sqlx/tests/v3_public_surface_tests.rs (1)

99-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add public.eql_v3_json to user_domain_names()

scalar_families() doesn’t include the non-scalar jsonb family, so this list now covers eql_v3_json_search and eql_v3_jsonb_entry but skips the storage-only eql_v3_json domain. Add it here so both public-surface placement checks keep covering the new column type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/sqlx/tests/v3_public_surface_tests.rs` around lines 99 - 116, The
user_domain_names function must also include the storage-only public.eql_v3_json
domain. Extend its explicit JSON domain list alongside eql_v3_json_search and
eql_v3_jsonb_entry, preserving the existing scalar-family collection and sorting
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/eql-domains/src/spec.rs`:
- Around line 28-36: Update full_name so the literal-name exception applies only
when family_name is "jsonb" and self.name is "json" or "json_search"; preserve
the existing family+suffix naming path for all other families.

In `@docs/upgrading/v3.0.md`:
- Line 16: Update the `eql_v3_json` rename entry in `packages/eql/CHANGELOG.md`
to link to the U-007 section
`docs/upgrading/v3.0.md#u-007-json-storage-only-and-searchable-document-rename`,
matching the existing changelog link style.

In `@src/v3/jsonb/blockers.sql`:
- Around line 27-285: Regenerate the bundled SQL assets so every corresponding
operator and function binding uses public.eql_v3_json_search instead of
public.eql_v3_json. Apply the same rename throughout
crates/eql-bindings/sql/cipherstash-encrypt.sql and
packages/eql/sql/cipherstash-encrypt.sql, covering the blocker definitions from
jsonb_blocked_exists through jsonb_blocked_concat_rhs and the related
functions.sql changes; preserve all signatures and behavior.

---

Outside diff comments:
In `@tests/sqlx/src/fixtures/validation.rs`:
- Around line 145-153: Update column_type_accepts_allowlisted_tokens to assert
that ColumnType::try_from("public.eql_v3_json") succeeds, while retaining the
existing public.eql_v3_json_search assertion so both allowlisted JSONB tokens
are covered.

In `@tests/sqlx/tests/v3_public_surface_tests.rs`:
- Around line 99-116: The user_domain_names function must also include the
storage-only public.eql_v3_json domain. Extend its explicit JSON domain list
alongside eql_v3_json_search and eql_v3_jsonb_entry, preserving the existing
scalar-family collection and sorting behavior.

In `@tests/sqlx/tests/v3_uninstall_tests.rs`:
- Around line 145-160: Extend tests/sqlx/tests/v3_uninstall_tests.rs at lines
145-160 to expect both public.eql_v3_json and public.eql_v3_json_search. At
lines 211-228, add an application column using public.eql_v3_json and insert a
valid {v,i,c} payload. At lines 296-298, assert that this storage-only column
retains its type and round-trip value after uninstall.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b3095fe-c531-438a-920a-77521aa46269

📥 Commits

Reviewing files that changed from the base of the PR and between 4119c42 and 5892e28.

⛔ Files ignored due to path filters (6)
  • packages/eql/src/generated/schema-manifest.ts is excluded by !**/generated/**
  • packages/eql/src/generated/schema/v3/eql_v3_json.json is excluded by !**/generated/**
  • packages/eql/src/generated/schema/v3/eql_v3_json_search.json is excluded by !**/generated/**
  • packages/eql/src/generated/v3/Json.ts is excluded by !**/generated/**
  • packages/eql/src/generated/v3/SteVecDocument.ts is excluded by !**/generated/**
  • packages/eql/src/generated/v3/index.ts is excluded by !**/generated/**
📒 Files selected for processing (46)
  • .changeset/json-storage-only-domain.md
  • .gitignore
  • crates/eql-bindings/bindings/v3/Json.ts
  • crates/eql-bindings/bindings/v3/SteVecDocument.ts
  • crates/eql-bindings/schema/v3/eql_v3_json.json
  • crates/eql-bindings/schema/v3/eql_v3_json_search.json
  • crates/eql-bindings/src/from_v2/mod.rs
  • crates/eql-bindings/src/from_v2/target.rs
  • crates/eql-bindings/src/v3/inventory.rs
  • crates/eql-bindings/src/v3/jsonb.rs
  • crates/eql-bindings/src/v3/payload.rs
  • crates/eql-bindings/tests/catalog_parity.rs
  • crates/eql-bindings/tests/domain_payload.rs
  • crates/eql-bindings/tests/from_v2.rs
  • crates/eql-bindings/tests/v3_conformance.rs
  • crates/eql-codegen/src/bindings.rs
  • crates/eql-codegen/src/dump.rs
  • crates/eql-domains/src/lib.rs
  • crates/eql-domains/src/spec.rs
  • crates/eql-domains/src/tests.rs
  • docs/upgrading/v3.0.md
  • src/v3/jsonb/blockers.sql
  • src/v3/jsonb/functions.sql
  • src/v3/jsonb/operators.sql
  • src/v3/jsonb/storage_blockers.sql
  • src/v3/jsonb/types.sql
  • tests/sqlx/fixtures/FIXTURE_SCHEMA.md
  • tests/sqlx/snapshots/eql_v3_public_surface.txt
  • tests/sqlx/src/fixtures/eql_plaintext.rs
  • tests/sqlx/src/fixtures/mod.rs
  • tests/sqlx/src/fixtures/v3_convert.rs
  • tests/sqlx/src/fixtures/v3_doc_integer.rs
  • tests/sqlx/src/fixtures/v3_json_storage.rs
  • tests/sqlx/src/fixtures/v3_ste_vec.rs
  • tests/sqlx/src/fixtures/validation.rs
  • tests/sqlx/src/jsonb_entry.rs
  • tests/sqlx/tests/encrypted_domain/jsonb_entry.rs
  • tests/sqlx/tests/generate_all_fixtures.rs
  • tests/sqlx/tests/payload_schema_tests.rs
  • tests/sqlx/tests/v3_json_storage_tests.rs
  • tests/sqlx/tests/v3_jsonb_bindings_tests.rs
  • tests/sqlx/tests/v3_jsonb_operator_surface_tests.rs
  • tests/sqlx/tests/v3_jsonb_tests.rs
  • tests/sqlx/tests/v3_privilege_tests.rs
  • tests/sqlx/tests/v3_public_surface_tests.rs
  • tests/sqlx/tests/v3_uninstall_tests.rs

Comment thread crates/eql-domains/src/spec.rs
Comment thread docs/upgrading/v3.0.md
Comment thread src/v3/jsonb/blockers.sql
…ily; link U-007

Address PR review (CodeRabbit):
- full_name: guard the json/json_search verbatim-name exception with
  family_name == "jsonb" so an unrelated future family with a colliding bare
  domain name still takes the normal family+suffix join (mirrors the guard in
  every_domain_name_starts_with_its_family_name).
- changeset: cross-link the U-007 upgrade note from the changelog entry body.
The Clean-DB v3 install smoke (tasks/test/clean_install_v3.sh) still drove the
searchable SteVec document surface through public.eql_v3_json — inserting an sv
payload and running ->/@>/to_ste_vec_query on it — which now belong to
public.eql_v3_json_search (and the storage-only public.eql_v3_json CHECK rejects
sv payloads). Point the searchable block at _search, and add a storage-only
smoke: the {v,i,c} CHECK accepts a ciphertext-only payload, rejects a SteVec
document, and the native-jsonb firewall raises for the storage domain too.

This directory was outside the rename sweep's src/crates/tests/packages scope.
Fixes the "Validate (Postgres 17)" CI failure on #397. CIP-3512.
The searchable encrypted-JSON surface is now public.eql_v3_json_search;
public.eql_v3_json is the new storage-only / encryption-only domain. Update the
reference and tutorial docs that described the searchable surface under the old
name, add storage-only mentions to json-support.md and proxy-configuration.md,
and refresh the splinter allowlist descriptions for the ->/->>/@>/to_ste_vec_query
functions (which now take a public.eql_v3_json_search arg).
@freshtonic freshtonic requested review from coderdan and tobyhede July 14, 2026 01:03
@tobyhede

Copy link
Copy Markdown
Contributor

_search for json type has slightly different semantics to text.
For the text type _search is the bloom filter matching.

Copy link
Copy Markdown
Contributor Author

@tobyhede but _search is generic terminology. Maybe we should change it in both places?

tobyhede added a commit that referenced this pull request Jul 15, 2026


The storage-domain tests used inline {v,i,c} payloads, which technically
violate the project rule that tests must use real ciphertexts, never synthetic
blobs. Port PR #397's real-ciphertext approach (adapted to this branch's
generated architecture and `json` module naming):

- New `v3_json_storage` fixture: encrypts JSON through cipherstash-client with
  `.storage_only()` (no index) into a real {v,i,c} envelope, column-typed
  `public.eql_v3_json` so the domain CHECK runs at load.
- New `v3_json_storage_tests` suite: a real client payload parses into the
  generated `Json` binding; `@>` on a real fixture row raises the firewall; the
  storage/search CHECKs are mutually exclusive on real data.
- Fix `v3_convert::targets_for`: an unindexed jsonb fixture now converts to the
  storage-only `eql_v3_json` target (was unconditionally `eql_v3_json_search`),
  with a unit test pinning the bare {v,i,c} envelope.
- Wire-up: allowlist `public.eql_v3_json`, register the fixture module +
  generation, gitignore the generated .sql.

The inline jsonb_check tests are kept — they cover what fixtures can't
(malformed-envelope rejection, the full blocked-operator matrix, and the
path-operator fall-through characterization).
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.

2 participants