docs(arch): channels substrate simplification + stream_type decomposition + sub-crate split

Three simplifications to the channels spec, all flowing from the review:

1. Substrate simplification (ADR-071 revised): the 9-byte chunk header is
   used in ALL substrates — in-line (TCP+TLS, WebTransport), native (QUIC
   bidi streams), and multi-connection. The ChannelsAdapter reads headers
   off every bidi stream it accepts; the transport's native multiplexing
   is a performance optimization (independent flow-control windows), not a
   protocol change. One wire format, one code path, one handler experience.
   The channel_id in the header is the correlation key across substrates.

2. Stream_type decomposition (ADR-071 revised): every stream_type is
   unidirectional. Bidirectionality is two stream_types (write + read), not
   one 'bidirectional' stream_type. Grouped in threes: 0/1/2 = data
   write/read/err, 3/4/5 = control write/read/err, % 3 formula. This
   resolves the TTY control channel's 'not actually bidirectional' flaw —
   control is now 3 (write, client→server) + 4 (read, server→client), each
   with its own reassembly buffer and EOF. Channel 0 uses [0,1] (call frames
   bidirectional via 0=in, 1=out). TTY uses [0,1,2,3,4]. ADR-072, 073, 074,
   077 updated for the new stream_type assignments.

3. Sub-crate decomposition (ADR-081 new): channels-core (pure multiplexer —
   wire format, demux/mux, ChannelBidiStreamSource, ChannelManager; depends
   on alknet-core only, no call dependency, ALPN-blind) / channels-call
   (channel 0 pre-negotiation + lifecycle op registrations; depends on
   channels-core + alknet-call) / channels-hub (relay) / channels-worker
   (ChannelClient). Isolates the call-protocol coupling from the pure
   multiplexer so the dependency graph is honest.

ADR-077 (TTY inside channels) updated: TTY now uses 5 sub-streams [0,1,2,3,4]
with control properly bidirectional via 3/4; amends ADR-052's stream_type
assignments for direct mode too (direct alknet/tty now uses 0-4, not 0-3).

Spec docs updated: channels-wire, channels-connection, channels-adapter,
channel-operations, overview, README.
This commit is contained in:
glm-5.2 committed 2026-07-12 15:47:42 +00:00
1 parent 2313c51f12
commit fd83fc1685
14 files changed
+734 -251

No files matched your search

@@ -39,27 +39,37 @@ impl ProtocolHandler for ChannelsAdapter {
async fn handle(&self, connection: Connection, auth: &AuthContext)
-> Result<(), HandlerError>
{
// 1. One bidi stream carries all channels.
// 1. Channel 0 is pre-negotiated as alknet/call (ADR-072).
// The first bidi stream the transport yields is channel 0.
let (send, recv) = connection.accept_bi().await?;
// 2. Channel 0 is pre-negotiated as alknet/call (ADR-072).
// Construct its reassembly buffers, wrap as a Connection via
// from_source(ChannelBidiStreamSource), and hand to the
// CallAdapter (looked up in the registry, same as every ALPN).
self.manager.preinstall_channel_0(send, recv, auth).await?;
// 3. Run the demux loop: read 9-byte headers, route payloads to
// per-(channel_id, stream_type) reassembly buffers.
self.manager.run_demux_loop(recv).await
// 2. Accept remaining bidi streams and read 9-byte headers off each.
// On an in-line transport (TCP+TLS, WebTransport), accept_bi()
// yields once and the header demuxes N channels from that stream.
// On QUIC native, accept_bi() yields repeatedly — each stream
// carries one logical channel, and the header provides
// stream_type + channel_id correlation. Same code path, same
// wire format (ADR-071 §substrate modes).
self.manager.run_demux_loop(connection).await
}
}
```
The `preinstall_channel_0` step is the only special case: it constructs the
reassembly buffers for `channel_id = 0`, wraps them as a `Connection` (via
`Connection::from_source` with a `ChannelBidiStreamSource` — ADR-074), and
hands that `Connection` to the `CallAdapter` — exactly as if `alknet/call`
had been the top-level ALPN. The `CallAdapter` is none the wiser.
The `preinstall_channel_0` step constructs the reassembly buffers for
`channel_id = 0` using stream_types [0, 1] (ADR-072), wraps them as a
`Connection` (via `Connection::from_source` with a `ChannelBidiStreamSource`
— ADR-074), and hands that `Connection` to the `CallAdapter` — exactly as if
`alknet/call` had been the top-level ALPN. The `CallAdapter` is looked up in
the same `HandlerRegistry` as every other ALPN.
`run_demux_loop` continues accepting bidi streams from the transport. For
each stream, it reads 9-byte headers and routes payloads to the matching
`(channel_id, stream_type)` reassembly buffer. On an in-line transport,
there is only one stream (channel 0 rides inside it via the header); the
header demuxes all channels. On QUIC, each subsequent stream is a new
channel; the header's `channel_id` correlates it. The loop is the same;
only the transport's stream count differs.
### `ChannelManager` — the shared state