stash init installs an EQL v3 database (since #732 / PR #705) but the client-scaffold code it writes into the customer's repo still teaches v2. So init hands the customer's coding agent v2 authoring guidance — Encryption, encryptedColumn('x').equality().freeTextSearch(), encryptedType<T>('x', { equality: true }) — against a v3 database. The scaffold and the database disagree.
Split out from #732, which fixed the database side (routing) but explicitly left the code side (scaffold templates) as follow-up.
Where
packages/cli/src/commands/init/utils.ts. Two live surfaces emit v2:
generatePlaceholderClient → DRIZZLE_PLACEHOLDER / GENERIC_PLACEHOLDER — what stash init writes, and the eql install safety net (db/client-scaffold.ts).
generateClientFromSchemas → the Drizzle/generic generators — reached via stash schema build introspection.
The core difficulty
v3 has no chainable capability tuners — capabilities are fixed by the concrete domain type. So { equality: true, freeTextSearch: true } does not map mechanically to v3; each SearchOp combination has to be resolved to the v3 domain whose fixed capability set matches (packages/stack/src/eql/v3/columns.ts).
Fix
PR #734. Ports both placeholders and both live plural generators to EncryptionV3 + types.* (@cipherstash/stack/v3, @cipherstash/stack-drizzle/v3), via a shared SearchOp-set → v3-domain mapping. Deletes the dead singular generators (generateClientFromSchema + helpers — zero callers) rather than porting v2 dead code.
Surfaced one real capability difference: v2 allowed equality on a boolean; v3 has no boolean eq/ord domain, so booleans map to storage-only.
Follow-up beyond this
Numeric collapses to the Integer* family — there is no width/float signal in the introspected SearchOp model, matching how v2 collapsed everything to dataType: 'number'. If v3 float/decimal domains should be scaffolded distinctly, that needs a richer introspection signal and is out of scope here.
stash initinstalls an EQL v3 database (since #732 / PR #705) but the client-scaffold code it writes into the customer's repo still teaches v2. So init hands the customer's coding agent v2 authoring guidance —Encryption,encryptedColumn('x').equality().freeTextSearch(),encryptedType<T>('x', { equality: true })— against a v3 database. The scaffold and the database disagree.Split out from #732, which fixed the database side (routing) but explicitly left the code side (scaffold templates) as follow-up.
Where
packages/cli/src/commands/init/utils.ts. Two live surfaces emit v2:generatePlaceholderClient→DRIZZLE_PLACEHOLDER/GENERIC_PLACEHOLDER— whatstash initwrites, and theeql installsafety net (db/client-scaffold.ts).generateClientFromSchemas→ the Drizzle/generic generators — reached viastash schema buildintrospection.The core difficulty
v3 has no chainable capability tuners — capabilities are fixed by the concrete domain type. So
{ equality: true, freeTextSearch: true }does not map mechanically to v3; eachSearchOpcombination has to be resolved to the v3 domain whose fixed capability set matches (packages/stack/src/eql/v3/columns.ts).Fix
PR #734. Ports both placeholders and both live plural generators to
EncryptionV3+types.*(@cipherstash/stack/v3,@cipherstash/stack-drizzle/v3), via a sharedSearchOp-set → v3-domain mapping. Deletes the dead singular generators (generateClientFromSchema+ helpers — zero callers) rather than porting v2 dead code.Surfaced one real capability difference: v2 allowed
equalityon a boolean; v3 has no boolean eq/ord domain, so booleans map to storage-only.Follow-up beyond this
Numeric collapses to the
Integer*family — there is no width/float signal in the introspectedSearchOpmodel, matching how v2 collapsed everything todataType: 'number'. If v3 float/decimal domains should be scaffolded distinctly, that needs a richer introspection signal and is out of scope here.