tasks: decompose vault, core, call crates into 28 atomic implementation tasks

Break down the three initial crates (alknet-vault, alknet-core, alknet-call)
into dependency-ordered task files for implementation agents.

Structure:
- tasks/vault/ (10 tasks) — drift fixes from ADR-025/026 refactor, review,
  spec sync. Vault is independent and can run fully in parallel with core/call.
- tasks/core/ (6 tasks) — crate init, core types, config, auth, endpoint,
  review. Core is foundational; call depends on it.
- tasks/call/ (12 tasks) — split into registry/ and protocol/ topic subdirs
  reflecting the two subsystems. CallAdapter is the merge point.

Key decisions:
- Drifts 3+9+10 grouped as one task (key-versioning-rotation) — the complete
  ADR-021 rotation feature that doesn't compile in pieces
- Reviews injected at end of each crate phase (vault, core, call)
- Vault spec-sync task removes the drift table and bumps doc status to stable
- ACME deferred in core/endpoint (noted as TODO; X509 manual certs for now)
- OperationEnv kept as a trait (load-bearing for ADR-024 layering)

Validated: 28 tasks, no cycles, 11 generations of parallel work.
Critical path runs through call (11 tasks). Vault completes by generation 4.
6 high-risk tasks identified (21%): irpc-removal, endpoint, operation-context,
operation-env, call-adapter, abort-cascade.
This commit is contained in:
2026-06-23 12:41:47 +00:00
parent 2e34590522
commit 098fd8b9b9
28 changed files with 4271 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
---
id: vault/key-versioning-rotation
name: Implement version-indexed encryption key paths, bump CURRENT_KEY_VERSION to 2, and add rotate method
status: pending
depends_on: [vault/irpc-removal]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Fix drift items #3, #9, and #10 as one coherent feature: the version-indexed
key rotation mechanism from ADR-021. These three drifts are tightly coupled —
`CURRENT_KEY_VERSION = 2` (drift #3), version-aware `encrypt`/`decrypt` via
`encryption_path_for_version` (drift #9), and the `rotate` method (drift #10)
form the complete key rotation feature. Splitting them would produce tasks that
don't compile independently.
### Drift #3: Bump CURRENT_KEY_VERSION
Current: `CURRENT_KEY_VERSION = 1` (but the key is HD-derived, and v1 is
reserved for the TypeScript PBKDF2 legacy per ADR-020).
Target: `CURRENT_KEY_VERSION = 2` (HD-derived, per ADR-020).
Version semantics:
- v1: TypeScript predecessor's PBKDF2-encrypted data — the vault **cannot**
decrypt it (different key derivation). Migration is a one-time re-encryption.
- v2: HD-derived at `m/74'/2'/0'/0'` (PATHS::ENCRYPTION) — current.
- v3+: `m/74'/2'/0'/1'`, `m/74'/2'/0'/2'`, etc. — future rotation versions.
### Drift #9: Version-aware encrypt/decrypt
Current: `encrypt`/`decrypt` always derive at `PATHS::ENCRYPTION` regardless of
the `key_version` parameter.
Target:
- `encrypt(plaintext, key_version)`: derive the encryption key at
`encryption_path_for_version(key_version)`, stamp the same `key_version` on
the resulting `EncryptedData`.
- `decrypt(encrypted)`: derive the key at
`encryption_path_for_version(encrypted.key_version)` — the blob carries its
own version, and each version maps to a distinct derivation path.
This requires:
1. `encryption_path_for_version(version: u32) -> Result<String, DerivationError>`
already exists in `derivation.rs` — verify it returns `InvalidPath` for
`version < 2` (v1 is TS legacy, v0 is meaningless).
2. `derive_encryption_key_for_version(version: u32) -> Result<DerivedKey, VaultServiceError>`
— a new method on `VaultServiceHandle` that maps version → path → derive.
Cached by path (same cache as `derive_encryption_key`).
3. `encrypt` and `decrypt` use `derive_encryption_key_for_version` instead of
deriving at the fixed `PATHS::ENCRYPTION` path.
### Drift #10: Implement rotate
Current: no `rotate` method exists.
Target:
```rust
pub fn rotate(&self, encrypted: &EncryptedData, to_version: u32) -> Result<EncryptedData, VaultServiceError>;
```
Decrypts with the old version's key (from `encrypted.key_version`), re-encrypts
with the new version's key (`to_version`). Returns the new `EncryptedData`
the caller replaces the blob in storage. No new mnemonic needed; the same seed
produces all version keys via different derivation paths (ADR-021).
### Implementation notes
- `derive_encryption_key(path)` (the path-based API) remains as-is for deriving
at arbitrary paths. `derive_encryption_key_for_version(version)` is the
version-aware API used by `encrypt`/`decrypt`. Both share the same cache
(keyed by derivation path).
- `encrypt` and `decrypt` extract the `EncryptionKey` from the `DerivedKey` via
`EncryptionKey::from_derived_bytes` (see encryption.md).
- `encryption_path_for_version` returns `InvalidPath` for `version < 2`.
`derive_encryption_key_for_version` propagates this as
`VaultServiceError::InvalidPath`.
### Scope
This task touches `encryption.rs` (CURRENT_KEY_VERSION), `service.rs` (encrypt,
decrypt, rotate, derive_encryption_key_for_version), and possibly `derivation.rs`
(verify `encryption_path_for_version`). It depends on the irpc removal task
(drift #4) because both modify `service.rs`.
## Acceptance Criteria
- [ ] `CURRENT_KEY_VERSION` is `2` in `encryption.rs`
- [ ] `derive_encryption_key_for_version(version)` method added to `VaultServiceHandle`
- [ ] `derive_encryption_key_for_version` returns `InvalidPath` for `version < 2`
- [ ] `encrypt(plaintext, key_version)` derives at `encryption_path_for_version(key_version)`
- [ ] `encrypt` stamps the passed `key_version` on the resulting `EncryptedData`
- [ ] `decrypt(encrypted)` derives at `encryption_path_for_version(encrypted.key_version)`
- [ ] `rotate(encrypted, to_version)` method implemented: decrypt old, re-encrypt new
- [ ] `rotate` returns `EncryptedData` with `key_version = to_version`
- [ ] Unit test: encrypt at v2, decrypt at v2 — round-trip succeeds
- [ ] Unit test: encrypt at v2, rotate to v3, decrypt at v3 — round-trip succeeds
- [ ] Unit test: decrypt v2 blob after rotation — old key still derivable (partial rotation safe)
- [ ] Unit test: `derive_encryption_key_for_version(1)` returns `InvalidPath`
- [ ] Unit test: `derive_encryption_key_for_version(0)` returns `InvalidPath`
- [ ] `cargo test` succeeds
- [ ] `cargo clippy` succeeds with no warnings
## References
- docs/architecture/crates/vault/README.md — Known Source Drift table items #3, #9, #10
- docs/architecture/crates/vault/encryption.md — Key Versioning, Rotation, EncryptionKey
- docs/architecture/crates/vault/service.md — encrypt, decrypt, rotate, derive_encryption_key_for_version
- docs/architecture/crates/vault/mnemonic-derivation.md — encryption_path_for_version, PATHS
- docs/architecture/decisions/020-hd-derivation-for-encryption-keys.md — ADR-020
- docs/architecture/decisions/021-key-rotation-via-version-indexed-paths.md — ADR-021
## Notes
> These three drifts are one feature: version-indexed key rotation (ADR-021).
> Splitting them would produce tasks that don't compile independently —
> bumping the version without version-aware encrypt/decrypt would make v2
> blobs undecryptable, and rotate without version-aware encrypt/decrypt has no
> keys to work with. Depends on irpc removal because both modify `service.rs`.
## Summary
> To be filled on completion