Bump the alkcall dependency to 0.2 (with the gateway feature) and converge on the promoted shared pieces: - The local dispatch spine (gateway/dispatch.rs, 721 lines) is deleted; GatewayDispatch, schema_disclosure_denial, and DEFAULT_DEADLINE are re-exported from alkcall::gateway (alkcall ADR-048). The 30 s default deadline preserves the previous behavior exactly. - gateway/schema_cache.rs (PublishSchemaCache) is deleted: alkcall CF-003 compiles publish_schema at registration time and exposes OperationRegistry::publish_validator; the /publish chunk stream resolves against it. Un-compilable schemas are now rejected at registration, so the two end-to-end fail-closed tests were reworked into a registration-rejection test (a stronger guarantee). - schema_disclosure_denial consumers (to_mcp, routes) use alkcall's promoted implementation; the alkhttp-local copy is gone (ADR-071 updated: the guard stays as defense-in-depth, the implementation no longer forks). - CF-001: from_wss drop monitor and the WS overlay tests use CallError::connection_closed; the review-001-ws-eof-signal race tests now assert retryable CONNECTION_CLOSED on both resolution paths (the tolerated non-retryable INTERNAL write-failure outcome is gone). - Added CHANGELOG.md (Keep a Changelog), Unreleased section records the bump and convergence. Verification: cargo test default 453 ok, wss 470 ok, mcp 526 ok, all-features 575 ok; clippy -D warnings clean (default + all-features, all-targets); fmt clean; cargo doc warning-free. Net: -1093 lines.
13 KiB
13 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-08-28 |
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 —
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 | draft | Crate purpose, two roles (server + client host), dependency edges, adapter location map |
| 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 | 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 | 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 | draft | from_mcp / to_mcp (feature-gated), streamable-HTTP-only, stdio exclusion |
Applicable ADRs
Ported from alknet (same numbers)
| ADR | Title | Relevance |
|---|---|---|
| 001 | ALPN-Based Protocol Dispatch | HttpAdapter registers on standard HTTP ALPNs |
| 002 | ProtocolHandler Trait | HttpAdapter implements ProtocolHandler |
| 003 | Crate Decomposition | alkhttp depends on alkcall alone (protocol-foundation exception, Amendment 1) |
| 004 | Auth as Shared Core | Bearer → resolve_from_token |
| 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/from_jsonschema produce leaf bundles (FromJsonSchema handler-bearing per ADR-066) |
| 023 | Operation Error Schemas | from_openapi/from_jsonschema/to_openapi error fidelity; HTTP_<status> error codes |
| 027 | TLS Identity Redesign | Browsers require X.509; applies to any browser-facing TLS (TLS provisioning itself is an alknet concern) |
| 034 | Outgoing-Only X.509 and Three Peer Roles | Browsers are not alknet peers (§4) |
| 036 | HTTP-to-Call Operation Mapping | |
| 037 | MCP Stdio Transport Exclusion | Streamable HTTP only; stdio not built |
| 039 | HTTP Server and Client Host Colocated | One crate for server + client host (shared HTTP deps, shared mapping) |
| 041 | MCP Tool-Gateway Pattern for to_mcp | 4 fixed gateway tools (search/schema/call/batch); Sub AND Pub excluded |
| 042 | OpenAPI Gateway Pattern for to_openapi | Fixed gateway endpoints, not one path per operation; per-caller AccessControl-filtered |
| 044 | Defer h3/WebTransport; Browsers Use WebSocket | WS as browser path stands; deferral mechanics superseded by ADR-069 (removal) |
| 045 | to_openapi Gateway-Spec Versioning | info.version (semver) tracks the gateway endpoint contract |
| 046 | Assembly-Layer Custom HTTP Routes | extra_routes: Option<Router> at construction |
| 047 | Remove the Direct-Call HTTP Surface | The 6 gateway endpoints are the sole invoke path |
| 048 | 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 | Streaming Handler for Subscription Operations | HandlerKind::Stream for Sub; HandlerKind::Sink for Pub (alkcall ADR-046) |
| 051 | YAML Input Format for from_openapi | from_json/from_yaml/from_str; JSON-first detection; yaml_serde |
| 066 | 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 | 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 | Gateway /publish Endpoint |
6th gateway endpoint for OperationType::Pub (producer→consumer streaming); NDJSON request body → call.published chunks |
| 069 | WebTransport Out of Scope | h3/WebTransport removed from alkhttp scope entirely (an alknet concern); supersedes the deferral framing of ADR-044 |
| 070 | from_wss Consumer Adapter |
Import a remote node's operations over WSS — same-protocol importer, channels-over-WS as transport; wss feature gate |
| 071 | Dispatch-Spine services/schema Op-Path Guard |
GatewayDispatch applies the GET /schema visibility+ACL checks to the meta-op's inner name (review-002 PRJ-16); alkcall CF-004 is the complete fix, this stays as defense-in-depth (now via the promoted alkcall::gateway shared check — spine + guard themselves live in alkcall per ADR-048) |
Relevant Open Questions
Open questions are tracked in open-questions.md. Key ones:
| OQ | Title | Status | Relevance |
|---|---|---|---|
| OQ-01 | WS message ↔ byte-stream adaptation | resolved | Validated by the production adapter (src/websocket/byte_adapter.rs) in both directions (server upgrade path + from_wss client) |
| OQ-02 | /publish body framing details |
resolved | First line {operation, chunk}; terminal errors are plain HTTP status + JSON body (ADR-068, src/gateway/routes.rs::publish_handler) |
| OQ-03 | from_wss reconnection semantics |
open | v1 = drop → retryable failures (ADR-070, implemented in src/adapters/from_wss.rs); whether a built-in reconnection policy is ever warranted is deferred to the assembly layer |
Key Design Principles
- HTTP is both a server surface and a client transport for adapters.
Inbound HTTP (
h2/http/1.1+ WebSocket upgrade) is served byaxumover aBiStream; outbound HTTP (from_openapi/from_mcpforwarding) usesreqwest. Both directions share the same HTTP dependencies, which is why they live in one crate. See ADR-039. - The HTTP surface is a fixed-endpoint gateway — 6 endpoints, not a
per-operation REST tree. An HTTP client invokes an operation via
POST /callwith{ "operation": "/fs/readFile", "input": {...} }, discovers what it can call viaAccessControl-filteredGET /search, learns an operation's shape viaGET /schema, streams aSuboperation viaPOST /subscribe(SSE), and feeds aPuboperation viaPOST /publish(NDJSON body). There is no per-operationPOST /{service}/{op}direct-call surface (removed by ADR-047; the per-caller API surface is the default).to_openapidescribes 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 and ADR-047. - Standard ALPNs, not alk ALPNs.
h2,http/1.1are IANA-registered ALPN strings. Any HTTP client (browser, curl, axios) connects without knowing about the alk stack — the TLS handshake negotiatesh2orhttp/1.1normally. This is the stealth mapping (ADR-010). from_openapi/from_mcp/from_jsonschemaare the no-env-vars injection point. The forwarding handlers readcontext.capabilities, notstd::env::var. See ADR-014.- MCP streamable HTTP only; stdio is not built. stdio = spawn arbitrary executable = RCE. See ADR-037.
- 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/calland carries the native call-protocol session (ADR-067). 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 viaservices/list/services/schemaas call-protocol ops (ADR-048). This supersedes the alknet design where WS carried bareEventEnvelopemessages (one envelope per WS message). - 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 §4. - 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.ioalkcall 0.1.1) — call protocol, channels protocol, vendored core types. Itsdocs/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; feedsCapabilitiesat 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.