docs(architecture): resolve review #003 — type/API surface completeness
Review #003 found 11 critical, 14 warning, and 6 suggestion findings after reviews #001 (governance/security) and #002 (cross-document consistency/two-way-door audit) were resolved. The theme: types and APIs that were *referenced* but never *defined*, and stale ADR sketches that didn't match the now-updated spec docs. Critical fixes (11): - C1: DerivedKey #[derive(Deserialize)] contradicted the custom Deserialize that rejects "[REDACTED]" — dropped the derive, added explicit manual Serialize/Deserialize impls (protocol.md). - C2: encrypt prose said "derived at PATHS::ENCRYPTION" but the signature takes key_version — updated to encryption_path_for_version (service.md). - C3: derive_encryption_key returned DerivedKey, derive_encryption_key _for_version returned EncryptionKey (same cache) — unified on DerivedKey, defined CachedKey (service.md). - C4: tokio vs std::sync::RwLock contradiction — specified std::sync::RwLock, dropped tokio from vault deps (ADR-018, ADR-025, service.md). - C5: Missing drift rows in vault README — added #9 (key_version ignored) and #10 (rotate not implemented). - C6: ADR-022 build_root_context and invoke() sketches omitted abort_policy (9 fields vs 10) — added the field to both sketches. - C7: Capabilities type referenced 20+ times, never defined — added struct definition to core-types.md with Clone+Send+Sync, Zeroize, sealed builder API, immutability guard. - C8: SessionOverlaySource on CallAdapter but never defined, crate violation (alknet-call can't depend on alknet-agent) — defined the trait in alknet-call (call-protocol.md), matching the IdentityProvider pattern. - C9: CompositeOperationEnv dispatch fall-through was "a two-way door" — added contains() to OperationEnv trait, made the composite probe before dispatching, eliminating the sentinel ambiguity. - C10: No API for Layer 2 (connection overlay) registration, CallConnection undefined — defined CallConnection struct + register_imported() API (call-protocol.md). - C11: with_local signature diverged between two examples (4 args vs 5) — added capabilities as the 5th arg, made both examples consistent. Warning fixes (14): - W1: invoke_with_policy restructured as required method, invoke gets a default impl delegating to it — eliminates duplication across impls. - W2: CachedKey defined (service.md). - W3: EncryptionKey constructor/glue specified, added to re-export list. - W4: Secp256k1ExtendedPrivKey defined, derive_ethereum_key glue shown. - W5: encryption_path_for_version rejects version < 2 (v1 is TS PBKDF2). - W6: Wire payload schemas for all event types + ResponseEnvelope → EventEnvelope conversion table (call-protocol.md). - W7: Timeout section — deadline on OperationContext, composed calls inherit parent's deadline, CallAdapter::with_timeout(). - W8: Request ID generation spec — UUID v4 for composed calls, wire ID vs internal ID relationship for abort cascade. - W9: unlock_new already-unlocked behavior specified (returns AlreadyUnlocked). - W10: KeyType Serialize/Deserialize justification corrected (stale irpc reference removed). - W11: OperationProvenance and CompositionAuthority defined inline in operation-registry.md (were only in ADR-022). - W12: encrypt/decrypt free functions marked pub(crate), relationship to VaultServiceHandle methods stated. - W13: rotate signature removed from encryption.md (it's a VaultServiceHandle method, not a free function). - W14: CallAdapter::new() + with_session_source() + with_timeout() constructors shown. Suggestion fixes (6): Seed: Clone note, VaultServiceInner invariant, ExtendedPrivKey accessor signatures, CURRENT_KEY_VERSION location, ADR-018 stale actor text, derivation helpers re-export note.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-22-16
|
||||
last_updated: 2026-06-23
|
||||
---
|
||||
|
||||
# Core Types
|
||||
@@ -178,6 +178,67 @@ in `StreamError`, but once a handler propagates via `HandlerError`, the
|
||||
endpoint treats all variants as "close the connection" (one-ALPN-per-
|
||||
connection, ADR-006).
|
||||
|
||||
## Capabilities
|
||||
|
||||
Outbound credentials injected by the assembly layer at registration time.
|
||||
A handler uses `Capabilities` to make authenticated outbound calls (LLM
|
||||
provider API keys, HTTP service tokens, signing keys). See ADR-014 for the
|
||||
secret-material flow and ADR-022 for the registration-bundle wiring.
|
||||
|
||||
```rust
|
||||
/// Outbound credentials for a handler. Non-serializable, zeroized,
|
||||
/// immutable after construction. `Clone` is required by the composition
|
||||
/// model (`parent.capabilities.clone()` in `OperationEnv::invoke()`).
|
||||
///
|
||||
/// The concrete internal shape (a typed map, a struct with named fields)
|
||||
/// is a two-way door, but the public API is fixed: `new()`, `with_api_key()`,
|
||||
/// `with_http_token()`, and `get()`. Fields are private — callers cannot
|
||||
/// mutate the credentials after construction. This makes the clone-semantics
|
||||
/// two-way door genuinely two-way: Arc-based clone (shared immutable state)
|
||||
/// and deep-copy clone (isolated state) are behaviorally identical when
|
||||
/// neither supports mutation. See ADR-014, ADR-022, review #002 W2.
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct Capabilities {
|
||||
// Private — no interior mutability. The builder API (new, with_*) is
|
||||
// the only construction path. Immutability after construction is the
|
||||
// security guard that makes clone semantics safe.
|
||||
entries: HashMap<String, Secret<String>>,
|
||||
}
|
||||
|
||||
impl Capabilities {
|
||||
/// Empty capabilities — for handlers that make no outbound calls.
|
||||
pub fn new() -> Self;
|
||||
|
||||
/// Add an API key (e.g., "google", "openai") to the capabilities.
|
||||
pub fn with_api_key(mut self, service: &str, key: String) -> Self;
|
||||
|
||||
/// Add an HTTP bearer token (e.g., "vastai", "github") to the capabilities.
|
||||
pub fn with_http_token(mut self, service: &str, token: String) -> Self;
|
||||
|
||||
/// Retrieve a credential by service name, if present.
|
||||
pub fn get(&self, service: &str) -> Option<&Secret<String>>;
|
||||
}
|
||||
```
|
||||
|
||||
- **Non-serializable**: `Capabilities` does **not** derive `Serialize`. It
|
||||
cannot appear in `EventEnvelope` payloads even by accident. This is a
|
||||
type-level enforcement of ADR-014's "call protocol carries no secret material."
|
||||
- **Zeroized**: derives `Zeroize` and `ZeroizeOnDrop`. Secret material does
|
||||
not linger in freed heap memory.
|
||||
- **`Clone` + `Send + Sync`**: required by the composition model —
|
||||
`OperationEnv::invoke()` clones the parent's capabilities for each child.
|
||||
`Send + Sync` is required because the context is held across async task
|
||||
boundaries.
|
||||
- **Immutable after construction**: no `set`, no `insert`, no `mut` accessors.
|
||||
This is the guard from review #002 W2 — it makes the Arc-vs-deep-copy clone
|
||||
semantics genuinely two-way (shared immutable state is safe).
|
||||
- **Module location**: `Capabilities` lives in alknet-core (it's a shared type
|
||||
— see overview.md's Shared Types table). alknet-call imports it.
|
||||
|
||||
See [operation-registry.md → Capability Injection](../call/operation-registry.md#capability-injection)
|
||||
for how the dispatch path populates `OperationContext.capabilities` from the
|
||||
registration bundle.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
| Decision | ADR | Summary |
|
||||
@@ -187,6 +248,7 @@ connection, ADR-006).
|
||||
| HandlerError is non-fatal | [ADR-010](../../decisions/010-alpn-router-and-endpoint.md) | Handler errors close the connection, not the endpoint |
|
||||
| SendStream/RecvStream wrap quinn + iroh | [ADR-010](../../decisions/010-alpn-router-and-endpoint.md) | Internal enum dispatch for both QUIC sources |
|
||||
| Connection stores handler-resolved identity | OQ-11 (resolved) | `set_identity` via `OnceLock` — write-once-read-many; read by handler-side logging, not by the endpoint (C13 resolved) |
|
||||
| Capabilities type | [ADR-014](../../decisions/014-secret-material-flow-and-capability-injection.md) | Non-serializable, zeroized, immutable after construction; `Clone` for composition propagation |
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
Reference in New Issue
Block a user