First speccing pass for alknet-http (HTTP interface crate: h2/http1.1/h3 server + from_openapi/to_openapi/from_mcp/to_mcp adapters). Specs (crates/http/): - README.md, overview.md — crate index, two-roles-in-one-crate framing, adapter location map, feature gates (h3, mcp), no-env-vars invariant - http-server.md — HttpAdapter for h2/http1.1, axum over QUIC stream, Bearer auth, SSE projection for subscriptions, /healthz, stealth decoy - http-adapters.md — from_openapi (reqwest) and to_openapi (projection), error fidelity (HTTP_<status> per ADR-023), type definitions - http-mcp.md — from_mcp/to_mcp (feature-gated), streamable-HTTP-only - webtransport.md — h3/WebTransport handler, browser streaming path, HTTP/3 request vs WebTransport session distinguished at framing layer ADRs: - ADR-036 HTTP-to-Call Operation Mapping (Proposed) — direct path mapping; to_openapi is projection, not router (the load-bearing one-way door from Phase 0 DH-3) - ADR-037 MCP Stdio Transport Exclusion (Proposed) — streamable HTTP only; stdio is not built (RCE-vector security position) - ADR-038 HTTP/3 and WebTransport as First-Class HTTP Transports (Proposed) — corrects the Phase 0 DH-2 deferral framing; h3 is in scope, not deferred, per ADR-009 §'What this framework is NOT' - ADR-039 HTTP Server and Client Host Colocated in alknet-http (Proposed) — one crate for server + client host (shared HTTP deps, shared operation-spec->HTTP mapping) - ADR-003 Amendment 1 — clarifies alknet-call is a protocol-foundation crate (the alknet-http -> alknet-call dependency edge) Open questions (OQ-38, OQ-39, OQ-40 added under 'Theme: alknet-http'): - OQ-38 WebTransport relay-as-proxy scope (genuine scope question, not a deferral — the decision is made when the use case becomes concrete) - OQ-39 to_openapi published-spec versioning (one-way after first publication) - OQ-40 reqwest client config and connection pooling (two-way-door) Architecture README and overview updated with doc table, ADR table (036-039), current-state note, and crate graph (alknet-http -> alknet-call edge). Reviewed by architecture-reviewer subagent: 3 critical, 4 warning, 5 suggestion issues found and fixed (missing ADR-039, WebTransport stream routing conflation, undefined types, stale OQ-37 deferral language, README OQ table completeness, Bearer-only attribution, cross-references, ADR-038 ALPN quote, feature-gate placeholder, MCP temporal language).
11 KiB
ADR-038: HTTP/3 and WebTransport as First-Class HTTP Transports
Status
Proposed
Context
The alknet-http Phase 0 research findings
(docs/research/alknet-http/phase-0-findings.md, decision point DH-2)
framed HTTP/3 + WebTransport as "deferred past v1" — a two-way-door
addition to land "when the agent service needs browser streaming." That
framing was a residual of the "two-way door as deferral" anti-pattern
that ADR-009 §"What this framework is NOT" was later written to prevent.
The deferral framing is rejected here; this ADR records the decision as
made.
WebTransport is not a "later" feature. It is the browser-streaming
transport — QUIC streams are cheap (better than WebSocket/SSE by far for
multiplexed bidirectional streaming), and WebTransport is supported in
major browsers. The alknet-http crate's purpose is to be the HTTP
interface library for downstream crates that need to expose HTTP, and
browser streaming is a first-class requirement of that purpose, not a
fast-follow.
The ALPN registry already reserves h3
The overview ALPN Registry maps h3 to HttpAdapter (HTTP/3 + WebTransport). The h3 ALPN is reserved; the implementation lands as
part of alknet-http, not as a future crate. This ADR confirms that
reservation and records the architectural decision: HTTP/3 + WebTransport
is in scope, in this crate, as a first-class transport alongside h2 and
http/1.1.
Why WebTransport matters
- QUIC streams are cheap. A browser opens a WebTransport connection once and multiplexes many bidirectional streams over it. This is fundamentally better than WebSocket (one stream, one connection) or SSE (one-directional, one stream per subscription) for the subscription-heavy, streaming-heavy patterns the call protocol supports natively.
- Browser support. WebTransport is supported in modern Chromium-based browsers (Chrome, Edge). The browser path for alknet is WebTransport, not WebSocket.
- The call protocol maps cleanly onto WebTransport streams. A
call.requestedover a WebTransport bidirectional stream is the same EventEnvelope framing over a different QUIC stream source. TheCallConnection/Dispatcherdispatch loop is stream-agnostic (ADR-012) — theh3handler hands a bidirectional stream to the call protocol the same way theh2/http/1.1handler hands a hyper connection to axum.
The TLS constraint (browsers require X.509)
Browsers do not support RFC 7250 raw public keys (ADR-027, OQ-12). A
WebTransport session from a browser requires an X.509 cert — meaning the
h3 handler is a domain-hosted-service concern, not a P2P concern. A
node serving WebTransport must have an X.509 identity (TlsIdentity::X509
or TlsIdentity::Acme). This is a property of the browser, not a
decision this ADR makes — it's recorded so the spec doesn't pretend a
raw-key node can serve browsers.
WebTransport relay-as-proxy
A distinct WebTransport feature — a proxy that terminates the browser's
WebTransport connection and forwards encrypted traffic to a P2P hub's
Ed25519 endpoint (so the hub need not expose its own public X.509 cert)
— was recorded in ADR-034 §5. That feature does not change the auth model
(bearer token + PeerEntry.auth_token_hash; the proxy is transport-only)
and was explicitly placed in the same bucket as the rest of h3/
WebTransport. With this ADR, h3/WebTransport is in scope; the
relay-as-proxy is a genuine scope question (does the proxy belong in
alknet-http or in a separate relay crate?), tracked as OQ-38 — not
deferred, just scoped to a separate decision when the proxy use case
becomes concrete.
Decision
HTTP/3 + WebTransport is a first-class HTTP transport in alknet-http,
implemented alongside h2 and http/1.1. It is not deferred.
The HttpAdapter implements ProtocolHandler for three ALPNs:
| ALPN | Transport | Use case | Browser? |
|---|---|---|---|
http/1.1 |
HTTP/1.1 over QUIC stream | Legacy clients, curl | No (browsers use h2/h3) |
h2 |
HTTP/2 over QUIC stream | Modern HTTP clients, curl | No (browsers use h3) |
h3 |
HTTP/3 / WebTransport | Browser streaming | Yes (X.509 required) |
All three are served by alknet-http. The h3 ALPN handler upgrades to
WebTransport sessions and serves both HTTP/3 requests (the standard
HTTP/3 over QUIC framing) and WebTransport streams (the bidirectional/
unidirectional stream API). The handler dispatches HTTP/3 requests
through the same axum Router as h2/http/1.1; WebTransport streams
that target the call protocol are handed to the call protocol's dispatch
loop directly (a WebTransport stream is a QUIC bidirectional stream, the
same stream type the call protocol already speaks).
WebTransport as the browser streaming path
The h3 handler drives two distinct stream types, distinguished at the
HTTP/3 framing layer (not by peeking application bytes):
- HTTP/3 request streams — standard HTTP/3 GET/POST carrying
:method/:path. Dispatched through the axumRouter, same ash2/http/1.1(ADR-036). These are not WebTransport streams. - WebTransport sessions — opened by a browser's
new WebTransport(url)call (an HTTP/3 extended CONNECT request). The handler accepts the session (thewtransportcrate'sEndpoint::server+accept+accept_bipattern, or the quinn HTTP/3 endpoint's WebTransport extension). Within an established session, the browser creates bidirectional streams viatransport.createBidirectionalStream(); the handler accepts each and dispatches by sub-protocol. For a call-protocol session, the first frame is anEventEnvelopeand the handler hands the stream to the call protocol'sDispatcher— no SSE translation, no HTTP framing. The browser'sWebTransportJS API speaks to this handler directly.
The stream-type distinction (HTTP/3 request vs. WebTransport session) is made at the HTTP/3 frame layer (regular request headers vs. extended CONNECT), not by reading the first application byte. The first-frame routing applies within a WebTransport session (determining the sub-protocol), not between an HTTP/3 request and a WebTransport stream.
This means the browser's subscription/streaming path uses WebTransport
streams directly, not the SSE projection (ADR-036) that HTTP/1.1 + HTTP/2
clients use. The same Subscription operation is served as SSE over
h2 and as a native WebTransport stream over h3 — the projection is
transport-dependent, the operation is the same.
h3 and the stealth mapping
The h3 handler participates in the same stealth model as h2/
http/1.1 (ADR-010, ADR-036): a client that offers h3 gets the HTTP
handler. Unknown WebTransport paths and unknown HTTP/3 paths get the
decoy (configurable). Real services use alknet/ssh, alknet/call, etc.
Implementation reference: wtransport
The wtransport crate (/workspace/wtransport/, v0.7.1) is a pure-Rust
WebTransport implementation built on quinn + h3/qpack. It provides
the Endpoint::server + accept + accept_bi API that the h3
handler uses. wtransport is a candidate dependency for the h3
feature gate; the exact WebTransport library choice (wtransport vs a
quinn-native HTTP/3 + WebTransport extension) is a two-way-door
implementation detail. The one-way constraint is: h3 is served, by this
crate, as a first-class transport.
Feature gating
The h3/WebTransport support is behind an h3 feature gate (the
WebTransport/HTTP/3 dependencies are heavier than h2/http/1.1):
[features]
default = ["h2", "http1"]
h3 = ["dep:wtransport"] # or the quinn h3 extension
mcp = ["dep:rmcp"] # MCP feature gate (ADR-037)
A deployment that only needs h2/http/1.1 (a non-browser-facing
node) does not compile the WebTransport dependencies. A browser-facing
node enables h3.
Consequences
Positive:
- Browser streaming uses QUIC streams directly, not SSE-over-HTTP/2.
The call protocol's subscription model maps onto WebTransport streams
with no translation loss — a
call.respondedstream over a WebTransport bidirectional stream is the native representation. - The
h3ALPN reservation in the overview is honored — the implementation lands in this crate, not a future one. - A browser-facing node (a hub with an X.509 cert) serves the same
operations over
h3as a non-browser-facing node serves overh2— the operation registry is transport-agnostic, the projection is transport-dependent. - The WebTransport relay-as-proxy (ADR-034 §5) has a clear home: it's a
feature that lives in or near
alknet-http'sh3handler, scoped by OQ-38.
Negative:
alknet-httpgains thewtransport(or equivalent HTTP/3 + WebTransport) dependency behind theh3feature. This is a heavier dependency thanh2/http/1.1. The feature gate keeps it out of non-browser-facing builds.- WebTransport is still a draft standard (the
wtransportREADME notes it). The API may change. This is inherent to being an early adopter of WebTransport; theh3feature gate isolates the risk. - Browsers require X.509 (ADR-027). A raw-key-only node cannot serve
WebTransport. This is a browser limitation, not an alknet decision,
but it means the
h3feature is useful only on domain-hosted nodes with X.509 certs.
Assumptions
-
WebTransport is the browser streaming transport for alknet. The browser path is WebTransport, not WebSocket and not SSE-over-HTTP/2. SSE remains the streaming projection for non-WebTransport HTTP clients (curl, axios over h2); WebTransport is the native path for browsers.
-
The
wtransportcrate (or an equivalent quinn-native HTTP/3 + WebTransport implementation) is the dependency for theh3feature. The exact library is a two-way-door implementation detail; the one-way constraint is thath3is served by this crate. -
The WebTransport relay-as-proxy (ADR-034 §5) is a separate scope decision (OQ-38). It is not deferred — it's a feature with a clear home (the
h3handler or a sibling relay crate) that gets designed when the browser-to-P2P-peer proxy use case becomes concrete.
References
- ADR-009 §"What this framework is NOT" — the anti-pattern this ADR corrects (two-way door as deferral)
- ADR-010 — the ALPN router;
h3is one of the ALPNs theHttpAdapterregisters for - ADR-012 — stream-agnostic correlation; a WebTransport stream is a QUIC bidirectional stream
- ADR-027 — the browser limitation (no RFC 7250); WebTransport requires X.509
- ADR-034 §4 (browsers are not alknet peers) and §5 (WebTransport relay-as-proxy, recorded for this bucket)
- ADR-036 — the SSE projection
for
h2/http/1.1that WebTransport replaces for the browser path docs/research/alknet-http/phase-0-findings.mdDH-2 — the deferral framing this ADR rejects/workspace/wtransport/— pure-Rust WebTransport (theh3feature's reference implementation)crates/http/webtransport.md— the spec that implements theh3handler