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:
140
tasks/vault/derivedkey-serialization.md
Normal file
140
tasks/vault/derivedkey-serialization.md
Normal 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
|
||||
Reference in New Issue
Block a user