glm-5.3-flash c89aa65fb2 chore(deps): consume alkcall 0.9.0
- bump alkcall 0.8.1 -> 0.9 (gateway feature unchanged). No code change
  forced: none of the 0.9.0 breaking surfaces are constructed or
  pattern-matched in this crate — no ManagerError match arms (the new
  InvalidAdopt variant changes nothing here), no RelayError matching
  (the removed PubTypedOpen variant is unreachable from this crate),
  no Establishment construction or Debug dependence (the WS ferry
  passes Option<OpenEstablisher> through unchanged, per the 0.8.0
  audit), and the op/register forward-through refactor does not touch
  the from_wss import filter or the adapter-registered leaf bundles
- ride-through fixes: tests/ws_overlay_ops.rs now expects
  register_imported's new Result<(), String> return (8 sites assert
  registration success instead of discarding the value — the bool
  return they consumed was ignored anyway); fuzz/shared follows the
  main crate to alkcall 0.9 so the harness replay resolves one
  alkcall version (the stale 0.8.1 spec pinned two copies against
  alkhttp's requirement and broke the shared-crate build)
- minor bump 0.6.0 -> 0.7.0 per the dep-wave convention (0.3.0 rode
  alkcall 0.6.0, 0.4.0 rode 0.7.0, 0.5.0 rode 0.8.0): this crate's
  public API carries alkcall types through re-exports and the
  ProtocolHandler impl, so a semver-breaking upstream is
  semver-relevant here

Verification: cargo test 441+70 pass (default), 520+70 pass
(all-features), clippy --all-features --all-targets -D warnings clean,
fmt --check clean, doc --no-deps clean, fuzz corpus replay 21/21
2026-10-14 05:19:29 +00:00
2026-10-14 05:19:29 +00:00
2026-10-14 05:19:29 +00:00
2026-10-14 05:19:29 +00:00
2026-10-14 05:19:29 +00:00
2026-09-29 21:02:32 +00:00
2026-09-18 13:48:26 +00:00

alkhttp

HTTP interface for the alk stack: serves HTTP/1.1 + HTTP/2 on standard ALPNs (with WebSocket upgrade carrying the channels protocol for browser bidirectional access) and hosts the HTTP-backed call-protocol adapters.

alkhttp wraps alkcall — the pure call + channels protocol crate — and turns its operation registry into an HTTP surface. It is the HTTP server host (a ProtocolHandler for the IANA h2/http/1.1 ALPNs) and the HTTP client host (the import adapters that call out over reqwest) in one crate.

What's inside

Server side (server feature) — the producer-facing HTTP surface:

  • HttpAdapter — an axum Router driven by hyper's HTTP/1.1 and HTTP/2 connection handling over a BiStream, served on standard ALPNs so any HTTP client connects without knowing about the alk stack.
  • Six fixed gateway endpoints — the sole HTTP invoke path (no per-operation REST tree): GET /search, GET /schema?name=…, POST /call, POST /batch, POST /subscribe (SSE), POST /publish (NDJSON).
  • /healthz, /openapi.json (a to_openapi projection of the local registry), and /mcp (the MCP tool gateway, feature mcp).
  • WebSocket upgrade on /alk/channels carrying the channels protocol: 8-byte chunk multiplexing with channel 0 pre-negotiated as alk/call, so a browser runs the native bidirectional call-protocol session.
  • Bearer auth, a stealth decoy surface for unknown paths, custom-route mounting, and assembly hooks for WS session caps and timeouts.

Client side (client feature) — the consumer-facing import adapters, each producing ordinary HandlerRegistration bundles for an alkcall registry:

  • from_openapi — import an external HTTP API described by an OpenAPI document (JSON or YAML), forwarding calls over the shared reqwest client.
  • from_jsonschema — import one non-OpenAPI HTTP endpoint behind a caller-built OperationSpec.
  • from_mcp — import a remote MCP server's tools as operations over streamable HTTP (feature mcp).
  • from_wss — import a remote alk node's operations over a WSS channels connection (feature wss).
  • SharedHttpClient — the hot-reloadable reqwest stack all from_* forwarding rides: same-host-only redirects, idempotent-only retries with a wall-clock budget, Retry-After support, TLS config, and streaming byte caps.

Both sides (openapi feature, implied) — the shared OpenAPI document model plus the reverse projections to_openapi (generate the gateway's OpenAPI document from the registry) and to_mcp (expose local operations as MCP tools).

Quick start

Serve the gateway over HTTP

HttpAdapter implements alkcall's ProtocolHandler; the endpoint / accept loop is the consumer's concern (dial and TLS live downstream). Wire it for the ALPN you serve and drive it with your connection source:

use std::sync::Arc;

use alkcall::core::auth::IdentityProvider;
use alkcall::core::types::Connection;
use alkcall::registry::registration::OperationRegistry;
use alkhttp::server::{DecoyConfig, HttpAdapter};

let registry = Arc::new(OperationRegistry::new());
let provider: Arc<dyn IdentityProvider> = /* your identity provider */;

let adapter = HttpAdapter::h2(provider, registry)
    .with_decoy(DecoyConfig::NotFound);

// adapter.alpn() == b"h2" — register it in your handler registry for
// that ALPN; each accepted Connection is handled by
// ProtocolHandler::handle(&adapter, connection, &auth).

Once wired, HTTP callers get the fixed gateway surface. Discovery is per-caller, filtered by AccessControl:

GET  /search                      → { "request_id": …, "result": "ok",
                                      "output": { "operations": [ … ] } }
GET  /schema?name=fs/readFile     → the operation's input/output schema
POST /call                        ← { "operation": "fs/readFile",
                                      "input": { … } }
POST /batch                       ← [ { "operation": …, "input": … }, … ]
POST /subscribe                   → SSE stream of call-protocol envelopes
POST /publish                     ← NDJSON body of published chunks

/openapi.json serves the same surface as an OpenAPI document, so standard tooling can drive the gateway without knowing the alk stack.

Import an OpenAPI-described API

use std::collections::HashMap;
use std::sync::Arc;

use alkhttp::adapters::{FromOpenAPI, HttpServiceConfig, OpenAPISpec};
use alkhttp::client::{HttpClientConfig, SharedHttpClient};

let doc = r#"{"openapi":"3.0.0","info":{"title":"Widgets","version":"1"},
    "paths":{"/widgets":{"get":{"operationId":"listWidgets",
    "responses":{"200":{"content":{"application/json":{"schema":{}}}}}}}}}"#;

let spec = OpenAPISpec::from_json(doc)?;
let config = HttpServiceConfig {
    namespace: "widgets".to_string(),
    base_url: "https://widgets.example.com".to_string(),
    auth: None, // or Some(HttpAuthScheme::Bearer) — the credential is
                // injected per-call from Capabilities, never from config
    default_headers: HashMap::new(),
};
let http_client = Arc::new(SharedHttpClient::new(HttpClientConfig::default())?);

let adapter = FromOpenAPI::new(spec, config, http_client);
let registrations = alkcall::client::OperationAdapter::import(&adapter).await?;

let mut registry = alkcall::registry::registration::OperationRegistry::new();
for reg in registrations {
    registry.register(reg)?;
}
// "widgets/listWidgets" is now callable — and composable — through the
// call protocol and the gateway.

Imported operations register Visibility::Internal (they are composition material); an External facade op composed over them via ctx.env.invoke is what the wire sees.

WebSocket for browsers

A browser upgrades GET /alk/channels (Bearer token in the Authorization header) to a WebSocket and speaks the channels protocol in binary messages: an 8-byte chunk header multiplexes N channels; channel 0 is pre-negotiated as alk/call and runs the native call-protocol session. Both sides can initiate calls; the browser can also register operations and open channels — it is a full consumer and producer on its own connection-local overlay.

Feature flags

The crate is feature-sided — build one side, or both:

Feature Default Gates
server yes the axum host, gateway routes, WS upgrade, /healthz, /openapi.json, to_openapi
client yes the shared outbound client host, from_openapi, from_jsonschema
openapi (implied) the shared OpenAPI document model (implied by both sides)
mcp no from_mcp (needs client) and to_mcp (needs server)
wss no the from_wss consumer adapter
h2, http1 yes the server's HTTP protocol features (imply server)

A lean single-side build takes default-features = false plus the side it needs:

alkhttp = { version = "0.5.0", default-features = false, features = ["server"] }

Security posture

  • No secret material on the wire. Request/response payloads and headers carry no keys or tokens beyond the caller's own Bearer credential. Outbound credentials flow vault → assembly layer → Capabilities → handler; the from_* adapters are the injection point, and no handler reads std::env::var.
  • Stealth mode. The HTTP surface serves on standard ALPNs, and unregistered paths answer with a configurable decoy (fake nginx 404, static site, or redirect) instead of advertising the gateway.
  • Tight forwarding defaults. Same-host-only redirects, idempotent-only retries with a wall-clock budget and Retry-After ceiling, 30 s request / 10 s connect / 30 s read timeouts, a 2 MiB gateway body cap (8 MiB on /mcp), and a 1 GiB streamed-bytes cap per subscription.
  • Internal-by-default imports. Adapter-registered operations are invisible to direct wire calls (NOT_FOUND) until composed behind an External facade.

Documentation

License

MIT OR Apache-2.0 — see LICENSE-MIT and LICENSE-APACHE.

S
Description
No description provided
Readme
2.1 MiB
0 Stars 6 Watchers 0 Forks
Languages
Rust 98.6%
Python 1.3%
Shell 0.1%