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
147 lines
8.0 KiB
Markdown
147 lines
8.0 KiB
Markdown
---
|
|
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) |