Skip to content

fix(cli): make stash encrypt work in Prisma Next projects - #819

Merged
coderdan merged 3 commits into
mainfrom
fix/prisma-next-backfill
Jul 30, 2026
Merged

fix(cli): make stash encrypt work in Prisma Next projects#819
coderdan merged 3 commits into
mainfrom
fix/prisma-next-backfill

Conversation

@coderdan

@coderdan coderdan commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #851.

Problem

stash encrypt backfill cannot run against a Prisma Next project. Found while dogfooding stash@1.0.0-rc.4 + the Prisma Next adapter at rc.4 on a Next.js 16 app; both failures reproduce on this branch.

Two independent causes:

  1. loadEncryptionContext hard-fails on the missing client file. Prisma Next integrations deliberately have no src/encryption/index.ts — the schema lives in the emitted contract.json and the runtime derives it via cipherstashFromStack. The CLI's only schema source is the client file, so every encrypt command dies with Encrypt client file not found on a correctly set-up project.
  2. cipherstash.cs_migrations never exists. It's created by stash eql install, which the Prisma Next flow skips (EQL installs through the prisma-next migration graph — which doesn't carry the tracking schema). Even with a hand-written bridge client file, the first appendEvent insert fails with an opaque Backfill failed (error).

Fix

  • context.ts: when the client file is missing, a new tryLoadPrismaNextContext pass (mirroring the existing Drizzle auto-derive pass) detects a Prisma Next project (detectPrismaNext), locates contract.json (src/prisma/, prisma/, project root), and reuses the adapter's own public deriveStackSchemasV3 + Encryption to build the context. Both packages resolve from the user's project via jiti, so the CLI's schema view always matches the application's. Inside a detected Prisma Next project, missing pieces are hard errors naming the fix (prisma-next contract emit, install the packages) rather than falling through to the misleading client-file error.
  • backfill.ts: bootstrap cs_migrations via the existing idempotent installMigrationsSchema right after connecting, before any event insert. No-op everywhere eql install already ran.
  • skills/stash-cli: documents both. Its configuration table previously implied the client option was required for encrypt backfill; it now says Prisma Next projects need no client file and explains where the schemas come from.

Review fixes (Copilot)

  • Bootstrap failures were opaque. installMigrationsSchema errors fell through to the generic catch, which by design prints only Backfill failed (…) because its message path can carry plaintext — the exact error this bootstrap exists to remove. Now wrapped in BackfillConfigError naming the cause and likely fix (CREATE on the database), printed verbatim; safe because the DDL touches no row data.
  • deriveStackSchemasV3's array type was an assertion, not a fact. It arrives via jiti.import() behind an as cast, so a version skew reshaping the export would return undefined and the schemas.length read would throw a bare TypeError past all the surrounding guidance. Guarded with Array.isArray, naming the skew.
  • requireUsableEncryptConfig misnamed its source. It phrased both refusals as "Encryption client in <path>", so the derived path read "Encryption client in …/contract.json" — pointing users at a file they must not author. Added an optional sourceLabel (defaulting to the existing wording, so the client-file caller is byte-identical) and pass a derived-source label.
  • skills/stash-prisma gained an "Encrypting data that already exists" section. The review asked for this skill too, not just stash-cli.
  • Dropped a stale #<issue> placeholder from the test header.

Tests

  • 4 unit tests for the derivation pass (non-prisma-next regression, happy path, missing-contract guidance, empty-schema guard)
  • 1 test pinning installMigrationsSchema runs before runBackfill
  • 2 from the review fixes: the non-array guard, and that a bootstrap failure explains itself rather than reporting "Backfill failed"
  • Full packages/cli suite: 938 passed (61 files); code:check 0 errors

End-to-end validation

Against a live Prisma Next app (contract-declared BigIntOrd + TextSearch twins, no client file):

$ stash encrypt backfill --table transaction --column description --encrypted-column descriptionEncrypted --confirm-dual-writes-deployed
● transaction.descriptionEncrypted is EQL v3 — lifecycle is backfill → switch → drop
└ Backfill complete.

$ stash encrypt status
transaction  description  backfilled  v3   unique, match
transaction  amount       backfilled  v3   —

Rebase onto main — two API changes folded in

This branch originally targeted remove_v2; when that merged as a squash the base retargeted to main but the fork point did not, leaving 99 commits and a conflicted diff. Rebased onto main down to this PR's own commit. Two changes that landed on main meanwhile required real fixes here, neither of which the type-checker could catch — both call sites are jiti.import() with string specifiers behind an as cast:

Left as-is: detectPrismaNext already matches @cipherstash/stack-prisma on main, and prisma-next contract emit / prisma-next migrate are upstream framework CLI commands, unaffected by our package rename.

Also added the missing changeset (stash patch) — this branch had none, so the fix would have shipped invisibly.

Both commits are GPG-signed.

Notes

  • encrypt status/drop get the derivation for free (shared loadEncryptionContext).
  • Related friction found in the same dogfooding run, not addressed here: the adapter's docs say extensionPacks: but the @prisma-next/postgres/config façade takes extensions:; eqlMatch's 3-char tokenizer floor is undocumented. Happy to file separately.

Related: #820 (db init additive-policy fix, same dogfooding run).

@coderdan
coderdan requested a review from a team as a code owner July 28, 2026 09:21
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 977140b

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

This PR includes changesets to release 12 packages
Name Type
stash Patch
@cipherstash/basic-example Patch
@cipherstash/e2e Patch
@cipherstash/stack Patch
@cipherstash/stack-drizzle Patch
@cipherstash/stack-supabase Patch
@cipherstash/stack-prisma Patch
@cipherstash/wizard Patch
@cipherstash/bench Patch
@cipherstash/migrate Patch
@cipherstash/test-kit Patch
@cipherstash/prisma-example 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 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Prisma Next encryption commands now derive encryption context from emitted contract.json files when no client file exists. Backfill also initializes cipherstash.cs_migrations before processing events, with tests and documentation covering both behaviors.

Changes

Prisma Next encryption flow

Layer / File(s) Summary
Prisma Next context fallback
packages/cli/src/commands/encrypt/context.ts, packages/cli/src/commands/encrypt/__tests__/*, skills/stash-cli/SKILL.md
Missing client files in Prisma Next projects now trigger contract discovery, v3 schema derivation, encryption client construction, and validation, with tests for success and failure paths.
Backfill migrations bootstrap
packages/cli/src/commands/encrypt/backfill.ts, packages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts, .changeset/prisma-next-encrypt-backfill.md, skills/stash-cli/SKILL.md
Backfill invokes installMigrationsSchema after connecting to the database and before backfill execution; tests and documentation describe this Prisma Next behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant loadEncryptionContext
  participant PrismaNextDetector
  participant ContractFile
  participant SchemaAdapter
  participant EncryptionClient
  loadEncryptionContext->>PrismaNextDetector: detect Prisma Next
  PrismaNextDetector->>ContractFile: locate emitted contract.json
  loadEncryptionContext->>SchemaAdapter: deriveStackSchemasV3(contract)
  SchemaAdapter->>EncryptionClient: Encryption({ schemas })
  EncryptionClient-->>loadEncryptionContext: return client and tables
Loading
sequenceDiagram
  participant backfillCommand
  participant PostgreSQL
  participant installMigrationsSchema
  participant runBackfill
  backfillCommand->>PostgreSQL: acquire connection
  backfillCommand->>installMigrationsSchema: initialize migrations schema
  installMigrationsSchema->>PostgreSQL: create cipherstash.cs_migrations if absent
  backfillCommand->>runBackfill: execute encryption backfill
Loading

Possibly related issues

  • cipherstash/stack#764 — Covers the Prisma Next missing-client and missing-migrations-schema problems addressed here.

Suggested reviewers: freshtonic

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing stash encrypt behavior for Prisma Next projects.
✨ 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 fix/prisma-next-backfill

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.

Copilot AI 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.

Pull request overview

Enables stash encrypt * commands (notably stash encrypt backfill) to run in Prisma Next projects by deriving schemas from the emitted contract.json when no encryption client file exists, and by ensuring the migrations tracking table (cipherstash.cs_migrations) is bootstrapped before backfill writes events.

Changes:

  • Add Prisma Next detection + contract-based schema derivation to loadEncryptionContext as a fallback when the configured client file is missing.
  • Ensure cipherstash.cs_migrations exists by calling installMigrationsSchema early in backfillCommand.
  • Add unit tests covering Prisma Next derivation paths and the backfill bootstrap ordering.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
packages/cli/src/commands/encrypt/context.ts Adds Prisma Next contract discovery + schema derivation fallback when the encryption client file is missing.
packages/cli/src/commands/encrypt/backfill.ts Bootstraps cipherstash.cs_migrations before any backfill event inserts.
packages/cli/src/commands/encrypt/tests/prisma-next-context.test.ts Adds tests for Prisma Next context derivation and for migrations schema bootstrap ordering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/cli/src/commands/encrypt/backfill.ts Outdated
Comment thread packages/cli/src/commands/encrypt/context.ts
Comment thread packages/cli/src/commands/encrypt/context.ts Outdated
Comment thread packages/cli/src/commands/encrypt/context.ts
Comment thread packages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts Outdated
Base automatically changed from remove-v2 to main July 29, 2026 07:06
`stash encrypt backfill` (and the other encrypt commands) could not run
against a Prisma Next project, for two independent reasons:

1. `loadEncryptionContext` hard-failed when the configured encrypt client
   file was missing — but Prisma Next integrations deliberately have no
   client file: encrypted columns are declared in the PSL contract and the
   runtime derives the schemas from the emitted contract.json. Add a
   fallback pass (mirroring the existing Drizzle auto-derive) that detects
   a Prisma Next project via `detectPrismaNext`, locates contract.json,
   and reuses the adapter's own `deriveStackSchemasV3` + `Encryption` to
   build the context. Both packages are resolved from the user's project
   so the CLI's schema view always matches the application's.

2. `cipherstash.cs_migrations` never existed: it is created by
   `stash eql install`, which the Prisma Next flow skips (EQL installs
   through the prisma-next migration graph, which doesn't carry the
   tracking schema). The first `appendEvent` insert then failed with an
   opaque relation-does-not-exist error. Bootstrap the schema in
   `backfillCommand` via the existing idempotent
   `installMigrationsSchema` before any event is written.

Also documents both in `skills/stash-cli` — notably that the `client`
config option is not required in a Prisma Next project, which that
skill's option table previously implied it was — and adds the changeset.

Validated end-to-end against a Prisma Next app (Next.js 16, contract-
declared BigIntOrd + TextSearch twins): backfill derives the schemas
with no client file, creates cs_migrations, converges, and
`encrypt status` reports both columns as backfilled v3.
@coderdan
coderdan force-pushed the fix/prisma-next-backfill branch from f513306 to 1f6b16b Compare July 30, 2026 05:22
Addresses Copilot review on #819.

- `installMigrationsSchema` failures fell through to the generic catch,
  which prints only "Backfill failed (…)" by design (its message path can
  carry plaintext). That is the exact opaque error this bootstrap exists
  to remove. Wrap it in `BackfillConfigError` naming the cause and the
  likely fix (CREATE on the database), which the handler prints verbatim —
  safe here because the DDL touches no row data.

- `deriveStackSchemasV3`'s array return type is an assertion, not a
  checked fact: it arrives through `jiti.import()` behind an `as` cast, so
  a version skew that reshaped the export would hand back `undefined` and
  the `schemas.length` read would throw a bare TypeError past all of the
  surrounding guidance. Guard with `Array.isArray` and name the skew.

- `requireUsableEncryptConfig` phrased both refusals as "Encryption client
  in <path>", so the derived path produced "Encryption client in
  …/contract.json" — pointing users at a file Prisma Next projects are not
  supposed to author. Add an optional `sourceLabel` (defaulting to the
  existing wording, so the client-file caller is unchanged) and pass a
  derived-source label.

- `skills/stash-prisma` gained an "Encrypting data that already exists"
  section: the encrypt lifecycle needs no client file here, and backfill
  bootstraps `cs_migrations` itself. The review asked for this skill as
  well as `stash-cli` (already updated in the previous commit).

- Dropped a stale `#<issue>` placeholder from the test header.

Two tests added: the non-array guard, and that a bootstrap failure
explains itself instead of reporting "Backfill failed". cli suite 938.

@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

🧹 Nitpick comments (1)
packages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts (1)

137-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the "package not installed" failure branch.

The suite covers missing-contract and empty-schema errors, but not the case where jiti.import('@cipherstash/stack-prisma/v3') (or @cipherstash/stack/v3) throws — the "Failed to load @cipherstash/stack-prisma / @cipherstash/stack" branch in context.ts (lines 237-244). The existing jitiImportMock already throws for unexpected specifiers, so a test could simply mock detectPrismaNextMock/existsSync as in the success test and make jitiImportMock reject to assert the exit(1) + message.

🤖 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/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts`
around lines 137 - 200, The Prisma Next context tests lack coverage for the
package-load failure path. Add a test in the `loadEncryptionContext — Prisma
Next contract derivation` suite that configures `detectPrismaNextMock` and
`fsMocks.existsSync` like the success case, makes `jitiImportMock` reject for
the stack package import, and asserts `loadEncryptionContext()` exits with code
1 and logs the “Failed to load `@cipherstash/stack-prisma` / `@cipherstash/stack`”
guidance.
🤖 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/cli/src/commands/encrypt/context.ts`:
- Around line 262-271: Wrap the `Encryption({ schemas })` call in
`tryLoadPrismaNextContext` with the function’s existing friendly error handling,
catching initialization failures and reporting them via the Prisma Next–specific
`console.error` followed by `process.exit(1)`. Preserve the subsequent
`requireUsableEncryptConfig(client.getEncryptConfig(), contractPath)` path for
successful initialization and include the caught error details in the guided
message.

---

Nitpick comments:
In `@packages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts`:
- Around line 137-200: The Prisma Next context tests lack coverage for the
package-load failure path. Add a test in the `loadEncryptionContext — Prisma
Next contract derivation` suite that configures `detectPrismaNextMock` and
`fsMocks.existsSync` like the success case, makes `jitiImportMock` reject for
the stack package import, and asserts `loadEncryptionContext()` exits with code
1 and logs the “Failed to load `@cipherstash/stack-prisma` / `@cipherstash/stack`”
guidance.
🪄 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 Plus

Run ID: 1a0299bb-478e-490c-a7b6-e4f8b0681814

📥 Commits

Reviewing files that changed from the base of the PR and between 20cb8c3 and 1f6b16b.

📒 Files selected for processing (5)
  • .changeset/prisma-next-encrypt-backfill.md
  • packages/cli/src/commands/encrypt/__tests__/prisma-next-context.test.ts
  • packages/cli/src/commands/encrypt/backfill.ts
  • packages/cli/src/commands/encrypt/context.ts
  • skills/stash-cli/SKILL.md

Comment thread packages/cli/src/commands/encrypt/context.ts Outdated
… one command that derives

Two follow-ups from the #819 review.

`tryLoadPrismaNextContext` caught every failure mode except the last one.
`Encryption({ schemas })` throws on invalid credentials or a bad keyset — a
plausible first run — and `loadEncryptionContext()` is called at
`backfillCommand`'s line 114, before that command's own try/catch at 131. So
the one unguarded call in the function was also the one whose throw escaped as
a raw stack trace. The client-file path never had this hole: there the client
is constructed inside the user's module, which `jiti.import` already wraps.
Now caught, with the message pointing at credentials rather than the contract,
since the schemas by definition derived fine to reach that line.

Both skills claimed "the encrypt commands" detect Prisma Next. Only
`encrypt backfill` does — it is the sole caller of `loadEncryptionContext`.
`encrypt status` and `encrypt drop` load `stash.config.ts` alone and resolve
against the database, so they never read a client file and never needed the
derivation. Named the single command in `stash-cli` and `stash-prisma`, and
said what the other two do instead.

Tests: 939 passed (61 files), up one for the new guard.
@coderdan
coderdan merged commit 90a0200 into main Jul 30, 2026
10 checks passed
@coderdan
coderdan deleted the fix/prisma-next-backfill branch July 30, 2026 06:42
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.

stash encrypt backfill cannot run in a Prisma Next project

3 participants