Skip to content

fix(cli): stash init --drizzle installs EQL v3, not v2#705

Merged
tobyhede merged 2 commits into
mainfrom
fix/init-drizzle-eql-v3
Jul 21, 2026
Merged

fix(cli): stash init --drizzle installs EQL v3, not v2#705
tobyhede merged 2 commits into
mainfrom
fix/init-drizzle-eql-v3

Conversation

@tobyhede

@tobyhede tobyhede commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

stash init --drizzle was the only flow still provisioning an EQL v2 database. Bare eql install and init for every other integration already default to v3.

init/steps/install-eql.ts:103    eqlVersion: drizzle ? '2' : undefined,

The pin's stated reason — "the Drizzle migration path is v2-only" — stopped being true when stash eql migration --drizzle shipped in 0811330. skills/stash-drizzle/SKILL.md didn't merely document the pin, it justified it with the same now-false claim.

The pin was causing the mismatch it appeared to prevent

Init installs stash-encryption, stash-drizzle and stash-cli into the customer's repo, and the two content skills are fully v3 — they document EncryptionV3, types.* and public.eql_v3_*. So init --drizzle handed the customer's coding agent v3 guidance against a v2 database it had just provisioned.

Why this isn't just dropping the flag

routeInstallPathForEqlVersion forces drizzle: false under v3, so a bare flag flip would have silently converted Drizzle projects to a direct install and lost their migration history.

Init's Drizzle branch now routes through eqlMigrationCommand({ drizzle: true, embedded: true }) — v3 SQL, still migration-first. Three things that needed handling:

Also fixed: eql install --drizzle's rejection message, which was actively steering users to --eql-version 2.

On the risk that didn't materialise

The concern going in was that flipping the pin would yield a v3 database with v2 authoring code, since init/utils.ts emits encryptedColumn().equality() throughout. Tracing callers showed those generators are dead on the init path:

  • build-schema.ts:129 only calls generatePlaceholderClient()
  • generateClientFromSchema{,s} has one caller, and it's commands/schema/build.ts — a different command
  • the column picker was already removed; SearchOp / ColumnDef in init/types.ts are vestigial, and init always sets schemas: []

Verification

install-eql.test.ts goes from 5 tests to 11, including a regression guard on the v2 pin, config scaffolding, --supabase forwarding, drizzle-kit-missing degradation, and no credential leakage on the failure path.

Full suite: 51 files, 589 tests passed. Build clean, code:check exits 0, manifest check confirms every command named in the skill edits resolves.

Follow-up, not in this PR

generatePlaceholderClient emits v2 pattern comments and Encryption({ schemas: [] }) for all integrations, not just Drizzle — so Supabase and plain-Postgres init already ship a v3 database with a v2 placeholder client. Pre-existing and broader than this fix; self-contained (two string constants plus schema/build.ts's generators). Worth its own issue.

🤖 Generated with Claude Code

The Drizzle init flow pinned `eqlVersion: '2'` when calling `installCommand`,
because `eql install --drizzle` — the only migration-generating install path at
the time — was v2-only and rejects `--drizzle` under the v3 default.

That made `stash init --drizzle` the single flow that provisions a v2 database.
A bare `stash eql install`, and init for every other integration, already
default to v3 (`resolveEqlVersion`, registry `default: '3'`). So every new
Drizzle customer was silently added to the v2 install base the team plans to
retire.

It was also internally inconsistent: init copies the `stash-drizzle` skill into
the same project, and that skill documents the v3 surface
(`@cipherstash/stack-drizzle/v3`, `types.*` domains, `EncryptionV3`,
`public.eql_v3_*` column types). The user's agent would author v3 code against
the v2 database init had just provisioned.

`stash eql migration --drizzle` (#691) closes the gap that forced the pin: v3
SQL, still migration-first. Init's Drizzle flow now routes through it, so the
install keeps landing in the project's Drizzle migration history while emitting
v3. The generated migration also carries the `cs_migrations` tracking schema, so
one `drizzle-kit migrate` covers everything `stash encrypt …` needs.

`eql install`'s config/client scaffolding isn't part of `eql migration`, so the
branch does it directly (`offerStashConfig({ ensure: true })` +
`ensureEncryptionClient`) to keep init's contract identical — without it, init
would finish with no `stash.config.ts` and every downstream command would
dead-end (#581).

A missing or misconfigured `drizzle-kit` now degrades to "EQL not installed"
with a pointer to `stash eql migration --drizzle`, rather than the old v2
path's `process.exit(1)` mid-init.

Also:
- `eqlMigrationCommand` gains `embedded` to suppress its intro/outro and
  `printNextSteps()` when run as an init step, so the user doesn't get two
  competing "what next" blocks.
- `eql install --drizzle`'s rejection message now points at the v3 alternative
  instead of only suggesting `--eql-version 2`.
- Updated `skills/stash-cli` and `skills/stash-drizzle`, which both documented
  the v2 pin as current behaviour.
@tobyhede
tobyhede requested a review from a team as a code owner July 20, 2026 02:48
@changeset-bot

changeset-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 30deef1

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

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

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 36 minutes

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

How can I continue?

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

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

How do review limits work?

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

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

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb39f674-77b5-4d6f-bbe7-06812a0b37b9

📥 Commits

Reviewing files that changed from the base of the PR and between 4888995 and 30deef1.

📒 Files selected for processing (10)
  • .changeset/init-drizzle-eql-v3.md
  • packages/cli/src/__tests__/supabase-migration.test.ts
  • packages/cli/src/commands/db/install.ts
  • packages/cli/src/commands/eql/__tests__/migration.test.ts
  • packages/cli/src/commands/eql/migration.ts
  • packages/cli/src/commands/init/steps/__tests__/install-eql.test.ts
  • packages/cli/src/commands/init/steps/install-eql.ts
  • packages/cli/src/commands/init/types.ts
  • skills/stash-cli/SKILL.md
  • skills/stash-drizzle/SKILL.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/init-drizzle-eql-v3

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.

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

Verdict

The change itself is sound and the installEqlStep Drizzle branch is well covered — install-eql.test.ts guards the v2-pin regression, the eqlMigrationPending mapping, the scaffolding takeover, the --supabase forward, the failure degrade, and the credential-leak axis.

The gaps are in the other two files the diff touches, where new behaviour landed with no new tests: the embedded suppression in eql/migration.ts and the v3Alternative pointer in db/install.ts. Both are pure/unit-shaped and fit the existing Vitest suites. Three inline comments below, all verified against the diff.

No security issues observed. install-eql.ts:129 deliberately swallows the error object rather than echoing it — correct, given state.databaseUrl flows through this step, and the diff already ships a test for that axis.

Review stats

Source Raw findings Survived
claude (claude-opus-4-8) [test-gap] 3 3
codex (gpt-5.5) [test-gap] 2 2
  • Merged/de-duplicated total: 3 kept findings (5 raw).
  • Cross-model overlap: 2 of 3 kept findings were raised independently by both models (the embedded suppression gap and the v3Alternative gap). 1 is single-source.
  • Dropped as unverifiable: 0.

Additional findings not posted inline

  • packages/cli/src/commands/init/steps/install-eql.ts:126 — lopsided negative case: the new forwards --supabase so a Drizzle-on-Supabase project gets the role grants test covers supabase === true, but nothing asserts a plain Drizzle project passes supabase: undefined. One extra expect(vi.mocked(eqlMigrationCommand).mock.calls[0][0].supabase).toBeUndefined() against drizzleState/drizzleProvider closes it.
  • packages/cli/src/commands/eql/migration.ts:165 — the embedded + dryRun combination is unreachable from init today (init never sets dryRun), so it's only worth a test if EqlMigrationOptions is treated as a stable programmatic surface. Noted, not recommended.

Comment thread packages/cli/src/commands/eql/migration.ts
Comment thread packages/cli/src/commands/db/install.ts
Comment thread packages/cli/src/commands/init/steps/install-eql.ts
…l-clientPath re-run

Close the three test gaps auxesis flagged on #705 — all callee-side or
negative-case coverage the diff's new behaviour landed without:

- eql/migration.ts: assert the six `if (!embedded)` suppression sites and
  the `?? false` default at the callee (banners emitted by default;
  suppressed + SQL still written when embedded; abort outro suppressed but
  CliExit still propagates).
- db/install.ts: assert the `v3Alternative` pointer via its distinctive
  `stash eql migration --drizzle` substring (the pre-diff `/--drizzle/`
  match was unguarded), plus the lopsided-negative that it stays absent for
  --latest / --migration / --migrations-dir.
- init/steps/install-eql.ts: cover the null-clientPath re-run path (config
  already on disk) and assert a plain Drizzle project passes
  supabase: undefined.

Tests-only; no production change.

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

Thanks for addressing the feedback @tobyhede 😍

@tobyhede
tobyhede merged commit fbb3780 into main Jul 21, 2026
9 of 10 checks passed
@tobyhede
tobyhede deleted the fix/init-drizzle-eql-v3 branch July 21, 2026 02:14
@tobyhede

Copy link
Copy Markdown
Contributor Author

Tracked retroactively as #732 (filed after merge; closed as completed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants