Files
alkhttp/tasks/adapters/from-wss.md
T
glm-5.3-flash 3a906cbd6a feat(adapters): from_wss consumer adapter behind the wss feature (ADR-070)
- 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.
2026-08-28 14:54:09 +00:00

91 lines
4.1 KiB
Markdown

---
id: adapter-from-wss
name: from_wss consumer adapter (wss feature)
status: completed
depends_on: [ws-byte-adapter, ws-upgrade-session]
scope: moderate
risk: high
impact: component
level: implementation
tags: [adapters, phase-3]
---
## 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
- [x] Adapter behind `wss` feature; base crate compiles without it
- [x] Round-trip test: from_wss consumer ↔ ws-upgrade-session server (both halves of the adapter exercised together)
- [x] Discovered ops invoke correctly; identity/ACL enforced end-to-end
- [x] Connection drop → in-flight calls fail retryable, no hang
- [x] Credentials flow from Capabilities only (no env-var reads)
- [x] `cargo test --all-features` passes
## 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:
1. **WS identity now propagates to channel 0** (src/websocket/
upgrade.rs): ChannelsAdapter constructs channel 0's `Connection`
fresh, 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.
2. **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 new
`split_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).