Commit Graph

203 Commits

Author SHA1 Message Date
c77024cdf5 fix(http): update websocket subscription tests to expect call.responded (dispatch_requested now routes Subscription via invoke_streaming) 2026-07-02 10:10:42 +00:00
9e4d17b1c5 feat(http/server/subscribe-sse-streaming): wire /subscribe to invoke_streaming and pipe BoxStream to SSE
Replace the one-event placeholder (subscribe_stream_from_envelope +
envelope_to_sse_stream, which called invoke() and wrapped the single
ResponseEnvelope) with the real streaming path: subscribe_handler now
calls GatewayDispatch::invoke_streaming() and pipes the
BoxStream<ResponseEnvelope> to SSE via subscribe_stream_from_envelope_stream
(futures::StreamExt::map). Each Ok(output) becomes a data: frame; each
Err becomes an event:error frame (terminal — stream ends after it);
natural stream end closes the SSE. Internal ops still return a single
NOT_FOUND error event via subscribe_stream_internal_error (kept). Client
disconnect drops the stream via Rust's Drop (abort cascade per ADR-016).
2026-07-02 10:04:27 +00:00
2905e55e72 Merge branch 'feat/call/protocol/dispatch-streaming-branch' into develop 2026-07-02 09:58:37 +00:00
c58eccd5a6 feat(call/protocol/dispatch-streaming-branch): branch handle_stream on op_type; Subscription → invoke_streaming → pump each → call.completed
Add DispatchResult::Once|Stream enum and Dispatcher::dispatch() that branches
on the registered op_type (ADR-049 §6): Query/Mutation → invoke() (unchanged
Once path), Subscription → invoke_streaming() (Stream path). handle_stream
matches on DispatchResult: the Once path writes one call.responded/call.error
frame (no call.completed); the Stream path pumps each ResponseEnvelope to the
wire via pump_stream (ResponseEnvelope.into() → call.responded for Ok,
call.error for Err), then writes call.completed on natural stream end. An Err
envelope is terminal — last_was_error suppresses call.completed after an error.
The streaming branch clears context.deadline to None (subscriptions are
unbounded — ADR-049 §6, call-protocol Timeouts). Abort (ADR-016) needs no new
code: handle_abort removes the pending entry and dropping the pump task
releases handler resources via Drop. dispatch_requested delegates to dispatch
for backward compatibility with existing callers.
2026-07-02 09:56:05 +00:00
4ac8d308e6 feat(http/gateway/invoke-streaming): add GatewayDispatch::invoke_streaming
Add the streaming analogue of invoke() returning BoxStream<ResponseEnvelope>.
Security invariants are identical to invoke() (internal: false,
forwarded_for: None, same capabilities/scoped_env/ACL) — shared via a
build_root_context_inner helper with a bounded flag. The streaming path
sets deadline: None (unbounded subscriptions, ADR-049 §6). Calls
OperationRegistry::invoke_streaming() (already on develop). to_mcp is
unchanged (MCP excludes Subscription, ADR-041).

Tests cover: subscription dispatch, leading-slash strip, unknown op
NOT_FOUND, internal op NOT_FOUND (not leaked), None identity FORBIDDEN,
Query op INVALID_OPERATION_TYPE, invoke() on Subscription returns
INVALID_OPERATION_TYPE (guard holds through gateway), and
build_root_context_streaming sets deadline: None while carrying the
registration bundle.
2026-07-02 09:54:14 +00:00
a1e4752fdf Merge branch 'feat/http/adapters/from-openapi-sse-streaming' into develop 2026-07-02 09:48:07 +00:00
6f05dd8995 feat(http/adapters/from-openapi-sse-streaming): branch from_openapi forwarding on op_type; Subscription → StreamingHandler (SSE → BoxStream<ResponseEnvelope>)
build_registration now branches on op_type: Subscription ops register a
StreamingHandler (HandlerKind::Stream) via make_streaming_handler that
streams SSE response chunks as ResponseEnvelope::ok() items (one per
data: frame); Query/Mutation ops keep the existing Handler
(HandlerKind::Once) via forward(). Closes the gap where a from_openapi-
imported Subscription returned only the last SSE event.

- forward_stream(): non-async fn returning ResponseStream; sends the
  request with Accept: text/event-stream, then streams SSE chunks via
  stream::unfold over response.bytes_stream(), reusing parse_sse_frames
  (multi-event, partial trailing, comments, multi-line data, BOM).
  HTTP error (non-2xx) → single ResponseEnvelope::error(), stream ends;
  SSE stream end → ResponseStream ends (→ call.completed on wire).
- Removed stream_subscription() (the collect-all placeholder that
  truncated to the last event). parse_sse_frames stays (reused).
- Query/Mutation forwarding unchanged (existing forward() path).
- Tests: Subscription registration is HandlerKind::Stream; Query
  registration is HandlerKind::Once; SSE subscription streams multiple
  ResponseEnvelope::ok() (one per data: frame); HTTP error → single
  error envelope; Query forwarding unchanged (single response).
2026-07-02 09:45:55 +00:00
5c37e5b3af Merge branch 'feat/call/client/from-call-streaming-forwarding' into develop 2026-07-02 09:45:29 +00:00
67b1adba98 feat(call/client/from-call-streaming-forwarding): branch from_call forwarding on op_type
Subscription ops discovered via services/list + services/schema now
register a StreamingHandler (HandlerKind::Stream) that calls
CallConnection::subscribe_with_payload and forwards the remote stream
end-to-end (ADR-049 §8). Query/Mutation ops keep the existing
make_forwarding_handler (HandlerKind::Once).

- Add CallConnection::subscribe_with_payload(payload) mirroring
  call_with_payload so the forwarding handler can populate forwarded_for
  (ADR-032) + auth_token on the subscription payload. subscribe() now
  delegates to subscribe_with_payload.
- Add make_streaming_forwarding_handler() in from_call.rs using
  make_streaming_handler + futures::stream::once(...).flatten() to await
  subscribe_with_payload then forward its stream.
- Branch build_bundles on spec.op_type (already parsed by rebuild_spec_for).
- Reuse build_forwarded_payload — no new payload-construction code.
- composition_authority: None, scoped_env: None for FromCall streaming
  leaves (same as Query/Mutation FromCall leaves).
- Abort cascade (ADR-016 §6) already wired via PendingRequestMap in
  subscribe_with_payload.

Closes the gap where a from_call-imported Subscription truncated to the
first value.
2026-07-02 09:43:45 +00:00
acaa0513e4 feat(call/registry): add OperationRegistry::invoke_streaming() returning ResponseStream
Streaming dispatch path for Subscription operations — counterpart to
invoke(). Same visibility + ACL checks (internal → handler_identity,
external → identity), then dispatches to the StreamingHandler. Pre-handler
errors (not-found, forbidden, INVALID_OPERATION_TYPE for non-Subscription
ops) yield a single error ResponseEnvelope via stream::once and end the
stream. Adds 6 unit tests covering dispatch, not-found, wrong-kind,
internal-from-external, ACL denied, and internal-call handler_identity ACL.

Refs ADR-049 §3, §5.
2026-07-02 09:39:31 +00:00
9c81129f24 feat(call): introduce StreamingHandler, HandlerKind, ResponseStream + INVALID_OPERATION_TYPE (ADR-049)
Add the foundational types for ADR-049 streaming handlers:
- StreamingHandler, ResponseStream type aliases and HandlerKind enum
  (Once | Stream) in registration.rs, with make_streaming_handler() helper
- CallError::invalid_operation_type() in wire.rs (sixth protocol code,
  retryable: false)
- HandlerRegistration.handler flipped from Handler to HandlerKind;
  HandlerRegistration::new() now takes HandlerKind
- OperationRegistryBuilder absorbs wrapping: with_local/with_leaf/
  with_leaf_provenance wrap raw Handler in HandlerKind::Once for
  Query/Mutation; new with_local_streaming/with_leaf_streaming take a
  StreamingHandler and wrap in HandlerKind::Stream for Subscription.
  Builder validates kind matches spec.op_type (mismatch = startup error)
- OperationRegistry::register() returns Result<(), String> with a clear
  mismatch message; all call sites updated to handle the Result
- invoke() matches on HandlerKind: Once -> existing path; Stream ->
  INVALID_OPERATION_TYPE error envelope (guards against silent
  truncation; invoke_streaming() added in a downstream task)
- OverlayOperationEnv::invoke_with_policy matches on HandlerKind:
  Once -> dispatch; Stream -> INVALID_OPERATION_TYPE (composition is
  request/response-only)
- Migrated every HandlerRegistration::new() construction site (~95)
  to wrap raw Handler in HandlerKind::Once(handler); the builder sites
  are handled by the builder-absorbs-wrapping change
- Updated two websocket subscription tests that relied on Subscription
  ops dispatching via invoke() to expect INVALID_OPERATION_TYPE
- Added unit tests for invoke/register validation and
  make_streaming_handler
2026-07-02 09:28:05 +00:00
b3ab6ef097 docs(http): mark http/adapters/to-openapi completed + fix formatting
to_openapi gateway projection merged: 5-endpoint OpenAPI doc (ADR-042/045), pure
projection, info.version 1.0.0, error fidelity (ADR-023). 230 tests pass. Clippy clean.
2026-07-01 23:37:35 +00:00
18156ac9d2 Merge origin/develop: integrate connection-overlay (resolve adapter.rs test conflict, keep /openapi.json route test) 2026-07-01 20:17:06 +00:00
dd6aacc598 feat(http): complete to_openapi gateway projection with error fidelity and route wiring
Refine to_openapi to project operation-level errors (with http_status)
onto /call and /subscribe responses via oneOf merge with protocol-level
errors, preserving HTTP_<status> prefix codes without collision. Fix
BTreeMap→serde_json::Map for Value::Object compatibility. Wire GET
/openapi.json route test. Apply cargo fmt across the crate.
2026-07-01 20:11:09 +00:00
2695a19502 feat(http): implement to_openapi gateway projection (5-endpoint OpenAPI doc, info.version 1.0.0)
to_openapi(registry) -> OpenAPISpec generates the fixed 5-endpoint
gateway doc (/search, /schema, /call, /batch, /subscribe) — pure
projection (ADR-017 §5), gateway pattern (ADR-042). info.version is
1.0.0 tracking the gateway contract (ADR-045). /call responses carry
protocol-level errors (400/401/403/404/500/504) plus operation-level
errors mapped by http_status (ADR-023). GET /openapi.json wired to
serve the generated spec.
2026-07-01 19:52:57 +00:00
468bd84f86 Merge feat/http-connection-overlay: Connection-local Layer 2 overlay for browser-registered ops
Adds AccessControl::check to OverlayOperationEnv::invoke_with_policy in alknet-call
so hub's calls to browser-registered ops are gated by the browser's AccessControl.
Creates src/websocket/overlay.rs with 19 integration tests: overlay scoping (not
PeerCompositeEnv), no PeerId, register_imported/all, overlay_env() routing,
PeerRef::Specific→NOT_FOUND, AccessControl gating, overlay drop on WS close,
ADR-016 abort cascade, bidirectionality, no-ops use-case scoping.
2026-07-01 19:46:56 +00:00
ad279693ce feat(http): connection-local Layer 2 overlay for browser-registered ops (ADR-024/034/044)
Enforce AccessControl on overlay ops in OverlayOperationEnv::invoke_with_policy
(alknet-call) so the hub's calls to browser-registered ops are gated by the
browser's AccessControl — matching OperationRegistry::invoke semantics for
internal composition (caller identity = parent handler_identity.as_identity()).

Add src/websocket/overlay.rs with 19 integration tests covering the connection-
local overlay acceptance criteria: browser ops land in the per-CallConnection
overlay (not PeerCompositeEnv), no PeerId for the browser, register_imported()/
register_imported_all() populate the overlay, hub outgoing calls route through
overlay_env() (not PeerRef::Specific), PeerRef::Specific('browser-X') routes to
NOT_FOUND, AccessControl gates hub calls (allowed/forbidden/default), overlay is
per-connection isolated and dropped on WS close, WS close aborts in-flight calls
with ADR-016 cascade, bidirectionality, and browser-with-no-ops use-case scoping.
2026-07-01 19:45:36 +00:00
58e16d088b review(http): mark http/review-mcp completed + fix formatting across crate
Review-mcp verification complete: all 12 checklist items pass (from_mcp/to_mcp
conformance, ADR-037/041/014/023/034, feature gate isolation, GatewayDispatch
concrete struct, test coverage 223+5). Applied cargo fmt across crate.
2026-07-01 19:32:42 +00:00
643f8727d2 review(http): fix formatting in from_mcp/mod.rs (review-mcp finding) 2026-07-01 19:31:27 +00:00
b127699aad Merge feat/http-to-mcp: to_mcp gateway projection (4-tool gateway, rmcp StreamableHttpService)
Implements src/adapters/to_mcp.rs: ToMcpGateway rmcp ServerHandler with 4 fixed
gateway tools (search/schema/call/batch). search dispatches services/list (ACL-
filtered, excludes Subscriptions), schema dispatches services/schema, call/batch
dispatch via GatewayDispatch::invoke with ResponseEnvelope→CallToolResult mapping.
Bearer auth via shared middleware around nest_service. Identity survives rmcp
framing (research §6 #2 confirmed). Feature-gated behind mcp; stdio NOT built
(ADR-037). Pure projection. 16 unit tests.

# Conflicts:
#	crates/alknet-http/src/server/adapter.rs
2026-07-01 19:24:33 +00:00
31291bd2d9 Merge feat/http-gateway-endpoints: 5 fixed gateway endpoints (search/schema/call/batch/subscribe)
Implements src/server/gateway_routes.rs: POST /call, GET /search, GET /schema,
POST /batch, POST /subscribe (SSE). All delegate to GatewayDispatch::invoke; auth
via ResolvedIdentity extractor; errors mapped via call_error_to_http_response
(identity-aware 401/403 split). Internal ops → 404. /schema adds ACL pre-check.
/subscribe projects ResponseEnvelope as SSE. /batch loops over invoke. Wired real
handlers into adapter.rs replacing placeholder 501s. 157 tests pass.

Note: /subscribe SSE completes after single event (registry invoke returns single
ResponseEnvelope, no streaming handler yet — research §6 OQ#5).

# Conflicts:
#	crates/alknet-http/src/server/adapter.rs
2026-07-01 19:19:50 +00:00
64696fec97 feat(http): implement to_mcp 4-tool gateway projection (rmcp ServerHandler, StreamableHttpService at /mcp)
to_mcp is the MCP-direction gateway projection (ADR-041): exposes 4 fixed
gateway tools (search, schema, call, batch) over rmcp StreamableHttpService
nested into the axum Router at /mcp, not one MCP tool per registry operation.
The LLM discovers operations on demand via search+schema.

- ToMcpGateway implements rmcp ServerHandler (call_tool, list_tools, get_info)
- tools/list returns the 4 fixed gateway tools, never the registry's ops
- search dispatches services/list via GatewayDispatch::invoke, excludes
  Subscription ops (ADR-041 §2), returns names + descriptions
- schema dispatches services/schema, returns the full OperationSpec
- call dispatches via GatewayDispatch::invoke (shared spine), maps
  ResponseEnvelope -> CallToolResult::structured (Ok) /
  CallToolResult::structured_error (Err(CallError))
- batch loops over invoke, returns an array of results
- Bearer auth via shared bearer_auth_middleware applied around nest_service
  (rmcp simple_auth_streamhttp pattern); Identity read from
  RequestContext.extensions -> http::request::Parts.extensions
  (research §6 #2 identity-survives-framing assumption, confirmed via test)
- to_mcp is a pure projection (consumes registry, produces no entries)
- Feature-gated behind mcp; stdio NOT built (ADR-037)
- /mcp route wired in adapter.rs replacing the placeholder 501

cargo test -p alknet-http --features mcp: 172 passed
cargo clippy -p alknet-http --features mcp --all-targets: clean
cargo check -p alknet-http (no mcp): clean
2026-07-01 19:18:19 +00:00
5a629a48e5 feat(http): implement 5 gateway endpoints (search/schema/call/batch/subscribe)
Implement the sole HTTP invoke path (ADR-042/047) in
src/server/gateway_routes.rs: POST /call reads { operation, input },
resolves identity via the shared ResolvedIdentity extractor, dispatches
via GatewayDispatch::invoke, and returns ResponseEnvelope as JSON with
errors mapped via call_error_to_http_response. GET /search dispatches
services/list (AccessControl-filtered); GET /schema dispatches
services/schema with an ACL pre-check (unauthorized -> 403). POST /batch
loops over invoke returning an array; POST /subscribe projects the
response as SSE (text/event-stream) with data frames for call.responded
and an error event for call.aborted. Internal ops return 404. Wire the
real handlers into adapter.rs router, replacing the placeholder 501s.
2026-07-01 19:17:59 +00:00
384ad03619 feat(http): implement WebSocket upgrade handler (native EventEnvelope session, no length prefix, bearer auth) 2026-07-01 19:15:11 +00:00
ccbff3c7f8 Merge feat/http-bearer-auth-middleware: Shared Bearer auth middleware
Implements src/server/auth.rs: bearer_auth_middleware (from_fn_with_state over
Arc<dyn IdentityProvider>, stashes Option<Identity> in request extensions),
extract_bearer_identity (Bearer-only: no/malformed/Basic/unresolvable → None,
not an error), ResolvedIdentity axum extractor. Wired into HttpAdapter router
via route_layer around gateway/openapi/mcp routes, excluding /healthz. 11 tests.

# Conflicts:
#	crates/alknet-http/Cargo.toml
#	crates/alknet-http/src/server/adapter.rs
#	crates/alknet-http/src/server/mod.rs
2026-07-01 18:51:29 +00:00
36f74dd31b feat(http): implement shared Bearer auth middleware (resolve_from_token, stash Identity in request extensions)
Add src/server/auth.rs with bearer_auth_middleware axum layer that
extracts the Authorization: Bearer header, resolves via
IdentityProvider::resolve_from_token, and stashes Option<Identity> in
request extensions. Shared by HTTP gateway routes and the to_mcp rmcp
service (research §4.4). No token, malformed header, or failed
resolution all yield None (unauthenticated, not an error) — Bearer-only
auth mechanism (ADR-004).

Includes ResolvedIdentity axum extractor reading from extensions, and
wires the middleware into the HttpAdapter router around the
gateway/openapi/mcp routes (excluding the raw /healthz route).
2026-07-01 18:48:00 +00:00
3702da1aee feat(http): implement /healthz raw route and stealth decoy fallback
GET /healthz: raw route, no auth, no OperationContext, returns 200 OK
with plain-text 'ok' (ADR-036). Decoy fallback for unknown paths via
DecoyConfig: fake nginx 404 (default), static site serving, or redirect.
Decoy does not leak alknet presence (no alknet headers/format). Custom
routes take precedence over decoy (decoy is fallback only). Wire real
handlers into HttpAdapter router replacing placeholder 501s.
2026-07-01 18:40:01 +00:00
3eb2a51184 Merge feat/http-from-mcp: from_mcp adapter (rmcp streamable HTTP, tools/list, structuredContent handling)
Implements FromMCP (feature-gated behind mcp) in src/adapters/from_mcp/: rmcp
StreamableHttpClientTransport connects to MCP endpoint, calls tools/list, builds
HandlerRegistration bundles (provenance FromMCP, leaf, Internal, Mutation,
capabilities=bearer token). Forwarding handler calls client.call_tool, maps
CallToolResult per structuredContent-preferred-over-content-blocks rule (declared
outputSchema → structuredContent; absent → ContentBlock union; no heuristic
JSON.parse; isError→CallError). No-env-vars (reads context.capabilities).
Streamable HTTP only (ADR-037). 19 unit + 5 integration tests.

# Conflicts:
#	crates/alknet-http/src/adapters/mod.rs
2026-07-01 18:22:32 +00:00
c9e5ea1c75 feat(http): implement from_mcp adapter (rmcp streamable HTTP client, tools/list discovery, structuredContent handling)
FromMCP (OperationAdapter, feature-gated behind mcp) discovers remote MCP
tools over streamable HTTP via rmcp's StreamableHttpClientTransport, calls
tools/list, and registers each as a HandlerRegistration bundle with a
forwarding handler that calls the remote tool via tools/call. Output
handling follows the structuredContent-preferred-over-content-blocks rule:
declared outputSchema + structuredContent is the composable result; absent
outputSchema falls back to the MCP ContentBlock union. isError:true maps to
a CallError with the error content. No-env-vars invariant: the handler reads
context.capabilities (injected at registration), never std::env::var (ADR-014).
Streamable HTTP only — stdio is not built (ADR-037). Provenance is FromMCP
(leaf: composition_authority None, scoped_env None, Internal by default,
ADR-015/022). Includes unit tests for schema/mapping logic and an integration
test that spins up a real rmcp streamable HTTP server and exercises the
forwarding handler end-to-end.
2026-07-01 18:21:45 +00:00
ad8d7879ae Merge feat/http-from-openapi: from_openapi adapter (OpenAPI parser, reqwest forwarding, no-env-vars)
Implements FromOpenAPI in src/adapters/from_openapi.rs: OpenAPISpec/HttpServiceConfig/
HttpAuthScheme types, $ref resolution, OperationAdapter::import() producing
HandlerRegistration bundles (Internal visibility, FromOpenAPI provenance,
HTTP_<status> error codes per ADR-023). Reqwest forwarding handlers read credentials
from OperationContext.capabilities (no-env-vars ADR-014) via SharedHttpClient.
JSON/text/binary response branching, SSE subscription streaming, Bearer/ApiKey/Basic
auth injection. 98 tests pass.
2026-07-01 18:19:44 +00:00
6b30e2ac15 feat(http): implement from_openapi adapter (OpenAPI parse + reqwest forwarding handlers)
Parses OpenAPI 3.x documents into HandlerRegistration bundles with
reqwest-backed forwarding handlers that inject credentials from
OperationContext.capabilities (no-env-vars invariant, ADR-014).
Error codes are prefixed HTTP_<status> (ADR-023); ops are Internal
leaves with FromOpenAPI provenance (ADR-015/022). SSE subscriptions
are consumed via parseSSEFrames; JSON/text/binary response branching
mirrors the TS prior art.
2026-07-01 18:18:28 +00:00
b313dcbf20 feat(http): implement HttpAdapter (ProtocolHandler for h2/http1.1, axum over QUIC)
Wires the axum Router (gateway endpoints + /healthz + /openapi.json + MCP +
custom routes via extra_routes merge ADR-046) and drives hyper's HTTP/1.1 or
HTTP/2 connection driver over a single QUIC bidirectional stream. The
QUIC-to-hyper bridge wraps the (SendStream, RecvStream) pair as a
TokioIo-compatible duplex and feeds it to hyper-util's auto Builder (which
auto-detects HTTP/1.1 vs HTTP/2). h3 ALPN is not registered (ADR-044).

Route handlers, healthz/decoy logic, openapi.json, the MCP route, and the WS
upgrade handler are wired as 501 Not Implemented placeholders for their
respective tasks. The router state holds Arc<OperationRegistry> +
Arc<dyn IdentityProvider>; the router is built once at construction and
cloned per connection (cheap Arc clone). DecoyConfig defaults to NotFound.

Adds hyper-util dependency (server, service, tokio features).
2026-07-01 18:07:56 +00:00
ea38f81c12 Merge feat/http-shared-http-client: Shared HTTP client with retry + Retry-After middleware
Implements SharedHttpClient (ArcSwap<ClientWithMiddleware>) with HttpClientConfig
(pool/timeout/retry/optional CA bundle+client cert), RetryTransientMiddleware from
reqwest-retry, and inlined RetryAfterMiddleware (~90 lines, bounded HashMap with LRU
eviction, parses Retry-After seconds + HTTP-date, sleeps on 429/503). reload() via
ArcSwap. No env-var reads; per-request credential injection only. 24 unit tests.
2026-07-01 17:21:55 +00:00
081fc911ef feat(http): implement shared HTTP client (ClientWithMiddleware + retry + Retry-After, OQ-40)
Adds SharedHttpClient wrapping ArcSwap<ClientWithMiddleware> with a
RetryTransientMiddleware + inlined RetryAfterMiddleware stack.
HttpClientConfig covers pool, timeout, retry policy, and optional CA
bundle/client cert. reload() rebuilds and swaps via ArcSwap. No env-var
reads; credential injection is per-request, not at construction.
2026-07-01 17:20:49 +00:00
9512e61e73 Merge feat/http-dispatcher-transport-abstraction: Expose EventEnvelope-level dispatch API for non-QUIC transports
Cross-crate change (alknet-call): expose Dispatcher::dispatch_requested as pub,
extract abort-cascade handling into pub handle_abort method, add
CallConnection::new_overlay_only(identity) constructor (Option A) for non-QUIC
transports. Existing QUIC path (CallAdapter, CallClient, run_loop, handle_stream)
unchanged. 13 unit tests in alknet-call + 6 integration tests in alknet-http.
2026-07-01 17:17:54 +00:00
ef53a03589 feat(call,http): expose EventEnvelope-level dispatch API for non-QUIC transports
Make Dispatcher::dispatch_requested pub and extract abort-cascade handling
into a pub handle_abort method so the WebSocket handler can feed deserialized
EventEnvelopes directly to the shared Dispatcher without a QUIC Connection.

CallConnection gains a new_overlay_only(identity) constructor (Option A) that
holds the Layer 2 overlay, PendingRequestMap, and resolved bearer Identity
without a QUIC Connection; identity() reads the stored field for the non-QUIC
case. compose_root_env uses the new identity() accessor for both paths.

The existing QUIC path (CallAdapter, CallClient, run_loop, handle_stream) is
unchanged — outgoing client methods guard on connection().is_none().
2026-07-01 17:17:02 +00:00
81781d89fa Merge feat/http-error-mapping: CallError-to-HTTP status error mapping (ADR-023)
Implements call_error_to_http_status, call_error_to_http_status_with_identity,
and call_error_to_http_response in src/gateway/error.rs. Five protocol codes
map to fixed statuses (404/422/504/500 + 401/403 split for FORBIDDEN).
HTTP_<status>-prefixed operation-level codes parse status from prefix. Unknown
operation-level codes default to 500. Retry-After header for retryable 503/429.
21 unit tests.

# Conflicts:
#	crates/alknet-http/src/gateway/mod.rs
2026-07-01 17:09:25 +00:00
33fecd5470 feat(http): implement CallError-to-HTTP error mapping (ADR-023)
Add gateway/error.rs with call_error_to_http_status,
call_error_to_http_status_with_identity, and call_error_to_http_response.
Maps the five protocol codes (NOT_FOUND/FORBIDDEN/INVALID_INPUT/TIMEOUT/
INTERNAL) to fixed HTTP statuses, splits FORBIDDEN into 401 (no identity) /
403 (identity present), maps HTTP_<status>-prefixed operation-level codes
to the status number (from_openapi fidelity), and defaults unknown
operation-level codes to 500. Retryable 503/429 errors carry a Retry-After
header when details.retry_after is present.
2026-07-01 17:06:49 +00:00
a4ce2c8173 feat(http): implement GatewayDispatch shared dispatch spine
Thin concrete struct (not a trait) holding Arc<OperationRegistry> +
Arc<dyn IdentityProvider>. Exposes resolve_bearer() (delegates to
identity_provider.resolve_from_token) and invoke() which builds a root
OperationContext for wire-ingress (internal: false, forwarded_for: None,
fresh UUID v4 request_id, deadline now+30s) carrying the registration
bundle's composition_authority/capabilities/scoped_env, then calls
OperationRegistry::invoke. Dispatches services/list and services/schema
unchanged (registered ops); AccessControl filtering in services/list
sees the caller's resolved identity. Re-exported from lib.rs.

Duplicates Dispatcher::build_root_context construction (the alknet-call
version is pub(crate) and tangled with CallConnection peer/session
overlays); the invariants (internal: false, forwarded_for: None) are
the load-bearing part and identical to the wire-ingress path.
2026-07-01 17:05:10 +00:00
c2e6ba5b96 feat(http): initialize alknet-http crate with module skeleton
Add crates/alknet-http with Cargo.toml, src/lib.rs, and the five
subsystem modules (server, gateway, client, adapters, websocket) per
ADR-039 (server + client host colocated). The mcp feature gate pulls in
rmcp with streamable HTTP transport features only (ADR-037 — no stdio);
h3/WebTransport is absent (deferred per ADR-044). alknet-core and
alknet-call use workspace path deps. The crate is added to the workspace
members list.
2026-07-01 16:41:14 +00:00
bfd1621b9b feat(call): add ScopedPeerEnv peer-pinned reachability (ADR-029 §4, call/scoped-peer-env) 2026-06-30 11:07:41 +00:00
850ac6b7bc fix(call): remove dead test helper and unused mut (clippy -D warnings clean) 2026-06-30 10:30:52 +00:00
507358b285 review(call): fix fmt drift in adapter.rs and env.rs (call/review-call-sync) 2026-06-28 22:29:10 +00:00
c106f4a37b feat(call): wire CallClient TLS client-auth and server cert verifier selection (call/call-client-verifier-selection)
Replace AcceptAnyServerCertVerifier (a security hole for X.509) with
verifier selection by PeerEntry presence (ADR-034 §3, OQ-29):

- build_client_auth presents the Ed25519 key as an RFC 7250 raw public
  key client cert (replaces with_no_client_auth), activating the
  PeerEntry fingerprint -> peer_id resolution path on quinn.
- select_server_verifier: Some(fingerprint) -> FingerprintPinVerifier
  (fingerprint match for known peers); None -> WebPkiServerVerifier
  (CA verification for public X.509 endpoints). None + Ed25519 raw key
  fails closed at handshake (no CA to fall back to).
- FingerprintPinVerifier matches ed25519:<hex> (raw key extraction) and
  SHA256:<hex> (DER hash); verifies handshake signatures via
  verify_tls13_signature_with_raw_key / verify_tls12/13_signature.
- Extract shared fingerprint logic into alknet_core::fingerprint (pub
  module) reused by endpoint (server-side) and call_client (client-side).
- remote_identity: None is load-bearing (not defaulted to placeholder).
- Integration tests updated to pin the self-signed server cert
  fingerprint (the known-peer path).
2026-06-28 22:24:09 +00:00
f5fede2758 feat(call): wire from_call forwarded_for and peer-keyed collision (call/from-call-forwarded-for) 2026-06-28 22:21:52 +00:00
7f9e5828b9 feat(call): wire dispatch_requested to resolve peer Identity, ACL gate, and forwarded_for (call/dispatch-peer-identity) 2026-06-28 22:21:23 +00:00
d04cb9c125 feat(call): add invoke_peer/peer_contains/PeerRef to OperationEnv for peer-keyed routing (call/operation-env-invoke-peer) 2026-06-28 22:09:35 +00:00
5d6a943ad4 feat(call): add forwarded_for field to OperationContext (call/operation-context-forwarded-for) 2026-06-28 22:08:35 +00:00
877c923244 feat(call): filter services/list by AccessControl and add services/list-peers opt-in (call/services-list-accesscontrol-filtered) 2026-06-28 22:03:29 +00:00
e8219fa550 feat(call): replace CompositeOperationEnv with peer-keyed PeerCompositeEnv (call/peer-composite-env) 2026-06-28 22:02:17 +00:00