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.
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-06-22-22 |
alknet-call
Structured RPC over QUIC: operations, request/response, streaming subscriptions, and service discovery. Implements ProtocolHandler on ALPN alknet/call.
Documents
| Document | Status | Description |
|---|---|---|
| call-protocol.md | draft | CallAdapter, EventEnvelope framing, stream model, PendingRequestMap, bidirectional calls |
| operation-registry.md | draft | OperationSpec, Handler, OperationRegistry, AccessControl, service discovery, irpc integration |
Applicable ADRs
| ADR | Title | Relevance |
|---|---|---|
| 001 | ALPN-Based Protocol Dispatch | CallAdapter registers on ALPN alknet/call |
| 002 | ProtocolHandler Trait | CallAdapter implements ProtocolHandler |
| 003 | Crate Decomposition | alknet-call depends on alknet-core and irpc |
| 013 | Rust as Canonical Implementation Language | Adapter traits defined in Rust; TS is reference/browser adaptation |
| 004 | Auth as Shared Core | AuthContext passed to call handlers |
| 005 | irpc as Call Protocol Foundation | irpc provides framing and service dispatch |
| 006 | ALPN String Convention | alknet/call ALPN, one ALPN per connection |
| 007 | BiStream Type Definition | CallAdapter receives Connection, not BiStream |
| 008 | Vault Integration Point | Vault accessed at assembly layer, not on the wire |
| 010 | ALPN Router and Endpoint | Static handler registration |
| 012 | Call Protocol Stream Model | Bidirectional streams, EventEnvelope, ID-based correlation |
| 014 | Secret Material Flow and Capability Injection | Call protocol carries no secret material; capabilities injected at assembly layer |
| 015 | Privilege Model and Authority Context | internal = authority switch not ACL skip; External/Internal visibility; handler identity + scoped env |
| 016 | Abort Cascade for Nested Calls | call.aborted cascades to descendants; default abort-dependents, continue-running opt-in |
| 017 | Call Protocol Client and Adapter Contract | CallClient opens connections; from_call imports remote ops; connection direction independent of call direction |
| 022 | Handler Registration, Provenance, and Composition Authority | Registration bundle carries provenance, composition authority, scoped env, capabilities |
| 023 | Operation Error Schemas | Operations declare domain errors; call.error carries typed details; adapter fidelity |
| 024 | Operation Registry Layering | Curated (static) + session/connection overlays (dynamic); OperationEnv as trait-object integration point; OperationContext.env split into scoped_env (data) and env (dispatch trait) |
Relevant Open Questions
| OQ | Title | Status | Relevance |
|---|---|---|---|
| OQ-07 | Call protocol scope within a connection | resolved (ADR-012) | Stream model, multiplexing, scope |
| OQ-13 | Operation path format and routing scope | resolved | /{service}/{op} is the correct design; remote dispatch is a separate layer |
| OQ-14 | Batch operation semantics | resolved | Correlated call.requested events is the correct protocol design |
| OQ-16 | Safe vault operations for call protocol exposure | resolved (ADR-014) | None exposed for now |
| OQ-19 | Session-scoped operation registries | resolved | Agent-written operations overlaid on curated registry via OperationEnv trait layering. Protocol doesn't need changes; OperationEnv must remain a trait. Generalized by ADR-024 to cover connection-scoped overlays. |
Key Design Principles
- One connection, full access: An
alknet/callconnection gives access to the entire operation registry — calls, subscriptions, batch, schema. - Protocol is symmetric: Both sides can initiate calls. The server calling a client uses the same EventEnvelope format and correlation.
- Stream-agnostic correlation: PendingRequestMap correlates by request ID, not by stream. The protocol works with any stream arrangement.
- Operation registry is layered: The curated layer (
Localprovenance) is static — registered at startup by the CLI binary, immutable for the process lifetime. Session (Session) and imported (FromCalletc.) ops are dynamic overlays at their respective scopes (per-session, per-connection). The registry supports JSON Schema discovery. See ADR-024. - irpc is one dispatch backend: Local operations dispatch directly. irpc service calls (in-process, type-safe) are internal. The call protocol is the external interface.
- Local dispatch only: The operation registry dispatches to local handlers. Remote dispatch (federation, head/worker routing) would be a separate mechanism at a different layer, not a modification to alknet-call's path format.
- No secret material on the wire: The call protocol carries no private keys, API keys, mnemonics, or decrypted credentials. Handlers receive outbound credentials through
OperationContext.capabilities, injected at the assembly layer. See ADR-014. - Abort cascades to descendants:
call.abortedfor a parent request cascades to all non-terminal descendants. Defaultabort-dependents;continue-runningopt-in. See ADR-016. - Internal calls switch authority context, not skip ACL: The
internalflag marks composition-originated calls. ACL runs against the handler's composition authority, not the caller's and not as a blanket skip. Operations have External/Internal visibility. Scoped composition env bounds reachability. See ADR-015, ADR-022. - Provenance determines composition capability: Only
LocalandSessionops can compose. Leaves (FromOpenAPI,FromMCP,FromCall) are forwarding stubs — they don't get composition authority or a scoped env. The assembly layer is the sole grantor of composition authority. See ADR-022.