Skip to content

feat(prisma-next): source EQL v3 install SQL from @cipherstash/eql at runtime#694

Merged
coderdan merged 4 commits into
mainfrom
feat/prisma-next-eql-runtime-source
Jul 19, 2026
Merged

feat(prisma-next): source EQL v3 install SQL from @cipherstash/eql at runtime#694
coderdan merged 4 commits into
mainfrom
feat/prisma-next-eql-runtime-source

Conversation

@coderdan

@coderdan coderdan commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Why

Baking the ~1.7 MB EQL v3 install bundle into the v3 baseline migration's ops.json meant every @cipherstash/eql upgrade required re-running the maintainer emit loop and committing another generated SQL blob. This is the concrete cost @coderdan flagged.

This is the "depend, don't bake" direction from #690 (PR3), scoped to what actually matters: the sourcing model. It does not change the contract-space head-ref/invariants or require framework-migration-emitter changes, because the SQL is not part of the contract storage hash.

What changed

  • @cipherstash/eql is now an exact-pinned runtime dependency (3.0.0, previously a devDependency), matching the EQL version used by @cipherstash/stack and the stash CLI.
  • The committed v3 baseline op carries a sentinel placeholder instead of baked SQL. control.ts uses withRuntimeEqlSqlPackage() to inject readInstallSql() from the installed @cipherstash/eql and recompute the migration hash from the resulting operations.
  • Upgrading the coordinated EQL pin now requires a dependency bump and rebuild/release, but no 1.7 MB migration re-emit.
  • ops.json: 1,717,297 → 1,219 bytes.

Why it's safe (no framework surgery)

The v3 baseline is an invariant-only self-edge (from === to; the bundle declares no contract-space storage). Therefore:

  • the install SQL does not change the contract-space storage hash;
  • runtime insertion recomputes the content-addressed migrationHash from the injected operations before Prisma Next materializes the package;
  • Prisma Next's canonical disk reader subsequently verifies that hash, preserving the normal migration-integrity check;
  • the apply planner routes on the cipherstash:install-eql-v3-bundle-v1 invariant, not SQL content.

Verified against @prisma-next/migration-tools@0.14.0.

Trusted SQL bundle insertion (not user-input SQL injection)

Here, “inject” means inserting a trusted package asset into the migration descriptor. It is not a path for arbitrary or user-provided SQL:

  • SQL comes only from exact-pinned @cipherstash/eql@3.0.0; no user input is interpolated.
  • A unique sentinel identifies the replacement location, and a missing sentinel fails immediately.
  • Only the sentinel step's sql field is replaced; sibling steps and migration fields are preserved.
  • The operation class is validated and the canonical migration hash is recomputed after replacement.
  • A descriptor → disk → integrity-checked read round trip verifies the materialized package.
  • The pnpm lockfile pins the npm artifact's registry integrity.

The trust chain is: exact-pinned package → trusted static SQL asset → exact sentinel replacement → recomputed content hash → verified disk round trip.

This does not sanitize arbitrary SQL or make user-generated raw SQL safe; it prevents untrusted input from entering this path.

User-facing impact

None. EQL still installs as part of prisma-next migration apply. The v2 baseline is untouched (still baked; v2 is legacy/unreleased).

Tests

  • The committed ops.json carries the sentinel and remains under 5 KB.
  • The descriptor injects readInstallSql() and recomputes the runtime migration hash.
  • A regression test materializes the descriptor package and reads it back through Prisma Next's integrity-checking migration reader.
  • The live-PG migration test compares the applied SQL with the runtime descriptor operation.
  • 602 Prisma Next tests pass; 30 credentialed live tests skip without credentials/Postgres. Build, typecheck, and code:check pass.

For @calvinbrewer

This changes the v3 baseline authoring model: migration.ts emits a placeholder, while the runtime descriptor injects the SQL and recomputes the content-addressed migration hash. The re-emit loop (pnpm exec tsx migrations/.../migration.ts) still works and is a deterministic no-op. Flagging since it touches the contract-space baseline you own—the invariant/head-ref wiring is unchanged.

Part of #690 (the "depend on @cipherstash/eql, don't bake" thread).

https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w

Summary by CodeRabbit

  • New Features
    • EQL v3 installation SQL is now resolved via the installed EQL package during migration/descriptor build-time injection.
    • Baseline migration artifacts no longer embed the full v3 install SQL bundle.
  • Bug Fixes
    • Migration packaging now injects and validates the install SQL consistently, updating migration hashes to match the materialized operations.
    • @cipherstash/eql is now a runtime dependency to stay aligned with v3 domain expectations.
  • Tests
    • Updated and expanded v3 and live migration-apply coverage to verify placeholder usage, runtime SQL resolution, and drift-guard replacement behavior.

… runtime

Baking the ~1.7 MB install bundle into the v3 baseline migration's ops.json
coupled every @cipherstash/eql upgrade to a prisma-next release: a patch or minor
bump meant re-emitting the migration and cutting a new adapter version.

Now @cipherstash/eql is a runtime dependency (^3.0.0). The committed v3 baseline
op carries a sentinel placeholder; the descriptor (control.ts) injects
readInstallSql() from the installed @cipherstash/eql at build time via
withRuntimeEqlSql(). A patch/minor now flows through npm resolution with no
re-emit and no adapter release — mirroring how the stash CLI already sources v3
SQL (#692).

Safe because the v3 baseline is an invariant-only self-edge (from === to): the
install SQL never contributes to the contract-space hash, and
contractSpaceFromJson passes ops through with no integrity check on their
contents (verifyMigrationHash runs only on the disk read path for the user's
repo, never on the in-memory extension descriptor). The apply planner routes on
the cipherstash:install-eql-v3-bundle-v1 invariant, not SQL content.

ops.json drops from 1,717,297 → 1,219 bytes. No user-facing change — EQL still
installs via `prisma-next migration apply`. 600 tests green; build + biome clean.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
@coderdan
coderdan requested a review from a team as a code owner July 18, 2026 14:19
@changeset-bot

changeset-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 01f1d15

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
@cipherstash/prisma-next-example Patch
stash Minor
@cipherstash/stack Minor
@cipherstash/stack-drizzle Minor
@cipherstash/stack-supabase Minor
@cipherstash/wizard Minor
@cipherstash/basic-example Patch
@cipherstash/e2e Patch
@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 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The EQL v3 baseline migration now stores a sentinel instead of install SQL. Prisma-next injects readInstallSql() from the installed EQL package during descriptor construction, recomputes the migration hash, and validates the runtime result.

Changes

EQL runtime SQL sourcing

Layer / File(s) Summary
Sentinel migration baseline
packages/prisma-next/migrations/.../*, packages/prisma-next/package.json
The baseline operation uses a runtime sentinel, its migration hash is updated, and @cipherstash/eql becomes an exact runtime dependency.
Runtime SQL injection
packages/prisma-next/src/migration/eql-bundle-v3.ts, packages/prisma-next/src/exports/control.ts
Runtime SQL replaces the sentinel during descriptor construction, and the migration hash is recomputed from the injected operations.
Runtime and live-apply validation
packages/prisma-next/test/v3/*, packages/prisma-next/test/live/*, .changeset/*
Tests validate placeholder storage, resolved SQL, round-trip materialization, replacement preservation, and descriptor-based live application; the changeset documents the update.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BaselineMigration
  participant CipherstashDescriptor
  participant EqlBundleV3
  BaselineMigration->>CipherstashDescriptor: provide sentinel install operation
  CipherstashDescriptor->>EqlBundleV3: build runtime migration package
  EqlBundleV3->>EqlBundleV3: readInstallSql()
  EqlBundleV3-->>CipherstashDescriptor: return injected SQL and recomputed hash
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: freshtonic

🚥 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 and concisely describes the main change: sourcing EQL v3 install SQL from @cipherstash/eql at runtime.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/prisma-next-eql-runtime-source

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: 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/v3/migration-v3.test.ts`:
- Around line 44-56: Remove the duplicate readonly execute property from the
type assertion used by the test at
packages/prisma-next/test/v3/migration-v3.test.ts lines 44-56. Apply the same
removal to the assertion at lines 58-71, leaving one execute declaration per
assertion so typechecking succeeds.
🪄 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: 4ac99242-8cb2-42fc-867f-02ffd2f9fed0

📥 Commits

Reviewing files that changed from the base of the PR and between 0811330 and e566f93.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • .changeset/prisma-next-eql-runtime-source.md
  • 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/package.json
  • packages/prisma-next/src/exports/control.ts
  • packages/prisma-next/src/migration/eql-bundle-v3.ts
  • packages/prisma-next/test/v3/migration-v3.test.ts

Comment thread packages/prisma-next/test/v3/migration-v3.test.ts
…ast injection

Code-review follow-ups on the runtime EQL-SQL sourcing:

- Pin @cipherstash/eql exact (3.0.0), not ^3.0.0. prisma-next's v3 domain TYPES
  come from @cipherstash/stack (which encodes them against a specific EQL
  release); floating the install SQL via a caret would let the applied schema
  drift from the pinned type surface, and diverge from the exact pins in
  @cipherstash/stack and the stash CLI (both install into the same DB). Exact
  keeps types and SQL coherent and still removes the 1.7 MB re-emit coupling —
  the actual win. Reframed the "flows through" wording to "no re-emit" throughout.

- withRuntimeEqlSql now matches the placeholder by its SENTINEL value (not an
  op id) and THROWS if no placeholder is present. Matching an op id required
  three hardcoded copies to agree; a drift silently applied the inert sentinel
  comment as the "install" (empty EQL, caught only by an indirect postcheck, or
  not at all when postchecks are disabled). Sentinel-match is immune to op-id
  drift and fails loudly at descriptor build. Dropped the V3_INSTALL_OP_ID const.

- The SQL read is wrapped (readV3InstallSql) with an actionable error, since it
  now runs at control.ts import for every control-plane consumer (was a raw
  readFileSync throw). Mirrors the CLI's guard.

- Non-lossy swap: only the sentinel step's `sql` is rewritten; sibling execute
  steps and extra fields are preserved. Added a drift-guard + non-lossy test.

601 tests green; build + biome + frozen-lockfile clean.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
…ace (typecheck)

The prior commit removed `readInstallSql` from eql-bundle-v3's re-export, but the
live-test helpers (`test/live/helpers/eql-v3.ts`, `migration-apply-live-pg.test.ts`)
import it from there — a TS2459 the vitest run misses (live suites are skipped)
but the `typecheck` CI step catches. Re-export it. Also non-null-guard
`contractSpace` in the new descriptor-injection test (`contractSpace!`), matching
descriptor.test.ts. typecheck exits 0; 601 tests green.

Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w
@coderdan

coderdan commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Differential Review: PR 694

Codex/gpt-5.6-sol

Executive Summary

Severity Count
🔴 CRITICAL 0
🟠 HIGH 1
🟡 MEDIUM 1
🟢 LOW 0

Overall Risk: HIGH
Recommendation: REJECT until the migration hash is recomputed after runtime SQL injection and the fresh-seed round trip is tested.

Key Metrics:

  • Files analyzed: 9/9 (100%)
  • Changed lines: +214 / -40
  • High-risk production entry points changed: 1 (@cipherstash/prisma-next/control)
  • Test gaps: 1 fresh descriptor → disk → verified-read workflow
  • Security regressions: 0 identified

What Changed

Commit Range: 08113301b524003fb2906240115bec644fccf069..95d7f39d8a3179adb7421bf15d77c7073d10c474
Commits: 3
Timeline: 2026-07-18

File group Risk Summary
src/exports/control.ts, src/migration/eql-bundle-v3.ts HIGH Replaces the committed sentinel with installed EQL SQL at descriptor import time
v3 migration JSON/TS HIGH Replaces baked SQL with a sentinel and changes the content hash
package.json, pnpm-lock.yaml MEDIUM Moves exact @cipherstash/eql@3.0.0 from dev to runtime dependencies
tests MEDIUM Covers in-memory injection, but not materialization and verified reload
changeset LOW Documents the published behavior change

Critical Findings

🟠 HIGH: Freshly seeded migrations fail their content-addressed integrity check

File: packages/prisma-next/src/exports/control.ts:89
Commit: e566f93339bfcc89ed583470477a02e96578ebf3
Blast Radius: Every fresh Prisma Next project that seeds this extension migration
Test Coverage: NO — current tests stop at the in-memory descriptor

Description:

control.ts keeps v3BaselineMetadata unchanged while replacing the sentinel in v3BaselineOps with the 1.7 MB runtime SQL. Prisma Next's seed phase materializes both descriptor values into the user's migration directory. The next load recomputes migrationHash over the mutated operations and rejects the package because the stored hash was computed over the sentinel.

Observed values:

stored metadata hash:    sha256:55f261c28b9fe8feee21270a652d758a5c90ccbe7aac9cc79ac50662b956160b
hash after SQL injection: sha256:d1ca5a987ab10eb85c2fe05700134b31f17f694d2d5b3df3969e902610b1d9cf
read result: MIGRATION.HASH_MISMATCH

Historical Context:

  • The v3 baseline and its content-addressed migration were introduced in a75513b45279bd9bddc8da1bae417a811974b280.
  • The baseline deliberately baked the SQL so migration.json and ops.json had one content identity.
  • This PR changes the operations after that identity has been computed.

Failure Scenario:

  1. A fresh user imports @cipherstash/prisma-next/control.
  2. withRuntimeEqlSql() injects the installed EQL SQL into the descriptor operations.
  3. migration plan seeds the descriptor migration into migrations/cipherstash/....
  4. Phase 2 reads the newly written package and calls verifyMigrationHash().
  5. The command stops with MIGRATION.HASH_MISMATCH; EQL and the application migration are not applied.

Proof of Concept:

const pkg = descriptor.contractSpace.migrations[1]
await materialiseMigrationPackage(root, pkg)
await readMigrationPackage(join(root, pkg.dirName))
// MigrationToolsError: MIGRATION.HASH_MISMATCH

The repository's Prisma example E2E does not expose this because its migration directory already contains the old baked package; the seed phase skips existing directories by design.

Recommendation:

Build the runtime migration package atomically: inject the SQL and recompute metadata.migrationHash with computeMigrationHash(metadata, injectedOps) before passing it to contractSpaceFromJson. Add a test that materializes the descriptor package into an empty directory and successfully reads it back with readMigrationPackage.

🟡 MEDIUM: The credentialed live migration test still asserts the removed baked-SQL model

File: packages/prisma-next/test/live/migration-apply-live-pg.test.ts:82
Commit: unchanged from baseline, invalidated by e566f93339bfcc89ed583470477a02e96578ebf3
Blast Radius: pnpm --filter @cipherstash/prisma-next test:live when live credentials and DATABASE_URL are present
Test Coverage: The default run skips this suite

Description:

The test reads committed ops.json directly and asserts op.execute[0].sql === readInstallSql(). The committed value is now the 124-character sentinel, while readInstallSql() is the full bundle, so this assertion is guaranteed to fail once the live gate opens. Its header and test name also still describe SQL as baked into ops.json.

Recommendation:

Read the operation from the injected descriptor (or, preferably, from the materialized-and-verified package used by the real CLI path), then apply and compare that SQL. Update the stale comments and test name.

Test Coverage Analysis

Local results:

  • pnpm --filter @cipherstash/prisma-next build
  • pnpm --filter @cipherstash/prisma-next typecheck
  • ✅ default package tests: 601 passed, 30 live tests skipped
  • pnpm run code:check (warnings only, as allowed by repository policy)
  • ❌ descriptor migration materialize/read reproduction: MIGRATION.HASH_MISMATCH
  • ⚠️ live-PG suite could not execute without credentials/database; static comparison proves its SQL identity assertion is false (124 sentinel characters versus 1,666,263 runtime SQL characters)

The highest-value invariant test is a round trip: descriptor package → materialize → verified read. That property is currently absent.

Blast Radius Analysis

Changed function/path Direct production callers Effective impact Priority
withRuntimeEqlSql() 1 (control.ts) All fresh consumers of the public control descriptor P1
@cipherstash/eql runtime SQL loader 1 descriptor import path Control-plane startup and migration authoring P1

The repository contains three direct consumers of cipherstashExtensionDescriptor; external consumers of the published ./control subpath are not enumerable locally.

Historical Context

No authorization, crypto primitive, plaintext-logging, or payload-shape guard was removed. The relevant removed behavior was content identity: commit a75513b4 emitted the exact SQL that its metadata hash attested. The PR preserves the SQL bytes but mutates them after loading the attestation.

The exact @cipherstash/eql@3.0.0 pin matches @cipherstash/stack and the CLI, and the lockfile retains registry integrity metadata. No additional dependency version was introduced; GitHub's OSV checks are green.

Recommendations

Immediate (Blocking)

  • Recompute the v3 migration hash from the injected operations.
  • Add a fresh-seed materialize/read test using Prisma Next's real migration I/O helpers.
  • Fix the stale live-PG migration test to exercise the injected/materialized operation.

Before Production

  • Add a fresh-project Prisma Next E2E fixture; the current checked-in fixture only tests the existing-directory path.

Technical Debt

  • Consider representing runtime injection as a helper returning { metadata, ops } so callers cannot update one half of the content-addressed package independently.

Analysis Methodology

Strategy: SURGICAL (751 relevant source/config files; 9 changed files)

Coverage:

  • Changed files reviewed: 9/9
  • High-risk diff regions: 100%
  • One-hop framework dependencies traced through descriptor seeding, materialization, hash verification, and aggregate loading
  • Baseline history and removed behavior reviewed with git log, git blame, and pickaxe search
  • Adversarial model: an ordinary fresh-project user, with no special privileges, triggers a deterministic availability failure through the documented migration workflow

Limitations:

  • No live ZeroKMS/Postgres credentials were available locally.
  • External consumers and future Prisma Next versions were not analyzed.

Confidence: HIGH

Appendix: Commit References

Commit Purpose
a75513b4 Baseline EQL v3 migration and live tests
e566f933 Runtime SQL sourcing
12094257 Exact pin, sentinel matching, fail-fast injection
95d7f39d Re-export/typecheck follow-up

@coderdan

Copy link
Copy Markdown
Contributor Author

Addressed the review findings in 01f1d15a.

  • Runtime EQL SQL injection now recomputes the content-addressed migration hash atomically with the injected ops.
  • Added a descriptor → disk → integrity-checked read regression test using Prisma Next’s canonical migration I/O helpers.
  • Updated the live-PG migration test to compare against the runtime descriptor op instead of the committed sentinel fixture.
  • Updated the migration documentation and changeset.

Validation:

  • pnpm --filter @cipherstash/prisma-next build
  • pnpm --filter @cipherstash/prisma-next typecheck
  • pnpm --filter @cipherstash/prisma-next test — 602 passed, 30 credentialed live tests skipped
  • pnpm run code:check
  • Fresh-seed reproduction now materializes and verifies successfully with hash sha256:d1ca5a987ab10eb85c2fe05700134b31f17f694d2d5b3df3969e902610b1d9cf.

@coderdan

Copy link
Copy Markdown
Contributor Author

Comprehensive human and AI review (Coderabbit and Codex/gpt-5.6-sol)

@coderdan
coderdan merged commit a2f80ea into main Jul 19, 2026
13 checks passed
@coderdan
coderdan deleted the feat/prisma-next-eql-runtime-source branch July 19, 2026 05:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant