feat(stack,cli): EQL v3 Supabase adapter (encryptedSupabaseV3) + v3 install path#547
Conversation
…pter The v2 query mechanism (direct EQL operators over PostgREST) unchanged; EncryptedQueryBuilderImpl gains narrow protected seams whose defaults preserve the v2 behaviour byte-for-byte, and a v3 subclass overrides them: - column recognition + property↔DB name resolution via buildColumnKeyMap (filters, mutations, aliased select casts `prop:db::jsonb`) - raw jsonb mutation payloads (no eql_v2 composite wrap) - full-envelope filter operands: every eql_v3.* domain CHECK requires the storage keys (v/i/c + index terms) and the SQL operators coerce their jsonb operand into the domain, so a narrowed encryptQuery term (c?: never) fails 23514 on EVERY domain — not just text_search as the design spec assumed. All operands go through encrypt() instead. - like/ilike on encrypted columns → PostgREST cs (bloom @>); the domains define no LIKE operator - Date reconstruction from cast_as on decrypted rows - capability validation: filters on storage-only columns or unsupported query types throw typed + runtime errors Wire-encoding unit tests (mock encryption + supabase clients) cover both dialects, including v2 regression pins for the seams. Part of CIP-3300; design spec in PR #546.
- Vendor the v3 SQL bundles into packages/cli/src/sql via a checked-in derivation script (scripts/build-eql-v3-sql.mjs): the full bundle is a byte-identical copy of the stack fixture monolith; the Supabase variant strips the two CREATE OPERATOR CLASS/FAMILY chunks at their --! @file markers, mirroring upstream's **/*operator_class.sql exclusion glob. Temporary vendoring (sync risk documented) until upstream ships v3 release artifacts. - EQLInstaller: eqlVersion option on install/isInstalled/ getInstalledVersion; v3 + --latest rejected (no public artifacts); grants keyed to the installed schema via the new supabasePermissionsSql(schemaName) helper (SUPABASE_PERMISSIONS_SQL unchanged, SUPABASE_PERMISSIONS_SQL_V3 added). - stash db install --eql-version 3: direct install only for now — explicit --drizzle/--migration/--latest are rejected up-front, auto-detected drizzle falls back to direct with a notice. Part of CIP-3300.
- supabase-v3.test.ts mirrors the v2 live suite over eql_v3 domains:
round-trips (incl. a Timestamptz column proving Date reconstruction),
bulk models, text_search equality (full-envelope operand), free-text
like→cs (include_original: false — load-bearing, see the suite header),
int4_ord equality + gte/lte range, timestamptz_ord range with Date
values. Same env gating as the v2 suite; the eql_v3 Exposed-schemas
dashboard step is documented as the manual prerequisite.
- supabase.test.ts gains the v2 encrypted-range test (gte/lte on an
orderAndRange number column) — the 'range filtering works on Supabase'
claim previously rested on a one-off live spike with no CI baseline.
- installEqlV3IfNeeded accepts { supabase: true }: opclass-stripped
bundle + eql_v3 grants, matching the CLI's --eql-version 3 --supabase.
Part of CIP-3300.
- stash-supabase skill: new 'EQL v3 (native eql_v3.* domains)' section — setup, per-domain DDL, --eql-version 3 install, the Exposed-schemas silent-fallback warning, v3-specific behaviour (full-envelope operands, like→cs, include_original: false for substring match), shared caveats. - Recreate docs/reference/supabase-sdk.md (deleted in def9f4b; AGENTS.md 'Useful Links' had a dangling reference) covering both adapters, the install + Exposed-schemas story, and the v3 encoding details. - Changeset: minor for @cipherstash/stack and stash. Part of CIP-3300.
🦋 Changeset detectedLatest commit: 1cd6620 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
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 |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Summary
Implements EQL v3 on Supabase per the design spec in #546 (CIP-3300):
encryptedSupabaseV3at parity with the v2encryptedSupabaseadapter, using nativeeql_v3.*domain columns. Stacked onfeat/eql-v3-types-module(#541), targeting that branch.Core principle preserved from the spec: v3 mirrors v2 exactly — same query mechanism (direct EQL operators over PostgREST), same operator-visibility caveat + Exposed-schemas requirement, same install/permissions story. Only the column types and the wire encoding differ.
What's here
SDK adapter (
@cipherstash/stack/supabase)encryptedSupabaseV3+EncryptedQueryBuilderV3Impl, a narrow subclass ofEncryptedQueryBuilderImpl. The base class gains protected dialect seams whose defaults preserve v2 byte-for-byte (pinned by v2 regression tests); the v3 subclass overrides: column recognition, property↔DB name resolution (buildColumnKeyMap, aliasedprop:db::jsonbselect casts), raw-jsonb mutation payloads (no composite wrap), full-envelope filter operands,like/ilike→ PostgRESTcs, andDatereconstruction fromcast_as.InferPlaintext<Table> & Record<string, unknown>; with an explicit row type, storage-only columns (e.g.types.Bool) are excluded from filter methods at the type level (runtime guard always applies). The sharedEncryptedQueryBuildergains an optional filterable-keys generic (defaulted — v2 signatures unchanged).DB bundle + install
packages/cli/src/sql/via a checked-in derivation script (fallback strategy from the spec; the Supabase variant strips the two opclass chunks at their--! @filemarkers, mirroring upstream's**/*operator_class.sqlexclusion glob; sync risk documented in the script).stash db install --eql-version 3 [--supabase]— direct path only for now (--drizzle/--migration/--latestrejected with clear errors). Grants generalized:supabasePermissionsSql(schemaName)shared by v2/v3 (SUPABASE_PERMISSIONS_SQLunchanged,SUPABASE_PERMISSIONS_SQL_V3added).installEqlV3IfNeeded(test helper) is Supabase-aware:{ supabase: true }= stripped bundle +eql_v3grants.Tests
supabase-v3.test-d.ts).supabase-v3.test.tsmirroring the v2 suite (same env gating): round-trips incl. aTimestamptzcolumn provingDatereconstruction, bulk models,text_searchequality, free-textlike→cs,int4_ordequality + range,timestamptz_ordrange withDatevalues.supabase.test.ts(gte/lteon anorderAndRangenumber column) — the spec flagged that encrypted range on Supabase had no CI-covered v2 baseline.Docs
stash-supabaseskill: new EQL v3 section (setup, DDL mapping, install, the Exposed-schemas silent-fallback warning, v3 behaviour, shared caveats).docs/reference/supabase-sdk.md(deleted indef9f4bd; fixes the dangling AGENTS.md link).@cipherstash/stack+stash.Deviations from the spec (found in the bundle source, not guessed)
text_searchequality. The spec assumed single-capability domains' terms satisfy their own CHECKs (e.g. "anint4_ordrange term carriesob, which is exactly whatint4_ordrequires"). They don't: everyeql_v3.*domain CHECK also requiresv,i, andc(see e.g. theint4_ord/text_eqCHECKs in the bundle), and protect-ffi'sEncryptedScalarQueryis typedc?: never— query terms can never pass any domain CHECK. The(domain, jsonb)operator functions also cast their operand into the domain (b::eql_v3.text_search), so there is no coercion-free path. The adapter therefore encrypts all filter operands with the storage path; the operators extract the term they need (eq_term/ord_term/match_term). This also resolves the spec's open question fix(ci): set up bun in github actions for release #2 — the full envelope isn't just the cleanest way, it's the only way.like/ilikecannot staylikeon the wire. The v3 domains define=,<>,<,<=,>,>=,@>,<@but no~~. Encrypted pattern filters are emitted as PostgRESTcs(@>onmatch_term). Match is tokenized + downcased solike/ilikeare equivalent;%wildcards should not be used.include_original: falseon the column's match index: with the defaulttrue, the full-envelope operand's bloom carries the whole pattern as an extra token, so substring patterns only match when equal to the stored value. Documented in the skill/reference; the live test's schema sets it explicitly with a load-bearing comment.Verification
packages/stack: 0src/type errors; 25 unit tests + 7 type tests pass; live suites parse and gate correctly (skipped without env). Pre-existing__tests__type errors andNot authenticatedlive-test failures on this branch are unchanged (no localCS_*/Supabase creds — same count at baseline).packages/cli: typecheck error count identical to baseline (all pre-existing, unbuilt workspace deps); 44 tests pass; both packagestsupbuild cleanly, v3 bundles ship indist/sql/.eql_v3in Exposed schemas +CS_*creds. The full-envelope equality behaviour matches the spec's live spike; the range/free-text assertions should be confirmed on the first live run.Out of scope (per spec)
Plaintext→encrypted migration lifecycle; encrypted
ORDER BY(OPE terms are the forward path); v3 in the Drizzle/Supabase-migration-file install paths; the spec's proposed install-time exposed-schema probe (documented as a firm follow-up — worth its own issue since it retroactively protects v2 too).Closes CIP-3300. Design: #546.