docs: Phase 1 architecture spec — ADRs 001..006, spec docs, OQ promotion
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
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-09-07
|
||||
---
|
||||
|
||||
# alktunnels — Overview
|
||||
|
||||
Arbitrary bidirectional tunnels over alkcall channels: TCP, UDP, unix
|
||||
sockets, and other stream or datagram substrates — in the `ssh -L` /
|
||||
`ssh -D` / `ssh -R` sense. A producer/consumer protocol crate riding
|
||||
alkcall channels the same way alktty does (`alk/tty` is the sibling
|
||||
precedent). This document covers the crate's purpose, the resource
|
||||
model in brief, dependencies, the ALPN, and the module map; component
|
||||
details are in the sibling documents ([wire.md](wire.md),
|
||||
[producer.md](producer.md), [consumer.md](consumer.md)).
|
||||
|
||||
## What
|
||||
|
||||
`alktunnels` registers the `alk/tunnel` ALPN's open op on alkcall
|
||||
channels and provides the typed consumer session:
|
||||
|
||||
- **The producer half** (a `channels/tunnel/sub` open op registered
|
||||
via `ChannelCore::register_openable_with_establisher`, alkcall
|
||||
ADR-047/049): the establisher validates params semantically
|
||||
(registry lookup of `{resource, substrate}` → backing), dials (or
|
||||
accepts for) the substrate, and returns the dialed handle via
|
||||
`Establishment::new(plan)`; the pump handler awaits
|
||||
`alkcall::channels::pump_bidi` over the channel and the substrate
|
||||
halves (ADR-050, R-02's lifetime contract).
|
||||
- **The consumer half** (`TunnelSession`, ADR-005): opens tunnel
|
||||
channels (`ChannelClient::open_channel` on the forward path; the
|
||||
reverse path's call + adopt equivalent), splits the channel
|
||||
`BiStream`, presents the substrate-shaped data plane (raw halves or
|
||||
datagram codec), and owns teardown (close/join/Drop).
|
||||
|
||||
The guiding insight (the OQ-TN-01 reframe):
|
||||
|
||||
> A tunnel is a resource, not an address. `params` identify a produced
|
||||
> resource + substrate; the producer owns the backing. Rich addressing
|
||||
> (SOCKS5 ATYP, per-datagram remotes) enters only through the
|
||||
> `-D`/dynamic composition path — inside the tunnel payload, never in
|
||||
> the wire.
|
||||
|
||||
## Why
|
||||
|
||||
The crate's purpose is to be the tunnel library for downstream
|
||||
consumers, the same role alktty plays for terminal sessions:
|
||||
|
||||
- A hub that runs agent workspaces exposes workers' services
|
||||
(postgres, redis, gitea HTTP) as named resources; a coordinator
|
||||
opens tunnels to them over alkcall channels — the VPN/SSH-tunnel
|
||||
service pattern without SSH.
|
||||
- A worker behind NAT dials the hub and serves its own open ops
|
||||
(the reverse flow, `ssh -R`); the hub opens tunnel channels toward
|
||||
it per local accept. Both POCs validated this end-to-end.
|
||||
- A `-D`-style consumer tunnels a SOCKS5 connection; the socks5
|
||||
server (assembly layer) does dynamic target selection inside the
|
||||
tunnel payload.
|
||||
- The protocol layer is substrate-agnostic by construction — the
|
||||
bookkeeping (target addressing, direction, lifecycle) never
|
||||
hardcodes a substrate (AGENTS.md convention 7). TCP, UDP, unix
|
||||
sockets, and in-process pipes ride the same open op, pump shape,
|
||||
and session type.
|
||||
|
||||
## The Resource Model in Brief
|
||||
|
||||
A tunnel channel's life (full detail in [wire.md](wire.md)):
|
||||
|
||||
1. **Open (the establishment phase).** The consumer calls
|
||||
`channels/tunnel/sub` with `params = {resource, substrate}`
|
||||
(ADR-001). The producer's establisher — the open op's awaited
|
||||
phase (alkcall ADR-049) — resolves the resource, dials the
|
||||
substrate, and returns the handle in the plan. Failure is a typed
|
||||
`channel:open_failed` (`dial_failed` / `unknown_resource` /
|
||||
`resource_shortage` / `handler_error` / `timeout`); the channel
|
||||
never exists on the opener's side afterward.
|
||||
2. **Pump (the data plane).** Two pumps, one per direction, via
|
||||
`pump_bidi` (ADR-050): each shuts the opposite sink down on
|
||||
completion; half-close semantics fall out. Stream substrates ride
|
||||
raw pass-through (0 B tunnel overhead); UDP rides the mandatory
|
||||
`[len: u16 BE]` codec (ADR-003, F-2).
|
||||
3. **Teardown.** The serving side is wrapper-managed (R-02); the
|
||||
adopting side's `TunnelSession` owns teardown (ADR-005, W3).
|
||||
EOF sentinels are the channels layer's; the codec never collides
|
||||
with them.
|
||||
|
||||
Direction is not in the protocol: whoever can reach the target is the
|
||||
producer; whoever wants the bytes is the consumer; connection
|
||||
direction (who dialed the transport) is independent of tunnel
|
||||
direction (OQ-TN-03's hub model). `-L`, `-R`, and `-D` are assembly
|
||||
shapes over the same open op.
|
||||
|
||||
## Dependencies
|
||||
|
||||
```
|
||||
alktunnels (default — wasm-clean)
|
||||
├── alkcall 0.7.0 (core types, channels: ChannelCore/ChannelClient/manager,
|
||||
│ pump_bidi, ServingConfig identity seam — CF-005/006)
|
||||
├── tokio (wasm-clean subset: rt, sync, io-util, macros, time)
|
||||
├── bytes, futures, serde/serde_json, thiserror, tracing, async-trait
|
||||
└── (no backend deps — sockets/process live in feature-gated modules)
|
||||
|
||||
alktunnels (local feature) — non-wasm by design
|
||||
└── adds: tokio/net (TCP dial, UDP bind/connect, unix sockets — unix pending OQ-TN-14)
|
||||
```
|
||||
|
||||
Same posture as alktty: the default crate is protocol-only and
|
||||
wasm-clean (AGENTS.md convention 4); substrate I/O is feature-gated
|
||||
(ADR-004). The wire format carries no alkcall-internal types
|
||||
(alktty ADR-006's self-containment rule applies to the tunnel codec —
|
||||
trivial here, since the codec is a 2-byte length prefix).
|
||||
|
||||
## Feature Gates
|
||||
|
||||
| Feature | Contents | Wasm |
|
||||
|---------|----------|------|
|
||||
| *(default)* | params, wire codec, open-op spec, establisher shapes, `TunnelSession` — protocol only | yes |
|
||||
| `local` | TCP/UDP/unix dial + listen helpers — halves-producing dial/listen functions (ADR-004; unix pending OQ-TN-14) | no |
|
||||
|
||||
## Module Map
|
||||
|
||||
```
|
||||
src/
|
||||
lib.rs — re-exports; crate docs
|
||||
params.rs — TunnelParams {resource, substrate} + open-op input schema (ADR-001)
|
||||
wire.rs — the data-plane codec: frame_datagram / DatagramReader (ADR-003)
|
||||
producer.rs — the open op: tunnel_open_spec (scope-gated), establisher
|
||||
shapes (dial + listen), pump handler (pump_bidi),
|
||||
register_tunnel_openable (ADR-004 shapes)
|
||||
consumer.rs — TunnelSession: open / adopt / stream_halves /
|
||||
take_halves / send_datagram /
|
||||
recv_datagram / pump_against / close / join / Drop (ADR-005)
|
||||
error.rs — TunnelError + the typed open-error surface (ADR-049 §4)
|
||||
local/ — the `local` feature module: real socket halves functions
|
||||
```
|
||||
|
||||
Public API surface is `lib.rs` re-exports (AGENTS.md convention 16).
|
||||
|
||||
## Design Decisions
|
||||
|
||||
All design decisions are documented as ADRs in [decisions/](decisions/).
|
||||
|
||||
| ADR | Decision | Summary |
|
||||
|-----|----------|---------|
|
||||
| [001](decisions/001-open-params-layout.md) | Open-op params: resource + substrate | `{resource, substrate}` — the producer's stable name, not an address; wire-stable from the first consumer |
|
||||
| [002](decisions/002-alpn-strategy.md) | Single `alk/tunnel` ALPN | Substrate in params selects the framing; no ALPN split, no API bifurcation |
|
||||
| [003](decisions/003-codec-and-udp-framing.md) | Data-plane codec | Raw pass-through (stream) / mandatory `[len: u16 BE]` (UDP — F-2 mandate); `len=0` is a legal empty datagram |
|
||||
| [004](decisions/004-no-backend-trait.md) | No TunnelBackend trait | Halves functions at the assembly layer; listen is an establisher shape; trait re-evaluated at the 3-consumer threshold |
|
||||
| [005](decisions/005-consumer-session-owns-teardown.md) | Consumer session owns teardown | `TunnelSession` with close/join/Drop — the W3 gap closes structurally |
|
||||
| [006](decisions/006-access-control-posture.md) | Access-control posture | The open gate is the boundary: `tunnel:open` scope (stable once published), op-level ACL, ownership seam, no allowlists in v1 |
|
||||
|
||||
## Open Questions
|
||||
|
||||
Open questions are tracked in [open-questions.md](open-questions.md).
|
||||
Key questions affecting this document:
|
||||
|
||||
- **OQ-TN-11**: resource naming collision domain + lifecycle (partially resolved)
|
||||
- **OQ-TN-12**: hub re-produce composition helper (deferred(scope))
|
||||
- **OQ-TN-13**: UDP truncation semantics (resolved — fail-loud, ADR-003)
|
||||
- **OQ-TN-14**: unix/stdio substrate placement (open)
|
||||
|
||||
## References
|
||||
|
||||
- Phase 0: `docs/research/` (findings, both POC summaries, survey)
|
||||
- Upstream ADRs: see [README.md](README.md) §References
|
||||
- Sibling: alktty (`/workspace/@alkdev/alktty/docs/architecture/`)
|
||||
Reference in New Issue
Block a user