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.
This commit is contained in:
@@ -1,23 +1,26 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-29
|
||||
last_updated: 2026-06-30
|
||||
---
|
||||
|
||||
# alknet-http
|
||||
|
||||
HTTP interface for alknet: serves HTTP/1.1, HTTP/2, and HTTP/3 (WebTransport)
|
||||
on standard ALPNs, and hosts the HTTP-backed call-protocol adapters
|
||||
(`from_openapi`, `to_openapi`, `from_mcp`, `to_mcp`).
|
||||
HTTP interface for alknet: serves HTTP/1.1 and HTTP/2 on standard ALPNs
|
||||
(with WebSocket upgrade for browser bidirectional access to the call
|
||||
protocol), and hosts the HTTP-backed call-protocol adapters
|
||||
(`from_openapi`, `to_openapi`, `from_mcp`, `to_mcp`). HTTP/3 + WebTransport
|
||||
(`h3`) is deferred per
|
||||
[ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md).
|
||||
|
||||
## Documents
|
||||
|
||||
| Document | Status | Description |
|
||||
|----------|--------|-------------|
|
||||
| [overview.md](overview.md) | draft | Crate purpose, two roles (server + client host), dependencies, adapter location map |
|
||||
| [http-server.md](http-server.md) | draft | `HttpAdapter` (`ProtocolHandler` for `h2`/`http/1.1`), axum over QUIC, Bearer auth, stealth, `/healthz` |
|
||||
| [http-server.md](http-server.md) | draft | `HttpAdapter` (`ProtocolHandler` for `h2`/`http/1.1` + WS upgrade), axum over QUIC, Bearer auth, stealth, `/healthz`, WebSocket browser path |
|
||||
| [http-adapters.md](http-adapters.md) | draft | `from_openapi` (reqwest client) and `to_openapi` (OpenAPI projection); no-env-vars invariant point |
|
||||
| [http-mcp.md](http-mcp.md) | draft | `from_mcp` / `to_mcp` (feature-gated), streamable-HTTP-only, stdio exclusion |
|
||||
| [webtransport.md](webtransport.md) | draft | `h3`/WebTransport handler — the browser streaming path |
|
||||
| [webtransport.md](webtransport.md) | deferred | `h3`/WebTransport handler — **deferred per ADR-044**; spec kept intact for revival |
|
||||
|
||||
## Applicable ADRs
|
||||
|
||||
@@ -34,23 +37,24 @@ on standard ALPNs, and hosts the HTTP-backed call-protocol adapters
|
||||
| [017](../../decisions/017-call-protocol-client-and-adapter-contract.md) | Call Protocol Client and Adapter Contract | `OperationAdapter` trait; `to_*` are projections; published-spec contract |
|
||||
| [022](../../decisions/022-handler-registration-provenance-and-composition-authority.md) | Handler Registration, Provenance, Composition Authority | `from_openapi`/`from_mcp` produce leaf bundles |
|
||||
| [023](../../decisions/023-operation-error-schemas.md) | Operation Error Schemas | `from_openapi`/`to_openapi` error fidelity; `HTTP_<status>` error codes |
|
||||
| [027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | TLS Identity Redesign | Browsers require X.509; WebTransport requires X.509 |
|
||||
| [034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) | Outgoing-Only X.509 and Three Peer Roles | Browsers are not alknet peers; WebTransport relay-as-proxy recorded |
|
||||
| [027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | TLS Identity Redesign | Browsers require X.509; applies to WebTransport (deferred) and any browser-facing TLS |
|
||||
| [034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) | Outgoing-Only X.509 and Three Peer Roles | Browsers are not alknet peers (§4 amended by ADR-044 §5 with the addressability rationale) |
|
||||
| [036](../../decisions/036-http-to-call-operation-mapping.md) | HTTP-to-Call Operation Mapping | Direct path mapping; `to_openapi` is projection, not router |
|
||||
| [037](../../decisions/037-mcp-stdio-transport-exclusion.md) | MCP Stdio Transport Exclusion | Streamable HTTP only; stdio not built |
|
||||
| [038](../../decisions/038-http3-and-webtransport-as-first-class.md) | HTTP/3 and WebTransport as First-Class HTTP Transports | `h3` in scope, not deferred |
|
||||
| [038](../../decisions/038-http3-and-webtransport-as-first-class.md) | HTTP/3 and WebTransport as First-Class HTTP Transports | **Superseded by ADR-044** (anti-pattern correction stands; specific decision reversed) |
|
||||
| [039](../../decisions/039-http-server-and-client-host-colocated.md) | HTTP Server and Client Host Colocated in alknet-http | One crate for server + client host (shared HTTP deps, shared mapping) |
|
||||
| [040](../../decisions/040-webtransport-alpn-stream-proxy.md) | WebTransport ALPN-Stream-Proxy | The substrate's mechanism for non-call ALPNs (SSH, git, SFTP) — browser → WebTransport stream → target ALPN handler via WASM parser |
|
||||
| [040](../../decisions/040-webtransport-alpn-stream-proxy.md) | WebTransport ALPN-Stream-Proxy | **Parked** per ADR-044; revives unchanged when WebTransport revives |
|
||||
| [041](../../decisions/041-mcp-tool-gateway-pattern.md) | MCP Tool-Gateway Pattern for to_mcp | 4 fixed gateway tools (search/schema/call/batch), not one tool per operation; Subscription excluded |
|
||||
| [042](../../decisions/042-openapi-gateway-pattern.md) | OpenAPI Gateway Pattern for to_openapi | 5 fixed gateway endpoints (search/schema/call/batch/subscribe), not one path per operation; per-caller AccessControl-filtered |
|
||||
| [043](../../decisions/043-webtransport-bidirectional-alpn-substrate.md) | WebTransport as a Bidirectional ALPN Transport Substrate | WebTransport carries ALPNs as bidirectional streams; call protocol is the first/canonical target (needs no WASM parser); both sides can initiate calls; no-`PeerId` non-peer clients use a connection-local overlay |
|
||||
| [043](../../decisions/043-webtransport-bidirectional-alpn-substrate.md) | WebTransport as a Bidirectional ALPN Transport Substrate | **Parked** per ADR-044; §2/§3 transfer to WebSocket for v1 |
|
||||
| [044](../../decisions/044-defer-webtransport-browsers-use-websocket.md) | Defer h3/WebTransport; Browsers Use WebSocket | `h3`/WebTransport deferred (scope); browser bidirectional path uses WebSocket; "browser is not a peer" rationale |
|
||||
|
||||
## Relevant Open Questions
|
||||
|
||||
| OQ | Title | Status | Relevance |
|
||||
|----|-------|--------|-----------|
|
||||
| OQ-11 | Handler-level auth resolution observability | resolved | HTTP handler stores resolved identity on `Connection` via `set_identity` |
|
||||
| OQ-12 | TLS identity provisioning | resolved | Browsers require X.509 (gates the entire `h3` feature) |
|
||||
| OQ-12 | TLS identity provisioning | resolved | Browsers require X.509 (applies to WebTransport when it revives; WebSocket uses the same TLS as h2/http1.1) |
|
||||
| OQ-13 | Operation path format | resolved | `/{service}/{op}` is the HTTP path (ADR-036) |
|
||||
| OQ-17 | Call protocol client and adapter contract | resolved | `OperationAdapter` trait; `to_*` projections |
|
||||
| OQ-24 | Operation error schemas | resolved | `from_openapi`/`to_openapi` error fidelity |
|
||||
@@ -63,11 +67,11 @@ on standard ALPNs, and hosts the HTTP-backed call-protocol adapters
|
||||
## Key Design Principles
|
||||
|
||||
1. **HTTP is both a server surface and a client transport for adapters.**
|
||||
Inbound HTTP (`h2`/`http/1.1`/`h3`) is served by `axum` over a QUIC
|
||||
stream; outbound HTTP (`from_openapi`/`from_mcp` forwarding) uses
|
||||
`reqwest`. Both directions share the same HTTP dependencies, which is
|
||||
why they live in one crate rather than being split. See
|
||||
[overview.md](overview.md).
|
||||
Inbound HTTP (`h2`/`http/1.1` + WebSocket upgrade) is served by `axum`
|
||||
over a QUIC stream; outbound HTTP (`from_openapi`/`from_mcp`
|
||||
forwarding) uses `reqwest`. Both directions share the same HTTP
|
||||
dependencies, which is why they live in one crate rather than being
|
||||
split. See [overview.md](overview.md).
|
||||
2. **The HTTP surface is a projection of the call protocol.** An HTTP
|
||||
request at `POST /fs/readFile` becomes a `call.requested` for
|
||||
`/fs/readFile`. The HTTP path IS the operation path on the
|
||||
@@ -78,7 +82,7 @@ on standard ALPNs, and hosts the HTTP-backed call-protocol adapters
|
||||
(direct-call surface) and [ADR-042](../../decisions/042-openapi-gateway-pattern.md)
|
||||
(`to_openapi` gateway, superseding ADR-036's original `to_openapi`
|
||||
clause).
|
||||
3. **Standard ALPNs, not alknet ALPNs.** `h2`, `http/1.1`, `h3` are
|
||||
3. **Standard ALPNs, not alknet ALPNs.** `h2`, `http/1.1` are
|
||||
IANA-registered ALPN strings. Any HTTP client (browser, curl, axios)
|
||||
connects without knowing about alknet — the TLS handshake negotiates
|
||||
`h2` or `http/1.1` normally. This is the stealth mapping (ADR-010).
|
||||
@@ -91,36 +95,34 @@ on standard ALPNs, and hosts the HTTP-backed call-protocol adapters
|
||||
arbitrary executable = RCE. Streamable HTTP is network-isolated,
|
||||
auth-gatable, and runs under alknet's auth model. See
|
||||
[ADR-037](../../decisions/037-mcp-stdio-transport-exclusion.md).
|
||||
6. **HTTP/3 + WebTransport is a first-class transport, not a deferral.**
|
||||
The browser streaming path uses QUIC streams directly. See
|
||||
[ADR-038](../../decisions/038-http3-and-webtransport-as-first-class.md).
|
||||
7. **The `h3` handler is an ALPN-stream-proxy for browsers.** A browser
|
||||
with a WASM parser can reach any non-call ALPN handler (SSH, git,
|
||||
SFTP) via WebTransport — no install, no native client, no VPN. The
|
||||
call protocol needs no proxy (it speaks EventEnvelope directly);
|
||||
the ALPN-stream-proxy is the substrate's mechanism for the protocols
|
||||
that need a client-side parser. SSH-over-WebTransport is
|
||||
HTTPS-shaped at the network layer (anti-censorship). See
|
||||
[ADR-040](../../decisions/040-webtransport-alpn-stream-proxy.md)
|
||||
and [ADR-043](../../decisions/043-webtransport-bidirectional-alpn-substrate.md).
|
||||
8. **`h3` requires X.509.** Browsers don't support RFC 7250 raw keys
|
||||
(ADR-027). A node serving WebTransport must have an X.509 identity.
|
||||
This is a browser limitation, not an alknet decision.
|
||||
9. **WebTransport is a bidirectional ALPN transport substrate.**
|
||||
WebTransport carries ALPN protocols as bidirectional streams; the
|
||||
call protocol is the first/canonical target (JSON-RPC over QUIC
|
||||
streams, needs no WASM parser, runs in Deno/Node/browsers/native
|
||||
Rust). Both sides of a WebTransport call-protocol session can
|
||||
initiate calls — the call protocol's bidirectionality applies
|
||||
unchanged. The HTTP/1.1 + HTTP/2 surface is the one-directional
|
||||
projection (HTTP is request/response); WebTransport restores the
|
||||
bidirectional call model. See
|
||||
[ADR-043](../../decisions/043-webtransport-bidirectional-alpn-substrate.md).
|
||||
6. **WebSocket is the browser bidirectional path.** A browser upgrades
|
||||
an HTTP/1.1 or HTTP/2 request to WebSocket and speaks the call
|
||||
protocol over binary WS messages — full-duplex, both sides can
|
||||
initiate calls (the call protocol's native bidirectionality, ADR-012).
|
||||
HTTP/3 + WebTransport (`h3`) is deferred per
|
||||
[ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md)
|
||||
— a scope decision (the browser bidirectional path doesn't require
|
||||
WebTransport's stream model; WebSocket suffices). The reversal
|
||||
trigger is a concrete ALPN-stream-proxy use case (a browser running
|
||||
a WASM SSH/SFTP/git client).
|
||||
7. **Browsers are not alknet peers.** A browser over WebSocket (or, when
|
||||
it revives, WebTransport) authenticates by bearer token, gets no
|
||||
`PeerId`, and its registered ops land in a connection-local Layer 2
|
||||
overlay. "Peer" means an 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 cryptographic identity of its own, ephemeral, not addressable
|
||||
from other nodes. See
|
||||
[ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md)
|
||||
§4 (amended by ADR-044 §5 with the addressability rationale).
|
||||
|
||||
## References
|
||||
|
||||
- `docs/research/alknet-http/phase-0-findings.md` — Phase 0 research
|
||||
(directionally close; DH-2's deferral framing is corrected by ADR-038)
|
||||
(directionally close; DH-2's deferral framing was corrected by
|
||||
ADR-038, then ADR-038 was superseded by ADR-044 which re-defers
|
||||
`h3`/WebTransport as a genuine scope decision)
|
||||
- `docs/research/alknet-call-completion/gap-analysis.md` — adapter
|
||||
location map, no-env-vars invariant
|
||||
- `/workspace/@alkdev/operations/src/from_openapi.ts`,
|
||||
@@ -128,4 +130,6 @@ on standard ALPNs, and hosts the HTTP-backed call-protocol adapters
|
||||
- `/workspace/rust-sdk/` — MCP Rust SDK (rmcp v1.8.0); streamable HTTP
|
||||
transport examples
|
||||
- `/workspace/wtransport/` — pure-Rust WebTransport reference
|
||||
implementation (the `h3` feature's candidate dependency)
|
||||
implementation (read during research; not a dependency. See ADR-044
|
||||
§"Research note" for why `wtransport` is probably not the right
|
||||
revival choice — the hyperium stack fits the axum integration better.)
|
||||
@@ -1,15 +1,17 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-29
|
||||
last_updated: 2026-06-30
|
||||
---
|
||||
|
||||
# HTTP Server
|
||||
|
||||
The `HttpAdapter` — the `ProtocolHandler` for `h2` and `http/1.1` (and
|
||||
`h3`, covered in [webtransport.md](webtransport.md)). This document
|
||||
WebSocket upgrade — see §"WebSocket browser path"). The `h3`/WebTransport
|
||||
path is deferred per [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md);
|
||||
the deferred spec is at [webtransport.md](webtransport.md). This document
|
||||
covers how axum is run over a QUIC bidirectional stream, Bearer auth
|
||||
resolution, the HTTP-to-call dispatch, the `/healthz` raw route, and
|
||||
stealth decoy.
|
||||
resolution, the HTTP-to-call dispatch, the `/healthz` raw route, stealth
|
||||
decoy, and the WebSocket browser path.
|
||||
|
||||
## What
|
||||
|
||||
@@ -54,12 +56,15 @@ impl ProtocolHandler for HttpAdapter {
|
||||
}
|
||||
```
|
||||
|
||||
The `HttpAdapter` registers for multiple ALPNs (`http/1.1`, `h2`, `h3`).
|
||||
The `HttpAdapter` registers for multiple ALPNs (`http/1.1`, `h2`).
|
||||
The endpoint's `HandlerRegistry` maps each ALPN byte string to the same
|
||||
adapter instance; `handle()` branches on `connection.remote_alpn()` to
|
||||
pick the HTTP framing. For `http/1.1` and `h2`, the framing is hyper's
|
||||
HTTP/1.1 or HTTP/2 over a QUIC bidirectional stream; for `h3`, it's the
|
||||
WebTransport/HTTP/3 path (see [webtransport.md](webtransport.md)).
|
||||
HTTP/1.1 or HTTP/2 over a QUIC bidirectional stream. WebSocket upgrade
|
||||
(§"WebSocket browser path") layers on top of the same hyper connection
|
||||
driver — a WS upgrade is an HTTP/1.1 or HTTP/2 request that switches
|
||||
protocols. The `h3` ALPN is deferred (ADR-044); the deferred handler
|
||||
design is at [webtransport.md](webtransport.md).
|
||||
|
||||
## Why
|
||||
|
||||
@@ -167,9 +172,12 @@ A `Subscription` operation served over `h2`/`http/1.1` projects its
|
||||
sends `call.aborted` for the in-flight subscription, which cascades
|
||||
to descendants per ADR-016.
|
||||
|
||||
This is the HTTP/1.1 + HTTP/2 streaming projection. Over WebTransport
|
||||
(`h3`), the subscription projects directly onto a WebTransport
|
||||
bidirectional stream — no SSE framing (see [webtransport.md](webtransport.md)).
|
||||
This is the HTTP/1.1 + HTTP/2 streaming projection. Over WebSocket
|
||||
(§"WebSocket browser path" below), the subscription projects directly
|
||||
onto the WS connection — `call.responded` events as binary WS messages,
|
||||
no SSE framing. WebTransport (`h3`, deferred per ADR-044) would project
|
||||
onto WebTransport bidirectional streams; see
|
||||
[webtransport.md](webtransport.md).
|
||||
|
||||
### One-directional projection (HTTP request/response)
|
||||
|
||||
@@ -188,14 +196,79 @@ SSE response — but even there, the *call* is client-initiated; only the
|
||||
*results* flow server→client.
|
||||
|
||||
This is a structural property of HTTP, not a design choice in this
|
||||
crate. WebTransport (`h3`) restores the bidirectional call model: a
|
||||
WebTransport session is a long-lived connection over which either side
|
||||
can open bidirectional streams and send `call.requested` events in
|
||||
either direction — the call protocol's native bidirectionality applies
|
||||
unchanged. See [webtransport.md](webtransport.md) and ADR-043. The
|
||||
HTTP/1.1 + HTTP/2 surface is the projection for clients that only speak
|
||||
HTTP; WebTransport is the surface for clients that can speak the call
|
||||
protocol in both directions.
|
||||
crate. **WebSocket restores the bidirectional call model for browsers**
|
||||
(see §"WebSocket browser path" below): a WS connection is a long-lived
|
||||
full-duplex channel over which either side can send `call.requested`
|
||||
frames in either direction — the call protocol's native bidirectionality
|
||||
applies unchanged (ADR-012 — stream-agnostic correlation; a WS message
|
||||
stream is another `BiStream`-satisfying transport). WebTransport (`h3`)
|
||||
would restore it via native multi-stream multiplexing, but WebTransport
|
||||
is deferred per ADR-044 — WebSocket is the v1 browser bidirectional path.
|
||||
The HTTP/1.1 + HTTP/2 surface is the projection for clients that only
|
||||
speak HTTP; WebSocket is the surface for browser clients that speak the
|
||||
call protocol in both directions.
|
||||
|
||||
### WebSocket browser path (ADR-044)
|
||||
|
||||
A browser connecting to a hub upgrades an HTTP/1.1 or HTTP/2 request to
|
||||
WebSocket (RFC 6455). The resulting full-duplex WS connection carries
|
||||
call-protocol `EventEnvelope` frames as binary WebSocket messages — one
|
||||
envelope per message. The browser authenticates by bearer token on the
|
||||
upgrade request (the HTTP `Authorization` header), resolved by the hub's
|
||||
`IdentityProvider::resolve_from_token`, same as any HTTP request. The WS
|
||||
connection is then a **bidirectional call-protocol session**:
|
||||
|
||||
- The browser opens the WS connection to `/alknet/call` (or `/`).
|
||||
- The handler hands the WS message stream to the call protocol's
|
||||
`Dispatcher` — the same dispatch loop the `CallAdapter` uses for
|
||||
`alknet/call` QUIC connections (ADR-012, stream-agnostic correlation).
|
||||
- The browser writes `EventEnvelope` frames as binary WS messages; the
|
||||
handler reads them and dispatches via `OperationRegistry::invoke()`.
|
||||
- Responses (`call.responded`, `call.error`, `call.completed`,
|
||||
`call.aborted`) are written back as binary WS messages.
|
||||
|
||||
**Bidirectionality:** the WS call-protocol session inherits the call
|
||||
protocol's native bidirectionality — both sides can initiate calls
|
||||
(ADR-043 §2, transferred to WebSocket per ADR-044 §3). The browser calls
|
||||
operations on the hub; the hub can call operations registered on the
|
||||
browser's side, over the same session, using the same `PendingRequestMap`
|
||||
and `EventEnvelope` framing as `alknet/call`. The browser case where the
|
||||
client registers no operations of its own is the common case — the
|
||||
server→client call direction is unused because the browser has nothing
|
||||
to call. That is a use-case scoping, not an architectural limitation.
|
||||
|
||||
**No SSE translation.** A `Subscription` operation served over WebSocket
|
||||
projects its `call.responded` stream directly as binary WS messages — no
|
||||
SSE `data:` framing. `call.completed` closes the stream; `call.aborted`
|
||||
closes it with an error frame. This is the native streaming projection
|
||||
for the WS path; SSE (ADR-036) is the projection for `h2`/`http/1.1`
|
||||
clients that don't upgrade to WebSocket.
|
||||
|
||||
**Browsers are not alknet peers.** A browser over WebSocket authenticates
|
||||
by bearer token, gets no `PeerId`, does not enter `PeerCompositeEnv`, and
|
||||
its registered ops (if any) land in a connection-local Layer 2 overlay —
|
||||
the inbound mirror of ADR-034 §2. The rationale (addressability vs.
|
||||
bidirectionality) is stated in ADR-044 §5 and amends ADR-034 §4 by
|
||||
reference. In short: "peer" means an 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 thing but not the
|
||||
first — it has no stable cryptographic identity of its own (it presents
|
||||
a bearer token the hub issued; nothing to pin), it is ephemeral (close
|
||||
the tab → connection dies → the connection-local overlay dies with it),
|
||||
and it is not addressable from other nodes (another alknet node has no
|
||||
way to reach "the browser currently connected to hub-A"; the hub holds
|
||||
it as a live `CallConnection` handle, not a peer-graph entry). The
|
||||
connection-local overlay is what gives the browser bidirectional-call
|
||||
capability *without* peer-graph membership.
|
||||
|
||||
**What WebSocket does not provide (deferred to WebTransport, ADR-044):**
|
||||
the ALPN-stream-proxy (ADR-040) — a browser running a WASM parser for
|
||||
SSH/SFTP/git to reach a non-call ALPN — requires WebTransport's
|
||||
multi-stream model and is the speculative use case whose deferral is
|
||||
ADR-044's reversal trigger. WebSocket carries the call protocol from a
|
||||
browser; it does not carry the non-call-ALPN substrate. A browser cannot
|
||||
reach SSH/SFTP/git ALPNs in the v1 release. See ADR-044.
|
||||
|
||||
### Auth
|
||||
|
||||
@@ -294,10 +367,11 @@ config is a two-way-door default (an operator picks what to serve); the
|
||||
Capabilities are used for outbound calls (`from_openapi`), never
|
||||
serialized into HTTP response bodies.
|
||||
- **`/healthz` is raw.** No auth, no call protocol. The one raw route.
|
||||
- **The `h3` ALPN is a first-class transport.** The `HttpAdapter`
|
||||
registers for `h3` when the `h3` feature is enabled (ADR-038). The
|
||||
`h3` handler is covered in [webtransport.md](webtransport.md); this
|
||||
document covers the `h2`/`http/1.1` path.
|
||||
- **WebSocket is the browser bidirectional path (ADR-044).** A browser
|
||||
upgrades an HTTP request to WS and speaks the call protocol over binary
|
||||
messages. `h3`/WebTransport is deferred (ADR-044); the ALPN-stream-proxy
|
||||
(ADR-040) is not available in v1. The `h3` ALPN and its feature gate are
|
||||
not implemented in the initial release.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
@@ -309,7 +383,8 @@ config is a two-way-door default (an operator picks what to serve); the
|
||||
| `/healthz` is a raw route | [ADR-036](../../decisions/036-http-to-call-operation-mapping.md) | No auth, no call protocol |
|
||||
| Stealth decoy | [ADR-010](../../decisions/010-alpn-router-and-endpoint.md) | HTTP handler on standard ALPNs serves decoy |
|
||||
| Bearer auth via `resolve_from_token` | [ADR-004](../../decisions/004-auth-as-shared-core.md) | HTTP handler credential source (settled) |
|
||||
| `h3` is first-class (not deferred) | [ADR-038](../../decisions/038-http3-and-webtransport-as-first-class.md) | The `h3` ALPN handler lives in this crate |
|
||||
| WebSocket is the browser bidirectional path | [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md) | Browsers upgrade to WS; `EventEnvelope` over binary messages; `h3`/WebTransport deferred |
|
||||
| Browsers are not alknet peers | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) §4 (amended by ADR-044 §5) | Bearer token, no `PeerId`, connection-local overlay (addressability vs. bidirectionality) |
|
||||
| Error mapping (call codes → HTTP status) | [ADR-023](../../decisions/023-operation-error-schemas.md) | Protocol/operation codes distinct; `HTTP_<status>` prefix for imported |
|
||||
|
||||
## Open Questions
|
||||
@@ -327,10 +402,13 @@ See [open-questions.md](../../open-questions.md) for full details.
|
||||
|
||||
- [ADR-036](../../decisions/036-http-to-call-operation-mapping.md) — the
|
||||
HTTP-to-call mapping this server implements
|
||||
- [ADR-038](../../decisions/038-http3-and-webtransport-as-first-class.md)
|
||||
— the `h3`/WebTransport companion to this server
|
||||
- [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md)
|
||||
— WebSocket is the v1 browser bidirectional path; `h3`/WebTransport
|
||||
deferred. States the "browser is not a peer" rationale (addressability
|
||||
vs. bidirectionality) that ADR-034 §4 closes without arguing.
|
||||
- [overview.md](overview.md) — crate overview, adapter location map
|
||||
- [webtransport.md](webtransport.md) — the `h3` ALPN handler
|
||||
- [webtransport.md](webtransport.md) — the deferred `h3` ALPN handler
|
||||
(kept intact for revival)
|
||||
- [http-adapters.md](http-adapters.md) — `from_openapi`/`to_openapi`
|
||||
- [../core/auth.md](../core/auth.md) — `IdentityProvider`, Bearer →
|
||||
`resolve_from_token`
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-29
|
||||
last_updated: 2026-06-30
|
||||
---
|
||||
|
||||
# alknet-http — Overview
|
||||
|
||||
The HTTP interface crate: serves inbound HTTP on standard ALPNs and hosts
|
||||
the HTTP-backed call-protocol adapters. This document covers the crate's
|
||||
two roles, its dependency edges, and the adapter location map. Component
|
||||
The HTTP interface crate: serves inbound HTTP on standard ALPNs (with
|
||||
WebSocket upgrade for browser bidirectional access) and hosts the
|
||||
HTTP-backed call-protocol adapters. This document covers the crate's two
|
||||
roles, its dependency edges, and the adapter location map. Component
|
||||
details are in the sibling documents.
|
||||
|
||||
## What
|
||||
@@ -16,11 +17,14 @@ details are in the sibling documents.
|
||||
architecture. It serves two roles in one crate:
|
||||
|
||||
1. **HTTP server** — a `ProtocolHandler` (`HttpAdapter`) that accepts
|
||||
HTTP/2, HTTP/1.1, and HTTP/3 (WebTransport) connections on the
|
||||
standard IANA ALPNs (`h2`, `http/1.1`, `h3`). It serves REST APIs, the
|
||||
HTTP/2 and HTTP/1.1 connections on the standard IANA ALPNs (`h2`,
|
||||
`http/1.1`), plus WebSocket upgrade (for browser bidirectional
|
||||
access to the call protocol). It serves REST APIs, the
|
||||
`to_openapi`/`to_mcp` projections of local call-protocol operations,
|
||||
the `/healthz` operational endpoint, and the decoy surface for
|
||||
stealth mode.
|
||||
stealth mode. HTTP/3 + WebTransport (`h3` ALPN) is deferred per
|
||||
[ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md);
|
||||
the deferred handler design is at [webtransport.md](webtransport.md).
|
||||
2. **HTTP client host** — the home of the HTTP-transport-backed call
|
||||
adapters: `from_openapi` (import external HTTP APIs as call
|
||||
operations, using `reqwest` for outbound calls) and `from_mcp` (import
|
||||
@@ -42,7 +46,9 @@ crates that need to expose an HTTP interface. A downstream consumer (the
|
||||
CLI binary, a hub deployment, a browser-facing service) wires
|
||||
`HttpAdapter` into the `HandlerRegistry` for the standard HTTP ALPNs and
|
||||
gets a full HTTP surface: REST projection of the call protocol, OpenAPI
|
||||
discovery, MCP tool exposure, and WebTransport for browsers.
|
||||
discovery, MCP tool exposure, and WebSocket for browser bidirectional
|
||||
access to the call protocol. (WebTransport is deferred per ADR-044; the
|
||||
deferred browser-streaming path is at [webtransport.md](webtransport.md).)
|
||||
|
||||
The key architectural insight that shapes the crate: **HTTP is both a
|
||||
server surface and a client transport for adapters.** The server side
|
||||
@@ -64,13 +70,15 @@ Calls": both sides can initiate calls). The HTTP/1.1 + HTTP/2 surface
|
||||
inherits HTTP's request/response constraint and projects the call
|
||||
protocol one-directionally (client→server calls only — see
|
||||
[http-server.md](http-server.md) §"One-directional projection").
|
||||
WebTransport (`h3`) is the HTTP-family transport that restores the
|
||||
call protocol's native bidirectionality — it is a transport substrate
|
||||
for the call protocol (and, via the ALPN-stream-proxy, for any ALPN),
|
||||
not a browser→hub one-way path. See [webtransport.md](webtransport.md)
|
||||
and ADR-043. The "from/to" naming of the OpenAPI/MCP adapters should not
|
||||
be read as a statement about the call protocol's directionality; it is
|
||||
a statement about OpenAPI's and MCP's directionality.
|
||||
**WebSocket is the HTTP-family transport that restores the call
|
||||
protocol's native bidirectionality for browsers** (ADR-044) — a WS
|
||||
connection is a long-lived full-duplex channel over which either side
|
||||
can send `call.requested` frames in either direction. WebTransport
|
||||
(`h3`, deferred) would restore it via native multi-stream multiplexing;
|
||||
WebSocket restores it via framed messages over one connection. The
|
||||
"from/to" naming of the OpenAPI/MCP adapters should not be read as a
|
||||
statement about the call protocol's directionality; it is a statement
|
||||
about OpenAPI's and MCP's directionality.
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -79,13 +87,22 @@ alknet-http
|
||||
├── alknet-core (ProtocolHandler, Connection, AuthContext, IdentityProvider, Capabilities)
|
||||
├── alknet-call (OperationAdapter, OperationSpec, Handler, HandlerRegistration,
|
||||
│ OperationRegistry, AdapterError, OperationProvenance)
|
||||
├── axum (HTTP server — Router, extractors, middleware)
|
||||
├── axum (HTTP server — Router, extractors, middleware, WebSocket upgrade)
|
||||
├── reqwest (HTTP client — from_openapi/from_mcp forwarding)
|
||||
├── hyper (HTTP/1.1 + HTTP/2 framing; axum is built on hyper)
|
||||
├── wtransport (HTTP/3 + WebTransport — feature-gated behind `h3`)
|
||||
└── rmcp (MCP streamable HTTP — feature-gated behind `mcp`)
|
||||
```
|
||||
|
||||
> **Note:** the `h3`/WebTransport dependency (`wtransport` or the
|
||||
> hyperium `h3` stack) is **not** in the v1 dependency tree —
|
||||
> `h3`/WebTransport is deferred per
|
||||
> [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md).
|
||||
> The `h3` feature gate and its dependency are absent from the initial
|
||||
> release; the browser bidirectional path uses WebSocket (native axum
|
||||
> support, no new dependency). The deferred dependency analysis is
|
||||
> recorded in ADR-044 §"Research note (for revival)" and
|
||||
> [webtransport.md](webtransport.md) §"Research note".
|
||||
|
||||
### The `alknet-call` dependency (ADR-003 Amendment 1)
|
||||
|
||||
`alknet-http` depends on `alknet-call`. ADR-003's rule is "no handler
|
||||
@@ -108,21 +125,22 @@ know `reqwest` is involved.
|
||||
|
||||
| ALPN | Handler | Transport | Browser? |
|
||||
|------|---------|-----------|----------|
|
||||
| `http/1.1` | `HttpAdapter` | HTTP/1.1 over QUIC stream | No |
|
||||
| `h2` | `HttpAdapter` | HTTP/2 over QUIC stream | No |
|
||||
| `h3` | `HttpAdapter` | HTTP/3 / WebTransport | Yes (X.509 required) |
|
||||
| `http/1.1` | `HttpAdapter` | HTTP/1.1 over QUIC stream (+ WS upgrade) | Yes (WS upgrade for bidirectional) |
|
||||
| `h2` | `HttpAdapter` | HTTP/2 over QUIC stream (+ WS upgrade) | Yes (WS upgrade for bidirectional) |
|
||||
| `h3` | — (deferred) | HTTP/3 / WebTransport | Deferred per ADR-044 |
|
||||
|
||||
These are standard IANA ALPN strings, not `alknet/`-prefixed. Any HTTP
|
||||
client connects without knowing about alknet — the TLS handshake
|
||||
negotiates `h2` or `http/1.1` normally, and the `HttpAdapter` serves
|
||||
HTTP. This is the stealth mapping (ADR-010): clients that don't offer
|
||||
alknet ALPNs get the HTTP handler, just like port scanners in stealth
|
||||
mode.
|
||||
mode. A browser negotiates `h2` or `http/1.1` and upgrades to WebSocket
|
||||
for the bidirectional call-protocol path (ADR-044).
|
||||
|
||||
The `HttpAdapter` registers for all three ALPNs (when the corresponding
|
||||
features are enabled). The endpoint's `HandlerRegistry` maps each ALPN to
|
||||
the same `HttpAdapter` instance; the handler branches on
|
||||
`connection.remote_alpn()` to pick the right framing.
|
||||
The `HttpAdapter` registers for `http/1.1` and `h2`. The endpoint's
|
||||
`HandlerRegistry` maps each ALPN to the same `HttpAdapter` instance;
|
||||
the handler branches on `connection.remote_alpn()` to pick the right
|
||||
framing. The `h3` ALPN is not registered in v1 (deferred per ADR-044).
|
||||
|
||||
## Adapter Location Map
|
||||
|
||||
@@ -139,7 +157,7 @@ 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/h3)
|
||||
├── HttpAdapter (axum server — inbound HTTP on h2/http1.1 + WS upgrade)
|
||||
├── 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)
|
||||
@@ -155,24 +173,27 @@ directions.
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default = ["h2", "http1"] # the non-browser HTTP surface
|
||||
h3 = ["dep:wtransport"] # HTTP/3 + WebTransport (browser path; X.509 required)
|
||||
default = ["h2", "http1"] # the HTTP surface (incl. WebSocket upgrade for browsers)
|
||||
mcp = ["dep:rmcp"] # from_mcp / to_mcp (streamable HTTP only — ADR-037)
|
||||
# h3 (HTTP/3 + WebTransport) is deferred per ADR-044 — not in the v1
|
||||
# feature set. The browser bidirectional path uses WebSocket (native to
|
||||
# axum, no feature gate). When WebTransport revives, the `h3` feature
|
||||
# gate returns; see ADR-044 and webtransport.md.
|
||||
```
|
||||
|
||||
- `h2` + `http1` (default): the `axum` + `hyper` HTTP/1.1 + HTTP/2
|
||||
server. This is the surface non-browser clients use.
|
||||
- `h3`: the `wtransport` (or quinn HTTP/3 extension) dependency. Adds
|
||||
the `h3` ALPN handler and the WebTransport streaming path. See
|
||||
[webtransport.md](webtransport.md) and
|
||||
[ADR-038](../../decisions/038-http3-and-webtransport-as-first-class.md).
|
||||
server, including WebSocket upgrade for browser bidirectional access
|
||||
(ADR-044). This is the surface all clients — including browsers, via
|
||||
WS upgrade — use in v1.
|
||||
- `mcp`: the `rmcp` dependency with streamable HTTP transport features
|
||||
only. Adds `from_mcp`/`to_mcp`. See [http-mcp.md](http-mcp.md) and
|
||||
[ADR-037](../../decisions/037-mcp-stdio-transport-exclusion.md).
|
||||
|
||||
A deployment that only needs the REST surface (no browsers, no MCP) uses
|
||||
the default features. A browser-facing hub enables `h3`. A deployment
|
||||
that wants MCP tool import/export enables `mcp`.
|
||||
A deployment that only needs the REST surface (no MCP) uses the default
|
||||
features. A browser-facing hub also uses the default features — the
|
||||
browser bidirectional path is WebSocket, native to axum, no `h3` feature
|
||||
gate needed. A deployment that wants MCP tool import/export enables
|
||||
`mcp`.
|
||||
|
||||
## The No-Env-Vars Invariant
|
||||
|
||||
@@ -202,18 +223,19 @@ verified against this invariant. See ADR-014 and
|
||||
## Architecture (component pointers)
|
||||
|
||||
- **[http-server.md](http-server.md)** — the `HttpAdapter` for `h2`/
|
||||
`http/1.1`: how axum is run over a QUIC bidirectional stream, Bearer
|
||||
auth resolution, the `/healthz` raw route, stealth decoy, and the
|
||||
HTTP-to-call dispatch (ADR-036).
|
||||
`http/1.1` (+ WebSocket upgrade): 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).
|
||||
- **[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).
|
||||
Error fidelity per ADR-023.
|
||||
- **[http-mcp.md](http-mcp.md)** — `from_mcp`/`to_mcp` (feature-gated),
|
||||
streamable HTTP only (ADR-037), the rmcp integration.
|
||||
- **[webtransport.md](webtransport.md)** — the `h3` ALPN handler,
|
||||
WebTransport session/stream handling, the browser streaming path
|
||||
(ADR-038).
|
||||
- **[webtransport.md](webtransport.md)** — the deferred `h3` ALPN
|
||||
handler (HTTP/3 + WebTransport). **Deferred per ADR-044**; kept intact
|
||||
for revival when a concrete ALPN-stream-proxy use case arrives.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
@@ -221,12 +243,13 @@ 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; `to_openapi` is projection, not router |
|
||||
| MCP stdio transport exclusion | [ADR-037](../../decisions/037-mcp-stdio-transport-exclusion.md) | Streamable HTTP only; stdio is not built (RCE vector) |
|
||||
| HTTP/3 + WebTransport first-class | [ADR-038](../../decisions/038-http3-and-webtransport-as-first-class.md) | `h3` in scope, not deferred; browser streaming uses QUIC streams |
|
||||
| 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 |
|
||||
| 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) |
|
||||
| WebTransport ALPN-stream-proxy | [ADR-040](../../decisions/040-webtransport-alpn-stream-proxy.md) | The substrate's mechanism for non-call ALPNs (SSH, git, SFTP) — browser → WebTransport stream → target ALPN handler via WASM parser |
|
||||
| ~~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 |
|
||||
| `to_mcp` tool-gateway pattern | [ADR-041](../../decisions/041-mcp-tool-gateway-pattern.md) | 4 fixed gateway tools (search/schema/call/batch), not one tool per operation |
|
||||
| `to_openapi` gateway pattern | [ADR-042](../../decisions/042-openapi-gateway-pattern.md) | 5 fixed gateway endpoints (search/schema/call/batch/subscribe); per-caller AccessControl-filtered |
|
||||
| WebTransport bidirectional ALPN substrate | [ADR-043](../../decisions/043-webtransport-bidirectional-alpn-substrate.md) | WebTransport carries ALPNs as bidirectional streams; call protocol is the first target; both sides can initiate calls; non-peer clients use a connection-local overlay |
|
||||
| ~~WebTransport bidirectional ALPN substrate~~ | [ADR-043](../../decisions/043-webtransport-bidirectional-alpn-substrate.md) | **Parked** per ADR-044; §2/§3 transfer to WebSocket for v1; §4/§5 revive with WebTransport |
|
||||
| `alknet-call` is protocol-foundation | [ADR-003](../../decisions/003-crate-decomposition.md) Am. 1 | `alknet-http` depends on `alknet-call` (types, not peer handler) |
|
||||
| Bearer auth via `resolve_from_token` | [ADR-004](../../decisions/004-auth-as-shared-core.md) | HTTP handler credential source + resolution (settled) |
|
||||
| Stealth mode = HTTP handler on standard ALPNs | [ADR-010](../../decisions/010-alpn-router-and-endpoint.md) | Decoy for unknown paths (settled) |
|
||||
@@ -234,8 +257,8 @@ verified against this invariant. See ADR-014 and
|
||||
| `OperationAdapter` trait is async | [ADR-017](../../decisions/017-call-protocol-client-and-adapter-contract.md) | HTTP adapters implement the async trait (settled) |
|
||||
| `to_*` adapters are projections | [ADR-017](../../decisions/017-call-protocol-client-and-adapter-contract.md) | `to_openapi`/`to_mcp` consume the registry, don't produce entries (settled) |
|
||||
| Error schema fidelity | [ADR-023](../../decisions/023-operation-error-schemas.md) | `from_openapi` maps HTTP status → `HTTP_<status>` codes; `to_openapi` projects back (settled) |
|
||||
| Browsers require X.509 | [ADR-027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | `h3`/WebTransport needs X.509 (settled) |
|
||||
| Browsers are not alknet peers | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) | Browser over WebTransport/HTTPS = bearer token, no `PeerId` (settled) |
|
||||
| Browsers require X.509 | [ADR-027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | `h3`/WebTransport needs X.509 (settled; applies when WebTransport revives) |
|
||||
| Browsers are not alknet peers | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) §4 (amended by ADR-044 §5) | Browser over WS/WebTransport = bearer token, no `PeerId` (settled; rationale in ADR-044 §5) |
|
||||
|
||||
## Open Questions
|
||||
|
||||
@@ -262,4 +285,6 @@ See [open-questions.md](../../open-questions.md) for full details.
|
||||
- `/workspace/@alkdev/operations/src/from_openapi.ts`,
|
||||
`/workspace/@alkdev/operations/src/from_mcp.ts` — TypeScript prior art
|
||||
- `/workspace/rust-sdk/` — MCP Rust SDK (rmcp); streamable HTTP examples
|
||||
- `/workspace/wtransport/` — pure-Rust WebTransport reference
|
||||
- `/workspace/wtransport/` — pure-Rust WebTransport reference
|
||||
(read during research; not a dependency — see ADR-044 §"Research note"
|
||||
for why `wtransport` is probably not the right revival choice)
|
||||
@@ -1,10 +1,43 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-29
|
||||
status: deferred
|
||||
last_updated: 2026-06-30
|
||||
---
|
||||
|
||||
# WebTransport — the h3 ALPN handler
|
||||
|
||||
> **DEFERRED per [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md).**
|
||||
> This spec is kept intact for revival. `h3`/WebTransport is not
|
||||
> implemented in the initial `alknet-http` release; the browser
|
||||
> bidirectional path uses WebSocket (see
|
||||
> [http-server.md](http-server.md) §"WebSocket browser path"). ADR-038
|
||||
> is superseded; ADR-040 and ADR-043 are parked (their decisions revive
|
||||
> unchanged when WebTransport revives). The reversal trigger is a
|
||||
> concrete deployment needing the ALPN-stream-proxy (a browser running
|
||||
> a WASM SSH/SFTP/git client to reach a non-call ALPN). Two transfers
|
||||
> apply during the deferment: ADR-043 §2 (call-protocol bidirectionality)
|
||||
> and §3 (the no-`PeerId` connection-local overlay) apply over WebSocket
|
||||
> unchanged; ADR-040 (the ALPN-stream-proxy) and ADR-043 §4 (the
|
||||
> non-call-ALPN substrate) do not — they require WebTransport's stream
|
||||
> model and revive with it.
|
||||
>
|
||||
> **Research note (for revival):** `wtransport` (v0.7.1, the reference
|
||||
> implementation read during initial research) is *probably not* the
|
||||
> right dependency choice at revival time, despite being a complete and
|
||||
> readable implementation. The load-bearing integration concern is that
|
||||
> the `h3` handler must route HTTP/3 requests through the same axum
|
||||
> `Router` as `h2`/`http/1.1` (ADR-036), and `wtransport` owns its own
|
||||
> HTTP serving path — bridging its request type into the `http::Request`
|
||||
> axum consumes is cross-ecosystem adapter work. The hyperium stack
|
||||
> (`h3` + `h3-quinn` + `h3-webtransport` + `h3-datagram`) operates at
|
||||
> the stream level and produces `http::Request` types natively, which is
|
||||
> a better fit for the axum integration — but its server-side
|
||||
> WebTransport API needs verification before commitment (the axum-bridge
|
||||
> feasibility is the load-bearing claim and is not yet confirmed against
|
||||
> actual crate APIs, only against READMEs and design philosophy). This
|
||||
> research is **not** run now (WebTransport is deferred); it is recorded
|
||||
> here so the revival does not re-derive the question from scratch. See
|
||||
> ADR-044 §"Research note (for revival)" for the cross-reference.
|
||||
|
||||
The `HttpAdapter` registration for the `h3` ALPN: HTTP/3 and
|
||||
WebTransport. WebTransport is a **bidirectional ALPN transport
|
||||
substrate** (ADR-043) — it carries ALPN protocols as bidirectional
|
||||
@@ -386,13 +419,19 @@ as a first-class transport.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
> **Note:** This table reflects the design as written for revival. ADR-038
|
||||
> is **superseded by [ADR-044](../../decisions/044-defer-webtransport-browsers-use-websocket.md)**;
|
||||
> ADR-040 and ADR-043 are **parked** (implementation deferred per ADR-044).
|
||||
> The decisions revive unchanged when WebTransport revives — see the
|
||||
> header note and ADR-044 for the scope rationale and reversal trigger.
|
||||
|
||||
| Decision | ADR | Summary |
|
||||
|----------|-----|---------|
|
||||
| `h3`/WebTransport is first-class | [ADR-038](../../decisions/038-http3-and-webtransport-as-first-class.md) | In scope, not deferred; browser streaming uses QUIC streams |
|
||||
| WebTransport is a bidirectional ALPN transport substrate | [ADR-043](../../decisions/043-webtransport-bidirectional-alpn-substrate.md) | Carries ALPNs as bidirectional streams; call protocol is the first/canonical target (needs no WASM parser); both sides can initiate calls |
|
||||
| WebTransport ALPN-stream-proxy | [ADR-040](../../decisions/040-webtransport-alpn-stream-proxy.md) | The substrate's mechanism for non-call ALPNs (SSH, git, SFTP) — browser → WebTransport stream → target ALPN handler via WASM parser |
|
||||
| Browsers require X.509 | [ADR-027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | `h3` needs X.509 (browser limitation) |
|
||||
| Browsers are not alknet peers | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) | Bearer token, no `PeerId` |
|
||||
| ~~`h3`/WebTransport is first-class~~ | [ADR-038](../../decisions/038-http3-and-webtransport-as-first-class.md) | **Superseded by ADR-044** (scope deferral); originally "in scope, not deferred; browser streaming uses QUIC streams" |
|
||||
| WebTransport is a bidirectional ALPN transport substrate | [ADR-043](../../decisions/043-webtransport-bidirectional-alpn-substrate.md) | **Parked** per ADR-044. Carries ALPNs as bidirectional streams; call protocol is the first/canonical target (needs no WASM parser); both sides can initiate calls |
|
||||
| WebTransport ALPN-stream-proxy | [ADR-040](../../decisions/040-webtransport-alpn-stream-proxy.md) | **Parked** per ADR-044. The substrate's mechanism for non-call ALPNs (SSH, git, SFTP) — browser → WebTransport stream → target ALPN handler via WASM parser |
|
||||
| Browsers require X.509 | [ADR-027](../../decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | `h3` needs X.509 (browser limitation; applies when WebTransport revives) |
|
||||
| Browsers are not alknet peers | [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md) §4 (amended by ADR-044 §5) | Bearer token, no `PeerId` (rationale in ADR-044 §5) |
|
||||
| WebTransport streams → call protocol directly | [ADR-012](../../decisions/012-call-protocol-stream-model.md) | Stream-agnostic; WebTransport stream = QUIC bidirectional stream |
|
||||
| `BiStream` is a trait (WASM door) | [ADR-007](../../decisions/007-bistream-type-definition.md) | Browser implements `BiStream` over WebTransport stream; WASM parser speaks the protocol |
|
||||
| Stealth on h3 | [ADR-010](../../decisions/010-alpn-router-and-endpoint.md) | Unknown paths get the decoy |
|
||||
|
||||
Reference in New Issue
Block a user