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:
@@ -18,9 +18,9 @@ The storage and auth strategy research (`docs/research/alknet-storage-strategy/f
|
||||
|
||||
The alknet-call crate is **implemented and reviewed** — both the server-side core and the client/adapter surface (207 lib + 2 integration tests passing). The alknet-core and alknet-call crate specs are in draft; the alknet-vault crate specs are stable.
|
||||
|
||||
**alknet-http specs drafted and consistency-reviewed.** The alknet-http crate (HTTP interface — `h2`/`http/1.1`/`h3` server + `from_openapi`/`to_openapi`/`from_mcp`/`to_mcp` adapters) now has architecture specs: [crates/http/](crates/http/) (overview, http-server, http-adapters, http-mcp, webtransport) and eight ADRs — [ADR-036](decisions/036-http-to-call-operation-mapping.md) (HTTP-to-call mapping; direct-call surface), [ADR-037](decisions/037-mcp-stdio-transport-exclusion.md) (MCP stdio exclusion), [ADR-038](decisions/038-http3-and-webtransport-as-first-class.md) (HTTP/3 + WebTransport as first-class, correcting the Phase 0 deferral framing), [ADR-039](decisions/039-http-server-and-client-host-colocated.md) (HTTP server + client host colocated in one crate), [ADR-040](decisions/040-webtransport-alpn-stream-proxy.md) (WebTransport ALPN-stream-proxy — the substrate's mechanism for non-call ALPNs like SSH/git/SFTP via WASM parser; the "VPN-like without being a VPN" use case), [ADR-041](decisions/041-mcp-tool-gateway-pattern.md) (`to_mcp` tool-gateway pattern — 4 fixed gateway tools instead of one tool per operation, addressing LLM context tool-bloat), [ADR-042](decisions/042-openapi-gateway-pattern.md) (`to_openapi` gateway pattern — 5 fixed gateway endpoints instead of one path per operation; per-caller AccessControl-filtered API surface; supersedes ADR-036's original `to_openapi` clause), [ADR-043](decisions/043-webtransport-bidirectional-alpn-substrate.md) (WebTransport as a bidirectional ALPN transport substrate — reframes WebTransport as carrying ALPNs as bidirectional streams with the call protocol as the first/canonical target; names the call protocol's bidirectionality over WebTransport; states the inbound no-`PeerId` connection-local overlay as the mirror of ADR-034 §2). ADR-003 Amendment 1 clarifies that `alknet-call` is a protocol-foundation crate (the `alknet-http` → `alknet-call` dependency edge). A consistency review pass corrected drift from the mid-spec pivot (the `to_openapi` gateway pattern landed in the prose but not in cross-references; the WebTransport specs inherited the OpenAPI/MCP direction assumption that doesn't hold for the call protocol) — ADR-036's `to_openapi` clause is now amended as superseded by ADR-042, ADR-034 §5's "deferral bucket" wording is corrected (the decision stands), and the http specs now name the one-directional HTTP projection vs. the bidirectional WebTransport substrate. The specs are in draft; implementation has not started. Three open questions carried: OQ-38 (WebTransport standalone relay service scope — distinct from the in-process ALPN-stream-proxy resolved by ADR-040), OQ-39 (`to_openapi` published-spec versioning), OQ-40 (reqwest client config).
|
||||
**alknet-http specs drafted and consistency-reviewed.** The alknet-http crate (HTTP interface — `h2`/`http/1.1` server + WebSocket browser path + `from_openapi`/`to_openapi`/`from_mcp`/`to_mcp` adapters) now has architecture specs: [crates/http/](crates/http/) (overview, http-server, http-adapters, http-mcp, webtransport) and nine ADRs — [ADR-036](decisions/036-http-to-call-operation-mapping.md) (HTTP-to-call mapping; direct-call surface), [ADR-037](decisions/037-mcp-stdio-transport-exclusion.md) (MCP stdio exclusion), [ADR-038](decisions/038-http3-and-webtransport-as-first-class.md) (HTTP/3 + WebTransport as first-class — **superseded by ADR-044**; its correction of the two-way-door-as-deferral anti-pattern stands, its specific decision is reversed by the scope deferral), [ADR-039](decisions/039-http-server-and-client-host-colocated.md) (HTTP server + client host colocated in one crate), [ADR-040](decisions/040-webtransport-alpn-stream-proxy.md) (WebTransport ALPN-stream-proxy — **parked** per ADR-044; revives unchanged when WebTransport revives), [ADR-041](decisions/041-mcp-tool-gateway-pattern.md) (`to_mcp` tool-gateway pattern — 4 fixed gateway tools instead of one tool per operation, addressing LLM context tool-bloat), [ADR-042](decisions/042-openapi-gateway-pattern.md) (`to_openapi` gateway pattern — 5 fixed gateway endpoints instead of one path per operation; per-caller AccessControl-filtered API surface; supersedes ADR-036's original `to_openapi` clause), [ADR-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), [ADR-044](decisions/044-defer-webtransport-browsers-use-websocket.md) (defer `h3`/WebTransport; browsers use WebSocket for the bidirectional call-protocol path; a scope decision per ADR-009 §"What this framework is NOT"; reversal trigger = a concrete ALPN-stream-proxy use case; states the "browser is not a peer" rationale — addressability vs. bidirectionality — that amends ADR-034 §4). ADR-003 Amendment 1 clarifies that `alknet-call` is a protocol-foundation crate (the `alknet-http` → `alknet-call` dependency edge). A consistency review pass corrected drift from the mid-spec pivot (the `to_openapi` gateway pattern landed in the prose but not in cross-references; the WebTransport specs inherited the OpenAPI/MCP direction assumption that doesn't hold for the call protocol) — ADR-036's `to_openapi` clause is now amended as superseded by ADR-042, ADR-034 §5's "deferral bucket" wording is corrected (the decision stands), and the http specs now name the one-directional HTTP projection vs. the bidirectional WebSocket (and, when revived, WebTransport) substrate. The specs are in draft; implementation has not started. Three open questions carried: OQ-38 (WebTransport standalone relay service scope — distinct from the in-process ALPN-stream-proxy resolved by ADR-040), OQ-39 (`to_openapi` published-spec versioning), OQ-40 (reqwest client config).
|
||||
|
||||
**Next step**: The storage/repo-pattern ADRs (030–033) are accepted and amend the core and call specs. The next implementation phase is the ADR-029 migration (peer-keyed overlays, `PeerRef` routing, retire `remote_safe`/`trusted_peer`) with the ADR-030 `PeerEntry` change and the ADR-032 `forwarded_for` field folded in — the `OperationContext`, `from_call` handler, and `AuthPolicy` are all under edit, making this the cheapest window. After that: alknet-http implementation (specs drafted, ADRs 036–038 proposed), which consumes the `CredentialStore` trait and the `OperationAdapter` contract. The alknet-ssh crate (the other post-core crate, specced in parallel) proceeds independently — it depends on `alknet-core`, not `alknet-call`.
|
||||
**Next step**: The storage/repo-pattern ADRs (030–033) are accepted and amend the core and call specs. The next implementation phase is the ADR-029 migration (peer-keyed overlays, `PeerRef` routing, retire `remote_safe`/`trusted_peer`) with the ADR-030 `PeerEntry` change and the ADR-032 `forwarded_for` field folded in — the `OperationContext`, `from_call` handler, and `AuthPolicy` are all under edit, making this the cheapest window. After that: alknet-http implementation (specs drafted; `h3`/WebTransport deferred per ADR-044, browser bidirectional path uses WebSocket), which consumes the `CredentialStore` trait and the `OperationAdapter` contract. The alknet-ssh crate (the other post-core crate, specced in parallel) proceeds independently — it depends on `alknet-core`, not `alknet-call`.
|
||||
|
||||
## Architecture Documents
|
||||
|
||||
@@ -39,10 +39,10 @@ The alknet-call crate is **implemented and reviewed** — both the server-side c
|
||||
| [crates/call/client-and-adapters.md](crates/call/client-and-adapters.md) | draft | CallClient (outbound connection opener), from_call / from_jsonschema, OperationAdapter trait, adapter location map, no-env-vars invariant, exchange-of-operations pattern |
|
||||
| [crates/http/README.md](crates/http/README.md) | draft | alknet-http crate index |
|
||||
| [crates/http/overview.md](crates/http/overview.md) | draft | Crate purpose, two roles (server + client host), dependencies, adapter location map |
|
||||
| [crates/http/http-server.md](crates/http/http-server.md) | draft | HttpAdapter for h2/http1.1, axum over QUIC, Bearer auth, stealth, /healthz |
|
||||
| [crates/http/http-server.md](crates/http/http-server.md) | draft | HttpAdapter for h2/http1.1 + WebSocket browser path, axum over QUIC, Bearer auth, stealth, /healthz |
|
||||
| [crates/http/http-adapters.md](crates/http/http-adapters.md) | draft | from_openapi (reqwest) and to_openapi (projection); no-env-vars injection point |
|
||||
| [crates/http/http-mcp.md](crates/http/http-mcp.md) | draft | from_mcp / to_mcp (feature-gated), streamable-HTTP-only, stdio exclusion |
|
||||
| [crates/http/webtransport.md](crates/http/webtransport.md) | draft | h3/WebTransport handler — the browser streaming path |
|
||||
| [crates/http/webtransport.md](crates/http/webtransport.md) | deferred | h3/WebTransport handler — deferred per ADR-044; browser bidirectional path uses WebSocket (see http-server.md). Spec kept intact for revival. |
|
||||
| [crates/vault/README.md](crates/vault/README.md) | stable | alknet-vault crate index |
|
||||
| [crates/vault/mnemonic-derivation.md](crates/vault/mnemonic-derivation.md) | stable | BIP39, SLIP-0010, BIP-0032, derivation paths, key types |
|
||||
| [crates/vault/encryption.md](crates/vault/encryption.md) | stable | AES-256-GCM, EncryptedData, key versioning, salt (Phase B reserved) |
|
||||
@@ -90,12 +90,13 @@ The alknet-call crate is **implemented and reviewed** — both the server-side c
|
||||
| [035](decisions/035-concrete-persistence-adapter-shapes.md) | Concrete Persistence Adapter Shapes — Read/Write Split, honker+SQLite | Accepted |
|
||||
| [036](decisions/036-http-to-call-operation-mapping.md) | HTTP-to-Call Operation Mapping | Proposed |
|
||||
| [037](decisions/037-mcp-stdio-transport-exclusion.md) | MCP Stdio Transport Exclusion | Proposed |
|
||||
| [038](decisions/038-http3-and-webtransport-as-first-class.md) | HTTP/3 and WebTransport as First-Class HTTP Transports | Proposed |
|
||||
| [038](decisions/038-http3-and-webtransport-as-first-class.md) | HTTP/3 and WebTransport as First-Class HTTP Transports | ~~Proposed~~ → **Superseded** by ADR-044 |
|
||||
| [039](decisions/039-http-server-and-client-host-colocated.md) | HTTP Server and Client Host Colocated in alknet-http | Proposed |
|
||||
| [040](decisions/040-webtransport-alpn-stream-proxy.md) | WebTransport ALPN-Stream-Proxy | Proposed |
|
||||
| [040](decisions/040-webtransport-alpn-stream-proxy.md) | WebTransport ALPN-Stream-Proxy | Proposed — **parked** (implementation deferred per ADR-044) |
|
||||
| [041](decisions/041-mcp-tool-gateway-pattern.md) | MCP Tool-Gateway Pattern for to_mcp | Proposed |
|
||||
| [042](decisions/042-openapi-gateway-pattern.md) | OpenAPI Gateway Pattern for to_openapi | Proposed |
|
||||
| [043](decisions/043-webtransport-bidirectional-alpn-substrate.md) | WebTransport as a Bidirectional ALPN Transport Substrate | Proposed |
|
||||
| [043](decisions/043-webtransport-bidirectional-alpn-substrate.md) | WebTransport as a Bidirectional ALPN Transport Substrate | Proposed — **parked** (implementation deferred per ADR-044; §2/§3 transfer to WebSocket) |
|
||||
| [044](decisions/044-defer-webtransport-browsers-use-websocket.md) | Defer h3/WebTransport; Browsers Use WebSocket | Accepted |
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -203,8 +203,8 @@ A browser reaching a hub over WebTransport (or HTTPS) is served by the
|
||||
hub's `alknet-http` handler. The browser authenticates by **bearer
|
||||
token** (HTTP `Authorization`), resolved by the hub's
|
||||
`IdentityProvider::resolve_from_token` against the hub's
|
||||
`PeerEntry.auth_token_hash` or `ApiKeyEntry`. The browser is **not** an
|
||||
alknet peer on the hub's side either — it does not get a `PeerId`, does
|
||||
`PeerEntry.auth_token_hash` or `ApiKeyEntry`. The browser is **not an
|
||||
alknet peer on the hub's side either** — it does not get a `PeerId`, does
|
||||
not enter `PeerCompositeEnv`, and its "ops" are HTTP routes / WebTransport
|
||||
streams served by `alknet-http`, not entries in the call-protocol
|
||||
peer-keyed overlay. The hub's `PeerEntry` for the browser (if any) is
|
||||
@@ -214,6 +214,29 @@ This keeps the peer graph populated only by full alknet nodes (role 3
|
||||
hubs and role-3-style spoke nodes), never by browsers or pure HTTP
|
||||
clients.
|
||||
|
||||
> **Amendment (rationale added by
|
||||
> [ADR-044](044-defer-webtransport-browsers-use-websocket.md) §5):** The
|
||||
> closure above is correct but states the conclusion without the
|
||||
> supporting argument. The distinction that makes it correct is:
|
||||
> **"peer" in alknet means an addressable node in the call-protocol peer
|
||||
> graph** — a stable `PeerId`, reachable via `PeerRef::Specific`, whose
|
||||
> ops land in `PeerCompositeEnv`, whose identity is stable across
|
||||
> reconnects. It does *not* mean "any endpoint that exchanges calls
|
||||
> during a live session." A browser is the second thing but not the
|
||||
> first, on three concrete grounds: (1) no stable cryptographic identity
|
||||
> of its own (it presents a bearer token the hub issued; nothing to
|
||||
> pin), (2) ephemeral (close the tab → connection dies → the
|
||||
> connection-local overlay dies with it; a `PeerEntry` keyed to a browser
|
||||
> would be dead within seconds), (3) 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 Layer 2 overlay
|
||||
> (ADR-043 §3, the inbound mirror of §2 above) is what gives the browser
|
||||
> bidirectional-call capability *without* peer-graph membership. This
|
||||
> rationale is transport-agnostic — it applies to WebSocket (the v1
|
||||
> browser path, ADR-044) and to WebTransport (when it revives) equally.
|
||||
> See ADR-044 §5 for the full statement.
|
||||
|
||||
### 5. WebTransport relay-as-proxy is a transport-only feature, scoped separately
|
||||
|
||||
A **WebTransport proxy** that terminates the browser's WebTransport
|
||||
@@ -237,12 +260,15 @@ is a real feature, especially for the browser-to-P2P-peer case. It is
|
||||
> WebTransport lands." That framing was a residual of the "two-way door
|
||||
> as deferral" anti-pattern (ADR-009 §"What this framework is NOT")
|
||||
> that [ADR-038](038-http3-and-webtransport-as-first-class.md) was later
|
||||
> written to reject — `h3`/WebTransport is a first-class transport, in
|
||||
> scope, not deferred. The *auth-model* decision in this §5 (the proxy
|
||||
> is transport-only; it does not change identity resolution) is
|
||||
> unchanged. The *scope* question (does the proxy belong in
|
||||
> `alknet-http` or a separate relay crate?) is tracked as OQ-38 — a
|
||||
> genuine scope question, not a deferral.
|
||||
> written to reject. ADR-038 has since been **superseded by
|
||||
> [ADR-044](044-defer-webtransport-browsers-use-websocket.md)**, which
|
||||
> re-defers `h3`/WebTransport as a genuine scope decision (the browser
|
||||
> bidirectional path uses WebSocket; WebTransport revives when a concrete
|
||||
> ALPN-stream-proxy use case arrives). The *auth-model* decision in this
|
||||
> §5 (the proxy is transport-only; it does not change identity
|
||||
> resolution) is unchanged by either ADR. The *scope* question (does the
|
||||
> proxy belong in `alknet-http` or a separate relay crate?) is tracked
|
||||
> as OQ-38 — a genuine scope question, not a deferral.
|
||||
|
||||
### 6. On-chain / smart-contract peer discovery fits the OQ-36 adapter pattern
|
||||
|
||||
@@ -340,11 +366,17 @@ It is noted here only to confirm it does not reopen OQ-37.
|
||||
intended — it is the same model iroh uses.
|
||||
|
||||
2. **Browsers never enter the peer-keyed overlay.** A browser is
|
||||
served by `alknet-http` (HTTP routes / WebTransport streams) and
|
||||
authenticates by bearer token. The hub may have a `PeerEntry` for
|
||||
the browser's token (to authorize it), but the browser is not a
|
||||
`PeerId`-bearing peer. This is the explicit closure of the
|
||||
"browser as peer" path — browsers are clients, not peers.
|
||||
served by `alknet-http` (HTTP routes / WebTransport streams /, per
|
||||
ADR-044, WebSocket) and authenticates by bearer token. The hub may
|
||||
have a `PeerEntry` for the browser's token (to authorize it), but the
|
||||
browser is not a `PeerId`-bearing peer. This is the explicit closure
|
||||
of the "browser as peer" path — browsers are clients, not peers.
|
||||
**The rationale** (addressability vs. bidirectionality — a browser
|
||||
has no stable identity of its own, is ephemeral, and is not
|
||||
addressable from other nodes) is stated in
|
||||
[ADR-044](044-defer-webtransport-browsers-use-websocket.md) §5, which
|
||||
amends §4 above by reference. The closure applies transport-
|
||||
agnostically.
|
||||
|
||||
3. **X.509 fingerprint pinning is only for known hubs.** Pinning an
|
||||
X.509 fingerprint for an arbitrary public API is brittle (cert
|
||||
@@ -395,13 +427,19 @@ It is noted here only to confirm it does not reopen OQ-37.
|
||||
repo/adapter pattern (trait in core, adapter additive in a separate
|
||||
crate)
|
||||
- `docs/research/alknet-http/phase-0-findings.md` — DH-2 (h3 /
|
||||
WebTransport; the original "deferred past v1" framing is rejected by
|
||||
ADR-038); the WebTransport-relay-as-proxy feature noted in this ADR's
|
||||
§5 is a transport-only feature whose scope is tracked as OQ-38
|
||||
- [ADR-038](038-http3-and-webtransport-as-first-class.md) — `h3` /
|
||||
WebTransport is a first-class transport, not deferred (amends the
|
||||
"deferral bucket" wording in this ADR's §5; the auth-model decision
|
||||
stands)
|
||||
WebTransport; the original "deferred past v1" framing was rejected by
|
||||
ADR-038, which is now itself superseded by
|
||||
[ADR-044](044-defer-webtransport-browsers-use-websocket.md) — a genuine
|
||||
scope deferral); the WebTransport-relay-as-proxy feature noted in this
|
||||
ADR's §5 is a transport-only feature whose scope is tracked as OQ-38
|
||||
- [ADR-038](038-http3-and-webtransport-as-first-class.md) — **superseded
|
||||
by [ADR-044](044-defer-webtransport-browsers-use-websocket.md)**.
|
||||
ADR-038 amended the "deferral bucket" wording in this ADR's §5 (the
|
||||
auth-model decision stands); ADR-044 reverses ADR-038's "h3 in scope
|
||||
now" decision as a scope deferral (the browser bidirectional path
|
||||
uses WebSocket; WebTransport revives when a concrete ALPN-stream-proxy
|
||||
use case arrives). The "browser is not a peer" closure in §4 above is
|
||||
amended by ADR-044 §5 with the addressability rationale.
|
||||
- `docs/research/references/iroh/iroh/04-sub-crates.md` — iroh's
|
||||
transport relay (`iroh-relay`), referenced to distinguish it from
|
||||
alknet's hub role
|
||||
|
||||
@@ -7,7 +7,8 @@ Proposed
|
||||
## Context
|
||||
|
||||
`alknet-http` implements `ProtocolHandler` for the standard HTTP ALPNs (`h2`,
|
||||
`http/1.1`, `h3`). An inbound HTTP request that targets an alknet operation
|
||||
`http/1.1`; `h3`/WebTransport is deferred per
|
||||
[ADR-044](044-defer-webtransport-browsers-use-websocket.md)). An inbound HTTP request that targets an alknet operation
|
||||
must become a call-protocol `call.requested` dispatch — the HTTP handler is a
|
||||
*projection* of the call protocol, not a parallel routing layer. The
|
||||
question is how an HTTP request maps to an operation invocation.
|
||||
@@ -96,9 +97,11 @@ A `Subscription` operation served over HTTP/1.1 or HTTP/2 projects its
|
||||
`call.responded` stream as Server-Sent Events. Each `call.responded` event
|
||||
becomes an SSE `data:` frame; `call.completed` closes the SSE stream;
|
||||
`call.aborted` closes the stream with an SSE error event. 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 is needed (see ADR-038 for the WebTransport path).
|
||||
HTTP/1.1 + HTTP/2 streaming projection. Over WebSocket (the v1 browser
|
||||
bidirectional path, ADR-044), the subscription projects directly onto the
|
||||
WS connection — `call.responded` events as binary WS messages, no SSE
|
||||
framing. WebTransport (`h3`) would project onto WebTransport bidirectional
|
||||
streams but is deferred per ADR-044.
|
||||
|
||||
### Auth
|
||||
|
||||
|
||||
@@ -2,7 +2,24 @@
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
**Superseded by [ADR-044](044-defer-webtransport-browsers-use-websocket.md).**
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -2,7 +2,19 @@
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
**Proposed — implementation deferred per [ADR-044](044-defer-webtransport-browsers-use-websocket.md).**
|
||||
|
||||
This ADR's decision is correct and is not superseded. It revives unchanged
|
||||
when WebTransport revives. ADR-044 defers `h3`/WebTransport as a scope
|
||||
decision (the browser bidirectional path uses WebSocket for v1; the
|
||||
ALPN-stream-proxy is the speculative use case whose concrete need is the
|
||||
reversal trigger). The proxy is the primary WebTransport-specific feature —
|
||||
it requires WebTransport's stream model and does not transfer to WebSocket.
|
||||
When a real deployment needs a browser running a WASM SSH/SFTP/git client to
|
||||
reach a non-call ALPN, this ADR is un-parked and implemented as written.
|
||||
|
||||
The `webtransport.md` spec is kept intact and marked `deferred` so the
|
||||
revival is unblocking already-written design, not re-deriving it.
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
@@ -214,11 +214,14 @@ require it for the common case.
|
||||
`/schema` at build time.
|
||||
|
||||
4. **`subscribe` (SSE) is the streaming projection for the gateway.**
|
||||
Over `h2`/`http/1.1`, subscriptions are SSE. Over WebTransport
|
||||
(`h3`), subscriptions project onto WebTransport streams directly
|
||||
(ADR-038) — the gateway's `/subscribe` is the `h2`/`http/1.1` path;
|
||||
the WebTransport path is the native call-protocol session
|
||||
(`webtransport.md`).
|
||||
Over `h2`/`http/1.1`, subscriptions are SSE. Over WebSocket (the v1
|
||||
browser bidirectional path, ADR-044), subscriptions project onto the
|
||||
WS connection directly as binary messages — the gateway's `/subscribe`
|
||||
is the `h2`/`http/1.1` SSE path; the WebSocket path is the native
|
||||
call-protocol session (`http-server.md` §"WebSocket browser path").
|
||||
WebTransport (`h3`, deferred per ADR-044) would project onto
|
||||
WebTransport streams; the deferred design is at
|
||||
`webtransport.md`.
|
||||
|
||||
## References
|
||||
|
||||
@@ -232,9 +235,11 @@ require it for the common case.
|
||||
- [ADR-036](036-http-to-call-operation-mapping.md) — the SSE projection
|
||||
for subscriptions over `h2`/`http/1.1` (the gateway's `/subscribe`
|
||||
endpoint uses the same SSE framing)
|
||||
- [ADR-038](038-http3-and-webtransport-as-first-class.md) — the
|
||||
WebTransport streaming path (the gateway's `/subscribe` is the
|
||||
`h2`/`http/1.1` path; WebTransport is native)
|
||||
- [ADR-044](044-defer-webtransport-browsers-use-websocket.md) —
|
||||
WebSocket is the v1 browser bidirectional path; `h3`/WebTransport
|
||||
deferred (the gateway's `/subscribe` is the `h2`/`http/1.1` SSE path;
|
||||
the WS path is the native call-protocol session). ADR-038 is
|
||||
superseded by ADR-044.
|
||||
- [ADR-041](041-mcp-tool-gateway-pattern.md) — the sibling gateway
|
||||
pattern for `to_mcp` (4 tools; `subscribe` excluded because MCP tool
|
||||
calls are request/response)
|
||||
|
||||
@@ -2,7 +2,28 @@
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
**Proposed — implementation deferred per [ADR-044](044-defer-webtransport-browsers-use-websocket.md).**
|
||||
|
||||
This ADR's decision is correct and is not superseded. It revives unchanged
|
||||
when WebTransport revives, **with two transfers to WebSocket that apply
|
||||
during the deferment**:
|
||||
|
||||
- **§2 (call-protocol bidirectionality) transfers to WebSocket unchanged.**
|
||||
WebSocket is full-duplex; the call protocol's bidirectionality applies over
|
||||
a WS connection exactly as §2 describes for WebTransport. The browser case
|
||||
where the client registers no ops remains a use-case scoping, not an
|
||||
architectural limitation.
|
||||
- **§3 (the no-`PeerId` connection-local overlay) transfers to WebSocket
|
||||
unchanged.** A browser over WSS has no `PeerId` on the hub's side for the
|
||||
same reasons it has none over WebTransport (ADR-044 §5); the
|
||||
connection-local Layer 2 overlay applies. The pattern is transport-agnostic.
|
||||
|
||||
What does **not** transfer to WebSocket is §4 (the non-call-ALPN substrate
|
||||
mechanism / the ALPN-stream-proxy, ADR-040) and §5's WebTransport-specific
|
||||
framing. Those require WebTransport's stream model and revive with it.
|
||||
ADR-044 §3 states the transfer explicitly; ADR-044 §5 states the
|
||||
"browser is not a peer" rationale (addressability vs. bidirectionality)
|
||||
that this ADR's §3 relies on but does not argue.
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
# ADR-044: Defer h3/WebTransport; Browsers Use WebSocket
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (supersedes ADR-038; parks ADR-040, ADR-043)
|
||||
|
||||
## Context
|
||||
|
||||
ADR-038 brought `h3`/WebTransport into scope as a first-class HTTP transport,
|
||||
framed against the "two-way door as deferral" anti-pattern (ADR-009 §"What
|
||||
this framework is NOT"). ADR-040 (the ALPN-stream-proxy) and ADR-043 (the
|
||||
bidirectional-substrate reframing) extended it. Three ADRs, one crate-spanning
|
||||
spec (`webtransport.md`), and a body of design work.
|
||||
|
||||
Working through the implementation path surfaced a different concern than the
|
||||
one ADR-038 was written to correct. ADR-038 correctly rejected *deferral-
|
||||
as-hedging*; the present decision is *deferral-as-scoping*, which ADR-009
|
||||
explicitly permits (a decision that "genuinely doesn't need to be made yet
|
||||
because the use case isn't concrete" — scope management, not door-type
|
||||
classification). The two must not be
|
||||
confused. Three concrete findings drove the scope re-evaluation:
|
||||
|
||||
### Finding 1 — the browser bidirectional path doesn't require WebTransport
|
||||
|
||||
The load-bearing use case for `h3`/WebTransport in v1 is **a browser reaching
|
||||
the call protocol bidirectionally**. ADR-043 §2 establishes that the call
|
||||
protocol's bidirectionality applies unchanged over any bidirectional stream —
|
||||
the `Dispatcher` is stream-agnostic (ADR-012). That property is not unique to
|
||||
WebTransport streams. **WebSocket is a full-duplex, long-lived connection over
|
||||
which either side can send framed messages**, and the call protocol's
|
||||
`EventEnvelope` framing fits a WebSocket binary message boundary cleanly (an
|
||||
`EventEnvelope` is a self-delimited JSON object; one frame = one WS binary
|
||||
message). The `call.requested`/`call.responded`/`call.completed`/`call.aborted`
|
||||
exchange works over WebSocket with no protocol change — the same `Dispatcher`,
|
||||
the same `PendingRequestMap`, the same correlation by request ID.
|
||||
|
||||
What WebTransport gives *over* WebSocket — native multiplexed bidirectional
|
||||
streams, datagrams, the "carry any ALPN as a stream" substrate framing
|
||||
(ADR-043) — is genuinely better engineering, but none of it is *required* for
|
||||
the call protocol from a browser. The call protocol multiplexes multiple calls
|
||||
over a single connection by request ID (ADR-012); it does not need
|
||||
WebTransport's per-stream multiplexing. The substrate/proxy framing (ADR-040,
|
||||
ADR-043) is the thing that *does* benefit from WebTransport's stream model —
|
||||
and that use case is the speculative one (see Finding 3).
|
||||
|
||||
### Finding 2 — WebTransport is a draft standard on an experimental dependency stack
|
||||
|
||||
WebTransport over HTTP/3 is still an IETF draft (`draft-ietf-webtrans-http3`,
|
||||
at `-07` at time of writing), not an RFC. The Rust implementation landscape is
|
||||
correspondingly immature:
|
||||
|
||||
- `wtransport` (the reference read during research) is a complete
|
||||
pure-Rust implementation, but its own README states it "is not considered
|
||||
completely production-ready" and "may undergo changes as the WebTransport
|
||||
specification evolves."
|
||||
- The hyperium stack (`h3` + `h3-quinn` + `h3-webtransport` + `h3-datagram`)
|
||||
fits the axum/hyper ecosystem more naturally (h3 produces `http::Request`
|
||||
types that axum consumes directly, which is load-bearing for the spec's
|
||||
"HTTP/3 requests go through the same axum `Router`" commitment), but h3's
|
||||
own README says it is "still very experimental... API could change."
|
||||
- A research spike would be needed to verify the hyperium stack's
|
||||
server-side WebTransport API before committing to it — the axum-bridge
|
||||
feasibility is the load-bearing claim and is not yet confirmed against
|
||||
actual crate APIs, only against READMEs and design philosophy.
|
||||
|
||||
Either choice puts a draft-standard protocol and an experimental Rust
|
||||
dependency on the security surface of `alknet-http`'s first release. The `h3`
|
||||
feature gate (ADR-038) isolates the risk for non-browser-facing deployments,
|
||||
but a browser-facing hub must enable it — so the risk is borne precisely by
|
||||
the deployment shape that motivates having a browser path at all.
|
||||
|
||||
### Finding 3 — the ALPN-stream-proxy is speculative; the call protocol is not
|
||||
|
||||
ADR-040 (the ALPN-stream-proxy — a browser with a WASM parser for SSH/SFTP/git
|
||||
reaching any ALPN handler via WebTransport) is the genuinely compelling
|
||||
WebTransport use case. It is also the one that is *not* required for v1:
|
||||
|
||||
- The call protocol from a browser works over WebSocket (Finding 1).
|
||||
- The downstream crates unlocked by completing `alknet-http` (the SSH, git,
|
||||
SFTP crates) do not require WebTransport or the proxy. They expose their
|
||||
ALPNs natively over QUIC; the proxy is a *browser reachability* feature
|
||||
for those ALPNs, not a prerequisite for the ALPNs to exist.
|
||||
- The WASM parsers (the browser-side SSH/SFTP/git clients) are themselves
|
||||
downstream artifacts not yet built. The proxy is only useful once a parser
|
||||
exists to consume it.
|
||||
|
||||
The proxy is "useful, and cheap-on-top *if* WebTransport already exists" —
|
||||
but WebTransport does not yet exist, and building it speculatively to enable
|
||||
a proxy whose consumers do not yet exist is the scope inversion.
|
||||
|
||||
### The iroh precedent
|
||||
|
||||
iroh's own relay (`iroh-relay`, the DERP-equivalent that provides NAT traversal
|
||||
fallback) chose **WebSocket (WSS)**, not WebTransport, for its fallback path.
|
||||
This is a strong signal from a project whose entire design center is QUIC and
|
||||
P2P connectivity: when the question was "what does a browser need to reach our
|
||||
protocol bidirectionally," their answer was WSS, not WebTransport. Aligning
|
||||
with that precedent is not cutting against competent practice — it is
|
||||
matching it.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Defer `h3`/WebTransport. Browsers reach the call protocol over WebSocket.
|
||||
|
||||
The `h3` ALPN, the `h3` feature gate, and the WebTransport dependency stack
|
||||
are **deferred** — not implemented in the initial `alknet-http` release. A
|
||||
browser connecting to a hub authenticates by bearer token and upgrades an
|
||||
HTTP/1.1 or HTTP/2 request to WebSocket. The resulting full-duplex WS
|
||||
connection carries call-protocol `EventEnvelope` frames as binary WebSocket
|
||||
messages. The browser is a bidirectional call-protocol client over this
|
||||
connection, using the same `Dispatcher` and `PendingRequestMap` as the
|
||||
`alknet/call` QUIC path (ADR-012 — stream-agnostic correlation; a WS message
|
||||
stream is just another `BiStream`-satisfying transport, extending ADR-012's
|
||||
stream-agnostic claim from QUIC bidirectional streams to any framed
|
||||
full-duplex byte channel).
|
||||
|
||||
This is a **scope** decision, not a hedging deferral (ADR-009 §"What this
|
||||
framework is NOT"). The reversal trigger is concrete: **a real deployment that
|
||||
needs the ALPN-stream-proxy (a browser running a WASM SSH/SFTP/git client to
|
||||
reach a non-call ALPN)**. When that use case arrives, ADR-038 / ADR-040 /
|
||||
ADR-043 revive as the design — they are not wrong, they are not-now. No
|
||||
"v1/later/when-it-arrives" hedging language attaches; the condition is stated
|
||||
as a concrete trigger.
|
||||
|
||||
### 2. ADR-038 is superseded by this ADR.
|
||||
|
||||
ADR-038's core decision — that `h3` is in scope, not deferred — is reversed
|
||||
by this ADR. ADR-038's *correction* of the "two-way-door-as-deferral"
|
||||
anti-pattern stands as a document (the anti-pattern is real); its specific
|
||||
decision (h3 in scope now) is superseded. ADR-038 is marked Superseded.
|
||||
|
||||
### 3. ADR-040 and ADR-043 are parked, not superseded.
|
||||
|
||||
ADR-040 (the ALPN-stream-proxy) and ADR-043 (the bidirectional-substrate
|
||||
reframing) are **not superseded** — their decisions are correct, and they
|
||||
revive unchanged when WebTransport revives. They are marked Proposed with an
|
||||
amendment noting implementation is deferred per this ADR. Two specific
|
||||
transfers apply during the deferment:
|
||||
|
||||
- **ADR-043 §2 (call-protocol bidirectionality over WebTransport) transfers
|
||||
to WebSocket unchanged.** WebSocket is full-duplex; the call protocol's
|
||||
bidirectionality applies over a WS connection exactly as ADR-043 §2
|
||||
describes for WebTransport. The browser case where the client registers
|
||||
no ops remains a use-case scoping, not an architectural limitation.
|
||||
- **ADR-043 §3 (the no-`PeerId` connection-local overlay) transfers to
|
||||
WebSocket unchanged.** A browser over WSS has no `PeerId` on the hub's
|
||||
side for the same reasons it has none over WebTransport (see §5 below);
|
||||
the connection-local Layer 2 overlay applies. The pattern is
|
||||
transport-agnostic.
|
||||
|
||||
What does *not* transfer to WebSocket is ADR-040 (the ALPN-stream-proxy) and
|
||||
ADR-043 §4 (the non-call-ALPN substrate mechanism). Those require
|
||||
WebTransport's stream model and revive with it. SSH/SFTP/git-over-WSS-from-a-
|
||||
browser is technically possible (multiplex logical streams over one WS frame
|
||||
stream) but is not specified here — it is the same speculative use case that
|
||||
motivates deferring WebTransport, and it is not needed for v1.
|
||||
|
||||
### 4. WebSocket is the browser bidirectional path; HTTP/1.1+HTTP/2 remain the one-directional projection.
|
||||
|
||||
`alknet-http`'s browser-reachable surface becomes:
|
||||
|
||||
| Transport | Direction | Use case |
|
||||
|-----------|-----------|----------|
|
||||
| `http/1.1`, `h2` | one-directional (client→server) | HTTP clients (curl, axios, `fetch` for request/response); SSE for subscription streaming (ADR-036) |
|
||||
| WebSocket (over `http/1.1` or `h2` upgrade) | **bidirectional** | Browser call-protocol clients; the path that restores the call protocol's bidirectionality for browsers |
|
||||
|
||||
WebSocket is the surface that **restores the call protocol's bidirectionality
|
||||
for browsers** (the role ADR-043 §5 assigned to WebTransport). The
|
||||
one-directional projection that ADR-043 §5 names for HTTP/1.1+HTTP/2 stands
|
||||
unchanged.
|
||||
|
||||
### 5. Browsers over WebSocket are not alknet peers — the rationale, stated.
|
||||
|
||||
ADR-034 §4 established that a browser over WebTransport is not an alknet peer
|
||||
(no `PeerId`, no `PeerCompositeEnv` entry). The same applies to a browser over
|
||||
WebSocket, and the rationale — which ADR-034 §4 states as a closure without
|
||||
the supporting argument — is worth making explicit because it is the
|
||||
load-bearing distinction:
|
||||
|
||||
**"Peer" in alknet means an addressable node in the call-protocol peer graph
|
||||
— a stable `PeerId`, reachable via `PeerRef::Specific`, whose ops land in
|
||||
`PeerCompositeEnv`, whose identity is stable across reconnects.** It does
|
||||
*not* mean "any endpoint that exchanges calls during a live session." A
|
||||
browser is the second thing but not the first, on three concrete grounds:
|
||||
|
||||
1. **No stable cryptographic identity of its own.** A `PeerEntry` is anchored
|
||||
to fingerprints (Ed25519, X.509) that *the peer* presents and the local
|
||||
node pins. A browser presents a bearer token the *hub* issued; the
|
||||
"identity" is the hub's bookkeeping for that token, not something the
|
||||
browser owns or that could be pinned by another node. There is nothing
|
||||
to put in `PeerEntry.fingerprints`.
|
||||
2. **Ephemeral.** Close the tab → connection dies → the connection-local
|
||||
Layer 2 overlay (ADR-043 §3 / ADR-034 §2) dies with it. A `PeerEntry`
|
||||
keyed to a browser would be a permanently-dead entry within seconds.
|
||||
`PeerRef::Specific("browser-X")` from another node would route to
|
||||
nothing.
|
||||
3. **Not addressable from other nodes.** `PeerRef::Specific` resolves through
|
||||
`PeerEntry` → `PeerId`. Another alknet node has no way to reach "the
|
||||
browser currently connected to hub-A"; the hub holds that connection as a
|
||||
live `CallConnection` handle, not as a peer-graph entry. The
|
||||
connection-local overlay is precisely the mechanism that gives the
|
||||
browser bidirectional-call capability *without* peer-graph membership.
|
||||
|
||||
This is the explicit closure of the "browser as peer" path, on both the
|
||||
inbound (this section) and outbound (ADR-034 §2) sides. The browser is a
|
||||
**bidirectional call target during a live session**, not a **peer-graph
|
||||
member**. The connection-local Layer 2 overlay (ADR-024, ADR-043 §3) is what
|
||||
makes the former possible without requiring the latter.
|
||||
|
||||
This rationale applies transport-agnostically — to WebSocket, to WebTransport
|
||||
when it revives, and to any future browser transport. ADR-034 §4 is amended
|
||||
by reference to this section.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- `alknet-http`'s first release does not carry a draft-standard protocol or
|
||||
an experimental dependency stack on its security surface. The browser path
|
||||
uses WebSocket, a mature, well-understood, RFC 6455 protocol with first-
|
||||
class axum support (`axum::extract::ws`).
|
||||
- The axum-bridge research spike for h3/WebTransport is not on the critical
|
||||
path. WebSocket upgrade over HTTP/1.1 or HTTP/2 is standard axum territory.
|
||||
- The downstream crates that `alknet-http` unblocks (SSH, git, SFTP) are not
|
||||
blocked on WebTransport or the proxy. They expose their ALPNs natively over
|
||||
QUIC; browser reachability for them is a future WebTransport feature.
|
||||
- Forward momentum is preserved: the `h3` handler, the feature gate, the
|
||||
`wtransport`/hyperium decision, and the ALPN-stream-proxy are all real
|
||||
design work that is already done (ADR-038, ADR-040, ADR-043,
|
||||
`webtransport.md`). Reviving them is unblocking already-written specs, not
|
||||
designing from scratch.
|
||||
|
||||
**Negative:**
|
||||
- ADR-038, ADR-040, and ADR-043 are not implemented in the initial release.
|
||||
Their design work is preserved (the ADRs and `webtransport.md` stay in the
|
||||
record), but a reader must cross-reference this ADR to know they are
|
||||
parked. The `webtransport.md` spec is marked `deferred` with a header note.
|
||||
- The ALPN-stream-proxy (ADR-040) is not available in v1. A browser cannot
|
||||
reach SSH/SFTP/git ALPNs in the initial release — it can reach the call
|
||||
protocol over WebSocket, but not the non-call ALPNs. This is the
|
||||
speculative use case whose deferral this ADR commits; the reversal trigger
|
||||
is a real deployment needing it.
|
||||
- WebSocket is a single stream; it lacks WebTransport's native multi-stream
|
||||
multiplexing. For the call protocol this is fine (correlation is by request
|
||||
ID, not by stream — ADR-012), but it means a future migration to
|
||||
WebTransport would be a genuine upgrade, not a no-op. The migration path
|
||||
is the spec that already exists (`webtransport.md`).
|
||||
- ADR-043's "WebTransport restores bidirectionality" framing (§5) becomes
|
||||
"WebSocket restores bidirectionality" for v1. The framing transfer is clean
|
||||
(§3 above), but the prose in `http-server.md` and the ADRs must reflect it.
|
||||
|
||||
## Reversal
|
||||
|
||||
This decision reverses when a concrete deployment needs the ALPN-stream-proxy
|
||||
— i.e., a real use case of a browser running a WASM SSH/SFTP/git client to
|
||||
reach a non-call ALPN over WebTransport. At that point:
|
||||
|
||||
1. The research spike deferred here (verify the hyperium stack's server-side
|
||||
WebTransport API and the axum-bridge feasibility — see §"Research note"
|
||||
in `webtransport.md`) is run.
|
||||
2. ADR-038 / ADR-040 / ADR-043 are un-parked and implemented as written,
|
||||
with the `webtransport.md` spec as the design.
|
||||
3. The WebSocket browser path (this ADR's §4) is not removed — it remains as
|
||||
the simpler browser path for deployments that don't need WebTransport's
|
||||
stream model. The two coexist.
|
||||
|
||||
The reversal is a one-way door at the *crate surface* (the `h3` feature gate
|
||||
becomes part of the published interface) but a two-way door at the
|
||||
*architecture* (the `webtransport.md` design already exists; reviving it is
|
||||
implementation work, not redesign). The `webtransport.md` spec is kept intact
|
||||
and marked `deferred` so the revival is unblocking, not re-deriving.
|
||||
|
||||
## Research note (for revival)
|
||||
|
||||
A note for the revival: `wtransport` (the reference implementation read during
|
||||
initial research) is *probably not* the right dependency choice, despite
|
||||
being a complete and readable implementation. The load-bearing integration
|
||||
concern is that `alknet-http`'s `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`) 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. 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 `webtransport.md` §"Research note" for the cross-reference.
|
||||
|
||||
## Assumptions
|
||||
|
||||
1. **The call protocol's `EventEnvelope` framing fits a WebSocket binary
|
||||
message boundary cleanly.** An `EventEnvelope` is a self-delimited JSON
|
||||
object; one envelope per WS binary message. No streaming deserializer
|
||||
across message boundaries is needed. This is verified by implementation
|
||||
when the WS browser path is built, not by a separate research spike — the
|
||||
call protocol spec (`call-protocol.md`) and the EventEnvelope shape
|
||||
already make this property clear, and WebSocket binary messages are a
|
||||
standard byte-framed transport.
|
||||
|
||||
2. **WebSocket upgrade over HTTP/1.1 or HTTP/2 is supported by the axum/
|
||||
hyper stack natively.** `axum::extract::ws` provides the upgrade handler;
|
||||
the underlying connection is the same hyper HTTP connection the `h2`/
|
||||
`http/1.1` handler already drives. No new framing library is needed.
|
||||
|
||||
3. **A browser over WebSocket has the same peer-model properties as a browser
|
||||
over WebTransport.** No `PeerId`, no `PeerCompositeEnv` entry, connection-
|
||||
local Layer 2 overlay (ADR-043 §3, ADR-034 §2). The rationale in §5 is
|
||||
transport-agnostic and applies identically to WSS.
|
||||
|
||||
4. **The downstream crates (SSH, git, SFTP) do not require WebTransport or
|
||||
the ALPN-stream-proxy to exist.** They expose their ALPNs natively over
|
||||
QUIC; the proxy is a browser-reachability feature, not a prerequisite for
|
||||
the ALPNs themselves. Browser reachability for non-call ALPNs is the
|
||||
speculative use case whose deferral this ADR commits.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR-009](009-one-way-door-decision-framework.md) §"What this framework is
|
||||
NOT" — the anti-pattern ADR-038 was written to correct; this ADR relies on
|
||||
ADR-009's explicit distinction between deferral-as-hedging (rejected) and
|
||||
deferral-as-scoping (permitted: a decision that "genuinely doesn't need to
|
||||
be made yet because the use case isn't concrete" — scope management, not
|
||||
door-type classification)
|
||||
- [ADR-038](038-http3-and-webtransport-as-first-class.md) — **superseded by
|
||||
this ADR.** Its correction of the two-way-door-as-deferral anti-pattern
|
||||
stands; its specific decision (h3 in scope now) is reversed.
|
||||
- [ADR-040](040-webtransport-alpn-stream-proxy.md) — **parked, not
|
||||
superseded.** Revives unchanged when WebTransport revives. The proxy is
|
||||
the speculative use case whose deferral is this ADR's reversal trigger.
|
||||
- [ADR-043](043-webtransport-bidirectional-alpn-substrate.md) — **parked, not
|
||||
superseded.** §2 (bidirectionality) and §3 (no-`PeerId` overlay) transfer
|
||||
to WebSocket unchanged; §4 (non-call-ALPN substrate) and §5's
|
||||
WebTransport-specific framing revive with WebTransport.
|
||||
- [ADR-034](034-outgoing-only-x509-and-three-peer-roles.md) §4 — browsers are
|
||||
not alknet peers; this ADR's §5 states the rationale (addressability vs.
|
||||
bidirectionality) that ADR-034 §4 closes without arguing. ADR-034 §4 is
|
||||
amended by reference to this ADR's §5.
|
||||
- [ADR-012](012-call-protocol-stream-model.md) — stream-agnostic correlation;
|
||||
a WebSocket message stream is another `BiStream`-satisfying transport. The
|
||||
call protocol multiplexes by request ID, not by stream.
|
||||
- [ADR-036](036-http-to-call-operation-mapping.md) — the HTTP-to-call
|
||||
mapping; the WebSocket browser path layers on top of the same axum
|
||||
`Router` and `OperationRegistry::invoke()` dispatch.
|
||||
- `crates/http/webtransport.md` — the deferred spec; marked `deferred` with
|
||||
a header note pointing here. Kept intact for revival.
|
||||
- `crates/http/http-server.md` — gains a "WebSocket browser path" section
|
||||
(the v1 browser bidirectional path) and the "browser is not a peer"
|
||||
rationale (this ADR's §5, transported to the spec that now carries the
|
||||
browser path).
|
||||
@@ -774,17 +774,19 @@ is a feature extension, not an unmade architecture decision.
|
||||
|
||||
2. **Standalone relay service (this OQ).** A full relay — a fork of
|
||||
`iroh-relay` — that provides NAT traversal infrastructure with
|
||||
WebTransport-based proxy as a fallback alongside websocket. This
|
||||
WebTransport-based proxy as a fallback alongside WebSocket. This
|
||||
is a separate service, not a mode of the `h3` handler: it
|
||||
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). ADR-034 §5 recorded it in
|
||||
the h3/WebTransport bucket; ADR-038 brought h3/WebTransport into
|
||||
scope; ADR-040 resolved the in-process proxy. This OQ is the
|
||||
remaining scope question: does the standalone relay live in a
|
||||
future `alknet-relay` crate (a fork of `iroh-relay` with
|
||||
WebTransport proxy fallback) or is it out of scope for the
|
||||
current alknet work?
|
||||
scope (later superseded by [ADR-044](decisions/044-defer-webtransport-browsers-use-websocket.md),
|
||||
which deferred h3/WebTransport as a scope decision — the browser
|
||||
bidirectional path uses WebSocket); ADR-040 resolved the in-process
|
||||
proxy (now parked per ADR-044). This OQ is the remaining scope
|
||||
question: does the standalone relay live in a future `alknet-relay`
|
||||
crate (a fork of `iroh-relay` with WebTransport proxy fallback) or
|
||||
is it out of scope for the current alknet work?
|
||||
|
||||
This is a genuine scope question, not a deferral. The relay use case
|
||||
is not yet concrete enough to commit the crate boundary — no
|
||||
@@ -796,8 +798,8 @@ is a feature extension, not an unmade architecture decision.
|
||||
The relay does not change the auth model (bearer token +
|
||||
`PeerEntry.auth_token_hash`; relay is transport-only), so it does not
|
||||
block any other ADR.
|
||||
- **Cross-references**: ADR-027, ADR-030, ADR-034, ADR-038, ADR-040,
|
||||
[webtransport.md](crates/http/webtransport.md)
|
||||
- **Cross-references**: ADR-027, ADR-030, ADR-034, ADR-038 (superseded),
|
||||
ADR-040 (parked), ADR-044, [webtransport.md](crates/http/webtransport.md)
|
||||
|
||||
### OQ-39: `to_openapi` Published-Spec Versioning
|
||||
|
||||
|
||||
Reference in New Issue
Block a user