Files
alktunnels/docs/architecture/open-questions.md
T
glm-5.3-flash f315b0e0ca docs+tasks: ADR-008 Amendment 1 (correlation token); decompose the graduation into tasks/tunnels
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).
2026-09-18 14:26:21 +00:00

16 KiB


status: draft last_updated: 2026-09-18 (OQ-TN-16 updated — ADR-008 Amendment 1: the correlation token)

alktunnels — Open Questions

Centralized tracker. The Phase 0 OQ ledger (docs/research/ phase-0-findings.md, OQ-TN-01..10) is promoted here with statuses current as of the Phase 1 spec; new Phase 1 questions continue the OQ-TN numbering. Format per docs/sdd_process.md.

Theme 1: Wire format and addressing

OQ-TN-01: Target addressing format (open-op params shape)

  • Origin: phase-0-findings.md (OQ-TN-01)
  • Status: resolved — ADR-001
  • Priority: high
  • Resolution: 2026-09-07 (Phase 1). params = self-contained JSON object: { "resource": <string>, "substrate": <string> }. The resource identifier is the producer's stable name (a registered resource id, not an address); substrate is the extensible discriminator (tcp | udp | unix extensible, additive). The producer owns the backing; no URL-style general addressing. Reframe
    • survey input (ssh-socks5-survey §OQ-TN-01 residue) + both POCs.
  • Cross-references: OQ-TN-02, OQ-TN-03, ADR-001, ADR-002

OQ-TN-02: Datagram substrates (UDP) — boundary preservation

  • Origin: phase-0-findings.md (OQ-TN-02)
  • Status: resolved — ADR-003
  • Priority: high
  • Resolution: split by path (2026-09-06 survey) + F-2 mandate (2026-09-07 reverse POC). Base open-op UDP resources: endpoint-at-open (one channel = one flow). Dynamic/-D UDP: per-datagram addressing inside the tunnel payload, assembly-layer. Boundary preservation: mandatory [len: u16 BE] per-datagram framing — mandatory for correctness, not cosmetics (F-2; the full rationale and the executable pin live in ADR-003). Under the codec, len=0 is a legal empty datagram (forward POC validated); EOF is the channels-level sentinel; the layers never collide.
  • Cross-references: OQ-TN-01, OQ-TN-07, ADR-003

OQ-TN-03: Direction semantics (-L / -R / dynamic)

  • Origin: phase-0-findings.md (OQ-TN-03)
  • Priority: high
  • Status: resolved — hub-owns-the-connection model (2026-09-05) + the reverse-flow POC (2026-09-07, reverse-poc-summary.md). No protocol-level direction: role follows the resource. -L/-R are the same open op with the entry point on different machines; no advertisement op is 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). -D composes at the assembly layer ("tunnel a socks5 connection"), out of base-crate scope.
  • Cross-references: OQ-TN-04, OQ-TN-08, ADR-004, ADR-005

OQ-TN-04: No forced local binding

  • Origin: phase-0-findings.md (OQ-TN-04)
  • Priority: medium
  • Status: resolved — binding is always assembly-layer and optional, on either side; the protocol never binds. API surface: dial flows with no local bind (both POCs), listen flows where the binding is the producer's establisher (listen variant, ADR-004), unbound/abstract flows (in-process pipes — the pump is generic over boxed halves). The concrete produce/consume surface is spec'd in producer.md / consumer.md (ADR-005).
  • Cross-references: OQ-TN-03, ADR-004, ADR-005

Theme 2: Crate architecture

OQ-TN-05: Backend inversion point — TunnelBackend trait?

  • Origin: phase-0-findings.md (OQ-TN-05)
  • Status: resolved — ADR-004
  • Priority: medium
  • Resolution: 2026-09-07 (Phase 1). No trait for substrate access: "produce boxed halves for a resource" is a function (UDP POC, OQ-TN-05 executable input). The only remaining thread — the hub re-produce composition — resolves the same way: a hub proxy is a consumer + producer composed at the assembly layer (the reverse POC's hub is exactly this shape minus re-export); a composition helper is deferred(scope) until a concrete hub-proxy consumer exists (OQ-TN-12).
  • Cross-references: OQ-TN-12, ADR-004

OQ-TN-06: The two-pump helper — extract now?

  • Origin: phase-0-findings.md (OQ-TN-06)
  • Priority: medium
  • Status: resolved (upstream) — alkcall::channels::pump_bidi landed in alkcall 0.6.0 (ADR-050, review 007 R-03), shape as this OQ sketched it; this crate is cited as convergence input. alktunnels consumes the helper; hand-rolling is out.
  • Cross-references: ADR-005 (consumer pump), ADR-050 (upstream)

Theme 3: Protocol surfaces

OQ-TN-07: ALPN strategy

  • Origin: phase-0-findings.md (OQ-TN-07)
  • Status: resolved — ADR-002
  • Priority: high
  • Resolution: 2026-09-07 (Phase 1). Option A: single alk/tunnel ALPN; the substrate discriminator in params selects the framing. Prior art (SSH one channel mechanism; SOCKS5 one control connection; udpgw self-describing framing) gives no reason for the split; option B's consumer-side API bifurcation buys nothing.
  • Cross-references: OQ-TN-01, OQ-TN-02, ADR-001, ADR-002

OQ-TN-08: Access control and ownership scope

  • Origin: phase-0-findings.md (OQ-TN-08)
  • Status: resolved — ADR-006 (2026-09-07 — the posture promoted to a decision). The resource is owned by the far side (alkcall op-level ACL applies as-is; no tunnel-specific policy layer, no allowlists in v1; the ownership seam is the establisher seeing the per-call opener identity). Discovery = the ACL-filtered ops listing (openable channels are operations, ADR-047); OperationSpec.description round-trips through discovery (review 006 E-02). The live resource-enumeration half (alkcall OQ-40) stays deferred upstream. Identity: 0.7.0's precedence chain (token > ServingConfig.identity > transport), per-call opener identity on the open-op hooks (CF-005/CF-006). The tunnel:open scope constant is stable once published (ADR-006 §2). -D = "tunnel a socks5 connection" (target selection in the socks5 protocol).
  • Cross-references: OQ-TN-03, ADR-006, producer.md (scope gating)

OQ-TN-09: Lifecycle, teardown, and error reporting

  • Origin: phase-0-findings.md (OQ-TN-09)
  • Priority: high
  • Status: resolved (superseded by alkcall ADR-049, 0.5.0; narrowed through 0.6.0/0.7.0) — establishment is the open op's awaited phase; dial failure is a typed channel:open_failed call error (reason ∈ dial_failed / unknown_resource / resource_shortage / handler_error / timeout), never a phantom channel. Residual resolved by the POCs: half-open semantics = the two-pump contract (W4 validated); the consumer session owns teardown (W3, ADR-005); v1 needs no mid-stream control frame (pump-phase failures are EOF-shaped by design, ADR-049 §6).
  • Cross-references: ADR-003 (sentinels), ADR-005, alkcall ADR-049

OQ-TN-10: POC scope (Phase 0 closure)

  • Origin: phase-0-findings.md (OQ-TN-10)
  • Priority: low
  • Status: resolved — UDP POC DONE (2026-09-06, 17 tests), reverse-flow POC DONE (2026-09-07, 14 tests; re-validated over alkcall 0.7.0, 16 tests). #3 split (OQ-TN-14): unix ships with the local feature; stdio is OUT — alktty's pipe mode owns process stdio (exit codes + signals are tty semantics). #4 (helper spike) resolved by alkcall ADR-050. Findings F-1 (plan payloads Send + Sync — documented upstream) and F-2 (UDP codec mandatory) recorded; W1 filed upstream and RESOLVED (CF-005/CF-006).
  • Cross-references: ADR-003, OQ-TN-05, OQ-TN-06

Theme 4: Phase 1 residues (new)

OQ-TN-11: Resource naming — collision domain and lifecycle of a resource id

  • Origin: OQ-TN-01's residue (stable resource name vs target address), carried into Phase 1
  • Status: partially resolved — the collision-domain half is resolved by ADR-001: the domain is the producer's registry (per-producer; two producers on the same connection may expose the same resource name independently — the open op resolves against the SERVING side's registry). The lifecycle half stays open: is a resource registry a producer-side assembly construct (the POCs' HashMap — the leading candidate), or does it need protocol-level register/rename/retire ops?
  • Priority: medium
  • Impacts: producer.md's ResourceRegistry shape and the discovery story (OQ-TN-08); a protocol-level registry op would be a new wire surface (one-way door).
  • Resolution: lifecycle half pending — no prior-art pressure (SSH lists no forwards in-band; discovery is the ops listing); decide when a consumer needs shared/lifecycle-managed resources.
  • Cross-references: ADR-001, OQ-TN-08

OQ-TN-12: Hub re-produce composition helper

  • Origin: OQ-TN-05's remaining thread (2026-09-05 hub model)
  • Status: resolved (upstream, 2026-09-18) — alkcall 0.8.0 ships the in-tree ChannelRelay + HubLegTemplate/HubLegImports (alkcall ADR-051, review 008 Unit 3): the relay openable wraps from_call-imported marked specs on the consumer-leg registry via the full open-op wrapper (ACL, cap/ledger, establishment bound, teardown), the translate hop calls the producer leg with forwarded_for from the consumer's per-call identity, the byte-forward hop is pump_bidi awaited inline, the spoke reply's channel_id is stripped/replaced (other fields ride through — bound flows end-to-end per-hop truthful), and the adopted producer-leg entry reclaims via a Drop guard on every plan path. The composition helper a hub proxy needed is upstream — this crate composes it (OQ-TN-05's resolution stands: no tunnel-side trait). Tracker task: architecture/oq-tn-12-tracker (tasks/architecture/).
  • Priority: low
  • Cross-references: OQ-TN-05, ADR-004, alkcall ADR-042 (relay as amended by ADR-051), ADR-008 §6 (the hub policy sentence)

OQ-TN-13: UDP truncation semantics at the codec boundary

  • Origin: forward POC Issues Surfaced #3
  • Status: resolved — ADR-003 (2026-09-07): fail-loud at the adapter level. A receive buffer smaller than the datagram is an error, never a silent truncation — a truncated datagram would corrupt the length-framed stream's framing invariants (the reverse POC's F-2 rationale: the codec's wire discipline is what keeps empty datagrams unambiguous; silent truncation is the same class of invariant break). The codec's MTU bound (65535) makes truncation unreachable for well-formed peers; failing loud turns a spec violation into a typed error.
  • Priority: medium
  • Impacts: the local feature's UDP adapter and TunnelSession::recv_datagram's error surface; consumer-visible (an error vs silent truncation is a behavioral difference), so the decision rides an Accepted ADR before the first consumer.
  • Cross-references: ADR-003, wire.md

OQ-TN-14: Unix-socket and stdio substrate placement

  • Origin: OQ-TN-10 #3 (the cheap un-POC'd item)
  • Priority: low
  • Status: resolved 2026-09-07 (split by substrate):
    • Unix: in scope for the local feature v1 — same halves shape as TCP (dial_unix), cheap, wire enum already carries unix (ADR-001). Implemented by tunnels/local-socket-halves.
    • Stdio: OUT of scope — alktty owns it. A spawned process's stdin/stdout/stderr IS a terminal session in alktty's model: LocalTtyBackend's pipe mode (tokio::process::Command + Stdio::piped(), alktty tty-local.md) is exactly the stdio case — 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 (exit code, signals — a byte tunnel has neither). Consumers needing to run commands remotely compose: alkcall dial → alktty session (pipe mode) — the two crates are siblings on the same channels substrate, not competitors. (The OQ-TN-10 #3 "cheap item" was scoped before the sibling relationship was this clear.)
  • Impacts: tunnels/local-socket-halves (unix yes, stdio no); the wire enum's unix value now has a v1 implementation path.
  • Cross-references: ADR-003, ADR-004, overview.md; alktty tty-local.md §pipe mode, alktty ADR-004 (exit code on a control chunk)

Theme 5: The graduation (alksocks consumer, 2026-09-15/16)

OQ-TN-15: The direct op (dynamic-target egress)

  • Origin: docs/research/tunnels-graduation.md §2 (the alksocks graduation discussion, 2026-09-15)
  • Priority: high
  • Status: resolved — ADR-007 (2026-09-16). Option (a): a second Sub-typed open op channels/tunnel/direct on the same alk/tunnel ALPN, params {substrate, target} (self-describing JSON target keyed by the sibling substrate — ATYP bytes rejected: not schema-describable, no unix path form), scope tunnel:direct (never implied by tunnel:open — structural ACL separation), dial-only (no listen/direct-BIND form — ingress is the listen + forwarded surface). The op-id convention needed an upstream alkcall amendment (ADR-047's discovery derivation was /sub-shaped) — filed in alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md (ask #1) and landed in alkcall 0.8.0 (ADR-047 amendment 3: flavor-form op ids + the explicit channel_open_alpn discovery field; hub relay reconstructs the marker).
  • Cross-references: ADR-001 (per-op amendment), ADR-002 Amendment 1, ADR-006 Amendment 1, wire.md, producer.md

OQ-TN-16: Listen metadata (bound-addr discovery + peer fidelity)

  • Origin: docs/research/tunnels-graduation.md §3 (the alksocks BIND POC's two asks: bound-addr without consuming an accept; peer metadata on accepted handles)
  • Priority: high
  • Status: resolved — ADR-008 (2026-09-16; amended 2026-09-18 — Amendment 1). The forwarded-tcpip shape adopted over per-ask carriers: (1) a bind-first listen establisher — establishment ends at bind time, so the open-op reply carries the real OS-chosen address via an additive bound field (ask #1; the carrier is the establisher → reply projection — filed as the review's ask #2 and landed in alkcall 0.8.0 as ADR-049 amendment 3, Establishment::with_reply_field + open_channel_with_reply), and the address-first-vs-accept-first BIND deadlock + the 10s establishment-bound problem dissolve with the shape; (2) the forwarded op channels/tunnel/forwarded (accept-as-open) — the producer opens a channel per accept with {listen, substrate, peer} in params (ask #2's carrier; the TargetHandle.peer option is superseded). Amendment 1: the correlation key is a producer-minted token (listen reply field + forwarded param), not the listen channel's ID — the serving-side handler never sees its own channel ID (alkcall 0.8.0's OpenHandler surface) and generic relays don't rewrite params, so a producer-minted token (which rides reply fields and params through hubs untouched) is the only key that works end-to-end. The accept-first listen shape stays for accept-queue-backed resources. Bonus from the same release: the in-tree ChannelRelay + HubLegTemplate (alkcall ADR-051) pass bound/listen through the hub per-hop truthful (ADR-008 §6 realized).
  • Cross-references: ADR-002 Amendment 1 (the flavor-form op id), ADR-006 Amendment 1 (tunnel:forwarded), ADR-005 (teardown ownership extended to TunnelListener), producer.md, consumer.md, wire.md