Skip to content

feat(cli): add the stash-indexing skill — indexing EQL v3 encrypted columns#773

Merged
coderdan merged 6 commits into
mainfrom
feat/stash-indexing-skill
Jul 23, 2026
Merged

feat(cli): add the stash-indexing skill — indexing EQL v3 encrypted columns#773
coderdan merged 6 commits into
mainfrom
feat/stash-indexing-skill

Conversation

@coderdan

@coderdan coderdan commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Closes #753.

Problem

No shipped skill taught how to index an EQL v3 encrypted column. Two independent integration evals (#753) produced correct, encrypted, searchable integrations with zero indexes on any encrypted predicate — the installed skills contain no CREATE INDEX example, and the ORE-superuser warning was being over-generalized into "you can't index encrypted columns on Supabase."

The skill

skills/stash-indexing/SKILL.md (262 lines, single self-sufficient file — required for the Codex/editor-agent inline path). Content is condensed from the EQL repo's docs/reference/database-indexes.md, which I verified is byte-identical between main and the eql-bindings-v3.0.2 tag — exactly the @cipherstash/eql version this repo pins and stash eql install applies. All six extractor functions the recipes use are verified present in the shipped 3.0.2 SQL bundle.

Covers, per the issue's proposed scope:

  • The functional-index recipes — eq_term (btree-first; hash noted for small tables), ord_term, ord_term_ore, match_term GIN, to_ste_vec_query GIN jsonb_path_ops, and field-level JSON ordering — each tied to the types.* factories and raw domain names, with the engaging query shape.
  • The superuser distinction as its own section: only the ORE opclass needs superuser; equality/ordering/match/containment indexes all work as a normal role on Supabase — plus the record_ops silent-inert-index failure mode and its pg_opclass detection query.
  • Storage-only domains (bare types.T, types.Boolean) called out as having no index option by design.
  • Engagement requirements (term presence, typed operands, ANALYZE), the ORDER BY sort-key / value::jsonb / GROUP BY traps, build-at-scale guidance (maintenance_work_mem, btree-vs-hash builds, de-TOAST floor), and the EXPLAIN checklist.
  • ORM interaction: Drizzle / Prisma Next / Supabase emit operators but no index DDL — per-integration DDL placement spelled out.
  • Rollout timing: after stash encrypt backfill, before switching reads (also added as a blockquote in stash-encryption's cutover step).

Note: the skill uses the doc-canonical eql_v3.match_term(col) GIN form rather than the issue's eql_v3_internal.bloom_filter(...)match_term exists in 3.0.2, is the supported surface, and eql_v3_internal is internal (the Supabase skill explicitly says not to expose it).

Wiring

  • SKILL_MAP + BASE_SKILLS (CLI) and the wizard's own SKILL_MAP: installed for every integration — the gap is cross-cutting, per the issue.
  • SKILL_PURPOSES: one-liner for the setup prompt. Also fixes the missing stash-prisma-next entry, which rendered "(no description)" — with a new test asserting every SKILL_MAP-installable skill has a purpose line, so the gap class can't recur.
  • Cross-links from stash-encryption, stash-cli (its db validate "No indexes" Info finding now points somewhere), stash-drizzle, stash-supabase, stash-prisma-next.
  • Root AGENTS.md skills list + drift table row; doctrine's common-skills line (also adds the previously missing stash-prisma-next there).
  • Changeset: stash + @cipherstash/wizard minor (precedent: the stash-prisma-next skill addition).

Testing

  • pnpm --filter stash test — 824 pass (incl. updated exact-array install expectations and the new purpose-line guard) against a fresh dist/ bundle containing the new skill
  • pnpm --filter @cipherstash/wizard test — 148 pass
  • node packages/cli/dist/bin/stash.js manifest --json — every stash command the new/edited skill text names resolves (eql install, db validate, encrypt backfill|cutover|drop, status)
  • pnpm run code:check — exit 0
  • No Linear IDs in skill content; frontmatter name matches the directory; description is a single line

https://claude.ai/code/session_01BkEpKJC3975NHsKgMrCT8R

Summary by CodeRabbit

  • New Features
    • Added the stash-indexing skill with end-to-end guidance for functional indexing of encrypted EQL v3 columns (recipes, verification via EXPLAIN, and large-table build practices).
    • Automatically included stash-indexing in onboarding/setup flows and across supported integrations.
    • Added encryptedIndexes to generate recommended functional index builders for encrypted columns.
  • Documentation
    • Expanded indexing/rollout guidance across the encryption, CLI, Drizzle, Supabase, and Prisma Next skill docs.
  • Bug Fixes
    • Fixed a recursion issue when deriving v3 column SQL types in older Drizzle versions.
  • Tests
    • Updated CLI/wizard and Drizzle tests to cover stash-indexing and encryptedIndexes.

Update — review round 2 (b23f9c2d)

Per review: the general skill stays general, each ORM skill now carries its own applied-indexing section, and Drizzle gets real API support.

New: encryptedIndexes on @cipherstash/stack-drizzle/v3. Spread ...encryptedIndexes(t) in pgTable's extras callback and it derives the functional indexes for every encrypted column from the same per-domain capability record the operator layer gates on (builder.build().indexes) — the emitted indexes and the operators that engage them cannot drift. Named <table>_<column>_<capability>, tracked by drizzle-kit generate. 7 new tests pin the mapping, naming, and exact extractor expressions.

Two findings from building it:

  1. Upstream drizzle-orm bug (≤0.45): ExtraConfigColumn.getSQLType() is return this.getSQLType() — unconditional self-recursion. Any extras-callback column blew the stack through isEqlV3Column/getEqlV3Column's fallback path. getSqlType now recovers the domain from the column's custom-type params (the same expression PgCustomColumn's constructor uses) instead of calling it.
  2. The shipped EQL 3.0.2 bundle has no eq_term overload for _ord/_ord_ore domainseql_v3.eq on them inlines to ord_term(a) = ord_term(b), so one ordering btree serves = and range alike. The skill's capability table and equality recipe followed the EQL doc's overstated claim ("equality needs hm — _eq, _ord, …"); both now match the shipped SQL, and the skill explicitly warns against adding an eq_term index to an _ord column. (The EQL repo's doc should get a corresponding fix — noted for follow-up.)

Skills reshaped: stash-indexing § "Where the Index DDL Goes" is now a general pointer list; stash-drizzle gains a full Indexing Encrypted Columns section (helper first, manual expression-index DSL as the escape hatch); stash-supabase and stash-prisma-next gain concrete sections with the per-domain DDL (prisma-next: schema.prisma can't express functional indexes → raw-SQL migration op in the same history that installs the EQL bundle).

Verification: 824 CLI + 384 stack-drizzle tests pass (fresh builds), Biome exit 0, no Linear IDs. New changeset: @cipherstash/stack-drizzle minor.

…olumns

Closes #753. Integrations that were otherwise correct shipped with zero
indexes on encrypted predicates: nothing in the installed skills said
encrypted columns *can* be indexed, and the ORE-superuser warning was
being over-generalized into "you can't index encrypted columns on
Supabase."

The new skill condenses the EQL repo's database-indexes reference
(byte-identical between main and the eql-bindings-v3.0.2 tag — exactly
the @cipherstash/eql version stash ships; all extractor functions
verified present in the bundled SQL):

- functional-index recipes (eq_term / ord_term / ord_term_ore /
  match_term / to_ste_vec_query) mapped to the types.* domains
- what needs superuser (only the ORE opclass) vs what works on
  Supabase/managed Postgres, incl. the record_ops silent-failure check
- which domains are storage-only by design
- engagement requirements, the ORDER BY sort-key and GROUP BY traps
- build-at-scale guidance and the EXPLAIN checklist
- per-integration DDL placement (no ORM emits index DDL) and rollout
  timing (after backfill, before switching reads)

Wiring: installed for every integration by both the CLI handoffs
(SKILL_MAP + BASE_SKILLS) and the wizard's skills prompt; cross-links
added from the encryption/cli/drizzle/supabase/prisma-next skills. Also
fixes the missing stash-prisma-next SKILL_PURPOSES entry (rendered
"(no description)" in the setup prompt) and adds a test guarding every
installable skill against that gap.

Claude-Session: https://claude.ai/code/session_01BkEpKJC3975NHsKgMrCT8R
@coderdan
coderdan requested a review from a team as a code owner July 23, 2026 06:29
@changeset-bot

changeset-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a4a78db

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

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

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds the stash-indexing skill with EQL v3 functional-index recipes, query and rollout guidance, and integration-specific migration instructions. Drizzle gains capability-driven index generation, while CLI and wizard installation, prompts, documentation, and tests are updated.

Changes

EQL v3 indexing and skill delivery

Layer / File(s) Summary
Drizzle encrypted index generation
packages/stack-drizzle/src/v3/*, packages/stack-drizzle/__tests__/v3/*
Adds encryptedIndexes for capability-specific functional indexes and fixes wrapped-column domain resolution without recursive lookup.
Indexing recipes and rollout guidance
skills/stash-indexing/SKILL.md, skills/stash-*/SKILL.md, .changeset/*
Documents extractor recipes, query-shape requirements, operational verification, migration placement, and encryption rollout timing.
CLI and wizard skill delivery
packages/cli/src/commands/init/..., packages/wizard/src/lib/*, AGENTS.md
Bundles stash-indexing across integrations and fallbacks, adds prompt descriptions, and updates repository guidance.
Delivery and prompt validation
packages/cli/src/commands/init/lib/__tests__/*, packages/wizard/src/__tests__/*
Validates skill maps, copied and failed results, generated sections, and prompt descriptions.

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

Sequence Diagram(s)

sequenceDiagram
  participant PgTable
  participant encryptedIndexes
  participant EqlV3Column
  participant IndexBuilders
  PgTable->>encryptedIndexes: provide encrypted columns
  encryptedIndexes->>EqlV3Column: read capability metadata
  EqlV3Column-->>encryptedIndexes: return supported index domains
  encryptedIndexes->>IndexBuilders: build extractor-based indexes
  IndexBuilders-->>PgTable: return configured indexes
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: calvinbrewer

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the stash-indexing skill for EQL v3 encrypted column indexing.
Linked Issues check ✅ Passed The PR covers the requested indexing guidance, ORM responsibilities, storage-only domains, ORE caveats, and rollout timing for #753.
Out of Scope Changes check ✅ Passed The code, docs, and tests all support the indexing skill and related integration wiring; no unrelated changes stand out.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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/stash-indexing-skill

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: 6

🤖 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 `@skills/stash-encryption/SKILL.md`:
- Line 863: Remove the blank line within the blockquote in SKILL.md, keeping
consecutive blockquote lines continuous so Markdownlint rule MD028 passes.
- Around line 862-863: Update the functional-index rollout note in the stash
encryption cutover section to explicitly apply the index-creation step only to
EQL v3 columns, using the existing stash-indexing recipes. Add a clear v2 path
stating that no equivalent functional indexes are required or available, so v2
readers do not follow the v3 instructions.

In `@skills/stash-indexing/SKILL.md`:
- Around line 72-74: Update the copy-paste SQL recipes in SKILL.md, including
the ORE and JSON field-ordering examples, to include the required ANALYZE
statement after index creation. Ensure every recipe matches the documented
requirement that ANALYZE is mandatory; do not weaken that requirement.
- Around line 105-119: Make each published skill self-contained: in
skills/stash-indexing/SKILL.md lines 105-119, retain the field-ordering guidance
and explicitly explain how to derive or obtain the deterministic JSON selector;
in skills/stash-drizzle/SKILL.md lines 82-83, add a minimal Drizzle custom-SQL
CREATE INDEX example; in skills/stash-prisma-next/SKILL.md lines 146-150, add a
minimal Prisma Next migration example; and in skills/stash-supabase/SKILL.md
lines 77-82, add a minimal Supabase migration example, ensuring each site
includes the minimum actionable details without relying on sibling skills.
- Line 8: Revise the headline rule in SKILL.md to apply only to encrypted
domains that carry searchable terms. Explicitly exclude storage-only domains,
which have no term extractor and must not receive indexes, while preserving the
existing guidance to index a functional expression over the term extractor
rather than the encrypted column itself.
- Around line 22-32: Clarify the schematic factory notation in the capability
table: change generic rows to use forms such as types.<T>Eq, types.<T>Ord, and
types.<T>OrdOre, and add concrete v3 examples including types.TextEq,
types.IntegerOrd, and types.TimestampOrdOre. Preserve the existing domain, term,
and index-recipe descriptions.
🪄 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: a0c4423b-e507-4d61-8de9-9c8b6e9be9b2

📥 Commits

Reviewing files that changed from the base of the PR and between b7fa61f and 703b687.

📒 Files selected for processing (16)
  • .changeset/stash-indexing-skill.md
  • AGENTS.md
  • packages/cli/src/commands/init/doctrine/AGENTS-doctrine.md
  • packages/cli/src/commands/init/lib/__tests__/build-agents-md.test.ts
  • packages/cli/src/commands/init/lib/__tests__/install-skills.test.ts
  • packages/cli/src/commands/init/lib/__tests__/setup-prompt.test.ts
  • packages/cli/src/commands/init/lib/install-skills.ts
  • packages/cli/src/commands/init/lib/setup-prompt.ts
  • packages/wizard/src/__tests__/install-skills.test.ts
  • packages/wizard/src/lib/install-skills.ts
  • skills/stash-cli/SKILL.md
  • skills/stash-drizzle/SKILL.md
  • skills/stash-encryption/SKILL.md
  • skills/stash-indexing/SKILL.md
  • skills/stash-prisma-next/SKILL.md
  • skills/stash-supabase/SKILL.md

Comment thread skills/stash-encryption/SKILL.md Outdated
Comment thread skills/stash-encryption/SKILL.md Outdated
Comment thread skills/stash-indexing/SKILL.md
Comment thread skills/stash-indexing/SKILL.md Outdated
Comment thread skills/stash-indexing/SKILL.md
Comment thread skills/stash-indexing/SKILL.md
coderdan added 2 commits July 23, 2026 17:12
Review: "no ORM integration emits this DDL" was underspecified for
Drizzle — its DSL supports expression indexes
(index().using('btree', sql`eql_v3.eq_term(...)`)), so the primary
vehicle is the table definition tracked by drizzle-kit generate, not a
custom-SQL migration. Prisma stays migration-only (its schema language
cannot express functional indexes — upstream limitation); the Supabase
wrapper owns no schema at all. The per-integration section now says so.

Claude-Session: https://claude.ai/code/session_01BkEpKJC3975NHsKgMrCT8R
Review direction on #773: keep stash-indexing general-purpose, move the
how-to-apply-it material into each ORM skill, and give Drizzle real API
support.

The helper (`encryptedIndexes` on the /v3 entry): spread
`...encryptedIndexes(t)` in pgTable's extras callback and it derives the
functional indexes for every encrypted column from the same per-domain
capability record the operator layer gates on — so the emitted indexes
and the operators that engage them cannot drift. Named
`<table>_<column>_<capability>`, tracked by drizzle-kit generate.

Building it surfaced two things:

- drizzle-orm ≤0.45's ExtraConfigColumn.getSQLType() recurses into
  itself (upstream bug), so isEqlV3Column/getEqlV3Column blew the stack
  on any extras-callback column. getSqlType now recovers the domain from
  the column's custom-type params instead.
- The shipped EQL 3.0.2 bundle has NO eq_term overload for the _ord /
  _ord_ore domains — eql_v3.eq on them inlines to an ordering-term
  comparison, so ONE ordering btree serves = and range alike. The
  stash-indexing capability table and equality recipe followed the EQL
  doc's overstated claim; both now match the shipped SQL (and the skill
  explicitly warns against adding an eq_term index to an _ord column).

Skills: stash-indexing's per-integration section is now a general
pointer list; stash-drizzle gains an Indexing Encrypted Columns section
(helper first, manual expression-index DSL second); stash-supabase and
stash-prisma-next gain concrete sections with the per-domain DDL.

Claude-Session: https://claude.ai/code/session_01BkEpKJC3975NHsKgMrCT8R

@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/stack-drizzle/__tests__/v3/indexes.test.ts (1)

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

Exercise encryptedIndexes through the v3 barrel.

This imports an internal module instead of the supported /v3 surface. Import from ../../src/v3/index so the behavior test covers the public API.

Proposed change
-import { encryptedIndexes } from '../../src/v3/indexes'
+import { encryptedIndexes } from '../../src/v3/index'
🤖 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/stack-drizzle/__tests__/v3/indexes.test.ts` at line 9, Update the
encryptedIndexes import in the v3 indexes tests to use the supported v3 barrel
entry at ../../src/v3/index instead of the internal indexes module, so the
behavior test exercises the public API.

Source: Coding guidelines

🤖 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 `@skills/stash-drizzle/SKILL.md`:
- Around line 395-405: Update the index mapping table to use public v3 factory
names instead of internal type symbols: replace types.TEq with types.TextEq,
types.TOrd with representative types.TextOrd/ types.IntegerOrd, types.TOrdOre
with types.TextOrdOre, and bare types.T with types.Text. Preserve the existing
index mappings and the types.Boolean entry.

---

Nitpick comments:
In `@packages/stack-drizzle/__tests__/v3/indexes.test.ts`:
- Line 9: Update the encryptedIndexes import in the v3 indexes tests to use the
supported v3 barrel entry at ../../src/v3/index instead of the internal indexes
module, so the behavior test exercises the public API.
🪄 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: d3de5631-89d5-45b9-ba15-117b9adc2b58

📥 Commits

Reviewing files that changed from the base of the PR and between 5bf876c and b23f9c2.

📒 Files selected for processing (10)
  • .changeset/drizzle-encrypted-indexes.md
  • packages/stack-drizzle/__tests__/v3/exports.test.ts
  • packages/stack-drizzle/__tests__/v3/indexes.test.ts
  • packages/stack-drizzle/src/v3/column.ts
  • packages/stack-drizzle/src/v3/index.ts
  • packages/stack-drizzle/src/v3/indexes.ts
  • skills/stash-drizzle/SKILL.md
  • skills/stash-indexing/SKILL.md
  • skills/stash-prisma-next/SKILL.md
  • skills/stash-supabase/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • skills/stash-indexing/SKILL.md

Comment thread skills/stash-drizzle/SKILL.md
Review: the _ord equality story splits on term injectivity, not on _ord
itself. Numeric/date/timestamp ordering terms (OPE and ORE) are
injective, so those domains carry no hm, have no eq_term overload, and
one ordering btree serves = and range. TEXT ordering terms are
non-injective and cannot be relied on for equality, so text_ord /
text_ord_ore also carry hm and answer = via eq_term — those columns get
an eq_term index alongside the ordering one.

Verified against the shipped 3.0.2 bundle (eq on text_ord/text_ord_ore/
text_search_ore inlines to eq_term; on numeric _ord to ord_term) and the
stack builders (TextOrd = unique+ope, TextOrdOre = unique+ore).
encryptedIndexes already emitted the right pair — it derives from the
builders — so this is tests (TextOrd/TextOrdOre coverage), docblock, and
skill prose: the stash-indexing capability table now carries the
injectivity rationale, and stash-drizzle/supabase/prisma-next qualify
the "one index serves =" claim to the numeric domains.

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

Copy link
Copy Markdown
Contributor Author

Round 3 (0f161743): the _ord equality story is about term injectivity, not _ord itself — numeric/date/timestamp ordering terms are injective (no hm, no eq_term overload, one ordering btree serves = and range), while text ordering terms are non-injective, so text_ord/text_ord_ore carry hm and get an eq_term index alongside the ordering one. Verified against both the shipped 3.0.2 SQL and the stack builders (TextOrd = unique+ope); encryptedIndexes already emitted the right pair since it derives from the builders — this round is test coverage (TextOrd/TextOrdOre), docblock, and skill prose.

The corresponding EQL doc fix is up: cipherstash/encrypt-query-language#418.

coderdan added a commit to cipherstash/encrypt-query-language that referenced this pull request Jul 23, 2026
The database-indexes page claimed equality rides eq_term/hm on the _ord
domains generally. The shipped 3.0.2 bundle disagrees for the
numeric-and-time types: their ordering terms (OPE and ORE alike) are
injective, so those _ord/_ord_ore domains carry no hm, define no eq_term
overload, and eql_v3.eq inlines to an ordering-term comparison — one
ordering btree serves = and range. Text ordering terms are non-injective
and cannot be relied on for equality, so text_ord/text_ord_ore (and the
text_search variants) carry hm and answer = via eq_term.

Corrected in five places (the recipe comment, requirement 1, the
equality query pattern — now with the numeric-_ord engagement example —
the term-check troubleshooting item, and the zero-rows note), plus a new
note stating the injectivity rule once.

Found while porting this guide into the stash agent skill
(cipherstash/stack#753 / cipherstash/stack#773): an eq_term index
recommended for a numeric _ord column doesn't compile (no overload), and
the "type the column as _ord for equality" guidance pointed users at an
index that can't exist.

Claude-Session: https://claude.ai/code/session_01BkEpKJC3975NHsKgMrCT8R
Comment thread packages/stack-drizzle/src/v3/indexes.ts
Comment thread packages/stack-drizzle/src/v3/indexes.ts Outdated
Comment thread packages/stack-drizzle/src/v3/indexes.ts
Comment thread packages/stack-drizzle/src/v3/indexes.ts Outdated
Comment thread packages/stack-drizzle/src/v3/indexes.ts
Comment thread skills/stash-cli/SKILL.md Outdated
Comment thread skills/stash-cli/SKILL.md Outdated
An audit of the EQL repo's documentation against the shipped
@cipherstash/eql@3.0.2 SQL surfaced four defects that had been copied
into the new indexing guidance:

- The JSON containment GIN recipe was a PostgreSQL syntax error: a cast
  over a function call is a general expression and needs its own parens
  in an index_elem — ((eql_v3.to_ste_vec_query(col)::jsonb)
  jsonb_path_ops). Verified both forms against Postgres 17. Fixed in
  stash-indexing, stash-supabase, and stash-prisma-next (the Drizzle
  helper already emitted the correct form).
- The troubleshooting operand-typing example cast a query parameter to
  the column domain (public.eql_v3_text_eq), whose CHECK requires the
  ciphertext key 'c' that query payloads deliberately omit — the correct
  cast is the term-only eql_v3.query_text_eq domain.
- The term probe checked hm/ob/bf but not op, the CLLW-OPE term the
  same page names as the equality-and-range term for _ord domains.
- GROUP BY guidance now says which extractor to group on per domain:
  eq_term only exists on hm-carrying domains; numeric/date/timestamp
  *Ord/*OrdOre group on their injective ordering term.

Also documented a trap the audit exposed: the EQL install SQL begins
with DROP SCHEMA eql_v3 CASCADE, so stash eql upgrade / reinstall
silently cascade-drops every functional index built on the extractors.
stash-indexing and stash-cli (eql upgrade) now say to re-run index
migrations + ANALYZE afterwards.

Claude-Session: https://claude.ai/code/session_01BkEpKJC3975NHsKgMrCT8R

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
skills/stash-cli/SKILL.md (1)

414-414: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Qualify the “No indexes” remediation for storage-only domains.

Not every encrypted column can or should receive an index: storage-only domains intentionally have no indexing option. Clarify that the Info finding is actionable only for domains with supported functional-index recipes.

🤖 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 `@skills/stash-cli/SKILL.md` at line 414, Update the “No indexes” remediation
text in SKILL.md to state that it is actionable only for domains with supported
functional-index recipes; explicitly exclude storage-only domains, where
encrypted columns intentionally have no indexing option.
🤖 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 `@skills/stash-cli/SKILL.md`:
- Line 397: Update the upgrade guidance in the install SQL documentation to
instruct users to create and apply a new index migration, or explicitly rerun
the extractor index DDL, after the schema recreation. Retain the requirement to
run ANALYZE, and do not suggest merely rerunning an already-applied migration.

In `@skills/stash-drizzle/SKILL.md`:
- Line 399: Update the mapping row for types.TextEq and numeric *Eq to also
cover date and timestamp equality domains, using wording consistent with the
companion N definition while preserving the existing eql_v3.eq_term index
mapping.

---

Outside diff comments:
In `@skills/stash-cli/SKILL.md`:
- Line 414: Update the “No indexes” remediation text in SKILL.md to state that
it is actionable only for domains with supported functional-index recipes;
explicitly exclude storage-only domains, where encrypted columns intentionally
have no indexing option.
🪄 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: 0df59a30-51c2-4d63-9f40-cfe3318920c7

📥 Commits

Reviewing files that changed from the base of the PR and between b23f9c2 and 62746dc.

📒 Files selected for processing (8)
  • .changeset/drizzle-encrypted-indexes.md
  • packages/stack-drizzle/__tests__/v3/indexes.test.ts
  • packages/stack-drizzle/src/v3/indexes.ts
  • skills/stash-cli/SKILL.md
  • skills/stash-drizzle/SKILL.md
  • skills/stash-indexing/SKILL.md
  • skills/stash-prisma-next/SKILL.md
  • skills/stash-supabase/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • .changeset/drizzle-encrypted-indexes.md
  • packages/stack-drizzle/tests/v3/indexes.test.ts
  • packages/stack-drizzle/src/v3/indexes.ts

Comment thread skills/stash-cli/SKILL.md Outdated
Comment thread skills/stash-drizzle/SKILL.md Outdated
…atform support

Dan's comments:
- indexes.ts docblock: example now shows its imports; the injectivity
  paragraph is rewritten in plain language; the extractor/tableNameOf
  helpers gained concrete input→output examples.
- @cipherstash/stack-drizzle README: new 'Indexing encrypted columns'
  section with the encryptedIndexes example.
- stash-cli: the db push / db activate section is removed (commands
  remain in the CLI; the skill no longer documents them). Cross-refs in
  the description, config table, and init steps scrubbed. The v2
  cutover flows in stash-encryption/-drizzle/-supabase still reference
  db push — flagged on the PR for a decision on the v2 lifecycle docs.
- ORE operator-class availability is now per-platform instead of
  'unavailable on managed Postgres': supported on self-hosted Postgres
  and AWS RDS/Aurora, refused on cloud-hosted Supabase (the one
  confirmed platform) — stated consistently across stash-cli,
  stash-indexing, stash-encryption, and stash-drizzle.

CodeRabbit:
- stash-encryption cutover note scoped to EQL v3 (v2 rollouts skip it)
  and converted from blockquote to paragraph (MD028).
- stash-indexing: ANALYZE added to the ORE and JSON field-ordering
  recipes; the JSON selector hash is now derivable in-file
  (encryptQuery with queryType 'steVecSelector'); reinstall guidance
  says to add a new migration (runners skip applied ones).
- stash-cli eql upgrade: same new-migration wording; db validate 'No
  indexes' Info finding scoped to term-carrying columns (storage-only
  domains need no action).
- stash-drizzle: equality row covers numeric/date/timestamp *Eq.

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

Copy link
Copy Markdown
Contributor Author

Round 4 (a4a78db6) — all review comments addressed:

Dan's comments

  • indexes.ts: docblock example now includes its imports; the injectivity paragraph is rewritten in plain language (the term-theory detail lives in the skill); extractor/tableNameOf got concrete input→output examples.
  • @cipherstash/stack-drizzle README: new Indexing encrypted columns section with the encryptedIndexes example.
  • db push / db activate section removed from stash-cli, with cross-references scrubbed (description, config table, init steps). One open question: the EQL v2 rollout/cutover flows in stash-encryption, stash-drizzle, and stash-supabase still instruct v2+Proxy users to run stash db push (the cutover pending-config precondition). Since the commands still exist in the CLI I left those v2 flows intact — if the commands are being removed outright, say the word and I'll sweep the v2 lifecycle docs too.
  • ORE operator-class availability is now stated per-platform everywhere (stash-cli, stash-indexing, stash-encryption, stash-drizzle): supported on self-hosted Postgres and AWS RDS/Aurora; refused on cloud-hosted Supabase — no more blanket "managed Postgres". One nuance from the citation research (details in the session summary): Aurora's delegation of CREATE OPERATOR CLASS to rds_superuser is release-noted (Aurora 12.12.0, Nov 2022; present in 13+), but for RDS for PostgreSQL proper no AWS documentation of the delegation exists — if our RDS claim is from a tested instance, we're good; if it's from memory, worth one verification run before this ships.

CodeRabbit

  • Cutover index note scoped to EQL v3 with a v2 no-op path, and de-blockquoted (MD028).
  • ANALYZE added to the ORE and JSON field-ordering recipes.
  • JSON selector hash now derivable within stash-indexing itself (encryptQuery(path, { queryType: 'steVecSelector' })).
  • Reinstall/upgrade guidance now says to add a new migration re-issuing the index DDL (runners skip applied migrations) — both stash-indexing and stash-cli.
  • db validate "No indexes" remediation scoped to term-carrying columns; storage-only domains explicitly need no action.
  • Drizzle equality row covers numeric/date/timestamp *Eq; a minimal rawSql index-migration example added to stash-prisma-next (op shape verified against @prisma-next/family-sql types).
  • Skipped: the suggestion to add a second Drizzle custom-SQL example at the pointer site (skill line ~82) — the full section exists later in the same file, and self-containment is per-file.

824 CLI + 384 stack-drizzle tests green; Biome clean.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
skills/stash-drizzle/SKILL.md (1)

380-391: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document encryptedIndexes in the v3 export list.

This section introduces encryptedIndexes as a public import, but the later “Other v3 Exports” list omits it. Add the helper there so the skill remains internally consistent and self-sufficient.

Proposed documentation update
-`types`, `makeEqlV3Column`, `getEqlV3Column`, ...
+`types`, `encryptedIndexes`, `makeEqlV3Column`, `getEqlV3Column`, ...

As per coding guidelines, SKILL.md is a public shipped artifact and must remain self-sufficient.

🤖 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 `@skills/stash-drizzle/SKILL.md` around lines 380 - 391, Update the “Other v3
Exports” list in SKILL.md to include encryptedIndexes alongside the documented
v3 exports, matching its public import and usage in the users table example.

Source: Coding guidelines

🤖 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.

Outside diff comments:
In `@skills/stash-drizzle/SKILL.md`:
- Around line 380-391: Update the “Other v3 Exports” list in SKILL.md to include
encryptedIndexes alongside the documented v3 exports, matching its public import
and usage in the users table example.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c5c491f-40c4-4516-bb46-3a490834c64d

📥 Commits

Reviewing files that changed from the base of the PR and between 62746dc and a4a78db.

📒 Files selected for processing (7)
  • packages/stack-drizzle/README.md
  • packages/stack-drizzle/src/v3/indexes.ts
  • skills/stash-cli/SKILL.md
  • skills/stash-drizzle/SKILL.md
  • skills/stash-encryption/SKILL.md
  • skills/stash-indexing/SKILL.md
  • skills/stash-prisma-next/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/stack-drizzle/src/v3/indexes.ts
  • skills/stash-indexing/SKILL.md

@coderdan

Copy link
Copy Markdown
Contributor Author

Docs/skills focused PR.

@coderdan
coderdan merged commit 239f79b into main Jul 23, 2026
13 checks passed
@coderdan
coderdan deleted the feat/stash-indexing-skill branch July 23, 2026 10:58
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.

Add a stash-indexing skill: how to index EQL v3 encrypted columns

1 participant