- Rewrote the remaining em dashes outside docs/reviews/ (preserved as
historical record, matching the alktty precedent): tests/ (313),
fuzz/ (34 plus one shell comment), tasks/ (671 plus 12 kept in
fences), docs/research + sdd_process + plans (525 plus 25 in
fences), docs/architecture/overview.md fence labels (13), README,
CHANGELOG, AGENTS.md, fuzz/README.md (176), and the
.opencode/agents prompts (110)
- Rewrote fenced content too: ASCII tree and module-map label
separators, Rust snippet comments, and template placeholders; wire
data, JSON/BAST examples, and transcripts survive verbatim
- Rewrote 6 assert/expect message literals in tests/ and 2 format
strings in fuzz/shared/src; no test data changed
- Added Project Convention 18: no em dashes in prose (doc comments,
markdown including docs/ and tasks/, agent prompts, commit
messages); fence content rule, en-dash clause rule, and the
no-spaced-hyphen prohibition follow alktty's convention 15
Follows 6b20924 (src doc comments) and cd0afe0 (docs/architecture).
Verification: cargo test (141 pass); cargo clippy --all-targets -D
warnings; cargo fmt --check; cargo doc --no-deps; wasm check and
clippy for wasm32-unknown-unknown with -D warnings; em dash grep over
the repo returns matches only in docs/reviews/
Rewrote every prose em dash under docs/architecture/ with natural
grammar (sentence splits, colons, semicolons, parentheses, commas);
wire-format fences, JSON/BAST examples, and byte diagrams untouched.
Verified: grep -rln '—' docs/architecture/ returns only
overview.md's fenced dependency/module-map blocks (12 dashes),
left verbatim per the fenced-code rule; all other files at zero.
- docs/reviews/003-…alkssh-input.md: Status block now resolved —
W1 absorbed (plan 003 §Decisions 1–4 + ADR-008 Amendment 2); W2/W3
pointed at the plan's §W2/§W3, open until those repos land
- docs/plans/003-…: status header planned → W1 landed (W2/W3 open);
the W1 plan-level AC box ticked; W2/W3 + family-coherence boxes
left unticked (sibling-repo sessions)
- Stale-comment sweep (src/ + docs/architecture/) aligned three
stragglers still riding the pre-amendment miss vocabulary:
- src/producer.rs: the accept-loop arm's "correlation miss →
handler_error-class" comment → the typed unknown_resource
open_failed under the establisher variant (Amendment 2)
- wire.md §The Forwarded Op reply/errors: the miss is the typed
unknown_resource open_failed (establisher variant); base shape's
in-session miss = the assembly-side post-open decline,
correlation-miss at debug
- producer.md §The Forwarded Op: "no establisher" → the two-shape
posture (Amendment 2 pointer); last_updated bumped
Verification: cargo test (14 suites, 0 failures), clippy --all-targets
-D warnings clean, cargo fmt --check clean, cargo doc --no-deps clean.
Implements plan 003 W1's API item (ADR-008 Amendment 2, task
tunnels/corrmiss-forwarded-gate): the assembly-injected correlation
gate wrapped into the alkcall OpenEstablisher dance for the forwarded
op's serving half.
- ForwardedGate: sync gate closure over the parsed
TunnelForwardedParams; a gate Err → typed channel:open_failed
before the channel is born (no phantom channel)
- register_tunnel_forwarded_openable_with_establisher: reuses
make_forwarded_op_handler unchanged (additive-only; the base
registration stays byte-for-byte compatible as the
compatibility + residual-race-fallback posture)
- ForwardedPlan: empty marker plan payload (typed-opaque posture,
payload choice pinned); no reply-field contribution
- Establisher parse failure maps TunnelEstablishError::HandlerError
→ channel:open_failed (deliberately pinned improvement over the
base shape's silent birth-teardown for malformed ingress); gate
errors pass through the existing From<TunnelEstablishError> table
(no new variants; stale token = unknown_resource); timeout: None;
wrapper order (schema → ACL → allocate → establisher → handler)
pinned in the establisher doc
- Two-shape posture doc updates: make_forwarded_op_handler's
correlation-miss block, register_tunnel_forwarded_openable's doc;
wire.md §The Forwarded Op + tunnel_forwarded_spec() doc synced
per Amendment 2's "syncs with the API task's in-code doc updates"
Verification: cargo test (85 green, existing suite untouched);
clippy --all-targets -D warnings (native + wasm32); cargo fmt --check;
cargo doc --no-deps (0 warnings); --all-features test run green
- The serving side of the forwarded op gains an establisher variant:
an assembly-provided gate closure (over TunnelForwardedParams) the
crate wraps into the alkcall OpenEstablisher dance via
register_openable_with_establisher; a stale/missing listen token
fails the open as a typed channel:open_failed with
details.reason = unknown_resource — never a phantom channel
(ADR-049's posture extended to the forwarded op's serving side)
- unknown_resource reused as-is; the wire vocabulary is unextended
and wire.md is untouched (the empty wire delta stated explicitly)
- Gate order pinned: the tunnel:forwarded ACL precedes the
establisher (alkcall's open-op wrapper order)
- The residual post-open race keeps teardown-at-birth; canonical
event name correlation-miss at debug (assembly-side convention);
the producer-side generic arm stays warn with the typed reason
- Additive-only surface: the base register_tunnel_forwarded_openable
stays; the establisher variant is the recommended default posture
- Tri-vocabulary history recorded (alkssh correlation-miss/debug,
alksocks prose/warn, alktunnels handler_error-class generic
arm/warn + the fabric-side silence) with sources
- The amended §3 sentence is marked inline, not silently replaced
Resolves review 003 via docs/plans/003-correlation-miss-resolve.md
§Decisions 1-4 (W1's ADR bullet). Doc-only; review 003's status block
and wire.md belong to other tasks and are untouched.
Verification: md renders coherently (read back); alkcall 0.10.1
EstablishmentError/reason() mapping and wrapper order verified in the
registry source before pinning
Plan for the review-003 resolution: route the forwarded-op correlation
miss through an establisher (typed unknown_resource failure, the
ADR-049 posture extended), adopt alkssh's correlation-miss as the
canonical event name for the residual decline, and align the family
(alkssh + alksocks waves). Adds the session's verification findings:
the alksocks prose variant (third vocabulary) and the fully-silent
in-session miss on the fabric side.
Verified: cargo fmt --check, cargo test (13 suites ok), clippy
--all-targets -D warnings (native + wasm32). Docs-only change.
- 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
- 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.
- 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
- 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.
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).
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.
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)
The Phase 0 OQ ledger (OQ-TN-01..10) promoted into
docs/architecture/open-questions.md (now OQ-TN-01..14, with four
new Phase 1 residues). Six ADRs and four spec docs, all Draft, all
decision-shaped per the SDD process (ADRs carry the WHY; specs carry
the WHAT and reference by number):
ADRs:
- 001 open-params-layout: params = {resource, substrate} — the
producer's stable name, not an address; wire-stable from the first
consumer; unknown substrates fail loudly at schema time
- 002 alpn-strategy: single alk/tunnel ALPN (option A); substrate in
params selects the framing; prior art (SSH/SOCKS5/udpgw) gives no
reason for the split
- 003 codec-and-udp-framing: raw pass-through (stream) / mandatory
[len: u16 BE] (UDP) — F-2 mandate recorded (the codec is mandatory
for correctness, not cosmetics); len=0 = legal empty datagram;
OQ-TN-13 resolved fail-loud (truncation)
- 004 no-backend-trait: halves functions at the assembly layer;
listen is an establisher shape; hub re-produce deferred(OQ-TN-12);
trait re-evaluated at the alknet ADR-078 convergence threshold
- 005 consumer-session-owns-teardown: TunnelSession with
open/adopt/stream_halves/take_halves/pump_against/close/join/Drop —
the W3 adopter gap closes structurally; Drop is the best-effort
fallback
- 006 access-control-posture: the open gate is the boundary;
TUNNEL_OPEN_SCOPE = tunnel:open (stable once published); op-level
ACL; ownership seam; no allowlists in v1; identity = 0.7.0's
precedence chain
Spec docs:
- overview.md: purpose, resource model, deps (alkcall 0.7.0,
wasm-clean default, local feature), module map
- wire.md: the open op (params/reply/typed errors), the data plane
by substrate, sentinels + half-close, byte diagrams
- producer.md: spec, establisher (dial + listen shapes), pump
handler (pump_bidi inline, R-02), registration API, ACL posture
- consumer.md: TunnelSession (forward open + reverse adopt
construction), data plane, teardown API (close/join/Drop incl.
pump-less join semantics)
- bast.md: the BAST doc for the UDP codec (convention 12's trigger
fired — the framing IS binary beyond pass-through)
- open-questions.md: OQ-TN-01..14; 01..10 promoted (faithful to the
phase-0 ledger's final states), 11 partially resolved (collision
domain = per-producer registry per ADR-001; lifecycle open),
12 deferred(scope), 13 resolved fail-loud (ADR-003), 14 open
Verified by an architecture-reviewer pass (2 criticals, 6 majors,
10 minors — all fixed: ADR-001/003-vs-OQ decision-state contradictions
resolved; the Layer-2 mislabel corrected to the ADR-047 §4 per-session
fork; alktty/alknet ADR misattributions fixed; codec placement pinned
to the establisher (pump stays substrate-agnostic); reverse-path
construction named (TunnelSession::adopt + pump_against); security
posture promoted to ADR-006; BAST doc written; F-2 rationale deduped;
README tables completed; channel_id > 0; MTU wording fixed; ADR-047 §5
attributions corrected; impacts lines added to unresolved OQs; review/
ledger pointer paths added to README references).
Verification: doc set internal cross-refs all resolve; cargo test,
fmt --check, doc --no-deps clean
alkcall 0.7.0 resolved every open downstream-consumer finding,
including this crate's reverse-flow POC W1 (filed as ledger CF-005,
with the CF-006 corollary):
- CF-005 (a): ServingConfig.identity — the explicit override
- CF-005 (b): transport Connection::identity() propagates to
channel 0 — set_identity before dialing is the mTLS/QUIC path
- CF-006: the establisher/pump handler receive the per-call opener
identity (dispatch-resolved), not the install-time context
- CF-007: ADR-016 code list completed (doc-only)
- F-1 (POC finding): the ChannelPlan type doc now carries the
Send + Sync payload constraint
alktunnels updates:
- Cargo.toml: alkcall 0.6.0 -> 0.7.0 (lockfile resolves 0.7.0)
- AGENTS.md convention 11: pin note updated; the 0.7.0 identity
surface marked load-bearing (precedence order + per-call opener)
- AGENTS.md architecture context: CF-005/CF-006 row added; the
'What the POC does NOT settle' list replaced with the accurate
settled state (Phase 0 complete; Phase 1 = the spec)
- reverse-poc-summary: W1 marked RESOLVED with the remediation
validated from the POC's topology (16 tests over 0.7.0: transport
identity alone authorizes, ServingConfig.identity overrides,
identity-less fails closed, CF-006 witness); F-1 marked documented
upstream; what-the-POC-does-not-validate and verification blocks
updated
- phase-0-findings OQ-TN-10: reverse POC entry notes the 0.7.0
re-validation and W1 resolution
The reverse POC (/workspace/alktunnels-reverse-poc, 8b35818) re-
validated: the default harness now authorizes on transport identity
alone — the 0.6.0-era auth_token workaround is dead code.
Verification: cargo test, clippy --all-targets -D warnings,
fmt --check, wasm32 check, doc --no-deps — all clean
POC at /workspace/alktunnels-reverse-poc (14 tests, clippy/fmt clean,
repeat-run stable) over alkcall 0.6.0:
- The review 007 non-finding trace confirmed by execution: the hub
(accept side) opens tunnel channels toward a connect-side worker
serving its own open op (from_connection_with_serving +
ChannelOperations::register_on + post-hoc openable registration)
- ADR-047 §5 exercised in the Pub-like orientation: the worker
(connect side) allocates odd IDs; the hub adopts
- R-01 plan flow under concurrency: two same-resource opens, distinct
channels, distinct dialed handles — the forward POC's handoff race
is structurally gone
- OQ-TN-03's last open thread closed: no advertisement op needed —
the listener is the initiator's own local resource; a far-side
listener is a producer-side listen establisher over the same open op
- W4 EOF sentinel propagation validated (half-close semantics);
W3 adopter self-reaping pinned as a TunnelSession spec requirement;
W2 post-hoc registration viable
Findings:
- F-1: ChannelPlan's Send+Sync bound constrains plan payloads
(socket halves carry + Sync; spec should pin the constraint)
- F-2: empty UDP datagrams collide with the EOF sentinel in raw
pass-through (pinned by an executable test) — the length-framed
codec is mandatory for UDP; raw pass-through stays
stream-substrate-only. Second validation of the codec decision;
OQ-TN-02 mandate strengthened
W1 (connect-side serving path resolves caller identity only via
payload auth_token) filed upstream as alkcall consumer-findings
ledger CF-005 (alkcall 0d287a9).
Verification: cargo test 14/14, clippy --all-targets -D warnings,
fmt --check — all clean, repeat-run stable
17 tests pass (7 codec + 10 integration). The producer/consumer
shape, ADR-049 establishment integration, codec decision, and typed
establishment errors all validated over alkcall 0.5.0 channels.
Key findings folded into the ledger:
- OQ-TN-05 resolved for stream substrates: no backend trait —
pump_halves generic over boxed halves (TCP into_split, UDP
UdpHalf adapter); remaining thread is only the hub re-produce
composition question
- OQ-TN-06 convergence test satisfied: producer pump and consumer
pump are the same shape modulo channel side; extraction decidable
in Phase 1
- New finding for the alkcall ledger: OpenHandler JoinHandle
semantics are load-bearing and undocumented — a handler that
returns before the data plane starts tears the channel down at
birth (the POC found this by hanging; the pump must be awaited
inline). Plus: Establishment should carry the channel plan
(the reserved extension point) to kill the establisher handoff
side-channel
- UdpHalf truncation semantics flagged for the Phase 1 ADR
(recommend fail-loud over silent truncation)
POC crate: /workspace/alktunnels-udp-poc (db224cc).
Structural rationale: alktty's 5-byte header is a sub-demux key for
five logical streams sharing one BiStream; a tunnel has exactly one
data stream per direction, so no type byte. TCP needs no length
prefix (channels already length-prefixes every chunk). UDP needs
boundary re-framing only, and u16 suffices (UDP max payload 65507).
Wire overhead table: tunnel TCP = 8 B/chunk total (channels header
only), tunnel UDP = 10 B/datagram, vs alktty's 13 B. udpgw
calibration: 5 B + per-datagram address.
Trade-offs accepted: no in-band control path for TCP channels ever
(post-ADR-049 this is clean; escape hatch = new ALPN, not a wire
change); sentinel collision does not exist (UDP len=0 = empty
datagram, legal; EOF is the channels-level sentinel on the BiStream
at a different layer); future multi-endpoint UDP gateway framing
lives inside datagram payloads (udpgw precedent).
Residual for the Phase 1 codec ADR: empty-datagram semantics; u16 BE
over varint (leaned, MTU-bounded anyway).
Research specialist survey (docs/research/ssh-socks5-survey.md):
SSH forwarding model (direct-tcpip/forwarded-tcpip payloads, open-
failure reason codes, tcpip-forward registration lifecycle), SOCKS5
(ATYP, CONNECT, UDP ASSOCIATE), error-vocabulary comparison table,
endpoint-at-open vs per-datagram analysis, and an explicit anti-
prior-art list (SSH window updates, packet-size negotiation, dual
channel numbering, SOCKS5 FRAG/auth/BND semantics, udpgw
KEEPALIVE/pooling — all mechanisms channels already owns or the
substrate change obsoletes).
Key corrections to phase-0 findings:
- OQ-TN-02 category error fixed: SSH has NO UDP forwarding at all;
the earlier '-D UDP associate over SSH' line was wrong (SSH -D
carries only the SOCKS5 TCP control connection). Real UDP-over-
stream prior art: tun2proxy udpgw + SOCKS5's own UDP relay.
- OpenSSH -D is protocol-level indistinguishable from -L (one
direct-tcpip channel per SOCKS CONNECT) — direct confirmation of
the -D = 'tunnel a socks5 connection' resolution.
OQ ledger updates:
- OQ-TN-01: minimal params shape supported ({resource, substrate});
direct-streamlocal is the extensibility template; ATYP not needed
in base params
- OQ-TN-02: RESOLVED as split by path — endpoint-at-open for base UDP
resources; per-datagram addressing only inside the -D payload;
channel ID replaces CONN_ID (no second demux); KEEPALIVE drops
- OQ-TN-07: option A (single alk/tunnel ALPN) strengthened
- OQ-TN-09: reason-code vocabulary from SSH's four codes + detail
string; udpgw ERR bit flagged as the counterexample to avoid
- OQ-TN-10: reverse-flow POC gains the tcpip-forward template
- Checklist: SSH/SOCKS5 survey item checked off
Worktree/branch for repo-dependent POCs; standalone crate in the
global workspace for self-contained POCs (alknet-channels-poc
precedent); findings always land in docs/research/.
- OQ-TN-08 residue updated with the upstream posture: we own alkcall,
upstream asks happen early (alktty/alkhttp precedent — both required
upstream fixes/additions; only 3 downstream dependents now, each
resolved issue makes future dependents cheaper). Ops-listing
metadata is expected to be a small upstream alkcall change, not a
workaround.
- OQ-TN-09: control-frame hunch accepted (self-contained frame,
alktty ADR-006 shape; dial errors are channel-closing, byte EOFs
stay per-direction); frame vocabulary follows the alktty pattern;
concrete set = Phase 1 ADR.
- New prior art section: the alktty stream-splitting pattern
(split BiStream -> ChunkReader/ChunkWriter -> pump to handle halves)
proposed as the general tunnel pattern — 'tty without the five
stream types'. Comparison table (tty vs TCP vs UDP codecs/handles/
pumps); implications for OQ-TN-09 (ctrl frames via stream types),
OQ-TN-05 (handle = boxed AsyncRead/AsyncWrite halves; UDP flow
table is the real design surface), OQ-TN-06 (helper convergence
test against pump_session may be nearly free). Open: codec choice
(reuse 5-byte / stripped / raw + UDP framing).
- Checklist updated accordingly.
Untangles the 'host owns the resource and proxies on top' posture for
tunnels:
- Protocol-crate levels assume the resource is owned by the other side
of the connection; alkcall's existing op-level ACL applies as-is. No
new policy layer, target allowlists, or tunnel-specific ownership
machinery. Proxy/overlay is a downstream (assembly-layer) concern
(hub workers expose tunnels; hub overlays them per-ACL).
- Discovery resolved: openable channels are operations (alkcall
ADR-047), so the existing bidirectional ACL-filtered ops listing IS
tunnel-resource discovery. A consumer learns a socks5/postgres/redis
tunnel is available from the same listing it already uses for ops.
- -D simplifies to 'just tunnel a socks5 connection': socks5 server
lives on the producing side; target selection happens in the socks5
protocol, not tunnel params; the dynamic-target policy residue
dissolves (socks5 resource ACL governs reachability).
Residue for Phase 1: per-resource metadata in the ops listing
(substrate type, name/description) — an alkcall ADR-047 interaction,
not a new mechanism. OQ-TN-01 discovery residue and the hub-model
residue list cross-linked accordingly.
Four corrections from discussion:
- ALPN prefix: alknet/ -> alk/ swap happened in alkcall v0.1.1; docs
must not perpetuate the old prefix (consumers would bake it in)
- XY problem: except in the -D/dynamic composition case, params only
identify a produced resource + substrate discriminator (tcp/udp/
extensible); the producer owns where the resource comes from (most
likely a local port); UDP must be structurally supported even
though TCP dominates expected use
- self-contained JSON open-op object accepted as the params path
(alktty NegotiateRequest precedent)
- russh has no UDP channel support at all — no direct-udp prior art;
noted in OQ-TN-02 and the survey list; SOCKS5 relevance scoped to
the -D composition path
Residue for OQ-TN-01: resource naming shape, discovery mechanism,
exact JSON layout (Phase 1 ADR before first consumer).
Captures the standing hub stance as prior art: the hub owns the
connection and explicitly proxies to expose resources for others.
Role follows the resource — whoever reaches the target is the producer
(registers openable channels), whoever wants bytes is the consumer.
SSH -L/-R collapse to the same producer/consumer pair with the entry
point on different machines (assembly-layer wiring); the exposed port
is a virtual ACL-scoped resource (register_openable shape), not a
bind; the hub proxy is a producer wrapping a consumer, terminating
and re-producing per hop so ACL applies per hop (distinct from
ADR-042 transparent relay); -D composes as a consumer opening
channels with per-connection dynamic targets.
OQ-TN-03 resolved. OQ-TN-04 mostly resolved (binding always
assembly-layer, optional, either side). OQ-TN-01 reframed: params
name a produced resource — resource naming + discovery added. OQ-TN-08
strengthened: register_openable per-resource registration is the
primary gate; remaining residue is the dynamic-target policy hook.
OQ-TN-05 gains the hub re-produce composition sub-question. OQ-TN-02
cross-linked: endpoint-at-open aligns with resource naming; per-
datagram addressing matches the -D composition path.
Documents the previously-undocumented UDP gateway prior art discussed
with the alknet-channels POC agent (/workspace/tun2proxy/src/udpgw.rs):
per-datagram length framing over a stream (LEN|FLAGS|CONN_ID|[SOCKS5
addr]|DATA), SOCKS5 ATYP addressing in-band, CONN_ID flow multiplexing,
keepalive/ERR flag packets, MTU cap, idle expiry. Folds implications
into OQ-TN-01 (addressing fork: per-channel vs per-datagram), OQ-TN-02
(framing mechanics production-proven; remaining fork documented),
OQ-TN-07 (single-ALPN option strengthened), OQ-TN-09 (flag-packet
vocabulary maps to establishment/error frame question), OQ-TN-10 (UDP
POC scope narrowed to channels-layer fit), and the survey checklist.
Also records the transport story: TCP vs QUIC is the alkcall layer's
concern; the tunnel crate is transport-agnostic.
Captures the ten open design questions from the setup discussion:
addressing format, UDP datagram semantics, -L/-R/-D direction model,
no-forced-binding API surface, backend inversion point, two-pump
helper extraction, ALPN strategy, access control scope, lifecycle/
error reporting, and candidate targeted POCs. Includes the settled
foundation (POC-validated prior art), a prior-art survey list, and
the Phase 0 convergence checklist.