- 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
Mechanical pass for the alkcall 0.6.0 bump (review 007's
establishment follow-ups sweep: R-01 Establishment plan payload,
R-02 OpenHandler lifetime doc note, R-03 pump_bidi extraction):
- bump alkcall 0.5 -> 0.6 (gateway feature unchanged)
- the OpenHandler signature gained a plan parameter —
Fn(Value, Option<ChannelPlan>, Connection, AuthContext) ->
JoinHandle<()> — so the test's echo_open_handler closure gains
`_plan` (the only closure-construction site in this crate)
- no ferry change: this crate constructs no Establishment and passes
the Option<OpenEstablisher> through unchanged (OpenableAlpn fields
and register_openable_with_establisher threading are unchanged in
0.6.0); the establisher's Establishment.plan now reaches the pump
handler's second parameter process-locally (ADR-049 amendment 2)
- docs updated: websocket.md (plan threading + pump_bidi pointer),
OpenableAlpn field docs, with_ws_openable_alpns doc comment
- CHANGELOG [Unreleased] consumption-wave entry
Verification: cargo test (454 passed), cargo test --all-features
(587 passed), clippy -D warnings (default + all-features),
fmt --check, cargo doc --no-deps clean
Review 007 Unit 2 (the two "implement" decisions taken during
remediation, plus the coverage gap):
- WS-29: the `op/register` override surface review-006 UP-02 and
ADR-048 recorded as landed is now implemented. The hook threads an
`op_register_acl` `AccessControl` into `op_register_spec` (the
permissive `AccessControl::default()` remains the default
everywhere); the built-in surface sets it via
`HttpAdapter::with_ws_op_register_acl`, bare-registry/custom routes
via the `OpRegisterAcl` request extension (mirroring
`ChannelsPolicy`/`WsTimeouts`/`OpenableAlpns`). A peer whose
identity does not satisfy the ACL gets `FORBIDDEN` on the announce.
Gates: builder path (`FORBIDDEN` scope-less / announce-ok scoped)
+ extension path.
- WS-30: the bare-registry `SessionState` is built by `FromRef` per
request, so its default-cap semaphore bounds nothing across
requests (corrects review-002 WS-17's "bounded at 64 sessions"
claim). New `SessionSlots` request extension carries the shared
semaphore for routes that need an effective cap; the upgrade
handler prefers it over the state value. Doc comments corrected
(`SessionState`, `WsTimeouts`, `ws_upgrade_handler`). Gate:
cap-1 route → 503 over cap → slot freed on session end.
- WS-32: the built-in openables threading
(`with_ws_openable_alpns` → `RouterState` → `SessionState` → hook)
gets its first gate — every Unit-3 gate rode the `OpenableAlpns`
extension fallback. `builder_path_openables_serve_the_data_channel_
surface` discovers the openable via `services/list`, opens the
channel, and round-trips bytes through the builder-built router.
Verification: cargo test 454 passed / 0 failed; cargo test
--all-features 587 passed / 0 failed (+5 gates); clippy (both
configs) clean; fmt clean.
Review: docs/reviews/007-ws-data-channel-surface-review.md
The WS-26 `ConnectionGuard` was bound inside its `if let` block, so it
dropped microseconds after insertion instead of living for the
channel-0 dispatcher task — `live_connections()` /
`live_connection_count()` were permanently empty for every session
(review 007 WS-28 [major], reproduced empirically; the handle
retention ADR-048 + review-006 record was aspirational at that commit).
Remediation (review 007 Unit 1):
- bind the guard as an `Option<ConnectionGuard>` in the channel-0
task's frame, mirroring `SessionGuard`'s shape in
`run_channels_session`; the block comment now describes the real
scope
- gate `live_connections_visible_mid_session_and_drain_after_teardown`
— handle visible mid-session (after a completed call proves the
dispatcher is up), drained after teardown; verified to fail against
the pre-fix tree and pass with the fix
Verification: cargo test 454 passed / 0 failed; cargo test
--all-features 582 passed / 0 failed; clippy (both configs) clean; fmt
clean.
Review: docs/reviews/007-ws-data-channel-surface-review.md (WS-28)
The alkcall 0.3.1 fix (fd21230, alkhttp review 006 UP-03) lands
PeerCompositeEnv::peer_operations, so services/list-peers now lists
peer-announced ops. Version pin stays 0.3 (semver-compatible); the
lockfile bump carries no source change.
- Extend op_register_served_per_session_and_collision_is_already_exists
with the discovery assertion that originally surfaced UP-03: after
the announce resolves, services/list-peers attributes consumer/exec
to the alice peer entry (fails against 0.3.0, verified by the
original draft).
- Review 006 UP-03 entry records the 0.3.1 landing (alkcall ADR-030).
Verification: cargo test 454 passed / 0 failed; cargo test
--all-features 582 / 0; clippy (default + all-features, all-targets,
-D warnings) clean; fmt clean; doc clean.
The WS path wires the alkcall 0.3 per-session mechanisms — the OQ-05
deferred half (review-003 WS-20/21/22/25/26, the decisions WS-24/WS-25
resolved upstream):
- install_channel_zero reworked per the ADR-047 §4 amendment #2 shape:
fork the deployment's base registry, register the generic channel
ops (channel/close, channel/control, channel/resources/subscribe —
WS-21), the deployment's openable ALPNs (ChannelCore::register_openable,
WS-22), the bootstrap discovery set closed over the fork
(install_bootstrap_discovery, F-06), and op/register (WS-25; the
collision set is the fork per review-005 G-03), then dispatch over
the fork. The session's ChannelsPolicy rides the hook (one policy
instance across open wrappers and the demux teardown path).
- OpenableAlpn { spec, open_handler } + HttpAdapter::with_ws_openable_alpns,
threaded RouterState -> SessionState -> hook, with the OpenableAlpns
request-extension fallback (mirroring ChannelsPolicy/WsTimeouts).
- WsSessions retains the channel-0 Arc<CallConnection> (WS-26) with a
self-removing guard (ConnectionGuard); live_connections() is the
deployment-visible surface.
- UP-01: ALREADY_EXISTS maps to 409 Conflict in the gateway error map.
- from_wss import excludes the protocol-session ops (bootstrap set +
channel lifecycle ops): the fork serves them per session, and proxying
session-scoped machinery (e.g. channel/close across sessions) would be
nonsense. Discovery runs first, the filter is the listing minus those
names.
- adapter_install_channel_zero cfg matches its caller (WS-27); it
inherits the reworked hook (session ops now served in the from_wss
test-server producer too).
Gates (Unit 3, tests/ws_upgrade_session.rs; the WS-23 e2e set):
open -> channel_id -> discoverable in services/list -> chunks both
ways -> handler sees bytes; channel/close resolves + ledger decrement;
cap denial (channel:-prefixed); mid-open disconnect teardown; TooLarge
demux resync through the WS path (16 MiB + 1 skip consumed);
op/register announce + overlay-collision + serving-registry-collision
ALREADY_EXISTS through the WS path.
call_and_await now filters by request id and tolerates data-channel
chunks (a prior Sub's trailing call.completed may interleave).
Verification: cargo test 454 (default) / 582 (all-features), clippy
both sides -D warnings clean, fmt clean, doc clean.
Bump alkcall 0.2 -> 0.3 (published 2026-09-04; the remediation of
alkcall reviews 004-005, the upstream resolution of alkhttp review 003
WS-24/WS-25). Two source-break classes, both mechanical:
- take_call_connection now returns Option<Arc<CallConnection>>
(was bare value) — drop the double-wrap in WssSession::connect.
- register/register_on/register_openable take &OperationRegistry
(was &mut) — drop 51 stale `mut` bindings (clippy --fix), which is
the interior-mutability change that makes the per-session fork
surface possible.
No behavior change in this crate yet; the 0.3 mechanisms (fork,
serve_single_stream, op/register, install_bootstrap_discovery,
collision policy) are the composition surface for alkhttp review 003
Unit 2 (WS-20..22, WS-26 wiring) — not wired here yet.
Verification: cargo test 453 passed (default) / 575 passed
(all-features), clippy both sides -D warnings clean, fmt clean,
cargo doc --no-deps clean.
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.
- forward_stream build-error arm (forward.rs): wire-level test asserts
one INVALID_INPUT envelope then stream end with zero upstream
contact (a panicking responder counts as the contact guard), plus
the from_jsonschema integration mirror (undeclared key + non-scalar
placeholder, each naming its rejection source)
- PEM read-failure arms (http_client.rs): nonexistent CA path →
CaBundleRead (sync new), nonexistent client-cert path → ClientCertRead
(async reload, prior generation retained)
- Over-cap poll_write rejection leg (byte_adapter.rs): cap+1 write →
InvalidData naming the cap; stream stays usable for an at-cap write
afterwards
- SSE parser edges: CRLF split across feed chunks frames one line;
invalid-UTF8 data lines drop without killing the frame stream
- from_value structural rejects: non-object doc, missing `info`,
missing `paths`, non-object `paths` each name the member
- Connection-failure arms: accept-path ConnectionClosed →
HandlerError::ConnectionClosed via stream_error_to_handler; read-pump
demux-gone break ends the pump when the byte-stream side is dropped
- Delete the caller-less `impl Default for WsTimeouts` (the extension
is constructed explicitly)
cargo llvm-cov --all-features: all named arms covered; TOTAL regions
94.18% (was 93.86%), lines 96.04% (was 95.77%); http_client.rs
86.56% lines (was 81.72%).
docs(tasks): mark review-002-fu-stream-error-coverage done
- corrupt CA bundle section -> CaBundleParse naming the path (the
pure-non-PEM variant parses as zero sections and is accepted by
reqwest, so the arm needs structurally broken PEM)
- garbage client identity -> ClientCertParse naming the cert path,
with the error asserting key material is never echoed
- config() reflects a reloaded CA bundle on the TLS path (FWD-12)
GW-16: empty body / malformed first line / missing header fields /
per-line cap / batch over-cap rejections now route through
call_error_to_http_response_with_identity, mapping INVALID_INPUT to
422 — same status as mid-stream chunk errors. One error class, one
status.
GW-17: invoke_sink wraps the registry sink invoke in the same 30 s
tokio::time::timeout the Once-op invoke uses; a hung sink handler
surfaces as a TIMEOUT (504, retryable) error envelope instead of
holding the HTTP connection forever. The sink wrapper bounds the
whole dispatch (chunk pacing included), matching http-server.md's
deadline contract.
Docs: http-server.md error table documents the 422 triggers and the
sink deadline; http-adapters.md batch cap status corrected.
to_openapi: gateway spec version 1.3.0 -> 1.4.0 (ADR-045 minor):
/publish framing faults and /batch cap reject documented at 422 (the
400 slots moved with the runtime); /publish 400 slot removed; 504
description covers the sink dispatch.
Verification: scripts/verify.sh OK (397 tests); cargo test
--all-features OK (513 tests); clippy --all-features --all-targets -D
warnings OK; cargo fmt --check OK.
- new tests/retry_policy_wire.rs: POST+500 = 1 upstream hit (method
gate), GET 500/503/200 = 3 hits (retry-to-success), budget exhaustion
stops retries under a 50-attempt cap (wall-time + hit-count bounds)
- forward_stream terminal arms on the wire: oversized SSE line ->
one INTERNAL terminal envelope; mid-stream transport abort (staged
via a notify gate so the abort is genuinely mid-stream) -> terminal
envelope after the delivered frame; pending event EOF-flush; dead
port through both forward() and forward_stream()
- Once-path decode arms: malformed application/json 200 -> INTERNAL
decode envelope; application/octet-stream 200 -> byte-array envelope
- COV-12 credential arms: ApiKey and Basic malformed values fail
loudly without echoing secret material; declared header-param
invalid name/value rejections
- new tests/client_config_reload.rs: config() reflects a reloaded
config (FWD-12 atomicity half)
Wire-level round trip through the real rmcp server: a scalar input
reaches the remote tool as {"value": <input>}, matching the
value_to_json_object wrap.
peer.call_tool through the real rmcp streamable-HTTP transport for
schema/call/batch/unknown — the production ServerHandler::call_tool
routing shell, previously only exercised via the invoke_tool bypass
for search.
Verification: cargo test --features 'mcp test-support' --test full_surface
A logical request's Retry-After waits are now bounded by
max_total_retry_duration, and a retry storm can no longer re-arm a
full ceiling per attempt:
- BudgetClock anchored per logical request by RetryGateMiddleware
(into the request Extensions, shared across retry attempts) and
read by the inner RetryAfterMiddleware every attempt; the monotonic
anchor projects the hard stop through wall-clock steps.
- maybe_sleep_for truncates the sleep to the remaining budget;
a spent budget skips the sleep entirely.
- record() keeps the EARLIEST deadline per URL (retry storms cannot
extend the first-seen deadline); a refresh that cannot make it
under the budget hard stop drops the entry so the next attempt
starts immediately instead of parking.
- Middleware without a budget anchor (budget = 0) keeps the prior
semantics; the shared client now wires
HttpClientConfig.max_total_retry_duration into the Retry-After
middleware.
- Wire tests (tests/retry_after_budget.rs): always-429 responder with
a 300 s Retry-After is bounded by budget + one attempt; separate
logical requests still honor the recorded throttle window.
- FWD-12 atomic reload pairing and the FWD-15 stream-client split
untouched (stack built in build_client_with_pems for both).
- WsTimeouts { idle, write } request extension mirrors ChannelsPolicy:
a deployment layers it on a WS route (bare-registry routes included)
to set the pump knobs per route
- precedence: extension present replaces the router state entirely;
absent falls back to SessionState (adapter-configured idle) and the
crate default write window — a Default impl never clobbers the
adapter-configured idle knob
- upgrade.rs module + handler docs now state the real defaults for
bare-registry routes (60 s idle + 60 s write, 64-session semaphore,
handler-private WsSessions) and the extension surface
- split_ws_to_bytes_idle_with_write exposes the WS-18 write window to
run_channels_session; acceptance test drives a bare-registry route
with a 150 ms extension idle window (1001 eviction observed)
Verification: scripts/verify.sh OK (343 passed), test-support suite
ok, clippy -D warnings clean, fmt clean
Mirrors the WS-13 progress semantics onto the axum upgrade path with
three integration tests over the real HttpAdapter surface (also the
COV-11b dark-knob gate — with_ws_idle_timeout had no test caller):
the forever-dribble client is evicted with 1001 despite arriving
messages; a productive session round-trips calls across many windows
without eviction; the None knob never evicts a dribbling client.
Replace rmcp's unbounded Peer::list_all_tools with a bounded walk over
list_tools: hard cap of 100 pages (MCP_MAX_TOOLS_LIST_PAGES) plus a 60 s
overall deadline (MCP_TOOLS_LIST_DEADLINE). Tripping either budget fails
loudly with AdapterError::DiscoveryFailed naming pages fetched and tools
accumulated — no silent truncation, no partial registration (import
fails closed, as before). A slow or hung page is cut off by a per-page
tokio timeout sized to the remaining budget.
Bounds are documented in the module doc. Integration test added: a
cycling-cursor server (next_cursor always Some("a")) terminates with
the clean budget error inside an outer 10 s guard; the existing 3-page
pagination test is unchanged and passes.
Verification: cargo test (304 pass), cargo test --features mcp,
cargo test --all-features (412 pass), clippy -D warnings both default
and --all-features --all-targets, cargo fmt --check, cargo doc --no-deps.
- /search, /schema: document the envelope wrapper and the real item/spec
fields (PRJ-01/02); /search drops unreachable 401/403, documents 404
(PRJ-15)
- error statuses: 422 for dispatch-path INVALID_INPUT /
INVALID_OPERATION_TYPE; extractor 400s documented as the plain-text
gap they are (PRJ-03); operation-declared errors projected by
http_status with x-runtime-behavior: 500 on non-HTTP_* codes (PRJ-04
project-honest decision) — no runtime changes
- /subscribe: 200+SSE only; event:error terminal contract documented
(PRJ-05, GW-12)
- components for requests/responses; CallRequest no longer inlined
per-path (PRJ-14); all library expect() paths removed (PRJ-11)
- error projections folded into BTreeMaps: same registry =>
byte-identical doc, sorted enums (PRJ-12)
- components.securitySchemes.bearerAuth + top-level security (PRJ-15)
- info.version 1.1.0 -> 1.2.0 (ADR-045 minor: additive documentation of
the settled runtime contract)
- 31 unit tests incl. golden print-level assertions mirroring the routes
tests' actual bodies and a determinism test
Verification: cargo test (288), clippy --all-targets -D warnings, fmt
--check, cargo doc --no-deps, cargo test --all-features (all green in a
clean worktree at HEAD; shared tree carries parallel agents' edits).
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.
Ported the alknet-http overlay verification to the channels-over-WS
session (tests/ws_overlay_ops.rs, test-support feature, 8 tests):
- overlay mechanism: browser-registered ops land in the connection's
Layer 2 overlay (register_imported), exposed via overlay_env() —
no PeerIds (browsers are not peers); PeerRef::Specific to a browser
id routes to nothing (NOT_FOUND)
- hub→browser call through compose_root_env's attached overlay
- AccessControl on browser ops gates hub calls (scope match allows,
missing scope FORBIDDEN)
- overlay dies with the connection; no leak between connections;
in-flight calls to browser ops resolve on close
- wire-level: 10 interleaved concurrent calls across two WS sessions
— no cross-correlation, no deadlock; disconnect mid-call resolves
and a fresh session works (no listener wedge)
byte_adapter: read_eof Notify now gated to the wss feature (its only
consumer is from_wss) so a test-support-only build is warning-free.
Verified: cargo test (182 lib), --all-features (227 lib + 5 MCP + 8
overlay + 10 WS integration), clippy -D warnings (default,
test-support, all-features), fmt.