- FromWss: dial wss:// -> split_tungstenite_to_bytes (client-side twin of the axum WS byte-adapter; one seam, both directions, OQ-01) -> Connection::from_bidi(b"alk/channels") -> alkcall ChannelClient (channel 0 install + dispatch loop) -> alkcall from_call importer. No protocol fork: specs mirror the remote, provenance FromCall. - Drop semantics (OQ-03 v1): session drop -> monitor fails all in-flight pendings retryable CONNECTION_CLOSED (WsPumps::read_eof Notify); no 30s-deadline hang. - Bearer token via constructor/assembly layer (ADR-014 no-env-vars). Production fix in the WS server half (upgrade.rs): the upgrade identity now propagates to channel 0's CallConnection (was AuthContext::anonymous -> dispatcher saw no identity, ACL checks ran unauthenticated; services/list filtered scoped ops for all callers). 9 in-module tests incl. full round-trip consumer<->server (both halves of the adapter together), ACL end-to-end, drop-no-hang. Verified: cargo test (227 lib default), --all-features (227 lib + 5 MCP + 10 WS integration), clippy -D warnings (both), fmt.
4.1 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| adapter-from-wss | from_wss consumer adapter (wss feature) | completed |
|
moderate | high | component | implementation |
|
Description
Implement from_wss per ADR-070 behind the wss feature
(tokio-tungstenite): dial wss:// endpoint → adapt the WS stream with
the shared byte-adapter → Connection::from_bidi(_, b"alk/channels") →
consumer half (alkcall ChannelClient machinery: channel 0 install +
client dispatch loop) → services/list + services/schema → one
forwarding HandlerRegistration per discovered op (FromCall provenance,
leaf, Internal default). Forwarding: serialize input → call.requested
frame on channel 0 → correlate by id. Bearer token from Capabilities
(no-env-vars). Reconnect policy: v1 = drop → retryable failures
(OQ-03 disposition).
Acceptance Criteria
- Adapter behind
wssfeature; base crate compiles without it - Round-trip test: from_wss consumer ↔ ws-upgrade-session server (both halves of the adapter exercised together)
- Discovered ops invoke correctly; identity/ACL enforced end-to-end
- Connection drop → in-flight calls fail retryable, no hang
- Credentials flow from Capabilities only (no env-var reads)
cargo test --all-featurespasses
References
- docs/architecture/decisions/070-from-wss-consumer-adapter.md
- docs/architecture/websocket.md (§The consumer-side mirror)
- alkcall ADR-028 (from_call pattern), ADR-043 (ChannelClient)
Notes
Two production fixes surfaced by the round-trip tests:
- WS identity now propagates to channel 0 (src/websocket/
upgrade.rs): ChannelsAdapter constructs channel 0's
Connectionfresh, so the identity set on the channels-layer connection never reached the dispatcher. install_channel_zero now sets auth.identity on the channel-0 connection before accept_bi, and run_channels_session passes the upgrade identity via AuthContext (was AuthContext::anonymous — identity: None). Without this, services/list ACL-filtered everything requiring scopes and every ACL check ran unauthenticated. - Connection-drop monitor: alkcall's client-side read pump (read_single_stream_until_closed) routes envelopes but does not fail pendings on EOF — fail_all lives in the dispatcher loops only. from_wss owns drop semantics per OQ-03 v1: the byte adapter exposes WsPumps::read_eof() (Notify fired on WS read EOF), a monitor selects on it + the session close signal and calls fail_all(CONNECTION_CLOSED, retryable) on channel 0's pending map.
Summary
Implemented from_wss per ADR-070 behind the wss feature:
src/adapters/from_wss.rs: FromWss (endpoint + optional bearer token + optional namespace prefix). WssSession::connect dials the WSS endpoint (Authorization: Bearer from the constructor token — assembly layer passes it from Capabilities; no env reads), adapts the tungstenite socket via the newsplit_tungstenite_to_bytes(the client-side twin of the axum server-side split — same chunk framing, same 1002 text-rejection, same EOF mapping; one seam, both directions, OQ-01), builds Connection::from_bidi(b"alk/channels"), runs alkcall ChannelClient (channel 0 install + demux/mux/read-pump), then reuses alkcall's from_call importer (services/list + services/schema + build_bundles) — no protocol fork (AGENTS.md convention 9). Provenance FromCall, leaf, Internal-by-default is the assembly layer's option; specs mirror the remote (ADR-017 §3).- Drop semantics (OQ-03 v1): WssSession drop → monitor fail_all retryable CONNECTION_CLOSED; in-flight calls resolve promptly, no 30s-deadline hang.
- 9 in-module tests: round-trip against the real server half (ws-upgrade-session axum server): discovery with admin-scope and unprivileged identities (ACL end-to-end), namespace prefix, echo invocation end-to-end, drop-no-hang, transport error classification, no-env-vars.
227 lib (default) / 227 lib (all-features) + 5 MCP + 10 WS integration green. clippy -D warnings both feature sets, fmt. Default build has no tungstenite client path (feature-gated).