Files
alknet/docs/research/alknet-crate-extraction/findings.md
T

542 lines
27 KiB
Markdown

# alknet-crate-extraction — Migration from the welded core to the extracted crates
**Status:** Findings in progress — mapping the existing code to the
target shape, phase by phase, so the migration can be ordered to keep
the tree compilable at each step.
**Date:** 2026-07-16
**Scope:** The three new crates (`alknet-tls`, `alknet-endpoint`,
`alknet-client`) + the prune of `alknet-core` and `alknet-call` + the
`alknet-http` residual fix. The specs are confirmed tight (reviewed +
amended); this doc is the *how* — what code moves where, in what order,
with what intermediate states.
---
## TL;DR
The migration is **additive-then-subtractive**: build all three new
crates first (no breakage), then prune the old code from core and call
(breakage confined to a single phase), then fix the http residual. Six
phases, four compilable intermediate states. The heaviest single file
(`endpoint.rs`, 1606 lines) is not a monolith — it's three concerns
welded together, each going to a different destination.
---
## What exists now (the source map)
### `crates/alknet-core/src/endpoint.rs` — 1606 lines, three concerns
The file is not 1600 lines of one thing. It's three concerns that the
extraction separates:
| Lines | Concern | Destination | LOC |
|-------|---------|-------------|-----|
| 1-492 | `AlknetEndpoint` struct, `HandlerRegistry`, `dispatch_quinn`/`dispatch_iroh`, accept loops, `build_auth_context`, `has_iroh_identity` | `alknet-endpoint` | ~492 |
| 493-934 | `TlsSetup`, `build_rustls_server_config`, `build_quinn_server_config_from_rustls`, `RawKeyCertResolver`, `Ed25519SigningKey`, `AcceptAnyCertVerifier`, `SelfSignedCert`, `generate_self_signed_cert`, `load_cert_chain`, `load_private_key`, `build_iroh_endpoint` | `alknet-tls` (TLS setup) / assembly layer (iroh builder) | ~442 |
| 935-1606 | 42 test functions | split by subject | ~671 |
The endpoint struct + dispatch + accept loops (the part that goes to
`alknet-endpoint`) is ~492 lines of implementation — not 1600. The TLS
setup code (~442 lines) goes to `alknet-tls`. The tests (~671 lines)
split by what they test. The file *feels* monolithic because the
`#[cfg(feature = "quinn")]` gates weave the three concerns together,
but the extraction unwinds that.
### `crates/alknet-call/src/client/call_client.rs` — 930 lines
| Lines | Concern | Destination | LOC |
|-------|---------|-------------|-----|
| 40-88 | `RemoteIdentity`, `CallCredentials` (struct + builder) | `alknet-core` (new `credentials.rs` or `auth.rs`) | ~48 |
| 90-100 | `ClientError` enum | **removed** (only produced by `connect`) | ~10 |
| 102-187 | `CallClient` struct + `new` + `spawn_dispatch` | **stays** (the pure protocol take-over) | ~85 |
| 189-320 | `build_quinn_client_config`, `build_client_auth`, `select_server_verifier`, `load_platform_root_cert_store`, `load_cert_chain`, `load_private_key`, `Ed25519SigningKey`, `RawKeyClientCertResolver`, `NoClientCertResolver`, `FingerprintPinVerifier` | `alknet-tls` | ~130 |
| 321-640 | `CallConnection`, `Dispatcher` wiring, wire-protocol helpers | **stays** (protocol) | ~320 |
| 640-930 | Tests (use `connect`, `CallCredentials`, TLS helpers) | rewrite to use `spawn_dispatch` directly or `AlknetClient` | ~290 |
The call crate's prune is ~140 lines of implementation + ~290 lines of
tests that need rewriting. What remains is the pure protocol: `CallClient`
+ `CallConnection` + `Dispatcher` + the wire protocol.
### `crates/alknet-http/src/server/adapter.rs` — the residual
The `HttpAdapter::handle` method does `connection.accept_bi()` → wraps
the send/recv pair in a hand-rolled `QuicStream` (lines 271-300, an
`AsyncRead+AsyncWrite` adapter) → feeds it to `serve_io()`. But
`serve_io` already accepts *any* `AsyncRead+AsyncWrite` (line 244). The
`QuicStream` wrapper is a hand-rolled version of what
`Connection::from_bidi`/`from_stream` (ADR-065, already landed) provides
natively.
The residual: `handle` assumes a quinn-style multi-stream connection
(`accept_bi` returns a fresh bidi stream). For a `from_bidi` connection
(TCP+TLS), `accept_bi` yields the single bidi stream once (ADR-070's
yield-once contract). The `QuicStream` wrapper works but is unnecessary
— the streams from `accept_bi` are already `AsyncRead+AsyncWrite`. The
fix is to drop `QuicStream` and use the streams directly (or via
`TokioIo::new`). This is a small change (~30 lines removed) but needs
verification against ADR-070's `accept_bi` semantics for `from_bidi`.
The `QuicStreamDuplex` test helper (lines 456-471) is the test-side
equivalent — it can be replaced with `Connection::from_stream` test
helpers (the `MockConnection`/`ConnectionKind::Mock` variants were
already removed per ADR-065; tests use `from_stream` with
`tokio::io::sink`/`empty`).
---
## The seven phases
### Phase 0: Move `CallCredentials`/`RemoteIdentity` to `alknet-core` (additive, no breakage)
**What:** Add `CallCredentials` + `RemoteIdentity` to a new
`crates/alknet-core/src/credentials.rs` (~50 lines). Update
`alknet-core/src/lib.rs` to `pub mod credentials` + re-export. Update
`alknet-call/src/client/mod.rs` to re-export from core instead of
defining locally. No other changes.
**Why first:** It's independent of the three new crates, purely
additive (core gains types, nothing breaks), and means `alknet-client`
(Phase 3) never has a temporary dep on `alknet-call`. The dep graph is
clean from the start.
**Compilable state:** `cargo test` passes across the workspace.
`alknet-call` re-exports the types from core; its own code + tests
import via `alknet_call::client::CallCredentials` (unchanged — the
re-export preserves the path).
**Done when:** `cargo test` passes, `CallCredentials` +
`RemoteIdentity` are defined in `alknet-core`, `alknet-call` re-exports
them.
### Phase 1: Create `alknet-tls` (greenfield, additive)
**What:** New crate `crates/alknet-tls/`. Extract the TLS setup code
from `alknet-core/endpoint.rs` (lines 493-934) + the client-side TLS
helpers from `alknet-call/client/call_client.rs` (lines 189-320).
**Types:** `TlsServerConfig`, `TlsClientConfig`, `TlsError`,
`FingerprintPinVerifier`, `RawKeyCertResolver`,
`RawKeyClientCertResolver`, `NoClientCertResolver`, `Ed25519SigningKey`
(consolidated — one copy, used by both server + client),
`AcceptAnyCertVerifier`, `SelfSignedCert`, `generate_self_signed_cert`,
`load_cert_chain`, `load_private_key`, `load_platform_root_cert_store`
(+ the `webpki-roots` fallback — new, not extracted).
**Deps:** `alknet-core` (TlsIdentity, Ed25519SecretKey, fingerprint),
`rustls`, `rustls-pemfile`, `rustls-native-certs`, `webpki-roots`,
`rcgen`, `tokio`, optional `quinn`/`tokio-rustls`/`rustls-acme`.
**Compilable state:** `alknet-tls` builds and tests standalone. Core and
call are unchanged — the old code still exists (duplicated). No
breakage. The new crate's tests are the moved tests from
`endpoint.rs` (the TLS-setup tests) + the moved tests from
`call_client.rs` (the verifier/client-config tests).
**Tests that move here (from `endpoint.rs`):**
- `raw_key_cert_resolver_only_raw_public_keys`
- `self_signed_cert_generation_produces_cert_and_key`
- `acme_directory_production_url` / `staging_url` / `custom_url`
- `tls_setup_x509_returns_no_acme_state`
- `build_rustls_server_config_raw_key_succeeds`
- `build_rustls_server_config_self_signed_succeeds`
- `build_quinn_server_config_from_rustls_succeeds`
- `load_private_key_returns_error_when_no_key_present` / `_file_missing`
- `load_cert_chain_returns_error_when_file_missing`
- `accept_any_cert_verifier_*` (4 tests)
- `ed25519_signing_key_*` (6 tests)
- `raw_key_cert_resolver_debug_is_implemented`
**Tests that move here (from `call_client.rs`):** the
`FingerprintPinVerifier` + `build_quinn_client_config` tests (need
adaptation — they currently test via `connect`, should test via
`TlsClientConfig::new` directly).
**Done when:** `cargo test -p alknet-tls` passes, the crate is
self-contained, no other crate changed.
### Phase 2: Create `alknet-endpoint` (greenfield, additive)
**What:** New crate `crates/alknet-endpoint/`. Extract the endpoint
struct + dispatch + accept loops from `alknet-core/endpoint.rs` (lines
1-492), built fresh against the ADR-083 shape (`new(handlers, dynamic,
identity_provider, drain_timeout)` + `with_quinn`/`with_iroh`/`with_tcp_tls`
+ public `dispatch` + `run`/`shutdown`). No `EndpointError` (removed per
the spec review). `shutdown()` is infallible.
**Types:** `AlknetEndpoint`, `HandlerRegistry`, `TcpTlsListener`,
private `dispatch_quinn`/`dispatch_iroh`/`dispatch_tcp_tls`,
`build_auth_context`, the `extract_*` helpers.
**Deps:** `alknet-core` (Connection, ProtocolHandler, AuthContext,
IdentityProvider, DynamicConfig), optional `quinn`/`iroh`/`tokio-rustls`,
`tokio`, `arc-swap`, `tracing`.
**Module breakdown (the 492 lines split):**
- `registry.rs` — `HandlerRegistry` (~40 lines + tests)
- `endpoint.rs` — `AlknetEndpoint` struct, `new`, builder methods,
`run`, `shutdown` (~120 lines)
- `dispatch.rs` — `dispatch` (public), `build_auth_context`, the
`acme-tls/1` guard (~80 lines)
- `accept/quinn.rs` — `dispatch_quinn`, `run_quinn_accept_loop`,
`extract_quinn_alpn`, `extract_quinn_client_fingerprint` (~80 lines)
- `accept/iroh.rs` — `dispatch_iroh`, `run_iroh_accept_loop`,
`extract_iroh_client_fingerprint` (~50 lines)
- `accept/tcp_tls.rs` — `dispatch_tcp_tls`, `run_tcp_tls_accept_loop`,
`extract_tcp_tls_alpn`, `extract_tcp_tls_client_fingerprint` (new,
~60 lines — not in the current code; written fresh per ADR-083)
This is a fresh build, not a move — the old `endpoint.rs` stays in core
until Phase 4. The new crate is written against the target shape, not
the old shape.
**Compilable state:** `alknet-endpoint` builds and tests standalone.
Core's `endpoint.rs` still exists (duplicate). No breakage.
**Tests that move here (from `endpoint.rs`):**
- `handler_registry_*` (5 tests)
- `build_auth_context_*` (3 tests)
- `dispatch_decision_logic_lookup_and_auth`
- `has_iroh_identity_*` (3 tests)
- `endpoint_constructs_with_iroh_raw_key_identity`
- `iroh_endpoint_runs_accept_loop_and_shutdown`
- `debug_for_alknet_endpoint_is_implemented_without_panicking`
**Done when:** `cargo test -p alknet-endpoint` passes, the crate is
self-contained, no other crate changed.
### Phase 3: Create `alknet-client` (greenfield, additive)
**What:** New crate `crates/alknet-client/`. The `AlknetClient` dial
seam — three dial methods (`dial_quic`/`dial_tcp_tls`/`dial_iroh`),
consuming `TlsClientConfig` from `alknet-tls` + `CallCredentials` from
`alknet-core`. The SOCKS5 proxy path (ADR-090) is feature-gated.
**Types:** `AlknetClient`, `ClientDialError`, `Socks5ProxyConfig`,
`Socks5Credentials` (behind `socks5` feature).
**Deps:** `alknet-core` (Connection, CallCredentials, RemoteIdentity,
Ed25519SecretKey), `alknet-tls` (TlsClientConfig), optional
`quinn`/`tokio-rustls`/`iroh`/`fast-socks5`.
**Feature gates:** `quinn = ["dep:quinn", "alknet-tls/quinn",
"alknet-core/quinn"]`, `tcp = ["dep:tokio-rustls", "alknet-tls/tcp"]`,
`iroh = ["dep:iroh", "alknet-core/iroh"]`, `socks5 = ["dep:fast-socks5"]`.
**Compilable state:** `alknet-client` builds and tests standalone. No
breakage — the old `CallClient::connect` still exists in
`alknet-call` (duplicate dial). The new crate's tests use
`AlknetClient::dial_*` + `spawn_dispatch`/`from_connection` or test
the dial in isolation with mock transports.
**Done when:** `cargo test -p alknet-client` passes, the crate is
self-contained, no other crate changed.
### Phase 4: Prune `alknet-core` (subtractive, breakage confined)
**What:** Delete `endpoint.rs` from core. Remove `pub mod endpoint`
from `lib.rs`. Remove the heavy deps (`quinn`, `iroh`, `rcgen`,
`rustls-pemfile`, `rustls-acme`) from `Cargo.toml` — but keep the
`quinn`/`iroh` *features* (they gate `Connection::from_quinn`/
`from_iroh` in `types.rs`). Add `CallCredentials` + `RemoteIdentity`
to core (from `alknet-call`).
**The `lib.rs` change:**
```rust
// Before:
pub mod endpoint; // ← removed
pub mod auth;
pub mod config;
// ... rest unchanged
// After:
pub mod auth;
pub mod config;
pub mod credentials; // ← new (CallCredentials, RemoteIdentity)
// ... rest unchanged
```
**The `Cargo.toml` change:** `quinn`/`iroh`/`rcgen`/`rustls-pemfile`/
`rustls-acme` leave `[dependencies]`. The `quinn`/`iroh` features stay
(gating `types.rs` constructors). The `acme` feature is removed
(vestigial — the ACME state machine is on `TlsServerConfig` in
`alknet-tls` now).
**The `fingerprint.rs` doc comment** (line 13) references
`alknet_core::endpoint` — update to reference `alknet-endpoint` /
`alknet-tls`.
**Breakage:** anything that imported `alknet_core::endpoint` breaks.
But per the source map, *nothing does* — no handler crate or call
imports the endpoint module. The only consumer is the future assembly
layer (hub/worker), which doesn't exist yet. So the prune is clean:
delete the file, update `lib.rs`, update `Cargo.toml`, fix the
`fingerprint.rs` comment. Core's own tests for `endpoint.rs` are gone
(moved to `alknet-tls` in Phase 1 and `alknet-endpoint` in Phase 2).
**Compilable state:** `cargo test -p alknet-core` passes (minus the
endpoint tests, which moved). The `quinn`/`iroh` features still work
(`Connection::from_quinn`/`from_iroh` in `types.rs`).
**Done when:** `cargo test -p alknet-core` passes, the crate is
lightweight (~3200 LOC, no heavy transport deps).
### Phase 5: Prune `alknet-call` (subtractive, breakage confined)
**What:** Delete `connect()` + all TLS helpers + `ClientError` from
`call_client.rs`. Move `CallCredentials`/`RemoteIdentity` to core
(already done in Phase 4 — here we just remove the old definitions +
update imports). Update `Cargo.toml` to drop `quinn`/`rustls`/
`rustls-native-certs`/`rustls-pemfile`. Rewrite the tests that used
`connect` to use `spawn_dispatch` directly (with `Connection::from_stream`
mocks) or `AlknetClient::dial_quic` + `spawn_dispatch`.
**The `call_client.rs` after prune:**
- `CallClient` struct + `new` + `registry` + `identity_provider` +
`spawn_dispatch` (~85 lines — unchanged)
- `CallConnection` + `Dispatcher` wiring (stays — protocol)
- `RemoteIdentity`/`CallCredentials` — removed (now in `alknet-core`,
re-imported from there)
- `ClientError` — removed
- `connect` + all `build_*`/`select_*`/`load_*`/`Ed25519SigningKey`/
`RawKeyClientCertResolver`/`NoClientCertResolver`/
`FingerprintPinVerifier` — removed (now in `alknet-tls`)
**The `Cargo.toml` after prune:** `quinn`, `rustls`,
`rustls-native-certs`, `rustls-pemfile` all leave. The `quinn` feature
either disappears or becomes a no-op (it only gated `connect` + the
TLS helpers, both removed). `alknet-call` becomes a pure protocol crate.
**Test impact (per the test audit below):** the lib tests in
`call_client.rs` (16 tests) split into 6 that stay unchanged
(protocol-level, use `spawn_dispatch(stub_connection())`) and 10 that
move to `alknet-tls` in Phase 1 (TLS/verifier tests). **Zero lib tests
need rewriting** — no test in `call_client.rs` calls `connect()`. The
`from_call.rs` tests (27 tests) stay unchanged — they use
`CallConnection` directly. The one integration test file
(`tests/two_node_call.rs`, 2 tests) calls `connect()` twice — it moves
to `alknet-client/tests/` (Phase 3) or is updated to use
`AlknetClient::dial_quic` + `spawn_dispatch` (Phase 5).
The implementation prune is mechanical: delete the error enum, delete
`connect`, delete the TLS helpers (~140 lines). The test work is: the
10 TLS tests already moved in Phase 1, the 6 protocol tests stay, the
integration test is handled separately.
**Compilable state:** `cargo test -p alknet-call` passes with the
rewritten tests. The crate has no TLS/transport deps.
**Done when:** `cargo test -p alknet-call` passes, the crate is a pure
protocol crate.
### Phase 6: Fix `alknet-http` (small, additive)
**What:** Remove the `QuicStream` wrapper from `server/adapter.rs`.
The `HttpAdapter::handle` method does `connection.accept_bi()` →
wraps in `QuicStream` → feeds to `serve_io`. After the fix, it does
`connection.accept_bi()` → uses the streams directly (they're already
`AsyncRead+AsyncWrite`) → feeds to `serve_io` via `TokioIo::new`.
**The `QuicStream` struct** (lines 271-300) is deleted. The
`QuicStreamDuplex` test helper (lines 456-471) is replaced with
`Connection::from_stream` test helpers.
**Verification needed:** confirm that `accept_bi()` on a `from_bidi`
connection yields the single bidi stream per ADR-070's yield-once
contract, and that the yielded streams are directly usable as
`AsyncRead+AsyncWrite` (no wrapper needed). If the yield-once semantics
require a different path for single-stream connections, the fix is
slightly larger — but the `serve_io` signature already accepts any
`AsyncRead+AsyncWrite`, so the adapter is ready.
**Compilable state:** `cargo test -p alknet-http` passes. The crate no
longer has the hand-rolled `QuicStream` wrapper.
**Done when:** `cargo test -p alknet-http` passes, the `QuicStream`
wrapper is gone.
---
## Intermediate states (compilable after each phase)
| After phase | State |
|-------------|-------|
| 0 (credentials) | `CallCredentials`/`RemoteIdentity` in core; call re-exports; no breakage |
| 1 (tls) | `alknet-tls` builds standalone; core/call/http unchanged (old code duplicated) |
| 2 (endpoint) | `alknet-endpoint` builds standalone; core still has old `endpoint.rs` (duplicate) |
| 3 (client) | `alknet-client` builds standalone; call still has old `connect` (duplicate) |
| 4 (core prune) | core is lightweight; `endpoint.rs` gone; `CallCredentials` in core |
| 5 (call prune) | call is pure protocol; `connect` + TLS helpers gone; Category B tests already moved |
| 6 (http fix) | http has no `QuicStream` wrapper; clean `accept_bi` path |
Phases 0-3 are purely additive — no existing code breaks, no tests
break. Phases 4-5 are subtractive — the pruned code's callers don't
exist yet (no assembly layer), so the breakage is confined to the
crate's own tests (and per the test audit, the call prune breaks zero
tests — the TLS tests moved in Phase 1, the protocol tests use
`spawn_dispatch` directly). Phase 6 is a small fix.
## Ordering rationale
The ordering is **deps before dependents, additive before subtractive**:
- **Phase 0** (`CallCredentials` to core) first because it's
independent, additive, and makes `alknet-client` (Phase 3) never
depend on `alknet-call`. ~50 lines moved, zero breakage.
- `alknet-tls` (Phase 1) because both `alknet-endpoint` (indirectly —
the assembly layer builds `TlsServerConfig`) and `alknet-client`
(directly — `TlsClientConfig`) depend on it. It has no dep on the
other new crates.
- `alknet-endpoint` (Phase 2) depends only on `alknet-core` (already
exists) — it doesn't need `alknet-tls` (the endpoint takes
pre-built transports). It could go before `alknet-tls`, but putting
tls first means the assembly layer's transport-building code has a
home from the start.
- `alknet-client` (Phase 3) depends on `alknet-tls`
(`TlsClientConfig`) + `alknet-core` (`CallCredentials` — moved in
Phase 0, so the dep is clean from the start).
- Phases 4-5 (the prunes) go last because they're subtractive. The
new crates (0-3) must exist first so the pruned code's
functionality has a home.
- Phase 6 (http fix) goes last because it's independent of the
extraction — it's a residual fix that could happen at any point
after ADR-065 landed (which it did). Putting it last keeps the
extraction phases clean.
- Phases 4-5 (the prunes) go last because they're subtractive. The
new crates (1-3) must exist first so the pruned code's
functionality has a home.
- Phase 6 (http fix) goes last because it's independent of the
extraction — it's a residual fix that could happen at any point
after ADR-065 landed (which it did). Putting it last keeps the
extraction phases clean.
## Resolved decisions
### `CallCredentials`/`RemoteIdentity` move — Phase 0 (before Phase 1)
**Decision:** Move `CallCredentials`/`RemoteIdentity` to `alknet-core`
as a standalone additive step *before any new crate is created*. It's
independent of everything else, purely additive (core gains two small
types, nothing breaks), and `alknet-call` re-exports them from core
so its own code + tests don't change yet. This means `alknet-client`
(Phase 3) never depends on `alknet-call` — the dep graph is clean from
the start, no temporary dep to clean up later.
The move is ~50 lines (struct definitions + builder impls) into a new
`crates/alknet-core/src/credentials.rs` (or `auth.rs` — `auth.rs`
already holds `AuthToken`, so `credentials.rs` is cleaner to keep the
auth module from growing). `alknet-call`'s `client/mod.rs` changes
`pub use call_client::{CallCredentials, RemoteIdentity}` to
`pub use alknet_core::{CallCredentials, RemoteIdentity}` (re-export).
No test changes — the tests import `CallCredentials` from
`alknet_call::client`, which still works via the re-export.
### Phase 5 test audit — `call_client.rs` (16 tests)
The 16 tests in `call_client.rs` split into three categories:
**Category A — protocol-level, stay in `alknet-call`, no rewrite
needed (6 tests):**
These tests use `spawn_dispatch(stub_connection())` or
`CallCredentials` directly. They don't touch `connect` or any TLS
helper. `stub_connection()` (line 582) uses
`Connection::from_stream(tokio::io::channel(...))` — already
transport-agnostic. These survive the prune unchanged.
| Test | Line | What it tests |
|------|------|---------------|
| `call_credentials_builder_methods` | 652 | `CallCredentials` builder |
| `external_op_dispatches_and_populates_capabilities` | 665 | dispatch + capabilities |
| `unknown_op_returns_not_found` | 679 | dispatch error path |
| `spawn_dispatch_returns_live_call_connection` | 691 | `spawn_dispatch` + ALPN |
| `call_client_is_send_sync` | 705 | trait bounds |
| `remote_identity_none_is_load_bearing_not_defaulted` | 921 | `CallCredentials::new()` |
**Category B — TLS/verifier tests, move to `alknet-tls` (8 tests):**
These test `FingerprintPinVerifier`, `build_client_auth`,
`select_server_verifier`, and `build_quinn_client_config` directly.
They're `#[cfg(feature = "quinn")]`-gated and test the TLS helpers,
not the call protocol. They move to `alknet-tls` in Phase 1 (adapted
to test `TlsClientConfig::new` instead of the free functions).
| Test | Line | What it tests | Move target |
|------|------|---------------|-------------|
| `fingerprint_pin_verifier_matches_correct_ed25519_fingerprint` | 750 | verifier accept | `alknet-tls` |
| `fingerprint_pin_verifier_rejects_wrong_ed25519_fingerprint` | 769 | verifier reject | `alknet-tls` |
| `fingerprint_pin_verifier_matches_correct_sha256_fingerprint` | 789 | verifier X.509 accept | `alknet-tls` |
| `fingerprint_pin_verifier_rejects_wrong_sha256_fingerprint` | 806 | verifier X.509 reject | `alknet-tls` |
| `select_server_verifier_returns_ca_verifier_for_none` | 822 | CA path | `alknet-tls` |
| `select_server_verifier_returns_fingerprint_pin_for_some` | 839 | pin path | `alknet-tls` |
| `build_client_auth_presents_ed25519_raw_key_without_error` | 857 | client cert resolver | `alknet-tls` |
| `build_client_auth_none_resolves_to_no_client_cert` | 879 | no-cert resolver | `alknet-tls` |
| `build_quinn_client_config_with_raw_key_identity_builds_without_error` | 893 | full config build | `alknet-tls` |
| `build_quinn_client_config_with_no_remote_identity_builds_without_error` | 909 | CA-verify config | `alknet-tls` |
Wait — that's 10, not 8. The two `build_quinn_client_config` tests
test the full config build (verifier + client-auth + provider wired
together). They move to `alknet-tls` and are adapted to test
`TlsClientConfig::new` + `for_quinn()` instead of the free function.
**Category C — `connect` integration test, remove (0 tests):**
No test in `call_client.rs` actually calls `connect()`. The tests that
exercise the full QUIC dial path are in `from_call.rs` (which has 27
tests) and in the integration tests. `call_client.rs`'s tests are all
either protocol-level (Category A) or TLS-helper-level (Category B).
This means the `connect` removal doesn't break any test in
`call_client.rs` itself — the tests that need a real connection already
use `spawn_dispatch(stub_connection())`.
**The `from_call.rs` tests (27 tests):** these use `CallConnection`
directly (constructed from `stub_connection()` or a mock), not
`connect`. They're protocol-level and stay in `alknet-call` unchanged.
The one reference to `connect()` is in a doc comment (line 76:
"the assembly layer calls `from_call` immediately after `connect()`")
— update the comment to say "after `AlknetClient::dial_*` +
`spawn_dispatch`".
**Net Phase 5 test impact:** 6 tests stay unchanged (Category A), 10
tests move to `alknet-tls` in Phase 1 (Category B), 0 tests need
rewriting. The `from_call.rs` tests (27) stay unchanged. The prune
of `call_client.rs` is mechanical: delete lines 90-640 (the error
enum + `connect` + TLS helpers), keep lines 102-187 (`CallClient` +
`spawn_dispatch`), update imports. The tests (lines 640-930) keep the
Category A tests, remove the Category B tests (moved in Phase 1), and
update the one doc comment.
This is much simpler than the initial estimate of "~290 lines of test
restructuring." The actual test work is: move 10 tests to `alknet-tls`
in Phase 1 (adapted to the new API), keep 6 tests unchanged, update one
doc comment. The `connect` removal breaks zero tests because no test
calls `connect`.
## Open questions for the migration plan
- **Phase 6 `accept_bi` semantics:** does `accept_bi()` on a
`from_bidi` connection yield the single bidi stream directly usable
as `AsyncRead+AsyncWrite`, or does it need a wrapper? Needs
verification against the `BidiStreamSource` impl for `from_bidi`
in `types.rs`.
- **Workspace `Cargo.toml`:** the three new crates need to be added to
the workspace member list. Trivial but worth noting.
- **Integration tests (`tests/two_node_call.rs`):** the one integration
test file (331 lines, 2 tests) calls `client.connect()` twice and
builds its own raw quinn server (`build_raw_quinn_server`, lines
58-94 — hand-rolled rustls + quinn server config, no
`AlknetEndpoint`). Both tests (`call_round_trips_over_quic_loopback`
and `from_call_discovers_and_forwards_over_quic_loopback`) need the
`connect` path replaced with `AlknetClient::dial_quic` +
`spawn_dispatch`. The server side (`build_raw_quinn_server`) should
eventually use `AlknetEndpoint` + `TlsServerConfig`, but that's a
larger rewrite — for Phase 5, the minimal fix is to replace just the
client `connect` call with the dial + take-over composition. The
server-side raw quinn setup can stay (it's a test helper, not
production code). This integration test will need
`alknet-call` to dev-depend on `alknet-client` (or the test moves to
`alknet-client`'s own integration tests). The cleaner option: move
`two_node_call.rs` to `alknet-client/tests/` once `alknet-client`
exists (Phase 3), since it's testing the dial + take-over
composition, not just the protocol.