Skip to content

Adopt EQL V3 self-configuring domain types in @cipherstash/stack schema #534

Description

@coderdan

Summary

Adopt EQL V3's self-configuring domain types in the @cipherstash/stack schema builder, consuming the new @cipherstash/eql package (published from the EQL repo — see that repo's companion issue).

EQL V3 replaces the per-column index-configuration model with self-configuring Postgres domain types (int4_eq, text_match, text_search, …). The type name encodes the searchable-encryption metadata, so:

  1. the types are Postgres Domains, which ORMs can read as jsonb; and
  2. the column is inherently self-configured — the searchable-encryption metadata no longer has to be configured client-side, because the type name specifies it (e.g. int4_eq always carries an HMAC equality term).

Current state

The going-forward builder is @cipherstash/stack/schema (packages/stack/src/schema/index.ts). It builds an EncryptConfig {v:1} with per-column cast_as + indexes: {ore, unique, match, ste_vec}, validates it, and passes it to protect-ffi at newClient(...) time (packages/stack/src/encryption/index.ts). It already has the seam for this migration: an eqlCastAsEnum (text/int/small_int/big_int/real/double/boolean/date/jsonb) and a toEqlCastAs() SDK→EQL mapper.

There is no EQL V3 footprint in the repo today — everything is EQL v2 (eql_v2 schema, eql_v2_encrypted composite type, eql_v2_configuration state machine, vendored eql-2.3.1 bundle).

Scope note. This issue targets the primary @cipherstash/stack/schema builder. The legacy standalone @cipherstash/schema (packages/schema) — used by @cipherstash/protect, now in maintenance mode — is intentionally out of scope here; migrate it separately if desired.

Goal

Migrate @cipherstash/stack/schema to depend on @cipherstash/eql and represent/emit V3 domain types instead of the index-config model, so that choosing a column's capabilities selects a self-configuring domain type rather than hand-assembling an indexes object.

Scope (primary)

  • Add @cipherstash/eql as a dependency and type column payloads with its generated types.
  • Map the builder API → V3 domain types, e.g.:
    • number + equality + range → int8_ord_ore (or width-appropriate int4_*)
    • string + match → text_match
    • string + equality + match + range → text_search
    • storage-only → the bare domain (int4, text, bool, …)
  • Decide whether the public API exposes the domain types directly or keeps the capability-builder (.equality() / .orderAndRange() / .freeTextSearch()) and infers the domain type from the chosen capabilities (eqlCastAsEnum / toEqlCastAs is the existing seam).
  • Replace the indexes object / EncryptConfig {v:1} emission accordingly.

Knock-on: docs / README (explicitly in scope)

  • README.md (top-level quick-start + "Searchable encryption"), packages/stack/README.md + CHANGELOG.md.
  • AGENTS.md, .cursorrules, docs/plans/encryption-migrations.md.
  • Agent skills: skills/stash-encryption, stash-drizzle, stash-dynamodb, stash-supabase, stash-cli (SKILL.md).
  • Examples: examples/basic, examples/prisma, examples/supabase-worker.
  • Add a changeset (repo uses Changesets, access: restricted).

Downstream impact (flagged — likely separate follow-up issues)

The schema builder is the entry point, but switching to self-configuring types ripples through the stack. Capturing these so they aren't lost:

  • protect-ffi owns cast_as-driven encoding and must accept/encode V3 domain types — coordinate a lockstep version bump (note the 7-day minimumReleaseAge cooldown excludes @cipherstash/protect-ffi*).
  • FFI initencryptConfigSchema hard-locks v: z.literal(1); packages/stack/src/wasm-inline.ts normalizeCastAs() rewrites cast_as via toEqlCastAs before the WASM newClient.
  • Proxy / DB config state machinepackages/cli db/push.ts (toEqlConfigpublic.eql_v2_configuration, pending/activate dance) and @cipherstash/migrate src/eql.ts (eql_v2.* wrappers). Self-configuring types may remove or reshape this.
  • Drizzlepackages/drizzle/src/pg/* hardcodes the eql_v2_encrypted type name and eql_v2.* operators (eq/gt/order_by/jsonb_path_query_first/…) and the composite-literal codec.
  • prisma-next — vendored eql-2.3.1 bundle (src/migration/eql-install.generated.ts), cipherstash/<type>@1 codec ids, and EQL_V2_* constants.
  • DynamoDBprotect-dynamodb / stack/src/dynamodb branch on cast_as === 'json' + indexes.ste_vec.

Acceptance criteria

  • @cipherstash/stack/schema depends on @cipherstash/eql and represents columns as V3 domain types (self-configuring) rather than the indexes config object.
  • Docs, README, examples, and skills are updated to the V3 type model.
  • A changeset is added; downstream-impact items above are filed as follow-up issues.

Dependency

Blocked on @cipherstash/eql being published from the EQL repo: cipherstash/encrypt-query-language#331

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions