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:
@@ -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).
|
||||
Reference in New Issue
Block a user