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.
8.1 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| client/review-client | Review alknet-client implementation for spec conformance, API shape, and test coverage | pending |
|
moderate | low | phase | review |
Description
Phase 3 review checkpoint. Verify the alknet-client crate is spec-conformant,
self-contained, and ready for downstream consumption by the assembly layer (hub/worker).
The crate must match the ADR-089/090/091 shape: three dial methods unified on
&ConnectionCredentials, pre-built transports via builder methods, ClientDialError
with five variants, and optional SOCKS5 proxy support.
Review Checklist
-
Crate structure:
- Module layout matches spec:
error.rs,client.rs,dial/{mod,quinn,tcp_tls,iroh}.rs,socks5.rs - Public API types:
AlknetClient,ClientDialError,Socks5ProxyConfig,Socks5Credentials - Re-exports in
lib.rsare correct and minimal - No dependency on
alknet-call(dial is below the protocol)
- Module layout matches spec:
-
AlknetClientAPI shape (ADR-089/090):new()takes no parameters (noStaticConfig, no credentials)with_quinn(endpoint: quinn::Endpoint)builder (feature-gated onquinn)with_tcp_tls(connector: TlsConnector)builder (feature-gated ontcp)with_iroh(endpoint: iroh::Endpoint)builder (feature-gated oniroh)with_socks5_proxy(proxy: Socks5ProxyConfig)builder (feature-gated onsocks5)Defaultimpl delegates tonew()Debugimpl lists configured transports (no transport internals)- No
connect()method (the old welded dial is not replicated)
-
ClientDialError(ADR-089):- Five variants:
TlsConfig,Connect,Handshake,NoTransport,Proxy TlsConfigwrapsalknet_tls::TlsErrorvia#[from]ConnectandHandshaketakeString(not concrete transport error types)NoTransporthastransport: &'static strfieldProxyis feature-gated onsocks5#[non_exhaustive]attribute- All variants have descriptive
#[error("...")]messages
- Five variants:
-
dial_quic(ADR-089 §3, ADR-091):- Signature:
(addr, server_name, alpn, creds: &ConnectionCredentials) -> Result<Connection, ClientDialError> - Builds
TlsClientConfig::new(creds, alpn)—TlsError→ClientDialError::TlsConfig - Converts to
quinn::ClientConfigviafor_quinn() - Uses pre-built quinn endpoint from
self.quinn - Returns
NoTransportwhenself.quinnisNone - Returns
Connection::from_quinn_with_alpn(conn, alpn) - Does NOT call
spawn_dispatch(protocol take-over is caller's concern) - Does NOT hardcode
alknet/callALPN - SOCKS5 proxy path: uses
Socks5UdpSocket+new_with_abstract_socketwhen proxy configured
- Signature:
-
dial_tcp_tls(ADR-089 §3, ADR-091):- Signature:
(host, addr, alpn, creds: &ConnectionCredentials) -> Result<Connection, ClientDialError> - Builds
TlsClientConfig::new(creds, alpn) - Uses pre-built
TlsConnectoror builds one fromTlsClientConfig - Connects TCP via
TcpStream::connect(addr) Connecterrors →ClientDialError::Connect- TLS handshake errors →
ClientDialError::Handshake - Returns
Connection::from_bidi(tls_stream, alpn, Some(addr)) - SOCKS5 proxy path: SOCKS5 CONNECT handshake before TLS
- Signature:
-
dial_iroh(ADR-089 §3, ADR-091):- Signature:
(alpn, creds: &ConnectionCredentials) -> Result<Connection, ClientDialError> - Does NOT use
TlsClientConfig(iroh has its own TLS) - Extracts
NodeIdfromcreds.remote_identity.fingerprint - Unknown iroh remote (
remote_identity: None) fails closed - Returns
Connection::from_iroh(conn) - No
addrorserver_nameparameter (iroh handles addressing internally)
- Signature:
-
SOCKS5 proxy (ADR-090):
Socks5ProxyConfigwithaddrandcredentialsfieldsSocks5CredentialswithusernameandpasswordfieldsSocks5UdpSocketimplementsquinn::AsyncUdpSocketdial_quicroutes through UDP ASSOCIATE when proxy configureddial_tcp_tlsroutes through CONNECT when proxy configured- No silent fallback to direct connection when proxy configured
Proxyerror variant used for proxy failures- Feature-gated on
socks5
-
Dependency hygiene:
alknet-coreandalknet-tlsare the only alknet dependencies- No dependency on
alknet-calloralknet-channels-call quinnis optional, gated behindquinnfeature (pullsalknet-tls/quinn+alknet-core/quinn)tokio-rustlsis optional, gated behindtcpfeature (pullsalknet-tls/tcp)irohis optional, gated behindirohfeature (pullsalknet-core/iroh)fast-socks5is optional, gated behindsocks5feature- No unexpected heavy deps
-
Test coverage:
AlknetClientconstruction tests:new(),Default,Send + Sync,DebugClientDialErrortests:#[from]conversion, display formatting,Send + Syncdial_quicerror path tests:NoTransport,TlsConfigdial_tcp_tlserror path tests:NoTransportdial_iroherror path tests:NoTransport, unknown remote fail-closedSocks5ProxyConfig/Socks5Credentialsconstruction tests- Integration test:
tests/dial_and_takeover.rs - Feature-gated tests are correctly annotated
-
Cross-cutting checks:
cargo build -p alknet-clientsucceeds (all feature combos)cargo test -p alknet-clientsucceeds (all feature combos)cargo clippy -p alknet-client --all-targetssucceeds with no warningscargo fmt --check -p alknet-clientpassescargo build --workspacestill succeeds (old code untouched)cargo test --workspacestill succeeds (old tests untouched)
Acceptance Criteria
- Crate structure matches spec (7 source files, correct module layout)
AlknetClientAPI matches ADR-089/090 shape (builder methods, noconnect(), noStaticConfig)ClientDialErrorhas 5 variants,#[non_exhaustive], correct#[from]impldial_quictakes&ConnectionCredentials, returnsConnection, ALPN is a parameterdial_tcp_tlstakes&ConnectionCredentials, returnsConnection,host+addrseparatedial_irohtakes&ConnectionCredentials, returnsConnection, noaddr/server_name- All three dials unified on
&ConnectionCredentials(ADR-091) dial_irohdoes NOT useTlsClientConfig(iroh has its own TLS)- SOCKS5 proxy:
Socks5ProxyConfig,Socks5Credentials,Socks5UdpSocket, proxy integration in dials - No silent fallback to direct connection when proxy configured
- No dependency on
alknet-call - All tests pass (unit + integration)
cargo build -p alknet-clientsucceeds (all feature combos)cargo test -p alknet-clientsucceeds (all feature combos)cargo clippy -p alknet-client --all-targetssucceeds with no warningscargo fmt --check -p alknet-clientpasses- Workspace still green:
cargo build --workspace+cargo test --workspacepass
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
- tasks/client/crate-init.md
- tasks/client/error-type.md
- tasks/client/client-core.md
- tasks/client/dial-quic.md
- tasks/client/dial-tcp-tls.md
- tasks/client/dial-iroh.md
- tasks/client/socks5-proxy.md
- tasks/client/tests.md
Notes
This review gates Phase 3 completion. The crate must be self-contained and spec-conformant before Phase 4 (core prune) begins, since the prune removes the old
endpoint.rsfrom core and the assembly layer (which consumesalknet-client) will wire the dial to the protocol take-overs. The old code incall_client.rsis intentionally still present (duplicated) — the prune happens in Phase 5. If deviations are found, document and fix before proceeding to Phase 4.
Summary
To be filled on completion