Files
alknet/tasks/client/crate-init.md
T
deepseek-v4-pro b476182d64 feat(tasks): decompose Phase 3 alknet-client into 9 atomic tasks
Phase 3 of the crate extraction (per findings.md): create the
alknet-client crate — the native client dial seam, client-side
analogue of AlknetEndpoint. Three dial methods (dial_quic,
dial_tcp_tls, dial_iroh) unified on &ConnectionCredentials (ADR-091),
pre-built transports via builder methods, optional SOCKS5 proxy
support (ADR-090).

9 tasks, 7 generations, no cycles:
- client/crate-init: Cargo.toml, feature flags, module skeleton
- client/error-type: ClientDialError enum (5 variants)
- client/client-core: AlknetClient struct + builder methods
- client/dial-quic: QUIC dial via quinn
- client/dial-tcp-tls: TCP+TLS dial via tokio-rustls
- client/dial-iroh: Iroh dial (key-not-config)
- client/socks5-proxy: Socks5ProxyConfig, Socks5UdpSocket, proxy integration
- client/tests: Unit tests + integration test
- client/review-client: Review checkpoint

Depends on: tls/review-tls, endpoint/review-endpoint (both completed).
Purely additive — old CallClient::connect stays until Phase 5 prune.
2026-07-17 12:07:08 +00:00

7.4 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
client/crate-init Initialize alknet-client crate with Cargo.toml, dependencies, and module skeleton pending
tls/review-tls
endpoint/review-endpoint
moderate low project implementation

Description

Phase 3, Task 1 of the crate extraction (per docs/research/alknet-crate-extraction/findings.md). Initialize the alknet-client crate from scratch. This crate provides the native client dial seam — AlknetClient, the client-side analogue of AlknetEndpoint. A multi-transport dialer that takes pre-built transport handles (quinn, TCP+TLS, iroh), dials a remote AlknetEndpoint on a chosen ALPN, and produces a Connection for the protocol take-overs to consume.

The dial is the client-side mirror of the server-side accept loop (ADR-083): one type that takes pre-built transport handles and produces a Connection, with the transport choice as a parameter. The protocol take-overs (CallClient::spawn_dispatch, ChannelClient::from_connection) are unchanged — they consume the Connection and do not know AlknetClient produced it.

Crate setup

Create crates/alknet-client/ with:

  • Cargo.toml — package metadata, dependencies, feature flags
  • src/lib.rs — crate root with module declarations and re-exports
  • Module skeleton files for:
    • src/error.rs — ClientDialError enum (5 variants: TlsConfig, Connect, Handshake, NoTransport, Proxy)
    • src/client.rs — AlknetClient struct, new, builder methods (with_quinn, with_tcp_tls, with_iroh, with_socks5_proxy)
    • src/dial/quinn.rs — dial_quic implementation (feature-gated on quinn)
    • src/dial/tcp_tls.rs — dial_tcp_tls implementation (feature-gated on tcp)
    • src/dial/iroh.rs — dial_iroh implementation (feature-gated on iroh)
    • src/dial/mod.rs — dial module declarations
    • src/socks5.rs — Socks5ProxyConfig, Socks5Credentials, Socks5UdpSocket, HTTP-to-SOCKS5 bridge (feature-gated on socks5)

Dependencies

Per the findings (Phase 3) and the architecture spec:

Crate Purpose
alknet-core Connection, ConnectionCredentials, RemoteIdentity, Ed25519SecretKey, types (workspace path)
alknet-tls TlsClientConfig — for quinn + tcp dials (workspace path)
quinn 0.11 QUIC transport (optional, feature-gated)
tokio-rustls 0.26 TCP+TLS transport (optional, feature-gated)
iroh 1.0 Iroh transport (optional, feature-gated)
fast-socks5 1 SOCKS5 client (optional, feature-gated — ADR-090)
tokio 1 (full) Async runtime, TcpStream, spawn
thiserror 2 ClientDialError

alknet-client depends on alknet-tls (for TlsClientConfig) and alknet-core (for Connection, ConnectionCredentials, RemoteIdentity, and types). It does not depend on alknet-call or alknet-channels-call — the dial is below the protocol.

Feature flags

[features]
default = []
quinn = ["dep:quinn", "alknet-tls/quinn", "alknet-core/quinn"]      # dial_quic
tcp = ["dep:tokio-rustls", "alknet-tls/tcp"]                        # dial_tcp_tls
iroh = ["dep:iroh", "alknet-core/iroh"]                              # dial_iroh
socks5 = ["dep:fast-socks5"]                                         # proxied dial paths (ADR-090)

The quinn and tcp features pull the corresponding features on alknet-tls (for TlsClientConfig::for_quinn / for_tcp_tls). The quinn and iroh features also pull the corresponding features on alknet-core — dial_quic produces a Connection via Connection::from_quinn_with_alpn and dial_iroh via Connection::from_iroh, both of which live in alknet-core's types.rs behind core's quinn / iroh features.

The socks5 feature (ADR-090) is independent of the transport features — it enables the proxy code path that dial_quic (UDP ASSOCIATE) and dial_tcp_tls (CONNECT) use when a proxy is configured. The fast-socks5 dep is behind socks5, so deployments that don't use a proxy don't pay the dep.

Workspace Cargo.toml

Add crates/alknet-client to the workspace members list in the root Cargo.toml.

Module skeleton

// src/lib.rs
//! alknet-client: Native client dial seam — multi-transport dialer that
//! produces `Connection`s for protocol take-overs.
//!
//! `AlknetClient` is the client-side analogue of `AlknetEndpoint`: a
//! multi-transport dialer that takes pre-built transport handles (quinn,
//! TCP+TLS, iroh), dials a remote `AlknetEndpoint` on a chosen ALPN, and
//! produces a `Connection`. The protocol take-overs
//! (`CallClient::spawn_dispatch`, `ChannelClient::from_connection`)
//! consume the `Connection` — the dial is below the protocol.
//!
//! An optional SOCKS5 proxy (ADR-090) routes the dials through a proxy
//! to hide the client's real IP from the hub.

pub mod client;
pub mod dial;
pub mod error;
#[cfg(feature = "socks5")]
pub mod socks5;

// Re-exports (filled in by subsequent tasks)

Each module file gets a doc comment and // TODO: implement marker.

Acceptance Criteria

  • crates/alknet-client/Cargo.toml exists with all dependencies and feature flags
  • crates/alknet-client/src/lib.rs exists with module declarations
  • Module skeleton files exist: error.rs, client.rs, dial/mod.rs, dial/quinn.rs, dial/tcp_tls.rs, dial/iroh.rs, socks5.rs
  • Root Cargo.toml members list includes crates/alknet-client
  • cargo check -p alknet-client succeeds
  • cargo clippy -p alknet-client succeeds with no warnings
  • Dual licensing: MIT OR Apache-2.0 (workspace-inherited)
  • alknet-core dependency uses workspace path (path = "../alknet-core")
  • alknet-tls dependency uses workspace path (path = "../alknet-tls")
  • No dependency on alknet-call (dial is below the protocol)
  • Feature flags: quinn, tcp, iroh, socks5 (all optional, default off)
  • quinn feature pulls alknet-tls/quinn + alknet-core/quinn
  • tcp feature pulls alknet-tls/tcp
  • iroh feature pulls alknet-core/iroh
  • socks5 feature pulls dep:fast-socks5 only (no alknet feature deps)
  • cargo build --workspace still succeeds (old code untouched)

References

  • docs/research/alknet-crate-extraction/findings.md — Phase 3
  • docs/architecture/crates/client/README.md — full architecture spec
  • docs/architecture/decisions/089-alknetclient-native-dial-seam.md — ADR-089
  • docs/architecture/decisions/090-client-dial-socks5-proxy-seam.md — ADR-090
  • docs/architecture/decisions/091-connectioncredentials-decouple-dial-from-call.md — ADR-091
  • crates/alknet-tls/Cargo.toml — reference for dep versions and feature flag pattern
  • crates/alknet-endpoint/Cargo.toml — reference for builder-method pattern
  • crates/alknet-call/src/client/call_client.rs — reference connect() implementation (lines 142-168)

Notes

This is the foundational setup task for alknet-client. All subsequent client/* tasks depend on this one. The crate depends on alknet-tls (for TlsClientConfig) and alknet-core (for Connection, ConnectionCredentials, RemoteIdentity). It does NOT depend on alknet-call — the dial is below the protocol. The old CallClient::connect in alknet-call is intentionally still present (duplicated) — the prune happens in Phase 5. The socks5 feature and fast-socks5 dep are opt-in; deployments that don't use a proxy pay nothing.

Summary

To be filled on completion