- version bump 0.4.1 -> 0.5.0 (minor per the dep-wave convention:
0.3.0 rode alkcall 0.6.0, 0.4.0 rode alkcall 0.7.0)
- README usage snippet 0.4.1 -> 0.5.0
- changelog: cut [0.5.0] with the alkcall 0.8.0 consumption under
Changed — the additive-only audit (reply fields, relay/hub-leg,
flavor-form discovery) and the one ride-through discovery change
(explicit channel_open_alpn for non-standard open-op names)
Verification: cargo test/clippy/fmt/doc green in the preceding deps
commit; no source change in this commit
- bump alkcall 0.7 -> 0.8 (gateway feature unchanged). No code change
forced: alkcall 0.8.0 is additive on the call-plane JSON and the
registry seams (review 008's remediation — the establisher reply
projection, flavor-form open-op ids in discovery, the in-tree
ChannelRelay + hub-leg install template) and none of the new
surfaces are constructed in this crate: no Establishment reply
fields (the WS ferry passes Option<OpenEstablisher> through
unchanged), no marked specs imported through from_call (the from_wss
filter excludes the protocol-session ops), no ChannelRelay /
HubLegImports / HubLegTemplate consumption (hub-leg assembly stays
at the assembly layer)
- one ride-through on this crate's discovery surface: a deployment's
with_ws_openable_alpns open-op specs with non-standard names now
serve an explicit channel_open_alpn string through services/list +
services/schema (alkcall 0.8.0 U-1, ADR-047 amendment 3) so hubs
relaying via from_call reconstruct the marker; standard-shape names
stay byte-stable
Verification: cargo test 456 pass (default) / 589 (all-features),
clippy --all-targets -D warnings clean (default + all-features),
fmt --check clean, doc --no-deps clean
- version bump 0.4.0 -> 0.4.1 (patch: the MSRV raise is a metadata
change that breaks no downstream able to build the crate before)
- README usage snippet 0.4 -> 0.4.1
- changelog: cut [0.4.1] with the alkcall 0.7.1 consumption + the
rust-version 1.85 -> 1.88 floor raise under Changed
Verification: cargo publish --dry-run --allow-dirty (cargo test/clippy/
fmt green in the preceding deps commit)
- the 1.85 claim was already false at the dependency level: the resolved
lockfile pulls icu_* 2.x (MSRV 1.86 via jsonschema -> idna) and, since
alkcall 0.7.1, alkcall itself declares 1.88 — 1.85 toolchains cannot
build the tree regardless of what Cargo.toml declared
- raising the floor to 1.88 aligns with the ecosystem MSRV decision
(noq QUIC path, per the alknet MSRV audit; recorded for alktls in
OQ-TLS-08) and breaks no downstream that could build the crate before
- alkcall 0.7.1 rides in via the lockfile (version = "0.7" already
covered it); no code change forced — 0.7.1 is its own MSRV raise plus
the cf-006/cf-007 work consumed in the 0.7.0 wave
- one 1.88 clippy lint fixed: uninlined_format_args in a forward.rs test
Verification: 1.85 check fails as documented (icu_* 1.88 floors); 1.88
toolchain check/test/clippy --all-targets -D warnings clean; stable 1.94
test 456 (default) / 589 (all-features), clippy -D warnings (default +
all-features), fmt --check, doc --no-deps
- version bump 0.3.0 -> 0.4.0 (the CONNECTION_CLOSED -> 503 gateway
mapping is a wire-visible behavior change for HTTP callers matching
on the previously-emitted 500 — a minor bump, not a patch)
- README usage snippet 0.3 -> 0.4
- changelog: cut [0.4.0] with the alkcall 0.7.0 wave entry
(CF-005/006/007, no code change forced) + the CONNECTION_CLOSED
mapping under Changed (wire-surface behavior)
Verification: cargo test (456 passed), cargo test --all-features
(589 passed), clippy -D warnings (default + all-features), fmt
--check, cargo publish --dry-run --allow-dirty
Dependency wave + one wire-surface behavior fix:
- bump alkcall 0.6 -> 0.7 (gateway feature unchanged). No code change
forced: this crate constructs no ServingConfig (the one 0.7.0
struct-literal break — from_wss keeps the pure-consumer
from_connection; opt-in serving remains the review-006 Part C
deferred decision), and the CF-006 per-call opener-identity overlay
is behavior-only upstream (hub-forwarded WS-ferry opens now resolve
the end client's identity, the intended effect)
- gateway error table maps CONNECTION_CLOSED -> 503 Service
Unavailable: alkcall 0.7.0's CF-007 amendment makes it the eighth
protocol code (ADR-016's list is formal at eight); it previously
fell to the catch-all 500, misreporting a retryable,
provably-undelivered call as a permanent server failure. retryable:
true, so the retry_after details path applies on 503. Tests pin the
mapping and the Retry-After passthrough
- ADR-023 amended: the protocol-code list is eight (ALREADY_EXISTS +
CONNECTION_CLOSED rows added to the §3 table, normative rule and
call.error code list updated); http-adapters.md six-code references
de-staled
Verification: cargo test (456 passed), cargo test --all-features
(589 passed), clippy -D warnings (default + all-features), fmt
--check, cargo doc --no-deps (0 warnings)
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
AGENTS.md, lib.rs, the README, and adapters/mod.rs docs all state
openapi is the shared spec model implied by both feature sides, and
the README's client row claims from_openapi under client — but the
feature graph only had server -> openapi. A lean client-only build
compiled yet silently lacked from_openapi (gated all(client,
openapi)).
Verification: cargo test (default + all-features), clippy both
feature sets -D warnings, fmt, publish --dry-run — all green.
The [Unreleased] section still led with the alkcall 0.2 bump; the
0.3 (per-session fork, op/register, UP-03), 0.4.0 (registry
input_schema enforcement), and 0.4.1 (early-arrival chunk parking)
waves were unrecorded. Also removes a copy-paste duplication in the
CF-003 entry.
Verification: cargo test, clippy (both feature sets), fmt, doc,
publish --dry-run — all green.
alkcall 0.4.0 now enforces OperationSpec.input_schema at dispatch
(INVALID_INPUT). All 438+16 tests pass; the gateway path is
unaffected because alkhttp's imported ops validate through their own
CompiledInputSchema closure, which composes with the new registry
check (registry = raw JSON Schema semantics, adapter = closed by
default).
cargo doc --no-deps was not actually clean before review 007's
remediation — it emitted 5 warnings that went uncounted (grep for
"^error|^warning" in the gate runs matched only on error/warning at
line start, and the review baselines recorded "clean" without a
counting check; the warnings were first noticed while re-verifying
the review-007 gate set). All pre-existing, none introduced by
review 007:
- src/adapters/mod.rs: the bare [`OpenAPISpec`] intra-doc link did
not resolve (the re-export sits in the same module, but rustdoc
resolves module-level links against the module's own scope) —
spelled out as `openapi_spec::OpenAPISpec`
- src/websocket/mod.rs: [`upgrade`] did not resolve from the
`websocket` module doc (the submodule is not in scope for the
parent's doc links) — plain `upgrade`
- src/websocket/upgrade.rs:215: [`CallConnection`] had no import in
scope — spelled out via the full alkcall path
- src/websocket/upgrade.rs:291: [`install_channel_zero`] linked a
private item from public docs — plain `install_channel_zero` hook
- src/server/adapter.rs:274: [`crate::websocket::OpenHandler`] is
not re-exported from `websocket` — spelled out via the alkcall
path
Verification: cargo doc --no-deps 0 warnings (was 5); cargo test 454
passed / 0 failed; cargo test --all-features 587 passed / 0 failed;
clippy (both configs) clean; fmt clean.
Review: docs/reviews/007-ws-data-channel-surface-review.md (the
baseline's "cargo doc → clean" claim was wrong on this dimension)
- WS-31: websocket.md §"Data channels for browsers" + ADR-067's landed
note record that WS-session discovery is the bootstrap set — the
hook's bootstrap `services/*` registrations overwrite a
base-registry `services/*` registration on the WS path by design
(a deployment's custom `services/list` is shadowed on WS sessions
only).
- ADR-048's landed note: correction + completion — the WS-26
retention sentence was aspirational at the landed commit (WS-28) and
is now real; the UP-02 posture's override half is now an explicit
surface (`with_ws_op_register_acl` / `OpRegisterAcl`), with the
note that `ChannelsPolicy` could not carry an op ACL.
- ADR-067's landed note: review-007 notes (WS-28 fix + gate, WS-29
surface, WS-31 record).
- OQ-05 resolution: the retention claim carries the WS-28 correction.
- review-006 UP-02 log + WS-26 paragraph: corrections marking what the
pre-fix tree did not have, with the landed remediation named.
- review-002 WS-17: the "bounded at 64 sessions" claim corrected —
the bare-registry semaphore was per-request and bounded nothing;
`SessionSlots` is the shared-cap surface.
- review-007 status: open for remediation → remediated, with the
decisions taken (both "implement" options) and the gate names.
Verification: cargo test 454 passed / 0 failed; cargo doc --no-deps
clean (6 pre-existing warnings, identical at baseline).
Review: docs/reviews/007-ws-data-channel-surface-review.md
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)
First review whose subject is the landed wiring itself (review 006
Units 2-4, 030c5ef/2053420/64fa10b), not the gap to it.
Findings:
- WS-28 [major] — the WS-26 ConnectionGuard is bound inside the
`if let` block and drops when it ends, not when the channel-0 task
ends: live_connections() is permanently empty for every session.
Reproduced empirically through the live WS path (scratch test,
run + removed). ADR-048's landed note and review-006's log record
behavior the code does not have.
- WS-29 [minor] — the op/register ACL override recorded as landed
(review-006 UP-02, ADR-048) is not implemented; the hook hardcodes
AccessControl::default() and ChannelsPolicy cannot express an op ACL.
- WS-30 [minor] — the bare-registry default session cap is built per
request (FromRef) and bounds nothing; corrects review-002 WS-17.
- WS-31 [minor] — install_bootstrap_discovery silently shadows a
deployment's own services/list on WS sessions (upstream-mandated,
unrecorded).
- WS-32 [minor] — the router-state openables threading
(with_ws_openable_alpns) has no gate; every Unit-3 gate rides the
extension fallback.
Plus non-findings bounding the re-review (UP-01 arm, policy threading,
from_wss exclusion set, extension precedence, gate fidelity) and a
sequenced remediation plan.
Baseline at 64fa10b: 454 / 582 tests, clippy both, fmt, doc — all clean.
- OQ-05: deferred → resolved (2026-09-04, review 006 Unit 2+3); the
consumer-set reframe recorded (WS is also the native-client fallback
behind hostile NAT/firewall; OQ-04 does not block the wiring).
- ADR-067: status amendment + the v1-cut blockquote gains the Wired
(2026-09-04) note — per-session-fork shape, openable surface, gates.
- ADR-048: landed-state amendment — §4's hub→browser direction has its
object (op/register → connection overlay, hub composes via the
retained Arc<CallConnection>); the op/register ACL posture (UP-02,
SRV-10 precedent) recorded.
- websocket.md: the step-7 deferral note and the §"Data channels for
browsers" status block removed (the section now documents the landed
surface: with_ws_openable_alpns, the OpenableAlpns fallback, cap
policy, discovery, gates); idle-knob deployment note for silent data
channels (semantics unchanged; the 60 s default bites more often —
set None at assembly for long-lived interactive channels).
- Review 003 status → remediated (all findings closed; log in review
006); its Unit-4 section marked landed.
- alknet-ADR-044 §5 pointer checked: not stale.
Verification: cargo test 454 / 0; --all-features 582 / 0; clippy
(both) clean; fmt clean; doc clean.
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.
- Unit 2/Unit 3 recorded as landed (030c5ef): hook rework shape,
policy threading (the ChannelsPolicy seam the ledger gate caught),
WS-26 handle retention, from_wss protocol-op exclusion, UP-01, the
six new gates, and the harness hardening notes (request-id filter,
flat CallError payload shape).
- UP-02 decided: op/register serves per session with the default ACL
(SRV-10 posture); the visibility tension resolves as ACL-gate.
- UP-03 [minor, alkcall-side] filed: services/list-peers cannot list
peer-announced ops — PeerCompositeEnv overrides peer_ids but not
peer_operations (trait default Vec::new()), so the amendment's
peer-discovery promise does not resolve on the wire. Discovered by
the gates; fix lives in alkcall; alkhttp's gate asserts
overlay-landing + collision semantics instead and is not blocked.
- Unit 4 (docs reconciliation) remains open.
Verification unchanged: cargo test 454/582, clippy both sides, fmt,
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.
Records the review pass of alkcall 0.3.0 against this crate:
- WS-24 (dispatch-resolution mechanism) and WS-25 (client-side op
registration) verified resolved upstream — per-session fork (alkcall
ADR-047 §4 amendment #2) + op/register (alkcall ADR-022 amendment
2026-09-03 + 2026-09-04 collision sub-amendment); the G-01 serving-
loop concurrency rework lands here via the bump alone.
- One new finding: UP-01 [minor] — ALREADY_EXISTS (new 0.3 protocol
code) unmapped in the gateway error table (would 500); fix rides
Unit 2. UP-02 records the op/register ACL posture decision for
Unit 2.
- WS-20/21/22/26/27/23 re-verified still open, now unblocked; the
remediation plan re-sequences (Unit 2 wiring with the concrete
fork shape, Unit 3 gates + two upstream-mirroring additions,
Unit 4 docs).
Baseline gates at df86f89: cargo test 453/575, clippy both sides,
fmt, doc — all clean.
Commits the re-point edits left uncommitted in the working tree
(5b62307's follow-through; alkcall review 005 verified these lines at
that tree), updated to the post-review-005 state:
- ADR-048 reconciliation note gains the 2026-09-04 update: alkcall
review 005 remediated the landed mechanisms (serving-loop
concurrency G-01/G-02, op/register collision policy G-03, spec
round-trip G-04, bootstrap-list alignment G-05) and alkcall 0.3.0
shipped them; alkhttp now consumes 0.3. The ADR-022 collision
sub-amendment binds here: a peer-announced op never shadows the
serving side's own registrations — the WS session's op/register
handler gates on the session fork.
- OQ-05 resolution gains the same dated update and extends the
cross-references to alkcall reviews 004-005.
What remains here is still alkhttp-side wiring only (review 003
Unit 2).
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.
The alkcall decision work (WS-24 dispatch-resolution mechanism, WS-25
client-side op registration) now lives upstream as alkcall review 004.
Unit 1 records the leading candidates and their alkhttp consequences:
- WS-24 -> option (a), per-session base registry (the only
wire-proven dispatch shape; fork surface = alkcall F-02)
- WS-25 -> channel-0 bootstrap op (op/register as an assumed op in
the bootstrap set; requires the alkcall client serving half, F-04)
- discovery composition: services/list on the fork + peer-registered
ops via the already-built services/list-peers
Consumer-set reframe from WS-23 unchanged; no findings added or
removed.
Focused review of the OQ-05 deferral (review-001 WS-03): what is
missing to wire browser/native data channels over WS, the deferral
rationale check, and the design gaps found behind the 'nothing new
to design' assumption.
Findings (continuing review-002 numbering):
- WS-20..22 [major]: mechanical wiring gap — install_channel_zero
discards the ChannelManager, generic channel lifecycle ops never
registered, no openable-ALPN deployment surface
- WS-23 [minor]: no browser-opened-channel e2e test; OQ-05 reopen
trigger reframed (native WS consumers are not blocked on OQ-04)
- WS-24 [major, cross-crate]: top-level dispatch never consults the
connection overlay — the ADR-047 §4 amendment mechanism cannot
resolve open ops on the wire (needs an alkcall decision)
- WS-25 [major]: no wire mechanism for client-side op registration —
ADR-048 browser-side bidirectionality is undesignable as specified
- WS-26 [major]: no retained live connection handle; hub cannot
reach a session's overlay or push toward it
- WS-27 [minor]: dead-code-gated test hook cleanup
Includes a non-findings section (channel-id split, demux hardening,
open-op wrapper completeness, idle-knob posture) and a 4-unit
remediation plan sequenced so the alkcall decision task (Unit 1)
gates the alkhttp wiring shape (Unit 2).
Verification: cargo test (453) / --all-features (575), clippy
(all-targets, both feature sets), fmt — all clean at 58665f2.
- README covering the gateway surface, both import/export adapter
families, feature flags, and security posture (all claims verified
against the current code)
- standard MIT and Apache-2.0 license texts matching the
license = "MIT OR Apache-2.0" manifest field
- Cargo.toml: point the manifest at README.md and exclude scripts/
from the published package
Split the feature graph so consumers pulling only the import adapters
(from_openapi / from_jsonschema / from_mcp) no longer compile the axum
/ hyper server stack, and server-only deployments no longer compile
reqwest. One crate, one import path — sides cut by features, not by a
crate split.
Feature graph:
- server (default): axum host, gateway, WS upgrade, to_openapi, to_mcp
- client (default): client host, forward, from_jsonschema, from_openapi
- openapi: shared OpenAPISpec model (implied by both sides)
- mcp: from_mcp needs client, to_mcp needs server
- wss: tungstenite transport (from_wss); tungstenite half of the
shared WS↔byte-stream adapter
- h2/http1: hyper protocol features; imply server
Wire-contract neutral: gateway endpoints, ALPNs, and all public API
shapes unchanged; defaults keep both sides on.
Supporting changes:
- forward.rs drops its axum::body::Bytes type leak (bytes crate types)
- bounded_join + error-echo caps move to input_validation (usable by
both sides; openapi_spec no longer imports from forward)
- byte_adapter: axum flavor compiles under server, tungstenite under
wss; the generic pumps stay shared (WS-11)
- input_validation / openapi_spec import-only internals gated to the
side that consumes them
- http-body-util moves to dev-dependencies (was test-only)
- integration-test required-features updated for the new sides
- from_wss unit tests (axum producer harness) gated to server
Verified: cargo test (defaults, 453) and --all-features (575) pass;
lean side builds (client / server / client,mcp / client,wss /
server,wss / openapi-only) build clean with zero warnings;
clippy -D warnings clean across all feature combinations; fmt 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.
Decision: advertise == enforce. The key allowlist (OAI-02) stays as the
first gate with its established unknown-key message; a compiled leaf
validator now runs second, so required/type/enum/pattern/bounds
violations surface as INVALID_INPUT 422 naming the keyword — not as
upstream round-trips.
- new src/adapters/input_validation.rs: CompiledInputSchema compiles an
op's input_schema once at import with the jsonschema crate (same
2020-12 dialect publish_schema uses) and validates peer input at call
time; the compile-time copy is hardened closed-by-default
(additionalProperties: false injected when absent) so the validator
reproduces the allowlist's unknown-key semantics; explicit
additionalProperties:true catch-all and schema values are preserved;
the original spec value is never mutated
- from_openapi/from_jsonschema import(): compile per registration,
capture the validator in the handler closure (re-import recompiles —
the closure capture is the invalidation story); a non-compilable
input schema fails import loudly (AdapterError::SchemaParse naming
the operation), matching the publish_schema fail-closed precedent
- from_openapi generated input schemas now carry
additionalProperties:false explicitly, so the /schema advert states
the enforced rule and external schema-driven validators reach the
same verdicts
- forward/forward_stream/build_request gain an
Option<&CompiledInputSchema> parameter; enforcement runs after the
allowlist
- round-trip test (review 002 Test-gap 10): the /schema-exported
input_schema is compiled with the same validator and driven against
build_request over a 10-input violation matrix — accept-sets exactly
equal in both directions; the chain-test that lets advertise/enforce
drift surface as a CI failure
- ADR-066: new decision section (advertise==enforce) with the trust-
boundary reasoning and the rejected option (b) rationale
- module + enforce_input_schema docs updated to the two-gate shape
cargo test --all-features 596 pass; clippy --all-features/-D warnings,
fmt, doc gates clean.
docs(tasks): mark review-002-fu-oai18-decision done
- 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
- dispatch.rs: qualify [invoke]/[invoke_streaming] as
[GatewayDispatch::invoke]/[::invoke_streaming] (module-level docs
need the type-qualified path)
- byte_adapter.rs: drop redundant explicit link targets for
DEFAULT_WS_WRITE_TIMEOUT / DEFAULT_WS_IDLE_TIMEOUT (already
re-exported at crate::websocket)
- from_wss.rs, byte_adapter.rs: backtick WssSession::drop and the
private WsFraming (all-features-only link errors, same class)
- Ws pump/timeout module docs spot-checked against WS-13/18/17 impls:
no drift found
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps now exits clean both
default-features and --all-features.
docs(tasks): mark review-002-fu-doc-warnings done
The bracketed planning task's five candidates, re-validated against
the post-bulk tree (0a932e5, all 23 bulk tasks done, 446/568 green):
1. OAI-18 -> review-002-fu-oai18-decision: still key-allowlist-only;
bulk blockers (yaml/path-item) both landed. Task records the
enforce-vs-scope-the-advert decision framework from review 002.
2. CON-08/CON-09 close() lever: deferred still (documented v1
contracts unchanged, no consumer pressure) — not tasked.
3. Cross-crate: CF-001..004 all open in alkcall, no alkhttp-side task;
PRJ-16 guard already documents its defense-in-depth re-scope
disposition (no work when CF-004 lands).
4. Stale-comment sweep: mostly absorbed by the bulk (SRV-11 comment
rewritten in-fix); residue = 3 cargo-doc warnings + WsTimeouts
redundant Default -> review-002-fu-doc-warnings.
5. ADR-045 version audit: clean (1.4.0 + annotations + test pin).
Post-bulk coverage re-pass (95.89% regions, bulk's new code fully
covered: PRJ-16 guard, body cap, router reorder, batch cap, WS
knobs, OAI-11 node budget all exercised) surfaced one unanticipated
residue -> review-002-fu-stream-error-coverage (forward_stream
build-error arm never exercised on a Sub op; PEM read-failure arms;
over-cap poll_write; SSE split-CRLF edge; from_value structural
rejects; WsTimeouts Default).
Bracketed pass itself marked completed (planning consumed).
taskgraph: 69 valid, no cycles; 3 pending.
The OAI-06 matrix lived only in the completed review-001 task notes;
acceptance for the review-002 loudness cluster requires the successor
doc section. New 'Loud unsupported-feature handling' section on
from_openapi: refused/warned/projected feature tables covering cookie
params, style/explode forms, servers, webhooks, callbacks, security,
oneOf requestBodies, unresolvable or content-less requestBody refs,
path-template validation, collision rejection, ref-sibling warns,
discriminator/xml warns, error-projection mappings, and the OAI-17
error-bounding contract.
build_request inserts header params first, then default_headers, then
credential headers — HeaderMap::insert replaces, so a declared
in: header parameter whose name matched a default or credential header
silently never delivered the peer's value upstream.
check_header_param_collisions runs at import (the assembly's
HttpServiceConfig is visible there): Authorization on an authed
namespace is rejected outright, a default_headers name match (compared
case-insensitively) fails import naming both keys, and an ApiKey
header_name match fails naming the credential header. Per the task's
decision note: the import-time surface does see the adapter's config,
so the loud point stays at import rather than first-call warn-once.
Tests: Authorization+Bearer, X-Tenant/x-tenant case-insensitive
default_headers, x-api-key/X-API-Key, and the no-collision clean path.
Internal/ACL-restricted op specs were readable through
POST /call {"operation":"services/schema","input":{"name":...}}
(the MCP call/batch tools identically): the outer-name pre-checks pass
(services/schema is External) and alkcall's services_schema_handler
projects any registered spec with no visibility/ACL check of its own
(alkcall CF-004 is the complete fix there).
- GatewayDispatch.invoke/invoke_streaming now apply the GET /schema
route's is-internal + access-control checks to the meta-op's inner
name input before dispatch (404 Internal / FORBIDDEN ACL), one
interception point covering /call, /batch, /subscribe and the MCP
call/batch tools; /publish cannot reach the Query-typed meta-op
- the visibility+ACL check is one shared fn (schema_disclosure_denial)
used by the HTTP /schema route, the dispatch guard, and the MCP
schema tool, so transports cannot drift
- when CF-004 lands, this guard remains as defense-in-depth (ADR-071)
Tests: dispatch-spine guard unit tests; /call 404 + 401/403 matrix,
/batch NOT_FOUND entry, /subscribe error event; MCP call/batch tools
via services/schema with an Internal inner name (mcp feature).
Verify: cargo test (405), --all-features (523), clippy default and
--all-features --all-targets -D warnings, fmt --check — all pass.
- 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)
Import errors echoed unbounded spec-derived strings: a 100k-path
servers-override list produced a multi-megabyte SchemaParse message.
forward::bounded_join caps list echoes at 8 items / 128 chars per item
with a ', … (+N more)' suffix, and is applied at the servers/callbacks
/security location lists, the unbound-placeholder and unbound-remnant
lists in build_registration/forward, and the call-time declared-keys
echo. resolve_ref caps interpolated $ref strings at 128 chars.
Tests: 100k-path servers fixture asserts message < 4 KiB (linear,
completes fast); bounded_join unit test pins count+width truncation
with unchanged small-case output.
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.
The OAI-06 loudness matrix had holes: callbacks, security requirement
blocks, top-level oneOf requestBodies, and schema-level
discriminator/xml keywords all vanished silently at import.
- callbacks + security: OpenAPISpec::validate_import_loud_features
(new, run from FromOpenAPI::import) rejects with locations and
remediation. Scoped to the service-import path, not the shared
from_value parse — the published gateway doc round-trips through
from_value inside to_openapi and legitimately declares security
markers for its external clients.
- top-level oneOf requestBody (no content map): refused in
parse_operation as an unresolvable/content-less body (OAI-15 arm,
message names OAI-14 oneOf).
- discriminator/xml inside consumed schemas: per-operation
tracing::warn listing the ignored keys (JSON-forwarding-only stance).
Tests: op-level callbacks, doc-level + op-level security, oneOf
requestBody each fail import naming the feature and location.
- 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)
$ref siblings are ignored under OpenAPI 3.0 semantics but would apply
under 3.1 — a 3.1-authored constraint beside a $ref previously
vanished silently, overstating /schema. warn_ref_siblings now fires at
each $ref consumption point (operation parameters, requestBody,
path-item parameters) naming the location and dropped keys, and the
module doc records the version stance: no openapi 3.1 gate, 3.0
reading with the warn as the visibility mechanism.