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,140 @@
---
id: vault/derivedkey-serialization
name: Implement always-redact DerivedKey serialization and reject redacted payloads on deserialize
status: pending
depends_on: [vault/irpc-removal]
scope: narrow
risk: medium
impact: component
level: implementation
---
## Description
Fix drift item #5: `DerivedKey` currently has dual serialization behavior — JSON
redacts the private key, but postcard (the binary format used by irpc) preserves
the raw bytes. ADR-025 dropped the postcard/remote path, so `DerivedKey` should
**always** redact on serialize and reject `"[REDACTED]"` on deserialize with an
explicit error.
### Current state
`protocol.rs` has `DerivedKey` with `#[derive(Serialize, Deserialize)]` (or
similar) that produces JSON redaction for JSON but preserves bytes in postcard.
The postcard tests in the test suite verify the binary round-trip.
### Target state
Per `docs/architecture/crates/vault/protocol.md` → Serialization Redaction:
`DerivedKey` must **not** derive `Deserialize` via `#[derive]`. It needs custom
`Serialize` and `Deserialize` impls:
**Custom Serialize** — always redacts `private_key`:
```rust
impl serde::Serialize for DerivedKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
use serde::SerializeStruct;
let mut s = serializer.serialize_struct("DerivedKey", 3)?;
s.serialize_field("key_type", &self.key_type)?;
s.serialize_field("private_key", "[REDACTED]")?;
s.serialize_field("public_key", &self.public_key)?;
s.end()
}
}
```
**Custom Deserialize** — rejects `"[REDACTED]"` with an explicit error:
```rust
impl<'de> serde::Deserialize<'de> for DerivedKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> {
#[derive(serde::Deserialize)]
struct DerivedKeyHelper {
key_type: KeyType,
private_key: Vec<u8>,
public_key: Vec<u8>,
}
let helper = DerivedKeyHelper::deserialize(deserializer)?;
if helper.private_key == b"[REDACTED]" {
return Err(serde::de::Error::custom(
"DerivedKey.private_key is \"[REDACTED]\" — redacted payloads \
cannot be deserialized. JSON round-tripping a DerivedKey is \
not supported (the private key is gone)."
));
}
Ok(DerivedKey {
key_type: helper.key_type,
private_key: helper.private_key,
public_key: helper.public_key,
})
}
}
```
**Debug impl** — also redacts:
```rust
impl fmt::Debug for DerivedKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DerivedKey")
.field("key_type", &self.key_type)
.field("private_key", &"[REDACTED]")
.field("public_key", &self.public_key)
.finish()
}
}
```
### Remove postcard tests
The postcard round-trip tests (which verified binary format preserved private
key bytes) are removed — ADR-025 dropped that path. The `postcard`
dev-dependency was removed in the irpc removal task (drift #4).
### Why custom impls instead of derives
A derived `Deserialize` would generate a default impl that conflicts with the
manual one, and would only fail incidentally (serde type mismatch: string vs
sequence), not with the explicit redaction-rejection error the spec requires.
The custom impl is required for the explicit error message.
### Scope
This task touches `protocol.rs` (the `DerivedKey` type, its serde impls, Debug
impl) and test files (remove postcard tests, add redaction tests). It depends on
the irpc removal task (drift #4) because both modify `protocol.rs`.
## Acceptance Criteria
- [ ] `DerivedKey` does not derive `Serialize` or `Deserialize` via `#[derive]`
- [ ] Custom `Serialize` impl always redacts `private_key` as `"[REDACTED]"`
- [ ] Custom `Deserialize` impl rejects `private_key == b"[REDACTED]"` with explicit error
- [ ] Custom `Debug` impl redacts `private_key` as `"[REDACTED]"`
- [ ] Postcard round-trip tests removed
- [ ] Unit test: JSON serialize produces `"[REDACTED]"` for `private_key`
- [ ] Unit test: JSON deserialize of a redacted payload returns an error (not a corrupted key)
- [ ] Unit test: `{:?}` on `DerivedKey` does not contain private key bytes
- [ ] `cargo test` succeeds
- [ ] `cargo clippy` succeeds with no warnings
## References
- docs/architecture/crates/vault/README.md — Known Source Drift table item #5
- docs/architecture/crates/vault/protocol.md — Serialization Redaction, Debug redaction
- docs/architecture/decisions/025-vault-local-only-dispatch.md — ADR-025 (resolves W8)
- docs/architecture/decisions/014-secret-material-flow-and-capability-injection.md — ADR-014
## Notes
> The redaction is defense-in-depth for logging safety, not the primary control
> — the primary control is that `DerivedKey` never crosses the call protocol
> wire (ADR-014). ADR-025 dropped the postcard/remote path that previously
> preserved bytes in binary formats. The custom Deserialize impl is required
> because `#[derive(Deserialize)]` would conflict and not produce the explicit
> redaction-rejection error. Depends on irpc removal because both modify
> `protocol.rs`.
## Summary
> To be filled on completion