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
{{ message }}
Repository navigation
stack-encrypt-derive: #[derive(EncryptFrom)] hand-writes combinators — emit the plan builder instead #1058
stack-encrypt (packages/stack-encrypt) is our Rust field-level encryption library. Its derive, #[derive(EncryptFrom)] in packages/stack-encrypt-derive (src/shape.rs, src/encrypt.rs), turns struct attributes into code that encrypts a value field by field, each field under its own context with its declared search indexes. #1057 adds a chained plan builder, the single front door described in docs/plans/2026-10-04-plan-builder.md (PR #1052).
Problem
The derive generates raw combinator code: per field it emits ciphertext().accepting().zip(equality())….under(label).project(select) and the nested-pair map that flattens the zipped tuple. That bookkeeping is repeated in every generated impl, and it is a second copy of the rules the builder will own. If the derive keeps its own spelling while bindings go through the builder, the two can drift again, which is exactly what ADR-0007 (on PR #1052) forbids.
Proposal
#[derive(EncryptFrom)] generates EncryptedUser::plan(), a builder chain built from the attributes.
EncryptFrom::encryption() returns that plan's description. The derive stops knowing combinator names; accepting, zip and nested-pair map move into the builder.
Proof of no change: the tests/ui trybuild snapshots and the shared label fixture must show identical bytes before and after. A byte change is a bug in this issue, not a migration.
From the plan doc's "Consolidation" section and sequencing step 3. Not yet verified by implementation.
Updated direction (recorded in the plan on #1052): the derive emits the plan, but only after two prerequisite PRs. One narrows the derive's grammar to the plan's. It removes field-level literal context (replaced by identity), non-plain literal contexts, nested, and a second from = field output. The other widens the plan with encrypt_into, context_field, the picker and the two starts. This issue is then PR 6 in that stack, with byte identity proven on the narrowed grammar.
Activity
coderdan commentedon Oct 5, 2026
Updated direction (recorded in the plan on #1052): the derive emits the plan, but only after two prerequisite PRs. One narrows the derive's grammar to the plan's. It removes field-level literal
context(replaced byidentity), non-plain literal contexts,nested, and a secondfrom = fieldoutput. The other widens the plan withencrypt_into,context_field, the picker and the two starts. This issue is then PR 6 in that stack, with byte identity proven on the narrowed grammar.chore(stack-encrypt): close the remaining review nits
coderdan commentedon Oct 5, 2026
Two preparatory issues now sit in front of this one, each resolved by a PR in the plan-builder stack:
#[derive(EncryptFrom)]accepts five shapes a plan cannot express, so it can never emit the plan with identical bytes #1078: the derive accepts five input shapes a plan cannot express (field-level contexts, non-plain literals,nested, two outputs from one field), so it could never emit a plan with identical bytes. Resolved by feat(stack-encrypt-derive): narrow the derive to the plan grammar (identity replaces field contexts; no nested; one output per field) #1073, which removes them.chore(stack-encrypt): close the remaining review nits
chore(stack-encrypt): close the remaining review nits
chore(stack-encrypt): close the remaining review nits