docs(arch): ConnectionCredentials decouples dial from call protocol (ADR-091) + migration plan cleanups
ADR-091: ConnectionCredentials — decouple the dial credential bundle from the call protocol. CallCredentials (call-protocol-level, carries auth_token) was being used as the dial's credential type, coupling the dial to the call protocol. The auth_token is a hub-layer identity correlation mechanism (browsers, alknet/register), not a transport credential. ConnectionCredentials (transport-level: local_identity + remote_identity) is the dial's credential bundle; all three dial signatures unify on &ConnectionCredentials; dial_iroh's node_id parameter is derived from remote_identity.fingerprint. CallCredentials stays in alknet-call with auth_token. The shape is validated by the future dial_ssh pattern (russh's check_server_key + authenticate_publickey consume the same two dimensions). Amends ADR-089 §3 (dial signatures) and §5 (move consequence), ADR-087 (input framing). Updates client/tls/call/core crate specs and the extraction plan's Phase 0/3/4/5. Migration plan cleanups (findings.md): - Remove duplicated ordering-rationale bullets (copy-paste artifact) - TL;DR: six phases -> seven (Phase 0 promoted); four compilable intermediate states -> each phase leaves workspace compilable - Remove inline 'Wait — that's 10, not 8' self-correction; fix Category B header to (10 tests) - Replace contradictory line ranges in Net Phase 5 test impact with name-based references - Decide quinn feature fate: removed (not no-op) - Note webpki-roots always-present per ADR-088 §5 in Phase 1 dep list
This commit is contained in:
1 parent
16ae3cf6c9
commit
613bf680cd
9 files changed
+599
-154
No files matched your search
@@ -210,7 +210,7 @@ adapter location map is now consistent: all HTTP-backed adapters
|
||||
|----------|--------|-------------|
|
||||
| [overview.md](overview.md) | draft | Workspace-level overview, crate graph (core mono-repo scope per ADR-085), hub/worker model, shared types, design principles |
|
||||
| [open-questions.md](open-questions.md) | draft | OQ index — theme-grouped tables + Deferred/Blocked section; per-OQ files in [`questions/`](questions/) |
|
||||
| [crates/core/README.md](crates/core/README.md) | draft | alknet-core crate index — shared types + auth + config (endpoint extracted to `alknet-endpoint` per ADR-083 Am. 2026-07-15; `CallCredentials`/`RemoteIdentity` moved here from `alknet-call` per ADR-089 §5) |
|
||||
| [crates/core/README.md](crates/core/README.md) | draft | alknet-core crate index — shared types + auth + config (endpoint extracted to `alknet-endpoint` per ADR-083 Am. 2026-07-15; `ConnectionCredentials`/`RemoteIdentity` moved here from `alknet-call` per ADR-091; `CallCredentials` stays in `alknet-call`) |
|
||||
| [crates/core/core-types.md](crates/core/core-types.md) | draft | ProtocolHandler, HandlerError, Connection (`Box<dyn BidiStreamSource>` — ADR-070), BidiStreamSource trait, BiStream, StreamError |
|
||||
| [crates/core/endpoint.md](crates/core/endpoint.md) | deprecated | Endpoint spec — **moved to `alknet-endpoint`** (ADR-083 Am. 2026-07-15); see [`crates/endpoint/README.md`](crates/endpoint/README.md) |
|
||||
| [crates/core/auth.md](crates/core/auth.md) | draft | AuthContext (incl. `anonymous` constructor), Identity, IdentityProvider, AuthToken, resolution flow |
|
||||
@@ -243,7 +243,7 @@ adapter location map is now consistent: all HTTP-backed adapters
|
||||
| [crates/vault/protocol.md](crates/vault/protocol.md) | stable | DerivedKey redaction, KeyType, serialization behavior |
|
||||
| [crates/hub/README.md](crates/hub/README.md) | draft | alknet-hub crate — composes a subset of three endpoint types (web/native/iroh — ADR-086), channels substrate (ADR-079 relay), worker registration flow (OQ-58), identity over transports, aggregated peer env, connection lifecycle, service discovery |
|
||||
| [crates/tls/README.md](crates/tls/README.md) | reviewed | alknet-tls crate — shared TLS config (`TlsServerConfig` + `TlsClientConfig`) shared across quinn + TCP+TLS + iroh; one cert, one ACME state machine, N transports; split ALPN lists per endpoint type (ADR-086, resolves OQ-62); `FingerprintPinVerifier` moved here from `alknet-call` (ADR-089 §5); `webpki-roots` fallback for empty platform stores (ADR-088 §5); fixes cert-reuse welding in `alknet-core/endpoint.rs` (ADR-082) |
|
||||
| [crates/client/README.md](crates/client/README.md) | draft | alknet-client crate — the native client dial seam (`AlknetClient`), client-side analogue of `AlknetEndpoint`; three dials (QUIC + TCP+TLS via `TlsClientConfig`, iroh via key); optional SOCKS5 proxy (ADR-090 — UDP ASSOCIATE for QUIC, CONNECT for TCP+TLS, force-relay-only + HTTP-to-SOCKS5 bridge for iroh; OQ-67 resolved); produces `Connection` for `CallClient`/`ChannelClient` take-over; `CallClient::connect`/`ChannelClient::connect_quic` removed (dial centralized here); `alknet/register` named (wire protocol deferred, OQ-66) |
|
||||
| [crates/client/README.md](crates/client/README.md) | draft | alknet-client crate — the native client dial seam (`AlknetClient`), client-side analogue of `AlknetEndpoint`; three dials (QUIC + TCP+TLS via `TlsClientConfig`, iroh via key) unified on `&ConnectionCredentials` (ADR-091); optional SOCKS5 proxy (ADR-090 — UDP ASSOCIATE for QUIC, CONNECT for TCP+TLS, force-relay-only + HTTP-to-SOCKS5 bridge for iroh; OQ-67 resolved); produces `Connection` for `CallClient`/`ChannelClient` take-over; `CallClient::connect`/`ChannelClient::connect_quic` removed (dial centralized here); `alknet/register` named (wire protocol deferred, OQ-66) |
|
||||
| [crates/endpoint/README.md](crates/endpoint/README.md) | draft | alknet-endpoint crate — the server-side accept-loop runner (`AlknetEndpoint`), extracted from `alknet-core` (ADR-083 Am. 2026-07-15); takes pre-built transports via `with_quinn`/`with_iroh`/`with_tcp_tls`; public `dispatch` for SSH/WT; `EndpointError` removed (vestigial); handler crates no longer transitively link quinn/iroh |
|
||||
| [crates/channels/README.md](crates/channels/README.md) | draft | alknet-channels crate — multiplexing proxy, 9-byte chunk format, N channels over one transport stream |
|
||||
| [crates/channels/overview.md](crates/channels/overview.md) | draft | Crate purpose, the multiplexing collapse, dependencies, transport agnosticism, WASM, relationship to existing crates |
|
||||
@@ -343,10 +343,11 @@ adapter location map is now consistent: all HTTP-backed adapters
|
||||
| [084](decisions/084-aws-lc-rs-crypto-provider.md) | aws-lc-rs as the TLS Crypto Provider | Accepted |
|
||||
| [085](decisions/085-workspace-scope-core-vs-consumer-repos.md) | Workspace Scope — Core vs. Consumer Repos | Accepted |
|
||||
| [086](decisions/086-endpoint-types-and-entry-points.md) | Endpoint Types and Entry Points | Accepted |
|
||||
| [087](decisions/087-tlsclientconfig-not-blocked-on-dial.md) | `TlsClientConfig` Not Blocked on Dial Seam | Accepted (§5 amended by ADR-089 — `FingerprintPinVerifier` moves to `alknet-tls`; `alknet-call` sheds TLS deps) |
|
||||
| [087](decisions/087-tlsclientconfig-not-blocked-on-dial.md) | `TlsClientConfig` Not Blocked on Dial Seam | Accepted (§5 amended by ADR-089 — `FingerprintPinVerifier` moves to `alknet-tls`; `alknet-call` sheds TLS deps; input framing amended by ADR-091 — `ClientVerifierContext` derived from `ConnectionCredentials`, not `CallCredentials`) |
|
||||
| [088](decisions/088-tlserror-shape.md) | `TlsError` Shape — Single Enum, Owned by `alknet-tls` | Accepted (§5 added — `webpki-roots` fallback when platform store is empty; §7 references ADR-089 for handshake-error surfacing) |
|
||||
| [089](decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — Native Client Dial Seam | Accepted (resolves OQ-55; `CallClient::connect` / `ChannelClient::connect_quic` removed; `CallCredentials`/`RemoteIdentity` moved to `alknet-core`; `FingerprintPinVerifier` moved to `alknet-tls`; `ClientError` removed; `alknet-call` sheds TLS deps) |
|
||||
| [089](decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — Native Client Dial Seam | Accepted (resolves OQ-55; `CallClient::connect` / `ChannelClient::connect_quic` removed; §3/§5 amended by ADR-091 — dial takes `ConnectionCredentials`, not `CallCredentials`; `CallCredentials` stays in `alknet-call`; `FingerprintPinVerifier` moved to `alknet-tls`; `ClientError` removed; `alknet-call` sheds TLS deps) |
|
||||
| [090](decisions/090-client-dial-socks5-proxy-seam.md) | Client-Dial SOCKS5 Proxy Seam | Accepted (§5 amended 2026-07-16 — OQ-67 resolved: iroh force-relay-only + HTTP-to-SOCKS5 bridge) |
|
||||
| [091](decisions/091-connectioncredentials-decouple-dial-from-call.md) | `ConnectionCredentials` — Decouple Dial Credentials from Call Protocol | Accepted (amends ADR-089 §3/§5 and ADR-087 input framing; dial takes `ConnectionCredentials` not `CallCredentials`; all three dial signatures unified; `dial_iroh`'s `node_id` derived from `remote_identity`; `auth_token` stays in call-protocol layer) |
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
@@ -246,19 +246,43 @@ attribution, filtered by the calling peer's authorization). See
|
||||
|
||||
### Credential sources for connections
|
||||
|
||||
`CallCredentials` (now in `alknet-core`, moved from `alknet-call` per
|
||||
ADR-089 §5 so the dial does not depend on the call protocol) carries
|
||||
the three credential dimensions (ADR-017 §7). Credentials come from
|
||||
`Capabilities` (ADR-014), never from environment variables. The three
|
||||
credential dimensions (ADR-017 §7):
|
||||
The credential dimensions are split across two layers (ADR-091):
|
||||
|
||||
- **`ConnectionCredentials`** (in `alknet-core`, moved from
|
||||
`alknet-call` per ADR-091) — the **transport-level** credential
|
||||
bundle, consumed by the dial (`AlknetClient`). Carries the two
|
||||
transport-identity dimensions: `local_identity` (the local node's
|
||||
`TlsIdentity`) and `remote_identity` (the expected fingerprint). The
|
||||
dial does not depend on the call protocol for this type.
|
||||
- **`CallCredentials`** (stays in `alknet-call`) — the
|
||||
**call-protocol** credential bundle. The `auth_token` dimension
|
||||
(ADR-017 §7) is a call-protocol / hub-layer concept: a bearer token
|
||||
correlated to an identity via `IdentityProvider::resolve_from_token`,
|
||||
used for browsers (no raw-key support) and `alknet/register` (no
|
||||
prior peer relationship). It is a per-request field on
|
||||
`call.requested` payloads, not a transport credential.
|
||||
|
||||
Credentials come from `Capabilities` (ADR-014), never from environment
|
||||
variables. The three credential dimensions (ADR-017 §7):
|
||||
|
||||
```rust
|
||||
pub struct CallCredentials {
|
||||
pub tls_identity: Option<TlsIdentity>, // RFC 7250 raw key or X.509
|
||||
pub auth_token: Option<AuthToken>, // call-protocol-level token
|
||||
pub remote_identity: Option<RemoteIdentity>, // expected fingerprint/cert (None = CA path, see below)
|
||||
// Transport-level (alknet-core, consumed by the dial — ADR-091)
|
||||
pub struct ConnectionCredentials {
|
||||
pub local_identity: Option<TlsIdentity>, // RFC 7250 raw key or X.509
|
||||
pub remote_identity: Option<RemoteIdentity>, // expected fingerprint (None = CA path / fail-closed)
|
||||
}
|
||||
|
||||
// Call-protocol-level (alknet-call — the auth_token stays here)
|
||||
// The auth_token is set per-request on call.requested payloads, not
|
||||
// carried by the dial.
|
||||
```
|
||||
|
||||
`CallCredentials` retains `auth_token` (and may assemble from
|
||||
`ConnectionCredentials` + `auth_token` at the take-over site, or the
|
||||
caller provides `auth_token` per-request via `call_with_payload`). The
|
||||
transport dimensions (`local_identity`, `remote_identity`) moved to
|
||||
`ConnectionCredentials` in `alknet-core` per ADR-091.
|
||||
|
||||
/// Expected identity of the remote node (ADR-017 §7, extended by
|
||||
/// ADR-034 §2). Carries a fingerprint string the assembly layer
|
||||
/// derives from `Capabilities` when the local node has a `PeerEntry`
|
||||
|
||||
@@ -155,7 +155,7 @@ hiding the client's real IP from the hub.
|
||||
|
||||
```rust
|
||||
impl AlknetClient {
|
||||
/// QUIC dial. Builds a `TlsClientConfig` from `credentials`
|
||||
/// QUIC dial. Builds a `TlsClientConfig` from `creds`
|
||||
/// (ADR-034 verifier selection + ADR-084 provider), dials `addr`
|
||||
/// on `alpn`, returns a `Connection` via
|
||||
/// `Connection::from_quinn_with_alpn`. The `server_name` is the
|
||||
@@ -167,10 +167,10 @@ impl AlknetClient {
|
||||
addr: SocketAddr,
|
||||
server_name: &str,
|
||||
alpn: &[u8],
|
||||
credentials: &CallCredentials,
|
||||
creds: &ConnectionCredentials,
|
||||
) -> Result<Connection, ClientDialError>;
|
||||
|
||||
/// TCP+TLS dial. Builds a `TlsClientConfig` from `credentials`,
|
||||
/// TCP+TLS dial. Builds a `TlsClientConfig` from `creds`,
|
||||
/// connects a `TcpStream` to `addr`, wraps with `TlsConnector`
|
||||
/// using `host` as the SNI, returns a `Connection` via
|
||||
/// `Connection::from_bidi` (ADR-065). Feature-gated on `tcp`.
|
||||
@@ -180,21 +180,23 @@ impl AlknetClient {
|
||||
host: &str,
|
||||
addr: SocketAddr,
|
||||
alpn: &[u8],
|
||||
credentials: &CallCredentials,
|
||||
creds: &ConnectionCredentials,
|
||||
) -> Result<Connection, ClientDialError>;
|
||||
|
||||
/// Iroh dial. Dials `node_id` on `alpn` via the iroh endpoint.
|
||||
/// The iroh path does NOT use `TlsClientConfig` — iroh has its
|
||||
/// own TLS (shares the `Ed25519SecretKey`, not the rustls config —
|
||||
/// ADR-087 §3, ADR-089 §3). The verifier is iroh's `NodeId` match
|
||||
/// (fingerprint pin by another name — ADR-034 §3). An unknown
|
||||
/// iroh remote fails closed (no CA). Feature-gated on `iroh`.
|
||||
/// Iroh dial. Dials on `alpn` via the iroh endpoint. The iroh path
|
||||
/// does NOT use `TlsClientConfig` — iroh has its own TLS (shares the
|
||||
/// `Ed25519SecretKey`, not the rustls config — ADR-087 §3, ADR-089
|
||||
/// §3). The local key is extracted from `creds.local_identity`; the
|
||||
/// remote `NodeId` is derived from `creds.remote_identity.fingerprint`
|
||||
/// (`ed25519:<hex>` → `NodeId::from_bytes`). The verifier is iroh's
|
||||
/// `NodeId` match (fingerprint pin by another name — ADR-034 §3).
|
||||
/// An unknown iroh remote fails closed (no CA). Feature-gated on
|
||||
/// `iroh`.
|
||||
#[cfg(feature = "iroh")]
|
||||
pub async fn dial_iroh(
|
||||
&self,
|
||||
node_id: iroh::NodeId,
|
||||
alpn: &[u8],
|
||||
local_key: &alknet_core::config::Ed25519SecretKey,
|
||||
creds: &ConnectionCredentials,
|
||||
) -> Result<Connection, ClientDialError>;
|
||||
}
|
||||
```
|
||||
@@ -204,9 +206,12 @@ The two rustls dials (`dial_quic`, `dial_tcp_tls`) share
|
||||
pin for a known peer, CA-verify for an unknown X.509 remote, fail-closed
|
||||
for an unknown raw-key remote) and the ADR-084 crypto provider
|
||||
(`aws_lc_rs`). The iroh dial is the exception: iroh has its own TLS and
|
||||
takes the `Ed25519SecretKey` directly, not a `rustls::ClientConfig`.
|
||||
The consistency is in the rule (ADR-034), not in the type — the same
|
||||
exception as the server side (ADR-082, ADR-087 §3).
|
||||
takes the `Ed25519SecretKey` directly (extracted from
|
||||
`creds.local_identity`), not a `rustls::ClientConfig`. The consistency
|
||||
is in the rule (ADR-034), not in the type — the same exception as the
|
||||
server side (ADR-082, ADR-087 §3). All three dials take
|
||||
`&ConnectionCredentials` — the unified transport-level credential
|
||||
bundle (ADR-091).
|
||||
|
||||
### What the dial does NOT do
|
||||
|
||||
@@ -266,7 +271,7 @@ invariant. The config's crate location (it can live in `alknet-client`,
|
||||
detail; the shape is decided (ADR-090 §1).
|
||||
|
||||
**The proxy is invisible above the dial.** `Connection`, dispatch,
|
||||
`CallCredentials`, `TlsClientConfig`, the hub's `supervise_worker`
|
||||
`ConnectionCredentials`, `TlsClientConfig`, the hub's `supervise_worker`
|
||||
closure — none know a proxy is in the path. The proxy is purely an
|
||||
establishment concern, localized to `alknet-client`. A proxied QUIC
|
||||
connection still yields a `Connection::from_quinn_with_alpn`; a
|
||||
@@ -328,25 +333,25 @@ the alknet type level — see ADR-090 §"Two distinct SOCKS5 uses".
|
||||
|
||||
### Credentials
|
||||
|
||||
`AlknetClient`'s dials take a `CallCredentials` bundle — the shared
|
||||
type from `alknet-core` (moved out of `alknet-call` so the dial does
|
||||
not depend on the call protocol; see ADR-089 §5). It carries the local
|
||||
`TlsIdentity`, the optional call-protocol-level `auth_token`, and the
|
||||
`RemoteIdentity` for verifier selection. The credentials come from
|
||||
`Capabilities` (ADR-014), never from environment variables — the
|
||||
no-env-vars invariant. The assembly layer derives them from the vault
|
||||
at startup and passes them to each dial.
|
||||
`AlknetClient`'s dials take a `ConnectionCredentials` bundle — the
|
||||
transport-level credential type from `alknet-core` (ADR-091). It
|
||||
carries the two dimensions every dial consumes: the local `TlsIdentity`
|
||||
(presented to the transport's identity layer) and the `RemoteIdentity`
|
||||
for verifier selection. The credentials come from `Capabilities`
|
||||
(ADR-014), never from environment variables — the no-env-vars
|
||||
invariant. The assembly layer derives them from the vault at startup
|
||||
and passes them to each dial.
|
||||
|
||||
**The `auth_token` is stripped at the TLS boundary.** `TlsClientConfig`
|
||||
and `ClientVerifierContext` are TLS-level types and do not carry the
|
||||
call-protocol auth token. The dial extracts the TLS-relevant fields
|
||||
from `CallCredentials` — `tls_identity` → the client-cert
|
||||
`local_identity`, `remote_identity` → the fingerprint-pin input to
|
||||
`ClientVerifierContext` — and builds a `TlsClientConfig` from those
|
||||
alone. The `auth_token` travels with the `Connection` into the
|
||||
protocol take-over (`spawn_dispatch` / `from_connection`), where it is
|
||||
sent as the first call-protocol frame when the protocol requires it.
|
||||
This keeps `alknet-tls` free of any call-protocol coupling.
|
||||
`ConnectionCredentials` is **not** the call-protocol credential bundle.
|
||||
The call-protocol `auth_token` (a hub-correlated bearer for browsers
|
||||
and `alknet/register` — ADR-017 §7) is a per-request field on
|
||||
`call.requested` payloads, not a transport credential. It stays in the
|
||||
call-protocol layer (`CallCredentials` in `alknet-call`); the dial does
|
||||
not carry it. `Dispatcher::resolve_identity` resolves it via
|
||||
`IdentityProvider::resolve_from_token` at dispatch time; the `from_call`
|
||||
forwarding handler sets it via `build_forwarded_payload`. This keeps
|
||||
`alknet-client` free of call-protocol coupling. See
|
||||
[ADR-091](../../decisions/091-connectioncredentials-decouple-dial-from-call.md).
|
||||
|
||||
### The dialable ALPNs
|
||||
|
||||
@@ -425,11 +430,13 @@ public key) is verified against the expected `NodeId`, which is
|
||||
fingerprint-pinning by another name. An unknown iroh remote fails
|
||||
closed (no CA to fall back to — ADR-034 §3, Assumption 1).
|
||||
|
||||
The `dial_iroh` method takes the `Ed25519SecretKey` as a parameter
|
||||
rather than pulling it from `CallCredentials` because iroh does not use
|
||||
the `TlsClientConfig` path. The assembly layer reads the key from
|
||||
`StaticConfig` (in core) and passes it directly, same as the server
|
||||
side's iroh endpoint construction.
|
||||
The `dial_iroh` method extracts the key from
|
||||
`creds.local_identity` (`ConnectionCredentials`) rather than taking a
|
||||
separate `Ed25519SecretKey` parameter because the dial signature is
|
||||
unified — all three dials take `&ConnectionCredentials` (ADR-091). The
|
||||
assembly layer reads the key from `StaticConfig` (in core) and passes
|
||||
it via `ConnectionCredentials`, same as the server side's iroh endpoint
|
||||
construction.
|
||||
|
||||
### Non-Rust native clients (out of scope)
|
||||
|
||||
@@ -557,7 +564,7 @@ don't use a proxy don't pay the dep.
|
||||
|
||||
```
|
||||
alknet-client
|
||||
├── alknet-core (Connection, CallCredentials, RemoteIdentity,
|
||||
├── alknet-core (Connection, ConnectionCredentials, RemoteIdentity,
|
||||
│ Ed25519SecretKey, types)
|
||||
├── alknet-tls (TlsClientConfig — for quinn + tcp dials)
|
||||
├── quinn (optional — dial_quic)
|
||||
@@ -569,14 +576,15 @@ alknet-client
|
||||
```
|
||||
|
||||
`alknet-client` depends on `alknet-tls` (for `TlsClientConfig`) and
|
||||
`alknet-core` (for `Connection`, `CallCredentials`, `RemoteIdentity`,
|
||||
and types). It does **not** depend on `alknet-call` or
|
||||
`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.
|
||||
`CallCredentials` and `RemoteIdentity` live in `alknet-core` (moved
|
||||
from `alknet-call` per ADR-089 §5 — both the call and channels clients
|
||||
need them, and the dial must not depend on the call protocol; see
|
||||
ADR-089 §5). `FingerprintPinVerifier` lives in `alknet-tls` (moved from
|
||||
`alknet-call` per ADR-087 §5 — it is a TLS concern, and
|
||||
`ConnectionCredentials` and `RemoteIdentity` live in `alknet-core`
|
||||
(transport-level credential types, moved from `alknet-call` per
|
||||
ADR-091 — the dial and the server-side transport construction both
|
||||
consume them, and the dial must not depend on the call protocol for the
|
||||
credential type). `FingerprintPinVerifier` lives in `alknet-tls` (moved
|
||||
from `alknet-call` per ADR-087 §5 — it is a TLS concern, and
|
||||
`TlsClientConfig::new` constructs it; moving it lets `alknet-call` shed
|
||||
its direct `rustls` dep entirely).
|
||||
|
||||
@@ -606,7 +614,7 @@ alknet-worker (uses AlknetClient to dial a hub)
|
||||
`AlknetClient` is one producer, but a test can hand them a
|
||||
`Connection::from_stream` directly. The dependency direction is:
|
||||
`alknet-client → alknet-tls → alknet-core`; the protocol crates are
|
||||
parallel, not downstream of the dial. `CallCredentials` and
|
||||
parallel, not downstream of the dial. `ConnectionCredentials` and
|
||||
`RemoteIdentity` live in `alknet-core` (not `alknet-call`), so the dial
|
||||
does not depend on the call protocol for the credential type.
|
||||
|
||||
@@ -632,8 +640,8 @@ let client = AlknetClient::new()
|
||||
// .with_iroh(iroh_endpoint) — if iroh is needed
|
||||
|
||||
// 3. Derive credentials from the vault (ADR-014 — no env vars).
|
||||
let creds = CallCredentials::new()
|
||||
.with_tls_identity(TlsIdentity::RawKey(local_key))
|
||||
let creds = ConnectionCredentials::new()
|
||||
.with_local_identity(TlsIdentity::RawKey(local_key))
|
||||
.with_remote_identity(RemoteIdentity {
|
||||
fingerprint: hub_fingerprint, // known peer → fingerprint pin
|
||||
});
|
||||
@@ -665,8 +673,9 @@ All design decisions are documented as ADRs in
|
||||
|
||||
| ADR | Decision | Summary |
|
||||
|-----|----------|---------|
|
||||
| [089](../../decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — native client dial seam | New crate `alknet-client`; client-side analogue of `AlknetEndpoint`; three dials (QUIC + TCP+TLS via `TlsClientConfig`, iroh via key); resolves OQ-55; `alknet/register` named, wire protocol deferred |
|
||||
| [089](../../decisions/089-alknetclient-native-dial-seam.md) | AlknetClient — native client dial seam | New crate `alknet-client`; client-side analogue of `AlknetEndpoint`; three dials (QUIC + TCP+TLS via `TlsClientConfig`, iroh via key); resolves OQ-55; `alknet/register` named, wire protocol deferred (§3/§5 amended by ADR-091 — dial takes `ConnectionCredentials`, not `CallCredentials`) |
|
||||
| [090](../../decisions/090-client-dial-socks5-proxy-seam.md) | Client-Dial SOCKS5 Proxy Seam | `AlknetClient` gains `with_socks5_proxy`; `dial_quic` routes via UDP ASSOCIATE, `dial_tcp_tls` via CONNECT, `dial_iroh` forces relay-only via an HTTP-to-SOCKS5 bridge; OQ-67 resolved; grounded in the quinn-proxy + iroh-proxy PoCs |
|
||||
| [091](../../decisions/091-connectioncredentials-decouple-dial-from-call.md) | `ConnectionCredentials` — decouple dial from call protocol | The dial credential bundle is `ConnectionCredentials` (transport-level: `local_identity` + `remote_identity`), not `CallCredentials` (call-protocol-level); all three dial signatures unify on `&ConnectionCredentials`; `dial_iroh`'s `node_id` derived from `remote_identity`; `auth_token` stays in the call-protocol layer; `CallCredentials` stays in `alknet-call` |
|
||||
|
||||
## Open Questions
|
||||
|
||||
@@ -700,7 +709,10 @@ See [open-questions.md](../../open-questions.md) for full details.
|
||||
## References
|
||||
|
||||
- [ADR-089](../../decisions/089-alknetclient-native-dial-seam.md) —
|
||||
the decision this spec implements
|
||||
the decision this spec implements (§3/§5 amended by ADR-091)
|
||||
- [ADR-091](../../decisions/091-connectioncredentials-decouple-dial-from-call.md)
|
||||
— `ConnectionCredentials` (the dial credential bundle; decouples the
|
||||
dial from the call protocol)
|
||||
- [ADR-090](../../decisions/090-client-dial-socks5-proxy-seam.md) —
|
||||
the SOCKS5 proxy seam (client-dial privacy)
|
||||
- [ADR-083](../../decisions/083-endpoint-as-accept-loop-runner.md) —
|
||||
|
||||
@@ -15,10 +15,12 @@ vestigial); core no longer carries the accept-loop runner or its
|
||||
transport deps (quinn, iroh, rcgen, rustls-acme).
|
||||
`Connection::from_quinn` / `from_iroh` stay in core's `types.rs` as
|
||||
shared constructors (gated on core's `quinn` / `iroh` features).
|
||||
`CallCredentials` and `RemoteIdentity` move to `alknet-core` (from
|
||||
`alknet-call`, per ADR-089 §5) — both the call and channels clients
|
||||
need them, and the dial (`alknet-client`) must not depend on the call
|
||||
protocol for the credential type.
|
||||
`ConnectionCredentials` and `RemoteIdentity` move to `alknet-core`
|
||||
(from `alknet-call`, per ADR-091) — the transport-level credential
|
||||
bundle consumed by the dial (`alknet-client`) and by server-side
|
||||
transport construction. `CallCredentials` (the call-protocol credential
|
||||
bundle, including `auth_token`) stays in `alknet-call` — the dial does
|
||||
not carry call-protocol dimensions.
|
||||
|
||||
## Documents
|
||||
|
||||
|
||||
@@ -503,7 +503,8 @@ pub struct TlsClientConfig {
|
||||
|
||||
impl TlsClientConfig {
|
||||
/// Build a client TLS config. Takes two inputs, both derived from
|
||||
/// `Capabilities` (ADR-014) / `CallCredentials`-shaped values:
|
||||
/// `Capabilities` (ADR-014) / `ConnectionCredentials`-shaped values
|
||||
/// (ADR-091):
|
||||
///
|
||||
/// 1. `local_identity` — the local node's `TlsIdentity` (RFC 7250
|
||||
/// raw key or X.509), presented as the client cert. `None` →
|
||||
@@ -555,14 +556,15 @@ The `ClientVerifierContext` carries the inputs to ADR-034's verifier
|
||||
selection (whether a `PeerEntry` exists for the remote, the expected
|
||||
fingerprint). The exact struct shape is an implementation detail; the
|
||||
decisions are in ADR-034. `ClientVerifierContext` is derived from
|
||||
`CallCredentials` (in `alknet-core`) at the dial site — `AlknetClient`
|
||||
extracts the TLS-relevant fields (`tls_identity` → `local_identity`,
|
||||
`remote_identity` → fingerprint-pin input) and builds a
|
||||
`ClientVerifierContext` from those. The call-protocol `auth_token` is
|
||||
**stripped at the TLS boundary** — it never reaches `TlsClientConfig` or
|
||||
`ClientVerifierContext`; it travels with the `Connection` into the
|
||||
protocol take-over. The `TlsError` variant granularity (covering both
|
||||
server and client errors) is decided — see
|
||||
`ConnectionCredentials` (in `alknet-core`, per ADR-091) at the dial
|
||||
site — `AlknetClient` extracts the TLS-relevant fields
|
||||
(`local_identity` → client cert, `remote_identity` → fingerprint-pin
|
||||
input) and builds a `ClientVerifierContext` from the latter. The
|
||||
call-protocol `auth_token` is not in `ConnectionCredentials` — it is a
|
||||
per-request field on `call.requested` payloads (a call-protocol / hub
|
||||
concept), not a transport credential; it never reaches `TlsClientConfig`
|
||||
or `ClientVerifierContext`. The `TlsError` variant granularity (covering
|
||||
both server and client errors) is decided — see
|
||||
[ADR-088](../../decisions/088-tlserror-shape.md) and the
|
||||
[`TlsError`](#tlserror) section below.
|
||||
|
||||
@@ -693,8 +695,9 @@ alknet-core (loses TLS setup code + endpoint)
|
||||
├── (rustls — only for fingerprint.rs types, if kept)
|
||||
|
||||
alknet-call (pure protocol crate — no TLS/transport deps per ADR-089 §5)
|
||||
└── alknet-core (ProtocolHandler, Connection, types; CallCredentials/RemoteIdentity
|
||||
moved to core per ADR-089 §5)
|
||||
└── alknet-core (ProtocolHandler, Connection, types; ConnectionCredentials/
|
||||
RemoteIdentity moved to core per ADR-091; CallCredentials stays in
|
||||
alknet-call)
|
||||
|
||||
alknet-hub (multi-transport endpoint)
|
||||
├── alknet-tls (TlsServerConfig — shared across quinn + TCP)
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (resolves OQ-64)
|
||||
Accepted (resolves OQ-64; input framing amended 2026-07-16 by ADR-091 —
|
||||
`ClientVerifierContext` is derived from `ConnectionCredentials.remote_identity`,
|
||||
not `CallCredentials.remote_identity`; the `auth_token` is not in the
|
||||
dial's credential bundle)
|
||||
|
||||
## Context
|
||||
|
||||
@@ -122,6 +125,16 @@ in ADR-034; the struct is a bag of already-decided inputs). It is
|
||||
sketched lightly here; the full variant-granularity of `TlsError` is
|
||||
OQ-63 (the next session).
|
||||
|
||||
> **Amendment 2026-07-16 (ADR-091):** `ClientVerifierContext` is derived
|
||||
> from `ConnectionCredentials.remote_identity` (the transport-level
|
||||
> credential bundle), not `CallCredentials.remote_identity`. The dial
|
||||
> (`AlknetClient::dial_quic` / `dial_tcp_tls`) extracts
|
||||
> `creds.remote_identity` from `ConnectionCredentials` and builds
|
||||
> `ClientVerifierContext` from it. `CallCredentials` is no longer in the
|
||||
> dial's path — its `auth_token` dimension is a call-protocol concept,
|
||||
> not a transport credential. See
|
||||
> [ADR-091](091-connectioncredentials-decouple-dial-from-call.md).
|
||||
|
||||
This is **not** the dial. `TlsClientConfig` produces a
|
||||
`rustls::ClientConfig`; the caller (the transport-specific dial helper
|
||||
— now `AlknetClient::dial_quic` / `dial_tcp_tls` per ADR-089; the
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (resolves OQ-55)
|
||||
Accepted (resolves OQ-55; §3 and §5 amended 2026-07-16 by ADR-091 —
|
||||
the dial credential bundle is `ConnectionCredentials` (transport-level),
|
||||
not `CallCredentials` (call-protocol-level); all three dial signatures
|
||||
unify on `&ConnectionCredentials`; `dial_iroh`'s `node_id` parameter is
|
||||
derived from `remote_identity`; the `auth_token` stays in the
|
||||
call-protocol layer, not the dial; `CallCredentials` stays in
|
||||
`alknet-call`, only `ConnectionCredentials`/`RemoteIdentity` move to
|
||||
`alknet-core`)
|
||||
|
||||
## Context
|
||||
|
||||
@@ -195,6 +202,9 @@ impl AlknetClient {
|
||||
local_key: &alknet_core::config::Ed25519SecretKey,
|
||||
) -> Result<Connection, ClientDialError>;
|
||||
}
|
||||
|
||||
// NOTE: The signatures above are the ORIGINAL (pre-ADR-091) shapes.
|
||||
// ADR-091 amends §3 — see the amendment note below.
|
||||
```
|
||||
|
||||
The three dials share `CallCredentials` (the local identity + remote
|
||||
@@ -205,6 +215,33 @@ server side's "iroh shares the key, not the config" (ADR-082, ADR-087
|
||||
§3) — the consistency is in the rule (ADR-034 verifier selection), not
|
||||
in the type.
|
||||
|
||||
> **Amendment 2026-07-16 (ADR-091):** The dial credential bundle is
|
||||
> `ConnectionCredentials`, not `CallCredentials`. `CallCredentials`
|
||||
> couples the dial to the call protocol (its `auth_token` field is a
|
||||
> call-protocol / hub-layer concept — bearer-token identity correlation
|
||||
> for browsers and `alknet/register`, not a transport credential). The
|
||||
> dial uses only the transport-identity dimensions (`local_identity` +
|
||||
> `remote_identity`); those move to `ConnectionCredentials` in
|
||||
> `alknet-core`. All three dial signatures unify on
|
||||
> `&ConnectionCredentials`:
|
||||
>
|
||||
> ```rust
|
||||
> dial_quic(addr, server_name, alpn, creds: &ConnectionCredentials) -> Connection
|
||||
> dial_tcp_tls(host, addr, alpn, creds: &ConnectionCredentials) -> Connection
|
||||
> dial_iroh(alpn, creds: &ConnectionCredentials) -> Connection
|
||||
> ```
|
||||
>
|
||||
> The `node_id: iroh::NodeId` parameter on `dial_iroh` is removed — it
|
||||
> is derived from `creds.remote_identity.fingerprint`
|
||||
> (`ed25519:<hex>` → `NodeId::from_bytes`), the same extraction pattern
|
||||
> the rustls dials use for the verifier. The consistency is now in both
|
||||
> the rule (ADR-034) and the type. `CallCredentials` stays in
|
||||
> `alknet-call` as the call-protocol credential bundle; the
|
||||
> `auth_token` is a per-request field on `call.requested` payloads
|
||||
> (set by the caller or the `from_call` forwarding handler, resolved by
|
||||
> `Dispatcher::resolve_identity`), not a dial-level credential. See
|
||||
> [ADR-091](091-connectioncredentials-decouple-dial-from-call.md).
|
||||
|
||||
### 4. The dial is transport-polymorphic across the native endpoint type
|
||||
|
||||
The native endpoint type (ADR-086) has QUIC + TCP+TLS (both
|
||||
@@ -259,6 +296,18 @@ options the original ADR-089 draft called a "two-way-door
|
||||
implementation detail"; it is not implementation detail — it determines
|
||||
the dep graph, and the dep graph requires it.
|
||||
|
||||
> **Amendment 2026-07-16 (ADR-091):** This consequence is superseded.
|
||||
> `CallCredentials` does **not** move to `alknet-core` — it stays in
|
||||
> `alknet-call` (it is the call-protocol credential bundle; its
|
||||
> `auth_token` field is a call-protocol / hub-layer concept, not a
|
||||
> transport credential). What moves to `alknet-core` is
|
||||
> `ConnectionCredentials` (a new type carrying only the
|
||||
> transport-identity dimensions: `local_identity` + `remote_identity`)
|
||||
> and `RemoteIdentity`. The dial consumes `ConnectionCredentials`, not
|
||||
> `CallCredentials`. All three dial signatures unify on
|
||||
> `&ConnectionCredentials`. See
|
||||
> [ADR-091](091-connectioncredentials-decouple-dial-from-call.md).
|
||||
|
||||
**Consequence: `FingerprintPinVerifier` moves to `alknet-tls`.** With
|
||||
`connect` removed and the verifier-selection logic centralized in
|
||||
`TlsClientConfig::new` (ADR-087), `FingerprintPinVerifier` has no
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
# ADR-091: `ConnectionCredentials` — Decouple the Dial Credentials from the Call Protocol
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (amends ADR-089 §3 and §5; amends ADR-087's `TlsClientConfig::new`
|
||||
input framing)
|
||||
|
||||
## Context
|
||||
|
||||
ADR-089 extracted the dial into `AlknetClient` and moved `CallCredentials`
|
||||
from `alknet-call` to `alknet-core` so the dial would not depend on the
|
||||
call protocol. The three dial signatures were:
|
||||
|
||||
```rust
|
||||
dial_quic(addr, server_name, alpn, credentials: &CallCredentials) -> Connection
|
||||
dial_tcp_tls(host, addr, alpn, credentials: &CallCredentials) -> Connection
|
||||
dial_iroh(node_id: iroh::NodeId, alpn, local_key: &Ed25519SecretKey) -> Connection
|
||||
```
|
||||
|
||||
Two problems surfaced on review:
|
||||
|
||||
### Problem 1: the iroh dial signature is asymmetric
|
||||
|
||||
`dial_quic` and `dial_tcp_tls` take `&CallCredentials`; `dial_iroh` takes
|
||||
a bare `&Ed25519SecretKey` + a separate `node_id: iroh::NodeId`. The
|
||||
asymmetry exists because iroh has its own TLS (it shares the key, not
|
||||
the rustls config — ADR-087 §3), so the iroh dial bypasses
|
||||
`TlsClientConfig` and reads the key directly. But the asymmetry forces
|
||||
the caller to know which dimension of the credential bundle each
|
||||
transport consumes, and it leaves no path for the iroh dial to receive
|
||||
the same inputs as the rustls dials — even though all three consume the
|
||||
same two things: a local identity (key/cert) and an expected remote
|
||||
identity (fingerprint).
|
||||
|
||||
### Problem 2: `CallCredentials` couples the dial to the call protocol
|
||||
|
||||
`CallCredentials` carries three dimensions (ADR-017 §7):
|
||||
|
||||
1. `tls_identity: Option<TlsIdentity>` — the local node's key/cert
|
||||
2. `auth_token: Option<AuthToken>` — a call-protocol-level bearer token
|
||||
3. `remote_identity: Option<RemoteIdentity>` — the expected remote fingerprint
|
||||
|
||||
The dial uses only dimensions 1 and 3 (the transport-identity layer).
|
||||
Dimension 2 (`auth_token`) is a **call-protocol** concept: it correlates
|
||||
a token to an identity via `IdentityProvider::resolve_from_token` — a
|
||||
mechanism that exists for two hub-dependent cases where TLS-fingerprint
|
||||
identity is unavailable:
|
||||
|
||||
- **Browsers** — no raw-key support, no client cert the hub can
|
||||
fingerprint; the browser authenticates via a bearer token over
|
||||
HTTP/WebSocket, and the hub's `IdentityProvider` resolves it.
|
||||
- **`alknet/register`** — a native worker that hasn't been enrolled dials
|
||||
in with no prior peer relationship; a registration token (or open
|
||||
registration) establishes identity, not a TLS fingerprint.
|
||||
|
||||
Both depend on a **hub** running `IdentityProvider` with token-to-identity
|
||||
mapping. A pure P2P connection (two nodes with raw-key identities) never
|
||||
needs `auth_token` — the TLS fingerprint IS the identity.
|
||||
|
||||
`auth_token` is not a transport credential. It is a per-request field on
|
||||
`call.requested` payloads (`Dispatcher::resolve_identity` reads
|
||||
`payload.get("auth_token")`; the `from_call` forwarding handler sets it
|
||||
via `build_forwarded_payload`). The dial never delivers it to the
|
||||
protocol take-over — `spawn_dispatch(&self, connection: Connection)`
|
||||
takes no credentials, and `Connection` (a `Box<dyn BidiStreamSource>`)
|
||||
carries no `auth_token` field. The `auth_token` in `CallCredentials` is
|
||||
unused by the dial and dropped after `connect()` in the current code.
|
||||
|
||||
By moving `CallCredentials` (with `auth_token` in it) to `alknet-core`
|
||||
for the dial's benefit, ADR-089 §5 would drag a call-protocol concept
|
||||
into the shared-types crate *for the dial's benefit* — when the dial
|
||||
doesn't use it. The dial should consume a transport-level credential
|
||||
bundle, not a call-protocol one.
|
||||
|
||||
### The two identity models
|
||||
|
||||
Underneath the three transports, there are two identity-consumption
|
||||
models, both consuming the same two dimensions:
|
||||
|
||||
| Model | Transports | Consumes | What the transport does |
|
||||
|-------|-----------|----------|------------------------|
|
||||
| **rustls config** | QUIC (quinn), TCP+TLS (tokio-rustls) | `local_identity` → `TlsClientConfig` (client cert); `remote_identity` → verifier (`FingerprintPinVerifier` / `WebPkiServerVerifier`) | Builds `rustls::ClientConfig`, hands to transport connector |
|
||||
| **key-native** | iroh, SSH (future — `docs/research/references/ssh/russh/06-usage-patterns.md`) | `local_identity` → `Ed25519SecretKey` → transport's key type (`iroh::SecretKey`, russh key); `remote_identity` → fingerprint → transport's verifier (`NodeId` match, known_hosts) | Reads the key directly; transport handles identity internally |
|
||||
|
||||
The difference is *how* each model consumes the dimensions, not *what*
|
||||
they are. A unified credential bundle carrying just those two dimensions
|
||||
lets every dial extract what its transport's identity layer needs,
|
||||
without call-protocol coupling.
|
||||
|
||||
## Decision
|
||||
|
||||
### `ConnectionCredentials` — the dial's credential bundle
|
||||
|
||||
A new type in `alknet-core`, carrying the two transport-identity
|
||||
dimensions every dial consumes:
|
||||
|
||||
```rust
|
||||
/// Transport-level credentials for an outbound dial. Consumed by
|
||||
/// `AlknetClient`'s dial methods and (for the server side) by the
|
||||
/// assembly layer when building transports. Carries only the dimensions
|
||||
/// the transport's identity layer needs — the local identity (key/cert
|
||||
/// presented to the transport) and the expected remote identity
|
||||
/// (fingerprint, driving verifier selection per ADR-034).
|
||||
///
|
||||
/// This is NOT the call-protocol credential bundle. The call-protocol
|
||||
/// `auth_token` (hub-correlated bearer for browsers / `alknet/register`)
|
||||
/// is a per-request field on `call.requested` payloads, not a
|
||||
/// transport credential. It stays in the call-protocol layer.
|
||||
pub struct ConnectionCredentials {
|
||||
/// The local node's identity (RFC 7250 raw key or X.509), presented
|
||||
/// to the transport's identity layer. rustls dials → `TlsClientConfig`
|
||||
/// (client cert via `RawKeyClientCertResolver`); iroh/SSH dials →
|
||||
/// key directly (`iroh::SecretKey::from_bytes`, russh key).
|
||||
pub local_identity: Option<TlsIdentity>,
|
||||
|
||||
/// Expected identity of the remote node. `Some(fingerprint)` → pin
|
||||
/// (known peer); `None` → CA verification for X.509 remotes or
|
||||
/// fail-closed for Ed25519 raw-key remotes (ADR-034 §2/§3). `None`
|
||||
/// is the public-X.509-endpoint state, not a missing field.
|
||||
pub remote_identity: Option<RemoteIdentity>,
|
||||
}
|
||||
```
|
||||
|
||||
`RemoteIdentity` moves with `ConnectionCredentials` to `alknet-core`
|
||||
(both are transport-level types; the dial and the server-side transport
|
||||
construction both consume them).
|
||||
|
||||
### Unified dial signatures
|
||||
|
||||
All three dials take `&ConnectionCredentials`:
|
||||
|
||||
```rust
|
||||
impl AlknetClient {
|
||||
#[cfg(feature = "quinn")]
|
||||
pub async fn dial_quic(
|
||||
&self,
|
||||
addr: SocketAddr,
|
||||
server_name: &str,
|
||||
alpn: &[u8],
|
||||
creds: &ConnectionCredentials,
|
||||
) -> Result<Connection, ClientDialError>;
|
||||
|
||||
#[cfg(feature = "tcp")]
|
||||
pub async fn dial_tcp_tls(
|
||||
&self,
|
||||
host: &str,
|
||||
addr: SocketAddr,
|
||||
alpn: &[u8],
|
||||
creds: &ConnectionCredentials,
|
||||
) -> Result<Connection, ClientDialError>;
|
||||
|
||||
#[cfg(feature = "iroh")]
|
||||
pub async fn dial_iroh(
|
||||
&self,
|
||||
alpn: &[u8],
|
||||
creds: &ConnectionCredentials,
|
||||
) -> Result<Connection, ClientDialError>;
|
||||
}
|
||||
```
|
||||
|
||||
The `node_id: iroh::NodeId` parameter on `dial_iroh` is removed — it is
|
||||
derived from `creds.remote_identity.fingerprint` (`ed25519:<hex>` →
|
||||
`NodeId::from_bytes`), the same way the rustls dials derive their
|
||||
verifier from `remote_identity`. The consistency is now in both the rule
|
||||
(ADR-034) and the type.
|
||||
|
||||
Each dial extracts what its transport's identity layer needs:
|
||||
|
||||
- **rustls dials** (`dial_quic`, `dial_tcp_tls`): `creds.local_identity`
|
||||
→ `TlsClientConfig::new` (client cert); `creds.remote_identity` →
|
||||
`ClientVerifierContext` (verifier selection).
|
||||
- **iroh dial** (`dial_iroh`): `creds.local_identity` →
|
||||
`Ed25519SecretKey` → `iroh::SecretKey::from_bytes`;
|
||||
`creds.remote_identity.fingerprint` → `NodeId` (verifier).
|
||||
|
||||
### `CallCredentials` stays in `alknet-call`
|
||||
|
||||
`CallCredentials` remains the **call-protocol** credential bundle. Its
|
||||
`auth_token` field stays — the call protocol uses it (the `from_call`
|
||||
forwarding handler populates `auth_token` on outgoing `call.requested`
|
||||
payloads; the hub's `Dispatcher::resolve_identity` resolves it via
|
||||
`IdentityProvider::resolve_from_token`). The call protocol layer
|
||||
assembles `CallCredentials` from `ConnectionCredentials` (the
|
||||
transport-level dimensions) + the call-protocol `auth_token`, or the
|
||||
caller provides the `auth_token` per-request via `call_with_payload`.
|
||||
|
||||
`CallCredentials` does **not** move to `alknet-core`. ADR-089 §5's move
|
||||
of `CallCredentials` to core is superseded by this ADR: only
|
||||
`ConnectionCredentials` and `RemoteIdentity` move to core. The call
|
||||
protocol's own credential type stays in the call crate.
|
||||
|
||||
### `TlsClientConfig::new` input framing
|
||||
|
||||
`TlsClientConfig::new` (ADR-087) takes a `ClientVerifierContext` derived
|
||||
from the credential bundle's `remote_identity`. The rustls dials extract
|
||||
`creds.local_identity` and `creds.remote_identity` from
|
||||
`ConnectionCredentials` and build `ClientVerifierContext` from the latter
|
||||
— the same extraction ADR-087 described, just from
|
||||
`ConnectionCredentials` instead of `CallCredentials`. The `auth_token`
|
||||
dimension is simply not present in `ConnectionCredentials`, so the
|
||||
"stripped at the TLS boundary" framing (ADR-089's claim that the token
|
||||
"travels with the Connection") is no longer needed — the token was never
|
||||
in the dial's credential bundle to strip.
|
||||
|
||||
### Future `dial_ssh` validates the shape
|
||||
|
||||
An SSH dial (`docs/research/references/ssh/russh/06-usage-patterns.md`)
|
||||
consumes the same two dimensions:
|
||||
|
||||
- `check_server_key(&mut self, key: &ssh_key::PublicKey)` — the verifier
|
||||
(fingerprint pin against known_hosts = `remote_identity`)
|
||||
- `authenticate_publickey("user", PrivateKeyWithHashAlg::new(...))` —
|
||||
local identity (the Ed25519 key = `local_identity`)
|
||||
- `channel_open_session()` → `Connection::from_bidi` (ADR-065)
|
||||
|
||||
`dial_ssh(addr, alpn, creds: &ConnectionCredentials)` fits the same
|
||||
signature. The SSH host-key verification is fingerprint-pinning
|
||||
(known_hosts), which is what `remote_identity` carries. The local SSH
|
||||
key is the same Ed25519 key iroh and raw-key quinn use. The pattern is
|
||||
general — `ConnectionCredentials` covers it without call-protocol
|
||||
coupling. SSH itself is unspecced (not yet specced — comes after
|
||||
channels, tunnels, TTY rework), but the russh usage patterns confirm
|
||||
the credential dimensions.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- **The dial is fully decoupled from the call protocol.**
|
||||
`ConnectionCredentials` carries only transport-identity dimensions;
|
||||
`alknet-client` has no call-protocol coupling in its credential type.
|
||||
The `auth_token` (a call-protocol / hub-layer concept) stays in
|
||||
`alknet-call` where it belongs.
|
||||
- **All three dial signatures are unified.** A caller no longer needs to
|
||||
know that iroh takes a bare key while quinn/tcp take a credential
|
||||
bundle — all take `&ConnectionCredentials`. The `node_id` parameter on
|
||||
`dial_iroh` is derived from `remote_identity`, the same extraction
|
||||
pattern the rustls dials use for the verifier.
|
||||
- **The `auth_token` spec inaccuracy is fixed.** ADR-089 claimed the
|
||||
`auth_token` "travels with the `Connection` into the protocol
|
||||
take-over, where it is sent as the first call-protocol frame." This
|
||||
was aspirational — `Connection` carries no `auth_token`, and
|
||||
`spawn_dispatch` takes no credentials. With `auth_token` out of the
|
||||
dial's credential bundle entirely, the claim is no longer needed. The
|
||||
token is a per-request field on `call.requested` payloads, set by the
|
||||
caller or the `from_call` forwarding handler.
|
||||
- **`dial_ssh` fits the same shape when it arrives.** The credential
|
||||
dimensions SSH needs (local key + expected host key) are exactly what
|
||||
`ConnectionCredentials` carries. No future ADR needed for the SSH dial
|
||||
signature.
|
||||
- **`CallCredentials` stays in `alknet-call` — its home.** The call
|
||||
protocol's own credential type is not dragged into core for the dial's
|
||||
benefit. Core gets `ConnectionCredentials` (transport-level); the call
|
||||
crate keeps `CallCredentials` (protocol-level).
|
||||
|
||||
**Negative:**
|
||||
|
||||
- **`CallCredentials` loses two fields.** `tls_identity` and
|
||||
`remote_identity` move to `ConnectionCredentials` in core;
|
||||
`CallCredentials` becomes `{auth_token: Option<AuthToken>}` (or is
|
||||
restructured — the call protocol may assemble it from
|
||||
`ConnectionCredentials` + `auth_token` at the take-over site, or the
|
||||
caller provides `auth_token` per-request). The exact restructure is a
|
||||
two-way-door implementation detail; the one-way decision is that the
|
||||
transport dimensions leave `CallCredentials`.
|
||||
- **The assembly layer assembles two credential bundles, not one.** Where
|
||||
ADR-089 had the assembly layer build one `CallCredentials`, it now
|
||||
builds `ConnectionCredentials` (for the dial) and, separately,
|
||||
provides the `auth_token` to the call-protocol layer (for the
|
||||
per-request payload). This is the correct layering — the dial and the
|
||||
protocol consume different dimensions — but it is one more type at the
|
||||
assembly site.
|
||||
- **ADR-089 §5's "CallCredentials moves to core" is superseded.** The
|
||||
move target changes from `CallCredentials` to `ConnectionCredentials`
|
||||
+ `RemoteIdentity`. `CallCredentials` stays in `alknet-call`. This
|
||||
affects the extraction plan's Phase 0 (the additive credentials move).
|
||||
|
||||
## Door type
|
||||
|
||||
**One-way.** The dial signatures (`dial_quic` / `dial_tcp_tls` /
|
||||
`dial_iroh` all taking `&ConnectionCredentials`) are the public API
|
||||
surface of `alknet-client`. The credential-type decoupling
|
||||
(`ConnectionCredentials` in core, `CallCredentials` in call) determines
|
||||
the dep graph (`alknet-client` depends on `alknet-core` for
|
||||
`ConnectionCredentials`, not on `alknet-call` for `CallCredentials`).
|
||||
Reversing would mean re-coupling the dial to the call protocol's
|
||||
credential type and re-asymmetrizing the iroh dial. The crate is
|
||||
greenfield (Phase 3 of the extraction plan), so the door is still open
|
||||
now — this ADR records the decision before implementation.
|
||||
|
||||
## References
|
||||
|
||||
- ADR-089 — `AlknetClient` native dial seam (§3 dial signatures amended
|
||||
— all take `&ConnectionCredentials`; §5 move amended —
|
||||
`ConnectionCredentials`/`RemoteIdentity` move to core, not
|
||||
`CallCredentials`)
|
||||
- ADR-087 — `TlsClientConfig` not blocked on dial (input framing
|
||||
amended — `ClientVerifierContext` derived from
|
||||
`ConnectionCredentials.remote_identity`, not `CallCredentials`)
|
||||
- ADR-034 — client-side verifier selection (the rule
|
||||
`ConnectionCredentials.remote_identity` drives — unchanged)
|
||||
- ADR-017 §7 — the three credential dimensions (the source of
|
||||
`CallCredentials`'s three fields; this ADR splits the transport
|
||||
dimensions from the protocol dimension)
|
||||
- `crates/alknet-call/src/protocol/dispatch.rs` —
|
||||
`Dispatcher::resolve_identity` reads `payload.get("auth_token")`
|
||||
(per-request, not connection-level)
|
||||
- `crates/alknet-call/src/client/from_call.rs` —
|
||||
`build_forwarded_payload` sets `auth_token` on outgoing payloads
|
||||
(per-request, the hub's own token)
|
||||
- `docs/research/references/ssh/russh/06-usage-patterns.md` — the SSH
|
||||
client usage patterns (check_server_key + authenticate_publickey)
|
||||
validating the `ConnectionCredentials` shape for a future `dial_ssh`
|
||||
@@ -16,8 +16,8 @@ with what intermediate states.
|
||||
|
||||
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
|
||||
(breakage confined to a single phase), then fix the http residual. Seven
|
||||
phases (0-6), each leaving the workspace compilable. The heaviest single file
|
||||
(`endpoint.rs`, 1606 lines) is not a monolith — it's three concerns
|
||||
welded together, each going to a different destination.
|
||||
|
||||
@@ -87,27 +87,35 @@ already removed per ADR-065; tests use `from_stream` with
|
||||
|
||||
## The seven phases
|
||||
|
||||
### Phase 0: Move `CallCredentials`/`RemoteIdentity` to `alknet-core` (additive, no breakage)
|
||||
### Phase 0: Move `ConnectionCredentials`/`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.
|
||||
**What:** Add `ConnectionCredentials` + `RemoteIdentity` to a new
|
||||
`crates/alknet-core/src/credentials.rs` (~40 lines).
|
||||
`ConnectionCredentials` is the transport-level credential bundle
|
||||
(ADR-091) — it carries `local_identity` + `remote_identity` (the two
|
||||
dimensions the dial consumes). `CallCredentials` stays in
|
||||
`alknet-call` (it is the call-protocol bundle; its `auth_token` field
|
||||
is a per-request call-protocol concept, not a transport credential).
|
||||
Update `alknet-core/src/lib.rs` to `pub mod credentials` + re-export.
|
||||
Update `alknet-call` to import `ConnectionCredentials` + `RemoteIdentity`
|
||||
from core and re-export them; `CallCredentials` retains `auth_token` and
|
||||
references the core types for the transport dimensions. 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.
|
||||
clean from the start. `ConnectionCredentials` (not `CallCredentials`)
|
||||
is what moves — the dial consumes transport-level dimensions, not
|
||||
call-protocol dimensions (ADR-091).
|
||||
|
||||
**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).
|
||||
`alknet-call` imports the types from core; its own code + tests
|
||||
continue to work via the re-export.
|
||||
|
||||
**Done when:** `cargo test` passes, `CallCredentials` +
|
||||
`RemoteIdentity` are defined in `alknet-core`, `alknet-call` re-exports
|
||||
them.
|
||||
**Done when:** `cargo test` passes, `ConnectionCredentials` +
|
||||
`RemoteIdentity` are defined in `alknet-core`, `alknet-call` imports
|
||||
them from core, `CallCredentials` stays in `alknet-call`.
|
||||
|
||||
### Phase 1: Create `alknet-tls` (greenfield, additive)
|
||||
|
||||
@@ -126,6 +134,12 @@ helpers from `alknet-call/client/call_client.rs` (lines 189-320).
|
||||
**Deps:** `alknet-core` (TlsIdentity, Ed25519SecretKey, fingerprint),
|
||||
`rustls`, `rustls-pemfile`, `rustls-native-certs`, `webpki-roots`,
|
||||
`rcgen`, `tokio`, optional `quinn`/`tokio-rustls`/`rustls-acme`.
|
||||
`rustls-native-certs` and `webpki-roots` are **always-present (not
|
||||
feature-gated)** — the unknown-X.509-remote CA-verification path in
|
||||
`TlsClientConfig::new` is transport-agnostic; the `webpki-roots`
|
||||
fallback merges built-in roots when the platform store is empty so
|
||||
`NoRootAnchors` is unreachable in practice (ADR-088 §5). Do not gate
|
||||
them under `quinn`/`tcp`.
|
||||
|
||||
**Compilable state:** `alknet-tls` builds and tests standalone. Core and
|
||||
call are unchanged — the old code still exists (duplicated). No
|
||||
@@ -209,15 +223,16 @@ self-contained, no other crate changed.
|
||||
|
||||
**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
|
||||
all unified on `&ConnectionCredentials` (ADR-091), consuming
|
||||
`TlsClientConfig` from `alknet-tls` + `ConnectionCredentials` 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`.
|
||||
**Deps:** `alknet-core` (Connection, ConnectionCredentials,
|
||||
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"]`,
|
||||
@@ -238,8 +253,9 @@ self-contained, no other crate changed.
|
||||
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`).
|
||||
`from_iroh` in `types.rs`). Add `ConnectionCredentials` +
|
||||
`RemoteIdentity` to core (from `alknet-call` — done in Phase 0;
|
||||
`CallCredentials` stays in `alknet-call` per ADR-091).
|
||||
|
||||
**The `lib.rs` change:**
|
||||
```rust
|
||||
@@ -252,7 +268,7 @@ pub mod config;
|
||||
// After:
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod credentials; // ← new (CallCredentials, RemoteIdentity)
|
||||
pub mod credentials; // ← new (ConnectionCredentials, RemoteIdentity)
|
||||
// ... rest unchanged
|
||||
```
|
||||
|
||||
@@ -284,19 +300,24 @@ 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`.
|
||||
`call_client.rs`. The transport dimensions (`ConnectionCredentials`/
|
||||
`RemoteIdentity`) already moved to core in Phase 0; here we remove the
|
||||
old definitions from `call_client.rs` and update imports.
|
||||
`CallCredentials` stays in `alknet-call` (retaining `auth_token`,
|
||||
referencing the core types — ADR-091). 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)
|
||||
- `RemoteIdentity`/`ConnectionCredentials` — removed from
|
||||
`call_client.rs` (now in `alknet-core`, re-imported from there).
|
||||
`CallCredentials` stays in `alknet-call` (retaining `auth_token` —
|
||||
ADR-091).
|
||||
- `ClientError` — removed
|
||||
- `connect` + all `build_*`/`select_*`/`load_*`/`Ed25519SigningKey`/
|
||||
`RawKeyClientCertResolver`/`NoClientCertResolver`/
|
||||
@@ -304,8 +325,11 @@ mocks) or `AlknetClient::dial_quic` + `spawn_dispatch`.
|
||||
|
||||
**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.
|
||||
is **removed** (it only gated `connect` + the TLS helpers, both
|
||||
removed — keeping it as a no-op would mislead a user enabling
|
||||
`quinn` on `alknet-call` expecting QUIC support; removing it surfaces
|
||||
any stray `#[cfg(feature = "quinn")]` the prune missed). `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
|
||||
@@ -367,11 +391,11 @@ wrapper is gone.
|
||||
|
||||
| After phase | State |
|
||||
|-------------|-------|
|
||||
| 0 (credentials) | `CallCredentials`/`RemoteIdentity` in core; call re-exports; no breakage |
|
||||
| 0 (credentials) | `ConnectionCredentials`/`RemoteIdentity` in core; call imports from core; `CallCredentials` stays in call; 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 |
|
||||
| 4 (core prune) | core is lightweight; `endpoint.rs` gone; `ConnectionCredentials` 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 |
|
||||
|
||||
@@ -386,9 +410,9 @@ tests — the TLS tests moved in Phase 1, the protocol tests use
|
||||
|
||||
The ordering is **deps before dependents, additive before subtractive**:
|
||||
|
||||
- **Phase 0** (`CallCredentials` to core) first because it's
|
||||
- **Phase 0** (`ConnectionCredentials` to core) first because it's
|
||||
independent, additive, and makes `alknet-client` (Phase 3) never
|
||||
depend on `alknet-call`. ~50 lines moved, zero breakage.
|
||||
depend on `alknet-call`. ~40 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
|
||||
@@ -399,8 +423,8 @@ The ordering is **deps before dependents, additive before subtractive**:
|
||||
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).
|
||||
(`TlsClientConfig`) + `alknet-core` (`ConnectionCredentials` — 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.
|
||||
@@ -409,34 +433,37 @@ The ordering is **deps before dependents, additive before subtractive**:
|
||||
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)
|
||||
### `ConnectionCredentials`/`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.
|
||||
**Decision:** Move `ConnectionCredentials`/`RemoteIdentity` to
|
||||
`alknet-core` as a standalone additive step *before any new crate is
|
||||
created* (ADR-091). It's independent of everything else, purely
|
||||
additive (core gains two small types, nothing breaks), and
|
||||
`alknet-call` imports 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
|
||||
`ConnectionCredentials` (not `CallCredentials`) is what moves — it is
|
||||
the transport-level credential bundle (`local_identity` +
|
||||
`remote_identity`), carrying only the dimensions the dial consumes.
|
||||
`CallCredentials` stays in `alknet-call` because its `auth_token` field
|
||||
is a call-protocol / hub-layer concept (bearer-token identity
|
||||
correlation for browsers and `alknet/register`), not a transport
|
||||
credential. See ADR-091 for the full rationale.
|
||||
|
||||
The move is ~40 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.
|
||||
auth module from growing). `alknet-call`'s `client/mod.rs` imports
|
||||
`ConnectionCredentials` + `RemoteIdentity` from core and re-exports
|
||||
them; `CallCredentials` stays defined in `alknet-call` (retaining
|
||||
`auth_token`, referencing the core types for the transport dimensions).
|
||||
Test changes are minimal — the Category A tests that reference
|
||||
`CallCredentials` directly may need import updates depending on how
|
||||
`CallCredentials` is restructured.
|
||||
|
||||
### Phase 5 test audit — `call_client.rs` (16 tests)
|
||||
|
||||
@@ -460,13 +487,17 @@ transport-agnostic. These survive the prune unchanged.
|
||||
| `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):**
|
||||
**Category B — TLS/verifier tests, move to `alknet-tls` (10 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).
|
||||
to test `TlsClientConfig::new` instead of the free functions). The
|
||||
two `build_quinn_client_config` tests test the full config build
|
||||
(verifier + client-auth + provider wired together) and are adapted
|
||||
to test `TlsClientConfig::new` + `for_quinn()` instead of the free
|
||||
function.
|
||||
|
||||
| Test | Line | What it tests | Move target |
|
||||
|------|------|---------------|-------------|
|
||||
@@ -481,11 +512,6 @@ to test `TlsClientConfig::new` instead of the free functions).
|
||||
| `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
|
||||
@@ -507,11 +533,13 @@ The one reference to `connect()` is in a doc comment (line 76:
|
||||
**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.
|
||||
of `call_client.rs` is mechanical: delete `ClientError`, `connect`,
|
||||
and all the TLS helpers (`build_*`, `select_*`, `load_*`,
|
||||
`Ed25519SigningKey`, `RawKeyClientCertResolver`, `NoClientCertResolver`,
|
||||
`FingerprintPinVerifier`); keep `CallClient` + `new` + `spawn_dispatch`
|
||||
unchanged; update imports. The test suite keeps the Category A tests,
|
||||
removes the Category B tests (moved in Phase 1), and updates 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`
|
||||
|
||||
Reference in new issue
Block a user