Files
alknet/tasks/client/crate-init.md
T
deepseek-v4-pro 83a94ec03f chore(client): mark all client tasks as completed
Phase 3 complete: alknet-client crate implemented with:
- AlknetClient struct with builder methods (with_quinn, with_tcp_tls, with_iroh, with_socks5_proxy)
- ClientDialError enum with 5 variants
- dial_quic, dial_tcp_tls, dial_iroh methods
- SOCKS5 proxy support (Socks5ProxyConfig, Socks5Credentials, Socks5UdpSocket)
- Unit tests (18) + integration test (3)
- All feature combos compile and test clean
- Workspace build and tests pass
2026-07-17 12:49:50 +00:00

156 lines
7.4 KiB
Markdown

---
id: client/crate-init
name: Initialize alknet-client crate with Cargo.toml, dependencies, and module skeleton
status: completed
depends_on: [tls/review-tls, endpoint/review-endpoint]
scope: moderate
risk: low
impact: project
level: 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
```toml
[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
```rust
// 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