Repository navigation
feat(stack-encrypt)!: move the stack crates, the guests and the Go module onto vitaminc ADR 0001 - #1213
feat(stack-encrypt)!: move the stack crates, the guests and the Go module onto vitaminc ADR 0001#1213coderdan wants to merge 15 commits into
Conversation
|
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
⛔ Files ignored due to path filters: 6
📒 Files selected for processing: 81
Comment |
Mutation testing (cargo-mutants,
|
| caught | missed | unviable | timeout |
|---|---|---|---|
| 48 | 0 | 40 | 0 |
cargo-mutants exited with no surviving mutants recorded. The run may not have completed cleanly; check the workflow run logs.
de14775 to
60db920
Compare
60db920 to
5aa6f06
Compare
5aa6f06 to
f21a238
Compare
f21a238 to
6e46b87
Compare
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Restacked onto
All checks were run locally at this tip: workspace tests, clippy, rustdoc, the EQL workspace (not the SQLx suite), all guest builds and the Go module. |
cipherstash-agent
left a comment
There was a problem hiding this comment.
Review attestation
Source: automated /code-review medium plus cross-check of the findings, run by Claude Code on head 6e46b87.
Critical or blocking issues: none found.
Scope read:
- The cipher rewrite in
packages/stack-encrypt/src/cipher.rs. - The
[patch.crates-io]pins. dynamic/ffi_value.rs.stack-guest-abi/src/value.rs.
Not read in full: the lockfiles, the trybuild snapshots, the Go module, the generated EQL targets, and the rest of the plan/ and target/ rewrites. Treat those as not covered by this attestation.
What I checked:
- The old per-node AAD rules now come from vitaminc's
planandseal_all.- Those rules are element and entry AAD, empty-marker handling, duplicate keys, and the all-passthrough refusal.
MintedKeys::openandBoundKeys::sealboth returnUnspecified, so a minted key cannot open a leaf and a retrieved key cannot seal one.- I did not verify vitaminc's own enforcement of the empty markers and duplicate keys. This PR depends on it.
- Batching is unchanged: one
generate_keyscall per pending, with keys consumed in walk order. An exhausted iterator is refused. FfiValue::Debugredacts text and bytes.Zeroizeis kept, andPassthroughis skipped as before.
Non-blocking (inline):
- 🟡 The pin comment, the PR description, and the CHANGELOG disagree about which vitaminc commit is pinned. The description names a branch and #439 to #445, but the code pins a
revfrom #464. - 🟡
ValueMap::entrydoes an O(n²) duplicate-key scan on host-supplied frames.
Still open from the PR's own test plan: the CI run, the Go 386, macOS and Windows jobs, and cargo publish --dry-run.
Generated by Claude Code
There was a problem hiding this comment.
🟡 Changes recommended
Credential text and oversized output buffers can leave sensitive copies unwiped in freed memory.
2 open findings
What changed in this PR
Migrates Stack Encrypt, its Go guests, and EQL integration to vitaminc ADR 0001, unifying typed Rust and data-plan leaf encodings.
Changes:
- Adopts tagged leaves, sink-based decryption, and transport frames while preserving batched key requests.
- Updates runtime values, guest serialization, and dependency pins.
- Refreshes tests, fixtures, examples, and breaking-change documentation.
| File | Description |
|---|---|
| packages/stack-guest-abi/src/lib.rs | Exposes host values. |
| packages/stack-guest-abi/src/last_error.rs | Encodes errors as host-value frames. |
| packages/stack-guest-abi/src/call.rs | Updates error-decoding tests. |
| packages/stack-guest-abi/src/abi.rs | Updates error-format documentation. |
| packages/stack-guest-abi/Cargo.toml | Adds frame-model dependencies. |
| packages/stack-encrypt/tests/ui/struct_field_must_open.stderr | Refreshes trait diagnostics. |
| packages/stack-encrypt/tests/ui/struct_field_must_open.rs | Uses a local non-decryptable type. |
| packages/stack-encrypt/tests/ui/plaintext_needs_encrypt.stderr | Refreshes encryption diagnostics. |
| packages/stack-encrypt/tests/ui/pass/query_only_and_borrowed.rs | Updates a trait comment. |
| packages/stack-encrypt/tests/ui/pass/owned_without_clone.rs | Reorders imports. |
| packages/stack-encrypt/tests/ui/owned_fan_out_needs_clone.rs | Reorders imports. |
| packages/stack-encrypt/tests/ui/drop_record.stderr | Updates ciphertext diagnostic types. |
| packages/stack-encrypt/tests/ui/decrypt_field_not_decryptable.stderr | Updates decryption diagnostics. |
| packages/stack-encrypt/tests/transcode.rs | Tests frame-based passthroughs. |
| packages/stack-encrypt/tests/source_mode.rs | Adapts plaintext implementations. |
| packages/stack-encrypt/tests/roundtrip.rs | Updates round trips and marker coverage. |
| packages/stack-encrypt/tests/record_lowering.rs | Adapts shared-record serialization. |
| packages/stack-encrypt/tests/plan_grammar.rs | Updates decryption bounds. |
| packages/stack-encrypt/tests/index.rs | Updates decryption bounds. |
| packages/stack-encrypt/tests/fixtures/record_lowering.json | Regenerates tagged ciphertext leaves. |
| packages/stack-encrypt/tests/fixtures/README.md | Documents shared leaf encoding. |
| packages/stack-encrypt/tests/common/mod.rs | Adds passthrough-frame helpers. |
| packages/stack-encrypt/src/target/transcode.rs | Replaces absent markers and boxed passthroughs. |
| packages/stack-encrypt/src/target/operations.rs | Updates opening contracts. |
| packages/stack-encrypt/src/target/mod.rs | Exposes internal key binding. |
| packages/stack-encrypt/src/target/index.rs | Updates indexed decryption bounds. |
| packages/stack-encrypt/src/target/core.rs | Adopts planned sealing and bound-key opening. |
| packages/stack-encrypt/src/plan/value.rs | Updates plan opening bounds. |
| packages/stack-encrypt/src/plan/mod.rs | Documents unified leaves. |
| packages/stack-encrypt/src/plan/chain.rs | Updates field trait bounds. |
| packages/stack-encrypt/src/plan/build.rs | Updates field opening contracts. |
| packages/stack-encrypt/src/lib.rs | Revises public exports and documentation. |
| packages/stack-encrypt/src/dynamic/value.rs | Implements plaintext visits and decryption sinks. |
| packages/stack-encrypt/src/dynamic/term.rs | Uses the local runtime value. |
| packages/stack-encrypt/src/dynamic/target.rs | Uses local value types. |
| packages/stack-encrypt/src/dynamic/mod.rs | Exports the runtime value model. |
| packages/stack-encrypt/src/dynamic/kind.rs | Uses local kind definitions. |
| packages/stack-encrypt/src/dynamic/context.rs | Updates value imports and frame references. |
| packages/stack-encrypt/fuzz/fuzz_targets/check_record.rs | Adapts fuzzed ciphertext shapes. |
| packages/stack-encrypt/fuzz/Cargo.toml | Pins the vitaminc revision. |
| packages/stack-encrypt/examples/mixed_user.rs | Demonstrates derives and passthrough frames. |
| packages/stack-encrypt/CONTEXT.md | Updates architectural terminology. |
| packages/stack-encrypt/CHANGELOG.md | Records breaking API and wire changes. |
| packages/stack-encrypt/Cargo.toml | Replaces the value-codec dependency. |
| packages/stack-encrypt-derive/src/lib.rs | Reorders documentation-example imports. |
| packages/eql/tests/encryption/tests/v4_types.rs | Uses Stack Encrypt runtime types. |
| packages/eql/tests/encryption/tests/text_eq.rs | Adapts custom plaintext implementations. |
| packages/eql/tests/encryption/tests/targets.rs | Updates runtime-type imports. |
| packages/eql/tests/encryption/fixtures/v4_rows.json | Regenerates encrypted row fixtures. |
| packages/eql/tests/encryption/Cargo.toml | Enables dynamic values and sink APIs. |
| packages/eql/crates/eql-codegen/src/targets.rs | Generates updated runtime imports. |
| packages/eql/crates/eql-bindings/src/v4/targets.rs | Updates generated target imports. |
| packages/eql/crates/eql-bindings/src/encryption/targets.rs | Uses Stack Encrypt value types. |
| packages/eql/crates/eql-bindings/src/encryption.rs | Updates ciphertext opening bounds. |
| packages/eql/crates/eql-bindings/README.md | Updates the target example. |
| packages/eql/crates/eql-bindings/CHANGELOG.md | Records integration changes. |
| packages/eql/crates/eql-bindings/Cargo.toml | Revises optional encryption dependencies. |
| packages/eql/Cargo.toml | Pins compatible vitaminc crates. |
| packages/eql/Cargo.lock | Locks migrated dependencies. |
| languages/golang/stashgen/emit.go | Clarifies integer-width handling in comments. |
| languages/golang/go.sum | Adds updated codec checksums. |
| languages/golang/go.mod | Updates Go codec dependencies. |
| languages/golang/encrypt/unit_test.go | Removes retired absent-leaf coverage. |
| languages/golang/encrypt/guest/tests/native_ops.rs | Adapts native guest operation tests. |
| languages/golang/encrypt/guest/tests/leak.rs | Updates frame-based leak tests. |
| languages/golang/encrypt/guest/src/targets.rs | Uses Stack Encrypt runtime types. |
| languages/golang/encrypt/guest/src/status.rs | Updates test imports. |
| languages/golang/encrypt/guest/src/options.rs | Updates runtime-value imports. |
| languages/golang/encrypt/guest/src/ops.rs | Migrates value and ciphertext serialization. |
| languages/golang/encrypt/guest/src/lib.rs | Documents transport formats. |
| languages/golang/encrypt/guest/src/config.rs | Updates runtime-value imports. |
| languages/golang/encrypt/guest/src/abi.rs | Decodes inputs as frames. |
| languages/golang/encrypt/guest/Cargo.toml | Pins vitaminc and adds the frame codec. |
| languages/golang/encrypt/ciphertext.go | Documents sealed-null behavior. |
| languages/golang/auth/guest/tests/leak.rs | Adapts credential leak tests. |
| languages/golang/auth/guest/src/ops.rs | Serializes credential outputs through HostValue. |
| languages/golang/auth/guest/src/lib.rs | Documents host-value frames. |
| languages/golang/auth/guest/Cargo.toml | Updates credential-guest dependencies. |
| languages/golang/auth/guest/Cargo.lock | Locks migrated dependencies. |
| Cargo.toml | Pins the shared vitaminc revision. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
An independent review of this PR found no blocking issues. Fixed in cc5ca95:
Copilot's output-buffer finding is fixed in 1574655. One thing outside this repo: |
|
CI follow-up in d757b27: the CRAP gate failed on |
|
CI follow-up in cbfbddd: The two "package cleanly for crates.io" checks still fail. They can't pass while stack-encrypt builds on unreleased vitaminc: |
Generated by Claude Code |
…scribed once and every leaf records its type Pins the root workspace's vitaminc crates to the branch that implements vitaminc ADR 0001 (cipherstash/vitaminc #439 to #445) with a temporary `[patch.crates-io]`, and moves stack-encrypt and the guest ABI onto it. - A value describes itself with vitaminc's `Plaintext` and reads back through `Decrypt`. `PendingStackCipherText::plan` walks it into vitaminc's pending tree with no I/O; the target layer's `Pending` still fetches every key of an assembly in one batched call, and the leaves are then sealed and opened through the bytes-only `Cipher`: `MintedKeys` seals each leaf under the next generated key, `BoundKeys` opens a leaf under the key bound to it, and vitaminc derives every leaf's AAD and checks the markers and container rules on both sides. - Every leaf is tagged (wire version 2), so a Rust `u32` field and a data plan's `uint32` field seal the same leaf and each opens the other's. The record fixture is regenerated; its terms are unchanged. - `None` is a null leaf, a passthrough is a `PassthroughFrame`, and `StackCipherText` is `Node<SealedValue>`. - `dynamic::FfiValue` and `ValueKind` move into this crate, unchanged in shape, with `Plaintext` and a `Decrypt` sink, and cross a binding's boundary as transport frames. The deeper rework of `dynamic::Value` and `Scalar` is #1140. - stack-guest-abi's outputs and last error are a `HostValue` written as a transport frame. Closes #1118
…with no absent marker to fuzz The fuzz crate is its own workspace and compiles stack-encrypt by path, so it carries the same temporary vitaminc pin. check_record's model loses the None node, which ADR 0001 retires, and builds its passthroughs as frames.
…guests Both WASI guests build against the same temporary vitaminc pin as the root workspace, and every value crosses as a transport frame. - The encrypt guest decodes inputs with `FfiValue::from_frame`, writes outputs through vitaminc's `FrameWriter` into a buffer still sized before the first byte, and reads and writes ciphertext trees with `vitaminc_aead_frame`'s codec, a passthrough as its frame. - The credential guest's outputs are `HostValue`s. - The Go module moves vcffi and vcvalue to the same vitaminc commit, whose container tags and leaf kinds match the frame. The absent leaf kind is gone from the binding's leaf set. vcffi sends an 8- or 16-bit integer at its own width, which the engine reads as the 32-bit integer of its sign, so a generated field seals as it did.
…and drop the unpinnable capacity hint cargo-mutants found the unused half of MintedKeys and BoundKeys, and the UINT8 arm of FfiValue's sink, untested: no Rust source walks a u8 as an int, so the test reads a host's frame of one. The sink's capacity hint changed nothing a test can see, so it is gone.
… 0001 so the EQL jobs and the EQL guest stay green eql-bindings' `stack-encrypt` feature and tests/encryption compile stack-encrypt by path, so they move with it. - The EQL workspace carries the same temporary vitaminc pin as the root, the fuzz crate and both guests: every workspace that compiles stack-encrypt now resolves one vitaminc. - `encryption::targets` and the generated `v3::targets` take `FfiValue` and `ValueKind` from `stack_encrypt::dynamic` (the feature turns on stack-encrypt's `dynamic`); the generator emits the new import. `vitaminc-aead-value` is gone from both manifests. - The `Ciphertext` target takes `Plaintext` in place of `Encrypt` and `Decrypt + Send + 'static` in place of `Decrypt<'static>`. - The encrypt guest's lockfile drops `vitaminc-aead-value`, which only its `eql` build still pulled in. No other EQL code names a vitaminc API. Rebased onto next, which carries the EQL v4 types: - `encryption::plaintext`'s `Int32`, `Int64`, `Bool`, `Float32` and `Float64` sealed vitaminc 0.5's tagged leaves through `Encrypt` and `DecipherVisitor`, which ADR 0001 removes. They now describe themselves as the primitive they wrap (`Plaintext`), so they seal the leaf a data plan's field of that type seals, and read back through `FromScalar` only a scalar of their own kind and width, as before: vitaminc's primitive targets would convert any integer that fits and widen an f32. `vitaminc-aead-value` and `vitaminc-protected` leave eql-bindings; `vitaminc-aead` stays for the scalar kinds. - `tests/encryption/tests/v4_types.rs` takes `FfiValue` and `ValueKind` from `stack_encrypt::dynamic`. - `v4_rows.json` is regenerated by the Go test over the deterministic EQL guest: ADR 0001 leaves are wire version 2, so the committed ciphertexts no longer opened.
…s Encrypt rename The vitaminc crates came from `feat/aead-remove-legacy-traits` by branch name. That stack has merged into vitaminc main and the branch is gone, so a lockfile refresh could no longer resolve it. Every workspace that compiles stack-encrypt (the root, both Go guests, packages/eql and the fuzz crate) now pins one commit of cipherstash/vitaminc#464, vitaminc main plus the order-term comparison, which is the commit ore.rs's vitaminc implementation (cipherstash/ore.rs#115) builds against. Pinned by rev, not branch, so a merge cannot pull it out from under the build. Between the two commits vitaminc renamed the source trait `Plaintext` to `Encrypt`, to pair with `Decrypt`. stack-encrypt re-exports it under the new name, and every bound, impl, import and doc link in the stack crates, the guests, stack-guest-abi and eql-bindings follows. `TargetError:: Plaintext`, `Supports`'s `In::Plaintext` and eql-bindings' own `targets::Plaintext` are different things and keep their names. The trybuild expectations follow the rename. The other breaking changes since (Timestamp as plaintext, Copy normalizers, vitaminc-ore's column handle and compare) reach no code at this layer. v4_rows.json is unchanged: the leaves are the same format.
…dule to the same vitaminc Review follow-ups for the move onto vitaminc ADR 0001. `HostValue::Text` held a plain `String`. Before this PR the auth guest built its output from `FfiValue::String`, whose bytes live in a `Protected` buffer, so the access token it returns was wiped when dropped; after it, every `token` call left a bearer token in freed guest memory. `Text` now holds `Zeroizing<String>` on every path that builds one (the constructor, JSON, a decoded frame), and a test pins it. The Go module still named vcffi and vcvalue at 2e895cb9caed, the head of the deleted `feat/aead-remove-legacy-traits` branch, which builds only while the Go proxy has it cached. It now names the commit the five Rust workspaces pin, 2413bd0e4bf7 (cipherstash/vitaminc#464); the Go code under bindings/go is the same at both. Docs that still called vitaminc's source trait `Plaintext` (CONTEXT.md, the mixed_user example, two test headers and two manifests) say `Encrypt`. The CHANGELOG no longer says a reader of any other type is refused: an integer reader takes any integer width the value fits, and an `f64` reader an `f32`, which change no value.
…registering one copies nothing unwiped Copilot review. The registry wipes the buffer it is handed, but `register` turns a `Vec` into a boxed slice, and a `Vec` with more capacity than length is reallocated on the way, leaving the old allocation unwiped. The encrypt guest reserved the exact encoded length before writing a frame, but vitaminc's frame writer reserves an upper bound per leaf (14 bytes for a six-byte string's 11), so a decrypt's plaintext output could still grow past it and leave a copy behind; the auth guest's `HostValue::encode` returned the writer's buffer as it was. Both now write into a buffer that is wiped when dropped and copy the frame into one whose capacity is its length. A test in each pins it over every value shape the guest writes.
The CRAP gate failed on `FfiValue::fmt` (complexity 15, 35% covered), which this PR moves into stack-encrypt: the one test printed three of its fourteen variants. A test now prints every variant and checks that text and bytes are redacted inside an array, an object and a passthrough as well as at the top.
`cargo udeps` failed on this PR: the `dynamic` feature still enabled `vitaminc-aead-frame`, but nothing in stack-encrypt uses it, since an `FfiValue` frames itself through vitaminc-aead's own `to_frame` and `Frame`. The guests and stack-guest-abi, which do carry ciphertext trees in the frame codec, keep their own dependency. `dynamic` is now a plain feature.
The two "package cleanly for crates.io" steps ran `cargo publish --dry-run` over the stack-* crates and eql-bindings. `cargo package` drops `[patch.crates-io]`, so once this branch pinned vitaminc to a git commit the verify compiled against vitaminc 0.5.1 from crates.io, which lacks the API the tree now uses, and failed with unresolved imports. That build tests a dependency the tree does not use, so it cannot pass until vitaminc is released, and it kept tests-crates and test-eql (and so ci-required) red. Both steps now read the crates' normal and build graph with `cargo tree`. When any crate in it resolves from git, they package with `--no-verify`, which still checks the metadata and that every path dependency names a version, and emit a warning naming the pins. With no git crate in the graph, as on main, they run the full verify as before. The release is not weakened: `cargo publish` verifies the same way and refuses these crates until the pins are registry versions.
…1 port - A dynamic passthrough value is framed when the source is read, not after the key round trip. One the frame cannot carry was framed only in the output adapter, inside `try_map`, so it cost a ZeroKMS key-generation call and then failed the whole record as `Error::Aead` with no field named. `check_field` now trial-frames a passthrough value and refuses it as `Error::Source` naming the field: a repeated key as `Reason::RepeatedKey`, and a value too deep or too long for a frame as the new `Reason::Unframeable`. A test pins that no key is generated. - Reading an object from a frame checks for a repeated key with a lookup. `ValueMap::entry` scanned every entry read so far, so an object of `n` keys in host-supplied bytes cost `n²` comparisons. The keys seen are now kept in a `BTreeSet`, ordered rather than hashed so no key can be chosen to collide. - In `stack-guest-abi`, the doc comment for `text_is_wiped_on_drop` sat on the test above it; it is back on its own test.
cbfbddd to
064c104
Compare
|
Fixed CI and the review points that were still open. CI (1a7fff5). Three checks were red: Both steps now read the crates' normal and build graph with
The release is not weakened. tobyhede's review (064c104):
cipherstash-agent's Copilot's two findings were already fixed in 1bb42ab (was cc5ca95) and beb3074 (was 1574655), and those threads are resolved. Run locally at this tip: workspace nextest (1,128 passed), clippy |
…io release Packaging without the registry build still resolves every dependency against crates.io. vitaminc-normalize, which stack-encrypt takes from #1214 on, has no release there at all, so `cargo package --no-verify` failed on stack-encrypt with "no matching package named `vitaminc-normalize`" and kept tests-crates red. Both packaging steps now call scripts/package-git-pinned-crates.sh while a git pin is in the graph. It packages the crates with `--no-verify`, and when one needs a pinned crate that crates.io does not have, it leaves that crate out with a warning naming the dependency and packages the rest. Any other packaging failure still fails the step, and a graph with no git pin still gets the full verify.
…nd the CI packaging workaround vitaminc 0.6.0 is published, the first release of ADR 0001, so every workspace that compiles stack-encrypt (the root, the encrypt and credential guests, the stack-encrypt fuzz crate and packages/eql) now requires the 0.6.0 crates and carries no `[patch.crates-io]` block. Each lockfile moves from the git rev to the registry. The Go module moves vcffi and vcvalue to the release commit (ca2298d3e7e8), which the Rust crates were cut from. cts-common 0.43 (from crates.io) still requires vitaminc 0.5, which the patch had unified with ours, so the graph now holds both; nothing passes a vitaminc type between them. One trybuild snapshot gains rustc's note that two `vitaminc_aead` versions are present. With no git crate in the graph, the two "package cleanly for crates.io" steps are back to the full `cargo publish --dry-run` they ran before the pin, so the workaround for a pinned graph goes: scripts/package-git-pinned-crates.sh, and the branches in tests-crates.yml and test-eql.yml that called it.
… 0.3.0 stack-encrypt's API is no longer 0.2.0's (vitaminc ADR 0001 and what this stack builds on it), but its manifest still said 0.2.0, which crates.io has. eql-bindings names stack-encrypt by path and version, so the packaged eql-bindings resolved the published 0.2.0 and failed to compile against it: test-eql.yml's "Verify eql-bindings packages cleanly for crates.io" step, whose registry build the git pin had been skipping. The version group moves to 0.3.0 by hand, as a stack-* version does, and eql-bindings and stack-encrypt name it. Until 0.3.0 is on crates.io the eql-bindings step warns that it cannot build against the registry, which is its documented answer for a stack-encrypt not yet published.

Summary
Stacked on #1209. This PR moves the stack repo's root Cargo workspace, the fuzz crate, both Go guests, the Go module and eql-bindings'
stack-encryptfeature onto vitaminc ADR 0001: a value describes itself once (Encrypt, which vitaminc calledPlaintextuntil cipherstash/vitaminc#456), reads back through aDecryptsink, crosses an FFI boundary as a transport frame, and is sealed one leaf at a time by a bytes-onlyCipher. It is PR 1 of 3; the next is #1140.Cargo.toml, the encrypt guest, the credential guest,packages/stack-encrypt/fuzzandpackages/eql) requires the vitaminc 0.6.0 crates, the first release of ADR 0001, and carries no[patch.crates-io]block. Until 0.6.0 was published this PR pinned them to a git commit of feat(ore)!: compare order terms through the scheme and column, not through Ord vitaminc#464; that pin is gone. The Go module takesvcffiandvcvalueat the release commit (ca2298d3e7e8).vitaminc-aead-valueis gone, and stack-encrypt no longer depends onvitaminc-aead-frame; the guests andstack-guest-abistill use it for the ciphertext codec.cts-common0.43 (crates.io) still requires vitaminc 0.5, so the graph holds both lines; nothing passes a vitaminc type between them.Cipher, batched as before.PendingStackCipherText::planwalks a value into vitaminc's pending tree with no I/O. The target layer'sPendingstill fetches every data key of an assembly in onegenerate_keyscall (RFC 0002: the fields of a record and the rows of a column merge into one request), and thenMintedKeysseals each leaf under the next generated key. Opening binds each retrieved key to its leaf andBoundKeysopens it; vitaminc derives every leaf's AAD and checks the empty markers and container rules on both sides, so there is one definition of them. vitaminc'sAsyncCipheris not used: its batch is one document, and a ZeroKMS request needs the descriptor of the root context, which a leaf's derived AAD does not carry.u32field and a data plan'suint32field seal the same leaf and each opens the other's, and a reader of another kind is refused instead of reading changed plaintext (an integer reader still takes any integer width the value fits). That closes stack-encrypt: a RustStringfield reads a data-plan leaf as "\nalice" — the Rust chain and a data plan seal different leaf bytes #1118. The record fixture is regenerated; its terms are unchanged, and the tests that pinned the old mismatch now pin the shared leaf.Noneis a null leaf; a passthrough is a frame.StackCipherTextisNode<SealedValue>.CipherText::None,transcode::Visitor::absent,StackDecipherandBoxedPassthroughare gone, andVisitor::passthroughtakes aPassthroughFrame.dynamic::FfiValueandValueKindmove into stack-encrypt, unchanged in shape, withEncrypt, aDecryptsink andto_frame/from_frame. This is the minimum that keeps today's behaviour; the rework ofdynamic::ValueandScalaris stack-encrypt: deletedynamic::ValueandScalar, derive terms through vitaminc, add block ORE, dropBoolorder terms #1140. vcffi now sends an 8- or 16-bit integer at its own width, and the sink reads it as the 32-bit integer of its sign, so a generatedint8field seals as it did.HostValues (outputs and the last error), written as transport frames. Text is held asZeroizing<String>, so the auth guest's access token is wiped as it was underFfiValue.stack-encryptfeature compiles stack-encrypt by path, so it moves with it:encryption::targetsand the generatedv3::targetstakeFfiValue/ValueKindfromstack_encrypt::dynamic(the generator emits the new import), and theCiphertexttarget takesEncryptand opens intoDecrypt + Send + 'static. No other EQL code names a vitaminc API. Its CHANGELOG has an entry.FrameWriterinto a buffer still sized before the first byte, and carries ciphertext trees invitaminc_aead_frame's codec. The credential guest's outputs areHostValues. The Go module movesvcffiandvcvalueto the same vitaminc commit (2413bd0e4bf7) and drops the absent leaf kind. No wasm is checked in; CI builds the guests as before.What this PR does not do
dynamic::ValueandScalar, derive terms through vitaminc, add block ORE, dropBoolorder terms #1140: deletingdynamic::Value/Scalar, terms through vitaminc-prf/vitaminc-normalize with NFC text equality, and no order terms for bool.Tests
FfiValue's frame round trip, its integer widths, the narrower integers and the refusals;HostValueround trip and refusals in stack-guest-abi. The trybuild snapshots are re-recorded under Rust 1.94.1;struct_field_must_opennow uses a local borrowed type, so its diagnostic points at no vitaminc source path.Element, the boxed passthrough and the absent marker are removed with them.Test plan
mise x --env test -- cargo nextest run --workspace --all-features: 1120 passed.cargo mutants --in-diffover stack-auth and stack-encrypt: the nine survivors of the first run are pinned or removed in a later commit, and a rerun over those functions catches every viable mutant.mise run test:doc,mise run doc,mise run wasm:wasi-check,mise run wasm:no-http-test.cargo clippy --workspace --all-targetswith and without--all-features,-D warnings;cargo fmt --checkin the root, the fuzz crate and both guests.wasm:guest:testandwasm:auth-guest:teststeps without theeqlfeature (clippy native and wasm32, rustdoc, nextest).wasm:guest:build,wasm:guest:build:deterministic,wasm:auth-guest:build.-runs=0, nightly).mise run wasm:guest:testin full, theeqlsteps included.wasm:guest:build:eqlandwasm:guest:build:eql:deterministic.scripts/go-binding-test.sh(gofmt, vet,go test ./...with every guest built,TestTextEqIsRefusedByTheBuildWithoutEQLTypesincluded) andgolangci-lint.test:crates,test:encryption,check:encryption:wasi,types:check, andtest:encryption:postgresagainst a local PostgreSQL 17.5.node scripts/lint-no-eql-registry-pins.mjsand theeql-suite-ci,eql-workflow-filtersandcrates-ciscript tests.386Go run (Linux only), macOS and Windows Go jobs: CI.Closes #1118
Part of #1140
On top of
nextRebased onto
next, which carries the EQL v4 types. Theeql-bindingsscalar plaintexts (Int32,Int64,Bool,Float32,Float64) are ported from vitaminc 0.5's visitor traits toEncryptandFromScalar, and still refuse a value of another width.v4_rows.jsonis regenerated, because ADR 0001 leaves are a new wire version.Summary by CodeRabbit
New Features
Bug Fixes