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.
This commit is contained in:
2026-08-28 14:54:09 +00:00
parent 4ac337c3a5
commit 3a906cbd6a
6 changed files with 824 additions and 19 deletions
+53 -9
View File
@@ -1,7 +1,7 @@
---
id: adapter-from-wss
name: from_wss consumer adapter (wss feature)
status: pending
status: completed
depends_on: [ws-byte-adapter, ws-upgrade-session]
scope: moderate
risk: high
@@ -25,12 +25,12 @@ frame on channel 0 → correlate by id. Bearer token from Capabilities
## Acceptance Criteria
- [ ] Adapter behind `wss` feature; 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-features` passes
- [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
@@ -40,8 +40,52 @@ frame on channel 0 → correlate by id. Bearer token from Capabilities
## Notes
> Agent fills during implementation.
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
> Agent fills on completion.
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).