Updated 2026-10-05 (PDT). The Go SDK design is PR cipherstash/stack#1070: docs/plans/2026-10-04-plan-builder.md, "The Go SDK", and the principles in docs/sdk-design-principles.md (ADR-0008). Dan's "EQL Plans in Go" proposal is taken. Item 2 keeps this issue's shape: eql-codegen emits a dispatch table beside inventory.rs, the plan grammar gains a target field form, and the guest build that holds the EQL types runs the Rust-derived plan for the named type. The Go package encrypt/eql holds generated structs that store the EQL value the guest returns, and it embeds that guest build. Item 3: the envelope is decided. A Stack Encrypt EQL value has the EQL v3 envelope (v: 3, eql_v3 domains) with a ciphertext prefixed stack-encrypt:1:; "EQL v4" names that form. Item 5: the record fixture guards the bytes (each side opens the other's records, same term bytes); there is no Go encoder to fixture. The engine produces TextEq only today; stashgen refuses the other types until the engine can produce them. The size of the guest build with the EQL types is measured before the SDK ships two builds or one.
Background
EQL (Encrypt Query Language) is our PostgreSQL library: SQL we install into a customer database that stores and queries encrypted payloads. Each EQL domain (TextEq, IntegerOrd, Json, …) fixes the JSON shape a column stores: a version, the column identifier i ({"t": table, "c": column}), the ciphertext, and the search terms that domain needs. packages/eql/crates/eql-bindings/ holds the Rust types for those domains; PR cipherstash/stack#971 added text encryption and queries there through stack-encrypt, our Rust field-level encryption library.
The plan builder (docs/plans/2026-10-04-plan-builder.md, PR cipherstash/stack#1052) lets a field's target be any EncryptFrom type. So an EQL domain can be a plan field target with no builder work.
Problem
- Rust: there is no way to say "this plan field is stored as EQL
TextEq" inside a plan.
- Go: the binding's record output is its own map (
{"c","eq","match","ore","ope"}), which is not an EQL payload, so Go cannot write rows EQL can query. There are no Go domain types.
- Name lookup: a binding sends a domain name as data. Nothing maps that name to the Rust type, and inferring the domain from a payload's keys would be unsafe (two domains can share keys).
Proposal
From the plan doc's "EQL v3 domains as field targets" section. Not yet verified by implementation.
- Rust:
encrypt_into::<Domain>(name) on the fields chain, for any EncryptFrom type, using <Domain as EncryptFrom<S>>::encryption() as that field's description.
- A name-to-type table in
eql-bindings: an explicit lookup from domain name to type, never inferred from payload keys. It lives in eql-bindings so every binding reuses it.
- Go: a package generated by
eql-codegen into languages/golang, inside the one Go module and the one guest, with every domain, and constructors only for domains that have Rust derives. Module size is measured and reported in the PR.
- A domain wraps an index's output for EQL's wire format. The index stays in stack-encrypt. For example the JSON domain frames the
Json index's document (#1060) into EQL's JSON envelope the way TextEq frames an equality term.
- Remove the Go record output map (
c/eq/match/ore/ope) once domains are field targets.
Naming note: the plan doc calls the generated Go package eqlv3 and the section "EQL v3 domains". Payloads emitted by stack-encrypt are intended to be EQL v4. The generated package name and the envelope version must follow the EQL v4 envelope work, and the plan doc's eqlv3 naming is to be corrected.
Relationship to other work
- Blocked by #1057 (the plan builder), #1046 (the Go chain), and the EQL v4 envelope work (the version and shape of the payload stack-encrypt emits).
- Blocks #1065 (TypeScript on stack-encrypt).
- Supersedes the "EQL v3 outputs as
EncryptAs targets" section previously in #1046.
- Design:
docs/plans/2026-10-04-plan-builder.md on PR cipherstash/stack#1052.
Background
EQL (Encrypt Query Language) is our PostgreSQL library: SQL we install into a customer database that stores and queries encrypted payloads. Each EQL domain (
TextEq,IntegerOrd,Json, …) fixes the JSON shape a column stores: a version, the column identifieri({"t": table, "c": column}), the ciphertext, and the search terms that domain needs.packages/eql/crates/eql-bindings/holds the Rust types for those domains; PR cipherstash/stack#971 added text encryption and queries there through stack-encrypt, our Rust field-level encryption library.The plan builder (
docs/plans/2026-10-04-plan-builder.md, PR cipherstash/stack#1052) lets a field's target be anyEncryptFromtype. So an EQL domain can be a plan field target with no builder work.Problem
TextEq" inside a plan.{"c","eq","match","ore","ope"}), which is not an EQL payload, so Go cannot write rows EQL can query. There are no Go domain types.Proposal
From the plan doc's "EQL v3 domains as field targets" section. Not yet verified by implementation.
encrypt_into::<Domain>(name)on the fields chain, for anyEncryptFromtype, using<Domain as EncryptFrom<S>>::encryption()as that field's description.eql-bindings: an explicit lookup from domain name to type, never inferred from payload keys. It lives ineql-bindingsso every binding reuses it.eql-codegenintolanguages/golang, inside the one Go module and the one guest, with every domain, and constructors only for domains that have Rust derives. Module size is measured and reported in the PR.Jsonindex's document (#1060) into EQL's JSON envelope the wayTextEqframes an equality term.c/eq/match/ore/ope) once domains are field targets.Naming note: the plan doc calls the generated Go package
eqlv3and the section "EQL v3 domains". Payloads emitted by stack-encrypt are intended to be EQL v4. The generated package name and the envelope version must follow the EQL v4 envelope work, and the plan doc'seqlv3naming is to be corrected.Relationship to other work
EncryptAstargets" section previously in #1046.docs/plans/2026-10-04-plan-builder.mdon PR cipherstash/stack#1052.