AlknetClient gains an optional SOCKS5 proxy (with_socks5_proxy) so a
native client can hide its real IP from the hub. dial_quic routes QUIC
through SOCKS5 UDP ASSOCIATE (validated by the /workspace/quinn-proxy-poc
PoC — quinn's AsyncUdpSocket + new_with_abstract_socket is the extension
point, 5/5 runs clean); dial_tcp_tls routes through SOCKS5 CONNECT. The
proxy is invisible above the dial (Connection, dispatch, credentials,
TLS config all proxy-unaware) and the no-proxy path is the zero-cost
default (socks5 feature + fast-socks5 dep are opt-in).
SOCKS5 is the sole proxy protocol (covers both TCP and UDP, so no HTTP
CONNECT variant needed). The two distinct SOCKS5 concepts — the
client-dial proxy (ADR-090, transport-layer privacy) and the planned
alknet-socks5 channels data-channel handler (ADR-085 scope, a service
one side offers the other) — compose at the SOCKS5 protocol level
without alknet-type-level coupling.
iroh is the exception: dial_iroh does not consume Socks5ProxyConfig —
iroh's proxy_url covers the relay-exposure surface, but the
direct-connection peer-exposure case is OQ-67 (deferred(unclear) — the
pieces exist but the iroh socket-stack composition isn't clear; does
not block the first hub deployment, which uses QUIC/TCP+TLS).
- ADR-090: Client-Dial SOCKS5 Proxy Seam
- OQ-67: iroh Proxy Support (Direct-Connection Peer Exposure)
- client README: proxy section, struct/builder, Proxy error variant,
socks5 feature gate, deps, assembly example, decisions/open-questions
- README/open-questions: index entries, Current State, OQ count 67/20
Each open question lives in its own file under questions/,
named NNN-slug.md (mirroring the ADR convention). This file is the index:
theme-grouped tables for scannability, plus a cross-theme
Deferred / Blocked section that surfaces the
safe-exit deferrals with their blocking conditions inline — so "what's
currently parked and why" is answerable at a glance.
Status values:
open — Needs to be resolved now. Has a clear path to resolution.
resolved — Decided. The resolution is stated cleanly, without caveats about how it could be changed later.
deferred(scope) — Cannot be resolved yet. The information is genuinely
missing — a crate spec, POC result, or use case that doesn't exist yet.
Has a concrete blocking condition. Not a failure — scope management.
deferred(unclear) — Cannot be resolved yet. The pieces exist (decided
in other ADRs, existing types, existing patterns) but the composition
— how they fit together — isn't clear yet. Resolution requires
investigation (work through examples, maybe POC), not waiting. Has a
concrete investigation target and an impacts field. Not a failure —
honest uncertainty in a poorly-defined problem space.
partially resolved — Some aspects decided, others deferred or open.
dissolved — The question was reframed out of existence (e.g., superseded
by an ADR that retires the premise). Kept for reference.
Impacts field: Every unresolved OQ (open, deferred(scope),
deferred(unclear), partially resolved) should have an Impacts
field stating what it blocks downstream. Be specific: "blocks the first
hub deployment because the hub dials workers" not "blocks the hub
crate." This is the triage signal that makes the deferral's urgency
visible.
Door type classifications follow ADR-009 — they describe reversal cost (how expensive it is to undo), not urgency:
One-way door: Reversal requires rewriting significant code or permanently closes a capability. Getting it wrong is expensive — requires ADR before implementation.
Two-way door: Reversal is cheap or additive. Getting it wrong is recoverable — decide, implement, revert if needed.
Door type is separate from whether a decision is made. A two-way door is a decision you make now and can revert later, not a decision to defer. See ADR-009 §"What this framework is NOT."
iroh Proxy Support (Direct-Connection Peer Exposure)
deferred(unclear)
two
med
Deferred / Blocked
The safe-exit visibility surface. These questions are parked because the
information needed to resolve them does not exist yet — each has a concrete
blocking condition. They are not failures; they are scope management. See
ADR-009 §"Safe Exit: Deferred Decisions." This section exists so "what's
currently blocking the architect" is answerable at a glance, not by
filtering the tables above.
OQ-09: WASM Target Boundaries
Blocked on: A concrete server-side WASM use case, or a deliberate confirmation that WASM stays a client-side design constraint. Tracked as architecture/oq-09-wasm-server-use-case in tasks/architecture/.
Priority: low
Amendment (2026-07-09): The Connection door is now open via Connection::from_stream (ADR-065) — a Connection can be constructed from any wasm-compatible stream. What remains closed is the accept-loop runtime (tokio::spawn does not run on WASM; PendingRequestMap/CallAdapter use tokio channels). The blocking condition (a concrete server-side WASM use case) is unchanged.
OQ-10: Git Adapter Scope — Smart Protocol Only or Full Server?
Blocked on: Speccing the alknet-git crate — resolve this when that crate is specified, not deferred past it. Tracked as architecture/oq-10-git-adapter-spec in tasks/architecture/.
Blocked on: A handler that needs stream operators and finds the existing combinators (Box::pin(stream::iter(...)), async_stream::stream!, futures::stream) insufficient. The operators library is a convenience, not a prerequisite for any handler.
Blocked on: a concrete use case for network or volume management over the call protocol. Dev containers use the default bridge network; hosted services declare networks/volumes in docker compose.
Blocked on: v1 implementation — the create input JSON Schema is finalized when register_docker_ops is written and tested against bollard's Config struct. An architectural decision (ADR-060 §5), not a deferral past implementation.
Resolved (ADR-089): AlknetClient is extracted as a new crate
alknet-client — the client-side analogue of AlknetEndpoint
(ADR-083). Three dial methods (dial_quic / dial_tcp_tls /
dial_iroh) produce a Connection for the protocol take-overs
(CallClient::spawn_dispatch, ChannelClient::from_connection) to
consume. The deferral's blocking condition (a second transport's
real dial) is met within the native endpoint type (ADR-086): QUIC +
TCP+TLS (both via TlsClientConfig, ADR-087) + iroh (key-based) —
three dial shapes, two sharing TlsClientConfig. The web/browser
client (WebSocket, HTTP) was never in scope. See
ADR-089 and
OQ-55.
Blocked on: OQ-58's token model (one-time vs. refresh,
single-use vs. multi-use, rotation). The alknet/register wire
protocol and the HTTP registration endpoint (OQ-58) share the
enrollment semantics and the token model — they should converge on
one model before either's wire format is locked.
Priority: medium
Impacts: Blocks native worker registration over QUIC/TCP+TLS
without HTTP (the native-only / no-HTTP-minimal-hub case). Does NOT
block the first hub deployment (web + native uses HTTP registration
for worker provisioning per OQ-58).
OQ-67: iroh Proxy Support (Direct-Connection Peer Exposure)
Investigation: Work through the iroh socket stack to determine
whether iroh exposes a socket abstraction analogous to quinn's
AsyncUdpSocket + new_with_abstract_socket (the hook the quinn
POC uses). If it does, a Socks5UdpSocket-equivalent for iroh is
the same shape as the quinn integration. If it does not, the
alternatives are: (a) force relay-only when a proxy is configured
(the conservative default — the peer always sees the relay's IP,
never the client's), or (b) accept the gap (document that iroh direct
connections expose the client's IP). The investigation needs a
concrete iroh-direct-with-proxy use case to drive the choice —
without one, "force relay-only" is the conservative default.
Priority: medium
Impacts: A client that dials over iroh direct (hole-punched)
connections and wants to hide its real IP from the peer. Does NOT
block the first hub deployment — hub outbound dials use QUIC/TCP+TLS
(both honor the proxy, ADR-090). Does NOT block the relay-mediated
iroh path — iroh's relay hides the client's IP from the peer, and
iroh's proxy_url hides it from the relay. The gap is the iroh
direct path only.
Blocked on: a real deployment observes head-of-line blocking on a
saturated channel where the bounded-buffer's stop-reading mitigation is
insufficient (e.g., a high-throughput file transfer over a tunnel that
saturates a channel and causes frequent demux stalls affecting other
channels). The intended use cases (TTY, SSH, tunnels) are not
high-throughput in the HOL-blocking sense; the trigger requires a
high-throughput use case.
Blocked on: a second two-pump handler existing (the tunnel handler is
the first; SSH direct-tcpip will be the second), so the shape
convergence is observable. Extracting the helper from one consumer would
bake in a shape that the second might not fit. The shutdown-on-completion
contract is decided (ADR-078); only the helper extraction is deferred.
OQ-64: Should alknet-tls Provide a Client-Side TLS Config Helper?
Resolved (ADR-087): alknet-tls provides TlsClientConfig. Not
blocked on the dial-seam extraction (OQ-55) — the TLS config is a
prerequisite for the dial, not a consequence of it. The circular
hedge (TLS config deferred behind the dial, dial needs the TLS
config) is broken. The hub-as-client requirement makes it a
prerequisite for the first hub deployment. See
ADR-087 and
OQ-64.