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
|
||||
|
||||
Reference in new issue
Block a user