- 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
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-09-16 (the graduation ADRs 007/008 + the amendments) |
alktunnels — Architecture
Arbitrary bidirectional tunnels over alkcall channels: the
alk/tunnel-family producer/consumer protocol crate. This directory
holds the architecture spec docs and the ADRs. It is the sibling of
alktty's architecture (/workspace/@alkdev/alktty/docs/architecture/)
— same substrate (alkcall channels), same shape (a producer
open-handler + a typed consumer session), different service (tunnels:
raw byte/datagram transport, not terminal sessions).
Documents
| Document | Status | Description |
|---|---|---|
| overview.md | draft | Crate purpose, the resource model in brief, dependencies, ALPN, feature gates, module map |
| wire.md | draft | The wire format: open-op params JSON (ADR-001; the direct op's {substrate, target} — ADR-007), the bound reply field + the forwarded op (ADR-008), the data-plane codec (raw pass-through for stream substrates; mandatory [len: u16 BE] framing for UDP — ADR-003), sentinels, half-close semantics |
| producer.md | draft | The producer half: the channels/tunnel/sub + channels/tunnel/direct open ops, three establisher shapes (dial, accept-first listen, bind-first listen), the pump handler, the forwarded-op opener side, registration, scope gating |
| consumer.md | draft | The consumer half: TunnelSession (stream + datagram variants, open/open_direct), TunnelListener (the listen flow), the reverse-flow initiator, teardown ownership (ADR-005/008) |
| bast.md | draft | The BAST (Binary Abstract Syntax Tree) document for the data-plane's binary framing (the UDP [len: u16 BE] codec — ADR-003); the stream pass-through has no binary framing to describe |
| open-questions.md | draft | Centralized OQ tracker (OQ-TN-01..16; the Phase 0 ledger promoted) |
Applicable ADRs
alktunnels-native ADRs (001..008). Upstream ADRs (alkcall/alknet) are
referenced by their own numbers and are NOT ported — the alkcall
originals at /workspace/@alkdev/alkcall/docs/architecture/decisions/
remain authoritative for the shared surfaces (establishment, channels
wire format, pump_bidi, identity seam).
| ADR | Title | Status |
|---|---|---|
| 001 | Open-Op Params Layout: Resource + Substrate | Accepted; amended (params layout is per-op — the direct op's params ride ADR-007) |
| 002 | Single alk/tunnel ALPN (Option A) |
Accepted; amended (flavor-form op ids: …/sub, …/direct, …/forwarded on the one ALPN) |
| 003 | Data-Plane Codec: Raw Pass-Through (Stream) / Mandatory Length Framing (UDP) | Accepted |
| 004 | No TunnelBackend Trait — Halves Functions at the Assembly Layer | Accepted |
| 005 | The Consumer Session Type Owns Teardown | Accepted |
| 006 | Access-Control Posture — the Open Gate Is the Security Boundary | Accepted; amended (tunnel:direct/tunnel:forwarded scopes; the hub re-produce policy sentence) |
| 007 | The Direct Open Op — Dynamic-Target Egress | Accepted |
| 008 | Listen Metadata — Bind-First Establisher and the Forwarded Op | Accepted |
Key Design Principles
-
A tunnel is a resource, not an address — with one additive exception. Base params identify a produced resource + substrate discriminator; the producer owns the backing (local port, docker container, in-process service, unix socket). Rich addressing enters through
-D/dynamic composition, which speaks its protocol inside the tunnel payload. The exception (ADR-007): the direct op lets a consumer ask the producer to dial an arbitrary target — a different capability on its own op and scope (tunnel:direct), never implied bytunnel:open. See ADR-001, ADR-007, and wire.md. -
Role follows the resource; connection direction is independent. Whoever can reach the target is the producer (registers openable channels); whoever wants the bytes is the consumer.
-Land-Rare the same open op with the entry point on different machines — no protocol-level direction (the hub-owns-the-connection model, OQ-TN-03). The composed-Rflow rides the bind-first establisher- the forwarded op (ADR-008). Validated by the reverse-flow POC: the worker serves on the connect side; the hub initiates per accept. See producer.md and consumer.md.
-
Raw pass-through for stream substrates; mandatory length framing for UDP. A tunnel has one data stream per direction — no sub-demux key, no tunnel-level header on stream substrates (0 B tunnel overhead; the channels 8-byte header is the only cost). UDP rides
[len: u16 BE]framing — mandatory for correctness (F-2; the rationale and the executable pin live in ADR-003). See ADR-003 and wire.md. -
The two-pump shape is
alkcall::channels::pump_bidi— never hand-rolled. The producer's pump handler and the consumer-side pump both consume the upstream helper (ADR-050); each pump shuts down the opposite sink on completion (half-close semantics fall out). TheOpenHandler's returnedJoinHandletracks the data-plane lifetime (early return = teardown-at-birth, R-02). See producer.md. -
No backend trait. Stream substrates need "produce boxed halves for a resource" — a function, not a trait (the UDP POC's OQ-TN-05 finding). The inversion point is the assembly layer wiring substrate halves into the establisher/pump; substrate types stay in feature-gated backend modules. The listen variant (producer accepts instead of dialing) is an establisher shape, not a trait. See ADR-004.
-
The consumer session type owns teardown. One session = one channel = one tunnel (the channel ID is the flow key). The adopted side has no wrapper awaiting its pump (reverse POC W3) — the session must hold the pump handle and reap the channel on completion/close. See ADR-005 and consumer.md.
-
Backpressure and limits are inherited, not redefined. Bounded per-channel buffers, the 256-channel cap, monotonic IDs, and the zero-length-sentinel EOF convention are alkcall channels invariants; the tunnel crate consumes them and does not build a second demux/mux (AGENTS.md convention 10).
-
Security posture: the open gate is the boundary. Scope-gated opens (
TUNNEL_OPEN_SCOPE = "tunnel:open",TUNNEL_DIRECT_SCOPE = "tunnel:direct",TUNNEL_FORWARDED_SCOPE = "tunnel:forwarded"— each stable once published; ADR-006 + Amendment 1), alkcall's op-level ACL viaregister_openable; caller identity resolves in the 0.7.0 precedence order (token >ServingConfig.identity> transport); the establisher/pump handler see the per-call opener identity. No target allowlists in the protocol (an assembly establisher MAY enforce them — policy, not protocol). Hub re-produce: the hub's ACL governs the final dial (the hub is the network boundary). See ADR-006, ADR-007, and producer.md. -
Wasm-clean default crate. The default crate (protocol only) compiles to
wasm32-unknown-unknown; socket/platform I/O lives in feature-gated backend modules (AGENTS.md conventions 4/14), same as alktty.
Relevant Open Questions
All open questions are tracked in
open-questions.md. The Phase 0 OQ ledger
(OQ-TN-01..10, docs/research/phase-0-findings.md) is promoted there;
the questions affecting the spec set are summarized in each doc's Open
Questions section.
References
- Phase 0 research:
docs/research/phase-0-findings.md(OQ ledger),docs/research/poc-summary.md(UDP POC),docs/research/reverse-poc-summary.md(reverse-flow POC),docs/research/ssh-socks5-survey.md(SSH/SOCKS5/udpgw prior art) - Upstream: alkcall ADR-047 (openable ALPNs are operations), ADR-049
(establishment phase + amendment 2 plan payload), ADR-050
(
pump_bidi), ADR-022 §2 (both-sides serving), ADR-047 §5 (connection-owner allocation), ledger CF-005/CF-006 (identity seam); alknet ADR-078 (two-pump contract), ADR-071/093 (channels wire format); alkcall ADR-042 (hub relay) - Upstream reviews/ledger (the spec cites these IDs):
/workspace/@alkdev/alkcall/docs/reviews/007-establishment-follow-ups-review.md(R-01 = theEstablishmentplan payload, R-02 = theOpenHandlerlifetime contract, R-03 =pump_bidi) and/workspace/@alkdev/alkcall/docs/reviews/consumer-findings-ledger.md(CF-005/CF-006 = the identity seam, resolved in 0.7.0) - The graduation research (the two new ADRs implement it):
docs/research/tunnels-graduation.md; the upstream alkcall asks (review 008) landed in alkcall 0.8.0 — flavor-form discovery derivation + the establisher → replyboundprojection, plus the in-treeChannelRelay/HubLegTemplate(alkcall ADR-051) - Sibling precedent: alktty (
/workspace/@alkdev/alktty) — the producer/consumer-on-channels shape, wasm-clean default, feature-gated local backend, BAST convention - POC code:
/workspace/alktunnels-udp-poc(forward),/workspace/alktunnels-reverse-poc(reverse)