Files
alkcall/docs/architecture/call-README.md
T
glm-5.3-flash 7b8a3e7558 docs: spec-level sync with the single-publish fix + port-debt repair
The spec docs had not caught up with the stub-sweep fix commits
(4eae18f..3317ea0), and the ported docs still carried alknet
numbering that now collides with alkcall's own ADR range.

Spec sync with the landed code:
- call-protocol.md: register_imported/register_imported_all return
  Result and refuse marked specs; marked-spec registration refusal
  contract spelled out (ADR-047 am. 4 §4.8, ADR-022 am. 2026-10-13)
- client-and-adapters.md: from_call flow gains the Pub sink-forwarding
  branch and the marked-op refusal (resolves OQ-37 — no from_call
  relay wrapper; sanctioned compositions are
  ChannelRelay::register_relay_openable / ChannelClient)
- ADR-051: "channel/control is not implemented" stale sentence
  replaced with the ADR-052 per-leg control contract
- OQ-37 resolved; OQ-22 split (primitive resolved; broker placement
  out of scope) with new OQ-42 for the downstream hub broker
  (deferred(scope), concretely blocked); OQ-24 resolved by the shipped
  add/strip surface; OQ-41 → deferred(scope) with blocking condition
- README: OQ bullets renumbered to tracker IDs; alknet-collision note
  rewritten (range 001..053, alknet prefix convention); new principle
  11 (implement-or-stop-advertising); AGENTS.md ADR range 001..053

Port-debt repair:
- 149 broken decision links across 17 docs — ported with alknet
  numbers/paths, most now remapped to alkcall files by slug; the
  rest link the alknet repo per the README convention
- stale OQ numbering rewritten to tracker IDs in call-protocol.md,
  call-README.md, operation-registry.md, client-and-adapters.md,
  ADR-025, channels-overview/README/wire, channel-client
- unambiguous "alknet ADR-NNN" labels where an alknet number collides
  with an alkcall ADR (052 TTY vs channel/control, 033 storage vs
  rust-canonical, 025/026/027+020 vault vs PeerEntry/auth ADRs)

Verified: cargo test (697 + guard), fmt, clippy --all-targets -D
warnings; full markdown link scan clean.
2026-10-13 09:34:37 +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 0–5) 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 alk/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 alk/call
002 ProtocolHandler Trait CallAdapter implements ProtocolHandler
031 Crate Decomposition alknet-call depends on alknet-core (no irpc — ADR-014)
033 Rust as Canonical Implementation Language Adapter traits defined in Rust; TS is reference/browser adaptation
003 Auth as Shared Core AuthContext passed to call handlers
013 irpc as Call Protocol Foundation Accepted → Superseded by ADR-014 (irpc was never integrated; framing is hand-rolled)
014 Hand-Rolled EventEnvelope Framing Wire format, registry, dispatch are hand-rolled in alknet-call; supersedes ADR-013
007 Connection::from_stream Generic single-stream connections; unblocks TCP+TLS/SSH/WT/wasm dispatch
004 ALPN String Convention alk/call ALPN, one ALPN per connection
005 BiStream Type Definition CallAdapter receives Connection, not BiStream
[008008 Vault Integration Point Vault accessed at assembly layer, not on the wire
[010010 ALPN Router and Endpoint Static handler registration
015 Call Protocol Stream Model Bidirectional streams, EventEnvelope, ID-based correlation
010 Secret Material Flow and Capability Injection Call protocol carries no secret material; capabilities injected at assembly layer
017 Privilege Model and Authority Context internal = authority switch not ACL skip; External/Internal visibility; handler identity + scoped env
020 Abort Cascade for Nested Calls call.aborted cascades to descendants; default abort-dependents, continue-running opt-in
022 Call Protocol Client and Adapter Contract CallClient opens connections; from_call imports remote ops; connection direction independent of call direction
027 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
018 Handler Registration, Provenance, and Composition Authority Registration bundle carries provenance, composition authority, scoped env, capabilities
016 Operation Error Schemas Operations declare domain errors; call.error carries typed details; adapter fidelity
019 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)
023 Peer-Scoped Registry Filtering Accepted → Superseded by ADR-024 (flat-namespace single-peer model couldn't express head→N-workers; parallel auth system duplicated AccessControl)
024 Peer-Graph Routing Model Peer-keyed overlays + PeerRef routing; AccessControl-based peer authorization; retires remote_safe/trusted_peer
025 PeerEntry and Identity.id Decoupling PeerId source = Identity.id = PeerEntry.peer_id (stable); supersedes ADR-024's UUID source
026 Forwarded-For Identity forwarded_for on OperationContext and call.requested; metadata only, never used by AccessControl::check
[033033 Storage Boundary and Repo/Adapter Pattern Core defines repo traits + in-memory defaults; persistence adapters are separate crates
045 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
012 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-01 Call protocol scope within a connection resolved (ADR-015) Stream model, multiplexing, scope
OQ-02 Operation path format and routing scope resolved /{service}/{op} is the correct design; remote dispatch is a separate layer
OQ-03 Batch operation semantics resolved Correlated call.requested events is the correct protocol design
OQ-04 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-09 Safe vault operations for call protocol exposure resolved (ADR-010) None exposed for now
OQ-10 Remote-safe marking shape dissolved (ADR-024) remote_safe/trusted_peer retired; peer authorization is AccessControl::check(peer_identity)
OQ-11 OperationAdapter error type (AdapterError variants) resolved DiscoveryFailed, SchemaParse, Transport, Unauthorized, SamePeerCollision; #[non_exhaustive]
OQ-12 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-13 from_call namespace collision resolved Same-peer collision = error; cross-peer dissolved by ADR-024 (separate sub-overlays)
OQ-14 CallClient TLS client-auth resolved Wire quinn client-auth; key-type-aware server cert verification; fingerprint normalization
OQ-15 PeerRef::Any routing policy resolved Insertion-order first-match; richer routing is a feature extension
OQ-16 services/list-peers re-export semantics resolved Opt-in services/list-peers; services/list is "own ops only"
OQ-17 Multi-hop federation deferred(scope) (feature extension) One-hop model is the commitment; multi-hop is a feature extension, not a deferral
OQ-18 PeerId — crypto identity vs stable logical id resolved (ADR-025) PeerId = Identity.id = PeerEntry.peer_id (stable across key rotation)
OQ-19 Persistent peer registry resolved (ADR-025; persistence adapters are alknet-side) Core trait + in-memory default; persistence adapters are separate crates
OQ-20 API key asymmetry dissolved PeerEntry supports multiple credential paths; ApiKeyEntry is for tokens that ARE the identity
OQ-21 X.509 outgoing-only case resolved (alknet ADR-034, not ported) 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 alk/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 → Capabilities → HandlerRegistration.capabilities → OperationContext.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.