- src/channels/gate2_tests.rs (ADR-051 gates 6/7, review 008 gate 2): the full consumer -> hub (HubLegTemplate) -> spoke relay e2e — the open resolves with the hub-allocated channel_id, `bound` survives the relay, data flows both directions with a fake 8-byte chunk header riding verbatim (the hub never parses the data plane, ADR-034/035), spoke-side close cascades to clean reclaim on both legs; hub-side disconnect (the consumer's duplex end dropped via a killable transport proxy) tears down both legs with the ledger decremented; the mid-establishment window (ADR-051 §6) pinned with its two reclaim signals — the consumer leg reclaims at its own transport EOF, the spoke channel (allocated before the establisher replied) is the honest residual, reclaimed when the spoke-leg transport ends; the channels/tty/sub standard-shape companion pins no derivation regression. - src/channels/relay.rs: the relay's adopted producer-leg channel entry now reclaims when the relayed pump completes (teardown after pump_bidi) — the consumer-leg wrapper's teardown cannot see the producer leg's manager; the RelayPlan carries the spoke id for the reclaim. - Release bookkeeping: 0.7.1 -> 0.8.0, the CHANGELOG entry covering Units 1-3 (Establishment reply projection, open_channel_with_reply, flavor-form discovery derivation, ChannelRelay, HubLegTemplate, gate-2 harness); review 008 Status -> Resolved with the U-1/U-2 commit refs and the 955->945 errata note; ADR-051 Status -> all units landed + the §6 mid-establishment residual expanded to the two-reclaim-signal shape the gate pins; the stale "0.7.2" version mentions in ADR-047/049 corrected to 0.8.0 (the units land unreleased). Verification: cargo test 669 passed / 0 failed; clippy --all-targets -- -D warnings clean; fmt --check clean; cargo doc --no-deps clean; cargo check + clippy on wasm32-unknown-unknown clean; cargo publish --dry-run --allow-dirty passed.
32 KiB
Changelog
All notable changes to this crate are documented here. The format is based on Keep a Changelog, and this crate adheres to Semantic Versioning.
[0.8.0] - 2026-09-18
Review 008's remediation lands in full — the graduation upstream asks
(U-1 flavor-form open-op ids in discovery; U-2 the establisher → reply
projection) plus the in-tree channel relay they demanded
(ChannelRelay, the hub-leg install template, and the review's gate-2
e2e harness). The channels data plane is untouched (ADR-034/035's
one-way doors stay closed); the new surfaces are additive on the
call-plane JSON and the registry seams.
Added
Establishmentreply projection (review 008 U-2, ADR-049 amendment 3) —Establishment::with_reply_field/with_reply_fields/reply_fields()(an optional map riding the typed-opaque plan carrier). The open-op wrapper merges the fields into the success output after reservingchannel_id— an establisher supplying the reserved key fails loudly (channel:open_failed, reasonhandler_error), never shadows. Absent fields leave the reply byte-identical to the pre-amendment shape.ChannelClient::open_channel_with_reply(review 008 U-2) — the full open-op success output (the extra reply fields, e.g. a bind-first listener'sbound) alongside the streams;open_channeldelegates with the fields discarded, its signature unchanged.- Flavor-form open-op ids in discovery (review 008 U-1, ADR-047
amendment 3) —
spec_to_json_pubemits an explicitchannel_open_alpnstring alongside the boolean marker when the op name is not a standardchannels/<seg>/(sub|pub)shape (standard shapes stay byte-stable);rebuild_spec_forprefers the explicit string and generalizes the derivation to strip the LAST path segment when the marker is present.channels/tunnel/direct-shaped ops now reconstruct WITH thechannel_openmarker through discovery — hubs relaying throughfrom_callnever see them as plain forwarding stubs. ChannelRelay(review 008 Unit 3a, ADR-042 as amended by ADR-051) — the in-tree relay component:ProducerLeg(Arc<CallConnection>+ producer-legChannelManager— not aChannelClient, whosetake_call_ connectiondetach is wrong for a hub with multiple connection claimants) andChannelRelay::register_relay_openable(registers a from_call-imported marked spec on a consumer-leg registry via the full open-op wrapper: ACL, per-identity cap/ledger, establishment bound, teardown). The translate hop calls the producer leg with the forwarded payload (forwarded_forfrom the consumer's per-call identity, ADR-026 §3) and adopts the spoke-allocated id; the byte-forward hop ispump_bidiawaited inline — the hub never parses chunk framing, ids are per-connection (no rewrite exists). The spoke reply'schannel_idis stripped (the consumer reply carries the hub-allocated id); the reply's other fields ride through (boundflows end-to-end, per-hop truthful). Reason mapping per ADR-051 §3 — the spoke's code and message are preserved (timeoutmaps todial_failed, the one non-1:1 row). The adopted producer-leg channel entry reclaims when the relayed pump completes. Rejection postures are loud: unmarked and Pub-typed marked specs fail registration (RelayError), never silent stubs.HubLegImports+HubLegTemplate(review 008 Unit 3b, ADR-051 §5) — the hub-leg install template as an in-tree export: theClonediscover/stash (from_call bundles split by the marker, with the per-consumerfiltered/onlysubset filter) and theinstall_channel_zerohook composing fork + generic channel ops + bootstrap discovery closed over the fork (review 004 F-06) + plain bundles as-is + relay openables + serving-identity resolution (CF-005 precedence) + the single-stream dispatch loop. A Pub-typed (or unregistrable) marked spec ends the leg's install task — channel 0 never dispatches. Discovered bootstrap-discovery op names are skipped on re-registration (the template's own install supersedes the imported copies).
Changed
- New module
src/channels/relay.rsandsrc/channels/hub_leg.rsre-exported fromchannels; the relay's teardown fix is part of this landing (no pre-relay behavior to preserve).
Verified
- 669 tests pass (
cargo test),clippy --all-targets -- -D warningsandfmt --checkclean. The gate-2 e2e harness (src/channels/gate2_tests.rs) pins the review's gate 2: the full consumer → hub → producer relay through the template (hub-allocated id,boundsurviving, data both directions with a fake 8-byte chunk header riding verbatim), hub-side disconnect tearing down both legs, the mid-establishment window's two reclaim signals (ADR-051 §6), and thechannels/tty/substandard-shape companion.
[0.7.1] - 2026-09-09
The MSRV floor becomes honest: rust-version raises from 1.85 to
1.88. The 1.85 claim was already false at the dependency level — the
resolved lockfile pulls icu 2.x (MSRV 1.86) and wasip2 1.87
transitively via jsonschema → idna, so no 1.85 toolchain could
build 0.7.0. The raise aligns with the ecosystem floor (noq's 1.88,
matching the QUIC path) and breaks no downstream that could build the
crate before. No API or wire-format change; downstreams on caret
requirements pick this up on their next cargo update.
Changed
rust-version1.85 → 1.88. Verified on a real 1.88 toolchain (full test suite, clippy). Two clippy lints promoted since 1.85 were fixed to keep-D warningsclean on the new floor:collapsible_else_if(channels::mux) anduninlined_format_args(core::types,registry::registration). Behavior-identical.
0.7.0 - 2026-09-07
The connect-side caller-identity seam (CF-005) plus the full sweep of every open alkcall finding from downstream consumers (CF-006, CF-007): a connect-side serving op can now authenticate the transport-authenticated peer by key-based identity (mTLS/QUIC), the open-op establisher/pump handler see the per-call opener identity, and the ADR-016 code list is current. The wire surface is unchanged.
Added
ServingConfig.identity: Option<Identity>(CF-005 remediation (a)) — an explicit caller identity for the connect-side serving dispatch (ChannelClient::from_connection_with_serving). Wins over the propagated transport identity; the payloadauth_tokenpath still takes precedence over both (ADR-017 §7). Semver-relevant at 0.x: struct literals must addidentity: None.core::auth::NoopIdentityProvider— a publicIdentityProviderthat resolves nothing; the identity-less posture for ACL-free serving and theServingConfig::default()provider (three private test copies of it existed across the crate).
Changed
- The transport identity propagates to channel 0 (CF-005
remediation (b)).
from_connection_with_servingcopies the transportConnection::identity()to the channel-0 connection viaset_identitybefore the serving loop starts, mirroring the accept side (the adapter hands the install hook the transportAuthContext). The connect-side serving dispatch now resolves the caller identity in precedence order: payloadauth_token→ServingConfig.identity_provider;ServingConfig.identity; transport identity. With none, the dispatch runs identity-less andAccessControl::checkfails closed (FORBIDDEN) — unchanged. The identity resolution is process-local (set_identityon the internal channel-0Connection); nothing new crosses the transport. - The open-op establisher and pump handler see the per-call opener
identity (CF-006 — the CF-005 corollary).
run_open_wrapperderives a per-callAuthContext— the opener's dispatch-resolved identity overlaid onto the install-time context — and passes it to both the establisher (ADR-049 §1) and the pump handler (OpenHandler). Identity-less calls keep the install-time identity; transport-truthful fields (alpn,remote_addr,tls_client_fingerprint) are never rewritten. Signatures are unchanged — behavior-only; on a per-connection registry (ADR-047 §4) this is identical to before, and on hub-forwarded opens the establisher now sees the end client instead of the hub. - ADR-016 amended: the protocol-code list is eight codes (CF-007 —
alkhttp review 006 Part C doc drift).
ALREADY_EXISTS(non-retryable,op/registercollision) andCONNECTION_CLOSED(retryable, provably-undelivered call) are now in the Context paragraph, the §3 table, and thefrom_openapicollision rule; §2a documents the undelivered-vs-ambiguous write-failure distinction. Doc-only. ChannelPlantype doc carries theSend + Syncpayload constraint (alktunnels POC F-1 — a non-Syncboxed handle needs a wrapper; documented so the next consumer doesn't re-derive it by compiler error).- Regression gates: the four
cf005_*tests insrc/channels/client.rs(transport propagation, override precedence, identity-less failure, token fallback + precedence) and the two open-op identity-overlay tests insrc/channels/operations.rs. Ledger: CF-005, CF-006, CF-007 → resolved (the ledger's Open section is now empty). Specs: ADR-022 §connect-side-serving, ADR-016 (code list), ADR-049 establisher doc note.
0.6.0 - 2026-09-07
The establishment follow-ups sweep (review 007): the Establishment
plan payload lands (R-01), the OpenHandler lifetime contract is
documented (R-02), and the two-pump helper is extracted (R-03). One
breaking change (below); the wire surface is unchanged.
Added
pump_bidi(review 007 R-03 / ADR-050). The two-pump helper —channels::pump_bidi(channel, peer_read, peer_write)— pumps a channel stream (BiStream-shaped) against a peer's split read/write halves: one pump per direction, each shutting down the opposite sink on completion (alknet ADR-078's shutdown-on- completion), joined, returning(u64, u64)copy counts. Errors are EOF-shaped by design (the POC + ADR-078 semantics — noErrstate; the review sketch'sio::Resultreturn was dead code). The helper pins the contract in one place instead of three (alktunnels POCpump_halves, alktty's channels session, the next consumer). Purely additive.
Changed
-
Establishmentcarries the channel plan (review 007 R-01 — breaking at 0.6.0). The reserved field is filled:Establishment { plan: Option<ChannelPlan> }withChannelPlan = Arc<dyn Any + Send + Sync>— typed-opaque, because the payload an establisher hands the pump handler is a live handle (a dialed socket, a TTY handle), not JSON. The wrapper threadsestablishment.planto theOpenHandler's new second parameter (Fn(Value, Option<ChannelPlan>, Connection, AuthContext) -> JoinHandle<()>);Nonewhen no establisher is registered or it returnedEstablishment::default(). Process-local: establisher → wrapper → handler; nothing new crosses the transport. This kills the side-channel handoff the alktunnels POC shipped (resource-keyed slot + poll loop) with its concurrent same-resource race — each open's establisher result flows to its own handler. Migration:Ok(Establishment {})→Ok(Establishment::default())(orEstablishment::new(handle)to deliver a handle); handler closures gain a_plan(orplan) parameter. -
OpenHandlerlifetime contract documented (review 007 R-02). Doc-only semantics note on theOpenHandlertype and the registration entry points: the returnedJoinHandlemust track the data-plane lifetime — the wrapper awaits it and its completion triggers channel teardown (drop of the demux sender = EOF to the handler's read half); a handler that returns before its pumps finish tears the channel down at birth (await pumps inline, never spawn-and-forget). Plus adebug!telemetry line inrun_open_wrapperwhen a handler exits without having accepted the channel'sBiStream(the birth-teardown hint).
0.5.0 - 2026-09-06
The channel-open establishment phase (ADR-049 — review 006 E-01 +
N-1). Additive establishment machinery; one breaking change (the
ChannelClient::open_channel error type, below).
Added
-
OperationSpec.description(review 006 E-02). AdditiveOption<String>op description (builder:with_description), disclosed byservices/listandservices/list-peers(local listings) when set, carried in theservices/schemawire shape (spec_to_json_pubemit /rebuild_spec_forparse — the field survivesfrom_calldiscovery andop/registerannouncement). Describes the op, not the produced resource set (ADR-047 §6 amendment: the live resource-enumeration half stays deferred, OQ-40). Absent on the wire when unset — additive for all consumers. -
Channel-open establishment phase (ADR-049 — review 006 E-01).
ChannelCore::register_openable_with_establisherregisters a per-ALPN open op with an establisher hook (OpenEstablisher): an awaited establishment phase — validate params semantically, dial the backend — bounded by the dispatch deadline or the registration's timeout override, elseESTABLISHMENT_TIMEOUT(10s; the earlier of the two). On establisher failure (error or deadline) the wrapper tears down the just-allocated channel (demux sender, opener-ledger take,policy.on_closeun-increment — the allocation and teardown balance) and replieschannel:open_failedwithdetails: { reason, message }, reason ∈dial_failed/unknown_resource/resource_shortage/handler_error/timeout. The SSH contract holds consumer-visibly: a failed open never returns achannel_id.register_openableis unchanged (no establisher = always-OK, existing registrations compile and behave identically); the establisher takes(input, auth)— the channel's yield-onceBiStreambelongs exclusively to the pump handler. Additive wire surface (new error-code string +detailsshape).
Changed
ChannelClient::open_channelreturns a typed error (ADR-049 §4 — review 006 N-1, breaking at 0.5.0). The open op'sCallErroris carried verbatim inChannelOpenError::CallFailedinstead of being flattened into a debug-formatted string, so consumers branch onchannel:open_failed's typed reason (establishment_reason()). Other variants:MissingChannelId(malformed success reply),AdoptFailed(local adoption failure). Mechanical for consumers — theStringwas a debug-formatting wrapper.
0.4.1 - 2026-09-05
Bug-fix release: chunks arriving for a not-yet-adopted channel are parked instead of dropped (the open-op response / first-data race). No API changes.
Fixed
- Early-arrival chunks for un-adopted channels are parked, not
dropped. The connect side adopts a channel (installs local routing
state) only after the open-op response arrives, but the accept side's
OpenHandlercan start pumping data the moment the channel opens — the two race. Previously the connect side's demux dropped those chunks (REQ-CH-04's lenient unknown-channel drop), silently losing the first chunks of any push-first producer (a TTY backend's banner or greeting, a sub protocol's initial frame).route_payloadnow parks up to 64 payloads per unknownchannel_idin a bounded early-arrival buffer andadopt_channeldrains them into the new receiver in order; chunks beyond the cap drop with the pre-existing debug log anddropped_unknown_chunkscounter (the counter now means "early-arrival overflow or genuinely unknown channel", not just the latter).clear_alldrops the parked buffers with the connection. Surfaced by alktty's consumer end-to-end test (review #001 L3): the session never resolved because the producer's first chunks (the stdout sentinel + exit chunk for an immediately-resolving backend) arrived before the adopt and were dropped.
0.4.0 - 2026-09-05
input_schema is now enforced at call time (the advertise-vs-enforce
leg ADR-016 promised: INVALID_INPUT is the registry's
schema-mismatch error code). Minor bump — behavioral break for any
caller that was passing schema-violating inputs to ops declaring a
non-trivial input_schema and relying on the validation being
documentation-only.
Changed
OperationSpec.input_schemais enforced by the registry on every dispatch. Until now the schema was advertise-only:services/schemadisclosed it, the wire and gateway paths disclosed it, but no dispatch entry point consulted it (the only schema alkcall actually enforced waspublish_schemaper-chunk on Pub ops, P-03). The schema now compiles once at registration (same fail-closed rule aspublish_schema/CF-003: an un-compilable schema is a registration error, never a silently-skipped contract) and is checked after the ACL gate in all three dispatch entry points —invoke,invoke_streaming, andinvoke_sink(viaresolve_sink_handler, preserving the P-08 single-source-of-truth property). Violations returnCallError::invalid_input("input failed input_schema validation")with the input echoed indetails. Raw-JSON-Schema semantics (permissive on unknown keys) — adapters that want closed-by-default enforcement keep doing their own hardening, as alkhttp'sCompiledInputSchemaalready does; the two checks compose. Built-in ops are unaffected: alkcall's own specs declare the empty object schema (accepts anything) andservices/schemaalready returnedINVALID_INPUTfor a missingnamebefore its handler ran. Motivated by alktty's code review #001 (L1): the channels open-op wrapper hands the registry-checkedinputto theOpenHandleras the authoritative params, which requires the registry to actually validate it.OperationRegistrygained aninput_validatorcache (compiled at registration, mirrored onforkand inOperationRegistryBuilderlike the publish validators);input_validator()ispub(crate)— the public surface is unchanged.
0.3.1 - 2026-09-04
Bug-fix release: services/list-peers can now list peer-announced
ops. No API breaks — OperationEnv gains one defaulted trait method
(non-breaking for all implementors; cargo semver-checks 196 checks
pass against the 0.3.0 baseline).
Fixed
services/list-peersshows each peer's operations (UP-03, surfaced by alkhttp's review 006).PeerCompositeEnvoverrodepeer_idsonly, sopeer_operationsfell to the trait default (Vec::new()) and every peer listed with an empty operations array — the ADR-022 amendment's "announced op is discoverable viaservices/list-peers" promise never resolved on the wire. ADR-030 prescribed the fix but it had never been ported into alkcall; the existinglist-peersunit tests passed because they mockpeer_operationswith hand-rolled envs. Implemented per ADR-030:OperationEnv::list_operation_names(defaultVec::new())- overrides on
OverlayOperationEnv(overlay map keys),PeerCompositeEnv(peer_operationsdelegates to the peer's overlay; its own aggregate mirrorscontains(): session + connections + base),LocalOperationEnv(registry names), andChannelsSessionEnv(delegates to base). Gate:announced_op_is_discoverable_via_services_list_peersexercises the exactcompose_root_envshape (announce viaop/register, then assert both thepeer_operationsprobe and theservices/list-peerswire shape attribute the op to the peer) — verified load-bearing (reverting the override fails the gate). ADR-030 status is now Accepted with the UP-03 provenance note.
- overrides on
0.3.0 - 2026-09-04
The connect side can serve (two-way ops over one channels connection),
per-session fork registries, and the op/register bootstrap op — the
remediation of reviews 004 and 005
(docs/reviews/004-*.md, docs/reviews/005-*.md). Two
OperationRegistry methods changed their return types from borrowed to
owned (source-breaking for annotated call sites; minor bump per 0.x
semver rules) — verified with cargo semver-checks (196 checks pass
against the published 0.2.0 baseline; the return-type changes were
caught by manual diff review, as the tool has no lint for that
pattern).
Added
OperationRegistry::fork— deep-copies a registry (handlers, provenance, composition authority, capabilities, cached publish-schema validators) into an independently-mutable copy. The per-session shape: fork the deployment's base registry, register the session's ops on the fork, dispatch the session over the fork (ADR-047 §4 amendment, 2026-09-03). Internally mutable, so a fork shared as anArccan receive registrations after the dispatcher was built —install_bootstrap_discoveryrelies on this.OperationRegistryBuilder::from_registry— seeds a builder from an existing registry's registrations (the fork surface expressed through the builder; registration order is not preserved — HashMap iteration).- Connect-side serving (two-way ops over one connection). The
channels connect side's channel-0 read pump previously resolved
outbound responses only and silently dropped inbound
call.requestedframes. NewChannelClient::from_connection_with_servingtakesOption<ServingConfig>(registry+identity_provider); withSome(..)the read pump becomes the full-duplex serving loop (Dispatcher::serve_single_stream), so a connected peer can call the consumer's ops.from_connectionkeeps the resolution-only pump (pure-consumer default). Serving is opt-in: the protocol is symmetric, the API is explicit (ADR-022 amendment, 2026-09-03). - **
Dispatcher::serve_single_streamandDispatcher::dispatch_startStartedDispatch** — the shared serving-loop machinery. Both single-stream loops (accept side and connect side) now run the same concurrency model: Once invocations, Sub pumps, and sink response writers are spawned, so same-connection nested composition resolves concurrently instead of deadlocking until the 30 s sweeper (review 005 G-01). Handles are tracked and aborted at loop exit; no lock is held across an await.
registry::op_registermodule — theop/registerbootstrap op (review 004 F-05). A connected peer announces the ops it serves over the wire:OP_REGISTER_NAME(op/register),OpRegisterRequest(wire round-trip viato_json/from_json),op_register_spec/op_register_handler. Announcements land in the connection overlay and are served through forwarding handlers, so hub→consumer import (from_call) and consumer→hub announce are symmetric. Collision policy (review 005 G-03): a peer-announced op may replace other peer-announced ops (whenreplaceis set) but never the serving side's own registrations — a name present on the serving registry rejects with the newCallError::already_exists(ALREADY_EXISTS, non-retryable), regardless ofreplace. The collision gate checks the serving registry, the session fork, and the connection overlay itself.install_bootstrap_discovery(review 004 F-06) — registersservices/list,services/list-peers, andservices/schemaagainst a registry with handlers closed over that sameArc, so per-session forks are the discovery source for their own openables (handlers see every op the registry serves at call time). ACL filtering stays per-caller. The bootstrap-op set on channel 0 is closed and now includesservices/list-peers(review 005 G-05 — doc alignment; the code had installed it since the amendment landed) plusop/register(ADR-022 amendment).spec_to_json_pub— public serialization of anOperationSpecinto theservices/schemawire shape (the shapeop/registerannounces with andservices/schemaserves).resource_id_pathnow survives the spec wire round-trip (review 005 G-04): both the serializer and theop/registerparser carry the field.CallConnection::overlay_contains/CallConnection::overlay_registration— read access to the connection overlay, forop/registerreplace semantics and composition reachability checks.
Changed
OperationRegistry::registrationreturnsOption<HandlerRegistration>(wasOption<&HandlerRegistration>) andlist_operationsreturnsVec<OperationSpec>(wasVec<&OperationSpec>). Source-breaking for call sites that annotate the borrowed types — clone-at-the-boundary instead. Minor bump per 0.x semver rules.- Registration is
&selfthroughout —OperationRegistry::register,ChannelOperations::register_on, andChannelCore::register_openabletake&OperationRegistry(was&mut). Source-compatible for callers (reborrow); this is what makes post-construction registration on a forked,Arc-shared registry possible. from_callcomposition reachability — the forwarding-handler path declares reachable namespaces on the composing handler'sscoped_env(empty is deny-by-default) and the connection overlay is attached only for identity-carrying connections (ADR-030 §5).
0.2.0 - 2026-08-31
Consumer-findings remediation (CF-001..004 from
docs/reviews/consumer-findings-ledger.md — alkhttp as the first real
consumer), the promoted gateway dispatch spine (ADR-048), and a
docs-hygiene pass. One behavior change noted below; otherwise additive.
Added
gatewayfeature: the transport-neutral dispatch spine (ADR-048). Newalkcall::gatewaymodule behind the opt-ingatewaycargo feature (default off; adds no dependencies).GatewayDispatchis the deadline-bounded, re-rooted-context invoke spine overOperationRegistry(invoke/invoke_streaming/invoke_sink) promoted from alkhttp's gateway after it proved transport-agnostic — hubs and spokes relaying calls (ADR-042 translate path) need the identical root-context discipline (internal: false,forwarded_for: None) without any HTTP.schema_disclosure_denialis the shared is-internal + ACL check forservices/schemainner-op-name disclosure (one implementation so transports cannot drift; ACL denial returnsFORBIDDEN, Internal visibility returns spec-404 — see ADR-048 for the split from the wire handler's conservative spec-404). The handler deadline is a constructor knob (with_deadline); the default remains 30 s. alkhttp migrates to this module in a follow-up and drops its local copy.
Changed
- Un-compilable
publish_schemavalues are rejected at registration time (CF-003).OperationRegistry::registerand everyOperationRegistryBuildermethod now returnErrwhen a Pub op'spublish_schemafails to compile. Previously the bad schema was accepted and the dispatch path failed open (warn log, chunks unvalidated). The compiled validator is now cached per-op (OperationRegistry::publish_validator) and consumed by the dispatch path — the per-request compile is gone. Code that registered un-compilable schemas will now get a registration error instead of a silently-unvalidated op.
Fixed
services/schemano longer discloses Internal or ACL-restricted op specs (CF-004). The schema handler now applies the same gates asinvoke()— Internal-visibility rejection andAccessControl::checkwith matching identity resolution. Restricted ops return spec-404 (NOT_FOUND), consistent with "restricted ops don't exist" elsewhere; no information leaks about the restricted surface. The gate is inside the handler, so every transport is covered.- Demux
TooLargeskip no longer allocates from the peer's header (CF-002). The skip now streams through a fixed 64 KiB buffer, and a cumulative 256 MiB skipped-bytes budget tears down connections that loop oversized headers. Previously the buffer was sized from the untrustedu32length (up to ~4 GiB pinned per dribbling peer). - Write failures before request delivery are retryable (CF-001).
New
CallError::connection_closed(CONNECTION_CLOSED,retryable: true) is returned when thecall.requestedframe write fails on any consumer path (call/subscribe/publish, both stream modes) — the call provably never reached the producer, so reconnect/retry is safe. Mid-publish write failures and producer-side connection-teardown failures remain non-retryableINTERNAL(delivery ambiguous). The new code string is additive; theretryableflag is the machine-readable signal for consumers.
Fixed (continued)
- Doc hygiene. Resolved the broken intra-doc link on
lib.rs's feature-gatedgatewaymention (rustdoc warned on default-feature builds), and re-pointed 40src/doc references from the old alknet mono-repo ADR numbering (ADR-049/050/052/065/070/074/092) to this crate's numbering (ADR-021/011/034/007/008/009/005) so published docs reference ADRs this crate actually carries. Failure-path coverage was added alongside: the CF-002 skipped-bytes teardown and skip-EOF arms, CF-001's subscribe write-failure sites (both stream modes), the publish request-frame failure in single-stream mode, and non-retryability pinning for mid-publish failures.
0.1.1 - 2026-08-17
A minor release that renames the ALPN prefix from alknet/ to alk/
and drops the alktype dependency. No public API changes — verified
with cargo semver-checks (196 checks pass, no semver update
required).
Changed
- ALPN prefix renamed. All ALPN strings changed from
alknet/<name>toalk/<name>:alknet/call→alk/call,alknet/channels→alk/channels. The rename landed before the first published consumer (alktty), so no interop break exists.CHANNELS_ALPNis nowb"alk/channels";CallAdapter::alpn()returnsb"alk/call";derive_alpn_from_op_namederivesalk/<alpn>fromchannels/<alpn>/(sub|pub)op names. ADR-004 amended with the rename rationale. See Review 003. alktypedependency dropped. The only usage (alktype::validation::build_validator) was a thin wrapper overjsonschema::options().build(); the call site now usesjsonschemadirectly. The crate is leaner — noalktype(and no transitiveindexmap/hashbrown 0.17) in the dependency tree.tokiofeatures slimmed.features = ["full"]replaced with["rt", "sync", "time", "io-util", "macros"]— the crate uses no net/fs/process features. This unblockswasm32-unknown-unknown.- WASM support. The crate now compiles for
wasm32-unknown-unknown(verified withcargo checkandcargo clippyon the wasm target).uuidgained therng-getrandomfeature andgetrandom 0.4thewasm_jsfeature so request-ID generation works on wasm.
Added
- BAST document for the chunk header.
docs/architecture/chunk-header.bast.jsonis the machine-readable wire-format spec for the channels 8-byte chunk header ([channel_id: u32 BE][length: u32 BE]). BAST is plain JSON — consumable by any language; thealktypecrate compiles it into readers/writers/validators, and future codegen derives language-specific implementations. Embedded in the crate aschannels::wire::CHUNK_HEADER_BASTso downstream Rust crates can consume it without a file lookup. The hand-rolledparse_header/write_headerfunctions remain the hot path; the BAST document is the contract.
0.1.0 - 2026-08-14
Initial crates.io release. The unification of alknet-call (structured
JSON RPC: operations, streaming subscriptions, service discovery) and
alknet-channels (multiplexing proxy: N logical channels over one
transport stream, channel 0 pre-negotiated as the call protocol).
Vendored core types (Connection, ProtocolHandler, BiStream,
BidiStreamSource, AuthContext, IdentityProvider, Capabilities,
OwnershipProvider), the Pub operation type / HandlerKind::Sink
(ADR-046), the channels protocol with openable-ALPNs-as-operations
(ADR-047), and the ChannelClient transport-agnostic client.