Files
alkcall/docs/architecture/call-README.md
glm-5.2 cc470a363a docs: port architecture specs + 45 ADRs from alknet, renumbered
Port the call + channels architecture documentation from the alknet
mono-repo into docs/architecture/, renumbered as alkcall ADR-001..045.

Renumbering map (alknet -> alkcall):
  Core:        001,002,004,006,007,011,065,070,092,014,050,091 -> 001-012
  Call:        005,064,012,023,015,022,024,016,049,017,028,029,030,032,066,069,067,068 -> 013-030
  Shared:      003,009,013 -> 031-033
  Channels:    071,093,072,073,074,075,076,094,079,080,081,089 -> 034-045

3 superseded/reversed ADRs kept for historical trail:
  - ADR-013 (irpc foundation, superseded by ADR-014)
  - ADR-023 (peer-scoped filtering, superseded by ADR-024)
  - ADR-077 (TTY inside channels, reversed by ADR-035 — not ported, TTY-only)

Ported docs (11 spec files + README + open-questions):
  - call-README.md, call-protocol.md, operation-registry.md, client-and-adapters.md
  - channels-README.md, channels-overview.md, channels-wire.md, channels-connection.md, channels-adapter.md, channel-operations.md, channel-client.md
  - README.md (index with doc table, ADR table grouped by category, key principles)
  - open-questions.md (lean — 30 OQs, renumbered OQ-01..030; includes new OQ-22 for the pub/sub gap)

Cross-reference rewriting:
  - All ADR-NNN references rewritten single-pass (no chaining bug)
  - Markdown link paths fixed
  - Title lines aligned with filenames
  - Non-ported ADR refs (052, 082, 086, etc.) left as-is with README note

The open-questions.md includes OQ-22 (new): the call protocol pub/sub
gap — subscribe exists but pub does not, needed for channels
channel/resources/subscribe fan-out. This is the next ADR to write
(alkcall ADR-046).
2026-08-12 07:06:57 +00:00

14 KiB
Raw Blame History

status: draft last_updated: 2026-07-17 review: call/review-call passed 2026-06-23 — registry, protocol, ADR (005/012/014/015/016/017/022/023/024), security, and pattern-consistency checks all conformant; 159 unit/integration tests green; cargo build, cargo clippy -- -D warnings, cargo fmt --check, cargo test clean. Call-completion gap (ADR-022 client/adapter surface) addressed 2026-06-26; ADR-024 migration landed. Transport generalization sweep (ADR-014 supersedes ADR-013; ADR-007 from_stream) synced 2026-07-09. Crate-extraction sweep (phases 05) landed 2026-07-17: ConnectionCredentials/RemoteIdentity in alknet-core (ADR-012); TLS helpers in alknet-tls (ADR-045 §5); dial in alknet-client (ADR-045); alknet-call is a pure protocol crate with no TLS/transport deps.

alknet-call

Structured RPC: operations, request/response, streaming subscriptions, and service discovery. Implements ProtocolHandler on ALPN alknet/call. Runs over QUIC (quinn/iroh) and, via Connection::from_stream (ADR-007), over any AsyncRead + AsyncWrite transport. A pure protocol crate — no TLS or transport deps (the dial is in alknet-client, the TLS config is in alknet-tls).

Documents

Document Status Description
call-protocol.md draft CallAdapter, hand-rolled EventEnvelope framing (no irpc — ADR-014), stream model, PendingRequestMap, bidirectional calls
operation-registry.md draft OperationSpec, Handler, OperationRegistry, AccessControl, service discovery, hand-rolled framing (no irpc — ADR-014)
client-and-adapters.md draft CallClient (transport-agnostic spawn_dispatch primary; dial lives in AlknetClient per ADR-045), from_call, OperationAdapter trait, adapter location map, no-env-vars invariant, exchange-of-operations pattern (from_jsonschema in alknet-http per ADR-027)

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 (no irpc — ADR-014)
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 AcceptedSuperseded by ADR-014 (irpc was never integrated; framing is hand-rolled)
064 Hand-Rolled EventEnvelope Framing Wire format, registry, dispatch are hand-rolled in alknet-call; supersedes ADR-013
065 Connection::from_stream Generic single-stream connections; unblocks TCP+TLS/SSH/WT/wasm 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
066 from_jsonschema as HTTP-Backed Single-Endpoint Adapter in alknet-http Moved from_jsonschema from alknet-call (broken schema-only placeholder) to alknet-http as a real reqwest-backed single-endpoint adapter; FromJsonSchema provenance stays in alknet-call as a leaf
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)
028 Peer-Scoped Registry Filtering AcceptedSuperseded by ADR-024 (flat-namespace single-peer model couldn't express head→N-workers; parallel auth system duplicated AccessControl)
029 Peer-Graph Routing Model Peer-keyed overlays + PeerRef routing; AccessControl-based peer authorization; retires remote_safe/trusted_peer
030 PeerEntry and Identity.id Decoupling PeerId source = Identity.id = PeerEntry.peer_id (stable); supersedes ADR-024's UUID source
032 Forwarded-For Identity forwarded_for on OperationContext and call.requested; metadata only, never used by AccessControl::check
033 Storage Boundary and Repo/Adapter Pattern Core defines repo traits + in-memory defaults; persistence adapters are separate crates
089 AlknetClient — Native Client Dial Seam The dial is in alknet-client; CallClient is spawn_dispatch only; alknet-call is a pure protocol crate with no TLS/transport deps
091 ConnectionCredentials — Decouple Dial from Call Protocol ConnectionCredentials/RemoteIdentity in alknet-core (not alknet-call); auth_token is a per-request payload field

Relevant Open Questions

OQ Title Status Relevance
OQ-07 Call protocol scope within a connection resolved (ADR-015) 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-010) 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-019 to cover connection-scoped overlays.
OQ-25 Remote-safe marking shape dissolved (ADR-024) remote_safe/trusted_peer retired; peer authorization is AccessControl::check(peer_identity)
OQ-26 OperationAdapter error type (AdapterError variants) resolved DiscoveryFailed, SchemaParse, Transport, Unauthorized, SamePeerCollision; #[non_exhaustive]
OQ-27 from_call re-import trigger resolved from_call is a manual free function; the assembly layer calls it after the dial (in AlknetClient). refresh() is a genuine feature addition. See ADR-028.
OQ-28 from_call namespace collision resolved Same-peer collision = error; cross-peer dissolved by ADR-024 (separate sub-overlays)
OQ-29 CallClient TLS client-auth resolved Wire quinn client-auth; key-type-aware server cert verification; fingerprint normalization
OQ-30 PeerRef::Any routing policy resolved Insertion-order first-match; richer routing is a feature extension
OQ-31 services/list-peers re-export semantics resolved Opt-in services/list-peers; services/list is "own ops only"
OQ-32 Multi-hop federation open (feature extension) One-hop model is the commitment; multi-hop is a feature extension, not a deferral
OQ-33 PeerId — crypto identity vs stable logical id resolved (ADR-025) PeerId = Identity.id = PeerEntry.peer_id (stable across key rotation)
OQ-34 Persistent peer registry resolved (ADR-025+033) Core trait + in-memory default; persistence adapters are separate crates
OQ-35 API key asymmetry dissolved PeerEntry supports multiple credential paths; ApiKeyEntry is for tokens that ARE the identity
OQ-37 X.509 outgoing-only case resolved (ADR-034) Three remote roles (public X.509 endpoint, transport relay, hub); PeerEntry asymmetry correct; verifier by PeerEntry presence

Key Design Principles

  1. One connection, full access: An alknet/call connection gives access to the entire operation registry — calls, subscriptions, batch, schema.
  2. Protocol is symmetric: Both sides can initiate calls. The server calling a client uses the same EventEnvelope format and correlation.
  3. Stream-agnostic correlation: PendingRequestMap correlates by request ID, not by stream. The protocol works with any stream arrangement.
  4. Operation registry is layered: The curated layer (Local provenance) is static — registered at startup by the CLI binary, immutable for the process lifetime. Session (Session) and imported (FromCall etc.) ops are dynamic overlays at their respective scopes (per-session, per-connection). The registry supports JSON Schema discovery. See ADR-019.
  5. Hand-rolled dispatch (no irpc): Operations dispatch through the hand-rolled OperationRegistry (ADR-014). The call protocol is the external interface; internal handler dispatch uses Handler/StreamingHandler trait objects (ADR-021), not an irpc service.
  6. 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.
  7. 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-010.
  8. Abort cascades to descendants: call.aborted for a parent request cascades to all non-terminal descendants. Default abort-dependents; continue-running opt-in. See ADR-020.
  9. Internal calls switch authority context, not skip ACL: The internal flag 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-017, ADR-018.
  10. Provenance determines composition capability: Only Local and Session ops can compose. Leaves (FromOpenAPI, FromMCP, FromCall, FromJsonSchema) 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-018. (FromJsonSchema is now a real HTTP-forwarding leaf per ADR-027, not a schema-only placeholder.)
  11. Connection direction is independent of call direction: Who opens the connection is a connection-layer concern, not a protocol-layer concern. Both sides can call each other once connected. The CallAdapter accepts connections; the CallClient takes them over (spawn_dispatch primary; dial in AlknetClient per ADR-045); both produce the same CallConnection and dispatch through the same loop. See ADR-022, client-and-adapters.md.
  12. Peer authorization via AccessControl: A remote peer's call is authorized by AccessControl::check(peer_identity) against the op's AccessControl — the same mechanism that gates every other call. No remote_safe flag, no trusted_peer bypass. An op with AccessControl::default() is callable by any peer; an op with required_scopes is callable only by peers whose Identity.scopes satisfy them; an op with Visibility::Internal is never callable from the wire. See ADR-024.
  13. Adapter trait lives with the types; implementations live with their transport: OperationAdapter is in alknet-call; from_call is in alknet-call (QUIC); from_jsonschema/from_openapi/from_mcp/to_openapi/to_mcp are in alknet-http (reqwest / axum). alknet-call stays lean — no HTTP client, no HTTP server. (from_jsonschema was originally in alknet-call as a schema-only placeholder; ADR-027 moved it to alknet-http as a real HTTP-backed adapter.) See client-and-adapters.md.
  14. No handler reads outbound credentials from any source other than OperationContext.capabilities (no-env-vars invariant): the credential injection path is vault → assembly layer → CapabilitiesHandlerRegistration.capabilitiesOperationContext.capabilities → handler. Downstream consumers' std::env::var reads are unreachable because the assembly layer never calls Default::default(). See ADR-010, client-and-adapters.md.