--- status: draft last_updated: 2026-09-07 --- # 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](overview.md) | draft | Crate purpose, the resource model in brief, dependencies, ALPN, feature gates, module map | | [wire.md](wire.md) | draft | The wire format: open-op params JSON (ADR-001), 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](producer.md) | draft | The producer half: the `channels/tunnel/sub` open op, the establisher (dial + listen variants), the pump handler, registration, scope gating | | [consumer.md](consumer.md) | draft | The consumer half: `TunnelSession` (stream + datagram variants), the reverse-flow initiator, teardown ownership (ADR-005) | | [bast.md](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](open-questions.md) | draft | Centralized OQ tracker (OQ-TN-01..14; the Phase 0 ledger promoted) | ## Applicable ADRs alktunnels-native ADRs (001..005). 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](decisions/001-open-params-layout.md) | Open-Op Params Layout: Resource + Substrate | Accepted | | [002](decisions/002-alpn-strategy.md) | Single `alk/tunnel` ALPN (Option A) | Accepted | | [003](decisions/003-codec-and-udp-framing.md) | Data-Plane Codec: Raw Pass-Through (Stream) / Mandatory Length Framing (UDP) | Accepted | | [004](decisions/004-no-backend-trait.md) | No TunnelBackend Trait — Halves Functions at the Assembly Layer | Accepted | | [005](decisions/005-consumer-session-owns-teardown.md) | The Consumer Session Type Owns Teardown | Accepted | | [006](decisions/006-access-control-posture.md) | Access-Control Posture — the Open Gate Is the Security Boundary | Accepted | ## Key Design Principles 1. **A tunnel is a resource, not an address.** `params` identify a produced resource + substrate discriminator; the producer owns the backing (local port, docker container, in-process service, unix socket). No URL-style general addressing in the wire — rich addressing enters only through `-D`/dynamic composition, which speaks its protocol inside the tunnel payload. See [ADR-001](decisions/001-open-params-layout.md) and [wire.md](wire.md). 2. **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. `-L` and `-R` are the same open op with the entry point on different machines — no protocol-level direction (the hub-owns-the-connection model, OQ-TN-03). Validated by the reverse-flow POC: the worker serves on the connect side; the hub initiates per accept. See [producer.md](producer.md) and [consumer.md](consumer.md). 3. **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](decisions/003-codec-and-udp-framing.md) and [wire.md](wire.md). 4. **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). The `OpenHandler`'s returned `JoinHandle` tracks the data-plane lifetime (early return = teardown-at-birth, R-02). See [producer.md](producer.md). 5. **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](decisions/004-no-backend-trait.md). 6. **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](decisions/005-consumer-session-owns-teardown.md) and [consumer.md](consumer.md). 7. **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). 8. **Security posture: the open gate is the boundary.** Scope-gated opens (`TUNNEL_OPEN_SCOPE = "tunnel:open"`, stable once published — ADR-006), alkcall's op-level ACL via `register_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, no tunnel-specific policy machinery in v1. See [ADR-006](decisions/006-access-control-posture.md) and [producer.md](producer.md). 9. **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](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 = the `Establishment` plan payload, R-02 = the `OpenHandler` lifetime 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) - 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)