Files
alknet/docs/architecture/questions/055-alknetclient-establishment-extraction.md
T
glm-5.2 77321a7e84 docs(arch): break the AlknetClient circular hedge — TlsClientConfig not blocked on dial (ADR-087, resolves OQ-64)
OQ-64 and OQ-55 were linked in a circular dependency: the client-side
TLS config was deferred behind the dial seam (OQ-55), but the dial
needs the TLS config. No second transport can dial until it has a TLS
config; the TLS config was deferred until a second transport dials.
Schrödinger's code — required and not required until observed.

ADR-087 breaks the circle by separating two concerns that were
conflated as 'the same seam':

1. TlsClientConfig — rustls::ClientConfig + ADR-034 verifier selection
   + ADR-084 crypto provider. Transport-agnostic. All decisions made.
   Buildable today. A PREREQUISITE for any dial, not a consequence of it.

2. The dial (AlknetClient::dial()) — transport-specific connection
   establishment. Extracting a transport-polymorphic dial from one
   shape (QUIC) would bake QUIC in. Legitimate deferral (OQ-55,
   unchanged).

The hub makes this non-optional: a hub dials out to workers it
supervises and to other hubs (hub-as-client). The first hub deployment
(web + native) dials workers over QUIC with the worker's fingerprint
pinned. There is no 'later' for the TLS config — it is on the critical
path for the first hub and for alknet-worker.

Changes:
- ADR-087: TlsClientConfig in alknet-tls, not blocked on OQ-55
- OQ-64: resolved (yes, alknet-tls provides TlsClientConfig)
- OQ-55: amended — only the dial seam is deferred; TLS client config
  is explicitly NOT part of the deferral
- TLS README: 'Server-only (for now)' section replaced with
  TlsClientConfig section; crate is no longer server-only
- Hub README: dial/supervision section references TlsClientConfig for
  outbound connections
2026-07-15 06:05:59 +00:00

4.9 KiB

OQ-55: AlknetClient / Client Establishment Extraction

  • Origin: docs/research/alknet-channels/phase-0-findings.md OQ-CH-14 (the AlknetClient clarification); docs/research/alknet-channels/poc-summary.md §"Issues Surfaced" #1 (the BidiStreamSource finding that motivates separating the client-extraction question from the Connection extension question).
  • Status: deferred(scope)
  • Door type: two-way
  • Priority: medium
  • Blocked on: a second transport's real dial existing, not just a second QUIC dial. The blocking condition is met when, e.g., the SSH crate's raw-TCP dial or the HTTP-wrapped call dial exists — so the transport-polymorphic dial+TLS seam is extractable from two different transport implementations, not two QUIC variants. ChannelClient's connect_quic does not unblock this; it is a second client but the same transport shape. ChannelClient's from_connection (the transport-agnostic take-over, ADR-080) is decided and is not the thing being deferred — the shared dial is. The deferral is on transport-polymorphism of the dial, not on client count or on the channels protocol's API.
  • What is NOT deferred (amended by ADR-087): the client-side TLS config (TlsClientConfig) is not part of this deferral. OQ-64 is resolved — alknet-tls provides TlsClientConfig now, unblocked by this OQ. The TLS config is a prerequisite for the dial, not a consequence of it; it is transport-agnostic (ADR-034 verifier selection + ADR-084 provider, both already decided). Each transport-specific dial helper builds its TlsClientConfig and passes it to its transport's connector. This OQ defers only the transport-polymorphic dial extraction — the shared AlknetClient::dial() that picks the transport and calls the right connector. When a second transport's dial exists, the dial seam is extractable; the TLS config is already shared by then.
  • Resolution: Not yet decidable. The shared substance across TLS-carrying transports is ADR-034's verifier-selection rule (PeerEntry presence → fingerprint pin : CA-verify / fail-closed) and the rustls::ClientConfig construction — now centralized in TlsClientConfig::new (ADR-087, OQ-64 resolved). What remains transport-specific is the dial itself — quinn::Endpoint::connect, TcpStream::connect + TlsConnector::connect, iroh's Endpoint::connect. We have one of ~5 shapes implemented (QUIC; the others being HTTP, TCP+TLS, WebTransport, raw TCP). Extracting a QUIC-shaped connector to core and naming it AlknetClient would bake QUIC in as the establishment shape — the same welding ADR-065 unwound on the server side, repeated on the client side. The dial is transport-polymorphic; the shared TLS config is narrow and now extracted (ADR-087). Until a second transport's dial exists, the seam between "dial" (per-transport) and "spawn the dispatcher" (per-crate) is not extractable from two real shapes — it's guessable from one.
  • What does NOT block on this: each crate building its own client standalone with a shared TlsClientConfig (ADR-087), and each transport-specific dial helper. CallClient's transport-agnostic take-over (spawn_dispatch) and ChannelClient's transport-agnostic take-over (from_connection, ADR-080) are decided; connect_quic dials QUIC and calls from_connection. The SSH crate's TCP client, the HTTP call client, a connect_tcp_tls / connect_webtransport helper — each builds its TlsClientConfig (ADR-087) and its own dial standalone. Core already permits all of this — Connection::from_stream / from_bidi (ADR-065) handles the non-QUIC transport on the server side, and nothing prevents a client from constructing a Connection the same way after its own transport-specific dial. The friction is the duplicated dial boilerplate (each dial helper calls its transport's connector), not duplicated TLS config (that is shared via ADR-087). The bidirectionality criterion (a crate needs a Client type when (a) the endpoint has protocol-level authority — e.g., channels' id allocation — or (b) the protocol needs a reliable establishment interface) is met by each crate independently; AlknetClient is the eventual shared dial seam, not a prerequisite for any single client to exist.
  • Cross-references: ADR-034 (verifier selection — centralized in TlsClientConfig::new per ADR-087), ADR-087 (TlsClientConfig is not blocked on this OQ — the TLS config is extracted; only the dial remains deferred), ADR-065 (server-side transport generalization — the client-side analogue this OQ's deferral avoids preempting), ADR-070 (the BidiStreamSource extension point, which is the Connection opening and is orthogonal to the client establishment question), OQ-CH-14 in docs/research/alknet-channels/phase-0-findings.md (the research-scope question this core-scope OQ carries forward).