Files
alknet/docs/architecture/decisions/038-http3-and-webtransport-as-first-class.md
glm-5.2 125cb49cc4 docs(http): defer h3/WebTransport (ADR-044); browsers use WebSocket for v1
Working through the WebTransport implementation path surfaced a scope
question distinct from the hedging-as-deferral anti-pattern ADR-038 was
written to correct. Three findings drove the re-evaluation:

1. The browser bidirectional call-protocol path doesn't require
   WebTransport — WebSocket is full-duplex, EventEnvelope fits a WS
   binary message boundary cleanly, and the Dispatcher is stream-
   agnostic (ADR-012). What WebTransport gives over WebSocket (native
   multi-stream multiplexing, the ALPN-as-stream substrate) benefits the
   proxy use case, not the call protocol.
2. WebTransport is a draft standard (-07, not RFC) on an experimental
   Rust dependency stack (wtransport/h3 both self-describe as not
   production-ready). Either choice puts a draft protocol on the
   security surface of the first release.
3. The ALPN-stream-proxy (ADR-040) is speculative — its WASM parser
   consumers (browser SSH/SFTP/git clients) don't exist yet, and the
   downstream crates WebTransport deferral blocks (SSH, git, SFTP)
   expose their ALPNs natively over QUIC regardless.

This is a scope decision (per ADR-009: a decision that 'genuinely
doesn't need to be made yet because the use case isn't concrete'), not
hedging. The reversal trigger is concrete: a real deployment needing
the ALPN-stream-proxy.

ADR-038 is superseded (its anti-pattern correction stands; its specific
'h3 in scope now' decision is reversed). ADR-040 and ADR-043 are
parked, not superseded — their designs revive unchanged when WebTransport
revives, with §2 (bidirectionality) and §3 (no-PeerId overlay) of ADR-043
transferring to WebSocket for v1.

ADR-044 §5 also states the 'browser is not a peer' rationale that
ADR-034 §4 closed without arguing: peer = addressable node in the
call-protocol peer graph (stable PeerId, PeerRef::Specific-reachable,
identity stable across reconnects), not 'any endpoint that exchanges
calls during a live session.' A browser is the second but not the first
(no stable crypto identity of its own, ephemeral, not addressable from
other nodes). ADR-034 §4 and Assumption 2 are amended by reference.

The wtransport-vs-hyperium dependency question is recorded (not
resolved — WebTransport is deferred) in ADR-044 §'Research note' and
webtransport.md so the revival doesn't re-derive it: wtransport probably
isn't the right choice (axum-bridge friction — it owns its own HTTP
serving path); the hyperium stack (h3 + h3-quinn + h3-webtransport) fits
the axum integration better but its server-side WebTransport API needs
verification before commitment.

Reviewed by architecture-review subagent; all critical cross-reference
issues (ADR-034 §5 stale 'in scope' assertion, ADR-036 Context listing
h3 as implemented, webtransport.md Design Decisions table) resolved.
2026-06-30 05:55:55 +00:00

12 KiB

ADR-038: HTTP/3 and WebTransport as First-Class HTTP Transports

Status

Superseded by ADR-044.

This ADR's correction of the "two-way-door-as-deferral" anti-pattern (ADR-009 §"What this framework is NOT") stands as a document — the anti-pattern is real, and the reasoning that rejected deferral-as-hedging is correct. However, this ADR's specific decision — that h3/WebTransport is in scope now, not deferred — is reversed by ADR-044. ADR-044 is a scope decision (permitted by ADR-009: "not needed for the current scope"), not a hedging deferral: the browser bidirectional path uses WebSocket (RFC 6455, mature, native axum support), the ALPN-stream-proxy (ADR-040) is the speculative use case whose deferral is the reversal trigger, and the draft-standard + experimental-deps surface area is not justified by a concrete v1 requirement.

ADR-040 and ADR-043 are parked, not superseded — their designs revive unchanged when WebTransport revives. See ADR-044 for the full scope rationale, the reversal trigger, and the research note on the wtransport-vs-hyperium dependency choice (recorded for the revival so it is not re-derived).

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.requested over a WebTransport bidirectional stream is the same EventEnvelope framing over a different QUIC stream source. The CallConnection/Dispatcher dispatch loop is stream-agnostic (ADR-012) — the h3 handler hands a bidirectional stream to the call protocol the same way the h2/http/1.1 handler 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):

  1. HTTP/3 request streams — standard HTTP/3 GET/POST carrying :method/:path. Dispatched through the axum Router, same as h2/http/1.1 (ADR-036). These are not WebTransport streams.
  2. WebTransport sessions — opened by a browser's new WebTransport(url) call (an HTTP/3 extended CONNECT request). The handler accepts the session (the wtransport crate's Endpoint::server + accept + accept_bi pattern, or the quinn HTTP/3 endpoint's WebTransport extension). Within an established session, the browser creates bidirectional streams via transport.createBidirectionalStream(); the handler accepts each and dispatches by sub-protocol. For a call-protocol session, the first frame is an EventEnvelope and the handler hands the stream to the call protocol's Dispatcher — no SSE translation, no HTTP framing. The browser's WebTransport JS 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.responded stream over a WebTransport bidirectional stream is the native representation.
  • The h3 ALPN 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 h3 as a non-browser-facing node serves over h2 — 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's h3 handler, scoped by OQ-38.

Negative:

  • alknet-http gains the wtransport (or equivalent HTTP/3 + WebTransport) dependency behind the h3 feature. This is a heavier dependency than h2/http/1.1. The feature gate keeps it out of non-browser-facing builds.
  • WebTransport is still a draft standard (the wtransport README notes it). The API may change. This is inherent to being an early adopter of WebTransport; the h3 feature 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 h3 feature is useful only on domain-hosted nodes with X.509 certs.

Assumptions

  1. 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.

  2. The wtransport crate (or an equivalent quinn-native HTTP/3 + WebTransport implementation) is the dependency for the h3 feature. The exact library is a two-way-door implementation detail; the one-way constraint is that h3 is served by this crate.

  3. 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 h3 handler 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; h3 is one of the ALPNs the HttpAdapter registers 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.1 that WebTransport replaces for the browser path
  • docs/research/alknet-http/phase-0-findings.md DH-2 — the deferral framing this ADR rejects
  • /workspace/wtransport/ — pure-Rust WebTransport (the h3 feature's reference implementation)
  • crates/http/webtransport.md — the spec that implements the h3 handler