- Cargo.toml: alkcall 0.10.0 -> 0.10.1 (crates.io, the
adoption-window ordering fix — review 013, routed from alkgit's
wave-2 gate; install + park-drain are one atomic section so a
demux-routed chunk can no longer overtake earlier-parked early
arrivals, plus the parked-forever loss variant closed) and
0.4.0 -> 0.4.1 (no alktunnels API change, no wire change, backwards-
compatible dep bump -> patch)
- CHANGELOG: Unreleased cut to 0.4.1 (the alkcall 0.10.1 ride + the
compare links updated)
- README: the 0.4.1 note (alkcall 0.10.1 adoption-window ordering fix)
- docs/architecture/overview.md: dep tree alkcall 0.10.1
Verification: cargo test (136 pass, default + all-features),
clippy --all-targets -D warnings (default, all-features,
wasm32-unknown-unknown), fmt --check, doc, wasm32 check, and cargo
publish --dry-run --allow-dirty all green
- Cargo.toml: alkcall 0.9.0 -> 0.10.0 (crates.io, the gateway
ownership-seam release) and 0.3.0 -> 0.4.0 (breaking dep bump +
public alkcall re-exports -> minor, matching the 0.3.0 ride on
alkcall 0.9.0)
- Zero source changes: the 0.10.0 breaking surface (the loud
ownership-wiring default on the GatewayDispatch spine, behind the
opt-in gateway feature) is off this crate's paths — tunnel opens
serve via ChannelCore::register_openable* registry dispatch, which
0.10.0 leaves untouched
- fuzz/shared/Cargo.toml: alkcall 0.10.0 (path-linked alktunnels must
see one alkcall version)
- CHANGELOG: 0.4.0 section + compare links
- README + docs/architecture/overview.md + AGENTS.md: the 0.10.0
notes / dep tree / alkcall refs
Verification: cargo test (136 default / 163 with local, all green),
fuzz/shared corpus replay (5/5), clippy --all-targets -D warnings
(native + wasm32-unknown-unknown), fmt --check, wasm32 check, and
cargo publish --dry-run --allow-dirty all green
Stale current-state claims from two versions back: the §11 pin
("alkcall = \"0.7.0\"") and the "alkcall is v0.7.x" statement, plus the
Architecture Context bullet (v0.7.x). The 0.6.0/0.7.0-surface prose
(establishment, CF-005/CF-006 identity seam) keeps its historical
version qualifiers — those describe where upstream landed, not current
state. Dated records in docs/research|reviews, tasks/, and the
CHANGELOG are untouched by design.
- Cargo.toml: alkcall 0.8.1 -> 0.9.0 (crates.io, the stub-sweep
release) and 0.2.1 -> 0.3.0 (breaking dep bump + public alkcall
re-exports -> minor, matching the 0.7.1 -> 0.8.0 ride on 0.2.0)
- fuzz/shared/Cargo.toml: alkcall 0.9.0 (path-linked alktunnels must
see one alkcall version)
- src/producer.rs + tests/producer_direct_op.rs: the only code contact
with the 0.9.0 breaking surface — Establishment no longer derives
Debug; two expect_err calls -> .err().expect, one untyped
panic!("{other:?}") fall-through -> typed Err/Ok arms
- CHANGELOG: 0.3.0 section + compare links
- README: the 0.3.0 note
- docs/architecture/overview.md: dep tree alkcall 0.9.0
Verification: cargo test (136 default / 163 with local), fuzz/shared
corpus replay (0 pass, empty target set — no regressions), clippy
--all-targets -D warnings (native + wasm32-unknown-unknown), fmt
--check, wasm32 check, and cargo publish --dry-run --allow-dirty all
green
- CHANGELOG: Unreleased cut to 0.2.1 (the fuzz/ workspace: three
cargo-fuzz targets + stable-toolchain corpus replay in fuzz/shared;
the two wave-2 diagnostics datagram_backlog/retained_bytes; the
alkcall 0.8.0 -> 0.8.1 bump — duplicate adopt no longer destroys the
live channel; compare links updated)
- README: the 0.2.1 hardening note (fuzzing + alkcall 0.8.1 + the two
additive diagnostics)
- docs/architecture/overview.md: dep tree alkcall 0.8.1
- Cargo.toml: 0.2.0 -> 0.2.1
Verification: cargo test (136 default / 163 with local), fuzz/shared
corpus replay (5 pass), clippy --all-targets -D warnings (default,
all-features, wasm32-unknown-unknown, fuzz/shared), fmt --check, doc,
wasm32 check, and cargo publish --dry-run --allow-dirty all green
(42 packaged files — fuzz/, docs/research|reviews|plans/, tasks/,
.opencode/, AGENTS.md all excluded)
The wave-2 stateful target from docs/research/fuzzing.md 3.3: op
sequences (arbitrary SessionSequence) over one live channels stack
per exec - MuxRunner -> raw chunk stream -> the real
run_demux_loop_for_client demux -> ChannelManager per side (producer
Connect / consumer Accept over one duplex transport, the reverse
POC's topology). The harness mirrors TunnelSession's data-plane
shapes: datagram planes (codec reader + pending, the
DatagramHalves mirror) and stream planes (pump_bidi against
in-process peer halves, the pump_against mirror).
Invariants (3.3): no-panic/no-wedge (5s reconcile join); two-pump
shutdown-on-completion; lossless p2c copy count; EOF-sentinel
teardown semantics (drain-before-EOF, map empties); FIFO datagram
reconciliation through the real demux path (stateful framing walk,
the wave-1 walk carried across ops); the 1.3 DatagramHalves.pending
backlog bound (<= routed-but-unpulled remainder); decoder-state
retained-bytes bound re-asserted per op.
Harness-model findings (all six wave findings are harness defects,
not crate bugs - the fuzzer-as-harness-checker pattern):
- duplicate adopt is a legal ChannelExists no-op (documented table)
- the p2c-EOF pump join is the DESIGNED half-close shape; the pump
completes once the c2p source EOFs too (teardown), not before
- route_payload routes into the LOCAL manager (demux direction);
the producer pump writes the channel send half - the first
campaign's bytes never reached the consumer; rewritten to the
real protocol shape (Open allocates, session-index addressing,
stateful framing model, Pull op, EOF sentinels only from
teardown/sender-drop)
- clear_all's return is the opener-ledger records, NOT the reaped
ids (adopted channels carry no ledger entry - the alkcall 7.7
lesson re-learned); the map-empties assert is the ground truth
Campaign (10-min detached, 6 runner): 228,676 execs, 32 fork jobs,
oom/timeout/crash 0/0/0, exit 0. Coverage 3427 edges / 17.5k
features still expanding at budget end. No crate bugs found - the
two-pump contract, EOF-sentinel semantics, and the pending backlog
bound are demonstrated properties.
Crate addition: TunnelSession::datagram_backlog() (the
DatagramHalves.pending diagnostic the 3.3 bound asserts against, in
the retained_bytes() style) + a suite test pinning its rest states.
Verification: main suite 136 passed; corpus replay (8 committed
seeds + 2 semantic fixtures) green; clippy -D warnings + fmt clean
(main, shared, wasm); wasm check clean; cargo fuzz build clean.
Bump the substrate dependency to alkcall 0.8.1 — the bug-fix release
whose fix (duplicate adopt/open no longer destroying the live channel)
is directly relevant to this crate's reverse-path adopt flow.
Adopt fuzzing per docs/research/fuzzing.md, scoped smaller than
alkcall's (the envelope/chunk/demux/manager surfaces are upstream and
now fuzzed there; this crate fuzzes its own named downstream target —
the ADR-003 datagram codec — plus the params/reply parsers):
- fuzz/ workspace per the alkcall pattern: nightly-pinned subtree
(rust-toolchain.toml), thin fuzz_target! binaries, and a
stable-toolchain shared crate carrying the invariant logic so the
committed corpus replays as plain `cargo test` (the fuzz gate in
the release checklist — no hosted CI, alkcall §7.9 posture).
- Targets (both over already-pub APIs; no #[cfg(fuzzing)] exposure):
- datagram_codec — DatagramReader::feed + frame_datagram: no-panic,
infallible decode, size bound, F-2 sentinel layering (empty
datagram only from a length-0 header at a frame boundary),
3-byte-slice splitting transparency, re-frame round-trip,
oversize rejection at the bound, is_mid_datagram framing state,
and the decoder-state retained-bytes bound.
- params_parse — serde params/reply parsers + establishment_reason:
no-panic on raw bytes and bounded structured Values, clean typed
failure, round-trips, listen: 0 pin, loud/lenient projection-pair
agreement, pass-through reason extraction model.
- New public diagnostic: DatagramReader::retained_bytes() (decoder-
state byte count; the fuzz bound asserts against it).
- 96 committed seeds (gen_fuzz_seeds.py) + the campaign-found crash
input promoted as datagram_codec/seed-900; grown corpora gitignored.
- run-detached.sh: setsid+nohup runner so campaigns never share fate
with the agent session process tree (fuzzing.md §5).
Campaign findings this session were harness-model defects, not crate
bugs (both invisible to example-based review; the alkcall
"fuzzer as harness-checker" pattern confirmed):
- the F-2 empty-datagram invariant first counted [00 00] pairs in the
raw stream — a pair inside a payload region is payload, not a
header; corrected to walk the framing (crash artifact saved).
- establishment_reason is a pass-through extractor of details.reason,
not a validated table (harness first asserted a closed set).
- is_mid_datagram means a partial datagram, not buffered header bytes
(a 1-byte header fragment is buffered, not in flight).
Verification: cargo test 135 passed; clippy -D warnings and fmt clean
(main + fuzz/shared); corpus replay green; cargo fuzz build clean;
wasm32-unknown-unknown check clean. Wave-1 campaigns (10-min detached,
fork mode): params_parse 3.14M execs / datagram_codec 827k execs,
0 crashes/OOMs/timeouts; coverage saturated on the codec state space.
- README sync: the direct op (ADR-007), the bind-first establisher +
the forwarded op (ADR-008), TunnelListener + the reply-field
projection, the 0.2 summary section, the local bind-first helpers
- CHANGELOG: Unreleased cut to 0.2.0 (the graduation + the alkcall
0.8.0 bump; compare links updated)
- Cargo.toml: 0.1.0 -> 0.2.0
Verification: cargo test (135 pass default / 162 with local), clippy
(all targets, -D warnings, both feature sets), fmt, doc, the
wasm32-unknown-unknown check + clippy, and cargo publish --dry-run
--allow-dirty all green
- N-3: parse-before-accept telemetry note on make_forwarded_op_handler
(the accepted-flag reads false for a malformed-params open by design)
- N-4: ADR-007 Consequences lines — the untagged-leniency-is-
schema-gated paragraph + the overlapping-variants caution
- N-2: ADR-008 §1 widened to name the any-byte idle-read posture
(the Ok(0)-only reading hangs on a write-happy peer)
- N-5: mint_token's doc comment records the 256-channel cap analysis
- The N-1/N-6/N-7 + G-1/G-3 no-action dispositions recorded in the
task file (the tracker the task asked for)
Verified: fmt, clippy (native + wasm32), test, cargo doc — all green
- D-1: wire.md's Direct Op + Forwarded Op sections record the shipped
schema constraints (port bounds, additionalProperties: false per
branch, listen minimum: 1) and the registry-side gate ordering
- D-2: ADR-007 §2 corrected in place + a dated erratum (oneOf was
wrong-shaped; validation is registry-side, not establisher-time);
wire.md's mirrored sentence corrected
Verified: cargo doc --no-deps clean; cargo fmt --check
- D-3: inline the serde-layer check into the renamed
serde_rejects_unknown_substrate_value; delete the overpromising
jsonschema_valid helper (the registry's real schema gate stays
pinned end-to-end by the integration probes)
- D-4: trim the stale Unreleased preamble to a one-liner (written
before the six graduation implementation commits landed)
Verified: cargo fmt --check; cargo clippy --all-targets -D warnings;
cargo test (86 + 2 doc-tests) green
- G-4: drive UnixListenerHalves::accept_loop — bind_unix + temp path,
the loop exits on the queue-closed handler error after one unix
client accept (the unix parity twin of the TCP accept-loop test;
closes the largest single uncovered block, local/mod.rs 591-609).
- G-5: pin ADR-008 §3's two fail-loud sentences — bind_unix("") is
the kernel autobind (no filesystem path) so local_addr fails loud
(578-583), and a socket2 autobind client against a NAMED listener
is refused by accept_pair's unnamed-peer guard (626-633; Linux —
abstract addresses/autobind are Linux-shaped, mio maps any
abstract-namespace peer to unnamed).
Verification: cargo test --features local green (162 tests);
clippy --all-targets -D warnings clean (native + wasm32-unknown-
unknown); cargo fmt --check clean.
- G-2: observe TunnelListener::close's explicit shutdown sentinel at
the far accept dispatcher — the accept loop cancels, the pushed
accept is never consumed, the serving-side listen channel reaps
(ADR-008 §1's teardown contract, graceful half).
- N-2 residue: pin the write-happy-peer teardown posture — a
non-empty read on the idle listen stream tears the listener down
(the fail-closed Ok(_) arm; driven via the raw client surface
since the typed listener holds its write half idle by contract).
Verification: cargo test --features local green (163 tests + 2
doc-tests); clippy --all-targets -D warnings clean (native +
wasm32-unknown-unknown); cargo fmt --check clean.
- BindFirstPlan gains substrate: Substrate, stamped at establisher
time from the one successful TunnelParams parse (one parse, one
source of truth; the plan-payload change is pre-consumer)
- make_bindfirst_accept_handler drops the second params re-parse and
the silent unwrap_or(Substrate::Tcp) fallback entirely — the
dispatcher reads plan.substrate; no fallback branch remains, so a
future drift cannot silently stamp Tcp into a UDP listen tunnel's
forwarded opens (the raw-vs-framed data-plane mismatch class)
- tests: forwarded_opens_carry_plan_stamped_substrate pins the plan
as the substrate's source over a real UDP bind-first wire flow
(a substrate-recording forwarded stub observes substrate: udp);
the unit downcast + establisher-flow tests pin the stamp
- docs: producer.md (the plan carries {listener, token, substrate};
the forwarded open's substrate also comes from the plan),
ADR-008 §1 (the stamp + the removed fallback)
- task review-002-m1-substrate-fallback: completed (Notes filled)
Verification: cargo test (145 pass), cargo test --all-features (153),
clippy --all-targets -D warnings (default + all-features), fmt
--check, doc --no-deps, wasm32 check + clippy -D warnings.
- M-1: the accept dispatcher's silent substrate fallback (plan-stamp
shape recommended; failure-path test required)
- D-3 + D-4: trivial batch (rename the overpromising schema test
helper; trim the stale CHANGELOG Unreleased preamble)
- D-1 + D-2: wire.md schema bounds + ADR-007 oneOf->if/then +
establisher-time -> registry-gate sentence (doc-only, pre-consumer)
- G-2 (+ N-2's write-happy-peer test): observe close()'s EOF sentinel
at the far accept dispatcher
- G-4 + G-5: unix accept-loop coverage + the unnamed unix fail-loud
guards (G-1/G-3/G-6/G-7 explicitly left uncovered per the review)
- N-1..N-7 recorder: the two durable-trail notes (N-3 telemetry
ordering sentence, N-4 ADR lines) + the no-action dispositions
All six independent (no depends_on) so sessions can parallelize;
validated with taskgraph (28 tasks, no cycles).
- src/consumer.rs: `TunnelListener` — the listen-flow sibling of
`TunnelSession`: `open` rides `open_channel_with_reply` (the U-2
surface, alkcall 0.8.0) and projects the bind-first `bound`/`listen`
reply fields via the lenient `listen_reply_fields`; `bound()` /
`listen()` present them (the Option posture — None for dial/
accept-first producers and old producers). Teardown ownership
(ADR-005's posture, listen-flavored): `close` (graceful — `shutdown`
on the held write half, the explicit EOF sentinel, then the sync
reap) and `Drop` (the held write half drops → the mux pump's
implicit-EOF chunk — the far accept dispatcher's teardown signal —
then the sync reap; never leaks). The held stream is the write half
(`MpscSendStream`) only, IDLE and private — a listen channel has no
data-plane role. No `Clone` (compile_fail doc-test).
`forwarded()` decided: NOT provided — the receipt queue is
assembly-side bookkeeping; the type hands the assembly the token.
- src/lib.rs: re-exports `TunnelListener`; crate docs mention the
listen-flow session.
- src/producer.rs: doc-fix drive-by — two pre-existing broken
intra-doc links (cargo doc is now 0-warning clean).
- tests/harness.rs: `wire_listener_bindfirst` + `ListenerTopology` —
the opener-dials shape (client + serving on the opener; bind-first
serving + the call-plane forwarded-open caller on the producer).
- tests/consumer_listener.rs: 5 integration tests — the bind-first
gate (reply fields + forwarded round-trip through the REAL
forwarded-openable registration + the teardown cascade), the Option
posture on a dial producer, close/Drop reaping (the leak-free
guarantee), the adopt-path pin (`adopt` gains nothing — a forwarded
channel presents the ordinary session semantics), and the scope
interaction (`tunnel:open`-only opener opens the listen channel;
forwarded opens to it fail FORBIDDEN).
- CHANGELOG: Unreleased → Added.
Verification: cargo test 132 passed (157 --all-features; doc tests
2/2 incl. both compile_fail asserts), clippy --all-targets -D warnings
clean (native + wasm32), fmt clean, wasm32-unknown-unknown check
passes (the default crate stays wasm-clean), cargo doc --no-deps clean.
- BindFn + BoundListener: the platform-boundary bind seam (the
protocol never binds, OQ-TN-04); the opaque listener rides the plan
- bindfirst_establisher: registry namespace gate -> token mint
(non-zero-seeded AtomicU32, per-open fresh, per-producer unique,
token 0 never minted — the wrap shape unit-pinned) -> bind ->
`bound` + `listen` reply fields -> the BindFirstPlan plan; the
typed errors pass through (bind failure dial_failed, exhaustion
resource_shortage, registry miss unknown_resource)
- BindFirstAcceptLoop: the type-erased accept closure (receives the
opaque listener; yields (peer, halves) per accept; Err is
loop-critical)
- ForwardedOpener: the per-accept forwarded-open caller — the call
plane (call_with_payload on channel 0 + manager.adopt_channel; the
reverse POC's open_reverse_channel shape)
- make_bindfirst_accept_handler: the accept dispatcher — accept_bi
first (R-02 telemetry), the idle listen-stream EOF sentinel cancels
the loop + tears the listener down (select!), per-accept pump_bidi
tasks tracked by the handler's JoinHandle (awaited on loop exit —
a forwarded pump never outlives its listener), per-accept failure
isolation (log + drop; v1 no retry), loop-critical accept errors
end the loop
- register_tunnel_bindfirst_openable: same spec + tunnel:open scope
gate (the per-registration establisher posture); the injected
seams ride BindFirstInjections {bind, accept_loop, open_forwarded}
(8-arg sketch -> the bundle; clippy too_many_arguments) + the
optional CF-006 identity witness (witness_establisher wrapper)
- harness: RegistrationMode::BindFirst + wire_bindfirst; the consumer
hook gained the accept-side identity propagation + a shared serving
registry (Topology.consumer_registry exposed); the forwarded-op
serving stub (make_forwarded_stub_handler — correlate by token,
accept the BiStream, pump_bidi against the correlated halves; the
opener-side registration lands with tunnels/forwarded-receipt)
- tests: 6 new integration tests (the full flow e2e — the acceptance
gate incl. the byte round-trip through the whole path, the
dial-open neither-field projection, EOF teardown, per-accept
isolation, typed establishment errors, token discipline) + 6 unit
tests (the mint discipline, the establisher flow at the
Establishment surface, the witness wrapper)
Verified: cargo test green (118 default; 138 --all-features), clippy
--all-targets -D warnings clean (native + wasm32), fmt clean,
wasm32-unknown-unknown check passes, cargo doc --no-deps clean.
tunnels/tunnel-listener + tunnels/forwarded-receipt used `scope: medium`
(not a valid variant — single/narrow/moderate/broad/system), which made
taskgraph silently drop both from the graph (validate failed; topo/
list missed them). tunnels/coverage-weak-spots used `status: done`
(pre-existing; valid variants are pending/in-progress/completed/failed/
blocked).
Verified: taskgraph validate (22 tasks), topo, cycles (none),
parallel, critical all resolve with the full graph.
Verification pass against the landed alkcall 0.8.0 surface surfaced one
design gap: ADR-008 §3 pinned the forwarded op's correlation key as the
listen channel's ID, but (1) the serving-side OpenHandler/establisher
surface never exposes the allocated channel ID, and (2) the in-tree
generic ChannelRelay forwards open-op params untouched (it rewrites
only the reply's channel_id), so a spoke's forwarded open would carry
an id the end opener never allocated. Amended (wire still unconsumed —
alksocks is docs-only — so pre-consumer and legal):
- ADR-008 Amendment 1: the bind-first establisher mints a fresh u32
correlation token per open — carried in the plan (for the accept
dispatcher) and contributed as the additive `listen` reply field
(the same projection carrier `bound` rides); the forwarded op's
params rename `channel` -> `listen` with token semantics. Tokens
ride reply fields + params through hubs untouched (relay-transparent
by construction, alkcall ADR-051). Consumer-minted tokens and a new
upstream handler surface were considered and rejected (rationale in
the amendment).
- Spec docs synced: wire.md (the `bound`+`listen` reply section, the
forwarded params), producer.md (bind-first shape + forwarded op),
consumer.md (TunnelListener gains `listen()`; correlation registry
posture), overview.md, OQ ledger (OQ-TN-16 updated),
tunnels-graduation.md.
Task decomposition (tasks/tunnels/, Phase 2 of the graduation — the
alkcall prerequisites are gone):
- direct-op — params/spec/establisher/open_direct (ADR-007)
- bindfirst-params — forwarded-op spec, TunnelListenReply, the
BindFirstPlan + token mint (wire types first, the params.md
ordering precedent)
- bindfirst-producer — bind-first establisher, accept dispatcher,
register_tunnel_bindfirst_openable
- forwarded-receipt — the opener-side serving op + reply-field reader
- tunnel-listener — the listen-flow consumer session (ADR-008 §4)
- local-bind-halves — the `local` TCP bind + accept-pairs loop,
unix parity decision, direct-op DialFn reuse pin
Dependency order: direct-op and bindfirst-params are independent
roots; bindfirst-producer -> {bindfirst-params}; forwarded-receipt ->
bindfirst-params; tunnel-listener -> {bindfirst-params,
forwarded-receipt}; local-bind-halves -> bindfirst-producer.
Verification: cargo test 71 passed; clippy --all-targets -D warnings;
fmt --check; wasm32-unknown-unknown check + clippy clean (docs + task
files only — no code change).
The alksocks graduation research (tunnels-graduation.md) is decided;
the ADRs are normative now:
- ADR-007: the direct open op — channels/tunnel/direct on the one
alk/tunnel ALPN, params {substrate, target} (self-describing JSON
target; ATYP bytes rejected as not schema-describable), scope
tunnel:direct (never implied by tunnel:open), dial-only.
- ADR-008: listen metadata — bind-first listen establisher (reply
gains additive 'bound' = BND.ADDR fidelity; the BIND deadlock +
10s-bound findings dissolve) + the forwarded op
channels/tunnel/forwarded (accept-as-open, params carry
{channel, substrate, peer} = reply#2 fidelity). Accept-first stays.
- ADR-001/002/006 amendments: params layout is per-op; flavor-form
op ids on the one ALPN; per-flavor scopes + the hub re-produce
policy sentence (the hub's ACL governs the final dial).
- wire/producer/consumer/overview/README updated for the new ops,
the three establisher shapes, TunnelListener, open_direct.
- OQ-TN-15/16 promoted + resolved; upstream alkcall asks filed at
alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md
(flavor-form discovery derivation; the establisher → reply
projection).
Verification: cargo test (22 passed), clippy -D warnings, fmt --check
(docs-only change; wasm check unaffected)
Downstream alksocks motivation: CONNECT/BIND/ASSOCIATE need three
additions, all generally useful:
- One wire addition: a direct/dynamic open op on the alk/tunnel ALPN
(channels/tunnel/direct, scope tunnel:direct) — the ssh
direct-tcpip capability, additive to ADR-001's resource-not-address
params. Options weighed; second-op lean (ACL separation at the op,
per the socks5:bind scope-gate precedent).
- The two BIND metadata asks (bound-addr discovery, peer metadata)
with carrier options, including the forwarded-tcpip alternative
(reverse open with peer/target in params) that retires both with
existing reverse-path machinery.
- Callback→provider mapping: the composed providers the graduation
gives each alksocks seam (DialFn/EgressFactory/BindAcceptFactory)
next to the local-socket ones.
Also records the vpn-like endgame positioning (virtual-subnet
translation via dial callback, signing as directory/identity-layer)
and alknodes as the OQ-TN-12 re-produce consumer.
Verification: docs-only change (cargo test/clippy/fmt unaffected).
The 0.7.1 lockfile resolution behind the MSRV floor raise. The
alkcall requirement in Cargo.toml stays "0.7.1" only after the floor
commit paired it there; the lockfile is the piece that actually moves
the resolved 0.7.x to the version whose MSRV (1.88) this crate now
declares. alkcall 0.7.1 changed no type alktunnels touches
(version-requirement- and patch-level only; full suite green on both
toolchains in the previous commit).
The 1.85 claim is now false at the dependency level: the lockfile pins
alkcall 0.7.x, and alkcall 0.7.1 raised its MSRV floor to 1.88, so no
1.85 toolchain can resolve this dependency graph regardless of what
Cargo.toml declares. Raising the floor keeps the claim honest and
aligns with the ecosystem floor. Verified: on a 1.85 toolchain with
--ignore-rust-version, the build fails in the graph (icu_locale_core
2.3.0, MSRV 1.86, via alkcall's jsonschema -> idna chain) before any
alktunnels code is reached — the raise is dependency-driven, and
unlike alktty, two clippy lints promoted since 1.85 surfaced here,
both test-only and behavior-identical:
- tests/harness.rs: unreachable_code — the hanging-establisher probe
ends in a typed Err(EstablishmentError::HandlerError) instead of an
unreachable!() cast (the sleep never completes; the per-op timeout
cancels the task, so behavior is unchanged)
- tests/end_to_end.rs: uninlined_format_args — channel_id inlined
into the format! string
Version-line bump deferred (crate is unpublished); no CHANGELOG here
yet.
Verification:
- 1.88: test 71 default / 91 all-features pass, clippy --all-targets
--all-features -D warnings, wasm32-unknown-unknown check + clippy
clean (target component installed)
- stable 1.94: test 71/91 pass, clippy --all-targets --all-features
-D warnings, fmt, doc --no-deps, wasm32 check + clippy clean
- DatagramReader::feed is infallible (the oversize bound is enforced
at frame time; the same zombie category as 4014ba6's InvalidLength).
The Vec<Result> shape, the local adapter's dead InvalidData mapping,
and consumer.rs's `?` drop.
- The sender-block machinery test (root-gated veth fixture, auto-skip
without root): the peer address unassigned + the neighbor table
flushed pins the connected sender in a persistent WouldBlock/ENOBUFS
state; the test drives the WouldBlock stash, the readiness-Pending
arms, AND the accepted-byte high-water Pend in one behavior, then
resolves (addr assign + peer bind) and asserts the queued datagrams
drain and deliver. Jumbo MTU (65535) keeps IP fragmentation out of
the way; the block-state probe runs on a throwaway socket (an
oversized probe's partially-built skb chain stays corked on EAGAIN
and poisons the socket).
- pump_queue_to_socket's stash arm now matches ENOBUFS(105) alongside
WouldBlock — the blocked-neighbor state reports 105, and both mean
"the socket cannot take this datagram now".
- connect_udp's resolve-failure arm covered (an RFC 6761 .invalid
target maps to dial_failed; rootless).
- The no-witness tunnel_establisher wrapper covered (a harness
DialNoWitness mode + a full open round-trip through the wrapper).
Negative result pinned in the task doc: tokio 1.53 structurally masks
UDP ICMP errors out of the poll drive (poll_recv_ready/try_io mask
ERROR out of the read direction), so the recv-side ECONNREFUSED
mapping arm is unreachable through the pump's drive — it joins the
defensive list.
Coverage: 92.86% -> 93.91% lines (50 -> 41 uncovered, all deliberate).
Verification: cargo test + cargo test --all-features (91 tests),
clippy --all-targets --all-features, fmt, wasm32 check+clippy,
cargo doc — all clean.
Closes the behavioral test gaps the 2026-09-09 coverage review found
(88.28% → 92.86% lines; details in tasks/tunnels/coverage-weak-spots.md):
- Datagram EOF paths (consumer.rs:377-382, 255) — previously
zero-covered documented contracts: a clean stream EOF resolves
`Ok(None)` (the pump-less session's shutdown signal), a mid-datagram
EOF fails loud with `TruncatedDatagram` (OQ-TN-13), both over a new
closable UDP echo dial (`closing_udp_echo_dial`, both shapes).
- UDP `take_halves` (consumer.rs:201-205) — pins the N-3 framing
contract: taken halves carry raw `[len: u16 BE]` wire bytes, and
the data-plane accessors fail `ChannelTaken` after the take.
- Reverse UDP `pump_against` (consumer.rs:289) — the framed side
round-trips end-to-end (payload + the F-2 empty datagram `len=0`
survives the pump), the N-11 boundary contract.
- `AcceptQueue::push` after close returns `Err(handle)`, closed pop
resolves `None`, and the accept loop's queue-closed mapping ends
the loop with the handler error (producer.rs:279-288, local
accept_loop).
- `establishment_reason` wrapper (params.rs) — unit test over the
typed surface (open_failed reason / non-establishment /
AdoptFailed); plus the schema-gate preemption probe in
producer_open_op (missing substrate is `INVALID_INPUT` before the
establisher runs).
- `UdpHalf` write machinery (local/mod.rs) — four poll-driven tests:
queue accept + flush drain to the wire + shutdown best-effort,
repeated flush cycles, the unconnected-socket error mapping
(deterministic EDESTADDRREQ), and the shutdown-drain-swallows-
errors posture. Host probes recorded in the task doc: no UDP
sender-blocking shape is reachable on this host (loopback/veth
drop silently; the fresh-socket try_send quirk is timing-tolerated,
never asserted), so the stash/high-water paths stay uncovered by
host limitation, not by oversight.
Verified: cargo test (default + --all-features), clippy --all-targets
-D warnings, fmt --check, wasm32 check + clippy, llvm-cov 92.86%.
The coverage review found `InvalidLength {declared, have}` is a
zombie: `DatagramReader::feed` never returns `Err` — it buffers
partial frames indefinitely, and the truncated-stream contract is
carried by `is_mid_datagram` (the codec diagnostic) plus
`TunnelIoError::TruncatedDatagram` (the session surface). The
variant's doc claimed it was the truncated-stream error; no spec
(ADR-003, wire.md, bast.md) names it, and no external consumer
exists (verified 2026-09-08 per ADR-005's N-3 amendment), so the
API window is open — removed before the first consumer pins it.
Also removes the `unreachable!` arm its removal strands in the
`local` adapter's recv mapping (a no-panic path; single-variant
destructure now), and records the planned-shape correction in the
completed wire-codec task doc.
Verified: cargo test (--all-features and default), clippy
--all-targets -D warnings, fmt --check, wasm32 check + clippy — all
clean.
Review 001 N-3 (tasks/tunnels/decide-take-framed-halves), decided
before the first external consumer:
- Option 1 (keep as-is, docs carry it), recorded as ADR-005
§Amendment. No external consumer exists (window open — the breaking
reshape was available but undemonstrated); one take_halves semantic
across substrates beats per-substrate divergence; pump_against
composes the framed adapter (UdpHalf), not framed halves; and the
take_framed_halves convenience is additive — it can land after the
first consumer defines its shape.
- consumer.md's datagram section states the posture; review 001 §N-3
resolution note landed; task marked completed.
Docs-only (no code change). Verified: fmt --check, doc --no-deps
warning-free, 63 default / 76 local tests green.
Review 001 remediation (tasks/tunnels/fix-udp-framed-adapter):
- U-1 [major]: UdpHalf now composes the ADR-003 codec at the
substrate boundary — poll_write de-frames channel bytes (incremental
DatagramReader) and sends each payload as one raw datagram;
poll_read frames raw target datagrams and hands frame bytes to the
pump. Targets see payload-only bytes; empty datagrams round-trip
(the F-2 fix end-to-end); >8 KiB datagrams arrive as ONE datagram.
Mechanics the verification gates forced beyond the sketch:
poll_write accepts all offered bytes (tokio copy re-offers a full
window unsatisfied — partial-frame holds deadlock >8 KiB frames),
backpressure is an accepted-byte high-water mark, socket drain
pumps on write/flush/shutdown; >caller-buffer frames stage across
reads (the truncation probe's assertion surface updated per the
re-validation correction).
- U-2 [minor]: connect_udp resolves the target and binds the
unspecified address of the matching family (v6 targets dial from
[::]:0); bind_tcp gains the wildcard-exposure doc warning.
- U-3 [minor]: the 65535 recv scratch allocates lazily on first recv
(full-size read kept — try_recv truncates silently; the MTU-shrink
idea confirmed unsafe in re-validation).
- N-1: the module/connect_udp/UdpHalf docs now describe the composed
codec truthfully.
Gates (permanent tests in tests/local_halves.rs): recording-target
suite (payload-only at the target, empty-datagram round-trip, 9000-
byte single datagram, unframed-target resolution), the 65507-byte
max-datagram staging probe, and the v6 dial gate.
Docs: ADR-003 §Amendment (the boundary mechanics), wire.md truncation
bullet, consumer.md adopt sketch (substrate param). Review 001
resolution notes on U-1/U-2/U-3/N-1; task marked completed.
Verified: 63 default / 76 local tests ×3 repeat runs; clippy
-D warnings (all-targets + wasm32), fmt --check, doc --no-deps
(warning-free), wasm check, publish dry-run.
- All findings re-validated (fresh executable probes for U-1 over
real sockets — all four behaviors reproduced; fixed-in-review
items confirmed in-tree; battery green 63/70).
- U-1 amendment: the review's fix sketch was direction-inverted
(frame-on-write describes the TARGET face; UdpHalf's caller is the
pump, so poll_write DE-frames channel bytes and poll_read FRAMES
target datagrams). Gate 4 and U-3 corrected: the truncation test's
assertion surface may change; the 65535 recv scratch is
correctness-load-bearing (tokio try_recv truncates silently).
- Task fix-udp-framed-adapter updated to the corrected mechanics.
- New task decide-take-framed-halves (N-3): the datagram
take_halves API posture, to decide before the first external
consumer.
Verification: cargo test 63/70 both configs, clippy -D warnings
(all-targets + wasm32), fmt, cargo doc --no-deps (0 warnings),
publish dry-run, taskgraph validate (15 tasks, acyclic).
Sharpened from 'v6 blind spot' into a general bind-posture rule after
follow-up discussion: no implicit wildcard binds (0.0.0.0 / :: — note
::1 is loopback, not a wildcard); connect_udp is a client socket so
the floor fix is family-correct binding, never a loopback default;
bind_tcp keeps its caller-supplied address (the correct posture) with
a wildcard-bind doc warning. Executable rider sharpened on the
remediation task.
Findings + review doc: docs/reviews/001-implementation-review.md (the
alkcall house pattern). Closes tunnels/review-core-crates and
tunnels/review-impl.
Findings:
- U-1 [major, NOT fixed] — the local UDP adapter is not the framed
adapter ADR-003 mandates: codec-framed bytes reach real targets,
empty datagrams are EOF-shaped at the adapter (tunnel teardown),
>8 KiB datagrams split, unframed target-initiated datagrams are
dropped. Executable-pinned by real-socket probes (temporary test,
deleted after the run). Remediation task: tunnels/fix-udp-framed-adapter.
- C-1 [major, fixed] — AcceptQueue::pop lost wakeup in the
check→register window (open op hangs to the establishment bound);
fixed via the tokio notify_waiters contract (Notified created before
the check each iteration) + multi-thread regression test.
- N-4/N-5/N-7/N-8/N-9/N-10/N-11 [minor, fixed] — serialize TunnelParams
in open_reverse_channel; stale doc ref; broken doc link; root
re-exports (TunnelParams/Substrate/tunnel_open_spec/OP_TUNNEL_OPEN);
README (publish dry-run was failing on the missing readme); harness
comment; pump_against UDP framing doc note.
- N-2 retracted by executable falsification (codec buffering is
bounded by construction); N-6 non-finding with rationale.
Verified: 63 tests default / 70 with --features local, 3x repeat-run
clean both configs; clippy -D warnings (all-targets + wasm32); fmt;
doc warning-free; wasm32 check; cargo publish --dry-run passes.
- src/local/mod.rs (feature = "local" -> tokio/net): dial_tcp (5s
timeout), connect_udp (ephemeral bind + connect + the FRAMED UdpHalf
adapter), dial_unix (OQ-TN-14 in v1), bind_tcp +
TcpListenerHalves::accept_loop (the listen shape's assembly half),
local_dial() (one DialFn covering all three substrates).
- UdpHalf truncation fails loud (OQ-TN-13): poll_read recvs into a
scratch buffer first and size-checks against the caller's buffer —
an over-size datagram is io::ErrorKind::InvalidData, never a silent
truncation (tokio's poll_recv-into-ReadBuf would truncate).
- No socket type appears outside src/local/ (convention 16); the
default crate stays wasm-clean; stdio bridging is NOT here (alktty
owns process stdio).
- Tests (tests/local_halves.rs, 7, feature-gated): TCP/UDP/unix dial
round-trips through the real establisher path, the 1400-byte MTU
datagram, the truncation fail-loud probe, dial-refusal ->
dial_failed, listen producer over a real TCP listener end-to-end.
Verified: cargo test green (default + --all-features, 44 + 7),
clippy -D warnings (default + all-features + wasm32), fmt clean,
cargo check --target wasm32-unknown-unknown passes.
- AcceptFn: the injected accepted-connection source (the assembly
layer owns the listener + accept loop; the protocol never binds —
OQ-TN-04).
- AcceptQueue: the protocol-side queue contract (async push/pop/
close; FIFO always-before-take ordering; close-while-waiting
resolves None). Empty-queue posture is assembly-owned (a late
accept is legitimate; resource_shortage is the closure's mapping;
the wrapper's 10s establishment timeout is the backstop).
- listen_establisher: same open op, same params, same typed errors —
registry namespace gate → accept() → Establishment::new(plan);
the pump handler is untouched (plan-flow with a different source).
- register_tunnel_listen_openable: same spec/pump registration with
the listen establisher (the honest shape vs a dial/accept enum:
the establisher is the only difference; one establisher per op id
per session registry — documented).
- Tests (tests/producer_listen.rs, 6): listen flow end-to-end, FIFO
ordering across two opens (R-01 plan-flow, listen-flavored), empty
queue -> resource_shortage, closed listener -> dial_failed,
unknown resource -> unknown_resource, late-push wait-then-resolve.
- Harness: RegistrationMode enum + wire_listen.
- TargetHandle gains a structural Debug impl (test ergonomics).
Verified: cargo test green (44), clippy -D warnings (native + wasm32),
fmt clean, wasm32 check passes.
Split the last open substrate-placement question:
- Unix: ships with the local feature v1 (dial_unix — same halves
shape as TCP; the wire enum already carried unix per ADR-001;
the params task's schema list includes all three values)
- Stdio: OUT of scope — a spawned process's stdin/stdout/stderr IS
alktty's pipe mode (LocalTtyBackend + tokio::process + Stdio::piped,
alktty tty-local.md): three multiplexed logical streams + the
exit-code control chunk (alktty ADR-004) + signal forwarding
(REQ-TTY-02). A stdio bridge here would be alktty's runner mode
with the terminal stripped out — a strictly worse duplicate that
also drops the semantics that matter (a byte tunnel has neither
exit codes nor signals). Remote command execution composes via
alktty on the same channels substrate.
Updated: open-questions.md OQ-TN-14 (resolved), overview.md feature
gate + deps + OQ summary, producer.md OQ ref, OQ-TN-10 promotion
(#3 split), phase-0-findings + both POC summaries' resolution notes,
params task (schema enum includes unix), local-socket-halves task
(unix ships, stdio does NOT — with the composition rationale),
oq-tn-14-tracker task repurposed (boundary-maintenance: re-opens only
if a consumer needs stdio-without-process-semantics — which would
need its own ADR, or if the alktty/alktunnels boundary needs
sharpening).
Verified: taskgraph valid (12 tasks, no cycles)