You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up from the portable-encryptedSupabase design (#708).
Rewritten and downgraded 2026-08-26 after an audit. The original described a design that was never built: AdapterEncryptionClient, fromNativeEncryptionClient, fromWasmEncryptionClient, assertClientShape and supportsLockContext have zero matches across all 31,837 objects in the git database. The portable-runtime series shipped a different shape (#912, 1a3e9808, 2026-08-19). Original text preserved in the issue history.
What actually exists
Six structural views, not three. Every one erases operands so a test double needs no cast:
The Supabase seam is adaptWasmEncryption (packages/stack-supabase/src/wasm-client-adapter.ts:79), which returns an EncryptionFactory — not a client view at all. packages/stack/src/adapter-kit.ts is 68 lines and still declares no client interface.
Why this is now LOW, and harder than it looked
"The three method sets are disjoint" is false. Drizzle's {encryptQuery} is a strict subset of Prisma's. What survives is weaker: no view is a superset of the other two.
A superset already exists — but it will not serve as the donor.UnderlyingNativeClient contains every member of every other view. Two problems:
It is a superset of member names, not an assignability supertype. Return types genuinely diverge. Prisma returns PromiseLike<StackResult<…>>; UnderlyingNativeClient returns chainable operation classes; DynamoDB's members return unknownon purpose — dynamodb/types.ts:66-78 records that declaring a chainable shape there "failed EVERY EQL v3 write" on the wasm entry (fix(stack,bench): wasm-inline DynamoDB v2 reads, and a bench seed that never encrypted (#772 review findings 10, 12) #788). These are different projections, not narrower copies.
It is module-private on purpose, and packages/stack/__tests__/typed-client-v3.test.ts:8 asserts that. Promoting it is a public-surface decision, not a refactor.
So consolidation is blocked on reconciling return types across two engines — the actual hard part, and unaddressed by the original framing. There is still no second consumer of the port: only stack-supabase has two entries.
The fourth "duplication" is not one.hasBuildColumnKeyMap (packages/stack/src/types.ts:251-259) probes one member on a table. isV3ColumnLike (packages/stack-supabase/src/column-map.ts:57-70) probes four on a column, and its own comment at :46-48 explains why four. Consolidating them would be wrong.
The two folded-in items
Item 1, the entry/arity check — obsolete, do not do it.options.encryptionClient was removed; passing the old form is a hard error at packages/stack-supabase/src/create.ts:252-256, naming it "the removed EQL v2 API". The engine binds at the entry point, so a native client cannot reach the WASM entry by construction. The supporting claim was also backwards: decryptModel.lengthdoes discriminate — 1 native (encryption/index.ts:466-468), 2 wasm (wasm-inline.ts:1250-1253) — merely inverted relative to bulkEncrypt (2 native, 1 wasm). Any arity probe must be per-method. The third data point is gone: packages/stack/src/encryption/v3.ts is now a 6-line re-export shim.
Item 2, the lock-context hardcode — mostly fixed; split the residual out. The named harm is closed: withUnsupportedChainers (wasm-client-adapter.ts:58-67) throws a named error citing #797 rather than a raw TypeError. What remains is a taxonomy gap — it throws a plain Error, not an EncryptionFailedError, so error.encryptionError stays undefined at packages/stack-supabase/src/query-builder.ts:599-616 while error.message carries the full explanation. A caller branching on that field, which the comment at :608-609 documents as the pattern, sees nothing. Roughly a five-line fix and it needs none of this refactor.
Scope if pursued
adapter-kit.ts (an audit decision per its own header, line 18), wasm-inline.ts, client-v3.ts, stack-drizzle/src/operators.ts:74 and its test-d, stack-prisma/src/v3/sdk-adapter-v3.ts:74, stack-supabase/wasm-client-adapter.ts and query-encrypt.ts:55.
Potentially breaking:CipherstashV3Client is a published type on two @cipherstash/stack-prisma entries (src/exports/stack.ts:33, src/exports/v3.ts:77). Moving or renaming it is a major there unless a deprecated alias stays.
Changesets:@cipherstash/stack minor, @cipherstash/stack-prisma major-or-patch, stack-drizzle and stack-supabase patch, plus stash patch — skills/stash-encryption/SKILL.md:197 documents the adapter-kit subpath.
Corrections to the original text
packages/prisma-next/ → packages/stack-prisma/. The former has zero tracked files today (it was a real package before the rename; the directory is now build residue), as are packages/drizzle, protect, protect-dynamodb, schema, stack-forge.
Drop the AdapterEncryptionClient row and the claim that the Adapter… name was chosen so this becomes a file move. No such name was chosen; this is now a rename.
Line fixes: adapter-kit.ts 60→68 lines, header quote at 18; operators.ts 67-73→74-80; types.ts 276→251-259; encryption/index.ts 651→601; wasm-inline.ts 944→1040; query-builder.ts 596-598→599-616. Delete v3.ts:367 — that surface no longer exists.
Three in-tree comments cite the moved hasBuildColumnKeyMap wrongly, and disagree with each other: adapter-kit.ts:63 says types.ts:268-275, column-map.ts:40 says 276-283, column-map.ts:139 says 276. Actual: 243-259.
Follow-up from the portable-
encryptedSupabasedesign (#708).What actually exists
Six structural views, not three. Every one erases operands so a test double needs no cast:
UnderlyingNativeClientpackages/stack/src/encryption/client-v3.ts:59-80encrypt,encryptQuery,encryptModel,bulkEncryptModels,decrypt,decryptModel,bulkDecryptModels,bulkEncrypt,bulkDecrypt,getEncryptConfigCipherstashV3Clientpackages/stack-prisma/src/v3/sdk-adapter-v3.ts:74-89bulkEncrypt,decrypt,bulkDecrypt,encryptQueryDynamicEncryptionClientpackages/stack-supabase/src/query-encrypt.ts:55-68encrypt,encryptModel,bulkEncryptModelsDynamoDBEncryptionClient/CallableEncryptionClientpackages/stack/src/dynamodb/types.ts:50-55,79-96encryptModel,bulkEncryptModels,decryptModel,bulkDecryptModelsOperandEncryptionClientpackages/stack-drizzle/src/operators.ts:74-80encryptQueryonlyEncryptionClientLikepackages/migrate/src/backfill.ts:69-84bulkEncryptModelsThe Supabase seam is
adaptWasmEncryption(packages/stack-supabase/src/wasm-client-adapter.ts:79), which returns anEncryptionFactory— not a client view at all.packages/stack/src/adapter-kit.tsis 68 lines and still declares no client interface.Why this is now LOW, and harder than it looked
"The three method sets are disjoint" is false. Drizzle's
{encryptQuery}is a strict subset of Prisma's. What survives is weaker: no view is a superset of the other two.A superset already exists — but it will not serve as the donor.
UnderlyingNativeClientcontains every member of every other view. Two problems:PromiseLike<StackResult<…>>;UnderlyingNativeClientreturns chainable operation classes; DynamoDB's members returnunknownon purpose —dynamodb/types.ts:66-78records that declaring a chainable shape there "failed EVERY EQL v3 write" on the wasm entry (fix(stack,bench): wasm-inline DynamoDB v2 reads, and a bench seed that never encrypted (#772 review findings 10, 12) #788). These are different projections, not narrower copies.packages/stack/__tests__/typed-client-v3.test.ts:8asserts that. Promoting it is a public-surface decision, not a refactor.So consolidation is blocked on reconciling return types across two engines — the actual hard part, and unaddressed by the original framing. There is still no second consumer of the port: only
stack-supabasehas two entries.The fourth "duplication" is not one.
hasBuildColumnKeyMap(packages/stack/src/types.ts:251-259) probes one member on a table.isV3ColumnLike(packages/stack-supabase/src/column-map.ts:57-70) probes four on a column, and its own comment at:46-48explains why four. Consolidating them would be wrong.The two folded-in items
Item 1, the entry/arity check — obsolete, do not do it.
options.encryptionClientwas removed; passing the old form is a hard error atpackages/stack-supabase/src/create.ts:252-256, naming it "the removed EQL v2 API". The engine binds at the entry point, so a native client cannot reach the WASM entry by construction. The supporting claim was also backwards:decryptModel.lengthdoes discriminate — 1 native (encryption/index.ts:466-468), 2 wasm (wasm-inline.ts:1250-1253) — merely inverted relative tobulkEncrypt(2 native, 1 wasm). Any arity probe must be per-method. The third data point is gone:packages/stack/src/encryption/v3.tsis now a 6-line re-export shim.Item 2, the lock-context hardcode — mostly fixed; split the residual out. The named harm is closed:
withUnsupportedChainers(wasm-client-adapter.ts:58-67) throws a named error citing #797 rather than a rawTypeError. What remains is a taxonomy gap — it throws a plainError, not anEncryptionFailedError, soerror.encryptionErrorstaysundefinedatpackages/stack-supabase/src/query-builder.ts:599-616whileerror.messagecarries the full explanation. A caller branching on that field, which the comment at:608-609documents as the pattern, sees nothing. Roughly a five-line fix and it needs none of this refactor.Scope if pursued
adapter-kit.ts(an audit decision per its own header, line 18),wasm-inline.ts,client-v3.ts,stack-drizzle/src/operators.ts:74and itstest-d,stack-prisma/src/v3/sdk-adapter-v3.ts:74,stack-supabase/wasm-client-adapter.tsandquery-encrypt.ts:55.Potentially breaking:
CipherstashV3Clientis a published type on two@cipherstash/stack-prismaentries (src/exports/stack.ts:33,src/exports/v3.ts:77). Moving or renaming it is a major there unless a deprecated alias stays.Changesets:
@cipherstash/stackminor,@cipherstash/stack-prismamajor-or-patch,stack-drizzleandstack-supabasepatch, plusstashpatch —skills/stash-encryption/SKILL.md:197documents theadapter-kitsubpath.Corrections to the original text
packages/prisma-next/→packages/stack-prisma/. The former has zero tracked files today (it was a real package before the rename; the directory is now build residue), as arepackages/drizzle,protect,protect-dynamodb,schema,stack-forge.AdapterEncryptionClientrow and the claim that theAdapter…name was chosen so this becomes a file move. No such name was chosen; this is now a rename.adapter-kit.ts60→68 lines, header quote at 18;operators.ts67-73→74-80;types.ts276→251-259;encryption/index.ts651→601;wasm-inline.ts944→1040;query-builder.ts596-598→599-616. Deletev3.ts:367— that surface no longer exists.hasBuildColumnKeyMapwrongly, and disagree with each other:adapter-kit.ts:63saystypes.ts:268-275,column-map.ts:40says276-283,column-map.ts:139says276. Actual:243-259.