Files
alktunnels/CHANGELOG.md
T
glm-5.3-flash d72820ddc2 release: v0.5.0 — the correlation gate (forwarded-op establishment phase)
- Cargo.toml: 0.4.1 -> 0.5.0 (minor, not patch: the release adds
  public API — ForwardedGate, ForwardedPlan,
  register_tunnel_forwarded_openable_with_establisher — additive only;
  the base registration stays byte-for-byte, the wire delta is empty)
- CHANGELOG: Unreleased cut to 0.5.0 (the establisher-variant entry +
  the release paragraph; the compare links updated)
- README: the forwarded-op serving snippet shows the recommended gate
  shape (register_tunnel_forwarded_openable_with_establisher) beside
  the base one

Review 003 W1 absorbed in-repo; W2/W3 are the alkssh/alksocks repos'
adoption waves. Verification: cargo test (default + --all-features),
fuzz corpus replay (fuzz/shared), clippy --all-targets -D warnings
(native + wasm32), wasm32 check, fmt --check, cargo doc --no-deps,
taskgraph validate (33 tasks), cargo publish --dry-run --allow-dirty
all green
2026-11-03 17:37:18 +00:00

22 KiB
Raw Permalink Blame History

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.

Unreleased

0.5.0 - 2026-11-03

The correlation-gate release: the forwarded op's serving half gains an establishment phase (review 003 W1 absorbed in-repo, ADR-008 Amendment 2), prompted by alkssh's consumer input on the composed -R deployment's failure vocabulary. Additive public API only (the minor bump, not a patch); the wire delta is empty and the base registration stays byte-for-byte. The rest of the wave is doc-only: the em-dash prose sweep across repo docs, spec docs, doc comments, and task files (no code-path change), and review 003's doc trail closure (W2/W3 are the sibling repos' adoption waves and remain booked there).

Added

  • The forwarded-op establisher variant (register_tunnel_forwarded_openable_with_establisher + ForwardedGate + ForwardedPlan, ADR-008 Amendment 2): the serving side of channels/tunnel/forwarded gains an establishment phase: the assembly injects a correlation gate (ForwardedGate, a sync closure over the parsed TunnelForwardedParams) and the crate wraps it into the alkcall OpenEstablisher dance, so a stale/missing listen token fails the open as a typed channel:open_failed with details.reason == "unknown_resource" BEFORE any channel is born (no phantom channel, extending ADR-049's posture to the forwarded op's serving side; the wire vocabulary is unextended). An establisher parse failure is likewise typed (handler_error), an improvement over the base shape's success-then-silent-birth-teardown for malformed ingress. Gate order pinned: the registry runs the tunnel:forwarded ACL before the establisher (pinned by test with a gate-invocation witness). The base register_tunnel_forwarded_openable stays byte-for-byte compatible as the compatibility + residual-race-fallback posture (the narrow gate-pass→handler-run correlation-miss race keeps teardown-at-birth); the establisher variant is the recommended shape and new assemblies take it by default. The wire delta is empty. Five gate tests pin the typed miss/no-phantom/no-handler, the unchanged gate-pass serving flow (full byte round-trip), the residual-race reap, the scope-gate ordering, and the malformed-params delta.

0.4.1 - 2026-10-22

The dependency-patch release: the alkcall substrate bumped 0.10.0 → 0.10.1 (the adoption-window ordering fix). No alktunnels API change, no wire change; the only file-level contacts with the bump are Cargo.toml/Cargo.lock.

Changed

  • alkcall 0.10.0 → 0.10.1 (crates.io). The bug-fix release (review 013, routed from alkgit's wave-2 gate): the adopt_channel install→drain window is now one atomic section, so a chunk routed by the demux inside the window can no longer be delivered ahead of earlier-parked early arrivals (the observed downstream shape was pkt-line desync in alkgit's open-op integration test under multi-process load), and a related loss variant (a chunk parked after the drain removed the queue, parked forever) is closed. Relevant in principle to this crate's reverse-path adopt flow (the bind-first establisher surfaces channels through the same adopt path), though no change in this crate's own surfaces was needed. No API, wire-format (EventEnvelope, the 8-byte chunk header), or documented-cap behavior change upstream: the fix surface is private; the behavior only strengthens. 136 tests pass; wasm-clean verified (check + clippy on wasm32-unknown-unknown).

0.4.0 - 2026-10-19

The dependency-major release: the alkcall substrate bumped 0.9.0 → 0.10.0 (the gateway ownership-seam release). No alktunnels API change, no wire change, and zero source changes; the only file-level contacts with the bump are Cargo.toml/Cargo.lock. Minor rather than patch for the 0.3.0 reason: the semver-incompatible dependency bump plus this crate's public re-exports of alkcall types (ChannelOpenError; Establishment/EstablishmentError shapes in the producer API) make it semver-visible to downstream consumers.

Changed

  • alkcall 0.9.0 → 0.10.0 (crates.io). The gateway ownership-seam release (review 012): GatewayDispatch gains with_ownership_provider / with_static_ownership_fallback, and the previously-silent static fallback becomes loud: an unwired spine refuses (an error envelope, FORBIDDEN, pre-dispatch) an op whose spec carries access_control.resource_type. None of it is on this crate's paths: alktunnels does not use the gateway feature or GatewayDispatch; tunnel opens serve via ChannelCore::register_openable/register_openable_with_establisher registry dispatch on the native wire, which 0.10.0 leaves untouched. All of this crate's alkcall contact points (register_openable/register_openable_with_establisher, Establishment::new, OpenHandler, ChannelClient::open_channel_with_reply, ManagerError::ChannelExists, ChannelOpenError) are unchanged between 0.9.0 and 0.10.0. 136 tests pass; wasm-clean verified (check + clippy on wasm32-unknown-unknown).

0.3.0 - 2026-10-14

The dependency-major release: the alkcall substrate bumped 0.8.1 → 0.9.0 (the stub-sweep release). No alktunnels API change, no wire change; the only code contact with the 0.9.0 breaking surface was test-side (Establishment no longer derives Debug; three expect_err/panic! sites restructured), which is why this is a minor bump rather than a patch: the semver-incompatible dependency bump plus this crate's public re-exports of alkcall types make it semver-visible to downstream consumers (matching the 0.7.1 → 0.8.0 ride on 0.2.0).

Changed

  • alkcall 0.8.1 → 0.9.0 (crates.io). The stub-sweep release: the marked-pub open contract (S-01), channel/control (S-02), channel/resources/subscribe (S-03), and op/register forward-through (S-04) replace the four always-failing stubs. None of the four stubs were on this crate's paths (no marked-pub registration, no control-handle subscription, no resource enumerator use, no generic op/register announcements), so no behavior change here. Of the 0.9.0 breaking list, Establishment dropping its Debug derive was the only surface this crate touched: two bindfirst_establisher unit tests' expect_err calls and one integration test's untyped panic!("{other:?}") fall-through restructured (the error type itself remains Debug; the Ok arm now formats via panic!("got Ok")). ManagerError::InvalidAdopt (additive), RelayError::PubTypedOpen removal, and the register_imported/register_imported_all return change are untouched by this crate's API use. 136 tests pass; wasm-clean verified.

0.2.1 - 2026-09-28

The hardening release: fuzzing adopted from the alkcall pattern (fuzz/ workspace, excluded from the published package) and the alkcall substrate bumped 0.8.0 → 0.8.1. No API break, no wire change.

Added

  • Fuzzing (fuzz/ workspace, docs/research/fuzzing.md): three cargo-fuzz targets over this crate's own attacker-reachable surfaces (the envelope/chunk/manager layer is upstream and fuzzed there per alkcall 0.8.1): datagram_codec (the ADR-003 codec: no-panic, infallible decode, size bound, F-2 sentinel layering, re-frame round-trip, retained-bytes bound), params_parse (the serde params/reply parsers: no-panic on raw bytes and bounded structured values, clean typed failure, round-trips), and session_pumps (the wave-2 stateful target: op sequences over a live channels stack per exec, the TunnelSession data planes mirrored, no-wedge + shutdown-on-completion + lossless p2c + EOF-sentinel teardown + the pending backlog bound asserted). Invariant logic lives in fuzz/shared/: a stable-toolchain crate replayed as plain cargo test, so the committed corpora (96 + 8 seeds, plus the campaign-promoted seed-900) stay executable without nightly. fuzz/ is excluded from the workspace and from the published package. Campaigns (detached, fork mode): ~25M+228k execs across waves 1–2, zero crashes/hangs/OOMs; all findings were harness-model defects, no crate bugs.

  • TunnelSession::datagram_backlog(): the UDP pending count (the datagrams decoded from a chunk beyond the one returned), the wave-2 harness's asserted backlog bound made caller-visible; None for stream sessions or after the halves were taken.

  • DatagramReader::retained_bytes(): the decoder-state byte count (leftover buffer + the in-flight partial datagram); post-feed bounded by one frame (≤ MAX_DATAGRAM_LEN + 2), pinned by the datagram_codec target.

Changed

  • alkcall 0.8.0 → 0.8.1 (crates.io). The bug-fix release: a duplicate adopt_channel/open_channel no longer destroys the live channel it collided with (found by alkcall's own manager_routing fuzz target), directly relevant to this crate's reverse-path adopt flow. No alktunnels API or wire change in this bump; the full suite passes unchanged.

0.2.0 - 2026-09-20

The graduation release: the direct op + the bind-first/forwarded metadata flow (ADRs 007/008), over alkcall 0.8.0. Composed against by alksocks (the SOCKS5 layer) and the planned alknodes assembly.

Added

  • TunnelListener: the listen-flow consumer session (ADR-008 §4), the listen-flow sibling of TunnelSession: TunnelListener::open (rides ChannelClient::open_channel_with_reply, alkcall 0.8.0's U-2 surface: the full reply arrives and the bind-first bound/ listen fields project through the lenient listen_reply_fields; adopt-free construction, since the client already adopted the channel), bound()/listen() (the Option posture: None for dial/accept-first producers and old producers), and the listen channel's teardown ownership (ADR-005's posture extended to the listen flow): close (the graceful EOF sentinel via shutdown, then the reap) and Drop (the held write half drops → the mux pump's implicit-EOF chunk → the far accept dispatcher's teardown signal; sync reap, so nothing leaks). The held BiStream's write half stays IDLE and private (a listen channel has no data-plane role; pumping one is meaningless). No Clone (compile-asserted). Forwarded sessions are NOT owned by the listener; they arrive as ordinary adopted-side TunnelSessions on the opener's manager (adopt gains nothing, pinned by test); the assembly correlates them via the listen() token (the receipt queue is assembly-side bookkeeping).

  • The local feature's bind-first substrate half (ADR-008 §1, shape 3), the real-socket sources behind the committed BindFn/BindFirstAcceptLoop contracts (behind local; the default crate stays wasm-clean): bind_tcp_first (port 0 resolved via local_addr, the bound reply field's data) + TcpListenerHalves::accept_pair (one accept → (peer, halves); the peer rides accept()'s return, the forwarded op's peer carrier); unix bind-first parity shipped (OQ-TN-14): bind_unix + UnixListenerHalves (local_addr, the queue-feeding accept_loop, the bind-first accept_pair, where an unnamed (autobind) peer fails loud: the forwarded peer param requires an address) + bind_unix_first; the assembly conveniences local_bind_first() (tcp/unix dispatch; udp fails loud: a UDP socket has no accept, the bind-first shape is not defined there, never a fake listener) and local_bindfirst_accept_loop() (downcasts the plan's opaque listener to the module's wrappers; a foreign listener type fails loud as handler_error). The accept-FIRST shapes (accept_loop + AcceptQueue) are untouched. The direct-op DialFn reuse is verified + pinned: local_dial is shared unchanged (the UDP framed wrap happens inside the closure, review 001 U-1); a TunnelSession::open_direct + Substrate::Udp test drives a REAL UDP echo socket through the direct op's establisher path.

  • The forwarded op's SERVING side + the lenient reply projection (ADR-008 §3 as amended), the listen-opener half, the receipt counterpart to the bind-first producer (the opener SERVES the op; the producer CALLS it per accept): ForwardedHandler (the injected assembly handler taking the parsed params + the channel BiStream; the assembly owns the pump, since the local halves are assembly state)

    • make_forwarded_op_handler (the built-in wrapper: deny-unknown params parse (a malformed open fails LOUD → the wrapper returns early → teardown-at-birth reaps the channel; no phantom, the correct shape for a malformed ingress), accept_bi FIRST (R-02 accepted-flag telemetry), then wrapper→assembly bridge, the injected handler's JoinHandle awaited inline); + register_tunnel_forwarded_openable (register_openable, so no establisher, the data plane starts immediately; scope gate tunnel:forwarded, ingress; a separate grant from tunnel:open/tunnel:direct, never implied by either). Plus listen_reply_fields (the lenient, infallible session-surface projection of the bind-first reply: unknown extra reply fields tolerated, a malformed shape logs loud and surfaces both-None; shared with TunnelListener). Correlation-miss teardown semantics documented + test-pinned: the assembly holds the map, its error path drops the stream, the channel reaps (the producer's accept loop drops the accepted connection on the open's failure, and the shapes mirror).
  • The bind-first establisher + accept dispatcher (shape 3, ADR-008 as amended), the -R/ingress establisher whose establishment phase BINDS (the address-first-vs-accept-first deadlock dissolves; the accept wait lives in the teardown-governed pump phase): BindFn (the substrate bind function, the platform boundary; the protocol never binds) + BoundListener (the opaque listener + the real OS-chosen bound address); bindfirst_establisher (registry namespace gate → token mint → bind → bound + listen reply fields via Establishment::with_reply_field → the BindFirstPlan plan payload; the token mint is the pinned discipline: non-zero- seeded AtomicU32, per-open fresh, per-producer unique, token 0 never minted); BindFirstAcceptLoop (the type-erased accept closure; it receives the opaque listener, yields (peer, halves) per accept); ForwardedOpener (the per-accept forwarded-open caller, the call plane: call_with_payload on channel 0 + manager.adopt_channel, the reverse POC's shape); make_bindfirst_accept_handler (the dispatcher: accept_bi first per R-02, the idle listen-stream EOF cancels the loop and tears the listener down, per-accept pump_bidi tasks tracked by the handler's JoinHandle, meaning a forwarded pump never outlives its listener; forwarded-open failures are per-accept isolated: log + drop, the loop continues, v1 no retry); register_tunnel_bindfirst_openable (same spec + tunnel:open scope gate as the dial/listen variants, the per-registration establisher posture; the injected seams ride BindFirstInjections {bind, accept_loop, open_forwarded} + the optional CF-006 identity witness). The opener-side forwarded-op serving shape (register_openable, no establisher) landed with tunnels/forwarded-receipt.

  • The forwarded op + the bind-first params surface (ADR-008 as amended): the wire-stable types pin first (the ordering tunnels/params used for the base op): OP_TUNNEL_FORWARDED / TUNNEL_FORWARDED_SCOPE constants; TunnelForwardedParams {listen, substrate, peer} (deny-unknown; listen is the producer-minted correlation token, positive by wire contract, i.e. schema minimum: 1 + a serde-level pin) with a per-substrate peer sub-schema (malformed peers fail the registry's schema gate before the opener-side handler); tunnel_forwarded_spec() (Sub-typed, External, alk/tunnel ALPN marker, no establisher, so the forwarded data plane starts immediately); TunnelListenReply + parse_listen_reply (the typed projection of the bind-first listen open's additive bound/listen reply fields: unknown reply fields tolerated, malformed ones fail loud via the new TunnelError::MalformedReply); BindFirstPlan (the opaque assembly-typed listener + the token, the plan payload the bind-first establisher/accept-dispatcher task consumes).

  • The direct op (channels/tunnel/direct, ADR-007), dynamic- target egress (the ssh direct-tcpip / SOCKS5 CONNECT shape), riding the same alk/tunnel ALPN: SubstrateAddr (the untagged substrate-shaped address object, also the forwarded op's peer shape) + TunnelDirectParams {substrate, target} with a per-substrate input_schema (malformed targets fail the registry's schema gate as INVALID_INPUT before the establisher), tunnel_direct_spec(), direct_establisher (+ witness variant; no registry lookup, so unknown_resource can never fire; targets render to the same backing-string form the registry backings use and ride the SAME injected DialFn), register_tunnel_direct_ openable (same pump handler + plan-flow as the base op), and TunnelSession::open_direct (separate constructor; tunnel:direct is a separate grant from tunnel:open, never implied by it; the session type, data planes, and teardown API are identical). An old producer rejects the unknown op loudly (NOT_FOUND).

Changed

  • alkcall 0.7.1 → 0.8.0 (crates.io). The two upstream asks the graduation ADRs depend on landed (alkcall review 008): the establisher → reply projection (Establishment::with_reply_field, ADR-049 amendment 3, the bind-first bound carrier) and flavor-form open-op ids in discovery (ADR-047 amendment 3: channels/tunnel/direct-shaped ops reconstruct their channel_open marker through discovery, so hub relay wraps them as channels, not plain forwarding stubs). Plus the bonus: ChannelRelay + HubLegTemplate (ADR-051), the hub re-produce machinery the graduation's re-produce story assumed would be assembly-layer, is now in-tree, with bound flowing end-to-end per-hop-truthful (ADR-051 §6, ADR-008 §6's sentence realized). No alktunnels API or wire change in this bump; 71/91 tests pass, wasm-clean verified.

  • BindFirstPlan gains substrate: Substrate (review 002 M-1 remediation); the accept dispatcher's silent unwrap_or(Substrate::Tcp) fallback (the second TunnelParams re-parse of wire input) is removed: the establisher stamps the parsed substrate into the plan (one parse, one source of truth) and the dispatcher reads it from the plan it already downcast. A drift can no longer silently stamp Tcp into a UDP listen tunnel's forwarded opens (a raw-vs-framed data-plane mismatch on the opener side). Plan-payload change is pre-consumer (no external BindFirstPlan consumer yet). Pinned by forwarded_opens_carry_plan_stamped_substrate (a UDP bind-first flow whose forwarded opens carry substrate: udp from the plan).

0.1.0 - 2026-09-10

The initial release: the v1 tunnel protocol implementation over alkcall 0.7.x channels, spec-complete (Phase 1) and implementation- complete (Phase 2, review 001 findings remediated).

Added

  • Wire surface (one-way-door stable from this release): the channels/tunnel/sub open op on the alk/tunnel ALPN; params {resource, substrate} (deny_unknown_fields; ADR-001); the tunnel:open scope gate (ADR-006).
  • Data-plane codec (ADR-003): raw pass-through for stream substrates (tcp, unix); mandatory [len: u16 BE] boundary- preserving framing for udp (len = 0 a legal empty datagram, never EOF; oversize rejected at frame time; truncation fails loud per OQ-TN-13). The BAST contract: docs/architecture/bast.md.
  • Producer half (ADR-004): ResourceRegistry, tunnel_establisher (dial shape) and listen_establisher (the -R far-side accept shape) over alkcall's establishment surface (ADR-049), make_tunnel_pump_handler (the pump_bidi two-pump contract, alkcall ADR-050), and the register_tunnel_openable / register_tunnel_listen_openable registration helpers.
  • Consumer half (ADR-005): TunnelSession with open (forward) / adopt (reverse), substrate-shaped data planes (stream_halves, take_halves, send_datagram/recv_datagram), pump ownership (pump_against), teardown ownership (close/join/Drop); open_reverse_channel for the hub-call path.
  • local feature (non-wasm): dial_tcp, bind_tcp + TcpListenerHalves accept loop, connect_udp wrapped in the FRAMED UdpHalf adapter (the codec composes at the substrate boundary, per review 001 U-1), dial_unix, and the all-substrate local_dial closure. Stdio intentionally out of scope (alktty's pipe mode owns process stdio; OQ-TN-14).
  • Identity seam (alkcall 0.7.0 CF-005/CF-006): payload-token > ServingConfig.identity > transport identity precedence proven end-to-end; the per-call opener identity reaches the establisher.
  • Test suite: 71 tests default / 91 with local, including the review 001 gates (real-socket UDP framing probes, the AcceptQueue lost-wakeup regression, the identity-precedence matrix). Wasm-clean default crate verified (wasm32-unknown-unknown check + clippy).