Problem
@cipherstash/migrate — the plaintext→encrypted column migration engine that backs the stash encrypt * CLI group — only supports EQL v2. Columns installed under EQL v3 (stash eql install --eql-version 3) are not covered by its migration lifecycle. Its own README says so explicitly.
With Stack 1.0 centered on EQL v3, this is a real gap: a user who adopts v3 has no first-party, resumable, production-safe path to encrypt an existing plaintext column.
Why v3 doesn't "just work"
The v2 coupling is concentrated in packages/migrate/src/eql.ts, which wraps the v2 configuration state machine and its public.eql_v2_configuration table:
eql_v2.select_pending_columns(), eql_v2.ready_for_encryption()
eql_v2.migrate_config() (pending → encrypting), eql_v2.activate_config() (encrypting → active), discard
eql_v2.rename_encrypted_columns() — the cut-over primitive
eql_v2.reload_config() — Proxy refresh
eql_v2.count_encrypted_with_active_config()
EQL v3 has none of this. v3 is domain-native: configuration lives in each column's concrete eql_v3_* domain type, not in a configuration table, and there is no pending→active state machine. So the entire config-lifecycle surface above has no v3 analogue, and v3 cut-over is inherently different (a plain column rename, since there is no config table to coordinate).
The stash encrypt command group (backfill, cutover, drop, status, plan) has zero v3 branches today — it is v2 end to end.
What is reusable
packages/migrate/src/backfill.ts is largely version-agnostic: it reads plaintext pages, delegates encryption to an injected client (bulkEncryptModels), and writes JSON back. The event log (state.ts → cipherstash.cs_migrations), keyset pagination (cursor.ts), and SQL quoting (sql.ts) are version-neutral too. The v3 work is therefore mostly about the lifecycle/cut-over layer, not the backfill core.
Proposed approach (to refine in the PR)
- Introduce an EQL-version seam for the lifecycle primitives (
eql.ts): keep the v2 config-state-machine implementation, add a v3 implementation whose lifecycle is minimal — backfill → rename cut-over — with the config-table/state-machine steps and reload_config/count_encrypted_with_active_config either dropped or replaced with v3-native equivalents.
- Verify the backfill core against v3 columns: the encrypted-column write must satisfy the concrete
eql_v3_* domain CHECK (today it writes $1::jsonb), and the payload leak-guard (isEncryptedPayload) must accept v3 storage payloads. Confirm the v3 encryption client exposes the same bulkEncryptModels surface.
- Wire
stash encrypt * to v3 — detect the column's EQL version (or accept --eql-version 3) and route to the v3 lifecycle.
- Docs/skills: update the migrate README's "targets EQL v2" caveat, and
skills/stash-encryption / skills/stash-cli for the v3 encrypt workflow.
Acceptance criteria
- A plaintext column can be encrypted end-to-end into an EQL v3 column via
@cipherstash/migrate primitives and the stash encrypt * CLI (resumable backfill + cut-over).
- v2 behavior is unchanged.
- Live-DB integration coverage for the v3 backfill + cut-over path (CI-only, alongside the existing v3 adapter suites).
- README/skills no longer state that v3 is uncovered.
Out of scope
- Changing the v2 lifecycle.
- The unintended
@cipherstash/migrate 1.0.0-rc.0 version bump (tracked separately) — this issue is about v3 capability, not versioning.
Context
Surfaced during the Stack 1.0.0-rc.0 release: @cipherstash/migrate was swept to 1.0.0-rc.0 by dependency propagation despite being a v2-only tool, which prompted the question of whether it should support v3 at all for the 1.0 line.
Problem
@cipherstash/migrate— the plaintext→encrypted column migration engine that backs thestash encrypt *CLI group — only supports EQL v2. Columns installed under EQL v3 (stash eql install --eql-version 3) are not covered by its migration lifecycle. Its own README says so explicitly.With Stack 1.0 centered on EQL v3, this is a real gap: a user who adopts v3 has no first-party, resumable, production-safe path to encrypt an existing plaintext column.
Why v3 doesn't "just work"
The v2 coupling is concentrated in
packages/migrate/src/eql.ts, which wraps the v2 configuration state machine and itspublic.eql_v2_configurationtable:eql_v2.select_pending_columns(),eql_v2.ready_for_encryption()eql_v2.migrate_config()(pending → encrypting),eql_v2.activate_config()(encrypting → active),discardeql_v2.rename_encrypted_columns()— the cut-over primitiveeql_v2.reload_config()— Proxy refresheql_v2.count_encrypted_with_active_config()EQL v3 has none of this. v3 is domain-native: configuration lives in each column's concrete
eql_v3_*domain type, not in a configuration table, and there is no pending→active state machine. So the entire config-lifecycle surface above has no v3 analogue, and v3 cut-over is inherently different (a plain column rename, since there is no config table to coordinate).The
stash encryptcommand group (backfill,cutover,drop,status,plan) has zero v3 branches today — it is v2 end to end.What is reusable
packages/migrate/src/backfill.tsis largely version-agnostic: it reads plaintext pages, delegates encryption to an injected client (bulkEncryptModels), and writes JSON back. The event log (state.ts→cipherstash.cs_migrations), keyset pagination (cursor.ts), and SQL quoting (sql.ts) are version-neutral too. The v3 work is therefore mostly about the lifecycle/cut-over layer, not the backfill core.Proposed approach (to refine in the PR)
eql.ts): keep the v2 config-state-machine implementation, add a v3 implementation whose lifecycle is minimal — backfill → rename cut-over — with the config-table/state-machine steps andreload_config/count_encrypted_with_active_configeither dropped or replaced with v3-native equivalents.eql_v3_*domain CHECK (today it writes$1::jsonb), and the payload leak-guard (isEncryptedPayload) must accept v3 storage payloads. Confirm the v3 encryption client exposes the samebulkEncryptModelssurface.stash encrypt *to v3 — detect the column's EQL version (or accept--eql-version 3) and route to the v3 lifecycle.skills/stash-encryption/skills/stash-clifor the v3 encrypt workflow.Acceptance criteria
@cipherstash/migrateprimitives and thestash encrypt *CLI (resumable backfill + cut-over).Out of scope
@cipherstash/migrate1.0.0-rc.0 version bump (tracked separately) — this issue is about v3 capability, not versioning.Context
Surfaced during the Stack 1.0.0-rc.0 release:
@cipherstash/migratewas swept to1.0.0-rc.0by dependency propagation despite being a v2-only tool, which prompted the question of whether it should support v3 at all for the 1.0 line.