chore(deps): repin the resolved sibling reviews — alkvault 0.2 + alktunnels 0.5.0 + alktty 0.8.2

- alkvault 0.1 -> 0.2 (breaking req): decrypt returns
  Zeroizing<String> (alkvault review 001, resolution paths 1+2); the
  adapter seam's own wrap in decrypt_and_decode is dropped (the
  pre-wrap window ADR-005 decision 3 recorded is closed upstream —
  the vault fences the plaintext from its return onward, every path
  including the UTF-8 error one; rotate never holds a bare plaintext
  either). decode_key_arm's signature is unchanged.
- alktunnels 0.4.1 -> 0.5.0 (breaking req, additive API): the
  review-003 W2 adoption's dep half (the establisher shape's
  registration ride lands in the next commit).
- alktty 0.8.1 -> 0.8.2 (patch, PTY exit-signal fidelity): pin text
  refreshed; no alkssh contract change (the negative-code convention
  is consumed verbatim).
- seam docs re-shaped to the closed-gap posture (key_source module +
  decrypt arm + decode arm; doubles.rs; fuzz key_decode battery).

Verification: cargo test (default + --all-features), clippy x2
-all-features -D warnings, fmt, fuzz corpus replay (11).

Verification of the flake check: e2e real_git_push row 6+ isolated
passes on both trees (one full-matrix load flake, pre-existing abort
race, not this change).
This commit is contained in:
glm-5.3-flash committed 2026-11-04 09:45:22 +00:00
1 parent 17c5890961
commit 36d08868c9
7 files changed
+72 -56

No files matched your search

Generated
+7 -7
View File
@@ -109,7 +109,7 @@ dependencies = [
[[package]]
name = "alkssh"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"alkcall",
"alkgit",
@@ -131,9 +131,9 @@ dependencies = [
[[package]]
name = "alktty"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2265167842b7d3ea09bc235f48450d3dcf22049a2ca71f8155de00acbf97c226"
checksum = "9cab9b0d3d6c1bdef2622799e3b95bb6063720153a124a57f4c60f95f3d42ea8"
dependencies = [
"alkcall",
"async-trait",
@@ -153,9 +153,9 @@ dependencies = [
[[package]]
name = "alktunnels"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee56da894f2d02120d3602df32c7f6a9db7f930a401df66d40b632cd9767af41"
checksum = "198d3aa766889cf5cc9b764319f9aa75e21234d623021893c26675f7c4276242"
dependencies = [
"alkcall",
"bytes",
@@ -169,9 +169,9 @@ dependencies = [
[[package]]
name = "alkvault"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e032b074f7fea71ea15528994c5b3a6503c082c519045bb3b0519281291918"
checksum = "b2522ded10a506770779e72063422057bb63f1b75457c1742a3362072adfb1c4"
dependencies = [
"aes-gcm",
"base64",
+31 -19
View File
@@ -8,7 +8,7 @@ exclude = ["fuzz"]
[package]
name = "alkssh"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
rust-version = "1.89"
license = "MIT OR Apache-2.0"
@@ -52,25 +52,37 @@ async-trait = "0.1"
# russh keys path). Never enable russh's `des`/`dsa` features.
russh = { version = "0.65", default-features = false, features = ["aws-lc-rs"] }
alkcall = "0.10"
alkvault = "0.1"
# alkvault 0.2 (breaking req bump, 0.1 -> 0.2, 2026-11-04): `decrypt`
# returns `Zeroizing<String>` (alkvault review 001's resolution — the
# pre-wrap plaintext window ADR-005 decision 3 recorded is closed
# upstream); the adapter-side wrap is dropped. Pin re-verified against
# crates.io: MIT OR Apache-2.0.
alkvault = "0.2"
# Wave-3 seam-handle types (`config.rs`'s `SessionSeam`/`GitSeam`).
# Pins verified against crates.io (2026-10-28): alktty 0.8.1, alkgit
# 0.1.0 — both MIT OR Apache-2.0, both rust_version 1.88 ≤ our 1.89,
# both over alkcall ^0.10 (shared with our `alkcall = "0.10"` → 0.10.1
# resolution). alktty's `local` feature stays off — alkssh never
# touches a real backend; the `local` shapes stay alktty-side.
alktty = { version = "0.8.1", default-features = false, optional = true }
# Pins verified against crates.io (2026-11-04): alktty 0.8.2 (0.1.0's
# 0.8.1 pin refreshed — 0.8.2 is the PTY exit-signal-fidelity fix,
# patch-level, API/wire unchanged; alkssh consumes negative exit
# codes verbatim so the derivation contract needed no change), alkgit
# 0.1.0 — both MIT OR Apache-2.0, both rust_version ≤ our 1.89,
# both over alkcall ^0.10 (shared with our `alkcall = "0.10"` →
# 0.10.1 resolution). alktty's `local` feature stays off — alkssh
# never touches a real backend; the `local` shapes stay alktty-side.
alktty = { version = "0.8.2", default-features = false, optional = true }
alkgit = { version = "0.1.0", default-features = false, optional = true }
# tunnel-only: the route-1 bridge's consumer surface (`TunnelSession::
# open_direct`/`take_halves`, `SubstrateAddr`/`Substrate`). Pin
# re-verified against crates.io at `w3-tunnel-direct` (2026-10-29):
# 0.4.1 is `max_stable_version`/`newest_version`, MIT OR Apache-2.0,
# rust_version 1.88 ≤ our 1.89, alkcall ^0.10.1 pinned exactly
# (single-versioned against our `alkcall = "0.10"`). The `local`
# feature stays off — the dial seam is injected (`DialFn`); the real
# dial/backends belong to the assembly, and the tests hand-roll their
# own dial/dial-fail seams (`tests/tunnel_mapping.rs`).
alktunnels = { version = "0.4.1", default-features = false, optional = true }
# open_direct`/`take_halves`, `SubstrateAddr`/`Substrate`) + the
# forwarded serving surface's establisher shape
# (`register_tunnel_forwarded_openable_with_establisher` +
# `ForwardedGate` — the review-003 W2 adoption, ADR-008 Amendment 2's
# recommended shape). Pin re-verified against crates.io (2026-11-04):
# 0.5.0 is newest_version, additive API only over 0.4.1 (the wire
# delta is empty, the base registration is byte-for-byte), MIT OR
# Apache-2.0, rust_version 1.88 ≤ our 1.89, alkcall ^0.10.1 pinned
# exactly (single-versioned against our `alkcall = "0.10"`). The
# `local` feature stays off — the dial seam is injected (`DialFn`);
# the real dial/backends belong to the assembly, and the tests
# hand-roll their own dial/dial-fail seams (`tests/tunnel_mapping.rs`).
alktunnels = { version = "0.5.0", default-features = false, optional = true }
# session-only: the OQ-44 reserved ttymodes JSON (`TerminalParamsWire.modes`)
# and the exit-chunk / negotiation-error-frame JSON reads in `tty_map.rs`.
# Same pin alktty carries (crate resolution stays single-versioned).
@@ -103,8 +115,8 @@ serde_json = "1"
# composition — the dev-dep `features = ["local"]` unification applies
# to the TEST graph only (feature unification folds `local` into the
# optional alktty dep in test builds; the published lib never enables
# it and `src/` never names a local shape). Same 0.8.1 pin.
alktty = { version = "0.8.1", default-features = false, features = ["local"] }
# it and `src/` never names a local shape). Same 0.8.2 pin.
alktty = { version = "0.8.2", default-features = false, features = ["local"] }
[profile.release]
lto = "thin"
+5 -5
View File
@@ -109,7 +109,7 @@ dependencies = [
[[package]]
name = "alkssh"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"alkcall",
"alktty",
@@ -146,9 +146,9 @@ dependencies = [
[[package]]
name = "alktty"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2265167842b7d3ea09bc235f48450d3dcf22049a2ca71f8155de00acbf97c226"
checksum = "9cab9b0d3d6c1bdef2622799e3b95bb6063720153a124a57f4c60f95f3d42ea8"
dependencies = [
"alkcall",
"async-trait",
@@ -166,9 +166,9 @@ dependencies = [
[[package]]
name = "alkvault"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e032b074f7fea71ea15528994c5b3a6503c082c519045bb3b0519281291918"
checksum = "b2522ded10a506770779e72063422057bb63f1b75457c1742a3362072adfb1c4"
dependencies = [
"aes-gcm",
"base64",
+7 -5
View File
@@ -5,10 +5,11 @@
//! `decrypt_and_decode`'s exact decode stage, not a fork).
//!
//! Invariants: no-panic on arbitrary plaintext shapes (the production
//! plaintext is the vault's `String` — valid UTF-8 by return type; the
//! battery shapes arbitrary bytes into that form via the lossy
//! conversion and drives it through the same `Zeroizing` wrap the
//! adapter seam applies) and arbitrary passphrase shapes (the
//! plaintext is the vault's `Zeroizing<String>` — valid UTF-8 by
//! return type; the battery shapes arbitrary bytes into that form via
//! the lossy conversion and drives it through the same zeroized
//! wrap the vault's `decrypt` return applies, alkssh 0.2.0 seam) and
//! arbitrary passphrase shapes (the
//! underlying decode's passphrase arm — the production chain always
//! passes `None`; the `Some` arm is robustness drive only); every
//! failure is the typed [`KeySourceError::Decode`] class — never a
@@ -16,7 +17,8 @@
//! failure text never carries the plaintext (the assertable half of
//! the Zeroizing posture — the plaintext exists solely inside the
//! call, dropped zeroized; no retention, no static cache — and the
//! typed error's only payload is the decode machinery's message).
//! typed error's only payload is the decode machinery's message
//! class, never the plaintext).
//!
//! The no-echo invariant's assertable form is *the bounded-message
//! pair* (reshaped in-task after the first campaign's finding): the
+1 -1
View File
@@ -202,7 +202,7 @@ impl Default for Limits {
/// The `session` feature's alktty seam handle (`mapping.md` §The
/// seams): what `drive_session[_pre_negotiated]` takes that the
/// assembly composes (alktty 0.8.1's `adapter.rs:205-241` signature).
/// assembly composes (alktty 0.8.2's `adapter.rs:205-241` signature).
/// **Shape decision** (recorded at `w3-seam-config`): a whole-`Option`
/// struct per seam, not flat `Option` fields — each seam is consumed
/// atomically by exactly one seam call, so a struct admits no
+18 -17
View File
@@ -18,9 +18,7 @@
//! `key_version` interpretation, no record-internals access
//! anywhere in the crate; version semantics belong to the vault.
//! - **At-use lifecycle** (decision 2): `vault.decrypt(blob)` →
//! `Zeroizing<String>` wrap at the adapter seam (decision 3 — the
//! known-gap contract; the vault-side improvement is upstream, not
//! here) → one-time
//! the at-use chain's single-use read → one-time
//! [`decode_secret_key`](russh::keys::decode_secret_key) → the
//! decoded `PrivateKey` lives for the dial's lifetime. The vault is
//! needed only at use time: locking the vault mid-session keeps
@@ -72,8 +70,8 @@ pub trait KeySource: Send + Sync + 'static {
///
/// This is the only surface where the dial direction touches
/// secret material: the plaintext exists solely inside this
/// method and is dropped (zeroized from the adapter seam onward)
/// before the decoded key is returned.
/// method and is zeroized by the vault's `Zeroizing<String>` return
/// at its drop, before the decoded key is returned.
async fn load(&self) -> Result<PrivateKey, KeySourceError>;
}
@@ -171,21 +169,23 @@ impl HostKeySource for VaultHostKeySource {
}
}
/// The ADR-005 at-use chain (decision 2): one vault decrypt, the
/// `Zeroizing<String>` seam wrap, one `decode_secret_key`.
/// The ADR-005 at-use chain (decision 2): one vault decrypt, one
/// `decode_secret_key`.
///
/// The plaintext `String` leaves the vault as
/// [`KeySourceError`]-mapped failure or enters the wrap immediately —
/// its only unfenced window is the vault's own `String` return, the
/// known-gap contract of ADR-005 decision 3 (the upstream ask is
/// alkvault's; OQ-07). The decode is the single inner-format
/// conversion (decision 1); a plaintext that is not OpenSSH-PEM key
/// material is a [`KeySourceError::Decode`] refusal.
/// The plaintext leaves the vault as
/// [`KeySourceError`]-mapped failure or as alkvault 0.2's
/// `Zeroizing<String>` return (the pre-wrap unfenced window ADR-005
/// decision 3 recorded is closed upstream — the vault fences the
/// plaintext from its return onward, zeroized on drop, including the
/// vault's own UTF-8 failure path; OQ-07's upstream ask is resolved,
/// amending the ADR's decision 3 note). The decode is the single
/// inner-format conversion (decision 1); a plaintext that is not
/// OpenSSH-PEM key material is a [`KeySourceError::Decode`] refusal.
fn decrypt_and_decode(
vault: &VaultServiceHandle,
blob: &EncryptedData,
) -> Result<PrivateKey, KeySourceError> {
let plaintext = Zeroizing::new(vault.decrypt(blob).map_err(map_vault_error)?);
let plaintext = vault.decrypt(blob).map_err(map_vault_error)?;
decode_key_arm(&plaintext)
}
@@ -199,8 +199,9 @@ fn decrypt_and_decode(
/// not a fork). Not documented API: the vault-facing surface is
/// exactly [`VaultKeySource`] / [`VaultHostKeySource`].
///
/// The `Zeroizing` posture rides the signature: the plaintext arrives
/// already wrapped at the adapter seam (decision 3), the decode reads
/// The `Zeroizing` posture rides the signature: the plaintext
/// arrives already zeroized (alkvault 0.2's return type — decision
/// 3's ownership moved upstream), the decode reads
/// it once, and nothing is retained — `Ok` carries the decoded
/// [`PrivateKey`], `Err` carries only the typed [`KeySourceError`]
/// class (never the plaintext).
+3 -2
View File
@@ -117,8 +117,9 @@ fn fixture_keys_carry_ssh_keygen_recorded_fingerprints() {
///
/// - *Contract stamp:* ADR-005 decision 1 (inner format: unencrypted
/// OpenSSH-PEM in the record).
/// - *Contract stamp:* ADR-005 decision 2 (decrypt-at-use →
/// `Zeroizing` wrap → one-time `decode_secret_key`).
/// - *Contract stamp:* ADR-005 decision 2 (decrypt-at-use → the
/// vault's `Zeroizing<String>` return → one-time
/// `decode_secret_key`).
#[tokio::test]
async fn test_vault_seal_then_key_source_decode_round_trips() {
let alpha = fixture("alpha");