docs(architecture): add ADR-023, resolve OQ-24 — operation error schemas
ADR-023 adds error_schemas to OperationSpec so operations can declare their domain-level failure modes (FILE_NOT_FOUND, RATE_LIMITED, etc.) distinct from protocol-level codes (NOT_FOUND, FORBIDDEN, etc.). The call.error payload gains an optional 'details' field carrying the typed error payload conforming to the declared schema. from_openapi/to_openapi map OpenAPI response status codes to/from ErrorDefinitions, making the adapter contract from ADR-017 faithful on the error axis. Also fixes W2 (KeyVersionMismatch stale comment in encryption.md — ADR-021 implements rotation without this variant) and W4 (derive_encryption_key_for_version missing from service.md method list). Spec updates: operation-registry.md (OperationSpec, ErrorDefinition, Handler error mapping, services/schema), call-protocol.md (call.error payload, CallError, ResponseEnvelope), README.md, overview.md, open-questions.md (OQ-24), call/README.md, encryption.md, service.md.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-19
|
||||
last_updated: 2026-06-20
|
||||
---
|
||||
|
||||
# Encryption
|
||||
@@ -194,7 +194,7 @@ pub enum EncryptionError {
|
||||
Encryption(String), // encryption failed
|
||||
Decryption(String), // decryption failed (wrong key, tampered data, bad UTF-8)
|
||||
Decoding(String), // base64 decoding failed
|
||||
KeyVersionMismatch { expected: u32, actual: u32 }, // reserved for future rotation (OQ-22)
|
||||
KeyVersionMismatch { expected: u32, actual: u32 }, // unused — see note below
|
||||
}
|
||||
```
|
||||
|
||||
@@ -202,12 +202,17 @@ Decryption failures are intentionally generic — they don't distinguish
|
||||
"wrong key" from "tampered data" from "corrupted storage" to avoid
|
||||
leaking information to an attacker.
|
||||
|
||||
`KeyVersionMismatch` is **defined but unused in v2** — neither `encrypt()`
|
||||
nor `decrypt()` returns it. It is reserved for future key rotation
|
||||
enforcement (OQ-22), where the vault may enforce version matching before
|
||||
decrypting. In v2, the `key_version` is stamped onto `EncryptedData` and
|
||||
`EncryptionKey` for forward compatibility but does not gate decryption. An
|
||||
implementer should not expect this variant to fire in v2.
|
||||
`KeyVersionMismatch` is **defined but unused.** ADR-021 implements key
|
||||
rotation via version-indexed derivation paths — `decrypt` derives the key
|
||||
at the path indicated by `encrypted.key_version`, so there is no
|
||||
version-mismatch to detect at the error level (every blob carries its own
|
||||
version, and every version has a derivable key). This variant predates
|
||||
ADR-021's rotation mechanism and is retained in the enum for source
|
||||
compatibility but is not emitted by any code path in v2. An implementer
|
||||
should not wire it up or expect it to fire. If a future use case requires
|
||||
enforcing version constraints (e.g., "refuse to decrypt blobs older than
|
||||
v3"), this variant could be repurposed — but that would be a new decision,
|
||||
not part of ADR-021's rotation scheme.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-19
|
||||
last_updated: 2026-06-20
|
||||
---
|
||||
|
||||
# Service
|
||||
@@ -126,6 +126,23 @@ Derive an AES-256-GCM encryption key at the given path. Same cache
|
||||
behavior as `derive_ed25519`. Returns a `DerivedKey` with
|
||||
`KeyType::Aes256Gcm`.
|
||||
|
||||
### derive_encryption_key_for_version(version) → EncryptionKey
|
||||
|
||||
```rust
|
||||
pub fn derive_encryption_key_for_version(&self, version: u32) -> Result<EncryptionKey, VaultServiceError>;
|
||||
```
|
||||
|
||||
Derive the encryption key for a specific key version. Maps the version to
|
||||
its derivation path via `encryption_path_for_version(version)` (ADR-021):
|
||||
v2 → `m/74'/2'/0'/0'`, v3 → `m/74'/2'/0'/1'`, etc. Cached by path. This is
|
||||
the version-aware method that `decrypt` uses to select the correct key for
|
||||
each blob — see [encryption.md](encryption.md) and ADR-021.
|
||||
|
||||
`derive_encryption_key(path)` (above) remains as the path-based API for
|
||||
deriving at arbitrary paths. `derive_encryption_key_for_version(version)`
|
||||
is the version-aware API used by `encrypt` and `decrypt`. The two share
|
||||
the same cache (keyed by derivation path).
|
||||
|
||||
### derive_ethereum_key(path) → DerivedKey (feature-gated)
|
||||
|
||||
```rust
|
||||
@@ -173,10 +190,10 @@ pub fn decrypt(&self, encrypted: &EncryptedData) -> Result<String, VaultServiceE
|
||||
```
|
||||
|
||||
Decrypt an `EncryptedData` blob. Derives (and caches) the encryption key
|
||||
at the version-indexed path indicated by `encrypted.key_version` (ADR-021).
|
||||
Each version maps to a distinct path (`m/74'/2'/0'/{version-2}'`), so old
|
||||
and new keys can coexist during partial rotation. See
|
||||
[encryption.md](encryption.md).
|
||||
at the version-indexed path indicated by `encrypted.key_version` via
|
||||
`derive_encryption_key_for_version` (ADR-021). Each version maps to a
|
||||
distinct path (`m/74'/2'/0'/{version-2}'`), so old and new keys can
|
||||
coexist during partial rotation. See [encryption.md](encryption.md).
|
||||
|
||||
### rotate(encrypted, to_version) → EncryptedData
|
||||
|
||||
|
||||
Reference in New Issue
Block a user