| stash | minor |
|---|
Rewrite db validate as eql validate, for the EQL v3 domain-type vocabulary.
Fixes a false finding on the most ordinary v3 columns. The old rule set
checked for ore / unique / match / ste_vec indexes and never learned
about ope. EQL v3's default ordering domains emit ope, so
types.IntegerOrd('age') and types.TimestampOrd('created_at') were both
reported as "Column is encrypted but has no indexes — it will not be
searchable". They are now silent.
The command reads your tables through the new
EncryptionClient.getSchemas(), so it sees each column's concrete domain
rather than the lossy encrypt config, and gains a database pass when one is
reachable.
Schema checks (no database needed):
| Rule | Severity |
|---|---|
An _ord_ore domain is declared — its ORE operator class needs superuser |
Warning |
| Storage-only column: encrypts and decrypts, carries no query terms | Info |
Searchable boolean column |
Error |
Free-text match index on a non-text domain |
Error |
Encrypted-JSONB (ste_vec) index without types.Json |
Error |
Database checks (skipped with a notice, not a failure, when no database is reachable):
| Rule | Severity |
|---|---|
| EQL v3 is not installed — reported once, remaining database checks skipped | Error |
| A declared table lives in a different schema than the one searched | Warning |
| A declared table is in the searched schema but invisible to the connected role | Warning |
A declared table name carries a schema qualifier (schema.table) — not checked |
Warning |
| A declared table exists in no schema at all | Error |
| A declared column is missing from a table that was found | Error |
| The database column's domain differs from the declared one | Error |
| The database column is still plain (no EQL domain) | Error |
An _ord_ore domain where the EQL install could not create the ORE operator class |
Error |
| A queryable column with no functional index over its term extractor | Info |
| A declared table name that resolved in the searched schema also exists in another one | Info |
--exclude-operator-family is removed: it warned that an ore index would not
support ORDER BY without operator families, and the pinned EQL v3 bundle
self-adapts. eql install / eql upgrade had already rejected the flag;
validate was its last consumer.
The database pass inspects current_schema() only, and distinguishes four
reasons a declared table can be missing from it, so that only the last fails
the command. In another schema (Prisma multiSchema, a tenant schema): a
Warning naming that schema. Present but invisible to the connected role: a
Warning carrying the GRANT SELECT to run — information_schema reports only
what the role holds a privilege on, so a missing grant is not a missing
migration. Declared as schema.table: a Warning saying it was not checked,
because validate matches table names unqualified. Absent everywhere: an Error.
Reported once per table rather than once per column.
The relation lookup that answers those questions excludes pg_* and
information_schema. Unscoped it matched the system views named columns,
domains, parameters, routines, sequences, tables and triggers — all
ordinary application table names — so a project declaring one of them that had
not run its migration was told the table "exists in schema information_schema",
as a Warning, and the command exited 0 on a genuinely unapplied migration.
An unqualified name found in more than one schema is now reported as an Info
naming the relation that was actually checked ("public"."users") and the
other schemas holding that name. A bare name resolves through search_path, so
users in both public and Supabase's auth left it ambiguous which relation
every other finding described. Info, not Warning: it must not fail or
de-clean an ordinary Supabase project.
Two of those used to exit 1 and no longer do: a privilege-invisible table and a schema-qualified declaration were both reported as "does not exist in any schema", which sent people to re-run a migration that had already run.
Against a project whose @cipherstash/stack predates getSchemas(), validate
says so and falls back to the encrypt config, running the index-derived rules
and skipping the domain ones.
stash db validate keeps working as a deprecated alias, like db install /
db upgrade / db status. Exits 1 on errors only.