# 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.