Files
alkhttp/docs/architecture/overview.md
T
glm-5.3-flash 4a825d33e7 feat(infra): full-surface integration suite + docs sync + publish prep
Full-surface integration suite (tests/full_surface.rs, mcp feature):
- one HttpAdapter over real TCP (ProtocolHandler::handle path) serving
  gateway endpoints, /openapi.json, /mcp, and the WS channels session
- gateway: search/schema/call/subscribe/batch/publish presence,
  envelope shapes, error fidelity end-to-end
- from_openapi import -> Internal-by-default invisible from the wire ->
  External facade composes it via env.invoke -> upstream HTTP API
  called end-to-end (ADR-015 composition model exercised)
- to_openapi 6-path doc validated against openapiv3 over the wire
- to_mcp: MCP client connects to /mcp on the served adapter, lists the
  4 gateway tools, search returns ACL-filtered ops (Sub excluded)

Production fix: the WS upgrade route was reserved but never wired into
HttpAdapter's router (the ws-upgrade-session tests built their own
router). Now wired with ws_bearer_auth (401 without a resolvable
token) around ws_upgrade_handler.

Docs sync: all 28 'Port notes' sections/blockquotes stripped from
ported ADRs/specs; OQ-01/OQ-02 statuses corrected to resolved in
overview.md, websocket.md, and the README table (open-questions.md was
already current).

Publish prep: cargo publish --dry-run --allow-dirty succeeds;
cargo doc --no-deps warning-free (ADR link targets fixed); feature
combinations (default / test-support / mcp / wss / all) compile
warning-free under clippy -D warnings.

Verified: cargo test (182 lib default), --all-features (227 lib + 29
integration), clippy -D warnings x3 feature sets, fmt, doc,
publish --dry-run.
2026-08-28 16:07:56 +00:00

18 KiB

status, last_updated
status last_updated
draft 2026-08-27

alkhttp — Overview

The HTTP interface crate: serves inbound HTTP on standard ALPNs (with WebSocket upgrade carrying the channels protocol 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

alkhttp is the HTTP protocol handler crate for the alk stack. It serves two roles in one crate (ADR-039):

  1. HTTP server — a ProtocolHandler (HttpAdapter) that accepts 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. The WS path carries the channels protocol (ADR-067): the 8-byte chunk header multiplexes N channels over the WS connection; channel 0 is pre-negotiated as alk/call and runs the shared Dispatcher.
  2. HTTP client host — the home of the HTTP-transport-backed call adapters: from_openapi (import external HTTP APIs described by an OpenAPI document, using reqwest for outbound calls), from_mcp (import remote MCP tools over streamable HTTP, using reqwest), from_jsonschema (import a single non-OpenAPI HTTP endpoint), and from_wss (import a remote alk node's operations over a WSS channels connection — same-protocol importer). The reverse projections to_openapi (generate an OpenAPI doc from the local registry's External operations) and to_mcp (expose local ops as MCP tools over streamable HTTP, using axum) also live here.

Both directions share the same HTTP dependencies (axum for serving, reqwest for calling out), which is why they live in one crate rather than being split into a server crate and a client crate.

Why

The crate's purpose is to be the HTTP interface library for downstream crates that need to expose an HTTP interface. A downstream consumer (a hub deployment, a browser-facing service, the alknet assembly layer) wires HttpAdapter into the handler registry for the standard HTTP ALPNs and gets a full HTTP surface: the gateway projection of the call protocol, OpenAPI discovery, MCP tool exposure, and WebSocket for browser bidirectional access to both the call protocol and data channels.

The producer/consumer model

The call protocol is bidirectional — both sides can initiate calls — and the channels protocol is symmetric — either side can open channels. Producer and consumer therefore describe what a side does on a given operation or channel, not what it is as a connection endpoint:

  • A producer (call) registers operations on an OperationRegistry and runs a Dispatcher; a consumer (call) uses a CallConnection to invoke them. On one connection both sides can be producers of some ops and consumers of others.
  • A producer (channels) accepts data channels (registers openable ALPNs); a consumer (channels) opens them via the per-ALPN open ops on channel 0. Both sides can open and accept on the same connection.

The HTTP surface maps onto this model as follows:

  • The HttpAdapter (gateway + WS upgrade) is the producer-facing surface: it runs the shared Dispatcher on channel 0 for WS sessions, and the gateway dispatches into the local registry for HTTP requests. A browser or HTTP client is a consumer of those ops.
  • The WS session is fully bidirectional: the browser side can also be a producer (registering ops via the connection-local Layer 2 overlay, opening channels), making the hub a consumer of browser ops on the same connection.
  • The from_* adapters are consumer-side: they import external surfaces (OpenAPI, MCP, WSS) into the local registry as ops the local process can then consume or compose.
  • The to_* adapters are producer-side projections: they expose the local registry's ops through foreign protocols (OpenAPI, MCP).

Connection direction (who opened the HTTP/TCP connection) is independent of these roles, exactly as in the call protocol.

A note on the "from/to" direction model: the from_openapi/to_openapi and from_mcp/to_mcp adapters are inherently directional because OpenAPI and MCP are client/server protocols — one side serves, the other calls. That directionality is a property of those protocols, not of the call protocol itself. The call protocol is bidirectional. The HTTP/1.1 + HTTP/2 surface inherits HTTP's request/response constraint and projects the call protocol one-directionally (consumer→producer calls only). WebSocket restores the call protocol's native bidirectionality for browsers — and now carries the channels protocol besides it.

Dependencies

alkhttp
├── alkcall           (call + channels protocol, vendored core types,
│                      registry, adapter contract — the sole alk dep)
├── axum              (HTTP server — Router, extractors, middleware, WebSocket upgrade)
├── hyper / hyper-util(hTTP/1.1 + HTTP/2 framing; axum is built on hyper)
├── reqwest           (HTTP client — from_openapi/from_mcp forwarding)
│   + reqwest-middleware, reqwest-retry
├── tokio-tungstenite (WSS client — from_wss; behind the `wss` feature)
├── yaml_serde        (YAML parse for from_openapi YAML input — ADR-051)
├── openapiv3         (OpenAPI doc model for from_openapi/to_openapi)
└── rmcp              (MCP streamable HTTP — feature-gated behind `mcp`)

Note: the h3/WebTransport dependency is not in the dependency tree and is not planned — WebTransport is out of scope in alkhttp (ADR-069); when a browser needs WebTransport it will be provided by the alknet layer, which owns transports. The browser bidirectional path is WebSocket (native axum support, no new dependency).

The alkcall dependency (ADR-003 Amendment 1)

alkhttp depends on alkcall. The old rule "no handler crate depends on another handler crate" survives as "no HTTP adapter depends on another transport adapter": alkcall is the protocol-foundation crate (it owns the vendored core types — Connection, ProtocolHandler, BiStream, AuthContext, IdentityProvider, Capabilities, OwnershipProvider, HandlerError, StreamError — plus the registry, dispatch, and wire format). alkhttp depending on alkcall is "HTTP uses the call protocol types," not "HTTP depends on another transport."

alkcall stays transport-agnostic — no reqwest, no axum, no HTTP dependencies. The from_openapi/from_mcp/from_jsonschema/from_wss forwarding handlers are opaque Arc<dyn Handler>-shaped registrations from the registry's perspective: constructed by alkhttp at registration time, stored in HandlerRegistration, dispatched by the Dispatcher which doesn't know reqwest or tokio-tungstenite is involved.

ALPNs

ALPN Handler Transport Browser?
http/1.1 HttpAdapter HTTP/1.1 over a BiStream (+ WS upgrade) Yes (WS upgrade for bidirectional)
h2 HttpAdapter HTTP/2 over a BiStream (+ WS upgrade) Yes (WS upgrade for bidirectional)
h3 Out of scope (ADR-069); an alknet concern

These are standard IANA ALPN strings, not alk/-prefixed. Any HTTP client connects without knowing about the alk stack — the TLS handshake negotiates h2 or http/1.1 normally, and the HttpAdapter serves HTTP. This is the stealth mapping (ADR-010).

The HttpAdapter registers for http/1.1 and h2. The endpoint's handler registry maps each ALPN to the same adapter instance; the handler branches on connection.remote_alpn() to pick the framing. WebSocket upgrade rides on either HTTP version (RFC 6455 over HTTP/1.1; extended CONNECT over HTTP/2).

Adapter Location Map

The decomposition principle: the adapter trait lives where the types live (alkcall); the adapter implementations live where their transport dependencies live (alkhttp).

alkcall (transport-agnostic — no HTTP client, no HTTP server)
├── OperationAdapter trait       (the contract — async)
├── from_call                    (same-protocol importer over a call connection)
├── CallConnection / CallClient  (outbound call surface)
└── ChannelsAdapter / ChannelClient (channels protocol, both sides)

alkhttp (owns HTTP server + HTTP client + WSS client)
├── HttpAdapter                  (axum server — inbound HTTP on h2/http1.1 + WS upgrade route)
├── [WS upgrade → channels session] (not an adapter — hands the WS byte stream to the
│                                channels machinery; see websocket.md, ADR-067)
├── from_openapi                 (parse OpenAPI doc + reqwest forwarding handler)
├── from_jsonschema              (single-endpoint reqwest forwarding handler — ADR-066)
├── to_openapi                   (generate OpenAPI doc from local registry — the gateway projection)
├── from_mcp   (feature-gated)   (import remote MCP tools over streamable HTTP — reqwest)
├── to_mcp     (feature-gated)   (expose local ops as MCP tools over streamable HTTP — axum)
└── from_wss   (feature-gated)   (import a remote node's ops over WSS — ADR-070)

alkcall never sees the HTTP client. The forwarding handlers are opaque from the registry's perspective. alkcall stays lean; alkhttp owns all HTTP directions and the WSS consumer.

Feature Gates

[features]
default = ["h2", "http1"]   # the HTTP surface (incl. WebSocket upgrade for browsers)
mcp = ["dep:rmcp"]           # from_mcp / to_mcp (streamable HTTP only — ADR-037)
wss = ["dep:tokio-tungstenite"]  # from_wss consumer adapter (ADR-070)
  • h2 + http1 (default): the axum + hyper HTTP/1.1 + HTTP/2 server, including WebSocket upgrade for browser bidirectional access. This is the surface all clients — including browsers, via WS upgrade — use.
  • mcp: the rmcp dependency with streamable HTTP transport features only. Adds from_mcp/to_mcp. See http-mcp.md and ADR-037.
  • wss: the tokio-tungstenite dependency. Adds from_wss. See ADR-070.

yaml_serde is not feature-gated — YAML OpenAPI is a first-class input format (some providers publish YAML-only schemas), not an edge case (ADR-051 §3).

The No-Env-Vars Invariant

The from_openapi/from_mcp/from_jsonschema/from_wss forwarding handlers are the credential injection point for the no-env-vars architecture. The path:

vault (alkvault) → assembly layer → Capabilities
  → HandlerRegistration.capabilities → OperationContext.capabilities
  → handler reads context.capabilities.get("<service>")
  → injects into outbound HTTP Authorization header (or WSS auth)

This makes any downstream std::env::var("OPENAI_API_KEY") read unreachable — the assembly layer never calls Default::default() on a provider; it constructs them with vault-derived credentials, or routes outbound calls through adapter operations that carry the credential in Capabilities.

This is a spec-level invariant: no handler reads outbound credentials from any source other than OperationContext.capabilities. All adapter implementations in alkhttp are verified against this invariant. See ADR-014.

Architecture (component pointers)

  • http-server.md — the HttpAdapter for h2/ http/1.1 (+ the WS upgrade route): how axum is run over a BiStream, Bearer auth resolution, the /healthz raw route, stealth decoy, the 6-endpoint gateway dispatch, and the WS upgrade route (which hands off to the channels session).
  • websocket.md — the WebSocket browser bidirectional path: the channels protocol over WS, framing via the WS↔byte-stream adapter, channel 0 and the shared Dispatcher, bidirectionality, connection-local Layer 2 overlay, the browsers-are-not-peers rationale, streaming (native, no SSE).
  • http-adapters.mdfrom_openapi, from_jsonschema, to_openapi, and from_wss. Error fidelity per ADR-023.
  • http-mcp.mdfrom_mcp/to_mcp (feature-gated), streamable HTTP only (ADR-037), the rmcp integration.

Design Decisions

Decision ADR Summary
HTTP-to-call operation mapping ADR-036 Direct path mappingrouting superseded by ADR-047; non-routing clauses survive (SSE, auth, /healthz, stealth, error mapping)
MCP stdio transport exclusion ADR-037 Streamable HTTP only; stdio is not built (RCE vector)
WebSocket carries the channels protocol ADR-067 WS = in-line channels substrate; channel 0 = alk/call; upgrade path /alk/channels; new in alkhttp
Gateway /publish endpoint ADR-068 6th gateway endpoint for Pub ops; NDJSON request body → call.published chunks; new in alkhttp
WebTransport out of scope ADR-069 h3/WebTransport removed from alkhttp scope; an alknet concern; supersedes ADR-044's deferral framing
from_wss consumer adapter ADR-070 Same-protocol importer over WSS; wss feature; new in alkhttp
WS carries the native session, not the gateway shape ADR-048 Amended by ADR-067: channel 0 carries the native call session; gateway endpoints are HTTP-only
HTTP server + client host colocated ADR-039 One crate for server + adapters (shared HTTP deps, shared mapping)
to_mcp tool-gateway pattern ADR-041 4 fixed gateway tools (search/schema/call/batch); Sub and Pub excluded
to_openapi gateway pattern ADR-042, ADR-047 Fixed gateway endpoints are the sole HTTP invoke path (no per-operation POST /{service}/{op}); per-caller AccessControl-filtered /search is the discovery; extended with /publish (ADR-068)
Assembly-layer custom HTTP routes ADR-046 extra_routes: Option<Router> at construction; default surface takes precedence on collision
alkhttp is protocol-foundation-dependent ADR-003 Am. 1 alkhttp depends on alkcall alone (types, not a peer transport)
Bearer auth via resolve_from_token ADR-004 HTTP handler credential source + resolution (settled; alkcall ADR-003 owns the mechanism)
Stealth mode = HTTP handler on standard ALPNs ADR-010 Decoy for unknown paths (settled)
Adapter-registered ops are Internal ADR-015 from_openapi/from_mcp/from_jsonschema/from_wss produce Internal leaves (record: alkcall ADR-017)
OperationAdapter trait is async ADR-017 HTTP adapters implement the async trait (record: alkcall ADR-022)
to_* adapters are projections ADR-017 to_openapi/to_mcp consume the registry, don't produce entries (record: alkcall ADR-022)
Error schema fidelity ADR-023 from_openapi maps HTTP status → HTTP_<status> codes; to_openapi projects back (record: alkcall ADR-016)
Browsers require X.509 ADR-027 Browser-facing TLS uses X.509 (TLS provisioning itself is an alknet concern)
Browsers are not alk peers ADR-034 §4 Browser over WS = bearer token, no PeerId

Open Questions

See open-questions.md for full details.

  • OQ-01 (resolved): WS message ↔ byte-stream adaptation — the production adapter (src/websocket/byte_adapter.rs) is validated in both directions (server upgrade path + from_wss client).
  • OQ-02 (resolved): /publish body framing — first line {operation, chunk}; terminal errors as plain HTTP status + JSON body (ADR-068).
  • OQ-03 (open): from_wss reconnection semantics — v1 = drop → retryable failures (ADR-070); policy deferred to the assembly layer.

References

  • The alkcall crate — docs/architecture/ for the call and channels protocol decisions; README.md §"Roles and Composition" for the producer/consumer role definitions.
  • The alkvault crate — secrets, feeding Capabilities at the assembly layer.
  • The alknet mono-repo — endpoint/transport concerns and the source specs this crate was ported from.