Phase 1 (SDD) — architecture documentation: Ported specs (adapted for alkcall, producer/consumer terms, 6-endpoint gateway, channels-over-WS, Sub/Pub operation types): - overview.md, http-server.md, http-adapters.md, http-mcp.md - README.md index (rewritten for alkhttp) New ADRs: - 067: WebSocket carries the channels protocol (8-byte chunk demux, channel 0 = alk/call, upgrade path /alk/channels) - 068: gateway /publish endpoint for Pub operations (NDJSON body) - 069: WebTransport out of scope in alkhttp (alknet concern) - 070: from_wss consumer adapter (wss feature, tokio-tungstenite) Ported ADRs (25, same numbers, port notes + amendments where the extraction changed facts): 001-004, 010, 014, 015, 017, 022, 023, 027, 034, 036, 037, 039, 041, 042, 044, 045, 046, 047, 048, 049, 051, 066. websocket.md rewritten for the channels session; open-questions.md seeded (OQ-01 WS byte-stream adapter, OQ-02 /publish framing, OQ-03 from_wss reconnect, OQ-04 browser client ownership). Verified: cargo test, clippy -D warnings, fmt, doc --no-deps.
160 lines
13 KiB
Markdown
160 lines
13 KiB
Markdown
---
|
|
status: draft
|
|
last_updated: 2026-08-27
|
|
---
|
|
|
|
# alkhttp
|
|
|
|
HTTP interface for the alk stack: serves HTTP/1.1 and HTTP/2 on standard
|
|
ALPNs (with WebSocket upgrade carrying the channels protocol for browser
|
|
bidirectional access to the call protocol), and hosts the HTTP-backed
|
|
call-protocol adapters (`from_openapi`, `from_jsonschema`, `from_mcp`,
|
|
`to_openapi`, `to_mcp`, `from_wss`). HTTP/3 + WebTransport (`h3`) is
|
|
**out of scope** per [ADR-069](decisions/069-webtransport-out-of-scope.md) —
|
|
it is an alknet-side concern, not an alkhttp one.
|
|
|
|
alkhttp is the extraction of `alknet-http` from the alknet mono-repo,
|
|
re-implemented on the published crates: `alkvault` (secrets) and
|
|
`alkcall` 0.1.1 (call protocol + channels protocol + vendored core
|
|
types — the former `alknet-core` and `alknet-call` merged).
|
|
|
|
## Documents
|
|
|
|
| Document | Status | Description |
|
|
|----------|--------|-------------|
|
|
| [overview.md](overview.md) | draft | Crate purpose, two roles (server + client host), dependency edges, adapter location map |
|
|
| [http-server.md](http-server.md) | draft | `HttpAdapter` (`ProtocolHandler` for `h2`/`http/1.1` + WS upgrade route), axum over a `BiStream`, Bearer auth, stealth, `/healthz`; WS hands off to the channels session spec |
|
|
| [websocket.md](websocket.md) | draft | WebSocket browser bidirectional path — the WS connection carries the **channels protocol** (8-byte chunk multiplexing, channel 0 pre-negotiated as `alk/call`); framing via the WS↔byte-stream adapter, dispatch, bidirectionality, connection-local Layer 2 overlay, browsers-are-not-peers rationale, streaming (native, no SSE), `from_wss` consumer adapter |
|
|
| [http-adapters.md](http-adapters.md) | draft | `from_openapi` (reqwest client; JSON + YAML input per ADR-051), `from_jsonschema` (single-endpoint reqwest forwarding handler per ADR-066), `to_openapi` (OpenAPI projection of the 6-endpoint gateway), `from_wss` (WSS consumer adapter per ADR-070); no-env-vars invariant point |
|
|
| [http-mcp.md](http-mcp.md) | draft | `from_mcp` / `to_mcp` (feature-gated), streamable-HTTP-only, stdio exclusion |
|
|
|
|
## Applicable ADRs
|
|
|
|
### Ported from alknet (same numbers)
|
|
|
|
| ADR | Title | Relevance |
|
|
|-----|-------|-----------|
|
|
| [001](decisions/001-alpn-protocol-dispatch.md) | ALPN-Based Protocol Dispatch | `HttpAdapter` registers on standard HTTP ALPNs |
|
|
| [002](decisions/002-protocol-handler-trait.md) | ProtocolHandler Trait | `HttpAdapter` implements `ProtocolHandler` |
|
|
| [003](decisions/003-crate-decomposition.md) | Crate Decomposition | alkhttp depends on alkcall alone (protocol-foundation exception, Amendment 1) |
|
|
| [004](decisions/004-auth-as-shared-core.md) | Auth as Shared Core | Bearer → `resolve_from_token` |
|
|
| [010](decisions/010-alpn-router-and-endpoint.md) | ALPN Router and Endpoint | Stealth mode = HTTP handler on standard ALPNs |
|
|
| [014](decisions/014-secret-material-flow-and-capability-injection.md) | Secret Material Flow | `from_openapi`/`from_mcp` are the credential injection point |
|
|
| [015](decisions/015-privilege-model-and-authority-context.md) | Privilege Model | Adapter-registered ops are `Internal` by default |
|
|
| [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`/`from_jsonschema` produce leaf bundles (`FromJsonSchema` handler-bearing per ADR-066) |
|
|
| [023](decisions/023-operation-error-schemas.md) | Operation Error Schemas | `from_openapi`/`from_jsonschema`/`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; applies to any browser-facing TLS (TLS provisioning itself is an alknet concern) |
|
|
| [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) |
|
|
| [036](decisions/036-http-to-call-operation-mapping.md) | HTTP-to-Call Operation Mapping | ~~Direct path mapping~~ — **routing superseded by ADR-047**; non-routing clauses survive |
|
|
| [037](decisions/037-mcp-stdio-transport-exclusion.md) | MCP Stdio Transport Exclusion | Streamable HTTP only; stdio not built |
|
|
| [039](decisions/039-http-server-and-client-host-colocated.md) | HTTP Server and Client Host Colocated | One crate for server + client host (shared HTTP deps, shared mapping) |
|
|
| [041](decisions/041-mcp-tool-gateway-pattern.md) | MCP Tool-Gateway Pattern for to_mcp | 4 fixed gateway tools (search/schema/call/batch); Sub AND Pub excluded |
|
|
| [042](decisions/042-openapi-gateway-pattern.md) | OpenAPI Gateway Pattern for to_openapi | Fixed gateway endpoints, not one path per operation; per-caller AccessControl-filtered |
|
|
| [044](decisions/044-defer-webtransport-browsers-use-websocket.md) | Defer h3/WebTransport; Browsers Use WebSocket | WS as browser path stands; deferral mechanics superseded by ADR-069 (removal) |
|
|
| [045](decisions/045-to-openapi-gateway-spec-versioning.md) | to_openapi Gateway-Spec Versioning | `info.version` (semver) tracks the gateway endpoint contract |
|
|
| [046](decisions/046-assembly-layer-custom-http-routes.md) | Assembly-Layer Custom HTTP Routes | `extra_routes: Option<Router>` at construction |
|
|
| [047](decisions/047-remove-direct-call-http-surface.md) | Remove the Direct-Call HTTP Surface | The 6 gateway endpoints are the sole invoke path |
|
|
| [048](decisions/048-websocket-native-session-not-gateway.md) | WebSocket Carries the Native Session, Not the Gateway Shape | Amended by ADR-067: WS carries the channels session; channel 0 carries the native call session |
|
|
| [049](decisions/049-streaming-handler-for-subscriptions.md) | Streaming Handler for Subscription Operations | `HandlerKind::Stream` for Sub; `HandlerKind::Sink` for Pub (alkcall ADR-046) |
|
|
| [051](decisions/051-yaml-input-for-from-openapi.md) | YAML Input Format for from_openapi | `from_json`/`from_yaml`/`from_str`; JSON-first detection; `yaml_serde` |
|
|
| [066](decisions/066-from-jsonschema-as-http-adapter.md) | `from_jsonschema` as HTTP-Backed Single-Endpoint Adapter | Real reqwest-backed single-endpoint adapter; provenance stays in alkcall |
|
|
|
|
### New in alkhttp
|
|
|
|
| ADR | Title | Summary |
|
|
|-----|-------|---------|
|
|
| [067](decisions/067-websocket-carries-channels.md) | WebSocket Carries the Channels Protocol | WS = in-line channels substrate: 8-byte chunk demux, channel 0 pre-negotiated as `alk/call`, upgrade path `/alk/channels`; the shared `Dispatcher` runs on channel 0 |
|
|
| [068](decisions/068-gateway-publish-endpoint.md) | Gateway `/publish` Endpoint | 6th gateway endpoint for `OperationType::Pub` (producer→consumer streaming); NDJSON request body → `call.published` chunks |
|
|
| [069](decisions/069-webtransport-out-of-scope.md) | WebTransport Out of Scope | h3/WebTransport removed from alkhttp scope entirely (an alknet concern); supersedes the deferral framing of ADR-044 |
|
|
| [070](decisions/070-from-wss-consumer-adapter.md) | `from_wss` Consumer Adapter | Import a remote node's operations over WSS — same-protocol importer, channels-over-WS as transport; `wss` feature gate |
|
|
|
|
## Relevant Open Questions
|
|
|
|
Open questions are tracked in [open-questions.md](open-questions.md).
|
|
Key ones:
|
|
|
|
| OQ | Title | Status | Relevance |
|
|
|----|-------|--------|-----------|
|
|
| OQ-01 | WS message ↔ byte-stream adaptation | open | The channels demux reads bytes (`read_exact`); axum WS is message-oriented. The WS↔byte-stream adapter's buffering/flush semantics are the core implementation risk for both the server path and `from_wss` |
|
|
| OQ-02 | `/publish` body framing details | open | NDJSON line = one published chunk; error envelope position (final JSON body vs HTTP trailer) needs a concrete decision before the gateway-spec version bumps |
|
|
| OQ-03 | `from_wss` reconnection semantics | open | Does a dropped WSS connection re-discover (`services/list`) and re-register, or hold stale registrations? |
|
|
|
|
## 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 `BiStream`; outbound HTTP (`from_openapi`/`from_mcp`
|
|
forwarding) uses `reqwest`. Both directions share the same HTTP
|
|
dependencies, which is why they live in one crate. See
|
|
[ADR-039](decisions/039-http-server-and-client-host-colocated.md).
|
|
2. **The HTTP surface is a fixed-endpoint gateway — 6 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`,
|
|
learns an operation's shape via `GET /schema`, streams a `Sub`
|
|
operation via `POST /subscribe` (SSE), and feeds a `Pub` operation via
|
|
`POST /publish` (NDJSON body). There is no per-operation
|
|
`POST /{service}/{op}` direct-call surface (removed by ADR-047; the
|
|
per-caller API surface is the default). `to_openapi` *describes* this
|
|
gateway surface. A deployment that wants a REST-like per-operation
|
|
surface builds it as a custom route projection (ADR-046). See
|
|
[ADR-042](decisions/042-openapi-gateway-pattern.md) and
|
|
[ADR-047](decisions/047-remove-direct-call-http-surface.md).
|
|
3. **Standard ALPNs, not alk ALPNs.** `h2`, `http/1.1` are
|
|
IANA-registered ALPN strings. Any HTTP client (browser, curl, axios)
|
|
connects without knowing about the alk stack — the TLS handshake
|
|
negotiates `h2` or `http/1.1` normally. This is the stealth mapping
|
|
([ADR-010](decisions/010-alpn-router-and-endpoint.md)).
|
|
4. **`from_openapi`/`from_mcp`/`from_jsonschema` are the no-env-vars
|
|
injection point.** The forwarding handlers read
|
|
`context.capabilities`, not `std::env::var`. See
|
|
[ADR-014](decisions/014-secret-material-flow-and-capability-injection.md).
|
|
5. **MCP streamable HTTP only; stdio is not built.** stdio = spawn
|
|
arbitrary executable = RCE. See
|
|
[ADR-037](decisions/037-mcp-stdio-transport-exclusion.md).
|
|
6. **WebSocket is the browser bidirectional path, and it carries the
|
|
channels protocol.** A browser upgrades an HTTP/1.1 or HTTP/2 request
|
|
to WebSocket and speaks the channels protocol over binary WS messages:
|
|
the 8-byte chunk header multiplexes N channels; channel 0 is
|
|
pre-negotiated as `alk/call` and carries the native call-protocol
|
|
session ([ADR-067](decisions/067-websocket-carries-channels.md)).
|
|
Both sides can initiate calls on channel 0; the browser may open
|
|
further channels via the per-ALPN open ops, exactly as a Rust peer
|
|
would over an in-line transport. The 6 gateway endpoints are the HTTP
|
|
one-directional projection and **do not appear on the WS path** —
|
|
discovery is via `services/list`/`services/schema` as call-protocol
|
|
ops ([ADR-048](decisions/048-websocket-native-session-not-gateway.md)).
|
|
This supersedes the alknet design where WS carried bare
|
|
`EventEnvelope` messages (one envelope per WS message).
|
|
7. **Browsers are not alk peers.** A browser over WebSocket
|
|
authenticates by bearer token, gets no `PeerId`, and its registered
|
|
ops land in a connection-local Layer 2 overlay. See
|
|
[ADR-034](decisions/034-outgoing-only-x509-and-three-peer-roles.md) §4.
|
|
8. **Producer/consumer, not server/client.** Both sides of a call or
|
|
channels connection can initiate. A producer exposes operations
|
|
(call) or opens data channels; a consumer calls operations or opens
|
|
channels; both sides can be both simultaneously. Connection direction
|
|
(who opened it) is independent of call/channel direction (who
|
|
calls/opens). The `from_*`/`to_*` adapter names remain directional
|
|
because OpenAPI and MCP are client/server protocols — that
|
|
directionality is a property of those protocols, not of the call
|
|
protocol.
|
|
|
|
## References
|
|
|
|
- The alkcall crate (`/workspace/@alkdev/alkcall`,
|
|
crates.io `alkcall 0.1.1`) — call protocol, channels protocol,
|
|
vendored core types. Its `docs/architecture/` owns the call-protocol
|
|
and channels decisions cited throughout: wire format (ADR-014), stream
|
|
model (ADR-015), registry layering (ADR-019), abort cascade (ADR-020),
|
|
streaming handler (ADR-021), adapter contract (ADR-022), channels wire
|
|
format (ADR-034/035), channel 0 pre-negotiation (ADR-036),
|
|
ChannelClient (ADR-043), Pub/Sink (ADR-046), openable ALPNs as
|
|
operations (ADR-047).
|
|
- The alkvault crate (`/workspace/@alkdev/alkvault`, crates.io) —
|
|
secrets; feeds `Capabilities` at the assembly layer (no direct
|
|
dependency from alkhttp).
|
|
- The alknet mono-repo (`/workspace/@alkdev/alknet`) — the source of
|
|
this extraction; retains the endpoint/transport/peer-graph concerns
|
|
(dial, TLS, QUIC, WebTransport) and the ADRs for them. |