ADR-076 framed the per-connection max_channels=256 cap as the DoS
defense, but a peer can open an unbounded number of transport
connections, so a per-connection cap bounds a connection's
reassembly-buffer cost, not a peer's total channels. The only coherent
unit for a channel DoS defense is the identity.
ADR-094 records the corrected design: a ChannelLifecyclePolicy trait
in channels-call (where the identity is already on OperationContext),
consulted by the channel/open handler (after AccessControl::check,
before allocation) and the channel/close handler (after the drain
completes). Default is PerIdentityChannelPolicy::new(256) — 256 per
PeerId across all the peer's connections, shared via Arc across every
channels connection a peer accepts. The cap is a peer concern (not
hub-specific), symmetric (both sides enforce), and lives in
channels-call because the channels layer is auth-blind by design
(ADR-075) — that is what makes it WASM-compatible, transport-agnostic,
and ALPN-blind.
For the hub-relay path (ADR-079), the spoke sees the hub as the direct
caller (ADR-032 — forwarded_for is metadata, not authority, for the cap
as for AccessControl::check), so the spoke caps the hub, not the
browser. A spoke serving a high-fan-out hub sets the hub peer's cap
higher via with_per_identity_caps — the spoke's own policy, not the
hub's. Recursive channels do not bypass the cap (the same policy can
be wired into the inner ChannelOperations).
ADR-076 is amended: the per-connection max_channels is reframed as a
per-connection memory bound (still returns channel:too_many_channels
when hit), the "DoS defense summary" table is removed, and the
"per-connection, not per-peer" line (the channels layer confessing a
hole and hoping the layer above would fill it) is corrected.
Spec docs updated to reference ADR-094: channel-operations.md gains a
"Per-identity channel cap" section (trait, default, enforcement
point, relay consequence, recursion); channels-adapter.md adds the
policy check as step 3 of the channel/open handler and the decrement
in channel/close; hub README adds the channel_policy field on Hub,
the with_channel_policy builder, a dedicated subsection, and the
inbound-peer-vs-hub-as-caller distinction; channels/README.md adds
ADR-094 to the Applicable ADRs table and a 9th Key Design Principle;
docs/architecture/README.md adds a Current State note and the ADR
table row.
Prune the channels spec to reflect the stream-unification resolution
(docs/research/stream-unification/findings.md): the channels wire format
goes from 9 bytes to 8 bytes, the channels layer no longer carries a
stream_type concept, into_sub_streams() is removed, and TTY always uses
its 5-byte format (carried transparently in the channels payload).
ADR-093 is the umbrella decision (the channels-layer consequence of
ADR-092's BiStream handler leaf): every channel is a BiStream, the
handler owns its sub-stream multiplexing, the channels layer routes by
channel_id only. Amends ADR-071 (8-byte header, no stream_type),
ADR-074 (into_sub_streams removed, accept_bi yields BiStream), reverses
ADR-077 (TTY always 5-byte), and the channels-facing clauses of
ADR-072/073/075/076/080/081. Adds ADR-092 forward-reference note
(into_sub_streams preservation subsequently reversed by ADR-093) and
the missing ADR-092 cross-reference on ADR-070.
Adds OQ-68 (add/strip API shape — built-in vs utility; the contract is
decided in ADR-093, the function surface is open; two-way door, low
priority, decision-ready when the channels crate's implementation
begins).
Rewrites the 7 channels spec docs (README, overview, channels-wire,
channels-connection, channels-adapter, channel-operations, channel-client)
to describe the post-amendment shape as current, with the 8-byte header,
the add/strip composition, single accept_bi accessor, BiStream per
channel, and TTY-always-5-byte.
Touch-up cross-references in hub README, client README, ADR-085, and
the OQ-45/47/65 question files (TTY-internal stream_type 3 →
STREAM_CTRL_IN; channels 9-byte → 8-byte).