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