docs: roles, composition, and dependency layering for downstream crates
- docs/architecture/README.md: add Roles and Composition section with the four roles (producer, consumer, hub, spoke), dependency layering diagram, protocol crate pattern, and the two-path adapter model - docs/architecture/channels-overview.md: fix stale alknet ADR numbers (071/072/073/074/075/076/077/078/079/080/081 -> 034-044), update relationship section for post-extraction world, update crate dependencies to reflect single-crate alkcall - src/lib.rs: add Downstream composition section with role table and protocol crate pattern, linking to the architecture README Verification: cargo test (483 passed), cargo clippy (clean), cargo fmt (clean), cargo doc (no warnings)
This commit is contained in:
1 parent
6c5aa0c8e1
commit
7cd8a57bc7
3 files changed
+250
-104
No files matched your search
@@ -140,6 +140,133 @@ questions affecting this crate:
|
||||
10. **Wire formats are stable**: EventEnvelope shape and the 8-byte chunk
|
||||
header are one-way doors. See ADR-014, ADR-034.
|
||||
|
||||
## Roles and Composition
|
||||
|
||||
alkcall is a pure protocol crate — no networking, no transport
|
||||
dependencies. It provides the call and channels protocols as a library.
|
||||
Downstream crates compose on top of it in a layered dependency chain.
|
||||
|
||||
### The four roles
|
||||
|
||||
| Role | Definition | Call protocol | Channels protocol |
|
||||
|------|-----------|---------------|-------------------|
|
||||
| **Producer** | Provides a resource consumers can use (often called "server") | Registers ops on an `OperationRegistry`, runs a `Dispatcher` to handle incoming `call.requested` | Runs a `ChannelsAdapter`, registers openable ALPNs via `ChannelCore::register_openable` |
|
||||
| **Consumer** | Consumes a resource from a producer (often called "client") | Uses `CallConnection` to call ops, uses `from_call` to discover/import remote ops | Uses `ChannelClient` to open channels via `call_open_op` + `open_channel` |
|
||||
| **Hub** | Central location spokes connect to; relays and routes between them | Both: runs a `Dispatcher` for ops it produces, holds `CallConnection`s to spokes for ops it consumes. Relays calls via `OperationEnv` peer routing | Both: runs a `ChannelsAdapter` for inbound connections, holds `ChannelClient`s to spokes. Relays data channels with `channel_id` rewrite (ADR-042) |
|
||||
| **Spoke / Worker** | Connects to a hub; provides and consumes resources | Both: produces ops (its own services), consumes hub ops (e.g. `services/list` to discover peers) | Both: produces channels (TTY, tunnel), may consume hub channels |
|
||||
|
||||
A single process can be a producer of some ops, a consumer of others, a
|
||||
channel opener for TTY, and a channel acceptor for tunnels — all on the
|
||||
same `alknet/channels` connection. The types don't encode the system
|
||||
role; they just don't prevent any combination.
|
||||
|
||||
Within a single connection, direction is independent of role:
|
||||
|
||||
- **Call initiator** / **call responder** — who sent `call.requested` vs
|
||||
who handles it. Either side can initiate on any connection.
|
||||
- **Channel opener** / **channel acceptor** — who called the per-ALPN
|
||||
open op vs who allocated the `channel_id`. The connection owner
|
||||
allocates (ADR-047 §5); either side can open.
|
||||
|
||||
### Dependency layering
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ alknet / alknode │
|
||||
│ (networking + composition) │
|
||||
│ │
|
||||
│ • QUIC / TCP+TLS dial and accept │
|
||||
│ • Hub: ChannelsAdapter + Dispatcher + │
|
||||
│ peer routing (PeerCompositeEnv) │
|
||||
│ • Spoke: ChannelClient + CallConnection + │
|
||||
│ from_call │
|
||||
│ • Wires protocol crates' producers into │
|
||||
│ registries, consumers into clients │
|
||||
└──────────────────┬───────────────────────────┘
|
||||
│ depends on
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
┌───┴───────┐ ┌────┴─────┐ ┌─────┴──────────┐
|
||||
│ alktty │ │alktunnels│ │ alktrader │
|
||||
│ (protocol)│ │(protocol)│ │ (protocol) │
|
||||
│ │ │ │ │ │
|
||||
│ • Session │ │ • Tunnel │ │ • Backend trait │
|
||||
│ • Backend │ │ • Backend│ │ • register_ops()│
|
||||
│ • 5-byte │ │ • bytes │ │ • TypedClient │
|
||||
│ wire │ │ • OpenH. │ │ │
|
||||
│ • OpenH. │ │ • reg_*()│ │ │
|
||||
│ • reg_*() │ │ │ │ │
|
||||
└───┬───────┘ └────┬─────┘ └─────┬───────────┘
|
||||
│ │ │
|
||||
└──────────────┼──────────────┘
|
||||
│ depends on
|
||||
┌──────────────────┴───────────────────────────┐
|
||||
│ alkcall │
|
||||
│ (call + channels protocols, no networking) │
|
||||
│ │
|
||||
│ • Connection, BiStream, ProtocolHandler │
|
||||
│ • OperationRegistry, OperationSpec, Handler │
|
||||
│ • CallConnection, Dispatcher, from_call │
|
||||
│ • ChannelClient, ChannelsAdapter, │
|
||||
│ ChannelManager, ChannelCore │
|
||||
└───────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Protocol crates** (alktty, alktunnels, alktrader) depend only on
|
||||
alkcall. They provide two halves:
|
||||
|
||||
1. **Producer half** — a `register_*()` function that takes an
|
||||
`&mut OperationRegistry` and registers ops with their handlers. For
|
||||
channels-based protocols, an `OpenHandler` factory. The crate doesn't
|
||||
know whether it's running on a hub, a spoke, or a standalone process.
|
||||
|
||||
2. **Consumer half** — a typed client wrapper around `CallConnection`
|
||||
(or `ChannelClient`) that exposes the crate's ops as async methods
|
||||
(e.g. `trader_client.status().await` instead of
|
||||
`call_connection.call("trader/status", ...).await`).
|
||||
|
||||
**alknet/alknode** depends on alkcall + whichever protocol crates are
|
||||
needed. It's the composition layer — the only place that knows about
|
||||
network topology, peer routing, and which protocol crates are wired in.
|
||||
Protocol crates never import a QUIC or TLS dependency.
|
||||
|
||||
### Pattern for protocol crates
|
||||
|
||||
A protocol crate that uses channels (e.g. alktty) follows this pattern:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ alktty (protocol crate, no network) │
|
||||
│ │
|
||||
│ TtySession { │
|
||||
│ drive(send, recv, backend) -> ExitCode │ ← pure protocol, takes
|
||||
│ } │ AsyncRead + AsyncWrite
|
||||
│ │
|
||||
│ TtyBackend trait │
|
||||
│ NegotiateRequest / ControlMessage │
|
||||
│ ChunkReader / ChunkWriter (5-byte) │
|
||||
└─────────────────────────────────────────┘
|
||||
▲ ▲
|
||||
│ │
|
||||
┌────────┴────────┐ ┌───────┴──────────────┐
|
||||
│ Direct ALPN │ │ Through channels │
|
||||
│ (alknet/tty) │ │ (alknet/channels) │
|
||||
│ │ │ │
|
||||
│ TtyAdapter │ │ OpenHandler │
|
||||
│ impl Protocol │ │ (registered via │
|
||||
│ Handler │ │ ChannelCore:: │
|
||||
│ │ │ register_openable) │
|
||||
│ Gets Connection │ │ │
|
||||
│ loops accept_bi │ │ Gets BiStream per │
|
||||
│ │ │ session │
|
||||
└─────────────────┘ └───────────────────────┘
|
||||
```
|
||||
|
||||
The protocol crate doesn't know which path it's on. It takes a
|
||||
`BiStream` (or `AsyncRead + AsyncWrite`) and drives the session. The
|
||||
two adapters are thin and live either in the protocol crate (behind
|
||||
feature flags) or in the downstream alknet crate.
|
||||
|
||||
## References
|
||||
|
||||
- `@alkdev/alknet: docs/architecture/` — the source architecture docs
|
||||
|
||||
@@ -120,50 +120,38 @@ design.
|
||||
|
||||
## Crate dependencies
|
||||
|
||||
alkcall is a single crate with two internal subsystems (call + channels).
|
||||
The channels subsystem is split into two modules:
|
||||
|
||||
- **`channels` (core)** — the pure multiplexer: wire format, demux/mux,
|
||||
`ChannelBidiStreamSource`, `ChannelManager`. ALPN-blind,
|
||||
call-protocol-blind, transport-blind. This is where the "streams are
|
||||
streams" insight lives.
|
||||
- **`channels` (call coupling)** — channel 0 pre-negotiation as
|
||||
`alknet/call` (ADR-036), the lifecycle operations (ADR-037) registered
|
||||
on the call protocol's `OperationRegistry`, `ChannelCore` (ADR-047 §3),
|
||||
and `ChannelClient` (ADR-043). This is where the call-protocol coupling
|
||||
lives, isolated from the pure multiplexer.
|
||||
|
||||
Downstream crates depend on alkcall as a single dependency:
|
||||
|
||||
```
|
||||
alknet-channels-core
|
||||
├── alknet-core (ProtocolHandler, Connection, HandlerRegistry,
|
||||
│ BidiStreamSource, BiStream, AuthContext)
|
||||
├── tokio (spawn, mpsc, io)
|
||||
├── bytes (Bytes for chunk payloads)
|
||||
├── async-trait
|
||||
├── thiserror
|
||||
└── tracing
|
||||
alktty / alktunnels / alktrader (protocol crates)
|
||||
└── alkcall (call + channels protocols, no networking)
|
||||
|
||||
alknet-channels-call
|
||||
├── alknet-channels-core (ChannelManager, ChannelsAdapter,
|
||||
│ ChannelBidiStreamSource, ChannelClient)
|
||||
├── alknet-call (OperationRegistry, HandlerKind, make_handler,
|
||||
│ make_streaming_handler, CallError, ResponseEnvelope)
|
||||
└── tokio
|
||||
|
||||
alknet-hub (the existing hub crate — consumes channels)
|
||||
├── alknet-channels-call
|
||||
└── alknet-call (from_call, CallAdapter, forwarded_for — ADR-042)
|
||||
|
||||
worker crates (any crate that dials a hub — consumes channels)
|
||||
└── alknet-channels-call (ChannelClient — ADR-043)
|
||||
alknet / alknode (networking + composition)
|
||||
├── alkcall
|
||||
├── alktty (optional — whichever protocol crates are wired in)
|
||||
├── alktunnels (optional)
|
||||
└── quinn / tokio-rustls (transport)
|
||||
```
|
||||
|
||||
`alknet-channels-core` is the pure multiplexer — wire format, demux/mux,
|
||||
`ChannelBidiStreamSource`, `ChannelManager`. It depends on `alknet-core`
|
||||
only. No `alknet-call` dependency. ALPN-blind, call-protocol-blind,
|
||||
transport-blind. This is where the "streams are streams" insight lives.
|
||||
|
||||
`alknet-channels-call` is the call-protocol coupling — channel 0
|
||||
pre-negotiation as `alknet/call` (ADR-036), the four lifecycle operations
|
||||
(ADR-037) registered on the call protocol's `OperationRegistry`, and
|
||||
`ChannelClient` (ADR-043). This is where the call-protocol coupling lives,
|
||||
isolated from the pure multiplexer.
|
||||
|
||||
The hub and worker are **consumers**, not sub-crates. The existing
|
||||
`alknet-hub` crate IS the channels hub — it depends on `channels-call` and
|
||||
uses channels as its substrate, with the relay logic (ADR-042) living in
|
||||
`alknet-hub` alongside its existing peer lifecycle and service discovery
|
||||
responsibilities. A worker is any crate that uses `ChannelClient` to dial.
|
||||
There are no `channels-hub` or `channels-worker` sub-crates.
|
||||
|
||||
See ADR-044 for the full decomposition rationale.
|
||||
The hub and worker are **consumers** of alkcall, not sub-crates. The
|
||||
downstream alknet crate IS the hub — it depends on alkcall and uses
|
||||
channels as its substrate, with the relay logic (ADR-042) living
|
||||
alongside its peer lifecycle and service discovery responsibilities.
|
||||
A worker is any crate that uses `ChannelClient` to dial. There are no
|
||||
`channels-hub` or `channels-worker` sub-crates.
|
||||
|
||||
## ALPN
|
||||
|
||||
@@ -209,62 +197,54 @@ The async shell and `alknet-core` dep graph are not fully WASM-clean yet
|
||||
(transitive `getrandom`/`rand` deps) — this is an implementation concern,
|
||||
not an architecture concern. The sync core's WASM compatibility is validated.
|
||||
|
||||
## Relationship to existing crates
|
||||
## Relationship to downstream crates
|
||||
|
||||
alkcall is a pure protocol crate — no networking, no transport
|
||||
dependencies. Downstream crates compose on top of it. See
|
||||
[README.md](README.md) §"Roles and Composition" for the full dependency
|
||||
layering and the producer/consumer/hub/spoke role definitions.
|
||||
|
||||
### Protocol crates (alktty, alktunnels, etc.)
|
||||
|
||||
Protocol crates depend only on alkcall. They provide:
|
||||
|
||||
- **Producer half** — a `register_*()` function that takes an
|
||||
`&mut OperationRegistry` and registers ops with their handlers. For
|
||||
channels-based protocols, an `OpenHandler` factory registered via
|
||||
`ChannelCore::register_openable`.
|
||||
- **Consumer half** — a typed client wrapper around `CallConnection`
|
||||
(or `ChannelClient`) that exposes the crate's ops as async methods.
|
||||
|
||||
A protocol crate can support two paths for its wire format:
|
||||
|
||||
| Path | How it connects | Wire format |
|
||||
|------|----------------|-------------|
|
||||
| Direct ALPN | `ProtocolHandler` on its own ALPN (e.g. `alknet/tty`), gets a `Connection`, loops `accept_bi` | Own wire format (e.g. TTY's 5-byte header) |
|
||||
| Through channels | Registered via `ChannelCore::register_openable`, gets a `BiStream` per session | Own wire format rides inside channels 8-byte payload |
|
||||
|
||||
The protocol crate doesn't know which path it's on — it takes a
|
||||
`BiStream` (or `AsyncRead + AsyncWrite`) and drives the session. The
|
||||
two adapters are thin and live either in the protocol crate (behind
|
||||
feature flags) or in the downstream alknet crate.
|
||||
|
||||
### alknet-call
|
||||
|
||||
Unchanged. The call protocol remains JSON-only, `EventEnvelope`-based. It
|
||||
runs on channel 0 exactly as on a top-level `alknet/call` connection. The
|
||||
`CallAdapter` receives a `Connection` backed by channel-0 chunk reassembly
|
||||
and dispatches operations — it doesn't know it's inside channels. The call
|
||||
protocol's `EventEnvelope` framing (ADR-014) is the channels payload; the
|
||||
channels layer carries it transparently.
|
||||
The call protocol runs on channel 0 exactly as on a top-level
|
||||
`alknet/call` connection. The `Dispatcher` receives a `Connection`
|
||||
backed by channel-0 chunk reassembly and dispatches operations — it
|
||||
doesn't know it's inside channels. The call protocol's `EventEnvelope`
|
||||
framing (ADR-014) is the channels payload; the channels layer carries
|
||||
it transparently.
|
||||
|
||||
What changes: the call protocol gains a new class of operations — channel
|
||||
lifecycle (ADR-037). These are registered on the `OperationRegistry` at
|
||||
assembly time and dispatched through the existing `OperationContext` /
|
||||
`AccessControl::check` path.
|
||||
### alknet-hub / alknode
|
||||
|
||||
### alknet-tty
|
||||
|
||||
The TTY crate gains a `channels` feature that enables inside-channels
|
||||
mode. In both direct mode (`alknet/tty` ALPN on a top-level connection) and
|
||||
inside-channels mode (`channel/open` with ALPN `alknet/tty`), the TTY
|
||||
adapter uses its own 5-byte wire format (ADR-052). The two modes differ
|
||||
only in *where the `BiStream` comes from* — a top-level connection vs a
|
||||
channels-backed `Connection`. The same `wire.rs` code runs in both modes
|
||||
(ADR-077): the channels layer strips its 8-byte header and hands TTY the
|
||||
payload bytes; TTY parses its 5-byte header from the payload. The
|
||||
`TtyBackend` trait and `TtyHandle` are unchanged; backends don't know
|
||||
which mode the adapter is in.
|
||||
|
||||
### alknet-ssh (future)
|
||||
|
||||
SSH as a channel type: an `alknet/ssh` channel carries the SSH binary
|
||||
protocol on its `BiStream`. The channels layer hands the reassembled
|
||||
`BiStream` to `SshAdapter`, which feeds it to russh. SSH as a channels
|
||||
transport: an SSH `direct-tcpip` channel could carry a channels connection
|
||||
(channels-over-SSH). The SSH crate doesn't need to know about channels —
|
||||
it implements `ProtocolHandler` for `alknet/ssh` and accepts a
|
||||
`Connection`. SSH multiplexes internally (its own channel protocol rides
|
||||
the channels payload transparently).
|
||||
|
||||
### alknet-docker
|
||||
|
||||
Docker lifecycle operations are call operations on channel 0 (unchanged
|
||||
from ADR-058). Interactive exec/attach opens a TTY channel via
|
||||
`channel/open` with ALPN `alknet/tty` and backend `docker`. No separate
|
||||
`alknet/tty` connection needed — one `alknet/channels` connection handles
|
||||
both JSON operations and raw TTY sessions.
|
||||
|
||||
### alknet-hub
|
||||
|
||||
The hub is the primary consumer. With channels, the hub holds one channels
|
||||
connection per leg (browser↔hub, hub↔spoke) and relays channels between
|
||||
them. The hub translates `channel/open` on channel 0 (re-issues on the
|
||||
spoke leg with `forwarded_for` — ADR-042) and byte-forwards data channels
|
||||
with `channel_id` rewrite. The hub's complexity collapses from
|
||||
O(protocols × transports × spokes) to O(channels).
|
||||
The hub is the primary consumer. With channels, the hub holds one
|
||||
channels connection per leg (browser↔hub, hub↔spoke) and relays
|
||||
channels between them. The hub translates per-ALPN open ops on channel
|
||||
0 (re-issues on the spoke leg with `forwarded_for` — ADR-042) and
|
||||
byte-forwards data channels with `channel_id` rewrite. The hub's
|
||||
complexity collapses from O(protocols × transports × spokes) to
|
||||
O(channels).
|
||||
|
||||
## Design Decisions
|
||||
|
||||
@@ -272,18 +252,18 @@ All design decisions are documented as ADRs in [decisions/](decisions/).
|
||||
|
||||
| ADR | Decision | Summary |
|
||||
|-----|----------|---------|
|
||||
| [071](decisions/071-channels-wire-format.md) | channels Wire Format | 8-byte chunk header (amended by ADR-035); channels layer has no `stream_type` concept; one-way door |
|
||||
| [093](decisions/093-channels-pure-channel-multiplexing.md) | channels Pure Channel Multiplexing | The umbrella decision: 8-byte header, no `stream_type`, `into_sub_streams` removed, `BiStream`-only, TTY always 5-byte |
|
||||
| [072](decisions/072-channel-0-pre-negotiated-call.md) | Channel 0 Pre-Negotiated | Channel 0 = `alknet/call` |
|
||||
| [073](decisions/073-channel-lifecycle-operations.md) | Channel Lifecycle Operations | `channel/open`/`close`/`control`/`resources/subscribe`; subscribe not poll; `direction` pinned |
|
||||
| [074](decisions/074-channelconnection-bidistreamsource.md) | ChannelConnection | Per-channel `BidiStreamSource`; yield-once `accept_bi` (amended by ADR-035 — `into_sub_streams` removed) |
|
||||
| [075](decisions/075-channelsadapter-and-channelmanager.md) | ChannelsAdapter and ChannelManager | Substrate-agnostic demux loop; REQ-CH-01..04 |
|
||||
| [076](decisions/076-backpressure-channel-limits-id-reuse.md) | Backpressure, Limits, ID Reuse | Bounded-buffer (1 MiB), 256-channel cap, monotonic IDs |
|
||||
| [077](decisions/077-tty-inside-channels.md) | TTY Inside Channels | TTY's two modes (direct vs channels); TTY always uses its 5-byte format, carried transparently in the channels payload |
|
||||
| [078](decisions/078-two-pump-shutdown-on-completion.md) | Two-Pump Pattern | Shutdown-on-completion contract; handler-level |
|
||||
| [079](decisions/079-hub-relay-translate-not-forward.md) | Hub Relay | Translate channel 0, byte-forward data channels with ID rewrite |
|
||||
| [080](decisions/080-channelclient.md) | ChannelClient | Client side; transport-agnostic `from_connection` primary; dial lives in `AlknetClient` (ADR-045, resolves OQ-55) |
|
||||
| [081](decisions/081-channels-subcrate-decomposition.md) | Sub-Crate Decomposition | `channels-core` (pure multiplexer) / `channels-call` (call coupling + ChannelClient); hub and worker are consumers |
|
||||
| [034](decisions/034-channels-wire-format.md) | channels Wire Format | 8-byte chunk header (amended by ADR-035); channels layer has no `stream_type` concept; one-way door |
|
||||
| [035](decisions/035-channels-pure-channel-multiplexing.md) | channels Pure Channel Multiplexing | The umbrella decision: 8-byte header, no `stream_type`, `into_sub_streams` removed, `BiStream`-only, TTY always 5-byte |
|
||||
| [036](decisions/036-channel-0-pre-negotiated-call.md) | Channel 0 Pre-Negotiated | Channel 0 = `alknet/call` |
|
||||
| [037](decisions/037-channel-lifecycle-operations.md) | Channel Lifecycle Operations | `channel/open`/`close`/`control`/`resources/subscribe`; subscribe not poll; `direction` pinned |
|
||||
| [038](decisions/038-channelconnection-bidistreamsource.md) | ChannelConnection | Per-channel `BidiStreamSource`; yield-once `accept_bi` (amended by ADR-035 — `into_sub_streams` removed) |
|
||||
| [039](decisions/039-channelsadapter-and-channelmanager.md) | ChannelsAdapter and ChannelManager | Substrate-agnostic demux loop; REQ-CH-01..04 |
|
||||
| [040](decisions/040-backpressure-channel-limits-id-reuse.md) | Backpressure, Limits, ID Reuse | Bounded-buffer (1 MiB), 256-channel cap, monotonic IDs |
|
||||
| [041](decisions/041-per-identity-channel-cap.md) | Per-Identity Channel Cap | 256 per `PeerId`, enforced via `ChannelLifecyclePolicy`; per-connection `max_channels` reframed as a memory bound |
|
||||
| [042](decisions/042-hub-relay-translate-not-forward.md) | Hub Relay | Translate channel 0, byte-forward data channels with ID rewrite |
|
||||
| [043](decisions/043-channelclient.md) | ChannelClient | Client side; transport-agnostic `from_connection` primary; dial lives in `AlknetClient` (ADR-045, resolves OQ-55) |
|
||||
| [044](decisions/044-channels-subcrate-decomposition.md) | Sub-Crate Decomposition | `channels-core` (pure multiplexer) / `channels-call` (call coupling + ChannelClient); hub and worker are consumers |
|
||||
| [047](decisions/047-openable-alpns-are-operations.md) | Openable ALPNs Are Operations | `channel/open` dissolves into per-ALPN ops; `ChannelCore` wrapper; connection-owner allocates `channel_id` |
|
||||
|
||||
## Open Questions
|
||||
|
||||
@@ -294,7 +274,7 @@ Key questions affecting this crate:
|
||||
— extracted as `alknet-client` with three dial methods.
|
||||
`ChannelClient`'s API is transport-agnostic (`from_connection`); the
|
||||
dial is the shared seam, now extracted. See
|
||||
[ADR-045](decisions/089-alknetclient-native-dial-seam.md).
|
||||
[ADR-045](decisions/045-alknetclient-native-dial-seam.md).
|
||||
- **OQ-56** (deferred(scope)): Full channel-level flow-control windowing —
|
||||
bounded-buffer is decided (ADR-040); full windowing is an extension
|
||||
blocked on a real HOL-blocking deployment observation.
|
||||
|
||||
+39
@@ -28,6 +28,45 @@
|
||||
//! pre-negotiated as `alknet/call`; channels 1..N are opened via
|
||||
//! per-ALPN open ops (`channels/<alpn>/sub`, `channels/<alpn>/pub`)
|
||||
//! on channel 0 (ADR-047).
|
||||
//!
|
||||
//! ## Downstream composition
|
||||
//!
|
||||
//! alkcall is a pure protocol crate — no networking, no transport
|
||||
//! dependencies. Downstream crates compose on top of it in a layered
|
||||
//! dependency chain. See `docs/architecture/README.md` §"Roles and
|
||||
//! Composition" for the full layering diagram and role definitions.
|
||||
//!
|
||||
//! ### The four roles
|
||||
//!
|
||||
//! | Role | Call protocol | Channels protocol |
|
||||
//! |------|---------------|-------------------|
|
||||
//! | **Producer** | Registers ops on an `OperationRegistry`, runs a `Dispatcher` | Runs a `ChannelsAdapter`, registers openable ALPNs via `ChannelCore::register_openable` |
|
||||
//! | **Consumer** | Uses `CallConnection` to call ops, uses `from_call` to discover/import remote ops | Uses `ChannelClient` to open channels via `call_open_op` + `open_channel` |
|
||||
//! | **Hub** | Both: runs a `Dispatcher` for ops it produces, holds `CallConnection`s to spokes for ops it consumes | Both: runs a `ChannelsAdapter` for inbound connections, holds `ChannelClient`s to spokes |
|
||||
//! | **Spoke / Worker** | Both: produces ops (its own services), consumes hub ops | Both: produces channels (TTY, tunnel), may consume hub channels |
|
||||
//!
|
||||
//! A single process can be a producer of some ops, a consumer of others,
|
||||
//! a channel opener for TTY, and a channel acceptor for tunnels — all on
|
||||
//! the same `alknet/channels` connection.
|
||||
//!
|
||||
//! ### Pattern for protocol crates
|
||||
//!
|
||||
//! A protocol crate (e.g. alktty, alktunnels) depends only on alkcall
|
||||
//! and provides two halves:
|
||||
//!
|
||||
//! 1. **Producer half** — a `register_*()` function that takes an
|
||||
//! `&mut OperationRegistry` and registers ops with their handlers.
|
||||
//! For channels-based protocols, an `OpenHandler` factory registered
|
||||
//! via [`channels::operations::ChannelCore::register_openable`].
|
||||
//! 2. **Consumer half** — a typed client wrapper around
|
||||
//! [`protocol::connection::CallConnection`] (or
|
||||
//! [`channels::client::ChannelClient`]) that exposes the crate's ops
|
||||
//! as async methods.
|
||||
//!
|
||||
//! The protocol crate doesn't know whether it's running on a hub, a
|
||||
//! spoke, or a standalone process. The networking + composition layer
|
||||
//! (alknet/alknode) wires protocol crates' producers into registries
|
||||
//! and consumers into clients.
|
||||
|
||||
pub mod channels;
|
||||
pub mod client;
|
||||
|
||||
Reference in new issue
Block a user