Skip to content

Prisma Next 0.16, payload-identifier routing, and digest-verified baked EQL migration artefacts#763

Merged
coderdan merged 7 commits into
mainfrom
feat/prisma-next-0-16-baked-eql
Jul 23, 2026
Merged

Prisma Next 0.16, payload-identifier routing, and digest-verified baked EQL migration artefacts#763
coderdan merged 7 commits into
mainfrom
feat/prisma-next-0-16-baked-eql

Conversation

@coderdan

@coderdan coderdan commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Three related pieces of work on the Prisma Next integration, in review order per commit:

1. Upgrade to Prisma Next 0.16 (from 0.14)

All 31 @prisma-next/* pins move to 0.16.0 in lockstep. The CipherStash encryption surface is unchanged. Notable:

  • createNamespace is now required in prismaContract(...) (0.15 stopped materialising a placeholder namespace) — added to both configs and documented in the bundled stash-prisma-next skill.
  • The PSL interpreter test moves to the 0.15 CST symbol-table input (parse + buildSymbolTable).
  • Contracts re-emitted: postgres-schema namespace kind, StorageColumnTypes maps, scalarList capability, discrete FK/index entities. Storage hashes unchanged.

Verified: unit + live-PG (27) + integration (595) suites green with a ~/.cipherstash device profile and the local compose Postgres.

2. Route v3 decoding from the EQL payload's own identifier

Every EQL v3 payload carries its required i: {"t", "c"} identifier, and ZeroKMS commits the cell key to it — making it the authoritative routing source, not the query AST. decode now reads it first (projected-column context kept as fallback for non-v3 documents), and decodeJson builds fully-routed envelopes from it instead of throwing.

This unblocks relation include() (the SQL ORM decodes included cells through decodeJson with no column ref available) and aggregate/computed projections. A two-model include() e2e test remains a follow-up (needs the db-ref planning workflow — see #762).

3. Bake the EQL install SQL into migration artefacts, digest-verified

Replaces the runtime-injection design. The framework applies extension migrations from on-disk vendored copies without the descriptor, and the CLI seed phase never refreshes an existing package directory — so injection's environment-dependent hashes orphaned every consumer's vendored copy on each EQL bump (PN-MIG-5002), and recomputing the hash from installed content meant tampering could never be detected.

Now each migration's self-emit embeds readVerifiedInstallSql() — refused unless its sha256 matches @cipherstash/eql's releaseManifest.installSqlSha256. One content-addressed identity flows from this repo's git history through the npm tarball into consumers' vendored copies and the DB ledger. EQL upgrades ship as new append-only migration directories (the existing invariant-edge pattern).

CI guards added:

  • Provenance pin — committed SQL digest must equal the installed manifest's; an EQL bump without the matching migration fails.
  • Frozen-hash pins — published migration hashes are literals; any byte change to published history fails.
  • Vendored-space parity — the example's migrations/cipherstash/ must be byte-identical to the shipped artefacts (catches the exact staleness class that wedged it).

Proven end-to-end: seed phase materialises byte-identical copies; fresh-DB migrate installs EQL 3.0.2 and records both invariants; a tampered vendored ops.json (injected DROP TABLE) is rejected by hash verification before execution; example e2e (40), live-PG (27), and unit (341) suites green. dist/control.js grows to ~5 MB (inlined artefacts, control-plane entry only — runtime entries unchanged, pinned by the bundling-isolation test).

Also fixes the upgrade migration's stale describe() space hashes and the eql-3.0.03.0.2 label drift.

rc consumers (none external today): delete migrations/cipherstash/ and re-run prisma-next migration plan once — the seed phase regenerates it byte-identical.

Follow-ups tracked in #762.

Summary by CodeRabbit

  • New Features
    • Improved encrypted-cell decoding by deriving routing from the EQL v3 payload identifier, improving decryption in ORM includes, JSON aggregates, and computed projections.
    • Added working JSON-plane decoding for EQL v3 encrypted values.
    • Added Prisma/SQL capability support for scalarList and expanded generated storage type mappings.
  • Bug Fixes
    • Migrations now use digest-verification for embedded EQL v3 install SQL to better detect mismatches and tampering.
  • Documentation
    • Updated guidance for Prisma Next 0.16, including required createNamespace setup and post-upgrade regeneration expectations; clarifies changes when upgrading EQL v3 and migration publication behavior.

Closes #765.

coderdan added 3 commits July 22, 2026 21:33
All @prisma-next/* deps move 0.14.0 -> 0.16.0 in lockstep. prismaContract
now requires the target's createNamespace factory (0.15 stopped
materialising a placeholder namespace) — added to both configs and the
bundled skill. The PSL interpreter test moves to the CST symbol-table
input. Contracts re-emitted: postgres-schema namespace kind,
StorageColumnTypes maps, scalarList capability; storage hashes unchanged.
…ifier

Every EQL v3 payload carries its required i: {t, c} identifier, and
ZeroKMS commits the cell key to it — the authoritative routing source.
decode now reads it first (ctx.column kept as fallback for non-v3
documents), and decodeJson builds fully-routed envelopes from it instead
of throwing, unblocking relation include() and aggregate projections.
…est-verified

Replaces runtime injection: the framework applies extension migrations
from on-disk vendored copies without the descriptor, and the seed phase
never refreshes an existing package dir, so injection's environment-
dependent hashes orphaned every consumer copy on each EQL bump
(PN-MIG-5002) and the recomputed hash laundered tampering.

Each migration self-emit now embeds readVerifiedInstallSql() — refused
unless sha256 matches @cipherstash/eql's releaseManifest.installSqlSha256.
One content-addressed identity flows from git through npm to vendored
copies and the DB ledger. CI guards: provenance pin (committed SQL digest
== installed manifest), frozen published-migration hashes, and byte-parity
between the example's vendored space and the shipped artefacts. The
example's stale vendored space is regenerated (stale v2 dir removed).
Also fixes the upgrade migration's stale describe() space hashes.

Follow-ups tracked in #762.
@coderdan
coderdan requested a review from a team as a code owner July 22, 2026 11:34
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 45c8d07

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

This PR includes changesets to release 11 packages
Name Type
@cipherstash/prisma-next Minor
stash Minor
@cipherstash/prisma-next-example Patch
@cipherstash/basic-example Patch
@cipherstash/e2e Patch
@cipherstash/stack Minor
@cipherstash/stack-drizzle Minor
@cipherstash/stack-supabase Minor
@cipherstash/wizard Minor
@cipherstash/bench Patch
@cipherstash/test-kit Patch

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

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

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 49 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 Plus

Run ID: d295964f-a3bc-492e-adea-d89f84235a2e

📥 Commits

Reviewing files that changed from the base of the PR and between 43edaf9 and 45c8d07.

📒 Files selected for processing (18)
  • .changeset/eql-baked-digest-verified.md
  • .changeset/prisma-next-migrate-command.md
  • examples/prisma/prisma-next.config.ts
  • examples/prisma/test/e2e/vitest.config.ts
  • packages/cli/src/cli/registry.ts
  • packages/cli/src/commands/db/__tests__/prisma-next-install-guard.test.ts
  • packages/cli/src/commands/db/install.ts
  • packages/cli/src/commands/init/__tests__/init-command.test.ts
  • packages/cli/src/commands/init/index.ts
  • packages/cli/src/commands/init/providers/__tests__/prisma-next.test.ts
  • packages/cli/src/commands/init/providers/prisma-next.ts
  • packages/cli/src/commands/init/steps/install-eql.ts
  • packages/cli/tests/e2e/eql-install-prisma-next.e2e.test.ts
  • packages/prisma-next/README.md
  • packages/prisma-next/src/v3/codec-runtime-v3.ts
  • packages/prisma-next/test/v3/codec-runtime-v3.test.ts
  • skills/stash-cli/SKILL.md
  • skills/stash-prisma-next/SKILL.md
📝 Walkthrough

Walkthrough

This PR upgrades Prisma Next dependencies and contracts, embeds digest-verified EQL v3 SQL in published migrations, adds migration parity and integrity checks, and changes EQL v3 decoding to derive routing from payload identifiers.

Changes

Prisma Next 0.16 contract and configuration

Layer / File(s) Summary
Dependency and namespace configuration
packages/prisma-next/package.json, examples/prisma/package.json, packages/prisma-next/prisma-next.config.ts, examples/prisma/prisma-next.config.ts
Prisma Next dependencies move to 0.16, and createNamespace is wired to postgresCreateNamespace.
Generated contract updates
packages/prisma-next/src/contract.*, examples/prisma/src/prisma/contract.*, examples/prisma/migrations/cipherstash/contract.json
Contracts add storage type maps and scalarList, change the namespace kind to postgres-schema, and remove EQL v2 mappings and the EQL v2 configuration model.
PSL validation and guidance
packages/prisma-next/test/psl-interpretation.test.ts, skills/stash-prisma-next/SKILL.md, .changeset/prisma-next-0-16.md
PSL tests use CST symbol tables and target configuration; documentation records the required namespace factory and regenerated contract changes.

Digest-verified EQL v3 migrations

Layer / File(s) Summary
Emit-time SQL embedding and descriptor wiring
packages/prisma-next/src/migration/eql-bundle-v3.ts, packages/prisma-next/src/exports/control.ts, packages/prisma-next/migrations/*/migration.ts
Install SQL is digest-verified and embedded in migration operations; descriptors use committed metadata and operations without runtime injection.
Published migration references
packages/prisma-next/migrations/*/migration.json, examples/prisma/migrations/cipherstash/*/migration.json, examples/prisma/migrations/cipherstash/refs/head.json
Baseline and upgrade hashes and references are updated for the baked artifacts.
Artifact integrity and parity tests
packages/prisma-next/test/v3/migration-v3.test.ts, packages/prisma-next/test/v3/vendored-space-parity.test.ts, packages/prisma-next/test/bundling-isolation.test.ts, packages/prisma-next/test/descriptor.test.ts, .changeset/eql-baked-digest-verified.md
Tests verify manifest digests, frozen hashes, byte-preserved descriptor artifacts, vendored parity, and runtime bundle isolation.

EQL v3 payload routing

Layer / File(s) Summary
Payload-derived decode routing
packages/prisma-next/src/v3/codec-runtime-v3.ts, packages/prisma-next/test/v3/codec-runtime-v3.test.ts, .changeset/v3-payload-routing.md
decode derives table and column from payload identifier i, falls back to projected context, and decodeJson builds routed encrypted envelopes while rejecting invalid identifiers.

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

Possibly related issues

Possibly related PRs

Suggested reviewers: calvinbrewer

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the three main changes: Prisma Next 0.16 upgrade, payload-identifier routing, and baked digest-verified EQL migrations.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/prisma-next-0-16-baked-eql

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

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@packages/prisma-next/src/v3/codec-runtime-v3.ts`:
- Around line 291-312: Update decodeJson in
packages/prisma-next/src/v3/codec-runtime-v3.ts to return null immediately when
the JSON value is null, matching decode’s nullable-column behavior before
routingKeyFromPayload runs. Add coverage in
packages/prisma-next/test/v3/codec-runtime-v3.test.ts at lines 351-361 verifying
decodeJson(null) round-trips as null; no other sites require changes.

In `@packages/prisma-next/test/psl-interpretation.test.ts`:
- Around line 17-20: Move packages/prisma-next/test/psl-interpretation.test.ts
into the appropriate __tests__/ directory and update its relative imports and
test configuration. Move
packages/prisma-next/test/v3/vendored-space-parity.test.ts under __tests__/ as
well, adjusting all relative paths for the new location while preserving both
suites’ behavior.
🪄 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: 7078b56a-8625-4946-8ae2-a8a50627609d

📥 Commits

Reviewing files that changed from the base of the PR and between 2aeba94 and cd77afc.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (33)
  • .changeset/eql-baked-digest-verified.md
  • .changeset/prisma-next-0-16.md
  • .changeset/v3-payload-routing.md
  • examples/prisma/migrations/cipherstash/20260601T0000_install_eql_bundle/migration.json
  • examples/prisma/migrations/cipherstash/20260601T0000_install_eql_bundle/ops.json
  • examples/prisma/migrations/cipherstash/20260601T0100_install_eql_v3_bundle/migration.json
  • examples/prisma/migrations/cipherstash/20260601T0100_install_eql_v3_bundle/ops.json
  • examples/prisma/migrations/cipherstash/20260720T0000_upgrade_eql_v3_3_0_2/migration.json
  • examples/prisma/migrations/cipherstash/contract.json
  • examples/prisma/migrations/cipherstash/refs/head.json
  • examples/prisma/package.json
  • examples/prisma/prisma-next.config.ts
  • examples/prisma/src/prisma/contract.d.ts
  • examples/prisma/src/prisma/contract.json
  • packages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/migration.json
  • packages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/migration.ts
  • packages/prisma-next/migrations/20260601T0100_install_eql_v3_bundle/ops.json
  • packages/prisma-next/migrations/20260720T0000_upgrade_eql_v3_3_0_2/migration.json
  • packages/prisma-next/migrations/20260720T0000_upgrade_eql_v3_3_0_2/migration.ts
  • packages/prisma-next/migrations/20260720T0000_upgrade_eql_v3_3_0_2/ops.json
  • packages/prisma-next/package.json
  • packages/prisma-next/prisma-next.config.ts
  • packages/prisma-next/src/contract.d.ts
  • packages/prisma-next/src/contract.json
  • packages/prisma-next/src/exports/control.ts
  • packages/prisma-next/src/migration/eql-bundle-v3.ts
  • packages/prisma-next/src/v3/codec-runtime-v3.ts
  • packages/prisma-next/test/bundling-isolation.test.ts
  • packages/prisma-next/test/psl-interpretation.test.ts
  • packages/prisma-next/test/v3/codec-runtime-v3.test.ts
  • packages/prisma-next/test/v3/migration-v3.test.ts
  • packages/prisma-next/test/v3/vendored-space-parity.test.ts
  • skills/stash-prisma-next/SKILL.md
💤 Files with no reviewable changes (1)
  • examples/prisma/migrations/cipherstash/20260601T0000_install_eql_bundle/migration.json

Comment thread packages/prisma-next/src/v3/codec-runtime-v3.ts
Comment thread packages/prisma-next/test/psl-interpretation.test.ts
Addresses code-review findings on the baked-EQL work:

- migration-v3.test.ts: the provenance pin compared each migration's baked
  SQL digest against the LIVE @cipherstash/eql manifest, so it self-
  destructed on the next EQL bump (frozen 3.0.2 bytes can't match a 3.0.3
  manifest) — pushing a maintainer toward re-emitting the published
  baseline. Reworked to a PUBLISHED_MIGRATIONS table with per-migration
  FROZEN digests (identity + baked-SQL, tied to each migration's own
  release) plus one bump-safe lockstep check (the installed release must be
  baked by SOME published migration) and an on-disk completeness check (no
  unpinned or stale migration dirs).

- descriptor.test.ts: the 'injects the runtime EQL install SQL' test still
  named and described the deleted sentinel-injection design and compared
  against a live readInstallSql() (also bump-unsafe). Rewritten to assert
  the op carries the baked bundle verbatim, without the live comparison;
  dropped the now-unused import.

- DEVELOPING.md: refreshed the sections that still documented the sentinel/
  runtime-injection design and instructed re-emitting published migrations
  (the exact action the new doctrine and frozen-hash guard forbid); added
  the second (upgrade) migration to the layout and invariants.

Reviewed in this branch; follow-ups (#4/#6/#8 payload-routing edges,
efficiency items) tracked separately.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@packages/prisma-next/test/descriptor.test.ts`:
- Around line 120-129: The descriptor test’s loose substring and size checks do
not verify the exact migration bundle. Update the assertions around installOp
and its execute SQL to reuse the frozen SHA-256/digest verification established
by migration-v3.test.ts, comparing the embedded SQL against the expected release
provenance without calling readInstallSql() or reading the installed package at
runtime.
🪄 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: e96a0ffb-1adb-43ed-afdc-ae47ebe331c1

📥 Commits

Reviewing files that changed from the base of the PR and between cd77afc and f395dde.

📒 Files selected for processing (3)
  • packages/prisma-next/DEVELOPING.md
  • packages/prisma-next/test/descriptor.test.ts
  • packages/prisma-next/test/v3/migration-v3.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/prisma-next/test/v3/migration-v3.test.ts

Comment thread packages/prisma-next/test/descriptor.test.ts
coderdan added 2 commits July 22, 2026 22:37
CodeRabbit flagged decodeJson(null) as throwing. It is unreachable: the
SQL runtime null-guards before both codec methods — decodeField before
decode, and sql-orm-client's include-decode loop (plus its many-typed
element path) before decodeJson — so a NULL cell never reaches the codec.
Null-handling is the runtime's contract; returning null here would also
break the framework's decodeJson(json): TInput envelope signature.
Comment only, no behavior change.
…t migrate'

Prisma Next has no 'migration apply' subcommand — the apply verb is the
top-level 'prisma-next migrate' ('migration' only groups plan/new/show/
status/log/list/graph/check). Running the prescribed 'migration apply'
errors: 'Unknown command: apply. Use prisma-next migrate --to <contract>'.

Fixes the stale name in the stash-prisma-next and stash-cli skills, the
@cipherstash/prisma-next README, and — user-visibly — stash init
--prisma-next's printed next-steps, the init flag help, and the stash eql
install Prisma-Next refusal message. Updated the CLI unit + e2e assertions
that pinned the old string. Historical CHANGELOG entries left as-is.

Surfaced by the rc.4 skilltester run (found at PN 0.14.0, confirmed 0.16.0);
tracked with the other skilltester findings in a follow-up issue.

@freshtonic freshtonic 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.

Reviewed by Claude Code on behalf of James Sadler.

Verdict: Approve

I reviewed all three bundled concerns with a focus on the digest verification, payload-identifier routing, and the dependency upgrade. The security-sensitive code is correct and well-tested. No blocking issues.

Digest verification (eql-bundle-v3.ts) — correct

  • assertInstallSqlDigest computes sha256 over the install SQL and compares against releaseManifest.installSqlSha256, throwing on mismatch (fails closed). Right algorithm, right bytes, right direction.
  • readVerifiedInstallSql() is the sole sanctioned emit source, so an ops.json can never be baked from bytes the pinned release does not attest to. Consistency of the digest computation is proven by assertInstallSqlDigest(readInstallSql()) passing in CI.
  • Apply-time integrity correctly hands off to the framework's existing verifyMigrationHash over the on-disk bytes (the baked SQL is inside ops.json, so tampering changes the migrationHash). The e2e tamper test (injected DROP TABLE rejected before execution) confirms the trust chain end-to-end.
  • The provenance test suite is well-designed: per-migration FROZEN literals (migrationHash + installSqlSha256) tied to each migration's OWN release, a completeness check against the on-disk directory set, and a single bump-safe LOCKSTEP check (.toContain(releaseManifest.installSqlSha256)). An EQL bump without a matching re-emit fails, while historical migrations keep their frozen digests. The committed migration.json hashes (2c873907…, 7bb96043…) match the frozen literals.

Payload-identifier routing (codec-runtime-v3.ts) — cryptographically sound

  • Reading (t, c) from the payload's own i identifier is authoritative because ZeroKMS commits the cell key to that identifier: a relocated/tampered payload fails to decrypt (fails closed). Trusting i is therefore no weaker than trusting the query AST, and it unblocks aggregates, computed projections, and decodeJson (relation includes).
  • No cross-tenant risk: the codec instance closes over a fixed this.#sdk (which carries tenant/dataset credentials). The payload only influences (table, column), never the SDK, so a payload cannot redirect decryption to another tenant.
  • routingKeyFromPayload correctly rejects non-object/null/empty-string cases. decode keeps ctx.column as a fallback only when no identifier is present; decodeJson fails closed with a diagnostic. Good test coverage including the prefer-payload-over-ctx case and the non-round-trippable encodeJson marker.

Prisma Next 0.16 upgrade — coherent

  • 31 @prisma-next/* pins bumped in lockstep; pnpm-lock.yaml shows only registry (npmjs integrity) resolutions — no git/tarball sources.
  • createNamespace: postgresCreateNamespace added to both configs (0.15 requirement) and the PSL interpreter test migrated to the CST parse + buildSymbolTable input, consistent with the changeset notes.

Non-blocking observations

  1. Baseline bytes changed (one-time invariant exception). This PR changes the baseline migration's ops.json/hash (35fc9000…2c873907…) while simultaneously establishing the "published migrations are immutable by dirName, never re-emit" invariant. That is a legitimate transition commit (moving off the sentinel/runtime-injection scheme) and the PR body documents the rc-consumer reset, but it is worth being explicit that this is a deliberate one-time exception to the very invariant being introduced — after this lands, the append-only rule must hold strictly.
  2. decode now silently prefers the payload identifier even when it disagrees with ctx.column. Intentional and safe (key commitment makes it the correct identity), but it removes the implicit cross-check that previously surfaced misplaced-data bugs as decryption failures. A value that ends up in the wrong column now decodes silently by its true identity. Not a security issue, but a low-cost diagnostic (warn when routingKeyFromPayload and ctx.column disagree) would preserve that signal. Optional.
  3. Both migrations bake identical 3.0.2 SQL (same installSqlSha256); baseline+upgrade both land on the 3.0.2 surface for a fresh DB, relying on the install bundle's re-install idempotency. Fine and documented, just noting it is load-bearing on that idempotency claim.

…dback)

Addresses the reviewer's non-blocking note on #763: identifier-first v3
routing decrypts a value by its own `i:{t,c}` identity even when the projected
column disagrees, which removes the implicit cross-check that column-first
routing gave for free (a misplaced value used to fail to decrypt). This
re-surfaces that signal.

`decode` now compares the payload identifier against the projected column and,
on disagreement, emits a `console.warn` naming both — schema identifiers only,
never plaintext/ciphertext. Routing is UNCHANGED: the payload identifier stays
authoritative (ZeroKMS commits the cell key to it). It warns rather than throws,
and dedupes once per distinct mismatch (a per-instance Set), because the same
disagreement is the expected benign shape of an un-re-encrypted column rename —
so it can't spam the hot path or cry wolf on a supported workflow. Mirrors the
once-per-process console.warn pattern already used in @cipherstash/stack.

3 new unit tests: warn-once-per-distinct-mismatch, a fresh warn for a different
mismatch, and no-warn on agreement / no-context (aggregates).

Also documents, in the baked-EQL changeset, that this PR's one-time baseline
re-emit is a deliberate exception to the append-only invariant it introduces
(reviewer note #1).

prisma-next unit suite: 344 pass; build/dts clean; code:check clean.
@coderdan

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in 45c8d079.

@freshtonic (approved, non-blocking notes)

#2 — warn when the payload identifier disagrees with ctx.column. Done. decode now compares the two and emits a console.warn on disagreement, naming both (table, column) pairs (schema identifiers only — never plaintext/ciphertext). Routing is unchanged: the payload identifier stays authoritative (ZeroKMS commits the cell key to it).

Two refinements over a naive per-cell warn, worth flagging since they're deliberate:

  • Deduped, once per distinct mismatch (a per-instance Set), so it stays off the decode hot path — it can't emit one line per row.
  • Warns, doesn't throw — because the same disagreement is the expected, benign shape of an un-re-encrypted column rename (the payload keeps its original i, which is exactly what identifier-first routing makes decrypt-able). A hard error there would break a supported workflow; a deduped warn preserves the misplaced-data signal without crying wolf.

Mirrors the once-per-process console.warn pattern already in @cipherstash/stack. 3 new unit tests cover warn-once-per-mismatch, a fresh warn for a different mismatch, and no-warn on agreement / aggregate (no-context).

#1 — one-time baseline re-emit is an invariant exception. Documented explicitly in .changeset/eql-baked-digest-verified.md: this PR's baseline hash change (35fc9000…2c873907…) is a deliberate one-time transition off runtime-injection; the append-only rule holds strictly for every release after this, enforced by the frozen-hash CI pins.

#3 — both migrations bake identical 3.0.2 SQL, load-bearing on re-install idempotency. Informational; no change (agreed, and it's documented).

CodeRabbit (3 inline comments) — verified already handled, no change

  1. decodeJson NULL fallback (codec-runtime-v3.ts) — already resolved by design in 43edaf9c: decodeJson(null) is unreachable (the SQL runtime null-guards before the codec), and returning null would break the framework's decodeJson(json): TInput envelope signature. Documented in-code.
  2. Move tests to __tests__/ — the whole prisma-next package uses test/ (40 files, 0 in __tests__/) and its vitest.config.ts targets test/**; the new files follow the package convention.
  3. Exact provenance check in descriptor.test.ts — already owned by test/v3/migration-v3.test.ts, which pins each published migration's baked-SQL sha256 to a frozen literal (subsuming the loose substring/size check); the descriptor test is deliberately scoped to "not a placeholder" and says so in a comment pointing there.

prisma-next unit suite: 344 pass; build/dts clean; code:check clean.

@coderdan
coderdan merged commit b7fa61f into main Jul 23, 2026
14 checks passed
@coderdan
coderdan deleted the feat/prisma-next-0-16-baked-eql branch July 23, 2026 05:21
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.

Docs/CLI: prisma-next migration apply does not exist — use top-level prisma-next migrate

2 participants