The single STREAM_CONTROL = 3 was documented as bidirectional but the
adapter had to ignore Exit from the client because the two directions
were indistinguishable on the same stream_type — half-duplex in
disguise. Phase 7 splits it into two halves so the bidirectionality is
literal on the wire.
Changes:
- wire.rs: STREAM_CTRL_IN = 3 (client→server), STREAM_CTRL_OUT = 4
(server→client); InvalidStreamType bound > 3 → > 4; Chunk::control
→ Chunk::ctrl_in/ctrl_out; ChunkWriter::write_control_json →
write_ctrl_in_json/write_ctrl_out_json; tests split accordingly.
- control.rs: ControlMessage doc updated with the stream_type column;
JSON shape unchanged.
- adapter.rs: pump_client_to_backend dispatches on STREAM_CTRL_IN
(Resize/Signal/Eof; Exit on ctrl_in is a protocol violation,
ignored); send_exit_chunk emits on STREAM_CTRL_OUT; STREAM_CTRL_OUT
from the client is a protocol violation, ignored. 3 new tests for
the direction enforcement; existing tests updated to the new
stream_types.
- negotiation.rs: framing-disambiguation doc updated (server-sent
stream_type set is {1, 2, 4}).
- alknet-tty-local/tests: common/mod.rs, pty.rs, pipe.rs updated to
the new constants.
Specs:
- ADR-052 amended (§4a 'Control channel split (Phase 7 amendment)').
- tty-wire.md + tty-adapter.md updated (last_updated 2026-07-18).
Verification:
- cargo test -p alknet-tty: 65 passed (was 61; +4 new tests).
- cargo test -p alknet-tty-local: 19 passed.
- cargo test --workspace --all-features: 1017 passed, 0 failed.
- cargo clippy --workspace --all-features: clean.
- cargo fmt --all: clean.
The to_mcp test helper full_registry_with_ops always registered ops
with HandlerKind::Once(make_echo_handler()) regardless of op_type.
When the search_returns_access_control_filtered_ops_excluding_subscriptions
test passed OperationType::Subscription for "events/stream", the
registry's kind validation (tightened in commit 9c81129, ADR-049)
rejected it with "handler kind mismatch: Subscription requires
HandlerKind::Stream (got HandlerKind::Once)" — panicking in
register().unwrap() before the test could run.
This was a pre-existing test-helper bug (predates Phase 6; verified by
stashing Phase 6 and reproducing on the develop baseline) but it
blocked Phase 9's 'Done when' criterion (cargo test -p alknet-http
passes).
Fix: added a handler_kind_for(op_type) helper that branches on op_type
(HandlerKind::Stream(make_echo_streaming_handler()) for Subscription,
HandlerKind::Once(make_echo_handler()) for Query/Mutation) and used
it in both register loops of full_registry_with_ops. The streaming
echo handler yields the input back as a single call.responded frame —
sufficient because the test only verifies that the MCP search tool
*excludes* Subscription ops from its listing; it never invokes the
handler.
Result: cargo test --workspace --all-features is fully green (1008
tests, 0 failures). Phase 9's 'Done when' criterion is met. The
findings doc's Phase 9 entry is updated to record the fix.
Closes Phase 9.
Phase 6 (BiStream unification) is complete (commit b60a584). Update the
findings doc to reflect what actually shipped and how it overlaps with
the remaining phases:
- Phase 6: marked Done. Added a 'What was done (cross-crate)' section
listing the actual changes per crate (alknet-core, alknet-http,
alknet-tty, alknet-call), so the doc records the implementation
shape not just the plan.
- Phase 9: marked Done — subsumed by Phase 6. ADR-092's migration
step 2 includes the alknet-http call-site update (drop QuicStream),
so Phase 6's call-site work landed Phase 9's deliverable. grep
confirms no QuicStream/QuicStreamDuplex remains.
- Phase 9: added a 'Pre-existing test failure to fix in a follow-up'
note for the to_mcp::tests::search_returns_access_control_filtered_ops_excluding_subscriptions
failure. The bug is in the test helper full_registry_with_ops
(to_mcp.rs:501-516) — it always uses HandlerKind::Once even for
OperationType::Subscription, which the registry's kind validation
(tightened in commit 9c81129, ADR-049) rejects. Predates Phase 6
(verified by stashing); blocks Phase 9's 'Done when' criterion
(cargo test -p alknet-http passes) and needs a small follow-up.
- Intermediate-states table: updated rows 6, 7, 8, 9. Phase 6 and 9
marked Done; Phase 7 and 8 noted as unchanged by Phase 6 (Phase 7's
work is in wire.rs/control.rs which Phase 6 didn't touch; Phase 8 is
docs-only).
Insert four new phases between the call prune (5) and the old http fix:
- Phase 6: Core stream unification (BiStream as handler leaf, ADR-092)
- Phase 7: TTY control-channel bidirectionality fix (STREAM_CTRL_IN/OUT)
- Phase 8: Channels spec cleanup (8-byte wire format, no stream_type)
- Phase 9: HTTP fix — drop QuicStream wrapper (now unnecessary after BiStream)
The old Phase 6 (http fix, deferred) is replaced — BiStream makes the
QuicStream wrapper dead code. Total: 10 phases (0-9).
SendStream implements only AsyncWrite, RecvStream implements only
AsyncRead. The QuicStream wrapper (44 lines) is a necessary adapter
that combines the split pair from accept_bi() into a single
AsyncRead+AsyncWrite type. Phase 6 is deferred — removing it would
require a new BidiStream abstraction or restructuring HttpAdapter::handle.
ADR-091 amended 2026-07-17: CallCredentials removed (not retained in
alknet-call). Trace showed CallCredentials.auth_token had no reader
(connect() read only tls_identity + remote_identity; spawn_dispatch
takes no credentials; from_call's credentials_auth_token was a
different type, always None, never connected). auth_token is a
per-request payload field — browsers send it in the WS payload; the
HTTP gateway resolves bearer → Identity at its boundary.
from_call's credentials_auth_token dead path removed in the same pass
(OpSummary field, handler params, build_forwarded_payload param, and
the two tests asserting the never-exercised Some path).
ADR-089 §5 further amended, ADR-080 noted, all spec READMEs and
overview updated. Migration plan (findings.md) corrected: Phase 5
prune now includes CallCredentials removal + from_call dead-path
removal; test audit corrected (4 unchanged + 2 move to core, not 6
unchanged); integration-test split documented; all 'or' hedges
resolved.
The Phase 5 'call_client.rs after prune' list inaccurately described
ConnectionCredentials as 'removed from call_client.rs' — it is a new
type in alknet-core, not a pre-existing type in call. Reworded to:
RemoteIdentity is removed (moved to core in Phase 0); CallCredentials
is restructured (transport dimensions leave for ConnectionCredentials
in core, auth_token stays). The source map table row for lines 40-88
is also corrected: the destination is a split, not a uniform move to
alknet-core.
ADR-091: ConnectionCredentials — decouple the dial credential bundle
from the call protocol. CallCredentials (call-protocol-level, carries
auth_token) was being used as the dial's credential type, coupling the
dial to the call protocol. The auth_token is a hub-layer identity
correlation mechanism (browsers, alknet/register), not a transport
credential. ConnectionCredentials (transport-level: local_identity +
remote_identity) is the dial's credential bundle; all three dial
signatures unify on &ConnectionCredentials; dial_iroh's node_id
parameter is derived from remote_identity.fingerprint. CallCredentials
stays in alknet-call with auth_token. The shape is validated by the
future dial_ssh pattern (russh's check_server_key + authenticate_publickey
consume the same two dimensions).
Amends ADR-089 §3 (dial signatures) and §5 (move consequence), ADR-087
(input framing). Updates client/tls/call/core crate specs and the
extraction plan's Phase 0/3/4/5.
Migration plan cleanups (findings.md):
- Remove duplicated ordering-rationale bullets (copy-paste artifact)
- TL;DR: six phases -> seven (Phase 0 promoted); four compilable
intermediate states -> each phase leaves workspace compilable
- Remove inline 'Wait — that's 10, not 8' self-correction; fix
Category B header to (10 tests)
- Replace contradictory line ranges in Net Phase 5 test impact with
name-based references
- Decide quinn feature fate: removed (not no-op)
- Note webpki-roots always-present per ADR-088 §5 in Phase 1 dep list