Feat/prisma next eql v3#655
Conversation
🦋 Changeset detectedLatest commit: bfa77d6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
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 |
|
Important Review skippedToo many files! This PR contains 114 files, which is 14 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (117)
You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds the EQL v3 Prisma-next surface: concrete per-domain constructors, v3 contracts and runtime wiring, capability-gated operators, JSONB wire handling, BigInt support, a pinned bundle migration, updated examples, and extensive unit, type-level, property, and live Postgres tests. ChangesEQL v3 Prisma-next
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/prisma-next/test/bundling-isolation.test.ts (1)
243-250: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReject impure chunks even when their metadata fingerprint matches.
A chunk containing every required marker is allowlisted even if it also contains SDK, migration, or wire-plane code. Add a negative forbidden-marker check before accepting it.
Proposed purity check
function isAllowedSharedChunk(chunk: string): boolean { if (!SHARED_CHUNK_PATTERN.test(chunk)) { return false } const body = readChunk(chunk).body + const forbidden = [ + ...CONTROL_FORBIDDEN, + ...RUNTIME_FORBIDDEN, + ...V2_WIRE_MARKERS, + ...V3_WIRE_MARKERS, + ] + if (forbidden.some((marker) => body.includes(marker))) { + return false + } return ALLOWED_SHARED_CHUNK_MARKER_SETS.some((markers) => markers.every((marker) => body.includes(marker)), ) }🤖 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 `@packages/prisma-next/test/bundling-isolation.test.ts` around lines 243 - 250, The isAllowedSharedChunk function currently allowlists chunks based only on required markers. Add a forbidden-marker check after reading the chunk body and reject the chunk if it contains SDK, migration, or wire-plane markers, before evaluating ALLOWED_SHARED_CHUNK_MARKER_SETS; preserve the existing required-marker validation for otherwise pure chunks.examples/prisma/migrations/app/20260714T2142_initial/end-contract.json (1)
21-102: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winRegenerate the contract with EQL v3 descriptors
domainandstoragenow referencecipherstash/eql-v3/*, butextensionPacks.cipherstashdoesn’t declare matching descriptors for those codec IDs. Add the v3 descriptors (and keep the existing ones too if both are intentional).🤖 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 `@examples/prisma/migrations/app/20260714T2142_initial/end-contract.json` around lines 21 - 102, Regenerate end-contract.json so extensionPacks.cipherstash declares descriptors for every cipherstash/eql-v3 codec referenced by the contract, including the bigint, date, text_search, boolean, json, and double descriptors shown in the model fields. Preserve existing descriptor entries when both legacy and v3 codecs are intentional, and ensure each descriptor’s codecId and capabilities match its referenced type.
🧹 Nitpick comments (3)
packages/prisma-next/test/v3/runtime-v3.test.ts (2)
97-106: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winVerify SDK binding rather than codec object identity.
Distinct codec objects can still reference the same SDK or shared state. Invoke each codec’s decrypt/read path and assert that only its corresponding SDK mock is called.
🤖 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 `@packages/prisma-next/test/v3/runtime-v3.test.ts` around lines 97 - 106, Replace the object-identity assertion in the createCipherstashV3RuntimeDescriptor isolation test with behavioral verification: create distinct SDK mocks, invoke each codec’s decrypt/read path, and assert each invocation calls only its corresponding SDK mock. Keep the per-descriptor setup and ensure cross-tenant SDK calls are not observed.
24-25: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winImport the v3 surface through the public entry point. The
../../src/v3/*imports can miss a broken./v3export map or barrel; use../../src/exports/v3forcreateCipherstashV3RuntimeDescriptorandcipherstashV3QueryOperationsinstead.🤖 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 `@packages/prisma-next/test/v3/runtime-v3.test.ts` around lines 24 - 25, Update the imports in runtime-v3.test.ts to obtain createCipherstashV3RuntimeDescriptor and cipherstashV3QueryOperations from the public ../../src/exports/v3 entry point, removing the direct ../../src/v3/* imports so the test exercises the v3 export surface.Source: Coding guidelines
packages/prisma-next/test/v3/bulk-encrypt-v3.test.ts (1)
26-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExercise the new v3 public entry point instead of source internals.
Import publicly exported v3 symbols through
../../src/exports/v3(or the package self-reference), retaining internal imports only for unavoidable fixture setup. This also tests the new export wiring.As per coding guidelines, tests should prefer public APIs and avoid private internals.
🤖 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 `@packages/prisma-next/test/v3/bulk-encrypt-v3.test.ts` around lines 26 - 34, Update the test imports for v3 symbols such as bulkEncryptMiddlewareV3, EncryptedNumber, markV3QueryTerm, and v3ToDriver to use the public exports from ../../src/exports/v3 or the package self-reference. Retain direct internal imports only for fixture setup symbols that are not publicly exported, and ensure the test exercises the public entry-point wiring.Source: Coding guidelines
🤖 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 `@examples/prisma/README.md`:
- Around line 7-14: Update the domain column in the table near
EncryptedTextSearch to use fully qualified v3 names by adding the public. schema
prefix to the remaining five rows: salary, accountId, birthday, emailVerified,
and preferences.
In `@examples/prisma/src/prisma/contract.d.ts`:
- Around line 60-78: Regenerate the extension-pack metadata in the contract,
including the sections around the field type declarations and lines 339-579, so
every codec referenced by the User fields uses the corresponding
cipherstash/eql-v3 codec instance and metadata. Replace remaining v2 codec
registrations, eql_v2_encrypted storage references, and v2 imports such as
EncryptedDouble with their generated v3 equivalents, while preserving the
existing contract structure.
In `@examples/prisma/src/prisma/contract.json`:
- Around line 21-28: Regenerate the CipherStash extension-pack metadata so
extensionPacks.cipherstash.types.codecTypes.codecInstances includes the active
cipherstash/eql-v3 codec IDs and types.storage maps them to eql_v3_encrypted
instead of the obsolete v2 entries. Ensure the generated contract is consistent
with the v3 codec IDs used by the active fields.
In `@examples/prisma/test/e2e/bool.e2e.test.ts`:
- Line 83: Update the capability probes in the bool e2e test, including the
emailVerified probe and the as-never call, to avoid type-erasing assertions. Use
a narrowly typed helper for the intentional runtime boundary, or add a focused
Biome suppression with a clear rationale if the assertions are required.
In `@packages/prisma-next/package.json`:
- Around line 63-66: Add the missing require field to the "./v3" export in the
package exports map, pointing to the appropriate compiled CommonJS artifact
while preserving the existing types and import entries.
In `@packages/prisma-next/src/exports/column-types.ts`:
- Around line 95-112: Update v3Authored so each returned factory invocation
creates and returns a fresh V3ColumnDescriptor, including independently cloned
nested typeParams and capabilities, rather than reusing the captured descriptor
object. Preserve the descriptor values derived from probe while ensuring
mutations to one result cannot affect later results, and add a test verifying
mutation isolation.
In `@packages/prisma-next/src/v3/bulk-encrypt-v3.ts`:
- Around line 82-84: Update the flow around stampRoutingKeysFromAst,
collectV3Targets, and the encryption handling at the referenced ranges so
routing is preserved per ParamRef rather than stored only on the shared envelope
instance. Ensure each parameter’s aliases receive independent routing metadata,
or reject conflicting reuse before encryption, so targets for different columns
cannot inherit the last routing stamp.
In `@packages/prisma-next/src/v3/operators-v3.ts`:
- Around line 677-684: Remove the cipherstashNotIlike operator from the operator
definitions, or replace its Bloom-filter negation with an exact
decrypt-and-post-filter implementation before exposing negative free-text
search. Do not retain the NOT eql_v3.contains predicate, and preserve the
existing positive free-text search behavior.
In `@packages/prisma-next/src/v3/sdk-adapter-v3.ts`:
- Around line 272-284: Update bulkDecrypt to validate that the unwrapped client
response contains exactly one entry per input ciphertext before mapping results.
If the response length differs from ciphertexts.length, throw an error instead
of returning a truncated or misaligned result; preserve the existing per-entry
error handling and plaintext conversion for valid responses.
- Around line 217-221: Add the repository-standard Biome suppression for
deliberate as never casts at both encryptQuery call sites in the SDK adapter,
preserving the existing rationale comments and behavior. Apply the suppression
only to these intentional assertions, or replace them with a typed adapter if
that is the established pattern.
In `@packages/prisma-next/src/v3/wire-v3.ts`:
- Around line 28-31: The v3ToDriver function can return undefined when
JSON.stringify receives an unsupported top-level value, violating its string |
null contract. Update the JSON.stringify result handling in v3ToDriver to
convert an undefined result to null while preserving serialized strings and the
existing null/undefined input behavior.
In `@packages/prisma-next/test/bundling-isolation.test.ts`:
- Around line 295-335: Update the bundling-isolation tests for the v3 and
middleware entries to scan every chunk returned by collectGraph(), not just each
entry body. Check the complete graphs against V2_WIRE_MARKERS and all six v2
codec factory symbols, including factories beyond createCipherstashStringCodec,
and fail with diagnostics identifying the offending chunks and markers.
In `@packages/prisma-next/test/live/bigint-live-pg.test.ts`:
- Around line 10-13: Load dotenv via a direct top-level import before all other
imports in packages/prisma-next/test/live/bigint-live-pg.test.ts (lines 10-13),
examples/prisma/test/e2e/str-range.e2e.test.ts (lines 18-26), and
packages/prisma-next/test/live/operators-live-pg.test.ts (lines 10-16), ensuring
environment variables are available before gates and database-dependent modules
initialize.
In `@packages/prisma-next/test/live/boolean-storage-live-pg.test.ts`:
- Around line 9-10: Load environment configuration before the live-test imports
by adding dotenv/config as the first import in
packages/prisma-next/test/live/boolean-storage-live-pg.test.ts (lines 9-10),
packages/prisma-next/test/live/migration-apply-live-pg.test.ts (lines 16-20),
and packages/prisma-next/test/live/side-by-side-clients-live-pg.test.ts (lines
17-20); no other changes are needed.
In `@packages/prisma-next/test/live/helpers/harness.ts`:
- Around line 208-224: Update the row-to-parameter construction in the rows.map
callback so every row is bound according to the canonical columnNames order
derived from the first row. Iterate columnNames after binding the row id,
retrieve each row’s corresponding cell, and append its value using that column’s
codec; preserve the existing AST references while handling missing columns
consistently with the expected query shape.
In `@packages/prisma-next/test/live/migration-apply-live-pg.test.ts`:
- Around line 66-83: Ensure the test setup around installEqlV3IfNeeded uses an
isolated fresh database or explicitly removes the pinned EQL v3 installation
before invoking it, so the helper cannot return early on a reused database.
Preserve the existing byte-for-byte assertions in the test and verify that the
customer-facing migration SQL is actually executed.
In `@packages/prisma-next/test/v3/bulk-encrypt-v3.test.ts`:
- Around line 285-304: Update the aborted-signal test for
bulkEncryptMiddlewareV3 so the beforeExecute rejection explicitly matches the
advertised RUNTIME.ABORTED code or message, rather than accepting any thrown
exception. Keep the existing createCtx signal setup and sdk.bulkEncryptCalls
empty assertion unchanged.
In `@packages/prisma-next/test/v3/operator-lowering-v3-json.test.ts`:
- Around line 89-96: Update the test case “rejects a null needle with an
isNull() hint” to assert that the thrown EncryptionOperatorError message
includes the promised isNull() guidance, while retaining the existing
error-class assertion.
---
Outside diff comments:
In `@examples/prisma/migrations/app/20260714T2142_initial/end-contract.json`:
- Around line 21-102: Regenerate end-contract.json so extensionPacks.cipherstash
declares descriptors for every cipherstash/eql-v3 codec referenced by the
contract, including the bigint, date, text_search, boolean, json, and double
descriptors shown in the model fields. Preserve existing descriptor entries when
both legacy and v3 codecs are intentional, and ensure each descriptor’s codecId
and capabilities match its referenced type.
In `@packages/prisma-next/test/bundling-isolation.test.ts`:
- Around line 243-250: The isAllowedSharedChunk function currently allowlists
chunks based only on required markers. Add a forbidden-marker check after
reading the chunk body and reject the chunk if it contains SDK, migration, or
wire-plane markers, before evaluating ALLOWED_SHARED_CHUNK_MARKER_SETS; preserve
the existing required-marker validation for otherwise pure chunks.
---
Nitpick comments:
In `@packages/prisma-next/test/v3/bulk-encrypt-v3.test.ts`:
- Around line 26-34: Update the test imports for v3 symbols such as
bulkEncryptMiddlewareV3, EncryptedNumber, markV3QueryTerm, and v3ToDriver to use
the public exports from ../../src/exports/v3 or the package self-reference.
Retain direct internal imports only for fixture setup symbols that are not
publicly exported, and ensure the test exercises the public entry-point wiring.
In `@packages/prisma-next/test/v3/runtime-v3.test.ts`:
- Around line 97-106: Replace the object-identity assertion in the
createCipherstashV3RuntimeDescriptor isolation test with behavioral
verification: create distinct SDK mocks, invoke each codec’s decrypt/read path,
and assert each invocation calls only its corresponding SDK mock. Keep the
per-descriptor setup and ensure cross-tenant SDK calls are not observed.
- Around line 24-25: Update the imports in runtime-v3.test.ts to obtain
createCipherstashV3RuntimeDescriptor and cipherstashV3QueryOperations from the
public ../../src/exports/v3 entry point, removing the direct ../../src/v3/*
imports so the test exercises the v3 export surface.
🪄 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: efc246fe-e8f1-4c75-ad03-04303692ca88
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (108)
.changeset/eql-v3-prisma-next.md.changeset/eql-v3-stack-domain-registry-exports.md.changeset/prisma-example-eql-v3.mdAGENTS.mdexamples/prisma/README.mdexamples/prisma/migrations/app/20260709T1034_initial/migration.jsonexamples/prisma/migrations/app/20260709T1034_initial/migration.tsexamples/prisma/migrations/app/20260709T1034_initial/ops.jsonexamples/prisma/migrations/app/20260714T2142_initial/end-contract.d.tsexamples/prisma/migrations/app/20260714T2142_initial/end-contract.jsonexamples/prisma/migrations/app/20260714T2142_initial/migration.jsonexamples/prisma/migrations/app/20260714T2142_initial/migration.tsexamples/prisma/migrations/app/20260714T2142_initial/ops.jsonexamples/prisma/migrations/cipherstash/20260601T0100_install_eql_v3_bundle/migration.jsonexamples/prisma/migrations/cipherstash/20260601T0100_install_eql_v3_bundle/ops.jsonexamples/prisma/migrations/cipherstash/refs/head.jsonexamples/prisma/prisma/schema.prismaexamples/prisma/src/db.tsexamples/prisma/src/index.tsexamples/prisma/src/prisma/contract.d.tsexamples/prisma/src/prisma/contract.jsonexamples/prisma/test/e2e/README.mdexamples/prisma/test/e2e/bigint.e2e.test.tsexamples/prisma/test/e2e/bool.e2e.test.tsexamples/prisma/test/e2e/date.e2e.test.tsexamples/prisma/test/e2e/global-setup.tsexamples/prisma/test/e2e/json.e2e.test.tsexamples/prisma/test/e2e/mixed.e2e.test.tsexamples/prisma/test/e2e/num.e2e.test.tsexamples/prisma/test/e2e/str-range.e2e.test.tspackages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/end-contract.d.tspackages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/end-contract.jsonpackages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/migration.jsonpackages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/migration.tspackages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/ops.jsonpackages/prisma-next/migrations/refs/head.jsonpackages/prisma-next/package.jsonpackages/prisma-next/src/contract-authoring.tspackages/prisma-next/src/exports/column-types.tspackages/prisma-next/src/exports/control.tspackages/prisma-next/src/exports/runtime.tspackages/prisma-next/src/exports/stack.tspackages/prisma-next/src/exports/v3.tspackages/prisma-next/src/extension-metadata/constants-v3.tspackages/prisma-next/src/middleware/bulk-encrypt.tspackages/prisma-next/src/migration/cipherstash-codec-v3.tspackages/prisma-next/src/migration/eql-bundle-v3.tspackages/prisma-next/src/stack/from-stack-v3.tspackages/prisma-next/src/stack/from-stack.tspackages/prisma-next/src/types/codec-types.tspackages/prisma-next/src/types/operation-types.tspackages/prisma-next/src/v3/barrel.tspackages/prisma-next/src/v3/bulk-encrypt-v3.tspackages/prisma-next/src/v3/catalog.tspackages/prisma-next/src/v3/codec-runtime-v3.tspackages/prisma-next/src/v3/derive-schemas-v3.tspackages/prisma-next/src/v3/envelope-number.tspackages/prisma-next/src/v3/from-stack-v3-validate.tspackages/prisma-next/src/v3/operators-v3.tspackages/prisma-next/src/v3/query-term.tspackages/prisma-next/src/v3/runtime-v3.tspackages/prisma-next/src/v3/sdk-adapter-v3.tspackages/prisma-next/src/v3/wire-v3.tspackages/prisma-next/test/authoring.test.tspackages/prisma-next/test/bulk-encrypt-middleware.helpers.tspackages/prisma-next/test/bulk-encrypt-middleware.test.tspackages/prisma-next/test/bundling-isolation.test.tspackages/prisma-next/test/column-types.test.tspackages/prisma-next/test/descriptor.test.tspackages/prisma-next/test/equality-trait-removal.test.tspackages/prisma-next/test/live/bigint-live-pg.test.tspackages/prisma-next/test/live/boolean-storage-live-pg.test.tspackages/prisma-next/test/live/bulk-encrypt-live-pg.test.tspackages/prisma-next/test/live/helpers/eql-v3.tspackages/prisma-next/test/live/helpers/harness.tspackages/prisma-next/test/live/helpers/live-gate.tspackages/prisma-next/test/live/migration-apply-live-pg.test.tspackages/prisma-next/test/live/operators-live-pg.test.tspackages/prisma-next/test/live/operators-null-live-pg.test.tspackages/prisma-next/test/live/side-by-side-clients-live-pg.test.tspackages/prisma-next/test/operation-types.types.test-d.tspackages/prisma-next/test/psl-interpretation-numeric.test.tspackages/prisma-next/test/psl-interpretation-other-types.test.tspackages/prisma-next/test/psl-interpretation.test.tspackages/prisma-next/test/v3/bulk-encrypt-v3.test.tspackages/prisma-next/test/v3/catalog-invariants.test.tspackages/prisma-next/test/v3/catalog.test.tspackages/prisma-next/test/v3/codec-runtime-v3.test.tspackages/prisma-next/test/v3/codec-types-v3.test-d.tspackages/prisma-next/test/v3/column-types.test-d.tspackages/prisma-next/test/v3/constants-v3.test.tspackages/prisma-next/test/v3/derive-schemas-v3.test.tspackages/prisma-next/test/v3/envelope-number.test.tspackages/prisma-next/test/v3/from-stack-divergence-v3.test.tspackages/prisma-next/test/v3/from-stack-v3.test.tspackages/prisma-next/test/v3/migration-v3.test.tspackages/prisma-next/test/v3/operator-gating-v3.test.tspackages/prisma-next/test/v3/operator-lowering-v3-equality.test.tspackages/prisma-next/test/v3/operator-lowering-v3-json.test.tspackages/prisma-next/test/v3/operator-lowering-v3-order-range.test.tspackages/prisma-next/test/v3/operator-lowering-v3-text-search.test.tspackages/prisma-next/test/v3/operator-lowering-v3.helpers.tspackages/prisma-next/test/v3/properties.test.tspackages/prisma-next/test/v3/runtime-v3.test.tspackages/prisma-next/test/v3/sdk-adapter-v3.test.tspackages/prisma-next/test/v3/wire-v3.test.tspackages/prisma-next/tsup.config.tspackages/stack/src/eql/v3/index.ts
💤 Files with no reviewable changes (3)
- examples/prisma/migrations/app/20260709T1034_initial/ops.json
- examples/prisma/migrations/app/20260709T1034_initial/migration.json
- examples/prisma/migrations/app/20260709T1034_initial/migration.ts
There was a problem hiding this comment.
Pull request overview
Adds first-class EQL v3 support to @cipherstash/prisma-next (plus the Prisma example app), introducing a v3-only entry point and runtime/codec/operator surface that targets public.eql_v3_* domain-backed JSONB wire format, along with migrations to install the EQL v3 bundle.
Changes:
- Add
@cipherstash/prisma-next/v3entry point (cipherstashFromStackV3) with v3 runtime descriptor, codec catalog/derivation, query-term routing, and JSONB wire helpers. - Introduce v3 migrations/baseline install plumbing (
@cipherstash/eql/sqlsourced SQL baked into ops) and update control/runtime exports accordingly. - Convert the Prisma example + e2e harness to v3 domains/operators (including lossless
bigintbeyondNumber.MAX_SAFE_INTEGERand v3 order-term sorting).
Reviewed changes
Copilot reviewed 103 out of 109 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds lockfile entries for v3 migration SQL source and live-test deps. |
| packages/stack/src/eql/v3/index.ts | Re-exports v3 domain registry utilities for downstream adapters. |
| packages/prisma-next/tsup.config.ts | Builds the new src/exports/v3.ts entry. |
| packages/prisma-next/test/v3/wire-v3.test.ts | Unit tests for v3 JSONB wire encoding/decoding behavior. |
| packages/prisma-next/test/v3/runtime-v3.test.ts | Pins v3 runtime descriptor identity, codecs, traits, and operation registration. |
| packages/prisma-next/test/v3/operator-lowering-v3-text-search.test.ts | Tests v3 lowering for free-text token containment operators. |
| packages/prisma-next/test/v3/operator-lowering-v3-json.test.ts | Tests v3 lowering + operand validation for encrypted JSON containment. |
| packages/prisma-next/test/v3/operator-gating-v3.test.ts | Tests capability gating and v2/v3 descriptor co-registration collision behavior. |
| packages/prisma-next/test/v3/migration-v3.test.ts | Asserts v3 baseline migration artifacts and invariant-only edge semantics. |
| packages/prisma-next/test/v3/from-stack-v3.test.ts | Validates v3-only entry point rejection paths and schema divergence checks. |
| packages/prisma-next/test/v3/from-stack-divergence-v3.test.ts | Tests exact-domain override divergence rules for v3 schemas. |
| packages/prisma-next/test/v3/envelope-number.test.ts | Introduces/validates v3 EncryptedNumber envelope semantics and redaction. |
| packages/prisma-next/test/v3/derive-schemas-v3.test.ts | Tests contract→v3 schema derivation via nativeType→factory mapping. |
| packages/prisma-next/test/v3/constants-v3.test.ts | Pins v3 codec id sets, traits derivation, and extension identity constants. |
| packages/prisma-next/test/v3/column-types.test-d.ts | Type-level drift guards for v3 column-type factory literal narrowing. |
| packages/prisma-next/test/v3/codec-types-v3.test-d.ts | Type-level drift guards for v3 CodecTypes entries/traits and envelope outputs. |
| packages/prisma-next/test/v3/catalog.test.ts | Tests derived v3 domain catalog invariants and factory mappings. |
| packages/prisma-next/test/v3/catalog-invariants.test.ts | Pins non-structural v3 catalog invariants against stack domain registry. |
| packages/prisma-next/test/psl-interpretation-other-types.test.ts | Renames PSL constructors to explicit *V2 forms for non-numeric v2 types. |
| packages/prisma-next/test/psl-interpretation-numeric.test.ts | Renames PSL constructors to explicit *V2 forms for v2 numeric types. |
| packages/prisma-next/test/operation-types.types.test-d.ts | Extends type-level operator dispatch tests to cover v3 marker-trait routing. |
| packages/prisma-next/test/live/operators-null-live-pg.test.ts | Live-PG suite for v3 NULL handling and operator operand rejection behavior. |
| packages/prisma-next/test/live/migration-apply-live-pg.test.ts | Live-PG suite asserting v3 bundle apply matches shipped migration bytes + postchecks. |
| packages/prisma-next/test/live/helpers/live-gate.ts | Adds env/profile gating for v3 live PG suites. |
| packages/prisma-next/test/live/helpers/eql-v3.ts | Adds idempotent, advisory-lock-guarded v3 install helper for live suites. |
| packages/prisma-next/test/live/bulk-encrypt-live-pg.test.ts | Live-PG test for v3 bulk-encrypt middleware end-to-end behavior. |
| packages/prisma-next/test/live/boolean-storage-live-pg.test.ts | Live-PG coverage for storage-only boolean domain and operator refusals. |
| packages/prisma-next/test/live/bigint-live-pg.test.ts | Live-PG coverage for lossless bigint and ordering/range semantics. |
| packages/prisma-next/test/equality-trait-removal.test.ts | Extends “no framework built-in traits” invariant checks to v3 descriptors. |
| packages/prisma-next/test/descriptor.test.ts | Updates descriptor tests to include v3 baseline migration and head ref invariants. |
| packages/prisma-next/test/bulk-encrypt-middleware.test.ts | Refactors v2 middleware tests to share harness with new v3 suite. |
| packages/prisma-next/test/bulk-encrypt-middleware.helpers.ts | New shared harness utilities for bulk-encrypt middleware tests. |
| packages/prisma-next/src/v3/wire-v3.ts | Implements v3 plain-JSONB wire encoding/decoding helpers. |
| packages/prisma-next/src/v3/runtime-v3.ts | Adds v3 runtime extension descriptor composition (codecs + query ops). |
| packages/prisma-next/src/v3/query-term.ts | Introduces the v3 query-term mark/inspection seam + v3 operator error type. |
| packages/prisma-next/src/v3/from-stack-v3-validate.ts | Adds v3 schema override validation based on exact public.eql_v3_* domain identity. |
| packages/prisma-next/src/v3/envelope-number.ts | Adds v3 EncryptedNumber envelope (sibling of v2 EncryptedDouble). |
| packages/prisma-next/src/v3/derive-schemas-v3.ts | Derives v3 stack schemas from contract nativeType via catalog factories. |
| packages/prisma-next/src/v3/catalog.ts | Derives v3 domain metadata/catalog from stack DOMAIN_REGISTRY. |
| packages/prisma-next/src/v3/barrel.ts | Exposes v3 user-facing envelopes via v3 subtree barrel exports. |
| packages/prisma-next/src/types/operation-types.ts | Adds v3 marker-trait-based type-level operator visibility rules. |
| packages/prisma-next/src/stack/from-stack.ts | Documents that this entry point is v2-only and points to v3 sibling. |
| packages/prisma-next/src/stack/from-stack-v3.ts | Adds v3-only one-call setup (cipherstashFromStackV3). |
| packages/prisma-next/src/migration/eql-bundle-v3.ts | Re-exports v3 bundle SQL/manifest from @cipherstash/eql/sql for emit-time baking. |
| packages/prisma-next/src/migration/cipherstash-codec-v3.ts | Adds v3 control-plane hooks (schema stripping only; no search-config ops). |
| packages/prisma-next/src/middleware/bulk-encrypt.ts | Exports AST routing-key stamping helper for reuse by v3 middleware. |
| packages/prisma-next/src/exports/v3.ts | New consolidated @cipherstash/prisma-next/v3 export surface. |
| packages/prisma-next/src/exports/stack.ts | Adds v3 stack entry point exports alongside existing v2 stack exports. |
| packages/prisma-next/src/exports/runtime.ts | Adds v3 runtime exports (descriptor, ops, wire helpers, envelopes). |
| packages/prisma-next/src/exports/control.ts | Adds v3 baseline migration + hooks into the control descriptor. |
| packages/prisma-next/package.json | Adds ./v3 export, live test script, and devDependencies for v3 emit/live suites. |
| packages/prisma-next/migrations/refs/head.json | Adds v3 bundle invariant to head ref. |
| packages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/migration.ts | Adds v3 baseline migration (invariant-only self-edge op). |
| packages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/migration.json | Adds emitted metadata for the v3 baseline migration. |
| packages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/end-contract.json | Adds emitted end-contract artifact for the v3 baseline migration. |
| packages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/end-contract.d.ts | Adds emitted end-contract type artifact for the v3 baseline migration. |
| examples/prisma/test/e2e/str-range.e2e.test.ts | Updates string-domain e2e coverage to v3 operators + semantics. |
| examples/prisma/test/e2e/README.md | Updates e2e harness documentation for v3 behavior and removed v2 limitations. |
| examples/prisma/test/e2e/num.e2e.test.ts | Updates numeric e2e coverage to v3 + EncryptedNumber and order-term sorting. |
| examples/prisma/test/e2e/global-setup.ts | Updates e2e global setup docs/comments for v3 baselines and domains. |
| examples/prisma/test/e2e/date.e2e.test.ts | Updates date e2e coverage to v3 and order-term sorting. |
| examples/prisma/test/e2e/bigint.e2e.test.ts | Updates bigint e2e to assert lossless > safe-integer behavior on v3. |
| examples/prisma/src/prisma/contract.json | Regenerates example contract for v3 codec ids, domains, and typeParams structure. |
| examples/prisma/src/db.ts | Switches example wiring to cipherstashFromStackV3 via @cipherstash/prisma-next/v3. |
| examples/prisma/README.md | Updates example documentation for v3 domain constructors and operator semantics. |
| examples/prisma/prisma/schema.prisma | Updates Prisma schema to v3 per-domain constructors and revised operator expectations. |
| examples/prisma/migrations/cipherstash/refs/head.json | Adds v3 bundle invariant to the example’s cipherstash space head ref. |
| examples/prisma/migrations/cipherstash/20260601T0100_install_eql_v3_bundle/migration.json | Adds example cipherstash-space v3 baseline metadata. |
| examples/prisma/migrations/app/20260714T2142_initial/ops.json | New initial app migration ops creating users against v3 domains. |
| examples/prisma/migrations/app/20260714T2142_initial/migration.ts | New initial app migration authored against v3 domains + codec refs. |
| examples/prisma/migrations/app/20260714T2142_initial/migration.json | New initial app migration metadata with updated storage hash. |
| examples/prisma/migrations/app/20260709T1034_initial/migration.ts | Removes prior v2-era initial migration (included per-column search-config ops). |
| examples/prisma/migrations/app/20260709T1034_initial/migration.json | Removes prior v2-era initial migration metadata. |
| AGENTS.md | Updates repo layout note to reflect prisma-next now supports both v2 and v3 entry points. |
| .changeset/prisma-example-eql-v3.md | Changeset for upgrading the example app to v3. |
| .changeset/eql-v3-stack-domain-registry-exports.md | Changeset for new stack v3 domain registry exports. |
| .changeset/eql-v3-prisma-next.md | Changeset describing prisma-next v3 surface (constructors, entry points, migrations). |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
coderdan
left a comment
There was a problem hiding this comment.
Really strong PR — disciplined commit sequence, green CI including live integration, and several things done exactly right: the domain catalog derived from DOMAIN_REGISTRY rather than hand-maintained (a new domain in stack flows through with zero prisma-next edits), the mark/collect/route seam sending every predicate operand through encryptQuery as a ciphertext-free $n::eql_v3.query_<domain> term, ord_term/ord_term_ore ordering, the self-parenthesised between, and the empty-needle containment guard matching the other adapters. The review asks below are consistency items, not correctness problems.
Requested changes
1. Free-text search must be match, not ilike 🔴
cipherstashIlike/cipherstashNotIlike lowering to eql_v3.contains is the exact category error we removed from the Drizzle and Supabase v3 surfaces (#588, #617): an ilike name promises SQL pattern semantics over what is fuzzy, order/multiplicity-insensitive, one-sided bloom token matching. v3 is a new opt-in surface — there's no continuity to preserve. Please name the operator match (plain match if Prisma's extension mechanics allow it; if a prefix is unavoidable see #3 below). If you want an ilike compat alias, follow the Supabase shim's pattern: deprecated, one-time warning, approximate-semantics disclaimer.
Two guards are also missing and need to ship with whatever the operator is called:
- Short-needle rejection — a needle below the tokenizer length blooms to nothing and can match every row.
matchNeedleErrorin@cipherstash/stack/adapter-kitexists for exactly this; both other adapters use it. - Wildcard handling — an ilike-shaped surface invites
'%foo%', and%gets tokenized as an ordinary character → silently wrong results. Strip leading/trailing%and reject interior%/_(seelikeNeedlein stack-supabase for the reference behavior).
2. Operator naming should line up with EQL, not the company
This is EQL — the naming should be informed by the eql_v3.* functions themselves (eq, neq, gt, gte, lt, lte, between, match, JSON contains) so the TS surface, the SQL it lowers to, and the EQL docs all use one vocabulary. cipherstashEq → the SQL reads eql_v3.eq(...); the prefix adds length without information. If the Prisma extension registry needs namespacing to avoid colliding with Prisma's native filter names, prefer an EQL-derived prefix (eqlEq, eqlMatch, …) over cipherstash*. The v2 surface keeps its existing names — this is about not carrying them onto the new surface.
3. Consume @cipherstash/stack/adapter-kit (and resolve the duplicated door)
This PR graduates DOMAIN_REGISTRY/factoryForDomain/stripDomainSchema to public eql/v3 while adapter-kit still exports the same symbols — two doors to the same registry, no pointer between them. adapter-kit is the seam built for first-party adapters (stack-drizzle and stack-supabase consume these exact symbols through it), and it now also carries the shared selector-path helpers and matchNeedleError this PR needs. Either import adapter-kit like the other adapters, or — if we decide graduation is right — do it properly in a coordinated change (pointer note in adapter-kit, migrate the other adapters' imports). Default to adapter-kit for this PR; the graduation can be its own discussion.
4. Adopt the shared test-kit family harness
The hand-rolled live suite is good, but it doesn't pin prisma-next against the same derived capability matrix and plaintext oracle as the other adapters. @cipherstash/test-kit's IntegrationAdapter + runFamilySuite gives you, per domain: the positive/negative op matrix derived from capabilities (flipping a capability flips a test), single-vs-bulk insert crossover, ordering assertions against the oracle, and absent/null handling — and every future extension to the kit then covers prisma-next for free. Implement a makePrismaNextAdapter() and run the family suites; keep your bespoke tests for the prisma-specific surfaces (middleware, codecs, envelope types) — that split is exactly how the other two adapters are organised.
5. Implement JSON selector querying the way Drizzle does (#623/#651)
The scope-out comment is honest, but the approach is already established — please add it here (or as an immediate follow-up PR if you prefer, with a tracking issue like #650):
- Path handling:
parseSelectorSegments/reconstructSelectorDocument/unsupportedLeafReasonare in adapter-kit now — shared validation, don't re-implement. - Lowering:
eql_v3.<op>(eql_v3.jsonb_path_query_first({{self}}, $sel::text), $needle::public.eql_v3_jsonb_entry)where$selis the bare selector hash fromencryptQuery(jsonPath, { queryType: 'searchableJson' })and$needleis INTERIM a storage encryption of the reconstructed{path: value}document (its ste_vec entry carries thec+hm/opthe comparison reads). Ciphertext-free RHS lands with cipherstash/protectjs-ffi#137 — carry the same interim comment Drizzle does. - Semantics to preserve:
neincludes absent-path rows (OR … IS NULL), scalar-leaf guard (orderingarm for gt/lt), and note the array-leaf behavior (a scalar needle does not match an array at the path — pinned live in stack-supabase's suite).
Happy to pair on any of these — the drizzle operators.ts selector block and the test-kit adapter implementations in stack-drizzle/stack-supabase integration/adapter.ts are the reference points.
…he eql/v3 graduation The branch had graduated DOMAIN_REGISTRY / factoryForDomain / stripDomainSchema onto the public eql/v3 barrel while adapter-kit still exported the same symbols — two doors to one registry. Revert the graduation and import through adapter-kit, the seam the Drizzle and Supabase adapters already consume (PR #655 review, item 3). Also make v3Authored return a fresh descriptor per invocation so a caller mutation cannot alter later contracts (review). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eqlMatch Rename the v3 query operations to the eql_v3.* function vocabulary they lower to — eqlEq/eqlNeq/eqlIn/eqlNotIn/eqlGt/eqlGte/eqlLt/eqlLte/ eqlBetween/eqlNotBetween/eqlJsonContains, ordering via eqlAsc/eqlDesc. The eql prefix (not bare eq/gt) is required because the framework's native scope-field methods already claim eq/neq/gt/…, which lower to plain SQL comparisons (PR #655 review, items 1+2). The v2 surface keeps its cipherstash* names; the sets are now disjoint. Free-text search is eqlMatch, not ilike: eql_v3.contains is fuzzy bloom token matching, not SQL pattern semantics. Two guards run before encryption: wildcard normalisation (leading/trailing % stripped, interior % or any _ rejected) and the shared adapter-kit matchNeedleError short-needle rejection. cipherstashNotIlike is removed outright — negating a may-false-positive bloom test silently drops matching rows. Type-level dispatch mirrors the split: v3 codec entries carry only cipherstash:v3-* marker traits, so eql* methods never surface on v2 columns and cipherstash* methods never surface on v3 columns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- wire-v3: v3ToDriver coerces JSON.stringify's undefined branch to null; v3FromDriver gains overloads so a nullable input yields a nullable result instead of laundering null into T. - sdk-adapter-v3: bulkDecrypt validates response cardinality (a truncated response would silently misalign plaintexts); the deliberate as-never bridges carry the repo's biome suppression. - pack meta: extensionPacks.cipherstash now registers the 40 v3 codec metadata instances + storage rows (derived from the catalog, never hand-listed) and the EncryptedNumber type import, so consumer contracts describe the codecs their fields use. - live suites: direct dotenv/config imports, canonical column-order binding in insertEncryptedRows (a differing key order could bind values to the wrong SQL columns), and migration-apply now uninstalls EQL v3 first so the customer-facing migration SQL genuinely executes on a reused database (test:live is serial now — the reinstall is destructive across files). - bundling-isolation: entry checks scan the complete import graph for every v2 wire/codec-factory marker, not just entry bodies. Verified live: 599 unit + 30 live-PG + 582 integration + 40 example-e2e + 7 README-walkthrough tests green against real ZeroKMS + Postgres. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement makePrismaNextAdapter() behind the shared @cipherstash/test-kit IntegrationAdapter seam and run runFamilySuite over every family — the same derived capability matrix, plaintext oracle, single-vs-bulk insert crossover, ordering assertions, and absent/null handling that pin the Drizzle and Supabase adapters (PR #655 review, item 4). Queries lower through the real v3 operator registry + postgres adapter + bulk-encrypt middleware; inserts run one-row vs multi-row INSERT statements, prisma-next's two batching shapes. New CI workflow mirrors the Drizzle job on both database variants. 582 tests green against real ZeroKMS + live Postgres. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Review feedback addressed — branch merged up to @coderdan's requested changes1. Free-text search is 2. EQL-derived operator naming — the v3 surface is now 3. adapter-kit, not graduation — the branch's 4. test-kit family harness — 5. JSON selector querying — taken as the sanctioned follow-up: tracking issue #677 captures the full approach from the review (adapter-kit Bot findingsFixed: fully-qualified Skipped with reason (inline replies on the threads): the 🤖 Generated with Claude Code |
…he eql/v3 graduation The branch had graduated DOMAIN_REGISTRY / factoryForDomain / stripDomainSchema onto the public eql/v3 barrel while adapter-kit still exported the same symbols — two doors to one registry. Revert the graduation and import through adapter-kit, the seam the Drizzle and Supabase adapters already consume (PR #655 review, item 3). Also make v3Authored return a fresh descriptor per invocation so a caller mutation cannot alter later contracts (review). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eqlMatch Rename the v3 query operations to the eql_v3.* function vocabulary they lower to — eqlEq/eqlNeq/eqlIn/eqlNotIn/eqlGt/eqlGte/eqlLt/eqlLte/ eqlBetween/eqlNotBetween/eqlJsonContains, ordering via eqlAsc/eqlDesc. The eql prefix (not bare eq/gt) is required because the framework's native scope-field methods already claim eq/neq/gt/…, which lower to plain SQL comparisons (PR #655 review, items 1+2). The v2 surface keeps its cipherstash* names; the sets are now disjoint. Free-text search is eqlMatch, not ilike: eql_v3.contains is fuzzy bloom token matching, not SQL pattern semantics. Two guards run before encryption: wildcard normalisation (leading/trailing % stripped, interior % or any _ rejected) and the shared adapter-kit matchNeedleError short-needle rejection. cipherstashNotIlike is removed outright — negating a may-false-positive bloom test silently drops matching rows. Type-level dispatch mirrors the split: v3 codec entries carry only cipherstash:v3-* marker traits, so eql* methods never surface on v2 columns and cipherstash* methods never surface on v3 columns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- wire-v3: v3ToDriver coerces JSON.stringify's undefined branch to null; v3FromDriver gains overloads so a nullable input yields a nullable result instead of laundering null into T. - sdk-adapter-v3: bulkDecrypt validates response cardinality (a truncated response would silently misalign plaintexts); the deliberate as-never bridges carry the repo's biome suppression. - pack meta: extensionPacks.cipherstash now registers the 40 v3 codec metadata instances + storage rows (derived from the catalog, never hand-listed) and the EncryptedNumber type import, so consumer contracts describe the codecs their fields use. - live suites: direct dotenv/config imports, canonical column-order binding in insertEncryptedRows (a differing key order could bind values to the wrong SQL columns), and migration-apply now uninstalls EQL v3 first so the customer-facing migration SQL genuinely executes on a reused database (test:live is serial now — the reinstall is destructive across files). - bundling-isolation: entry checks scan the complete import graph for every v2 wire/codec-factory marker, not just entry bodies. Verified live: 599 unit + 30 live-PG + 582 integration + 40 example-e2e + 7 README-walkthrough tests green against real ZeroKMS + Postgres. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement makePrismaNextAdapter() behind the shared @cipherstash/test-kit IntegrationAdapter seam and run runFamilySuite over every family — the same derived capability matrix, plaintext oracle, single-vs-bulk insert crossover, ordering assertions, and absent/null handling that pin the Drizzle and Supabase adapters (PR #655 review, item 4). Queries lower through the real v3 operator registry + postgres adapter + bulk-encrypt middleware; inserts run one-row vs multi-row INSERT statements, prisma-next's two batching shapes. New CI workflow mirrors the Drizzle job on both database variants. 582 tests green against real ZeroKMS + live Postgres. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
06df347 to
b6ff7c4
Compare
|
Heads-up: I rebased this branch onto the |
|
Branch note — what just happened with the history here Timeline of this morning's conflict, for anyone following along:
Resolution: aborted the duplicate-history merge, reset the branch to the rebased remote, cherry-picked the #684 fix on top (clean apply), re-validated (typecheck green with and without a built Current CI on Coordination ask: if the branch needs rebasing again, a heads-up first please — a force-push while someone holds unpushed commits reproduces exactly this duplicate-history merge. 🤖 Generated with Claude Code |
…MAIN_REGISTRY Codec ids are the DOMAIN_REGISTRY key verbatim (cipherstash/eql-v3/eql_v3_*@1) per the GA eql_v3_* domain naming; json is a first-class exposed domain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t test
The closed union infers eqlType from getEqlType()'s return type — the
stack's bundled d.ts drops the private definition field's type, so the
EqlTypeForColumn infer-D path widens to public.${string} downstream.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y gating Add cipherstashV3QueryOperations() (src/v3/operators-v3.ts): the EQL v3 operator set registered by the v3 extension descriptor only (decision 1b — same cipherstash* method names as v2, never co-registered; the flat OperationRegistry collision is pinned by test). Lowering matches the stack-drizzle v3 dialect byte-for-byte: operands are ciphertext-free query terms cast to the column domain's eql_v3.query_<domain> type (eql_v3.eq/neq/gt/gte/lt/lte/contains, self-parenthesised (gte AND lte) range, OR-of-eq membership), JSON containment is OPERATOR(public.@>) with the irregular ::eql_v3.query_jsonb cast (cipherstashJsonContains, empty-needle guarded), and ordering extracts eql_v3.ord_term / ord_term_ore by the domain's flavour via the free-standing cipherstashV3Asc/Desc helpers. Operands are bound as pg/text@1 params (bare $N; the template supplies the query cast — the column codec's storage-domain cast would fail the query term's CHECK) and each envelope is routing-key stamped plus marked with its queryType (markV3QueryTerm / v3QueryTermTypeOf), the seam Task 7's SDK adapter uses to route WHERE operands through encryptQuery instead of storage bulkEncrypt. Every operator gates on the concrete domain's catalog capabilities and throws the v3-owned EncryptionOperatorError naming column, domain, operator, and missing capability. v3 codec descriptors are pinned to cipherstash:*-only traits alongside the v2 equality-trait regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tor + cipherstashFromStackV3
- deriveStackSchemasV3(contractJson): walks the 0.14 namespace envelope
and maps each v3 column's public.eql_v3_* nativeType to its concrete
@cipherstash/stack/eql/v3 factory (capabilities are intrinsic to the
domain — no typeParams flags), preserving exact domain identity.
- createCipherstashV3Sdk(client, schemas): adapts the EncryptionV3
TypedEncryptionClient (structural surface, no casts) to the framework
CipherstashSdk. Consumes the Task-6 query-term seam: envelopes marked
by markV3QueryTerm route through encryptQuery — one batch crossing
per scalar flavour (stack-drizzle's inArray surface), single-call for
searchableJson (stack-drizzle's JSON containment path) — while
unmarked values take the storage bulkEncrypt path, position-stable.
- bulkEncryptMiddlewareV3 now collects pg/text@1-bound marked envelopes
(WHERE operands), forwards the envelope itself through the SDK seam,
and writes the returned term back as query-term JSONB text without
stamping it into the envelope's storage-ciphertext slot.
- createCipherstashV3RuntimeDescriptor({ sdk }): the v3 extension
descriptor under v3's own id/version (decision 1b — never
co-registered with v2; shared method names collide by design).
- assertV3SchemasAgree: override validation on EXACT domain identity
(integer_ord ≠ integer_ord_ore despite shared cast_as).
- cipherstashFromStackV3: the v3-only entry point — hard-errors on v2
cipherstash codec ids and on contracts with no v3 columns; returns
{ extensions, middleware, encryptionClient } like the v2 factory.
- Query-term seam (markV3QueryTerm / v3QueryTermTypeOf +
EncryptionOperatorError) extracted to src/v3/query-term.ts so the
middleware/adapter don't import the operator registry; operators-v3
re-exports it unchanged.
- Exports: v3 surface added to ./runtime and ./stack; new ./v3 subpath
(ESM, matching the package's existing ESM-only exports map).
- bundling-isolation: allow the v3 catalog chunk (pure domain metadata)
as a second cross-plane shared chunk alongside the v2 constants chunk.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…/eql Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ting Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bundling isolation (test/bundling-isolation.test.ts): - v3.js joins the entry existence check and is pinned against both the contract-space artefacts (RUNTIME_FORBIDDEN) and the v2 wire plane - content-fingerprinted wire markers (encodeEqlV2EncryptedWire / makeCipherstashCellCodec vs CipherstashV3CellCodec / createV3CodecDescriptors / bulkEncryptMiddlewareV3): no code-split chunk may reference both wire planes, the v3 entry graph never reaches the v2 codec-runtime chunk, and middleware.js (v2-only entry) never loads the v3 wire Live-PG suites (test/live/, self-skipping via describeLivePg when CS creds — env vars or ~/.cipherstash profile — or DATABASE_URL are absent; `pnpm test:live` convenience script added): - helpers: live-gate (skip posture), eql-v3 (advisory-locked installEqlV3IfNeeded over the Task-8 migration source SQL), harness (real cipherstashFromStackV3 + real bulkEncryptMiddlewareV3 driven through real execution plans, operator SELECTs lowered by the real postgres adapter, envelope fromInternal().decrypt() read path) - operators-live-pg: eq/ne/inArray, contains (ilike), gt/lte/between, date + timestamp ranges, ord_term ordering, JSON containment (@> with eql_v3.query_jsonb — ruled decision 2), per-family decrypt - operators-null-live-pg: NULL cells round-trip, null operands rejected - boolean-storage-live-pg: storage-only round-trip, operators refuse - bigint-live-pg: lossless bigint beyond MAX_SAFE_INTEGER, eq/range/order - migration-apply-live-pg: applied SQL is byte-identical to the shipped ops.json, invariant id, domains + eql_v3 schema, op postchecks, no add_search_config - bulk-encrypt-live-pg: real EncryptionV3 through the real middleware, JSONB cells, ciphertext stamping, decrypt round-trip - side-by-side-clients-live-pg: v2 and v3 clients coexisting in-process against one database, distinct extension identities and wires Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also a stack changeset for the new eql/v3 barrel exports, and the AGENTS.md repository-layout line updated for the v2/v3 split. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gaps surfaced by converting the example app and driving the real CLI +
runtime rather than unit fixtures:
- CodecTypes/QueryOperationTypes cover all 40 v3 codec ids with
trait-accurate operator visibility (cipherstash:v3-* marker traits;
cipherstashJsonContains v3-only; storage-only domains surface no
operator methods at the type level).
- The v3 runtime descriptor presents the pack id ('cipherstash') with
v3's own version — the runtime matches contract extensionPacks by
descriptor id, so the old distinct id failed startup with
RUNTIME.MISSING_EXTENSION_PACK.
- Every v3 codec id registers a control-plane expandNativeType hook
stripping the public. qualifier (the planner requires the hook for
typeParams-carrying columns; bare names match introspected udt_name).
No onFieldEvent — v3 emits zero add_search_config ops.
- The v3 bundle op is reclassified 'data': the integrity checker
rejects self-edges without a data-class op.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Schema: every column is a concrete public.eql_v3_* domain (EncryptedTextSearch / EncryptedDoubleOrd / EncryptedBigIntOrd / EncryptedDateOrd / EncryptedBoolean / EncryptedJson); wiring via cipherstashFromStackV3. Migrations regenerated from the v3 contract — initial app migration carries zero add_search_config ops; the cipherstash space ships both bundle baselines. E2E suites adapted to the v3 surface and green from a wiped database (7 files / 40 tests, live PG + ZeroKMS): equality/range/free-text tokens, order-term sorting, JSON @> containment (positive, negative, multi-key, nested, empty-needle rejection), lossless bigint beyond MAX_SAFE_INTEGER, and eql_v3_boolean's storage-only operator refusal pinned as a feature. README walkthrough e2e (repo-level) also green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he eql/v3 graduation The branch had graduated DOMAIN_REGISTRY / factoryForDomain / stripDomainSchema onto the public eql/v3 barrel while adapter-kit still exported the same symbols — two doors to one registry. Revert the graduation and import through adapter-kit, the seam the Drizzle and Supabase adapters already consume (PR #655 review, item 3). Also make v3Authored return a fresh descriptor per invocation so a caller mutation cannot alter later contracts (review). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eqlMatch Rename the v3 query operations to the eql_v3.* function vocabulary they lower to — eqlEq/eqlNeq/eqlIn/eqlNotIn/eqlGt/eqlGte/eqlLt/eqlLte/ eqlBetween/eqlNotBetween/eqlJsonContains, ordering via eqlAsc/eqlDesc. The eql prefix (not bare eq/gt) is required because the framework's native scope-field methods already claim eq/neq/gt/…, which lower to plain SQL comparisons (PR #655 review, items 1+2). The v2 surface keeps its cipherstash* names; the sets are now disjoint. Free-text search is eqlMatch, not ilike: eql_v3.contains is fuzzy bloom token matching, not SQL pattern semantics. Two guards run before encryption: wildcard normalisation (leading/trailing % stripped, interior % or any _ rejected) and the shared adapter-kit matchNeedleError short-needle rejection. cipherstashNotIlike is removed outright — negating a may-false-positive bloom test silently drops matching rows. Type-level dispatch mirrors the split: v3 codec entries carry only cipherstash:v3-* marker traits, so eql* methods never surface on v2 columns and cipherstash* methods never surface on v3 columns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- wire-v3: v3ToDriver coerces JSON.stringify's undefined branch to null; v3FromDriver gains overloads so a nullable input yields a nullable result instead of laundering null into T. - sdk-adapter-v3: bulkDecrypt validates response cardinality (a truncated response would silently misalign plaintexts); the deliberate as-never bridges carry the repo's biome suppression. - pack meta: extensionPacks.cipherstash now registers the 40 v3 codec metadata instances + storage rows (derived from the catalog, never hand-listed) and the EncryptedNumber type import, so consumer contracts describe the codecs their fields use. - live suites: direct dotenv/config imports, canonical column-order binding in insertEncryptedRows (a differing key order could bind values to the wrong SQL columns), and migration-apply now uninstalls EQL v3 first so the customer-facing migration SQL genuinely executes on a reused database (test:live is serial now — the reinstall is destructive across files). - bundling-isolation: entry checks scan the complete import graph for every v2 wire/codec-factory marker, not just entry bodies. Verified live: 599 unit + 30 live-PG + 582 integration + 40 example-e2e + 7 README-walkthrough tests green against real ZeroKMS + Postgres. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… contract
Rename every v3 operator call to the EQL-derived vocabulary (eqlEq,
eqlMatch, eqlAsc/eqlDesc, …), fully qualify the README's domain table
(public.eql_v3_*), describe free-text search as fuzzy bloom token
matching under its real needle semantics, drop the removed
cipherstashNotIlike coverage, add a direct dotenv/config import to the
str-range suite, and remove the type-erasing probe assertions in the
bool suite. pnpm emit regenerates contract.{json,d.ts} — the
extensionPacks.cipherstash block now carries the 40 v3 codec instances
and storage rows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement makePrismaNextAdapter() behind the shared @cipherstash/test-kit IntegrationAdapter seam and run runFamilySuite over every family — the same derived capability matrix, plaintext oracle, single-vs-bulk insert crossover, ordering assertions, and absent/null handling that pin the Drizzle and Supabase adapters (PR #655 review, item 4). Queries lower through the real v3 operator registry + postgres adapter + bulk-encrypt middleware; inserts run one-row vs multi-row INSERT statements, prisma-next's two batching shapes. New CI workflow mirrors the Drizzle job on both database variants. 582 tests green against real ZeroKMS + live Postgres. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4c7abac to
8db7196
Compare
|
Heads-up @calvinbrewer — I rebased this branch onto current
Verified locally: prisma-next builds, 599 unit tests pass (30 live skipped without creds). No content changes from me — pure rebase. If you have local work on top, re-stack on |
The `searchableJson` encryptQuery call spreads its two `as never` operands across separate argument lines, so the single `// biome-ignore` above the call reached neither cast — both still tripped the no-type-erasing-assertions plugin, and the ignore itself was flagged as an ineffective suppression. Put one ignore directly above each cast line. Warning-only (CI gates on errors), but resolves the CodeRabbit thread and the dangling ineffective-suppression note. The scalar branch was already correct. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
coderdan
left a comment
There was a problem hiding this comment.
Epic. Really great work. Excited to get this over the line.
…sma-next skill
SKILL_MAP was typed Record<Integration, ...> but omitted the 'prisma-next'
key, so installSkills and the AGENTS.md builder hit SKILL_MAP['prisma-next']
= undefined and threw 'not iterable' for any repo the CLI detected as Prisma
Next. tsc catches the missing key, but the build (tsup) transpiles without
type-checking, so the error shipped in rc.2 — reachable by any Prisma user
running stash init (Prisma Next is auto-detected).
- Add the prisma-next SKILL_MAP entry.
- Route both consumers through a new skillsFor() helper that degrades an
unmapped integration to the base skill set (stash-encryption + stash-cli)
instead of crashing — belt-and-braces for the next Integration variant,
since tsup won't type-check the map.
- New skills/stash-prisma-next/SKILL.md documenting the EQL v3 Prisma Next
surface: domain-named column types (EncryptedTextSearch, EncryptedDoubleOrd,
...), cipherstashFromStackV3 wiring, runtime envelopes, the eql* operators,
and EQL install via prisma-next migration apply (not stash eql install).
- Tests: SKILL_MAP has a non-empty entry for every integration; skillsFor
falls back for an unmapped one; buildAgentsMdBody('prisma-next', ...) inlines
the skill (the exact call that used to crash); installSkills copies it.
- Meta: AGENTS.md skill-map table + skills list updated.
Companion to #655 (the @cipherstash/prisma-next EQL v3 package) — that PR
releases the package but touches no CLI code, so this crash survives it.
Stacked on feat/prisma-next-eql-v3; retarget to main when #655 lands.
Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
…(rc.2 M1) (#683) * fix(cli): stop init/plan/impl crashing on Prisma Next; ship stash-prisma-next skill SKILL_MAP was typed Record<Integration, ...> but omitted the 'prisma-next' key, so installSkills and the AGENTS.md builder hit SKILL_MAP['prisma-next'] = undefined and threw 'not iterable' for any repo the CLI detected as Prisma Next. tsc catches the missing key, but the build (tsup) transpiles without type-checking, so the error shipped in rc.2 — reachable by any Prisma user running stash init (Prisma Next is auto-detected). - Add the prisma-next SKILL_MAP entry. - Route both consumers through a new skillsFor() helper that degrades an unmapped integration to the base skill set (stash-encryption + stash-cli) instead of crashing — belt-and-braces for the next Integration variant, since tsup won't type-check the map. - New skills/stash-prisma-next/SKILL.md documenting the EQL v3 Prisma Next surface: domain-named column types (EncryptedTextSearch, EncryptedDoubleOrd, ...), cipherstashFromStackV3 wiring, runtime envelopes, the eql* operators, and EQL install via prisma-next migration apply (not stash eql install). - Tests: SKILL_MAP has a non-empty entry for every integration; skillsFor falls back for an unmapped one; buildAgentsMdBody('prisma-next', ...) inlines the skill (the exact call that used to crash); installSkills copies it. - Meta: AGENTS.md skill-map table + skills list updated. Companion to #655 (the @cipherstash/prisma-next EQL v3 package) — that PR releases the package but touches no CLI code, so this crash survives it. Stacked on feat/prisma-next-eql-v3; retarget to main when #655 lands. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w * docs(skills): fix three factual errors in stash-prisma-next flagged on #683 - init does NOT scaffold wiring for Prisma Next (it skips the encryption-client scaffold — schema is derived from contract.json); say so instead of 'scaffolds the wiring'. (Copilot) - eqlAsc/eqlDesc are free functions taking the column expression (eqlAsc(u.salary)), not zero-arg methods; fix the ORDER BY table row to match the code example. (Copilot) - install-skills.ts: the skillsFor regression guard is a test asserting SKILL_MAP has an entry for every value in a maintained ALL_INTEGRATIONS list, not one that reads the provider registry; correct the comment. (Copilot) Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w * feat(cli): refuse `stash eql install` in a Prisma Next project (#683) Prisma Next installs the EQL bundle through its own migration ledger (migrations/cipherstash/ applied by `prisma-next migration apply`), so the standalone installer is the wrong tool — running it applies EQL out-of-band from the framework's ledger. `stash init --prisma-next` already skips the installer; this closes the manual-invocation hole. - New pure `prismaNextInstallGuard(cwd, { force })` (mirrors validateInstallFlags): returns actionable guidance when a Prisma Next project is detected and --force isn't set, else null. Called early in installCommand, before any DB I/O — fails fast with a pointer to `prisma-next migration apply`. - --force overrides (deliberate standalone install escape hatch). - messages.eql.prismaNextDetected as the stable leader. - Tests: 4 unit (config-file detection, dep detection, --force override, non-prisma-next passthrough) + 1 pty-less e2e proving the wiring (refuses, exit 1, no DB). Skill gotcha + changeset updated. Addresses CJ/Dan's #683 review ask to enforce this in the CLI. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
…QL install) First of the three PRs in #690: make migration-first, ORM-agnostic EQL v3 install the front door, so every integration sources install SQL from one place (the CLI's bundled variants) instead of vendoring its own. This is the pattern that keeps Drizzle Supabase-safe; prisma-next's superuser-on-Supabase failure is fixed by the stacked follow-ups (`--prisma` emitter + removing prisma-next's baked baseline), which land on top of the prisma-next EQL v3 work (#655). `stash eql migration --drizzle [--supabase] [--name] [--out] [--dry-run]`: - Generates a Drizzle custom migration (via `drizzle-kit generate --custom`, then injects the SQL) carrying the bundled EQL **v3** install script + the `cs_migrations` tracking schema — one `drizzle-kit migrate` does everything `stash encrypt …` needs. - `--supabase` appends the v3 role grants (`eql_v3` + `eql_v3_internal` → anon/authenticated/service_role), matching `stash eql install --supabase`. - v3 only — no `--eql-version` (prisma-next never shipped v2). - `--prisma` is registered but fails with a pointer to #690 until the stacked PR. The SQL assembly (`buildEqlV3MigrationSql`) is a pure, unit-tested function; the drizzle-kit scaffold/inject orchestration reuses the proven helpers from the v2 `install --drizzle` path (`findGeneratedMigration`, `cleanupMigrationFile`, now exported). Registry + dispatch + help + `stash-cli`/`stash-drizzle` skills updated. Tests: 4 new unit (bundle present, grants gated on --supabase, tracking schema), 544 CLI unit green, manifest resolves the command, biome clean. Changeset: stash minor. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
…shipped Dan's review on #691: the `stash eql migration --prisma` copy said the flag "ships with prisma-next EQL v3 support" / "not available yet" — but that support has shipped (#655/#683/#685), and Prisma Next installs EQL v3 through its OWN migration system (`prisma-next migration apply`), not through this command. Reframed all five spots to current reality (not "coming soon", but "use prisma-next migration apply"): the user-facing message + its doc comment, the `eqlMigrationCommand` code comment, the `--prisma` flag help in the registry, the changeset, and the stash-cli skill (which ships to customers). No behaviour change — `--prisma` still fails with a pointer. Build + 566 tests green. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
…QL install) (#691) * feat(cli): add `stash eql migration --drizzle` (v3, migration-first EQL install) First of the three PRs in #690: make migration-first, ORM-agnostic EQL v3 install the front door, so every integration sources install SQL from one place (the CLI's bundled variants) instead of vendoring its own. This is the pattern that keeps Drizzle Supabase-safe; prisma-next's superuser-on-Supabase failure is fixed by the stacked follow-ups (`--prisma` emitter + removing prisma-next's baked baseline), which land on top of the prisma-next EQL v3 work (#655). `stash eql migration --drizzle [--supabase] [--name] [--out] [--dry-run]`: - Generates a Drizzle custom migration (via `drizzle-kit generate --custom`, then injects the SQL) carrying the bundled EQL **v3** install script + the `cs_migrations` tracking schema — one `drizzle-kit migrate` does everything `stash encrypt …` needs. - `--supabase` appends the v3 role grants (`eql_v3` + `eql_v3_internal` → anon/authenticated/service_role), matching `stash eql install --supabase`. - v3 only — no `--eql-version` (prisma-next never shipped v2). - `--prisma` is registered but fails with a pointer to #690 until the stacked PR. The SQL assembly (`buildEqlV3MigrationSql`) is a pure, unit-tested function; the drizzle-kit scaffold/inject orchestration reuses the proven helpers from the v2 `install --drizzle` path (`findGeneratedMigration`, `cleanupMigrationFile`, now exported). Registry + dispatch + help + `stash-cli`/`stash-drizzle` skills updated. Tests: 4 new unit (bundle present, grants gated on --supabase, tracking schema), 544 CLI unit green, manifest resolves the command, biome clean. Changeset: stash minor. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w * fix(cli): harden `stash eql migration` per review (#691) Addresses the review on #691 — correctness, security, telemetry, and coverage: - Run the PROJECT-LOCAL drizzle-kit (`pnpm exec` / `npx --no-install`), not the `pnpm dlx`/`yarn dlx` download-and-run form, so it resolves the project's own drizzle.config.ts + schema. New shared `execCommand`/`execArgv` in init/utils (setup-prompt's private copy folded in). - Invoke via `spawnSync` with an argv array instead of `execSync` on a shell string — a `--name` with spaces or shell metacharacters is now one inert token (no word-splitting, no command injection). Plus a `[\w-]+` name guard. - Pass `--out` through to `drizzle-kit generate` (always) so the flag actually steers where the migration is written, and our lookup can't miss it. - Validation exits and every abort throw `CliExit(1)` instead of `process.exit`, so the telemetry `finally` runs and the branches are unit-testable. - Guard `loadBundledEqlSql` (corrupt bundle → clean error, not a fatal trace), add the missing `p.intro`, and call `printNextSteps()` so the now-preferred install path isn't less helpful than `eql install`. - HELP banner lists `eql migration`. Tests: 14 migration unit (target selection incl. `--supabase`-alone, name guard, dry-run, argv threading, non-zero drizzle-kit, write-failure cleanup, SQL order), 3 `findGeneratedMigration` unit (now public API), e2e smoke + `eql migration --help`. 557 unit / 65 e2e green, code:check clean. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w * fix(cli): bun exec must not surprise-download; drop type-erased test cast CodeRabbit review on #691: - `execArgv`/`execCommand` bun case emitted `bun x`, which auto-installs a missing binary from npm — contradicting the "run a project-local binary, never surprise-download" contract these helpers exist to honour (the npm case already passes `--no-install`). Bun supports `--no-install`; pass it for both the argv and shell forms. Updated the setup-prompt assertion to match. - migration.test.ts: replaced the type-erasing `undefined as unknown as writeFileSync` hoisted-mock placeholder with a throwing placeholder cast to the specific type — fails loud if the mock factory never runs, and drops the `as unknown` (test files are plugin-exempt, but this is cleaner). CLI build + 566 unit tests green; biome clean. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w * docs(cli): correct stale `--prisma` copy now that prisma-next v3 has shipped Dan's review on #691: the `stash eql migration --prisma` copy said the flag "ships with prisma-next EQL v3 support" / "not available yet" — but that support has shipped (#655/#683/#685), and Prisma Next installs EQL v3 through its OWN migration system (`prisma-next migration apply`), not through this command. Reframed all five spots to current reality (not "coming soon", but "use prisma-next migration apply"): the user-facing message + its doc comment, the `eqlMigrationCommand` code comment, the `--prisma` flag help in the registry, the changeset, and the stash-cli skill (which ships to customers). No behaviour change — `--prisma` still fails with a pointer. Build + 566 tests green. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w * docs(cli): correct `--prisma` copy to match #690 (planned emitter, not "use prisma-next") My previous pass framed `--prisma` as "not supported — use `prisma-next migration apply`", which contradicts #690: the `--prisma` emitter IS a planned follow-up (PR3) that writes the install migration in the framework `Migration` shape and lets prisma-next DROP its baked install baseline. Pointing users at `prisma-next migration apply` is the very approach #690 replaces. Reframe all five spots to "not available yet — the Prisma Next emitter is a follow-up tracked in #690; use `--drizzle` today": the message + its doc comment, the `eqlMigrationCommand` code comment, the `--prisma` flag help, the changeset, and the stash-cli skill. (The `stash eql install` guard + stash-prisma-next skill keep their `prisma-next migration apply` wording — that correctly describes the current baked-baseline install, which is a different thing from this CLI emitter.) No behaviour change. Build + 566 tests green. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
Summary by CodeRabbit
New Features
Bug Fixes
Documentation