build: import the stack-* crates, node bindings and Go module - #1001
Conversation
…ld is never handed the caller's context
Review of the context-parameter commit found the derive's where clause
and the docs disagreeing in both directions. A row whose fields all carry
literals left `__Ctx` unbounded, so `encrypt_into_with_context(&cipher,
tenant)` compiled against it and silently dropped `tenant` — while the
docs said the compiler turns that form away. And a `from` field with no
literal made the impl demand `SuppliedContext` regardless of the field's
type, so a row nesting another (all-literal) row lost the context-free
forms it had before, and the context the caller was then forced to pass
never reached a leaf. The same bound landed on `DecryptInto` for one-way
term fields that decryption never opens.
One rule replaces all of that: a `from` field is derived under its own
`context`, or under `()` if it has none — never under the caller's. Its
type decides whether `()` will do: a nested row accepts it, a leaf refuses
it at the field (the obligation is checked in the body, spanned at the
field type) until it is given a literal. A row therefore never passes the
caller's context anywhere and is implemented for `Ctx = ()` exactly, which
makes `encrypt_into` / `decrypt_from` the only forms that compile, as
documented. Handing every column of a row one shared context was the
cross-column transplant the per-field contexts exist to prevent, so
nothing legitimate is lost; `#[stash(row = ..)]` fills the literal in.
The derive's context bound is now `__Ctx: Clone` — the per-field bounds
already imply `EncryptContext` / `DecryptContext`, and the extra impl
lifetime went with it. `Record::derived()` replaces three inlined filters
and the by-field candidate list is computed once.
Also from the review:
- `EncryptFrom` / `DecryptInto` `on_unimplemented` notes said "if `{Ctx}`
is `()` … use `encrypt_into_with_context`" unconditionally, which never
fired for `()` (the `SuppliedContext` note wins) and gave false advice
on a source-type mismatch. Both now state the rule; `SuppliedContext`'s
note covers `from` fields and how a context type of your own opts in.
- The "extend with your own SEM type" recipe guarded with
`context.as_bytes().is_empty()` on an encoded `PrfContext`, which is
never true. `is_degenerate_aad` / `is_degenerate_prf_context` are
public; the recipe and the `PrefixTerm` example use them.
- `DecryptFrom<S, C>`'s blanket impl had dropped its `S: DecryptInto`
clause, so the trait held for every triple and meant nothing as a
bound. It mirrors `EncryptInto` now: no trait parameters.
- Decrypt leaves bound `Ctx: SuppliedContext<'c>` alone; it implies
`DecryptContext`.
- Stale spellings from the rename: `encrypt_into::<EqualityTerm>(..)`,
two-parameter `EncryptFrom<P, _>` / `DecryptInto<P, _>`, and the design
doc's `#[encrypted(source = ..)]` / "omit `source`".
Pinned by `tests/ui/row_with_context.rs` (`_with_context` against a row),
`tests/ui/from_leaf_without_context.rs` (a `from` leaf with no literal,
reported at the field) and `a_row_nests_in_a_row_without_a_context`.
Claude-Session: https://claude.ai/code/session_01HU1Bbw4eQp9kEEneXxDcKr
…_context sugar Review fixes on the context-parameter change: - A derived `DecryptInto` generic over the caller's context bounds it by `DecryptContext` again. A term field's `DecryptField` accepts any context (it opens nothing), so field bounds alone let a record whose ciphertext field carries a literal accept — and silently discard — any `Clone` value as its decrypt context. Pinned by an expansion test and a `compile_fail` doctest on `DecryptContext`. - `encrypt_into_with_context` / `decrypt_from_with_context` bound `Ctx: SuppliedContext`, so `()` is refused on the sugar and misusing a row now gets the guided E0277 on the decrypt side too, not a bare E0308. - `SuppliedContext: DecryptContext` is declared, not hand-mirrored, so the documented implication holds by construction; the transitional `is_degenerate_*` predicates now say in rustdoc that they are deleted when vitaminc#291 lands. - The empty-`context` derive error no longer advises a `from` field to drop the attribute — a dead end, since a `from` field is never handed the record's context — and the check runs after parsing so attribute order cannot change the advice. - Derive internals: one `FieldContext` classification replaces the four parallel projections of literal/unit/caller; the impl scaffolding is shared between the two derives; encrypt-side field bounds are spanned at the field type as decrypt's already were; the stray raw derived- field filter uses `Record::derived`, computed once per expansion. - The hand-written composite examples inherit the leaves' context policy through per-field bounds — the clauses the derive emits — instead of restating it, and RFC 0002 no longer claims a row "leaves the context unbounded" where the implementation is `()` alone. Claude-Session: https://claude.ai/code/session_01P5YHK3w6Kj9ajTnmkaXCHW
The Naming section still recorded `#[derive(Encrypted)]` as the macro name, and the implementation notes still called the derive unbuilt and the row snippet "future". `stack-encrypt-derive` ships `#[derive(EncryptFrom)]` / `#[derive(DecryptInto)]` under `#[stash(..)]`, each named after the trait it emits — which is also why the sketched noun could not stand: it names neither trait, and one noun cannot cover both directions. Claude-Session: https://claude.ai/code/session_01VEKAfJiDDhSxEZRAVJQJPX
Review findings from cipherstash/cipherstash-suite#2163: - The derived encrypt impl now bounds its caller context by `EncryptContext`, mirroring the decrypt side. Without it, a third-party leaf generic over its context let the raw `EncryptFrom::encrypt_from` accept — and silently discard — any `Clone` value as its context. Pinned by `a_caller_context_must_be_an_encrypt_context`. - The `encrypt_into_with_context` sketch in the design doc now shows the `Ctx: SuppliedContext<'c>` bound the shipped signature has. - The `seal_pending` doc no longer names `encrypt_into::<StackCipherText>`, a call that no longer compiles. - The `SuppliedContext` doc no longer claims coverage of composites containing `()`, and the crate re-exports `IntoPrfContext` / `PrfContext` so a context newtype needs no direct `vitaminc-prf` dependency. Claude-Session: https://claude.ai/code/session_01P5YHK3w6Kj9ajTnmkaXCHW
…s `from` and context A row no longer needs an attribute on any field. `row = User` says the record is a row of the struct `User`: every derived field is derived from the plaintext field of its own name, under the context `"<snake_case type>/<plaintext field>"` — `age` from `user.age` under `"user/age"`, a tuple row's `.0` under `"user/0"`. Both halves name the column, not the encrypted struct, so `#[stash(from = email_address)]` (the override for a name that differs) is derived under `"user/email_address"`; `#[stash(context = "..")]` is taken verbatim. Nothing is pluralised or otherwise guessed. `row` is exclusive with `plaintext`, and must name a struct directly. Because the inferred context is the AAD of every stored ciphertext in the column, renaming the plaintext type or a field is a data migration; the docs say to pin the old literal with `context = ".."` first. A field the plaintext does not have is reported by rustc at the field (`tests/ui/row_field_missing.rs`); `row` + `plaintext` at the attribute (`tests/ui/row_with_plaintext.rs`). Claude-Session: https://claude.ai/code/session_01HU1Bbw4eQp9kEEneXxDcKr
…nested` hands a field `()` `row = User` now requires `context = "users"` beside it; each field is derived under `"<context>/<field>"`. The prefix is part of the stored data's identity — the AAD of every ciphertext in the row and the domain of every term — so it is never inferred from the Rust type's name: two plaintext types with the same name in different modules can no longer silently share every column context (byte-identical index terms across their tables, ciphertexts transplantable between them), and renaming a struct can no longer silently change the AAD of every stored row. `#[stash(nested)]` opts a row field out of the inferred context: it is handed `()`, which a nested row accepts and a leaf refuses — the ()-handoff the docs promised now exists in row mode, not only under `plaintext = ..`. Also addressed from the same review: - `supplied_aad` / `supplied_prf_context` are the one public choke point for validating-and-encoding a supplied context; the `is_degenerate_*` predicates return to crate-private, and the third-party-leaf recipe goes through the choke point, whose signature survives the vitaminc#291 migration. - `DecryptField` carries a `#[diagnostic::on_unimplemented]` pointing a term-only bundle inside an auto-mode record at `#[stash(decrypt)]`. - The deferred exactly-one-decryptable check for generic records is pinned by a `compile_fail` doctest on `Decryptable` (trybuild runs `cargo check`, which never evaluates post-monomorphization consts, so a ui test cannot reach it). - The empty-container fail-fast loss and the caller context a literal-carrying record discards on decrypt are documented where they bite (the container impls, `DecryptContext`, the attributes guide). - The `SuppliedContext` roster notes its coupling to vitaminc's `IntoAad` implementor list and the orphan-rule consequence. - Both derives build field bounds through one shared `push_field_bounds`, and the generic-source vs listed-plaintexts emission fork collapses to one loop per derive. Claude-Session: https://claude.ai/code/session_01P5YHK3w6Kj9ajTnmkaXCHW
Missed in bd3b61824: docs/target-directed-encryption.md and RFC 0002 §7 still described the row prefix as inferred from the snake-cased type name. Claude-Session: https://claude.ai/code/session_01P5YHK3w6Kj9ajTnmkaXCHW
…ke point `is_degenerate_aad` went back to crate-private when `supplied_aad` became the one public way to validate and encode a supplied context, so the design doc's `StackCipherText` bridge no longer matched the impl it quotes. Claude-Session: https://claude.ai/code/session_01VEKAfJiDDhSxEZRAVJQJPX
Review finding on cipherstash/cipherstash-suite#2164: the row decrypt now runs before the field-alone plan decrypt, so the counter reads exactly 1 where the other counter assertions in the file are exact too, instead of the `>= 1` that hid the total. Claude-Session: https://claude.ai/code/session_01P5YHK3w6Kj9ajTnmkaXCHW
…h/claude/stack-encrypt-derive-row feat(stack-encrypt-derive): `#[stash(row = User, context = "users")]` infers each field's `from` and context
…ASI without reqwest Phase 1 of the stack-encrypt Go bindings (docs/stack-encrypt-go-bindings.md). On wasm32-wasip1 reqwest 0.13.4 selects a native backend (tokio-full and the aws-lc-sys TLS provider) that does not build for WASI, and it was the only thing standing between the three stack crates and the target. HTTP is host-provided under wazero, so it has to be out of the WASI build by construction: - `http` feature, default on, in all three crates: stack-encrypt/http -> stack-kms/http -> stack-auth/http -> dep:reqwest. stack-auth keeps the token model, `AuthStrategy`, `AuthStrategyFn` and `StaticTokenStrategy` unconditionally; every HTTP-speaking strategy, the refresh engine, device binding and `Token::refresh` sit behind the feature. stack-kms keeps `Client<C>`, key derivation and the key-source traits; `HttpConnection`, its options and `StackKmsBuilder` sit behind it. stack-encrypt keeps `StackCipher::builder().kms(..)`; `StackCipher::new()` and the from-environment `init` sit behind it. stack-kms and stack-encrypt take stack-auth as a path dep with `default-features = false` (a workspace dep's defaults cannot be turned off by a member). - `StackKms<C, Conn = HttpConnection>` with `StackKms::connect(opts, credentials, client_key)`: the transport-injecting constructor a host with its own `ZeroKMSConnection` builds through, and the only one without `http`. `ZeroKMSConnection` gains `ensure_base_url` / `has_base_url` so endpoint discovery from the token's `services` claim works over any connection (previously inherent to `HttpConnection`). - `Error::ConnectionInit` boxes the connection's own init error. - `StackCipher::builder()` lives on `impl StackCipher<FromEnv>` so it resolves without a type annotation whether or not `http` is on. - `wasm:wasi-check` now gates stack-auth, stack-kms and stack-encrypt (`--no-default-features`) alongside the core crates; the CI workflow's paths cover them. - A unit test drives `StackKms` end to end over the in-memory `TestConnection`. Verified: `mise run wasm:wasi-check` passes for all eight crates with no reqwest/hyper/aws-lc-sys in any wasip1 tree; `mise run lint` clean; 451 tests pass with `--all-features` and with `--no-default-features`; doc tests and rustdoc (`-D warnings`) pass. BREAKING CHANGE: stack-auth's `RequestError` tuple payload is now `Box<dyn std::error::Error + Send + Sync + 'static>` instead of `reqwest::Error`. Construct it via `RequestError::from(reqwest_error)` (or box the error yourself) instead of `RequestError(reqwest_error)`, and recover the concrete error with `.0.downcast_ref::<reqwest::Error>()` instead of using `.0` as a `reqwest::Error` directly. `source()` behaviour is unchanged; the `Display` message is now "Request to the auth server failed" (previously "HTTP request failed"). Claude-Session: https://claude.ai/code/session_01HU1Bbw4eQp9kEEneXxDcKr
…h item-level http gates Without the http feature the crate previously silenced dead-code analysis entirely via #![cfg_attr(not(feature = "http"), allow(dead_code))]. Gate the eleven affected helpers (URL massaging, clock sharing, refusal classification, token setters, AccessKey secret access) individually with #[cfg(feature = "http")] instead, so the compiler verifies the feature partition in both directions: no-http code reaching an http helper fails to compile, and newly dead code warns instead of being swallowed. Test fallout handled so no coverage regresses in the no-http run: - workspace_crn tests assign Token.region directly instead of the now http-only set_region, keeping workspace_crn covered without http - test_refresh_debug_does_not_leak_tokens loses its http gate (it never needed HTTP) so the Debug secret-leak regression test runs in the no-default-features shape the WASI guest ships - http-only test helpers (TestClock, crn_with_workspace, jwt_with_workspace, classify_issuance_failure_tests) are gated with their consumers The one honest residual is AccessKey's inner field: parsing is unconditional token-model API, but only the http-gated AccessKeyStrategy consumes the secret, so that single field keeps a scoped cfg_attr(not(http), allow(dead_code)). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDes81qQM2ConLobY5G5JD
Feature additivity (the two API-shape bugs): - stack-auth: RequestError has one definition with an always-boxed payload; the http-gated From<reqwest::Error> does the boxing. Its public field's type no longer changes under feature unification, which would have broken a no-http host the moment anything else in its graph enabled http. - stack-encrypt: Error::Config is unconditional with a boxed source (the http-gated From<StackKmsBuilderError> boxes), so the enum's variant set no longer tracks the feature. Build config: - stack-auth's workspace entry is now default-features = false (the same pattern as cllw-ore/cts-common); stack-kms and stack-encrypt use the workspace dep again instead of hand-written path+version pins, and the consumers that rely on the default transport re-enable it with features = ["http"] (cipherstash-client, cipherstash-cli, cts-web dev-dep, stack-auth node/wasm bindings). One version pin remains, at the root. Cargo.lock is unchanged. Docs and doctests: - cargo test --no-default-features now passes including doctests in all three crates: http-only examples (README via include_str, token_store, StackKmsBuilder quick-start, StackCipher::new) are doc-gated on the feature with short no-http fallbacks, and the no-http rustdoc's broken intra-doc links are fixed. RUSTDOCFLAGS=-D warnings is clean in both shapes; default-features doctest coverage is unchanged. API cleanups: - ZeroKMSConnection::ensure_base_url / has_base_url get default impls (no-op / true) with the first-value-wins contract documented, so transports that don't do endpoint discovery (TestConnection, and hosts that pin at init) no longer stub them; HttpConnection keeps its overrides. - The unused From<ConnectionInitError> for Error impl is deleted; StackKms::connect is the one construction path for Error::ConnectionInit. - StackCipherBuilder::new() (+ Default) is the canonical builder entry point; StackCipher::builder() stays as a thin alias on the phantom FromEnv impl for annotation-free inference. - token.rs's scattered per-test http gates collapse into one gated refresh_tests module; make_token and the Debug secret-leak test stay ungated so they keep running without http. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDes81qQM2ConLobY5G5JD
…-web dev-dep wasm:wasi-check only runs cargo check, so the no-default-features shape's unit tests, doctests, and rustdoc had no gate — the 7 doctest failures and 21 broken intra-doc links fixed in the previous commit merged green. Add wasm:no-http-test (per-crate cargo test + RUSTDOCFLAGS=-D warnings cargo doc, default features off, one crate per invocation so dev-dep feature unification can't switch http back on) and run it from test-wasi.yml, whose path filters already cover the three crates. cts-web's stack-auth dev-dep only uses StaticTokenStrategy, which is part of the unconditional token model, so it doesn't need the http feature. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDes81qQM2ConLobY5G5JD
Keep HttpConnection failure Display to the status line — full body and headers can carry sensitive response content into logs and are unbounded; they remain available via Debug. Align RequestError's message with its transport-agnostic shape: the payload is no longer necessarily an HTTP error, so say 'request to the auth server failed' instead. Claude-Session: https://claude.ai/code/session_01BpqczxAVwUsTYdCRWh9dYb
…x Phase 1 contract docs Review follow-ups from cipherstash/cipherstash-suite#2158: 1. `cargo test -p stack-encrypt --no-default-features` still resolved reqwest: the stack-kms dev-dependency didn't set `default-features = false`, so dev-dep feature unification pulled stack-kms/http -> stack-auth/http -> reqwest into the graph `wasm:no-http-test` claimed was HTTP-free. Disable defaults on the dev-dep (the tests only need `test-support` for FakeDataKeySource; none are HTTP-dependent). Verified: `cargo tree --no-default-features -e normal,dev,build -i reqwest` matches nothing, and the full no-default-features test/doctest run passes. 2. The plan doc named StaticTokenStrategy as part of the unconditional no-http surface, but it is (correctly) gated behind `cfg(any(test, feature = "test-utils"))` and has no production users. Revise the Phase 1 contract to name AuthStrategyFn (and the guest's HostTokenStrategy) as the supported production path; StaticTokenStrategy stays a test double. Claude-Session: https://claude.ai/code/session_01BpqczxAVwUsTYdCRWh9dYb
… manifests Review follow-up on cipherstash/cipherstash-suite#2158: two manifest comments still described StaticTokenStrategy as part of the unconditional no-`http` surface, contradicting the corrected plan and the actual `cfg(any(test, feature = "test-utils"))` gate. Name AuthStrategyFn as the production path and state where the test double lives. Comments only; no dependency or feature change. Claude-Session: https://claude.ai/code/session_01BpqczxAVwUsTYdCRWh9dYb
The variant had {0} in the message and #[source] on the same field, so
chain printers (anyhow {:#}, tracing) showed the inner error twice —
against this file's own convention that Display stays static and the
source chain carries the detail. Flagged by Toby on cipherstash/cipherstash-suite#2158.
Claude-Session: https://claude.ai/code/session_01P5YHK3w6Kj9ajTnmkaXCHW
The Go/wazero binding needs byte formats both languages agree on before the
guest is written. This freezes the two commitments stack-encrypt makes:
SealedValue leaf: to_bytes/from_bytes with the canonical layout
`version(1) ‖ iv(16) ‖ tag_len(u16 LE) ‖ tag ‖ local_ciphertext`. The
version byte is bound into every leaf's AAD through a new labelled
derivation — PAE("stack-encrypt/leaf", version, derived_aad, tag), replacing
the unlabelled (aad, tag) tuple — mirroring how vitaminc binds its inner
wire version, so bytes relabelled with a future version byte fail
authentication instead of parsing under the wrong rules. The derivation
bytes are pinned by a unit test; decode failures surface as the new
LeafBytesError.
Index terms: every term type now exposes its frozen encoding.
EqualityTerm is the 32 PRF bytes as-is and OreTerm/OpeTerm are the raw CLLW
ciphertext bytes — byte-identical to what the EQL layer hex-encodes into
hm/oc/op, so rows written through a binding compare against rows the
Rust/EQL path wrote. MatchTerm encodes its sorted positions as
little-endian u16s (EQL sends bf as a JSON integer array, so the
byte-string form is this crate's own). cllw-ore's variable-width
ciphertext types gain length-validating TryFrom<&[u8]> for the decode path
(the direction the existing From<Vec<u8>> FIXME asks for).
tests/frozen_bytes.rs carries the golden vectors (fixed hex the Go decoder
tests against) plus structural-rejection and real-leaf round-trip coverage;
tests/term_bytes.rs continues to pin the derivations these encodings wrap.
Part of CIP-3553 (stack-encrypt Go bindings), phase 2 of
docs/plans/stack-encrypt-go-bindings.md.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDes81qQM2ConLobY5G5JD
…te layout Expand SealedValue's frozen-encoding rustdoc with an offset table for the v1 envelope and a diagram of the two nested framings (this crate's envelope around vitaminc's LocalCipherText), spelling out where each version byte lives and which AAD derivation binds it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDes81qQM2ConLobY5G5JD
…e formats - cllw-ore variable-width types: one private length predicate per type, applied by both `from_bytes(Vec<u8>) -> Result` and `TryFrom<&[u8]>`. The unchecked path is now `from_bytes_unchecked` (replacing the FIXME'd `From<Vec<u8>>`); it stays `pub` and `FromHex` stays permissive because cipherstash-client's ste_vec terms carry a tagged bit stream (65/66 bytes) that the byte-aligned rule would reject. Callers renamed. - `SealedValue` is valid-by-construction: `from_parts` and serde deserialisation reject tags longer than the u16 length field, so `to_bytes()` is infallible. `TryFrom<&[u8]>` rustdoc no longer claims a generic-codec justification. - Document that the labelled, versioned `leaf_aad` cannot open leaves sealed under the phase-1 AAD (plain AEAD failure, no version signal). - Reword the "Byte encodings" docs: same shape as v1 / cipherstash-client terms, values are not comparable across the two. Claude-Session: https://claude.ai/code/session_01AhxMmV52dSYwjAT8KENHRV
… byte APIs - `MatchTerm::from_positions`/`from_bytes` reject positions outside the filter size fixed by `O: MatchConfig`, so a mis-decoded position list fails loudly instead of producing a term that never matches. - Replace the stringly `TermError::MalformedTermBytes(&str)` with a `TermBytesError` enum (`PartialEq`), mirroring `LeafBytesError`; CLLW decode failures report the offending length rather than the deliberately opaque `cllw_ore::Error`. - Every term type now exposes `to_bytes()` and `TryFrom<&[u8]>`; `as_bytes()` only where a contiguous buffer exists. Plan doc states the per-type surface instead of "on every term type". - Reframe the match term's LE-u16 byte string as the frozen FFI transport encoding; the stored and queried contract is the position list. - Scope the `SealedValue` byte-format commitment to ciphertext and link the index-term encodings. Claude-Session: https://claude.ai/code/session_01AhxMmV52dSYwjAT8KENHRV
- Move the sealed-leaf AAD breaking change out of `SealedValue`'s rustdoc and into a new packages/stack-encrypt/CHANGELOG.md. Release history does not belong on the type. - Drop the cross-reference from `SealedValue`'s public docs to the `cipher` module docs. `mod cipher` is private, so its module-level `//!` docs render nowhere in `cargo doc` output except the source listing — the pointer sent readers to documentation the HTML does not contain. The leaf-AAD derivation it referred to is already stated inline. - Write `LocalCipherText` and `Aes256Cipher` as code spans rather than intra-doc links. Both are private imports of vitaminc types, so rustdoc resolves them only when dependency docs are built; under `--no-deps` (what CI and `wasm:no-http-test` run) the links silently degrade to literal `[Name]` text, with no warning. Code spans render the same either way. - Mark `LeafBytesError`, `TermBytesError` and `TermError` `#[non_exhaustive]` so the versioned decoders can gain variants without a source break. Claude-Session: https://claude.ai/code/session_01BpqczxAVwUsTYdCRWh9dYb
The seal path constructed SealedValue directly from the DataKeyWithTag a DataKeySource returned, without the tag_fits_length_field check every other construction site applies. A custom source returning a tag longer than the u16 length field would (in release builds) produce a leaf whose to_bytes saturates the length field but appends the whole tag, so from_bytes no longer inverts the encoding. seal_leaf now validates the generated tag before building the leaf, and a seal-path boundary test drives encrypt through a DataKeySource that inflates its tags past u16::MAX, asserting the seal fails rather than mis-encodes. Claude-Session: https://claude.ai/code/session_01P5YHK3w6Kj9ajTnmkaXCHW
The cipher module was private with its items re-exported at the crate root, so the 68 lines of module-level internals documentation (batching, AAD derivation, wire format) never appeared in cargo doc output — the crate docs pointed readers at src/cipher.rs source instead. The module is now pub (matching sem and target) and the crate docs link to it. The rustdoc lints this surfaces (a link to the private leaf_aad, two redundant explicit StackKms link targets) were fixed in the previous commit; cargo doc is warning-free with and without default features. Claude-Session: https://claude.ai/code/session_01P5YHK3w6Kj9ajTnmkaXCHW
…h/claude/stack-encrypt-wasi-phase2 feat(stack-encrypt): freeze the byte formats the crate owns (WASI phase 2)
…raits Two additive API changes the WASI guest (next commit) needs: - PendingStackCipherText::into_pending — turn a pending tree into a Pending request carrier without settling it, so several independently built trees (e.g. one per record field, decoded from FFI values that are not Clone) merge with Pending::zip/all and seal in one batched generate_keys call. seal() is now expressed through it; same sealing path either way. - sem re-exports CllwOreEncrypt/CllwOpeEncrypt: they already appear in the module's public bounds (ore_term, OreTerm, ...), so a caller writing a generic wrapper over the term APIs has to be able to name them without depending on cllw-ore directly. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDes81qQM2ConLobY5G5JD
…se 3) The wasm32-wasip1 guest at bindings/go/stackencrypt/guest (a detached workspace, like the fuzz crates), per phase 3 of docs/plans/stack-encrypt-go-bindings.md. Control stays in Rust: request assembly, key derivation, batching and AAD/PRF context binding run unmodified inside the guest; the host provides exactly two imports. - Exports (vitaminc guest ABI conventions: buffer registry with zeroizing dealloc, packed-u64 results, hostile-input validation, no handle-id reuse): se_alloc/se_dealloc, se_cipher_init/se_cipher_free, se_encrypt/se_decrypt (+_element), se_encrypt_record/se_decrypt_record, se_term. Status codes 1-4 match vitaminc's; 5-11 map the ZeroKMS request outcomes and term failures so the Go caller can tell a bad token from a tampered ciphertext. - Host imports (module cipherstash_transport): transport_send — cipherstash/cipherstash-suite#2099's import generalised to (method, url, headers, body) with 'name: value' line headers — and token_get (phase-1 auth: the host owns minting and refresh). WasiHostConnection implements stack_kms::ZeroKMSConnection over it, with the endpoint pinned from the init config or discovered from the token's services claim; HostTokenStrategy implements stack_auth::AuthStrategy over token_get. - Values cross in the vitaminc FFI codec (one codec, shared with the vitaminc guest); ciphertext-tree leaves are the frozen phase-2 SealedValue byte encoding, so a leaf lifted out of a tree is exactly what a database column holds. - Records: a plan {field -> {context, outputs: [c|eq|match|ore|ope]}} drives per-field ciphertexts and locally derived terms; all rows of a batch seal in one generate_keys via the new PendingStackCipherText::into_pending. Terms ride the result tree as passthrough bytes nodes. - Native tests (26) run the same ops the ABI drives against FakeDataKeySource: codec round trips, native-decryptable leaves, term bytes equal to the native sem derivations, a counting key source pinning the one-call batching, and status mapping for hostile inputs. The release .wasm's import surface is exactly WASI + cipherstash_transport. - mise tasks: wasm:guest:build, wasm:guest:test. Extracting the shared ABI modules into a common vitaminc crate is a follow-up in the vitaminc repository; the registry/session modules here are copies with pointers back. bridge.go and the integration harness land with the Go module (phases 4-5). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDes81qQM2ConLobY5G5JD
…nsports `HttpConnection` and the WASI guest each carried their own copy of the same table: 2xx must be JSON and deserialize, and 404/401/403/409 map to specific `ViturRequestErrorKind`s so callers can tell a bad token from a missing keyset without parsing strings. Two copies of a protocol contract drift. Move it to `connection::classify`, outside the `http` feature gate, so a guest built without `http` reaches the same verdicts as the default transport. `HttpConnection` now reads the response once and delegates; `BaseUrlUnresolved`, `FailureResponse` and `UnexpectedContentType` are public so a host bringing its own transport can return the same errors. `Display` on the two response errors stays the concise form landed in f3c87fbcc for cipherstash/cipherstash-suite#2158 — status and expectation only. Body and headers are unbounded, attacker-influenced text and these types' `Display` reaches logs; both remain available through `Debug`. Claude-Session: https://claude.ai/code/session_01BpqczxAVwUsTYdCRWh9dYb
`ClientKey::from_hex_v1` is strict lowercase hex, but the encodings a user actually holds are not: `secretkey.json` serialises standard padded base64, and hex pasted from elsewhere may be upper case. Front-ends that take key material from an untyped boundary — an environment variable, a config file, the WASI guest's FFI config object — were rejecting valid keys for their encoding alone. Add `from_encoded_v1`, the lenient counterpart, matching what `SecretKey::from_hex` and `EnvKeyProvider` already accept. Decoding stays constant-time (`base16ct` / `base64ct`) and the intermediate bytes are wiped on every path. Claude-Session: https://claude.ai/code/session_01BpqczxAVwUsTYdCRWh9dYb
`pnpm run test` now reaches the @cipherstash/auth and @cipherstash/profile vitest suites, which load the napi module. Their `test` scripts do not build it, and nothing else in run-tests does, so profile-store.test.ts failed with "Failed to load native binding for linux-x64" on this PR (Run Tests, Node 22 and 24). run-tests now builds both bindings with `build:debug` after the protect-ffi binding, before the test steps. The previous commit has `build:debug` write its typings to the committed native.d.ts, so the build leaves the tree clean. This step and that fix came from the CI port (#1003); they land here because this PR merges first and has to pass on its own. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
The root [workspace.dependencies] pinned both exactly, and stack-auth inherits the requirement when it is packaged, so stack-auth 0.43.0 would require cts-common =0.43.0 and zerokms-protocol =0.12.31. The suite then could not unify registry stack-auth with a later compatible cts-common patch: its [patch.crates-io] entry would go unused, and a second cts-common would break the Crn, Region and WorkspaceId types that stack-auth exposes. That defeats the cts-common release decision in plan section 13.7. Use caret requirements for the two crates. Cargo.lock still holds the exact versions, so this changes no resolved version: every lock passes cargo metadata --locked unchanged. recipher and cllw-ore stay exact, because only unpublished crates use them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
Rename the module from github.com/cipherstash/cipherstash-suite/bindings/go to github.com/cipherstash/stack/languages/golang, as plan section 3.4 decides. The old path names a private repository whose bindings/go folder the suite removal deletes, so a go get of it could never resolve. Nothing outside the module imports the old path, and no version of it was ever tagged or fetchable through the Go proxy. The change is generated by stack-migration/go-rename.sh (go mod edit -module, plus a text replace in the module's .go and .md files): 36 files, 53 lines. go.sum is unchanged, and go vet, go build and go test pass under the new path. At the freeze, re-run the script rather than replaying this commit, because the cutover re-export can bring the old path back in new files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
go vet catches an import of the old suite module path, but not a stale go get line or pkg.go.dev link in a README. Check that go.mod declares the stack path, and that no tracked file outside docs/plans/ names the suite path, so the cutover re-export cannot bring it back unnoticed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
wasm:wasi-check greps cargo tree output for crates that must never link into a WASI guest (wasm-bindgen, web-sys, js-sys, reqwest, hyper, aws-lc-sys). CI sets CARGO_TERM_COLOR=always, so cargo wraps each line's tree prefix in ANSI colour codes, and the grep, which anchors on that prefix, can never match. A deliberate-break run on 2 October 2026 added wasm-bindgen to stack-kms, and the CI step still printed "all WASI crates compile with no JS-host or native-HTTP deps". Only the cargo check half of the step was working. Ask cargo tree for --color never. With CARGO_TERM_COLOR=always, the gate now fails on that break and passes on the clean tree. A script test checks every cargo tree call in the task asks for uncoloured output, and records why with a coloured sample the patterns cannot match. cipherstash-suite has the same task and the same CI setting, so its gate is dead too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
229f473 to
b3ffccd
Compare
bceb0bc to
1ad1055
Compare
| @@ -0,0 +1,68 @@ | |||
| import fs from 'node:fs' | |||
| import parseChangeset from '@changesets/parse' | |||
There was a problem hiding this comment.
Nothing calls this script now. Commit 359a6f18 deleted its workflow, require-auth-npm-changeset.yml, and no package.json script or workflow names it.
Its rule is also the opposite of the rule in lint-no-auth-changeset.mjs. This script fails when a stack-auth change has no @cipherstash/auth changeset. The new lint fails when a changeset names one. AGENTS.md warns about this case for parked changesets: two guards with opposite rules, and one of them half-retired. If PR E connects this script again before it deletes the lint, the two will fail each other.
The script also imports @changesets/parse, which is not a root dependency. Under the strict pnpm layout, that import may not resolve.
Please delete this script in this PR. PR E can bring the rule back when it arms publishing.
| The stack-* crates came from `cipherstash/cipherstash-suite`, which still owns | ||
| `cipherstash-client`, `cts-common`, `zerokms-protocol`, `recipher` and | ||
| `cllw-ore`. Here those come from crates.io, pinned exactly in the root | ||
| `Cargo.toml`. |
There was a problem hiding this comment.
This says the suite crates are "pinned exactly in the root Cargo.toml". Commit 5147dd00 changed cts-common and zerokms-protocol to caret requirements, so only recipher and cllw-ore keep an exact = pin. The comment in Cargo.toml gives the reason. Please make this sentence agree. For example: "Here those come from crates.io, and Cargo.lock holds the exact versions. recipher and cllw-ore are pinned with =. cts-common and zerokms-protocol take caret requirements, because the published stack-auth inherits them."
| // beforehand; `test:cargo` runs the crate's own tests. `wasm/**` is the | ||
| // output of `build:wasm`, cached with the native build it ships beside. | ||
| "@cipherstash/auth#build:native": { | ||
| "outputs": ["*.node", "native.d.ts", "wasm/**"] |
There was a problem hiding this comment.
This entry has two problems. Nothing runs turbo run build:native today, so neither problem occurs yet.
- The cache key is
$TURBO_DEFAULT$forlanguages/typescript/packages/authonly. The binding compilespackages/stack-auththrough a path dependency, and that folder is outside the package. When only thestack-authcrate changes, turbo gets a cache hit and restores the previous*.node. Thestash#buildentry above shows the fix: add$TURBO_ROOT$/packages/stack-auth/**, the rootCargo.tomlandCargo.locktoinputs. build:nativedoes not makewasm/**.build:wasmmakes it, from../stack-auth-wasm. Withwasm/**in this list, turbo stores whateverwasm/is on disk whenbuild:nativeends, and a later cache hit restores it, even if it is stale.
If no workflow will call this task through turbo, delete the entry. If one will, fix inputs and remove wasm/**.
| @@ -0,0 +1,189 @@ | |||
| # npm releases (changesets) | |||
There was a problem hiding this comment.
Not a blocker. This document describes the release process of the suite, and most of it is wrong in this repository:
release-npm.yml,publish-auth-npm.ymlandpublish-profile-npm.ymldo not exist here.- The paths
packages/stack-auth/nodeandpackages/stack-profile/nodedo not exist. - It says that changesets never sees the platform packages. In this repository,
pnpm-workspace.yamlnow includes them. - It says that
@cipherstash/profilepublishes on the first Version Packages merge. This PR marks it private.
An agent that reads this file gets wrong instructions. Please add a short note at the top that says the file is historical (the process of the suite, which PR E replaces with release.yml), or move the file into docs/plans/.
| # `index.node`. Each `build:debug` writes its typings to the committed | ||
| # `native.d.ts`, so the build leaves the tree clean. | ||
| - name: Build the auth and profile node bindings | ||
| run: pnpm --filter @cipherstash/auth --filter @cipherstash/profile run build:debug |
There was a problem hiding this comment.
Not a blocker. In each run-tests matrix leg, this step compiles two napi bindings from cold. It has no Rust cache, and it uses the default rustc of the runner, not the 1.94.1 pin in mise.toml. The protect-ffi step above restores index.node from a content-hash cache. If PR C does not add a cache here, please record that as a follow-up.
freshtonic
left a comment
There was a problem hiding this comment.
Automated review of 1ad1055d. I read the 15 commits after the import merge, but not the lockfiles or the formatting-only commit fa05f243. I checked the merge by its shape only, as the description asks.
The release-gate work is good. The files and noTreeBytes shapes fail closed, a missing listed file throws, and the Biome exclusions stop a reformat from turning into a gate skew. The wasi-check fix is correct: the coloured tree prefix contains m, so ^[^a-z]* could never match it. The new test records why.
I found no blocker. Please fix items 1 and 2 before merge, because they are small. Items 3 to 5 can be follow-ups. Each item has an inline comment.
scripts/check-auth-npm-changeset.mjshas no caller, and its rule is the opposite of the rule in the newlint-no-auth-changeset.mjs.AGENTS.mdsays the suite crates are "pinned exactly".Cargo.tomlgives two of them caret requirements.- The cache key of the
@cipherstash/auth#build:nativeturbo entry does not include thestack-authcrate, and the entry listswasm/**, which a different script makes. This is latent, because nothing calls the task through turbo yet. docs/npm-releases.mddescribes the release process of the suite, not the process in this repository.tests.ymlnow compiles two bindings from cold in each matrix leg.
What I did not check: the content of the 408 imported files, the Go and Rust code, and the new tests in release-gate.test.mjs. A changeset is not necessary: @cipherstash/auth is frozen, the new lint forbids a changeset for it, and no other published surface changes.
coderdan
left a comment
There was a problem hiding this comment.
Light review of 1ad1055d, done from the diff against the merge base b3ffccd4 with the suite checkout alongside for comparison.
Verified
- The six
packages/stack-*crates are byte-identical to suitemainexcepttasks.toml(paths andmise x --env testprefixes) and the fuzz crates' now-trackedCargo.lock. - The Go module and the three TypeScript bindings differ from the suite only by the module path rename,
npm/toplatforms/,workspace:*pins, the Biome reformat, and the script renames that keep cargo offpnpm test. cargo metadata --lockedpasses for the root lock and the five detached locks.cargo fmt --checkis clean on the root workspace.- The script guards pass locally (1,019 tests), including the new Go module path, auth changeset, WASI gate and publish opt-out guards.
- The freeze plumbing is coherent:
FROZEN_PUBLISHERScarries the seven auth packages, thefilesdigest covers the wrapper's 15 published files, Biome is kept off exactly those files, and the changeset lint stops a bump on the PR rather than onmain.
Nothing blocking. Four inline nits, none of which need to land before the freeze. The CI gap (no Rust tests run on the crates in this PR) is as the description says: PR C ports it, and the merge order covers it.
Generated by Claude Code
| # NOTE: this must stay the LAST command — the crap-stack-auth.yml CI workflow runs | ||
| # this task and relies on mise appending its trailing args (e.g. --format github) | ||
| # to this `cargo crap` invocation. | ||
| "mise x --env test -- cargo crap --path packages/stack-auth --lcov {{config_root}}/target/stack-auth-lcov.info --exclude 'node/**' --exclude 'wasm/**' --exclude 'examples/**' --exclude '**/tests.rs' --exclude '**/tests/**' --fail-above", |
There was a problem hiding this comment.
Nit: --exclude 'node/**' and --exclude 'wasm/**' are stale. Those directories moved to languages/typescript/packages/auth and stack-auth-wasm, so nothing under packages/stack-auth matches them now. Harmless, but worth dropping in the CI port or a follow-up.
Generated by Claude Code
| - minor | ||
| - patch | ||
| ignore: | ||
| # Released from cipherstash-suite and pinned with exact `=` requirements |
There was a problem hiding this comment.
Nit: this comment predates 5147dd00. cts-common and zerokms-protocol now take caret requirements in the root Cargo.toml; only recipher and cllw-ore (and the two Go guests) still pin with =. The ignore list itself is still right, since all four move in lockstep with the suite by hand. Same stale wording as the AGENTS.md line already flagged above.
Generated by Claude Code
| # path; this step builds it, as the protect-ffi step above does for | ||
| # `index.node`. Each `build:debug` writes its typings to the committed | ||
| # `native.d.ts`, so the build leaves the tree clean. | ||
| - name: Build the auth and profile node bindings |
There was a problem hiding this comment.
Non-blocking: this step compiles the root workspace with whatever Rust the runner ships, not the 1.94.1 pinned in the root mise.toml. Nothing in this job runs mise install at the repo root (the protect-ffi action runs mise from its own folder, which does not pin Rust). It passes today, and napi build only needs the crates to compile, so this is fine for PR B. Worth keeping in mind for the CI port, where the trybuild snapshots and clippy do depend on the pinned toolchain.
Generated by Claude Code
|
|
||
| ```bash | ||
| mise run wasm:guest:build wasm:auth-guest:build | ||
| cd bindings/go && go run ./stackencrypt/example |
There was a problem hiding this comment.
Nit: a few prose references to the old bindings/go folder survived the rename, because go-module-path.test.mjs only checks the full module URL. The ones that are actual instructions are this line and example/explicit/README.md:27,31 (cd bindings/go no longer exists). The rest are comments: example/main.go:7, packages/stack-encrypt/CONTEXT.md:6, packages/stack-guest-abi/Cargo.toml:12, packages/stack-guest-abi/tasks.toml:2, stack-guest-abi/src/{lib,buffers}.rs, stackauth/guest/src/lib.rs:52, both guests' Cargo.toml, and ADR 0005. Fine as a follow-up; if you want the guard to catch these too, a second needle on bindings/go with an allowlist for the vitaminc/bindings/go/... imports would do it.
Generated by Claude Code
AGENTS.md and a comment in .github/dependabot.yml still said the suite crates are pinned exactly in the root Cargo.toml. PR B (#1001) gave cts-common and zerokms-protocol caret requirements, because the published stack-auth inherits them, so only recipher and cllw-ore keep an exact pin there. freshtonic and coderdan raised both in review of #1001. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
|
@freshtonic and @coderdan, thank you both. This PR merged at 18:25 UTC as part of today's cutover. Here is where each item went. freshtonic's two items for before the merge
Follow-up issues for the rest
🤖 Generated with Claude Code |
This PR brings six Rust crates, three bindings and the Go module into this repository from
cipherstash/cipherstash-suite, with 807 commits of their history. It then makes them build and test here. The crates arestack-auth,stack-profile,stack-kms,stack-encrypt,stack-encrypt-deriveandstack-guest-abi. The bindings, which let JavaScript call the Rust code, are@cipherstash/auth,@cipherstash/profileandstack-auth-wasm. The Go module lands inlanguages/golang.This is PR B of 6 in the stack crates import, which moves this code here from the private suite repository. The stack crates import plan in Linear lists every step, and Linear issue CIP-4274 tracks the work.
This PR is stacked on PR A, #1000. Its base branch is PR A's branch, so the diff shows only this PR's own changes. It stays a draft until the freeze on Friday 2 October 2026, Pacific time. The freeze is the window in which the six PRs merge in order: #1000, #1001, #1003, #1002, #1009, then #1010. The steps for that day are in §9.1 of the plan.
Check the import merge by its file list, and read the 15 later commits
The first commit,
d0cea7cc, merges the suite's history into this repository. It adds 408 files, so check its shape rather than reading it:git diff --name-status b3ffccd4 d0cea7ccprints 408 lines, and every one is an added file (A). No existing file changes.git rev-list --count d0cea7cc^2prints 807, the number of suite commits it brings in.Read the 15 commits after the merge line by line. You can skim three parts of them:
2371f731adds fourCargo.lockfiles, which hold 15,526 of its 16,410 added lines.fa05f243reformats 21 files and changes nothing else.a981590arenames the Go module in 36 files, 53 lines. A script on the migration machine,stack-migration/go-rename.sh, generated it.The import merge keeps the suite's history and changes no file
The merge brings in an export: a copy of the suite's history that keeps only the files that move.
git filter-repo, a tool that rewrites history to keep chosen paths, made it from suite commite059b8ed. The export's head is82a85b4d, with 807 commits and 408 files, and its history starts on 11 February 2026.The two histories share no commit, and the merge changes nothing inside the files. The suite's 13 workflows land in
.github/imported-workflows/, where GitHub never runs them, and the next commit deletes them. PR C ports the CI.Commit messages name suite PRs as
cipherstash/cipherstash-suite#NNNN. That way, GitHub does not link them to this repository's PRs with the same numbers.Each commit after the merge makes one change
359a6f18cleans up the imported files. It deletes the imported workflows and stray files, and fixes links in the moved docs. It marks@cipherstash/profileprivate, because it was never published. It also points the Go guests' dependencies at crates.io, because the suite folders they named do not exist here. The guests are the Rust crates that the Go module runs as WebAssembly.6bb5775dstops Biome, the formatter and linter, from changing two kinds of file. The first is the 15 files that@cipherstash/authpublishes. The release gate,scripts/release-gate.mjs, runs on every push tomainbefore anything publishes to npm. From commit 6, it compares those 15 files byte for byte with version 0.44.0 on npm, so a reformat would make it fail. The second is generated files.fa05f243reformats the imported bindings with this repository's Biome config. The suite used double quotes and semicolons, and this repository does not. The commit holds formatting changes and Biome's safe fixes only.d94d6a77fixes two lint errors that the reformat brings to light in the auth binding's tests. The suite's linter was off for these files. A missing failure now fails the assertion, instead of throwing aTypeError.2371f731makes the repository root a Rust and Go workspace. It adds the rootCargo.tomlandCargo.lock, and lockfiles for the three crates that hold the fuzz tests. It refreshes the two Go guests' lockfiles, and those five crates each stay a separate Cargo workspace. It also adds entries for mise, pnpm and turbo, and splits the binding scripts sobuildandtestnever run cargo. mise is the tool that pins this repository's tool versions and runs its tasks.8c86f4bffreezes@cipherstash/authand its six platform packages in the release gate. Each platform package holds the native binary for one platform, such asdarwin-arm64. A frozen package fails the gate if its version changes, or if its published files differ from npm's copy. The commit also adds Dependabot entries, and a temporary check,lint-no-auth-changeset, that fails if a pending changeset names an auth package. A changeset is a file in.changeset/that says which packages a change releases.542fa511adds the new workspaces to the repository checks that list every workspace, such as the checks on Cargo publish settings and lockfile freshness.1c52c8f6describes the crates, bindings and Go module inAGENTS.md,SECURITY.md,CODEOWNERSandCONTRIBUTING.md.f7b00319moves the six cargo test tools intomise.test.toml, at exact versions. mise passes the root config down to every subfolder, so the EQL and protect-ffi CI jobs built these tools too. They built them with the runner's Rust 1.92 and failed, becausecargo-udeps0.1.61 needs Rust 1.93. mise readsmise.test.tomlonly for test runs, so those jobs no longer get the tools.8e8dd7abmakes the profile binding write its generated typings tonative.d.ts, as the auth binding does. Before, its build overwrote the hand-writtenindex.d.ts, which left the tree changed and the package's types wrong.0dc76af1builds the auth and profile bindings intests.ymlbefore the test run. Without the build,pnpm run testfailed to load the profile binding. This step started in PR C and moved here, so PR B passes CI on its own.5147dd00givescts-commonandzerokms-protocolcaret version requirements, such as^0.43.0. A caret requirement accepts any later compatible version. The publishedstack-authinherits these requirements, so an exact pin would stop the suite sharing one copy ofcts-commonwith it. The lockfiles still hold the exact versions, so no resolved version changes.a981590arenames the Go module fromgithub.com/cipherstash/cipherstash-suite/bindings/gotogithub.com/cipherstash/stack/languages/golang. The old path names a folder in a private repository, sogo getcould never fetch it. Nothing outside the module imported the old path, and no version of it was ever tagged.d3524b87adds a test thatgo.moddeclares the new path, and that no file outsidedocs/plans/names the old one. A later export from the suite could bring the old path back, and this test would catch it.1ad1055dfixeswasm:wasi-check, which could never fail in CI. The check searchescargo treeoutput for crates such aswasm-bindgen, which must never be in a build for WASI. WASI is the WebAssembly System Interface that the guests target. CI turns on coloured output, and the colour codes stopped the search matching. The check now asks for--color never, and fails when a deliberate break addswasm-bindgentostack-kms.The shared suite crates come from crates.io
The imported crates depend on four crates that stay in the suite:
cts-common0.43.0,cllw-ore0.5.0,recipher0.3.1 andzerokms-protocol0.12.31. The root workspace takes them from crates.io. Phase 0 of the plan released them from suite main, and their source matches suite main.Publishing stays off until PR E
This PR brings
@cipherstash/authand its six platform packages here, but the release gate freezes them. A version bump of any of them makes the gate fail, so nothing publishes. PR E removes the freeze.The checks pass locally and in CI
cargo metadata --lockedpasses in all 8 Cargo workspaces. It fails if a lockfile is out of date.cargo tree -dlists crates that appear in more than one version. It shows onects-commonand one set of vitaminc crates.go:testandgo:lintpass.pnpm install --frozen-lockfile,code:checkandtest:scriptspass, with 1,020 script tests.@cipherstash/auth.Plain cargo test fails 3 tests, so CI uses nextest
Plain
cargo test --workspacefails 3stack-kmstests. A test helper sets environment variables while other tests run in parallel, so the tests race. nextest runs each test in its own process, so CI passes. PR C adds a test that stops any workflow running plaincargo testover the workspace.A Developers team member must merge this PR
mainrequires signed commits.git filter-reporewrote the 807 suite commits, so they carry no signature. A member of the GitHub Developers team must merge this PR, and bypass only the signature rule.Merge it with a merge commit, which keeps every commit, and never with a squash or a rebase. A squash would replace the 807 suite commits with one, and lose the history that this PR exists to keep.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a