docs(vault): remove drift tracking artifacts, bump vault docs to stable

The vault spec-to-implementation sync is complete. Remove the drift
tracking tools that were only needed during sync:

- Remove the Known Source Drift table from vault/README.md
- Remove 'known drift' / 'current source uses X' prose from Security
  Constraints sections in vault/README.md, encryption.md, and service.md.
  The permanent constraint statements (OsRng for IVs, zeroized drop,
  no unwrap, etc.) are preserved.
- Remove the drift paragraph in encryption.md Key Versioning.
- Remove stale 'to be updated per ADR-025' / 'postcard tests to be
  removed' notes in protocol.md References.
- Bump status: draft -> stable in the frontmatter of all vault docs
  (README, mnemonic-derivation, encryption, service, protocol).
- Update architecture/README.md: vault doc status entries to stable,
  Current State paragraph reflects vault implementation complete (no
  'pending ADR-025/026 refactor' language).
This commit is contained in:
2026-06-23 14:15:13 +00:00
parent b93a85a280
commit 323ee85d40
6 changed files with 28 additions and 70 deletions

View File

@@ -7,7 +7,7 @@ last_updated: 2026-06-23
## Current State ## Current State
**Pre-implementation.** The project has completed a pivot from a three-layer model to an ALPN-as-service model. The greenfield workspace contains only `alknet-vault` (stable — implementation exists, pending ADR-025/026 refactor to drop irpc and remove derive_password) and research/reference material. Foundational ADRs (001026) are in place. ADR-024 resolves the registry mutability question and the `OperationContext.env` type identity crisis by layering the registry by trust boundary. ADR-025 drops irpc from the vault, making it local-only by construction. ADR-026 records the HD-derivation key model as a foundational decision. Review #003 (type/API surface completeness) resolved: `DerivedKey` derive contradiction, `encrypt` prose, return-type divergence, RwLock contradiction, drift table gaps, ADR-022 stale sketches, `Capabilities`/`SessionOverlaySource`/`CallConnection`/`CachedKey` definitions, `CompositeOperationEnv` dispatch contract, `with_local` signature, payload schemas, timeout propagation, and request ID generation. The alknet-core, alknet-call, and alknet-vault crate specs are in draft. **Pre-implementation.** The project has completed a pivot from a three-layer model to an ALPN-as-service model. The greenfield workspace contains only `alknet-vault` (stable — implementation complete and verified, local-only by construction per ADR-025, HD-derivation key model per ADR-026) and research/reference material. Foundational ADRs (001026) are in place. ADR-024 resolves the registry mutability question and the `OperationContext.env` type identity crisis by layering the registry by trust boundary. ADR-025 drops irpc from the vault, making it local-only by construction. ADR-026 records the HD-derivation key model as a foundational decision. Review #003 (type/API surface completeness) resolved: `DerivedKey` derive contradiction, `encrypt` prose, return-type divergence, RwLock contradiction, drift table gaps, ADR-022 stale sketches, `Capabilities`/`SessionOverlaySource`/`CallConnection`/`CachedKey` definitions, `CompositeOperationEnv` dispatch contract, `with_local` signature, payload schemas, timeout propagation, and request ID generation. The alknet-core and alknet-call crate specs are in draft; the alknet-vault crate specs are stable.
**Next step**: Implementation. All open questions are resolved. The specs have passed three review passes (#001 governance/security model, #002 cross-document consistency/two-way-door audit, #003 type/API surface completeness). **Next step**: Implementation. All open questions are resolved. The specs have passed three review passes (#001 governance/security model, #002 cross-document consistency/two-way-door audit, #003 type/API surface completeness).
@@ -25,11 +25,11 @@ last_updated: 2026-06-23
| [crates/call/README.md](crates/call/README.md) | draft | alknet-call crate index | | [crates/call/README.md](crates/call/README.md) | draft | alknet-call crate index |
| [crates/call/call-protocol.md](crates/call/call-protocol.md) | draft | CallAdapter, EventEnvelope framing, stream model, PendingRequestMap, bidirectional calls, streaming subscribe example | | [crates/call/call-protocol.md](crates/call/call-protocol.md) | draft | CallAdapter, EventEnvelope framing, stream model, PendingRequestMap, bidirectional calls, streaming subscribe example |
| [crates/call/operation-registry.md](crates/call/operation-registry.md) | draft | OperationSpec, Handler, OperationRegistry, AccessControl, capability injection, service discovery, irpc integration | | [crates/call/operation-registry.md](crates/call/operation-registry.md) | draft | OperationSpec, Handler, OperationRegistry, AccessControl, capability injection, service discovery, irpc integration |
| [crates/vault/README.md](crates/vault/README.md) | draft | alknet-vault crate index | | [crates/vault/README.md](crates/vault/README.md) | stable | alknet-vault crate index |
| [crates/vault/mnemonic-derivation.md](crates/vault/mnemonic-derivation.md) | draft | BIP39, SLIP-0010, BIP-0032, derivation paths, key types | | [crates/vault/mnemonic-derivation.md](crates/vault/mnemonic-derivation.md) | stable | BIP39, SLIP-0010, BIP-0032, derivation paths, key types |
| [crates/vault/encryption.md](crates/vault/encryption.md) | draft | AES-256-GCM, EncryptedData, key versioning, salt (Phase B reserved) | | [crates/vault/encryption.md](crates/vault/encryption.md) | stable | AES-256-GCM, EncryptedData, key versioning, salt (Phase B reserved) |
| [crates/vault/service.md](crates/vault/service.md) | draft | VaultServiceHandle lifecycle, actor dispatch, cache, error model | | [crates/vault/service.md](crates/vault/service.md) | stable | VaultServiceHandle lifecycle, direct dispatch, cache, error model |
| [crates/vault/protocol.md](crates/vault/protocol.md) | draft | DerivedKey redaction, KeyType, serialization behavior | | [crates/vault/protocol.md](crates/vault/protocol.md) | stable | DerivedKey redaction, KeyType, serialization behavior |
## ADR Table ## ADR Table

View File

@@ -1,5 +1,5 @@
--- ---
status: draft status: stable
last_updated: 2026-06-23 last_updated: 2026-06-23
--- ---
@@ -30,10 +30,10 @@ seed and derived private keys never cross the network.
| Document | Status | Description | | Document | Status | Description |
|----------|--------|-------------| |----------|--------|-------------|
| [mnemonic-derivation.md](mnemonic-derivation.md) | draft | BIP39, SLIP-0010, BIP-0032, derivation paths, key types | | [mnemonic-derivation.md](mnemonic-derivation.md) | stable | BIP39, SLIP-0010, BIP-0032, derivation paths, key types |
| [encryption.md](encryption.md) | draft | AES-256-GCM, EncryptedData, key versioning, HD derivation (ADR-020) | | [encryption.md](encryption.md) | stable | AES-256-GCM, EncryptedData, key versioning, HD derivation (ADR-020) |
| [service.md](service.md) | draft | VaultServiceHandle lifecycle, direct dispatch, cache, error model | | [service.md](service.md) | stable | VaultServiceHandle lifecycle, direct dispatch, cache, error model |
| [protocol.md](protocol.md) | draft | DerivedKey redaction, KeyType, serialization behavior | | [protocol.md](protocol.md) | stable | DerivedKey redaction, KeyType, serialization behavior |
## Applicable ADRs ## Applicable ADRs
@@ -92,17 +92,13 @@ documented here so implementation agents don't miss them. See
[service.md → Security Constraints](service.md#security-constraints) for [service.md → Security Constraints](service.md#security-constraints) for
the full list. the full list.
- **OsRng for IVs**: AES-GCM IVs must use `OsRng`, not `rand::random()`. The - **OsRng for IVs**: AES-GCM IVs must use `OsRng`, not `rand::random()`.
current source uses `rand::random()` — this is a known drift from the
spec and must be corrected during implementation sync.
- **Zeroized drop**: `Seed`, `Mnemonic`, `ExtendedPrivKey`, - **Zeroized drop**: `Seed`, `Mnemonic`, `ExtendedPrivKey`,
`Secp256k1ExtendedPrivKey`, `EncryptionKey`, `CachedKey`, and `Secp256k1ExtendedPrivKey`, `EncryptionKey`, `CachedKey`, and
`DerivedKey` all derive `Zeroize` and `ZeroizeOnDrop`. The cache must `DerivedKey` all derive `Zeroize` and `ZeroizeOnDrop`. The cache must
clear on drop, not just on explicit `lock()`. clear on drop, not just on explicit `lock()`.
- **No `unwrap()` outside tests**: poisoned lock recovery uses - **No `unwrap()` outside tests**: poisoned lock recovery uses
`unwrap_or_else(|e| e.into_inner())` or explicit error propagation. The `unwrap_or_else(|e| e.into_inner())` or explicit error propagation.
current source uses `unwrap()` in `VaultServiceHandle` methods — this
is a known drift and must be corrected.
- **DerivedKey redaction in serialization**: `DerivedKey` serializes the - **DerivedKey redaction in serialization**: `DerivedKey` serializes the
`private_key` as `"[REDACTED]"` in all formats (ADR-025 dropped the `private_key` as `"[REDACTED]"` in all formats (ADR-025 dropped the
postcard/remote path that previously preserved bytes in binary formats). postcard/remote path that previously preserved bytes in binary formats).
@@ -111,26 +107,6 @@ the full list.
not the primary control — the primary control is that `DerivedKey` never not the primary control — the primary control is that `DerivedKey` never
crosses the call protocol wire (ADR-014). crosses the call protocol wire (ADR-014).
## Known Source Drift
The vault crate carries over source from the POC. The following items are
known divergences between the current source and the spec. All must be
corrected during implementation sync. This table is the single source of
truth for drift tracking — if an item is fixed in source, update this table.
| # | Item | Current source behavior | Target behavior (per spec) | Source location | Spec reference |
|---|------|------------------------|-----------------------------|-----------------|----------------|
| 1 | IV generation | `rand::random()` | `OsRng` (CSPRNG) | `encryption.rs` L133 | [encryption.md → Security Constraints](encryption.md#security-constraints), [service.md → Security Constraints](service.md#security-constraints) |
| 2 | RwLock `unwrap()` | `unwrap()` on every `RwLock` acquisition (L142, 161, 182, 191, 196, 227, 264, 307, 340, 367) | `unwrap_or_else(\|e\| e.into_inner())` for poisoned lock recovery | `service.rs` (see line numbers) | [service.md → Security Constraints](service.md#security-constraints) |
| 3 | `CURRENT_KEY_VERSION` | `1` (HD-derived, but v1 is reserved for TS PBKDF2 legacy per ADR-020) | `2` (HD-derived, per ADR-020) | `encryption.rs` | [encryption.md → Key Versioning](encryption.md#key-versioning), [ADR-020](../../decisions/020-hd-derivation-for-encryption-keys.md) |
| 4 | irpc dependency | `VaultProtocol` enum with `#[rpc_requests]`, `VaultServiceActor`, `Client<VaultProtocol>`, irpc/postcard deps | Remove entirely — direct method calls on `VaultServiceHandle` (ADR-025) | `protocol.rs`, `service.rs`, `Cargo.toml` | [ADR-025](../../decisions/025-vault-local-only-dispatch.md) |
| 5 | `DerivedKey` dual serialization | JSON redacts, postcard preserves bytes | Always redact on serialize; reject `"[REDACTED]"` on deserialize with error (ADR-025, resolves W8) | `protocol.rs` | [protocol.md → Serialization Redaction](protocol.md#serialization-redaction), [ADR-025](../../decisions/025-vault-local-only-dispatch.md) |
| 6 | `HashMap::clear` zeroization | `KeyCache::clear()` removes entries and relies on `CachedKey`'s `Drop` impl for zeroization | Verify `HashMap::clear()` actually drops values (it does, but worth a test) | `cache.rs` | [service.md → Security Constraints](service.md#security-constraints) |
| 7 | `derive_password` / `site_password_path` | `derive_password`, `derive_password_string`, `site_password_path` methods exist | Remove entirely — password-manager pattern not relevant to RPC system's vault (ADR-025, resolves C9) | `service.rs`, `mnemonic-derivation.rs` | [ADR-025](../../decisions/025-vault-local-only-dispatch.md) |
| 8 | `unlock_new` return type | Returns `String` (not zeroized on drop) | Return `Zeroizing<String>` — the mnemonic is the root of trust and must not linger in freed memory (resolves W7) | `service.rs` | [service.md → unlock_new](service.md#unlock_newword_count--phrase) |
| 9 | `key_version` ignored in encrypt/decrypt | `encrypt`/`decrypt` always derive at `PATHS::ENCRYPTION` regardless of `key_version` | Derive at `encryption_path_for_version(key_version)` — encrypt stamps the passed version, decrypt selects the key by the blob's version (ADR-021) | `service.rs` | [service.md → encrypt](service.md#encryptplaintext-key_version--encrypteddata), [ADR-021](../../decisions/021-key-rotation-via-version-indexed-paths.md) |
| 10 | `rotate` not implemented | No `rotate` method exists | Implement `rotate(encrypted, to_version)` — decrypt with old version's key, re-encrypt with new version's key (ADR-021) | `service.rs` | [service.md → rotate](service.md#rotateencrypted-to_version--encrypteddata), [ADR-021](../../decisions/021-key-rotation-via-version-indexed-paths.md) |
## Public API ## Public API
The vault re-exports its primary types from the crate root: The vault re-exports its primary types from the crate root:

View File

@@ -1,5 +1,5 @@
--- ---
status: draft status: stable
last_updated: 2026-06-23 last_updated: 2026-06-23
--- ---
@@ -219,12 +219,6 @@ Rotation decrypts with the old version's key and re-encrypts with the new
version's key. No new mnemonic needed — the same seed produces all version version's key. No new mnemonic needed — the same seed produces all version
keys via different paths. See ADR-021 for the full mechanism. keys via different paths. See ADR-021 for the full mechanism.
**The current source uses `CURRENT_KEY_VERSION = 1` with HD derivation and
does not implement version-indexed paths or `rotate`.** These are drift
items to be corrected during implementation sync. See ADR-020 (version
bump to 2) and ADR-021 (rotation mechanism). See the [Known Source
Drift](README.md#known-source-drift) table in the vault README.
## Errors ## Errors
```rust ```rust
@@ -281,12 +275,10 @@ These are security-critical implementation requirements.
- **OsRng for IVs**: The IV must be generated with `OsRng` (or an - **OsRng for IVs**: The IV must be generated with `OsRng` (or an
equivalent CSPRNG), never `rand::random()`. IV reuse under the same key equivalent CSPRNG), never `rand::random()`. IV reuse under the same key
is catastrophic for GCM — it breaks authenticity and creates a is catastrophic for GCM — it breaks authenticity and creates a
two-time-pad on the plaintext. **The current source uses two-time-pad on the plaintext. `rand::random()` uses the thread-local RNG
`rand::random()` for IV generation (`encryption.rs` line 133) — this is a which may not be a CSPRNG on all platforms; `OsRng` reads from the
known drift from the spec and must be corrected during implementation operating system's entropy source and is the correct choice for
sync.** `rand::random()` uses the thread-local RNG which may not be a cryptographic nonces.
CSPRNG on all platforms; `OsRng` reads from the operating system's
entropy source and is the correct choice for cryptographic nonces.
- **Zeroized drop**: `EncryptionKey` derives `Zeroize` and - **Zeroized drop**: `EncryptionKey` derives `Zeroize` and
`ZeroizeOnDrop`. The key bytes are zeroized before deallocation. Do not `ZeroizeOnDrop`. The key bytes are zeroized before deallocation. Do not
store key material in types that don't zeroize. store key material in types that don't zeroize.

View File

@@ -1,5 +1,5 @@
--- ---
status: draft status: stable
last_updated: 2026-06-23 last_updated: 2026-06-23
--- ---

View File

@@ -1,5 +1,5 @@
--- ---
status: draft status: stable
last_updated: 2026-06-23 last_updated: 2026-06-23
--- ---
@@ -236,9 +236,8 @@ ADR-025 and [open-questions.md](../../open-questions.md).
## References ## References
- Implementation: `crates/alknet-vault/src/protocol.rs` (to be updated - Implementation: `crates/alknet-vault/src/protocol.rs`
per ADR-025 — remove `VaultProtocol` enum and irpc usage)
- Tests: `crates/alknet-vault/src/protocol.rs` (unit tests for redaction - Tests: `crates/alknet-vault/src/protocol.rs` (unit tests for redaction
and zeroize behavior; postcard tests to be removed) and zeroize behavior)
- [service.md](service.md) — `VaultServiceHandle` runtime API - [service.md](service.md) — `VaultServiceHandle` runtime API
- [mnemonic-derivation.md](mnemonic-derivation.md) — what `KeyType` means - [mnemonic-derivation.md](mnemonic-derivation.md) — what `KeyType` means

View File

@@ -1,5 +1,5 @@
--- ---
status: draft status: stable
last_updated: 2026-06-23 last_updated: 2026-06-23
--- ---
@@ -356,29 +356,20 @@ don't miss them.
- **OsRng for IVs**: AES-GCM IVs and any cryptographic nonces must use - **OsRng for IVs**: AES-GCM IVs and any cryptographic nonces must use
`OsRng` (or equivalent CSPRNG), not `rand::random()`. IV reuse under the `OsRng` (or equivalent CSPRNG), not `rand::random()`. IV reuse under the
same key is catastrophic for GCM (authenticity breaks, two-time-pad on same key is catastrophic for GCM (authenticity breaks, two-time-pad on
plaintext). **The current source uses `rand::random()` for IV generation plaintext).
in `encryption::encrypt()` — this is a known drift and must be corrected
during implementation sync.**
- **Zeroized drop**: `Seed`, `Mnemonic`, `CachedKey`, `EncryptionKey`, - **Zeroized drop**: `Seed`, `Mnemonic`, `CachedKey`, `EncryptionKey`,
`ExtendedPrivKey`, `Secp256k1ExtendedPrivKey`, and `DerivedKey` all `ExtendedPrivKey`, `Secp256k1ExtendedPrivKey`, and `DerivedKey` all
derive `Zeroize` and `ZeroizeOnDrop`. The cache must clear on drop, not derive `Zeroize` and `ZeroizeOnDrop`. The cache must clear on drop, not
just on explicit `lock()`. **The current `KeyCache::clear()` removes just on explicit `lock()`.
entries but relies on `CachedKey`'s `Drop` impl for zeroization —
verify that `HashMap::clear()` actually drops the values (it does, but
this is worth a test).**
- **No `unwrap()` or `expect()` outside tests**: poisoned lock recovery - **No `unwrap()` or `expect()` outside tests**: poisoned lock recovery
uses `unwrap_or_else(|e| e.into_inner())` or explicit error propagation. uses `unwrap_or_else(|e| e.into_inner())` or explicit error propagation.
A panic in one vault operation must not brick the vault for all other A panic in one vault operation must not brick the vault for all other
operations. **The current source uses `unwrap()` on every `RwLock` operations. A poisoned lock should be recovered with
acquisition in `VaultServiceHandle` (lines 142, 161, 182, 191, 196, 227, `unwrap_or_else(|e| e.into_inner())`, not panicked.
264, 307, 340, 367) — this is a known drift and must be corrected. A
poisoned lock should be recovered with `unwrap_or_else(|e|
e.into_inner())`, not panicked.**
- **`DerivedKey` is move-only, not `Clone`**: `DerivedKey` does not derive - **`DerivedKey` is move-only, not `Clone`**: `DerivedKey` does not derive
`Clone`. It is move-only — consumers receive it by value and zeroize it `Clone`. It is move-only — consumers receive it by value and zeroize it
when done (handled by `#[zeroize(drop)]`). This prevents accidental when done (handled by `#[zeroize(drop)]`). This prevents accidental
duplication of secret material. **The current source does not derive duplication of secret material.
`Clone` on `DerivedKey` — this is correct.**
- **Cache eviction zeroizes**: when the cache evicts an entry (LRU or - **Cache eviction zeroizes**: when the cache evicts an entry (LRU or
TTL), the `CachedKey` is dropped, which triggers `ZeroizeOnDrop`. Do not TTL), the `CachedKey` is dropped, which triggers `ZeroizeOnDrop`. Do not
replace `CachedKey` with a type that doesn't zeroize. replace `CachedKey` with a type that doesn't zeroize.