Repository navigation
Description
Background
Stack Encrypt (packages/stack-encrypt) is the Rust engine that every CipherStash language SDK runs on. It has two Rust APIs side by side on next:
- The new API. Owned conversions through
EncryptTargetandDecryptSource, and paired entry points such askeyset.encrypt(..)andkeyset.encrypt_with_context(..)(feat(stack-encrypt): add owned scalar ciphertext API #1404). - The old API.
EncryptFrom,DecryptInto,encrypt_as, the helpers intarget::legacy, and the plan chain inplan/chain.rs, with its.context(..),.extend(..), and.keyset(..)steps.
The Stack Encrypt API plan, merged in pull request #1365, keeps the new API and removes the old one.
Problem
Two APIs for the same job make the right one hard to find. The chain also causes real defects. A context part added late in a chain can replace an earlier one (#1120). Rust then builds a different context from Go, and a query finds nothing.
Proposal
The plan items "One context argument, and paired entry points" and "Remove the structural API" describe the work.
- Add
Planned::new(&plan, value), withencrypt,encrypt_with_context,decrypt, anddecrypt_with_context. It replaces the chain inplan/chain.rsand every chain state. - Pass context as one argument, built from
ScopeandBindvalues. The type fixes the order: scopes, then bindings. - Choose the keyset first. It is the receiver of the scalar entry points, and the argument of every other verb, as in
encrypt(&keyset). There is nousingmethod. - Check a stored context part passed on decrypt against the row before any key is fetched.
ExpectedContextdoes this today. It stops being a public type. - Replace the
CipherScopetrait and thedynamic::Scopeenum with one borrowed value,CipherRef<'c, K>. Both reduce to a connection and an optional keyset ID. Build it withFrom<&Cipher>andFrom<&Keyset>. Entry points takeimpl Into<CipherRef>. AKeysetreference refuses a leaf from another keyset withForeignKeyset. ACipherreference opens leaves from any keyset the client can use. - Remove
EncryptFrom,DecryptInto,encrypt_as, andtarget::legacy. - Remove the old context path type
LabelandIntoLabel. A plan's context is its declaredScopeparts, and the guest sends parts, not a path. - Rename the plan builder's output method from
build()totarget. - Move EQL, the Go guest, and every example to the new API in the same change.
encrypt_tree and decrypt_tree stay, as explicit structural operations.
Acceptance
- The examples in the plan's Target state compile.
-
tests/uicompile-fail cases cover a binding in a query, and a scope after a bind. -
plan/chain.rsis gone, andPlannedruns every test the chain ran. - No consumer uses a removed type: EQL, the guest, examples, and generic callers.
- Batching, lifecycle, and cross-language conformance checks pass. Stored data and released SDK contracts are unchanged.
- stack-encrypt: a second
.extend()replaces the first, so Rust builds a different context than Go and a query silently misses #1120 is closed.
Do not add aliases only to keep the unpublished prototype names. Account for any contract a customer already depends on.
Relationship to other work
- Depends on stack-encrypt refactor: migrate record derives and plans to owned declarations #1483, stack-encrypt refactor: complete configured definitions and typed query APIs #1486, stack-encrypt refactor: add explicit owned whole-value blob encryption #1484, and stack-encrypt refactor: add typed encoded ciphertext targets and tag access #1485, as each applies.
- The renames in stack-encrypt refactor: finalize crate ownership, naming and coordinated migration #1489 change some of the names above, such as
StackCiphertoCipherandKeysetCiphertoKeyset.
Plans: the Stack Encrypt API plan and the conformance roadmap.
Activity
feat(stack-encrypt)!: derive owned record declarations with EncryptTa…
feat(stack-encrypt)!: derive owned record declarations with EncryptTa…
feat(stack-encrypt)!: derive owned record declarations with EncryptTa…
feat(stack-encrypt)!: derive owned record declarations with EncryptTa…
feat(stack-encrypt)!: derive owned record declarations with EncryptTa…
feat(stack-encrypt)!: derive owned record declarations with EncryptTa…
coderdan commentedon Nov 9, 2026
Pull request #1597 is the first part of this issue:
Plannedis now the one lowering of a data plan (composeandopeningindynamic::planned), and the plan builder is out of the dynamic path. It closes #1120, whose fix had already landed. Still to do here, in the pull requests that follow: remove the plan chain and builder (plan/chain.rs,plan/build.rs,plan/value.rs,plan/field_ref.rs,pick!),EncryptFrom,DecryptInto,encrypt_asandtarget::legacy; replaceCipherScopeanddynamic::ScopewithCipherRef; makeExpectedContextprivate; removeLabelandIntoLabel; and move EQL, the Go guest and the examples to the new API. The field markers (#1595) are the typed query those removals need; #1596 lists the operator methods they still lack, which must land before the chain goes.