docs(http): add ADR-048 and websocket.md — WS carries native session, not gateway

Promote the WebSocket browser path from a section in http-server.md to a
first-class spec (websocket.md) and commit the contract-pattern decision
(ADR-048): a WS connection carries the native EventEnvelope call-protocol
session, not the HTTP gateway shape. The gateway endpoints are HTTP-only;
discovery on WS is via services/list/services/schema as ordinary call-protocol
ops; subscriptions project as native call.responded events (no SSE).

ADR-044 already decided WS as the v1 browser bidirectional path; ADR-048
clarifies the shape of what ADR-044 committed (§1 implies native session;
the ADR makes it an explicit implementer-visible rule). The from_wss adapter
(importing a remote node's ops over WS) is recorded as out-of-scope with a
concrete reversal trigger so it is not re-derived later.

Spec cleanup: http-server.md WS section collapsed to a stub pointer;
websocket.md Why section references ADRs rather than re-arguing them;
length-prefix decision made canonical (no prefix on WS — message boundary
is the delimiter); default upgrade path pinned (/alknet/call) with HTTP/2
extended CONNECT noted; indexes (README, http/README, overview) updated.
This commit is contained in:
2026-06-30 12:27:00 +00:00
parent bfd1621b9b
commit b71db99753
9 changed files with 859 additions and 90 deletions

View File

@@ -157,11 +157,15 @@ alknet-call (lean — no HTTP client, no HTTP server)
└── CallClient (outbound connection opener)
alknet-http (owns HTTP server + HTTP client)
├── HttpAdapter (axum server — inbound HTTP on h2/http1.1 + WS upgrade)
├── HttpAdapter (axum server — inbound HTTP on h2/http1.1 + WS upgrade route)
├── [WS upgrade → native session] (hands the WS message stream to the shared Dispatcher —
│ not an adapter; see websocket.md, ADR-048)
├── from_openapi (parse OpenAPI doc + reqwest forwarding handler)
├── to_openapi (generate OpenAPI doc from local registry)
├── from_mcp (feature-gated) (import remote MCP tools over streamable HTTP — reqwest)
── to_mcp (feature-gated) (expose local ops as MCP tools over streamable HTTP — axum)
── to_mcp (feature-gated) (expose local ops as MCP tools over streamable HTTP — axum)
└── from_wss (out of scope) (future: import a remote alknet node's ops over WS —
from_call-aligned, same-protocol; see websocket.md §"Future")
```
`alknet-call` never sees the HTTP client. The `from_openapi`/`from_mcp`
@@ -223,10 +227,16 @@ verified against this invariant. See ADR-014 and
## Architecture (component pointers)
- **[http-server.md](http-server.md)** — the `HttpAdapter` for `h2`/
`http/1.1` (+ WebSocket upgrade): how axum is run over a QUIC
`http/1.1` (+ the WS upgrade route): how axum is run over a QUIC
bidirectional stream, Bearer auth resolution, the `/healthz` raw route,
stealth decoy, the HTTP-to-call dispatch (ADR-036), and the WebSocket
browser bidirectional path (ADR-044).
stealth decoy, the HTTP-to-call dispatch (ADR-036/042/047), and the
WS upgrade route (which hands off to the native call-protocol session).
- **[websocket.md](websocket.md)** — the WebSocket browser bidirectional
path: native `EventEnvelope` call-protocol session (not the gateway
shape, ADR-048), framing, dispatch via the shared `Dispatcher`,
bidirectionality, connection-local Layer 2 overlay, the
browsers-are-not-peers rationale, streaming (native `call.responded`,
no SSE), and the deferred `from_wss` adapter.
- **[http-adapters.md](http-adapters.md)** — `from_openapi` (parse
OpenAPI, build forwarding handlers with `reqwest`) and `to_openapi`
(generate an OpenAPI doc from the registry's `External` operations).
@@ -244,6 +254,7 @@ verified against this invariant. See ADR-014 and
| HTTP-to-call operation mapping | [ADR-036](../../decisions/036-http-to-call-operation-mapping.md) | ~~Direct path mapping~~**routing superseded by ADR-047**; gateway `/call` is the sole invoke path; ADR-036's non-routing clauses survive (SSE, auth, `/healthz`, stealth, error mapping) |
| MCP stdio transport exclusion | [ADR-037](../../decisions/037-mcp-stdio-transport-exclusion.md) | Streamable HTTP only; stdio is not built (RCE vector) |
| Defer h3/WebTransport; browsers use WebSocket | [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md) | `h3`/WebTransport deferred (scope, not hedging); browser bidirectional path uses WebSocket; ADR-038 superseded, ADR-040/043 parked |
| WebSocket carries the native session, not the gateway shape | [ADR-048](../../decisions/048-websocket-native-session-not-gateway.md) | WS is the native `EventEnvelope` session; the gateway endpoints are HTTP-only; discovery via `services/list`/`services/schema` as call-protocol ops; subscriptions as native `call.responded` events (no SSE) |
| HTTP server + client host colocated | [ADR-039](../../decisions/039-http-server-and-client-host-colocated.md) | One crate for server + adapters (shared HTTP deps, shared mapping) |
| ~~HTTP/3 + WebTransport first-class~~ | [ADR-038](../../decisions/038-http3-and-webtransport-as-first-class.md) | **Superseded by ADR-044** (anti-pattern correction stands; specific decision reversed) |
| ~~WebTransport ALPN-stream-proxy~~ | [ADR-040](../../decisions/040-webtransport-alpn-stream-proxy.md) | **Parked** per ADR-044; revives unchanged when WebTransport revives |