docs(architecture): resolve review #002 remaining Tier 4 findings
Add ADR-026 (vault key model — HD derivation) recording the foundational HD-derivation decision, 74' coin type reservation, SLIP-0010/Ed25519 default, secp256k1 feature-gating, and AES-256-GCM cipher choice. These were previously inline rationale with no ADR (W9). Extend ADR-018 with an explicit EncryptedData wire format lock — fields, encoding, and semantics are frozen; no removal without a format-version migration (W10). Resolve the remaining guard clauses and spec decisions: - W2: Capabilities must be immutable after construction (no interior mutability). Makes the Arc vs deep-copy clone semantics genuinely two-way. - W5: Published to_* specs are compatibility contracts — best-effort mappings are two-way before first publication, one-way after. Version generated specs. - W6: Salt field clarification — v2 salt is permanently unused; a future KDF is a different derivation family, not a version-indexed path; the field saves a wire-format change only. - W7: unlock_new returns Zeroizing<String> — the mnemonic is the root of trust and must not linger in freed memory. - W17: OQ-09 WASM — server-side dispatch door is honestly closed (Connection is concrete, tokio-bound), not implicitly preserved. - W18: OQ-10 git — composability fork (raw smart protocol vs call-protocol projection) is a separate decision from ERC721 scope. - W20: from_openapi must prefix imported error codes (HTTP_404) to avoid collision with protocol-level codes (NOT_FOUND). Normative rule, not naming convention. - W21: ScopedOperationEnv field is private — construction via new()/ empty(), query via allows(). Makes the future subgraph refactor non-breaking. - C13: Connection::set_identity — the endpoint does not read identity() after handle() returns (Connection is moved into the spawned task). Observability is handler-side logging. Simplest honest answer. - W1: OperationAdapter trait is async, returns Vec<HandlerRegistration>. from_call requires async discovery; ADR-022 changed the return type. - W11: CompositionAuthority::as_identity() defined — constructs a synthetic Identity (label as id, scopes, resources) not resolvable via IdentityProvider. Second Identity construction path, acknowledged. - W14: SecretKey is iroh::SecretKey (Ed25519) — consistent with the endpoint's iroh dependency. - W19: Grandchild abort propagation is inherit-by-default (option a) — invoke() with no explicit policy inherits parent's policy. ContinueRunning auto-propagates to grandchildren unless explicitly overridden.
This commit is contained in:
@@ -104,7 +104,49 @@ The vault defines its own types and does not share types with alknet-core:
|
||||
- `EncryptedData` is the vault's encrypted blob format. It is shared with
|
||||
`alknet-storage` (a future crate) by type-level agreement, not by a crate
|
||||
dependency — both crates must agree on the serialization format (see
|
||||
[encryption.md](../crates/vault/encryption.md)).
|
||||
[encryption.md](../crates/vault/encryption.md)). The format is **frozen**
|
||||
(see Decision below).
|
||||
|
||||
## `EncryptedData` Wire Format Lock
|
||||
|
||||
The `EncryptedData` struct is a **stable wire format** shared with
|
||||
`alknet-storage` (a future crate) and the TypeScript consumer
|
||||
(`@alkdev/storage`) by type-level agreement, not by a crate dependency.
|
||||
Both crates and the TypeScript consumer must agree on the serialization
|
||||
format. The format is now explicitly **frozen**:
|
||||
|
||||
```rust
|
||||
pub struct EncryptedData {
|
||||
pub key_version: u32, // rotation tracking
|
||||
pub salt: String, // base64, 32 bytes — unused in v2 (wire-format compat)
|
||||
pub iv: String, // base64, 12 bytes — AES-GCM nonce
|
||||
pub data: String, // base64 — ciphertext + auth tag
|
||||
}
|
||||
```
|
||||
|
||||
The frozen compatibility surface:
|
||||
|
||||
- **Fields**: `key_version`, `salt`, `iv`, `data` — no fields may be
|
||||
removed or renamed. New fields may be added only if they are optional
|
||||
(default on deserialization) and do not change the meaning of existing
|
||||
fields.
|
||||
- **Encoding**: all binary fields are base64-encoded as strings for JSON
|
||||
serialization. This is the cross-language wire format.
|
||||
- **Field semantics**: `key_version` selects the derivation path
|
||||
(ADR-021). `salt` is unused in v2 but is part of the frozen format —
|
||||
it cannot be removed without a format-version migration (a future KDF
|
||||
in v3 would use the salt for *new* data, not retroactively for v2 data
|
||||
— see ADR-020, W6). `iv` is the 12-byte GCM nonce. `data` is the
|
||||
ciphertext with the GCM auth tag appended.
|
||||
|
||||
**Why this needs an explicit lock**: the "type-level agreement, not a
|
||||
crate dependency" approach means there is no compiler enforcement of the
|
||||
format across crates. The stability contract existed only in prose. An
|
||||
implementer modifying `EncryptedData` (e.g., removing the unused `salt`
|
||||
field) would find no ADR saying "this format is frozen." This decision
|
||||
makes the freeze explicit and enforceable by review.
|
||||
|
||||
**This resolves review #002 W10.**
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
Reference in New Issue
Block a user