Skip to content

Add a stash-edge skill: WASM entry, credentials, and the raw-SQL predicate surface #754

Description

@coderdan

Follow-up to #753 (indexing). Same evals: stash@1.0.0-rc.4 / EQL v3 3.0.2, two surfaces, two coding agents each.

Summary

There is a stash-drizzle, a stash-supabase, and a stash-dynamodb. There is nothing for the runtimes and drivers that don't use an ORM — Deno / Supabase Edge Functions on the WASM entry, and hand-written SQL over pg / postgres-js.

An integration on that path currently has to be assembled from two [!IMPORTANT] asides in stash-encryption and from node_modules type declarations. That integration did come out correct and fully verified, but essentially nothing that made it correct came from a skill.

What had to be reverse-engineered

Every one of these was recorded as having no authoritative source — recovered from package internals, the Postgres catalog, or experiment:

Decision Where the answer actually came from
Raw predicate forms — ${term}::jsonb::eql_v3.query_text_search, eql_v3.matches(col, term), infix >=/< on query_numeric_ord JSDoc inside dist/wasm-inline.d.ts (~lines 1517, 1813–1844), cross-checked against pg_proc / pg_operator
Bind the Encrypted payload as an object (sql.json(x)), never pre-stringified Experiment — five binding forms tried after JSON.stringify(...)::jsonb failed the domain CHECK with a message naming neither JSON nor encoding
EQL index terms derive from the ZeroKMS client key, not the workspace/keyset Experiment (see the separate footgun below)
deno.json npm: specifiers, mandatory CS_* config, --env-file plumbing, absence of the model helpers on the WASM entry Assembled from asides + type declarations

Grepping the skills installed by stash init for postgres-js|::jsonb::eql|sql.json|query_text_search|query_numeric_ord|eql_v3.matches( returns exactly one hit across all of them — a code comment describing runBackfill's db parameter. The binding surface is genuinely absent.

Two things worth folding in

1. The credential-identity rule (silent data footgun). Index terms are derived from the client key. Rows written by one client and queried by another — e.g. stash encrypt backfill running on the local device profile, then queried by an Edge function using minted CS_* keys — decrypt correctly but never match a query. There is no error; searches just quietly return nothing. Suggested wording for stash-cli's env section:

every writer of a searchable column — including stash encrypt backfill and your seed scripts — must use these same credentials, or their rows decrypt but never match a query.

2. The authoring surfaces don't interchange. stash-encryption describes the WASM entry as "its own Encryption factory plus the v3 authoring surface", which reads as though a schema authored with @cipherstash/stack/eql/v3 can be shared between the Node tooling and the Deno function. It does not typecheck against @cipherstash/stack/wasm-inline — and since it works at runtime, the natural fix is a cast that silently papers over it. One shared schema module is the obvious layout for exactly the projects the WASM entry exists to serve, so this is worth stating explicitly either way.

Scope / naming question

stash-edge fits the runtime half — WASM entry, stash env credential minting, Deno specifics. But the raw-SQL predicate cookbook is not edge-specific, and scoping it under an edge-named skill would leave a supported surface uncovered:

  • hono-pg — no ORM, driver pg, plain Node, officially supported. No WASM, no Deno, but exactly the same binding problem.
  • supabase-edge — no ORM, Deno + WASM.

So the raw-SQL binding surface serves a supported Node path, while the edge/WASM material serves the experimental one. My suggestion:

  • stash-edge — WASM entry setup, CS_* credential minting via stash env, the credential-identity rule, deno.json / --env-file plumbing, and which helpers are absent on that entry.
  • a second skill (stash-sql? or a "Raw SQL" section in stash-encryption) — the predicate cookbook for pg / postgres-js, lifted from the wasm-inline.d.ts JSDoc plus the object-binding rule.

Happy to file the raw-SQL one separately if you'd rather keep these apart — flagging it here so the split is a decision rather than an accident.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions