Skip to content

skills: add the EQL v3 type → predicate → domain → index capability matrix - #908

Open
coderdan wants to merge 1 commit into
dan/ore-unavailable-at-installfrom
dan/skills-type-capability-matrix
Open

skills: add the EQL v3 type → predicate → domain → index capability matrix#908
coderdan wants to merge 1 commit into
dan/ore-unavailable-at-installfrom
dan/skills-type-capability-matrix

Conversation

@coderdan

Copy link
Copy Markdown
Contributor

Fixes #892. Stacked on #907 (→ #906) — this PR's diff is the last commit only.

The gap

I burned real time discovering that types.Double doesn't mint ORE blocks and I needed types.DoubleOrdOre.

Picking the wrong factory is silent at authoring time. There is no type error and no runtime warning — the predicate you wanted simply does not run, and you find out from a query. So the cost of not documenting it lands late, on someone who has already written the schema.

The skills had the pieces: a capability-suffix table, a family table, a naming rule, an indexing table in stash-indexing, a query-domain table in stash-postgres. What they did not have was one row you can look your factory up in. Answering "can types.Double do a range query" meant composing two tables and knowing the exceptions. It cannot — types.Double is storage-only.

What this adds

A capability matrix high in skills/stash-encryption/SKILL.md, inside ### The types Namespace: one row per factory, all 40, with the Postgres column domain, the predicates it supports, the extractor to index it through, and whether it works on managed Postgres. Cross-linked from stash-indexing ("use it to pick the type; use this page to index it") and stash-postgres ("this one is the SQL-side view of it").

Rows are explicit rather than types.<N>Ord-style shorthand on purpose: the reporter's move is Ctrl-F for types.Double, which only works if the literal string is on the page. The three things the table then calls out in prose are the ones the shorthand was hiding — that a bare factory name answers nothing, that the numeric _ord domains deliberately have no eq_term overload (equality rides the injective ordering term, so adding an equality index there indexes a function that does not exist), and that Ord and OrdOre mint non-cross-comparable terms so you cannot swap one for the other without re-encrypting.

Also the schema-layout note the issue asked for: public holds the column domains, eql_v3 the query domains and operator functions, eql_v3_internal the index-term types — a single encrypted comparison touches all three, which is why the Supabase grants have to cover the last two, and why public surviving a reinstall is what keeps your columns.

Two corrections

Keeping it honest

The issue suggested generating the table from types.ts. I pinned it instead: this repo has no markdown codegen, and the skills are hand-authored prose wrapped around their tables, so a generator would own a fragment of a file humans edit. Pinning is the pattern already used for skill content (skill-supabase-apply.test.ts), and it fails in the same place a generator would — the moment source and prose disagree.

packages/stack/__tests__/eql-v3-capability-matrix-skill.test.ts derives from the types namespace by probing each factory (getEqlType(), build().indexes) and asserts:

  • exactly one row per factory, no duplicates, nothing extra;
  • each row names the domain that factory actually builds;
  • every storage-only factory says "storage only";
  • every row names the extractors its factory emits — and none it does not, which is what stops the matrix from ever telling someone to put an eq_term index on a numeric _ord column;
  • every ORE row is marked unusable where the opclass is absent.

I confirmed it fails rather than passing vacuously, by breaking a domain name and by giving a storage-only row a predicate — both caught.

pnpm --filter @cipherstash/stack test (1050) and pnpm --filter stash test (1331) green.

https://claude.ai/code/session_01AwM5Cm5ddasXozb6stxPR1

Picking the wrong `types.*` factory is silent at authoring time -- no type
error, no runtime warning, just a predicate that never runs. The skills
documented the capability suffixes and the families they apply to, but never
the 40 concrete factories in one lookup, so answering "can `types.Double` do
a range query" meant composing two tables and knowing the exceptions. It
cannot: `types.Double` is storage-only.

One row per factory: column domain, supported predicates, the extractor to
index through, and whether it works on managed Postgres. Plus a note on which
schema holds what, and why the Supabase grants have to cover `eql_v3` and
`eql_v3_internal` both.

Two corrections fell out of writing it:

- The `Ord` vs `OrdOre` callout said the install "disables" the `_ord_ore`
  domains. Precisely: the bundle adds an always-raising CHECK, so a write
  fails -- unusable, not merely unindexed. It now says that, notes RDS and
  Aurora do support ORE while cloud-hosted Supabase does not, and points at
  `eql preflight` / `eql status` instead of asking the reader to guess.
- The `stash-postgres` naming table omitted `types.TextOrdOre` entirely; its
  `<N>` shorthand covers only the numeric and temporal families.

A test derives the matrix from the `types` namespace and fails if the skill
disagrees -- every factory present once, mapped to the domain it builds,
naming the extractors it emits and none it does not. Verified to fail on a
wrong domain name and on a storage-only row claiming a predicate.

Claude-Session: https://claude.ai/code/session_01AwM5Cm5ddasXozb6stxPR1
@coderdan
coderdan requested a review from a team as a code owner August 18, 2026 12:33
@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c6514fe

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/stack-prisma Patch
@cipherstash/wizard Patch
@cipherstash/bench 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

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

Approve. This is the right artifact for the failure mode (silent-at-authoring-time factory choice → Ctrl-F for the literal factory name), and the pinning test makes it the rare skills table that cannot silently drift. CI green.

What I verified:

  • The test's derivation is sound: it probes each factory's actual getEqlType() and build().indexes, requires exactly one row per factory with none extra, and asserts extractors both positively and negatively. The negative half is the one that matters — it's what stops the matrix from ever telling someone to put an eq_term index on a numeric _ord column, the precise trap the prose calls out.
  • The backticked() matcher genuinely closes the substring trap: in a TextOrdOre row, `ord_term_ore` cannot false-match the `ord_term` needle (the char after ord_term is _, not a backtick), so the negative assertions are satisfiable and meaningful. I walked the TextOrd/TextOrdOre/IntegerOrd rows against their factories' index kinds and each holds.
  • Pin-over-generate is the right call here — the repo has no markdown codegen, the table is embedded in hand-authored prose, and skill-supabase-apply.test.ts already establishes the pattern. The author confirmed the test fails non-vacuously by mutating a domain name and a storage-only row; the section-scoped matrixRows() (heading → closing paragraph) prevents an example elsewhere in the file satisfying a row.
  • The two corrections are accurate against the stack: the eql_ore_unavailable CHECK means ⛔-unusable rather than ⚠️-unindexable on the 3.0.4 bundle (consistent with #907's model, which this stacks on), and types.TextOrdOre genuinely fell through the stash-postgres naming table's <N> shorthand gap.
  • Cross-links from stash-indexing and stash-postgres designate one canonical table with the others as views of it — the structure that prevents three tables drifting into three answers. Changeset present (stash patch, correct for a skills-only change).

One cosmetic observation, not blocking: the "Managed Postgres" column header carries ⛔ on every ORE row while the callout below explains it is not a blanket managed-Postgres rule (RDS/Aurora clear the gate). The "see below" hedge in the cells covers it, but a header like "Unprivileged install" would make the column self-consistent if the table is ever revised.

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