Files
alknet/docs/architecture/crates/http/README.md
glm-5.2 b71db99753 docs(http): add ADR-048 and websocket.md — WS carries native session, not gateway
Promote the WebSocket browser path from a section in http-server.md to a
first-class spec (websocket.md) and commit the contract-pattern decision
(ADR-048): a WS connection carries the native EventEnvelope call-protocol
session, not the HTTP gateway shape. The gateway endpoints are HTTP-only;
discovery on WS is via services/list/services/schema as ordinary call-protocol
ops; subscriptions project as native call.responded events (no SSE).

ADR-044 already decided WS as the v1 browser bidirectional path; ADR-048
clarifies the shape of what ADR-044 committed (§1 implies native session;
the ADR makes it an explicit implementer-visible rule). The from_wss adapter
(importing a remote node's ops over WS) is recorded as out-of-scope with a
concrete reversal trigger so it is not re-derived later.

Spec cleanup: http-server.md WS section collapsed to a stub pointer;
websocket.md Why section references ADRs rather than re-arguing them;
length-prefix decision made canonical (no prefix on WS — message boundary
is the delimiter); default upgrade path pinned (/alknet/call) with HTTP/2
extended CONNECT noted; indexes (README, http/README, overview) updated.
2026-06-30 12:27:00 +00:00

14 KiB

status, last_updated
status last_updated
draft 2026-06-30

alknet-http

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.

Documents

Document Status Description
overview.md draft Crate purpose, two roles (server + client host), dependencies, adapter location map
http-server.md draft HttpAdapter (ProtocolHandler for h2/http/1.1 + WS upgrade route), axum over QUIC, Bearer auth, stealth, /healthz; WS hands off to the native session spec
websocket.md draft WebSocket browser bidirectional path — native EventEnvelope call-protocol session (not the gateway shape, ADR-048); framing, dispatch, bidirectionality, connection-local Layer 2 overlay, browsers-are-not-peers rationale, streaming (native call.responded, no SSE), deferred from_wss adapter
http-adapters.md draft from_openapi (reqwest client) and to_openapi (OpenAPI projection); no-env-vars invariant point
http-mcp.md draft from_mcp / to_mcp (feature-gated), streamable-HTTP-only, stdio exclusion
webtransport.md deferred h3/WebTransport handler — deferred per ADR-044; spec kept intact for revival

Applicable ADRs

ADR Title Relevance
001 ALPN-Based Protocol Dispatch HttpAdapter registers on standard HTTP ALPNs
002 ProtocolHandler Trait HttpAdapter implements ProtocolHandler
003 Crate Decomposition alknet-http depends on alknet-core + alknet-call (protocol-foundation exception, Amendment 1)
004 Auth as Shared Core Bearer → resolve_from_token
007 BiStream Type Definition HttpAdapter receives Connection, accepts a stream for hyper
010 ALPN Router and Endpoint Stealth mode = HTTP handler on standard ALPNs
014 Secret Material Flow from_openapi/from_mcp are the credential injection point
015 Privilege Model Adapter-registered ops are Internal by default
017 Call Protocol Client and Adapter Contract OperationAdapter trait; to_* are projections; published-spec contract
022 Handler Registration, Provenance, Composition Authority from_openapi/from_mcp produce leaf bundles
023 Operation Error Schemas from_openapi/to_openapi error fidelity; HTTP_<status> error codes
027 TLS Identity Redesign Browsers require X.509; applies to WebTransport (deferred) and any browser-facing TLS
034 Outgoing-Only X.509 and Three Peer Roles Browsers are not alknet peers (§4 amended by ADR-044 §5 with the addressability rationale)
036 HTTP-to-Call Operation Mapping Direct path mappingrouting superseded by ADR-047; non-routing clauses survive (SSE projection, Bearer auth, /healthz, stealth, error mapping)
037 MCP Stdio Transport Exclusion Streamable HTTP only; stdio not built
038 HTTP/3 and WebTransport as First-Class HTTP Transports Superseded by ADR-044 (anti-pattern correction stands; specific decision reversed)
039 HTTP Server and Client Host Colocated in alknet-http One crate for server + client host (shared HTTP deps, shared mapping)
040 WebTransport ALPN-Stream-Proxy Parked per ADR-044; revives unchanged when WebTransport revives
041 MCP Tool-Gateway Pattern for to_mcp 4 fixed gateway tools (search/schema/call/batch), not one tool per operation; Subscription excluded
042 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 WebTransport as a Bidirectional ALPN Transport Substrate Parked per ADR-044; §2/§3 transfer to WebSocket for v1
044 Defer h3/WebTransport; Browsers Use WebSocket h3/WebTransport deferred (scope); browser bidirectional path uses WebSocket; "browser is not a peer" rationale
045 to_openapi Gateway-Spec Versioning Published gateway doc carries info.version (semver) tracking the gateway endpoint contract, not the operation set; consumers detect breaking changes via the major version
046 Assembly-Layer Custom HTTP Routes on HttpAdapter extra_routes: Option<Router> at construction; deployments add raw HTTP endpoints (e.g., OAI-compatible proxy, or a REST-like per-operation projection) that coexist with the default surface; default surface takes precedence on collision
047 Remove the Direct-Call HTTP Surface; Gateway Is the Sole Invoke Path POST /{service}/{op} direct-call surface removed; the 5 gateway endpoints are the sole invoke path; per-caller AccessControl-filtered /search is the discovery; ADR-036's non-routing clauses survive
048 WebSocket Carries the Native Call-Protocol Session, Not the Gateway Shape WS is the native EventEnvelope session; the gateway endpoints (/search//schema//call//batch//subscribe) are HTTP-only and do not appear on WS; discovery via services/list/services/schema as call-protocol ops

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 (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
OQ-26 OperationAdapter error type resolved AdapterError variants reused by HTTP adapters
OQ-37 X.509 outgoing-only / three peer roles resolved Browsers are not peers; hub with mixed fingerprints
OQ-38 WebTransport standalone relay service scope open (scope, not deferral) The standalone relay (future alknet-relay, fork of iroh-relay) — distinct from the in-process ALPN-stream-proxy (ADR-040)
OQ-39 to_openapi published-spec versioning resolved info.version semver tracks the gateway endpoint contract (ADR-045); per-caller operation set discovered via /search, not in the doc
OQ-40 reqwest client config and connection pooling resolved ClientWithMiddleware + RetryTransientMiddleware + inlined RetryAfterMiddleware; rebuild-and-swap hot-reload

Key Design Principles

  1. HTTP is both a server surface and a client transport for adapters. 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.
  2. The HTTP surface is the 5-endpoint gateway — a few fixed endpoints, not a per-operation REST tree. An HTTP client invokes an operation via POST /call with { "operation": "/fs/readFile", "input": {...} }, discovers what it can call via AccessControl-filtered GET /search, and learns an operation's shape via GET /schema. There is no per-operation POST /{service}/{op} direct-call surface (removed by ADR-047; the per-caller API surface is the default — the "dump the full API regardless of privs" failure mode is structurally impossible). to_openapi describes this gateway surface (5 fixed endpoints; per-caller operations discovered via /search, not preloaded into the doc). A deployment that wants a REST-like per-operation HTTP surface builds it as a custom route projection (ADR-046). See ADR-042 (gateway pattern), ADR-047 (direct-call surface removed; ADR-036's routing superseded, non-routing clauses survive), and ADR-046 (custom routes extension point).
  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).
  4. from_openapi/from_mcp are the no-env-vars injection point. The forwarding handlers read context.capabilities, not std::env::var. This is the architectural mechanism that makes aisdk's env-var reads unreachable. See ADR-014, client-and-adapters.md.
  5. MCP streamable HTTP only; stdio is not built. stdio = spawn arbitrary executable = RCE. Streamable HTTP is network-isolated, auth-gatable, and runs under alknet's auth model. See ADR-037.
  6. WebSocket is the browser bidirectional path, and it carries the native call-protocol session, not the gateway shape. 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). The to_openapi gateway endpoints (/search//schema//call//batch/ /subscribe, ADR-042/047) are the HTTP one-directional projection and do not appear on the WebSocket path — WS is the call protocol's own native session, with discovery via services/list/services/schema as ordinary call-protocol ops (ADR-048). HTTP/3 + WebTransport (h3) is deferred per ADR-044 — 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). See websocket.md for the full spec, including the deferred from_wss adapter (out of scope — a future from_call-aligned importer over WS, not needed for the v1 browser-client case).
  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 §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 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, /workspace/@alkdev/operations/src/from_mcp.ts — TypeScript prior art
  • /workspace/rust-sdk/ — MCP Rust SDK (rmcp v1.8.0); streamable HTTP transport examples
  • /workspace/wtransport/ — pure-Rust WebTransport reference 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.)