docs: re-key architecture docs off the alknet mono-repo — speak as alkcall
The ported docs still framed alknet siblings (alknet-core/-call/-channels/
-http/-tls/-client) as live load-bearing context of this crate. With the
extraction complete and alknet headed for glue-level replacement, the docs
now speak as alkcall and cite the real sibling crates (alkhttp, alktls,
alktty, alktunnels, alktrader).
- ADR-054 (new): re-own the verifier-selection rule in-crate — three
remote roles (public X.509 endpoint / transport relay / hub),
ServerCertVerifier by PeerEntry presence, RemoteIdentity None
load-bearing. Provenance: alknet ADR-034 (never ported), demoted to
footnote. Resolves OQ-21's cross-repo authority gap.
- OQ-43 (new, deferred(scope)): composition-layer ownership and shape
after the alknet replacement — the named receiver for the
downstream-side deferrals (OQ-33 endpoint layer, OQ-41 QUIC-native
substrate, OQ-42 hub broker, OQ-19 persistence adapters, ADR-045 dial
seam). README layering diagram redrawn with the composition layer as
TBD-ownership instead of "alknet / alknode" in concrete.
- Spec docs reframed: titles, self-references, dial/TLS/from_jsonschema
homes, malformed legacy cite rows removed; dead relative research
paths qualified as source-repo paths.
- ADR amendments (append-only): 027 (alknet-http -> alkhttp), 031 (post-
extraction state), 044 (split landed as modules in alkcall), 045
(name normalization; citation-space note).
- Citation hygiene: unprefixed/colliding source-repo cites prefixed
("alknet ADR-NNN (provenance)") across 009/011/022/034/035/038/040/
043/044/045; stale OQ cites corrected (session registries OQ-04, batch
semantics OQ-03, verifier rule ADR-054); stale anchors fixed.
- README provenance note consolidated; citation conventions documented.
Verification: markdown link check (all local links resolve, all in-crate
ADR cites point at existing decisions/ files); table integrity check
(uniform row widths across open-questions.md and edited tables); residual
alknet-mention sweep (only provenance/historical/intentional framing
remains); architecture review subagent pass with zero remaining criticals.
Docs-only change — no code paths touched.
This commit is contained in:
1 parent
7b8a3e7558
commit
5f019c02f1
26 files changed
+670
-383
No files matched your search
@@ -1,11 +1,11 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-07-09
|
||||
last_updated: 2026-10-13 (alkcall reframing)
|
||||
---
|
||||
|
||||
# Call Protocol
|
||||
|
||||
The wire protocol, stream model, framing, and adapter that alknet-call implements on ALPN `alk/call`.
|
||||
The wire protocol, stream model, framing, and adapter that alkcall implements on ALPN `alk/call`.
|
||||
|
||||
## What
|
||||
|
||||
@@ -15,7 +15,7 @@ The `CallAdapter` implements `ProtocolHandler` for ALPN `alk/call`. It receives
|
||||
|
||||
## Why
|
||||
|
||||
The call protocol is the primary programmatic interface to an alknet node. While SSH provides interactive shell access and HTTP provides REST APIs, the call protocol provides structured, discoverable RPC — the same interface that NAPI clients, MCP tools, and other automation consumers use.
|
||||
The call protocol is the primary programmatic interface to an alk node. While SSH provides interactive shell access and HTTP provides REST APIs, the call protocol provides structured, discoverable RPC — the same interface that NAPI clients, MCP tools, and other automation consumers use.
|
||||
|
||||
The protocol must be:
|
||||
- **Cross-language**: JSON wire format consumable from TypeScript, Python, any language
|
||||
@@ -40,7 +40,7 @@ pub struct CallAdapter {
|
||||
registry: Arc<OperationRegistry>,
|
||||
identity_provider: Arc<dyn IdentityProvider>,
|
||||
/// Layer 1 — optional session-overlay source (agent crate supplies this;
|
||||
/// None for non-agent deployments). See ADR-019, OQ-19.
|
||||
/// None for non-agent deployments). See ADR-019, OQ-04.
|
||||
session_source: Option<Arc<dyn SessionOverlaySource + Send + Sync>>,
|
||||
/// Default timeout for wire calls (30s). Composed calls inherit the
|
||||
/// parent's remaining deadline via `OperationContext.deadline`.
|
||||
@@ -57,8 +57,9 @@ impl CallAdapter {
|
||||
default_timeout: Duration::from_secs(30) }
|
||||
}
|
||||
|
||||
/// Agent deployment: supply a session-overlay source. The agent crate
|
||||
/// implements `SessionOverlaySource`; alknet-call defines the trait.
|
||||
/// Agent deployment: supply a session-overlay source. A downstream
|
||||
/// consumer (e.g. the agent crate) implements `SessionOverlaySource`;
|
||||
/// alkcall defines the trait.
|
||||
pub fn with_session_source(mut self, source: Arc<dyn SessionOverlaySource + Send + Sync>) -> Self {
|
||||
self.session_source = Some(source);
|
||||
self
|
||||
@@ -71,10 +72,11 @@ impl CallAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Session overlay integration point (ADR-019). Defined in alknet-call
|
||||
/// because `CallAdapter` must name the type — alknet-call cannot depend on
|
||||
/// alknet-agent (agent depends on call, not reverse). The agent crate
|
||||
/// implements this trait; alknet-call defines it. This is the same pattern
|
||||
/// Session overlay integration point (ADR-019). Defined in alkcall
|
||||
/// because `CallAdapter` must name the type — alkcall cannot depend on
|
||||
/// its downstream consumers (an agent depends on call, not reverse). The
|
||||
/// consumer (e.g. the agent crate) implements this trait; alkcall
|
||||
/// defines it. This is the same pattern
|
||||
/// as `IdentityProvider` (ADR-003: core defines the trait, handlers impl it).
|
||||
///
|
||||
/// The session overlay is an `OperationEnv` impl that wraps the curated base
|
||||
@@ -234,7 +236,7 @@ The `Value` type is `serde_json::Value`. The envelope is JSON because it must be
|
||||
Binary payloads (postcard, protobuf) are base64-encoded as a JSON string within the `payload` field. The convention is: if an operation's output schema specifies a binary field, the handler encodes it as a base64 string and the client decodes it. The `EventEnvelope` structure is not aware of this convention — it carries a `serde_json::Value` and does not interpret the payload. This is a handler-level concern, not a protocol-level concern.
|
||||
|
||||
This is hand-rolled length-prefixed JSON framing (ADR-014), coincidentally
|
||||
the same shape irpc uses. The Rust implementation in alknet-call is
|
||||
the same shape irpc uses. The Rust implementation in alkcall is
|
||||
canonical — the `@alkdev/pubsub` TypeScript adapters serve as a reference
|
||||
and browser adaptation, not a parallel implementation (see ADR-033).
|
||||
|
||||
@@ -352,7 +354,7 @@ The call protocol defines five top-level operations, expressed through event typ
|
||||
| **batch** | multiple `call.requested` (different IDs) → multiple `call.responded` | Multiple operations in one round |
|
||||
| **schema** | `call.requested` name `services/list` or `services/schema` → `call.responded` | Discover available operations |
|
||||
|
||||
Batch is not a separate event type — it's multiple `call.requested` events with different request IDs. The client sends them (on one or many streams) and correlates the responses by ID. See OQ-14.
|
||||
Batch is not a separate event type — it's multiple `call.requested` events with different request IDs. The client sends them (on one or many streams) and correlates the responses by ID. See OQ-03.
|
||||
|
||||
### Bidirectional Calls
|
||||
|
||||
@@ -573,8 +575,8 @@ Handlers clean up resources when their call is cancelled (in Rust, the future is
|
||||
|
||||
- The call protocol does not depend on any database. `PendingRequestMap` is in-memory. Durable session storage is a consumer concern.
|
||||
- Operation specs use JSON Schema. The envelope is always JSON. Binary payloads may be base64-encoded in the `payload` field.
|
||||
- Batch is not a protocol primitive — multiple `call.requested` events with correlated IDs provide equivalent semantics. See OQ-14.
|
||||
- The call protocol is transport-agnostic at the envelope level. The `EventEnvelope` framing can run over QUIC streams, WebSocket frames, or Worker `postMessage`. The `CallAdapter` is the `ProtocolHandler` implementation that receives a `Connection` (any transport — ADR-007) and dispatches `EventEnvelope` frames. **The `EventEnvelope` shape (`{ type, id, payload }`) was derived from the `@alkdev/pubsub` `EventEnvelope` (`/workspace/@alkdev/pubsub/src/types.ts`), which already has a working WebSocket client/server implementation (`event-target-websocket-client.ts` / `event-target-websocket-server.ts`) and a generalized "event target" abstraction. The call protocol refined the envelope with typed event names (`call.requested`, `call.responded`, etc.) and structured payloads; the delta is small and well-defined, making a browser (and Node) WebSocket client straightforward to derive from the pubsub prior art. See alknet ADR-044, [ADR-048](/workspace/@alkdev/alknet/docs/architecture/decisions/048-websocket-native-session-not-gateway.md), and [websocket.md](/workspace/@alkdev/alknet/docs/architecture/crates/http/websocket.md).
|
||||
- Batch is not a protocol primitive — multiple `call.requested` events with correlated IDs provide equivalent semantics. See OQ-03.
|
||||
- The call protocol is transport-agnostic at the envelope level. The `EventEnvelope` framing can run over QUIC streams, WebSocket frames, or Worker `postMessage`. The `CallAdapter` is the `ProtocolHandler` implementation that receives a `Connection` (any transport — ADR-007) and dispatches `EventEnvelope` frames. **The `EventEnvelope` shape (`{ type, id, payload }`) was derived from the `@alkdev/pubsub` `EventEnvelope` (`/workspace/@alkdev/pubsub/src/types.ts`), which already has a working WebSocket client/server implementation (`event-target-websocket-client.ts` / `event-target-websocket-server.ts`) and a generalized "event target" abstraction. The call protocol refined the envelope with typed event names (`call.requested`, `call.responded`, etc.) and structured payloads; the delta is small and well-defined, making a browser (and Node) WebSocket client straightforward to derive from the pubsub prior art. Provenance: alknet ADR-044 and the alknet websocket spec (not ported) recorded the WebSocket-native session decision; the WebSocket carrier for the alk protocols is implemented in the sibling host crate `alkhttp` (its `websocket` feature), not in alkcall — alkcall stays transport- and host-blind.
|
||||
- `OperationEnv::invoke()` dispatches through the local registry. Remote dispatch (federation, head/worker routing) would be a separate mechanism at a different layer. See ADR-014 and OQ-13.
|
||||
- **The call protocol carries no secret material.** Secret material (private keys, API keys, mnemonics, decrypted credentials, raw tokens) must not appear in `call.requested` payloads, `call.responded` payloads, or `OperationContext.metadata`. The wire format carries `serde_json::Value` and cannot enforce this at the type level — the constraint is architectural, enforced by the operation registry and by convention. Operations that need to share public key material use a dedicated operation that returns only the public component. See ADR-010.
|
||||
- **Abort cascades to descendants.** `call.aborted` for a parent request cascades to all non-terminal descendants in the call tree. Default policy is `abort-dependents`; `continue-running` is an opt-in. See ADR-020.
|
||||
@@ -587,7 +589,7 @@ Handlers clean up resources when their call is cancelled (in Rust, the future is
|
||||
| Call protocol stream model | [ADR-015](decisions/015-call-protocol-stream-model.md) | Bidirectional streams, EventEnvelope, ID-based correlation |
|
||||
| ALPN per connection | [ADR-004](decisions/004-alpn-convention-and-connection-model.md) | `alk/call` is a distinct ALPN, one connection per ALPN |
|
||||
| ProtocolHandler receives Connection | [ADR-005](decisions/005-bistream-type-definition.md) | CallAdapter gets Connection, can accept/open multiple streams |
|
||||
| Vault integration point | [ADR-008](/workspace/@alkdev/alknet/docs/architecture/decisions/008-secret-service-integration.md) | Vault is a capability source, accessed at assembly time |
|
||||
| Vault integration point | [ADR-010](decisions/010-secret-material-flow-and-capability-injection.md) | Vault is a capability source, accessed at assembly time (provenance: alknet ADR-008, superseded in-crate by ADR-010) |
|
||||
| Secret material flow | [ADR-010](decisions/010-secret-material-flow-and-capability-injection.md) | Call protocol carries no secret material; capabilities injected at assembly layer |
|
||||
| Privilege model and authority context | [ADR-017](decisions/017-privilege-model-and-authority-context.md) | `internal` = authority switch not ACL skip; External/Internal visibility; handler identity + scoped env |
|
||||
| Abort cascade for nested calls | [ADR-020](decisions/020-abort-cascade-for-nested-calls.md) | `call.aborted` cascades to descendants; default `abort-dependents`, `continue-running` opt-in |
|
||||
@@ -615,8 +617,8 @@ See [open-questions.md](open-questions.md) for full details.
|
||||
`SamePeerCollision`); `#[non_exhaustive]`. See
|
||||
[client-and-adapters.md](client-and-adapters.md).
|
||||
- **OQ-12** (resolved): `from_call` re-import trigger — `from_call` is a
|
||||
manual free function; the assembly layer calls it after the dial (in
|
||||
`AlknetClient`). See
|
||||
manual free function; after the dial (in the composition layer's dial helper
|
||||
`AlknetClient` — today the alknet glue; OQ-43). See
|
||||
[ADR-028](decisions/028-from-call-manual-free-function.md).
|
||||
- **OQ-13** (resolved): `from_call` namespace collision — same-peer collision
|
||||
= error; cross-peer dissolved by ADR-024 (separate sub-overlays). See
|
||||
@@ -637,14 +639,16 @@ See [open-questions.md](open-questions.md) for full details.
|
||||
rotation), not a connection-assigned UUID.
|
||||
- **OQ-19** (resolved by ADR-025): Persistent peer registry —
|
||||
the storage boundary is `core trait + in-memory default`; persistence
|
||||
adapters are separate crates (the concrete adapter shapes were named in
|
||||
alknet research, not re-specced here).
|
||||
- **OQ-21** (resolved): X.509 outgoing-only case — three remote
|
||||
adapters are separate crates (composition-layer work — OQ-43; the
|
||||
concrete adapter shapes were named in the source repo's research
|
||||
(alknet, provenance), not re-specced here).
|
||||
- **OQ-21** (resolved by ADR-054): X.509 outgoing-only case — three remote
|
||||
roles named (public X.509 endpoint, transport relay, hub); pure-client
|
||||
X.509 connections are not in the peer graph on the client side. The
|
||||
deciding ADR (alknet ADR-034, "Outgoing-Only X.509 and Three Peer
|
||||
Roles") was not ported into alkcall; the surviving alkcall surface is
|
||||
ADR-025's `PeerEntry` asymmetry. See [client-and-adapters.md](client-and-adapters.md).
|
||||
X.509 connections are not in the peer graph on the client side.
|
||||
Re-owned in-crate by ADR-054 (verifier selection by `PeerEntry`
|
||||
presence; provenance: alknet ADR-034, not ported); ADR-025's
|
||||
`PeerEntry` asymmetry is the surviving registry surface. See
|
||||
[client-and-adapters.md](client-and-adapters.md).
|
||||
|
||||
## References
|
||||
|
||||
@@ -655,6 +659,6 @@ See [open-questions.md](open-questions.md) for full details.
|
||||
- ADR-024: Peer-graph routing model (peer-keyed overlays + `PeerRef` routing)
|
||||
- ADR-025: PeerEntry and Identity.id decoupling (`PeerId` source)
|
||||
- ADR-026: Forwarded-for identity (`forwarded_for` on `call.requested` and `OperationContext`)
|
||||
- ADR-034: Outgoing-only X.509 and the three peer roles
|
||||
- ADR-054: Outgoing-only X.509 and the three remote roles (re-owned in-crate; verifier selection by `PeerEntry` presence)
|
||||
- ADR-021: Streaming handler for subscriptions (server-side streaming dispatch path)
|
||||
- Reference implementation: `/workspace/@alkdev/alknet-main/crates/alknet-core/src/call/`
|
||||
- Reference implementation: this crate (`src/`); historical origin: the alknet / alknet-main call-core sources (provenance)
|
||||
Reference in new issue
Block a user