Channel 0 was dead in both directions (C-01): the call protocol's
stream-per-request model (open_bi per call) is incompatible with
channel 0's single yield-once BiStream — every call_open_op failed
with StreamClosed on the connect side, and channel 0's Connection was
a black hole on the accept side.
The fix is single-stream call mode (ADR-036 amendment): all
EventEnvelope frames are multiplexed on channel 0's one BiStream.
Changes:
- CallConnection gains single_stream_writer: Option<Arc<SharedFrameWriter>>
and new_single_stream() constructor. call_with_payload,
subscribe_with_payload, publish_with_payload, and abort branch on
is_single_stream() — in single-stream mode they write frames through
the shared writer (mutex-serialized) instead of opening a fresh
open_bi per call.
- Dispatcher::run_loop_single_stream reads frames off channel 0's read
half, dispatches call.requested, writes responses through the shared
writer, and routes in-flight call.published/call.completed/
call.aborted to the matching Pub sink's chunk_tx by request_id.
- ChannelsAdapter::handle's InstallChannelZero hook now receives
channel 0's Connection (built by the adapter) and runs the
single-stream dispatch loop on it — closing the accept-side black
hole. Mux runner is spawned BEFORE install_channel_zero so
mux.register(0) can complete.
- ChannelClient::from_connection uses CallConnection::new_single_stream
and spawns a read pump (read_single_stream_until_closed) that routes
channel-0 response frames into the PendingRequestMap via
dispatch_envelope — closing the connect-side StreamClosed path.
- ADR-036 amendment records the single-stream-mode decision (two-way
door: implementation detail, wire format unchanged).
Acceptance gate (C-25 #1): three end-to-end tests wire
ChannelClient ↔ ChannelsAdapter over a real tokio::io::duplex pair
carrying the channels 8-byte chunk header wire format:
- channel_0_end_to_end_call_round_trip: a Query op round-trip
- channel_0_end_to_end_unknown_op_returns_not_found: NOT_FOUND
- channel_0_end_to_end_publish_delivers_chunks: a Pub op with 3 chunks
Verification: 437 tests pass (was 434; +3 e2e), clippy clean, fmt
clean, doc warnings unchanged (4, pre-existing C-09).