docs: remove CallCredentials — dead field, dead from_call path, auth_token is per-request payload

ADR-091 amended 2026-07-17: CallCredentials removed (not retained in
alknet-call). Trace showed CallCredentials.auth_token had no reader
(connect() read only tls_identity + remote_identity; spawn_dispatch
takes no credentials; from_call's credentials_auth_token was a
different type, always None, never connected). auth_token is a
per-request payload field — browsers send it in the WS payload; the
HTTP gateway resolves bearer → Identity at its boundary.

from_call's credentials_auth_token dead path removed in the same pass
(OpSummary field, handler params, build_forwarded_payload param, and
the two tests asserting the never-exercised Some path).

ADR-089 §5 further amended, ADR-080 noted, all spec READMEs and
overview updated. Migration plan (findings.md) corrected: Phase 5
prune now includes CallCredentials removal + from_call dead-path
removal; test audit corrected (4 unchanged + 2 move to core, not 6
unchanged); integration-test split documented; all 'or' hedges
resolved.
This commit is contained in:
deepseek-v4-pro committed 2026-07-17 08:54:04 +00:00
1 parent ec46fc6d59
commit e91d943857
13 files changed
+422 -200

No files matched your search

+3 -3
View File
@@ -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 | | [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/) | | [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; `ConnectionCredentials`/`RemoteIdentity` moved here from `alknet-call` per ADR-091; `CallCredentials` stays in `alknet-call`) | | [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` removed per ADR-091 Am. 2026-07-17) |
| [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/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/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 | | [crates/core/auth.md](crates/core/auth.md) | draft | AuthContext (incl. `anonymous` constructor), Identity, IdentityProvider, AuthToken, resolution flow |
@@ -345,9 +345,9 @@ adapter location map is now consistent: all HTTP-backed adapters
| [086](decisions/086-endpoint-types-and-entry-points.md) | Endpoint Types and Entry Points | 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; input framing amended by ADR-091 — `ClientVerifierContext` derived from `ConnectionCredentials`, not `CallCredentials`) | | [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) | | [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; §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) | | [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` removed per ADR-091 Am. 2026-07-17; `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) | | [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) | | [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` is a per-request payload field; `CallCredentials` removed per Am. 2026-07-17) |
## Open Questions ## Open Questions
@@ -130,12 +130,14 @@ impl CallClient {
/// and to let `alknet-call` shed its TLS/transport deps entirely. /// and to let `alknet-call` shed its TLS/transport deps entirely.
/// Callers compose `AlknetClient::dial_quic(...).await?` + /// Callers compose `AlknetClient::dial_quic(...).await?` +
/// `CallClient::new(...).spawn_dispatch(conn)`. `ClientError` is /// `CallClient::new(...).spawn_dispatch(conn)`. `ClientError` is
/// removed (it was produced only by `connect`). /// removed (it was produced only by `connect`). `CallCredentials`
/// is removed (its `auth_token` field had no reader; `auth_token`
/// is a per-request payload field — ADR-091, amended 2026-07-17).
#[cfg(feature = "quinn")] #[cfg(feature = "quinn")]
pub async fn connect( pub async fn connect(
&self, &self,
addr: SocketAddr, addr: SocketAddr,
credentials: CallCredentials, credentials: CallCredentials, // REMOVED — CallCredentials is removed
) -> Result<CallConnection, ClientError>; ) -> Result<CallConnection, ClientError>;
} }
``` ```
@@ -246,7 +248,8 @@ attribution, filtered by the calling peer's authorization). See
### Credential sources for connections ### Credential sources for connections
The credential dimensions are split across two layers (ADR-091): The credential dimensions are split across two layers (ADR-091, amended
2026-07-17):
- **`ConnectionCredentials`** (in `alknet-core`, moved from - **`ConnectionCredentials`** (in `alknet-core`, moved from
`alknet-call` per ADR-091) — the **transport-level** credential `alknet-call` per ADR-091) — the **transport-level** credential
@@ -254,16 +257,21 @@ The credential dimensions are split across two layers (ADR-091):
transport-identity dimensions: `local_identity` (the local node's transport-identity dimensions: `local_identity` (the local node's
`TlsIdentity`) and `remote_identity` (the expected fingerprint). The `TlsIdentity`) and `remote_identity` (the expected fingerprint). The
dial does not depend on the call protocol for this type. dial does not depend on the call protocol for this type.
- **`CallCredentials`** (stays in `alknet-call`) — the - **`auth_token`** — a **per-request payload field**, not a
**call-protocol** credential bundle. The `auth_token` dimension call-protocol credential bundle. `Dispatcher::resolve_identity`
(ADR-017 §7) is a call-protocol / hub-layer concept: a bearer token reads `payload.get("auth_token")` on each `call.requested` payload.
correlated to an identity via `IdentityProvider::resolve_from_token`, Browsers send it directly in the WebSocket call payload; the HTTP
used for browsers (no raw-key support) and `alknet/register` (no gateway resolves the bearer token to an `Identity` at its boundary
prior peer relationship). It is a per-request field on (the call layer sees the identity, not the token). `CallCredentials`
`call.requested` payloads, not a transport credential. is **removed** (its `auth_token` field had no reader — `connect()`
read only `tls_identity` + `remote_identity`; `spawn_dispatch` takes
no credentials; the `from_call` forwarding path's `auth_token` source
was `OpSummary.credentials_auth_token: Option<String>`, always
`None`, never connected to `CallCredentials.auth_token`). See
ADR-091 (amended 2026-07-17) for the full trace.
Credentials come from `Capabilities` (ADR-014), never from environment Credentials come from `Capabilities` (ADR-014), never from environment
variables. The three credential dimensions (ADR-017 §7): variables. The transport-identity dimensions (ADR-017 §7):
```rust ```rust
// Transport-level (alknet-core, consumed by the dial — ADR-091) // Transport-level (alknet-core, consumed by the dial — ADR-091)
@@ -272,16 +280,15 @@ pub struct ConnectionCredentials {
pub remote_identity: Option<RemoteIdentity>, // expected fingerprint (None = CA path / fail-closed) pub remote_identity: Option<RemoteIdentity>, // expected fingerprint (None = CA path / fail-closed)
} }
// Call-protocol-level (alknet-call — the auth_token stays here) // auth_token is a per-request payload field, not a credential struct.
// The auth_token is set per-request on call.requested payloads, not // Browsers send it in the WebSocket call payload; the HTTP gateway
// carried by the dial. // resolves bearer → Identity at its boundary.
// Dispatcher::resolve_identity reads payload.get("auth_token").
``` ```
`CallCredentials` retains `auth_token` (and may assemble from There is no call-protocol credential bundle. `CallCredentials` is
`ConnectionCredentials` + `auth_token` at the take-over site, or the removed. The transport dimensions (`local_identity`, `remote_identity`)
caller provides `auth_token` per-request via `call_with_payload`). The moved to `ConnectionCredentials` in `alknet-core` per ADR-091.
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 /// Expected identity of the remote node (ADR-017 §7, extended by
/// ADR-034 §2). Carries a fingerprint string the assembly layer /// ADR-034 §2). Carries a fingerprint string the assembly layer
@@ -710,8 +717,10 @@ Based on the gap analysis and the downstream unblock chain:
(mis)placed in `alknet-call` as a schema-only placeholder; ADR-066 (mis)placed in `alknet-call` as a schema-only placeholder; ADR-066
moved it to `alknet-http` as a real HTTP-backed adapter. See Adapter moved it to `alknet-http` as a real HTTP-backed adapter. See Adapter
Location Map. Location Map.
- **No secret material on the wire.** `CallCredentials` carries vault-derived - **No secret material on the wire.** `ConnectionCredentials` carries vault-derived
material for the *outbound* connection (TLS identity, auth token); the material for the *outbound* connection (TLS identity); `auth_token` is a
per-request payload field (browsers send it in the WebSocket call payload;
the HTTP gateway resolves bearer → `Identity` at its boundary). The
call protocol's wire format carries no private keys, API keys, or decrypted call protocol's wire format carries no private keys, API keys, or decrypted
credentials (ADR-014). The no-env-vars invariant (above) is the dispatch-side credentials (ADR-014). The no-env-vars invariant (above) is the dispatch-side
corollary. corollary.
@@ -748,7 +757,7 @@ Based on the gap analysis and the downstream unblock chain:
`ServerCertVerifier` uses CA verification (`WebPkiServerVerifier`) for `ServerCertVerifier` uses CA verification (`WebPkiServerVerifier`) for
such remotes; known peers (hub with `PeerEntry`) use fingerprint such remotes; known peers (hub with `PeerEntry`) use fingerprint
pinning. See [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md). pinning. See [ADR-034](../../decisions/034-outgoing-only-x509-and-three-peer-roles.md).
- **`CallCredentials.remote_identity: None` is load-bearing.** `None` - **`ConnectionCredentials.remote_identity: None` is load-bearing.** `None`
means "no `PeerEntry` for this remote → use CA verification (X.509) means "no `PeerEntry` for this remote → use CA verification (X.509)
or fail closed (Ed25519 raw key)" per the ADR-034 §3 verifier rule. or fail closed (Ed25519 raw key)" per the ADR-034 §3 verifier rule.
The implementation must not default `remote_identity` to a placeholder The implementation must not default `remote_identity` to a placeholder
@@ -61,9 +61,12 @@ impl ChannelClient {
/// not delegated, to avoid `alknet-channels-call` depending on /// not delegated, to avoid `alknet-channels-call` depending on
/// `alknet-client`. Callers compose `AlknetClient::dial_quic` + /// `alknet-client`. Callers compose `AlknetClient::dial_quic` +
/// `from_connection`. See "Relationship to `AlknetClient`" below. /// `from_connection`. See "Relationship to `AlknetClient`" below.
/// The `CallCredentials` parameter is moot — `CallCredentials` is
/// removed per ADR-091 (amended 2026-07-17); the dial consumes
/// `ConnectionCredentials` from `alknet-core`.
pub async fn connect_quic( pub async fn connect_quic(
addr: SocketAddr, addr: SocketAddr,
credentials: CallCredentials, credentials: CallCredentials, // REMOVED — CallCredentials is removed
) -> Result<Self, ChannelError>; ) -> Result<Self, ChannelError>;
/// Open a data channel with the given ALPN and params. Sends /// Open a data channel with the given ALPN and params. Sends
+2 -2
View File
@@ -346,7 +346,7 @@ and passes them to each dial.
The call-protocol `auth_token` (a hub-correlated bearer for browsers The call-protocol `auth_token` (a hub-correlated bearer for browsers
and `alknet/register` — ADR-017 §7) is a per-request field on and `alknet/register` — ADR-017 §7) is a per-request field on
`call.requested` payloads, not a transport credential. It stays in the `call.requested` payloads, not a transport credential. It stays in the
call-protocol layer (`CallCredentials` in `alknet-call`); the dial does call-protocol layer (`auth_token` is a per-request payload field); the dial does
not carry it. `Dispatcher::resolve_identity` resolves it via not carry it. `Dispatcher::resolve_identity` resolves it via
`IdentityProvider::resolve_from_token` at dispatch time; the `from_call` `IdentityProvider::resolve_from_token` at dispatch time; the `from_call`
forwarding handler sets it via `build_forwarded_payload`. This keeps forwarding handler sets it via `build_forwarded_payload`. This keeps
@@ -675,7 +675,7 @@ All design decisions are documented as ADRs in
|-----|----------|---------| |-----|----------|---------|
| [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`) | | [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 | | [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` | | [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` is a per-request payload field; `CallCredentials` removed per Am. 2026-07-17 |
## Open Questions ## Open Questions
+1 -1
View File
@@ -18,7 +18,7 @@ shared constructors (gated on core's `quinn` / `iroh` features).
`ConnectionCredentials` and `RemoteIdentity` move to `alknet-core` `ConnectionCredentials` and `RemoteIdentity` move to `alknet-core`
(from `alknet-call`, per ADR-091) — the transport-level credential (from `alknet-call`, per ADR-091) — the transport-level credential
bundle consumed by the dial (`alknet-client`) and by server-side bundle consumed by the dial (`alknet-client`) and by server-side
transport construction. `CallCredentials` (the call-protocol credential transport construction. `ConnectionCredentials` (the transport-level credential
bundle, including `auth_token`) stays in `alknet-call` — the dial does bundle, including `auth_token`) stays in `alknet-call` — the dial does
not carry call-protocol dimensions. not carry call-protocol dimensions.
+2 -2
View File
@@ -312,7 +312,7 @@ impl Hub {
pub async fn connect_quic_worker( pub async fn connect_quic_worker(
&self, &self,
addr: SocketAddr, addr: SocketAddr,
credentials: CallCredentials, credentials: ConnectionCredentials,
config: FromCallConfig, config: FromCallConfig,
) -> Result<(PeerId, ChannelClient), HubError>; ) -> Result<(PeerId, ChannelClient), HubError>;
} }
@@ -542,7 +542,7 @@ impl Hub {
``` ```
The supervision loop takes a `dial` closure rather than a `SocketAddr` The supervision loop takes a `dial` closure rather than a `SocketAddr`
+ `CallCredentials` pair. This keeps the loop transport-agnostic — + `ConnectionCredentials` pair. This keeps the loop transport-agnostic —
the caller decides the transport by what the closure does. The the caller decides the transport by what the closure does. The
backoff and re-discovery logic is the same regardless of transport. backoff and re-discovery logic is the same regardless of transport.
+1 -1
View File
@@ -696,7 +696,7 @@ alknet-core (loses TLS setup code + endpoint)
alknet-call (pure protocol crate — no TLS/transport deps per ADR-089 §5) alknet-call (pure protocol crate — no TLS/transport deps per ADR-089 §5)
└── alknet-core (ProtocolHandler, Connection, types; ConnectionCredentials/ └── alknet-core (ProtocolHandler, Connection, types; ConnectionCredentials/
RemoteIdentity moved to core per ADR-091; CallCredentials stays in RemoteIdentity moved to core per ADR-091; CallCredentials removed per ADR-091 Am. 2026-07-17
alknet-call) alknet-call)
alknet-hub (multi-transport endpoint) alknet-hub (multi-transport endpoint)
@@ -122,9 +122,16 @@ impl ChannelClient {
/// ADR-034 verifier selection), then calls `from_connection`. /// ADR-034 verifier selection), then calls `from_connection`.
/// Additive and two-way-door — `connect_tcp_tls`, /// Additive and two-way-door — `connect_tcp_tls`,
/// `connect_webtransport`, etc. join it as transports are added. /// `connect_webtransport`, etc. join it as transports are added.
///
/// **REMOVED per ADR-089 §5.** The dial is extracted into
/// `AlknetClient`; `connect_quic` is deleted, not delegated.
/// Callers compose `AlknetClient::dial_quic` + `from_connection`.
/// The `CallCredentials` parameter is moot — `CallCredentials` is
/// removed per ADR-091 (amended 2026-07-17); the dial consumes
/// `ConnectionCredentials` from `alknet-core`.
pub async fn connect_quic( pub async fn connect_quic(
addr: SocketAddr, addr: SocketAddr,
credentials: CallCredentials, credentials: CallCredentials, // REMOVED — CallCredentials is removed
) -> Result<Self, ChannelError>; ) -> Result<Self, ChannelError>;
/// Open a data channel with the given ALPN and params. Sends /// Open a data channel with the given ALPN and params. Sends
@@ -9,7 +9,10 @@ unify on `&ConnectionCredentials`; `dial_iroh`'s `node_id` parameter is
derived from `remote_identity`; the `auth_token` stays in the derived from `remote_identity`; the `auth_token` stays in the
call-protocol layer, not the dial; `CallCredentials` stays in call-protocol layer, not the dial; `CallCredentials` stays in
`alknet-call`, only `ConnectionCredentials`/`RemoteIdentity` move to `alknet-call`, only `ConnectionCredentials`/`RemoteIdentity` move to
`alknet-core`) `alknet-core`; §5 further amended 2026-07-17 by ADR-091 —
`CallCredentials` is removed entirely (its `auth_token` field had no
reader); `auth_token` is a per-request payload field; the `from_call`
`credentials_auth_token` dead path is removed)
## Context ## Context
@@ -307,6 +310,25 @@ the dep graph, and the dep graph requires it.
> `CallCredentials`. All three dial signatures unify on > `CallCredentials`. All three dial signatures unify on
> `&ConnectionCredentials`. See > `&ConnectionCredentials`. See
> [ADR-091](091-connectioncredentials-decouple-dial-from-call.md). > [ADR-091](091-connectioncredentials-decouple-dial-from-call.md).
>
> **Further amendment 2026-07-17 (ADR-091):** `CallCredentials` is
> **removed**, not retained in `alknet-call`. The "stays in
> `alknet-call`" framing above is itself superseded: a trace of the code
> showed `CallCredentials.auth_token` had no reader (`connect()` read
> only `tls_identity` + `remote_identity`; `spawn_dispatch` takes no
> credentials; the `from_call` forwarding path's `auth_token` source was
> `OpSummary.credentials_auth_token: Option<String>`, always `None`,
> never connected to `CallCredentials.auth_token`). The original ADR-091
> rationale ("the `from_call` forwarding handler populates `auth_token`
> from `CallCredentials`") cited a code path that does not exist.
> `auth_token` is a per-request payload field — browsers send it in the
> WebSocket call payload; the HTTP gateway resolves bearer → `Identity`
> at its boundary (the call layer sees the identity, not the token);
> `Dispatcher::resolve_identity` reads `payload.get("auth_token")`.
> There is no call-protocol credential bundle. The `from_call`
> `credentials_auth_token` dead path is removed in the same pass. See
> [ADR-091](091-connectioncredentials-decouple-dial-from-call.md) §"`CallCredentials`
> is removed."
**Consequence: `FingerprintPinVerifier` moves to `alknet-tls`.** With **Consequence: `FingerprintPinVerifier` moves to `alknet-tls`.** With
`connect` removed and the verifier-selection logic centralized in `connect` removed and the verifier-selection logic centralized in
@@ -391,11 +413,14 @@ several possible native clients sharing the same wire protocols.
- **`CallClient::spawn_dispatch` / `ChannelClient::from_connection`** - **`CallClient::spawn_dispatch` / `ChannelClient::from_connection`**
— the take-over APIs are unchanged. They consume the `Connection` — the take-over APIs are unchanged. They consume the `Connection`
the dial produces; they do not know `AlknetClient` produced it. the dial produces; they do not know `AlknetClient` produced it.
- **`CallCredentials` / `RemoteIdentity`** — **moved to `alknet-core`** - **`RemoteIdentity`** — **moved to `alknet-core`** (see §5, as amended
(see §5). The shape is unchanged; the location changes from by ADR-091). The location changes from `alknet-call` to `alknet-core`
`alknet-call` to `alknet-core` so the dial does not depend on the so the dial does not depend on the call protocol. The call and
call protocol. Both the call and channels clients consume them from channels clients consume it from core. (`CallCredentials` is removed
core. per ADR-091's 2026-07-17 amendment — it is not moved, it is deleted;
its `auth_token` field had no reader. `ConnectionCredentials` is the
new transport-level credential bundle in core, carrying
`local_identity` + `remote_identity`.)
- **The channels substrate (ADR-071)** — unchanged. The dial produces a - **The channels substrate (ADR-071)** — unchanged. The dial produces a
`Connection`; the channels protocol runs on it. `Connection`; the channels protocol runs on it.
- **ADR-086 (endpoint types / entry points)** — the endpoint-type model - **ADR-086 (endpoint types / entry points)** — the endpoint-type model
@@ -450,14 +475,17 @@ several possible native clients sharing the same wire protocols.
**Negative:** **Negative:**
- **Breaking change: `CallClient::connect` / `ChannelClient::connect_quic` - **Breaking change: `CallClient::connect` / `ChannelClient::connect_quic`
removed; `CallCredentials` / `RemoteIdentity` / `FingerprintPinVerifier` removed; `RemoteIdentity` / `FingerprintPinVerifier` relocated;
relocated; `ClientError` removed.** Call sites that used the `CallCredentials` removed; `ClientError` removed.** Call sites that
convenience constructors must switch to `AlknetClient::dial_*` + used the convenience constructors must switch to `AlknetClient::dial_*`
`spawn_dispatch` / `from_connection`. Import paths for + `spawn_dispatch` / `from_connection`. Import paths for
`CallCredentials` / `RemoteIdentity` change from `alknet_call` to `RemoteIdentity` change from `alknet_call` to `alknet_core`;
`alknet_core`. This is expected — the develop branch is a total `CallCredentials` is removed (per ADR-091's 2026-07-17 amendment) —
rewrite; there are no external consumers to preserve compatibility callers pass `ConnectionCredentials` to the dial and, where needed,
for. The migration plan handles the call-site + import updates. `auth_token` as a per-request payload field. This is expected — the
develop branch is a total rewrite; there are no external consumers to
preserve compatibility for. The migration plan handles the call-site +
import updates.
- **A new crate.** `alknet-client` is one more crate in the workspace. - **A new crate.** `alknet-client` is one more crate in the workspace.
The cost is low (the dial is narrow), and the dependency profile The cost is low (the dial is narrow), and the dependency profile
rules out the alternatives, but it is a new entry in the crate rules out the alternatives, but it is a new entry in the crate
@@ -490,11 +518,11 @@ shared client dial crate is structural — every outbound-dialing role
re-distributing the dial across crates, reintroducing the duplicated re-distributing the dial across crates, reintroducing the duplicated
boilerplate. The three-dial API (`dial_quic` / `dial_tcp_tls` / boilerplate. The three-dial API (`dial_quic` / `dial_tcp_tls` /
`dial_iroh`) is one-way — changing the signatures after consumers exist `dial_iroh`) is one-way — changing the signatures after consumers exist
is a rewrite. The internal implementation (how `CallCredentials` feeds is a rewrite. The internal implementation (how `ConnectionCredentials`
`TlsClientConfig::new`, how the iroh dial maps the `Ed25519SecretKey`) feeds `TlsClientConfig::new`, how the iroh dial maps the
is two-way. The `alknet/register` ALPN name is one-way (wire `Ed25519SecretKey`) is two-way. The `alknet/register` ALPN name is
compatibility); its wire protocol is two-way until the dedicated ADR one-way (wire compatibility); its wire protocol is two-way until the
lands. dedicated ADR lands.
## References ## References
@@ -3,7 +3,10 @@
## Status ## Status
Accepted (amends ADR-089 §3 and §5; amends ADR-087's `TlsClientConfig::new` Accepted (amends ADR-089 §3 and §5; amends ADR-087's `TlsClientConfig::new`
input framing) input framing; amended 2026-07-17 — `CallCredentials` is removed, not
retained in `alknet-call`; `from_call`'s `credentials_auth_token` dead
path removed; `auth_token` is a per-request payload field, not a
call-protocol credential)
## Context ## Context
@@ -173,21 +176,104 @@ Each dial extracts what its transport's identity layer needs:
`Ed25519SecretKey` → `iroh::SecretKey::from_bytes`; `Ed25519SecretKey` → `iroh::SecretKey::from_bytes`;
`creds.remote_identity.fingerprint` → `NodeId` (verifier). `creds.remote_identity.fingerprint` → `NodeId` (verifier).
### `CallCredentials` stays in `alknet-call` ### `CallCredentials` is removed (amendment 2026-07-17)
`CallCredentials` remains the **call-protocol** credential bundle. Its > **This section supersedes the original "CallCredentials stays in
`auth_token` field stays — the call protocol uses it (the `from_call` > `alknet-call`" decision.** The original rationale rested on a code
forwarding handler populates `auth_token` on outgoing `call.requested` > path that does not exist. The trace below is the correction.
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 `CallCredentials` is **removed**, not retained. Once the transport
of `CallCredentials` to core is superseded by this ADR: only dimensions (`local_identity`, `remote_identity`) move to
`ConnectionCredentials` and `RemoteIdentity` move to core. The call `ConnectionCredentials` in `alknet-core`, `CallCredentials` would
protocol's own credential type stays in the call crate. reduce to a one-field struct `{ auth_token: Option<AuthToken> }` — and
that field has **no reader**.
**The trace (why the original rationale was wrong).** The original
section claimed the call protocol uses `CallCredentials.auth_token`
because "the `from_call` forwarding handler populates `auth_token` on
outgoing `call.requested` payloads." That chain does not connect:
- `from_call`'s signature is `from_call(connection: &CallConnection,
config: FromCallConfig)` — no `CallCredentials` parameter.
`FromCallConfig` has no credential field.
- The `auth_token` the `from_call` forwarding handlers *can* set on
payloads is sourced from `OpSummary.credentials_auth_token`, an
`Option<String>` that is **hardcoded to `None` at every construction
site** (`from_call.rs:185, 748, 757`). It is not read from
`CallCredentials.auth_token`, and it is a different type
(`Option<String>` vs `Option<AuthToken>`). The two were never
connected, even in intent.
- The consuming side — `Dispatcher::resolve_identity`
(`dispatch.rs:119`) — reads `payload.get("auth_token").as_str()` from
the per-request call payload. It does not read `CallCredentials`.
**Where `auth_token` actually originates.** It is a per-request payload
field, populated by two real paths, neither of which touches
`CallCredentials`:
- **Browsers over WebSocket** — the browser sends `auth_token` directly
in the `call.requested` JSON payload (`websocket/mod.rs:202–206`); the
WS layer (`upgrade.rs:178–181`) passes `envelope.payload` straight to
`dispatch_requested`. The browser is the originator; the WS layer is
a transparent passthrough.
- **HTTP gateway (bearer)** — `gateway/dispatch.rs` resolves the
`Authorization: Bearer` header to an `Identity` at the HTTP boundary
(`resolve_bearer`, line 58) and passes the `Identity` into
`build_root_context`. The call protocol sees the resolved `Identity`,
not the token. `auth_token` does not enter the call payload on this
path.
So `CallCredentials.auth_token` is a write-only field (it has a setter,
`with_auth_token`, and zero readers). `connect()` — `CallCredentials`'s
only consumer — is removed in Phase 5 of the migration. With `connect`
gone, nothing constructs or reads `CallCredentials` except the tests.
**`auth_token`'s two real use cases (confirming no call-protocol
credential bundle is needed):**
1. **HTTP auth** — the inbound case. The HTTP gateway resolves the
bearer token to an `Identity` via `IdentityProvider::resolve_from_token`
at the HTTP boundary. The call protocol receives the `Identity`, not
the token.
2. **Registration** (`alknet/register` native ALPN, `/register` HTTP
endpoint) — a client not yet associated with a hub presents a
one-time registration token; the hub creates a `PeerEntry` (a new
identity based on the fingerprint). Outbound, the vault manages the
token on the client side; inbound, the hub's registration handler
consumes it. Neither path involves `CallCredentials`.
A hub does not "forward with its own token" in the way the original
rationale assumed. Where the hub authenticates to an outside service
(another hub's HTTP interface, an external API), the vault manages that
outbound token — it is not a call-protocol credential. The
`from_call` `credentials_auth_token` path was a future hatch for a
use case that dissolved once `IdentityProvider::resolve_from_token`
solved the inbound identity problem: the hub authenticates as itself
(its `Identity` is on the connection), and the spoke authorizes the hub
as the direct caller. No per-forwarded-call token is needed.
**`from_call`'s `credentials_auth_token` is removed too.** It is the
same family of dead code — an always-`None` field of a different type
than `CallCredentials.auth_token`, never connected to anything. The
`credentials_auth_token` field on `OpSummary`, the `credentials_auth_token`
parameters on `make_forwarding_handler` / `make_streaming_forwarding_handler`,
and the `auth_token` parameter on `build_forwarded_payload` are removed.
The forwarding handlers stop emitting `auth_token` in payloads (which
they never did in practice — the source was always `None`). The two
`from_call` tests asserting the `Some` path
(`build_forwarded_payload_sets_auth_token_when_provided`,
`streaming_forwarding_handler_sets_auth_token_when_provided`) are
removed — they test a code path never exercised in production. If a
future hub needs its own token on forwarded payloads, that is a fresh,
end-to-end-wired feature, not a vestigial path.
**What does NOT move to `alknet-core`:** `ConnectionCredentials` and
`RemoteIdentity` move (the original decision). `CallCredentials` does
not move — it is removed. ADR-089 §5's move of `CallCredentials` to
core is superseded twice over: first by the original ADR-091 (move
`ConnectionCredentials` instead), and now by this amendment (remove
`CallCredentials` entirely). There is no call-protocol credential
bundle; `auth_token` is a per-request payload field, full stop.
### `TlsClientConfig::new` input framing ### `TlsClientConfig::new` input framing
@@ -229,8 +315,8 @@ the credential dimensions.
- **The dial is fully decoupled from the call protocol.** - **The dial is fully decoupled from the call protocol.**
`ConnectionCredentials` carries only transport-identity dimensions; `ConnectionCredentials` carries only transport-identity dimensions;
`alknet-client` has no call-protocol coupling in its credential type. `alknet-client` has no call-protocol coupling in its credential type.
The `auth_token` (a call-protocol / hub-layer concept) stays in There is no call-protocol credential bundle — `auth_token` is a
`alknet-call` where it belongs. per-request payload field, not a credential.
- **All three dial signatures are unified.** A caller no longer needs to - **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 know that iroh takes a bare key while quinn/tcp take a credential
bundle — all take `&ConnectionCredentials`. The `node_id` parameter on bundle — all take `&ConnectionCredentials`. The `node_id` parameter on
@@ -240,74 +326,99 @@ the credential dimensions.
`auth_token` "travels with the `Connection` into the protocol `auth_token` "travels with the `Connection` into the protocol
take-over, where it is sent as the first call-protocol frame." This take-over, where it is sent as the first call-protocol frame." This
was aspirational — `Connection` carries no `auth_token`, and was aspirational — `Connection` carries no `auth_token`, and
`spawn_dispatch` takes no credentials. With `auth_token` out of the `spawn_dispatch` takes no credentials. With `CallCredentials` removed,
dial's credential bundle entirely, the claim is no longer needed. The the claim is not merely unneeded; the field it described was never
token is a per-request field on `call.requested` payloads, set by the read. `auth_token` is a per-request field on `call.requested` payloads,
caller or the `from_call` forwarding handler. set by browsers (in the WS payload) or resolved by the HTTP gateway at
its boundary (bearer → `Identity`).
- **`dial_ssh` fits the same shape when it arrives.** The credential - **`dial_ssh` fits the same shape when it arrives.** The credential
dimensions SSH needs (local key + expected host key) are exactly what dimensions SSH needs (local key + expected host key) are exactly what
`ConnectionCredentials` carries. No future ADR needed for the SSH dial `ConnectionCredentials` carries. No future ADR needed for the SSH dial
signature. signature.
- **`CallCredentials` stays in `alknet-call` — its home.** The call - **A dead credential type and a dead forwarding-token path are removed
protocol's own credential type is not dragged into core for the dial's (amendment 2026-07-17).** `CallCredentials` is removed (its
benefit. Core gets `ConnectionCredentials` (transport-level); the call `auth_token` field had no reader). `from_call`'s
crate keeps `CallCredentials` (protocol-level). `credentials_auth_token` is removed (always `None`, different type
than `CallCredentials.auth_token`, never connected). Both were future
hatches from the era before `IdentityProvider::resolve_from_token`
solved the inbound identity problem; the hatches dissolved once it
did. See the amended §"`CallCredentials` is removed" above for the
trace.
**Negative:** **Negative:**
- **`CallCredentials` loses two fields.** `tls_identity` and - **`CallCredentials` is removed (a public type).** Callers that
`remote_identity` move to `ConnectionCredentials` in core; constructed `CallCredentials` (the integration test; any future
`CallCredentials` becomes `{auth_token: Option<AuthToken>}` (or is assembly-layer code) switch to `ConnectionCredentials` for the dial.
restructured — the call protocol may assemble it from `auth_token`, where needed, is a per-request payload field (browsers
`ConnectionCredentials` + `auth_token` at the take-over site, or the send it in the WS payload; the HTTP gateway resolves bearer →
caller provides `auth_token` per-request). The exact restructure is a `Identity` at its boundary). This is expected — `connect()` was
two-way-door implementation detail; the one-way decision is that the `CallCredentials`'s only consumer and is removed in the same
transport dimensions leave `CallCredentials`. migration. There are no external consumers (develop branch is a total
- **The assembly layer assembles two credential bundles, not one.** Where rewrite).
ADR-089 had the assembly layer build one `CallCredentials`, it now - **The assembly layer builds one credential bundle, not two.** Where
builds `ConnectionCredentials` (for the dial) and, separately, ADR-089 had the assembly layer build one `CallCredentials` (and the
provides the `auth_token` to the call-protocol layer (for the original ADR-091 reframed it as two — `ConnectionCredentials` for the
per-request payload). This is the correct layering — the dial and the dial + a per-request `auth_token`), the assembly layer now builds
protocol consume different dimensions — but it is one more type at the `ConnectionCredentials` for the dial only. `auth_token` is not a
assembly site. credential the assembly layer constructs; it is a per-request payload
- **ADR-089 §5's "CallCredentials moves to core" is superseded.** The field the browser (or the HTTP gateway's bearer resolution) supplies.
move target changes from `CallCredentials` to `ConnectionCredentials` This is fewer types at the assembly site, not more.
+ `RemoteIdentity`. `CallCredentials` stays in `alknet-call`. This - **ADR-089 §5's "CallCredentials moves to core" is superseded twice.**
affects the extraction plan's Phase 0 (the additive credentials move). The original ADR-091 reframed the move target as `ConnectionCredentials`
(not `CallCredentials`); this amendment removes `CallCredentials`
entirely. What moves to `alknet-core`: `ConnectionCredentials` +
`RemoteIdentity`. What does not move: `CallCredentials` (removed, not
relocated). This affects the extraction plan's Phase 0 (additive
credentials move) and Phase 5 (the call prune now removes
`CallCredentials` and the `from_call` dead path, not just `connect`
and the TLS helpers).
## Door type ## Door type
**One-way.** The dial signatures (`dial_quic` / `dial_tcp_tls` / **One-way.** The dial signatures (`dial_quic` / `dial_tcp_tls` /
`dial_iroh` all taking `&ConnectionCredentials`) are the public API `dial_iroh` all taking `&ConnectionCredentials`) are the public API
surface of `alknet-client`. The credential-type decoupling surface of `alknet-client`. The credential-type decoupling
(`ConnectionCredentials` in core, `CallCredentials` in call) determines (`ConnectionCredentials` in core, no call-protocol credential bundle)
the dep graph (`alknet-client` depends on `alknet-core` for determines the dep graph (`alknet-client` depends on `alknet-core` for
`ConnectionCredentials`, not on `alknet-call` for `CallCredentials`). `ConnectionCredentials`, not on `alknet-call`). Reversing would mean
Reversing would mean re-coupling the dial to the call protocol's re-coupling the dial to the call protocol's credential type and
credential type and re-asymmetrizing the iroh dial. The crate is re-asymmetrizing the iroh dial. The `CallCredentials` removal
greenfield (Phase 3 of the extraction plan), so the door is still open (amendment 2026-07-17) is the same door — removing a public type whose
now — this ADR records the decision before implementation. only consumer (`connect`) is removed in the same migration. The crate
is greenfield (Phase 3 of the extraction plan), so the door is still
open now — this ADR records the decisions before implementation.
## References ## References
- ADR-089 — `AlknetClient` native dial seam (§3 dial signatures amended - ADR-089 — `AlknetClient` native dial seam (§3 dial signatures amended
— all take `&ConnectionCredentials`; §5 move amended — — all take `&ConnectionCredentials`; §5 move amended —
`ConnectionCredentials`/`RemoteIdentity` move to core, not `ConnectionCredentials`/`RemoteIdentity` move to core, not
`CallCredentials`) `CallCredentials`; §5 further amended 2026-07-17 — `CallCredentials`
removed, not retained in `alknet-call`)
- ADR-087 — `TlsClientConfig` not blocked on dial (input framing - ADR-087 — `TlsClientConfig` not blocked on dial (input framing
amended — `ClientVerifierContext` derived from amended — `ClientVerifierContext` derived from
`ConnectionCredentials.remote_identity`, not `CallCredentials`) `ConnectionCredentials.remote_identity`, not `CallCredentials`)
- ADR-034 — client-side verifier selection (the rule - ADR-034 — client-side verifier selection (the rule
`ConnectionCredentials.remote_identity` drives — unchanged) `ConnectionCredentials.remote_identity` drives — unchanged)
- ADR-017 §7 — the three credential dimensions (the source of - ADR-017 §7 — the three credential dimensions (the historical source of
`CallCredentials`'s three fields; this ADR splits the transport `CallCredentials`'s three fields; the transport dimensions moved to
dimensions from the protocol dimension) `ConnectionCredentials`, the `auth_token` dimension is a per-request
payload field, and `CallCredentials` itself is removed)
- `crates/alknet-call/src/protocol/dispatch.rs` — - `crates/alknet-call/src/protocol/dispatch.rs` —
`Dispatcher::resolve_identity` reads `payload.get("auth_token")` `Dispatcher::resolve_identity` reads `payload.get("auth_token")`
(per-request, not connection-level) (per-request, not connection-level — the consumer of `auth_token`)
- `crates/alknet-call/src/client/from_call.rs` — - `crates/alknet-call/src/client/from_call.rs` — the
`build_forwarded_payload` sets `auth_token` on outgoing payloads `credentials_auth_token` field on `OpSummary` and the
(per-request, the hub's own token) `auth_token` parameter on `build_forwarded_payload` (the removed dead
path; always `None`, different type than `CallCredentials.auth_token`,
never connected)
- `crates/alknet-http/src/gateway/dispatch.rs` — `resolve_bearer` (the
HTTP path: bearer → `Identity` at the boundary; the call layer sees
the identity, not the token)
- `crates/alknet-http/src/websocket/mod.rs` — the WS path:
`auth_token` in the browser's call payload, passed through to
`dispatch_requested` unchanged
- `docs/research/references/ssh/russh/06-usage-patterns.md` — the SSH - `docs/research/references/ssh/russh/06-usage-patterns.md` — the SSH
client usage patterns (check_server_key + authenticate_publickey) client usage patterns (check_server_key + authenticate_publickey)
validating the `ConnectionCredentials` shape for a future `dial_ssh` validating the `ConnectionCredentials` shape for a future `dial_ssh`
+4 -3
View File
@@ -96,10 +96,11 @@ alknet-vault (standalone — foundational to ACL: key derivation, identity)
├── Substrate ├── Substrate
│ alknet-core ProtocolHandler, Connection, BidiStreamSource, AuthContext, │ alknet-core ProtocolHandler, Connection, BidiStreamSource, AuthContext,
│ │ IdentityProvider, StaticConfig, DynamicConfig, fingerprint, │ │ IdentityProvider, StaticConfig, DynamicConfig, fingerprint,
│ │ CallCredentials, RemoteIdentity │ │ ConnectionCredentials, RemoteIdentity
│ │ (endpoint extracted to alknet-endpoint; core is now lightweight │ │ (endpoint extracted to alknet-endpoint; core is now lightweight
│ │ types+auth+config — no quinn/iroh/rcgen deps; CallCredentials │ │ types+auth+config — no quinn/iroh/rcgen deps; ConnectionCredentials
│ │ moved here from alknet-call per ADR-089 §5) │ │ + RemoteIdentity moved here from alknet-call per ADR-091;
│ │ CallCredentials removed per ADR-091 Am. 2026-07-17)
│ ├── alknet-tls TlsServerConfig + TlsClientConfig + FingerprintPinVerifier — shared TLS config across quinn + TCP+TLS + iroh (ADR-082/087; FingerprintPinVerifier moved from alknet-call per ADR-089 §5) │ ├── alknet-tls TlsServerConfig + TlsClientConfig + FingerprintPinVerifier — shared TLS config across quinn + TCP+TLS + iroh (ADR-082/087; FingerprintPinVerifier moved from alknet-call per ADR-089 §5)
│ ├── alknet-call CallAdapter on alknet/call, CallClient (spawn_dispatch only — connect removed per ADR-089 §5), OperationRegistry, adapters (no TLS/transport deps) │ ├── alknet-call CallAdapter on alknet/call, CallClient (spawn_dispatch only — connect removed per ADR-089 §5), OperationRegistry, adapters (no TLS/transport deps)
│ ├── alknet-channels │ ├── alknet-channels
@@ -61,7 +61,7 @@
4. **Session credential return** — what does the hub return on 4. **Session credential return** — what does the hub return on
successful registration? A `PeerEntry`? A session token? Both? successful registration? A `PeerEntry`? A session token? Both?
How does the returned credential feed into the subsequent How does the returned credential feed into the subsequent
`alknet/channels` connection's `CallCredentials`? `alknet/channels` connection's `ConnectionCredentials`?
5. **Relationship to OQ-58** — the HTTP registration endpoint 5. **Relationship to OQ-58** — the HTTP registration endpoint
(OQ-58) and `alknet/register` share the enrollment semantics (OQ-58) and `alknet/register` share the enrollment semantics
(create `PeerEntry`, return credential) but differ in transport (create `PeerEntry`, return credential) but differ in transport
+149 -86
View File
@@ -47,16 +47,24 @@ but the extraction unwinds that.
| Lines | Concern | Destination | LOC | | Lines | Concern | Destination | LOC |
|-------|---------|-------------|-----| |-------|---------|-------------|-----|
| 40-88 | `RemoteIdentity`, `CallCredentials` (struct + builder) | split: `RemoteIdentity` + new `ConnectionCredentials` → `alknet-core` (`credentials.rs`); `CallCredentials` restructured (transport dimensions leave, `auth_token` stays) → stays in `alknet-call` (ADR-091) | ~48 | | 40-88 | `RemoteIdentity`, `CallCredentials` (struct + builder) | split: `RemoteIdentity` + new `ConnectionCredentials` → `alknet-core` (`credentials.rs`); `CallCredentials` **removed** (its `auth_token` field had no reader — see Phase 5) | ~48 |
| 90-100 | `ClientError` enum | **removed** (only produced by `connect`) | ~10 | | 90-100 | `ClientError` enum | **removed** (only produced by `connect`) | ~10 |
| 102-187 | `CallClient` struct + `new` + `spawn_dispatch` | **stays** (the pure protocol take-over) | ~85 | | 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 | | 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 | | 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 | | 640-930 | Tests (16 total — see Phase 5 audit) | split: 10 TLS/verifier tests → `alknet-tls` (Phase 1); 4 protocol-level tests stay in `alknet-call` unchanged; 2 `CallCredentials`-field tests → `alknet-core` (testing `ConnectionCredentials`); 0 lib tests call `connect()` | ~290 |
The call crate's prune is ~140 lines of implementation + ~290 lines of The call crate's prune is ~140 lines of implementation + `CallCredentials`
tests that need rewriting. What remains is the pure protocol: `CallClient` removal + `from_call`'s `credentials_auth_token` dead-path removal. The
+ `CallConnection` + `Dispatcher` + the wire protocol. test work: 10 tests move to `alknet-tls` (Phase 1), 2 `CallCredentials`
tests move to `alknet-core` (testing `ConnectionCredentials`), 4
protocol-level tests stay unchanged. The integration test
(`two_node_call.rs`, 2 tests) splits: the dial+takeover composition test
moves to `alknet-client/tests/` (Phase 3, rewritten with a minimal echo
`ProtocolHandler`); the `from_call` test stays in `alknet-call` (Phase 5,
rewritten to use `spawn_dispatch` + loopback `Connection`). What remains
is the pure protocol: `CallClient` + `CallConnection` + `Dispatcher` +
the wire protocol.
### `crates/alknet-http/src/server/adapter.rs` — the residual ### `crates/alknet-http/src/server/adapter.rs` — the residual
@@ -93,14 +101,16 @@ already removed per ADR-065; tests use `from_stream` with
`crates/alknet-core/src/credentials.rs` (~40 lines). `crates/alknet-core/src/credentials.rs` (~40 lines).
`ConnectionCredentials` is the transport-level credential bundle `ConnectionCredentials` is the transport-level credential bundle
(ADR-091) — it carries `local_identity` + `remote_identity` (the two (ADR-091) — it carries `local_identity` + `remote_identity` (the two
dimensions the dial consumes). `CallCredentials` stays in dimensions the dial consumes). `CallCredentials` is **removed** (its
`alknet-call` (it is the call-protocol bundle; its `auth_token` field `auth_token` field had no reader — `connect()` read only
is a per-request call-protocol concept, not a transport credential). `tls_identity` + `remote_identity`; `spawn_dispatch` takes no
Update `alknet-core/src/lib.rs` to `pub mod credentials` + re-export. credentials; the `from_call` forwarding path's `auth_token` source was
Update `alknet-call` to import `ConnectionCredentials` + `RemoteIdentity` a different, always-`None` field never connected to `CallCredentials`).
from core and re-export them; `CallCredentials` retains `auth_token` and `auth_token` is a per-request payload field, not a call-protocol
references the core types for the transport dimensions. No other credential. Update `alknet-core/src/lib.rs` to `pub mod credentials` +
changes. re-export. Update `alknet-call` to import `ConnectionCredentials` +
`RemoteIdentity` from core and re-export them; remove `CallCredentials`
and its builder methods. No other changes.
**Why first:** It's independent of the three new crates, purely **Why first:** It's independent of the three new crates, purely
additive (core gains types, nothing breaks), and means `alknet-client` additive (core gains types, nothing breaks), and means `alknet-client`
@@ -115,7 +125,7 @@ continue to work via the re-export.
**Done when:** `cargo test` passes, `ConnectionCredentials` + **Done when:** `cargo test` passes, `ConnectionCredentials` +
`RemoteIdentity` are defined in `alknet-core`, `alknet-call` imports `RemoteIdentity` are defined in `alknet-core`, `alknet-call` imports
them from core, `CallCredentials` stays in `alknet-call`. them from core, `CallCredentials` is removed from `alknet-call`.
### Phase 1: Create `alknet-tls` (greenfield, additive) ### Phase 1: Create `alknet-tls` (greenfield, additive)
@@ -299,16 +309,28 @@ lightweight (~3200 LOC, no heavy transport deps).
### Phase 5: Prune `alknet-call` (subtractive, breakage confined) ### Phase 5: Prune `alknet-call` (subtractive, breakage confined)
**What:** Delete `connect()` + all TLS helpers + `ClientError` from **What:** Delete `connect()` + all TLS helpers + `ClientError` +
`call_client.rs`. The transport dimensions (`ConnectionCredentials`/ `CallCredentials` from `call_client.rs`. The transport dimensions
`RemoteIdentity`) already moved to core in Phase 0; here we remove the (`ConnectionCredentials`/`RemoteIdentity`) already moved to core in
old definitions from `call_client.rs` and update imports. Phase 0; here we remove the old definitions from `call_client.rs` and
`CallCredentials` stays in `alknet-call` (retaining `auth_token`, update imports. `CallCredentials` is **removed** (its `auth_token`
referencing the core types — ADR-091). Update `Cargo.toml` to drop field had no reader — `connect()` read only `tls_identity` +
`quinn`/`rustls`/`rustls-native-certs`/`rustls-pemfile`. Rewrite the `remote_identity`; `spawn_dispatch` takes no credentials; the
tests that used `connect` to use `spawn_dispatch` directly (with `from_call` forwarding path's `auth_token` source was
`Connection::from_stream` mocks) or `AlknetClient::dial_quic` + `OpSummary.credentials_auth_token: Option<String>`, always `None`,
`spawn_dispatch`. never connected to `CallCredentials.auth_token`). `auth_token` is a
per-request payload field, not a call-protocol credential. Update
`Cargo.toml` to drop `quinn`/`rustls`/`rustls-native-certs`/
`rustls-pemfile`. Also remove `from_call`'s `credentials_auth_token`
dead path: the `credentials_auth_token` field on `OpSummary`, the
`credentials_auth_token` parameters on `make_forwarding_handler` /
`make_streaming_forwarding_handler`, and the `auth_token` parameter on
`build_forwarded_payload` are all removed (always `None`, different
type than `CallCredentials.auth_token`, never connected). The two
`from_call` tests asserting the `Some` path
(`build_forwarded_payload_sets_auth_token_when_provided`,
`streaming_forwarding_handler_sets_auth_token_when_provided`) are
removed — they test a code path never exercised in production.
**The `call_client.rs` after prune:** **The `call_client.rs` after prune:**
- `CallClient` struct + `new` + `registry` + `identity_provider` + - `CallClient` struct + `new` + `registry` + `identity_provider` +
@@ -316,13 +338,10 @@ tests that used `connect` to use `spawn_dispatch` directly (with
- `CallConnection` + `Dispatcher` wiring (stays — protocol) - `CallConnection` + `Dispatcher` wiring (stays — protocol)
- `RemoteIdentity` — removed from `call_client.rs` (moved to - `RemoteIdentity` — removed from `call_client.rs` (moved to
`alknet-core` in Phase 0; re-imported from there) `alknet-core` in Phase 0; re-imported from there)
- `CallCredentials` — restructured, stays in `alknet-call`: the - `CallCredentials` — **removed** (its `auth_token` field had no
transport dimensions (`tls_identity`, `remote_identity`) leave for reader; `connect()` was its only consumer and is removed in this
`ConnectionCredentials` in `alknet-core` (Phase 0); the `auth_token` phase; `auth_token` is a per-request payload field, not a credential
field stays (it is a call-protocol concept, not a transport credential — ADR-091, amended 2026-07-17)
— ADR-091). `CallCredentials` references the core types for the
transport dimensions or assembles from `ConnectionCredentials` +
`auth_token` at the take-over site.
- `ClientError` — removed - `ClientError` — removed
- `connect` + all `build_*`/`select_*`/`load_*`/`Ed25519SigningKey`/ - `connect` + all `build_*`/`select_*`/`load_*`/`Ed25519SigningKey`/
`RawKeyClientCertResolver`/`NoClientCertResolver`/ `RawKeyClientCertResolver`/`NoClientCertResolver`/
@@ -337,23 +356,37 @@ any stray `#[cfg(feature = "quinn")]` the prune missed). `alknet-call`
becomes a pure protocol crate. becomes a pure protocol crate.
**Test impact (per the test audit below):** the lib tests in **Test impact (per the test audit below):** the lib tests in
`call_client.rs` (16 tests) split into 6 that stay unchanged `call_client.rs` (16 tests) split into 4 that stay unchanged
(protocol-level, use `spawn_dispatch(stub_connection())`) and 10 that (protocol-level, use `spawn_dispatch(stub_connection())`), 10 that
move to `alknet-tls` in Phase 1 (TLS/verifier tests). **Zero lib tests move to `alknet-tls` in Phase 1 (TLS/verifier tests), and 2 that move
need rewriting** — no test in `call_client.rs` calls `connect()`. The to `alknet-core` (testing `ConnectionCredentials` — the
`from_call.rs` tests (27 tests) stay unchanged — they use `call_credentials_builder_methods` and
`CallConnection` directly. The one integration test file `remote_identity_none_is_load_bearing_not_defaulted` tests, which
(`tests/two_node_call.rs`, 2 tests) calls `connect()` twice — it moves access `remote_identity`/`tls_identity` fields that moved to
to `alknet-client/tests/` (Phase 3) or is updated to use `ConnectionCredentials`). The `from_call.rs` tests: 25 stay unchanged
`AlknetClient::dial_quic` + `spawn_dispatch` (Phase 5). (protocol-level, use `CallConnection` directly), 2 are removed (the
`credentials_auth_token` `Some`-path tests — see above). The
integration test file (`tests/two_node_call.rs`, 2 tests) splits:
`two_node_call_round_trip` (dial + take-over composition) moves to
`alknet-client/tests/` (Phase 3, rewritten with a minimal echo
`ProtocolHandler` on a test ALPN — no `alknet-call` dependency);
`from_call_discovers_and_forwards_over_quic_loopback` (call-protocol-
specific, uses `from_call`) stays in `alknet-call` (Phase 5, rewritten
to use `spawn_dispatch` + loopback `Connection` — **not**
`AlknetClient::dial_quic`, which would re-create the dep the prune
removes).
The implementation prune is mechanical: delete the error enum, delete The implementation prune: delete `ClientError`, delete `connect`,
`connect`, delete the TLS helpers (~140 lines). The test work is: the delete `CallCredentials` and its builder methods, delete the TLS
10 TLS tests already moved in Phase 1, the 6 protocol tests stay, the helpers (~140 lines), delete `from_call`'s `credentials_auth_token`
integration test is handled separately. dead path (~30 lines). The test work: 10 TLS tests already moved in
Phase 1, 2 `CallCredentials` tests move to `alknet-core`, 4 protocol
tests stay, 2 `from_call` dead-path tests removed, the integration
test splits as above.
**Compilable state:** `cargo test -p alknet-call` passes with the **Compilable state:** `cargo test -p alknet-call` passes with the
rewritten tests. The crate has no TLS/transport deps. rewritten tests. The crate has no TLS/transport deps, no
`CallCredentials`, no `from_call` dead path.
**Done when:** `cargo test -p alknet-call` passes, the crate is a pure **Done when:** `cargo test -p alknet-call` passes, the crate is a pure
protocol crate. protocol crate.
@@ -396,20 +429,21 @@ wrapper is gone.
| After phase | State | | After phase | State |
|-------------|-------| |-------------|-------|
| 0 (credentials) | `ConnectionCredentials`/`RemoteIdentity` in core; call imports from core; `CallCredentials` stays in call; no breakage | | 0 (credentials) | `ConnectionCredentials`/`RemoteIdentity` in core; call imports from core; `CallCredentials` removed; no breakage |
| 1 (tls) | `alknet-tls` builds standalone; core/call/http unchanged (old code duplicated) | | 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) | | 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) | | 3 (client) | `alknet-client` builds standalone; call still has old `connect` (duplicate) |
| 4 (core prune) | core is lightweight; `endpoint.rs` gone; `ConnectionCredentials` 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 | | 5 (call prune) | call is pure protocol; `connect` + TLS helpers + `CallCredentials` + `from_call` dead path gone; Category B tests already moved; 2 `CallCredentials` tests moved to core |
| 6 (http fix) | http has no `QuicStream` wrapper; clean `accept_bi` path | | 6 (http fix) | http has no `QuicStream` wrapper; clean `accept_bi` path |
Phases 0-3 are purely additive — no existing code breaks, no tests 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 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 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 crate's own tests (and per the test audit, the call prune removes
tests — the TLS tests moved in Phase 1, the protocol tests use `CallCredentials` and the `from_call` dead path; the TLS tests moved in
`spawn_dispatch` directly). Phase 6 is a small fix. Phase 1, the `CallCredentials` tests moved to core, the protocol tests
use `spawn_dispatch` directly). Phase 6 is a small fix.
## Ordering rationale ## Ordering rationale
@@ -454,43 +488,58 @@ to clean up later.
`ConnectionCredentials` (not `CallCredentials`) is what moves — it is `ConnectionCredentials` (not `CallCredentials`) is what moves — it is
the transport-level credential bundle (`local_identity` + the transport-level credential bundle (`local_identity` +
`remote_identity`), carrying only the dimensions the dial consumes. `remote_identity`), carrying only the dimensions the dial consumes.
`CallCredentials` stays in `alknet-call` because its `auth_token` field `CallCredentials` is **removed** (its `auth_token` field had no reader
is a call-protocol / hub-layer concept (bearer-token identity — `connect()` read only `tls_identity` + `remote_identity`;
correlation for browsers and `alknet/register`), not a transport `spawn_dispatch` takes no credentials; the `from_call` forwarding
credential. See ADR-091 for the full rationale. path's `auth_token` source was `OpSummary.credentials_auth_token:
Option<String>`, always `None`, never connected to
`CallCredentials.auth_token`). `auth_token` is a per-request payload
field, not a call-protocol credential. See ADR-091 (amended
2026-07-17) for the full rationale and trace.
The move is ~40 lines (struct definitions + builder impls) into a new The move is ~40 lines (struct definitions + builder impls) into a new
`crates/alknet-core/src/credentials.rs` (or `auth.rs` — `auth.rs` `crates/alknet-core/src/credentials.rs`. `alknet-call`'s
already holds `AuthToken`, so `credentials.rs` is cleaner to keep the `client/mod.rs` imports `ConnectionCredentials` + `RemoteIdentity`
auth module from growing). `alknet-call`'s `client/mod.rs` imports from core and re-exports them; `CallCredentials` is removed from
`ConnectionCredentials` + `RemoteIdentity` from core and re-exports `alknet-call`. Test changes: the two `CallCredentials`-field tests
them; `CallCredentials` stays defined in `alknet-call` (retaining (`call_credentials_builder_methods`,
`auth_token`, referencing the core types for the transport dimensions). `remote_identity_none_is_load_bearing_not_defaulted`) move to
Test changes are minimal — the Category A tests that reference `alknet-core` testing `ConnectionCredentials`; `call_client_is_send_sync`
`CallCredentials` directly may need import updates depending on how drops the `CallCredentials`/`RemoteIdentity` assertions (or they move
`CallCredentials` is restructured. with the types).
### Phase 5 test audit — `call_client.rs` (16 tests) ### Phase 5 test audit — `call_client.rs` (16 tests)
The 16 tests in `call_client.rs` split into three categories: The 16 tests in `call_client.rs` split into three categories:
**Category A — protocol-level, stay in `alknet-call`, no rewrite **Category A — protocol-level, stay in `alknet-call`, no rewrite
needed (6 tests):** needed (4 tests):**
These tests use `spawn_dispatch(stub_connection())` or These tests use `spawn_dispatch(stub_connection())` and don't touch
`CallCredentials` directly. They don't touch `connect` or any TLS `connect`, `CallCredentials` fields, or any TLS helper.
helper. `stub_connection()` (line 582) uses `stub_connection()` (line 582) uses
`Connection::from_stream(tokio::io::channel(...))` — already `Connection::from_stream(tokio::io::channel(...))` — already
transport-agnostic. These survive the prune unchanged. transport-agnostic. These survive the prune unchanged.
| Test | Line | What it tests | | Test | Line | What it tests |
|------|------|---------------| |------|------|---------------|
| `call_credentials_builder_methods` | 652 | `CallCredentials` builder |
| `external_op_dispatches_and_populates_capabilities` | 665 | dispatch + capabilities | | `external_op_dispatches_and_populates_capabilities` | 665 | dispatch + capabilities |
| `unknown_op_returns_not_found` | 679 | dispatch error path | | `unknown_op_returns_not_found` | 679 | dispatch error path |
| `spawn_dispatch_returns_live_call_connection` | 691 | `spawn_dispatch` + ALPN | | `spawn_dispatch_returns_live_call_connection` | 691 | `spawn_dispatch` + ALPN |
| `call_client_is_send_sync` | 705 | trait bounds | | `call_client_is_send_sync` | 705 | trait bounds (import update: `RemoteIdentity` moved to core) |
| `remote_identity_none_is_load_bearing_not_defaulted` | 921 | `CallCredentials::new()` |
**Category A2 — `CallCredentials`-field tests, move to `alknet-core`
(2 tests):**
These test `CallCredentials` fields (`remote_identity`, `tls_identity`)
that moved to `ConnectionCredentials` in `alknet-core` (ADR-091). They
move to `alknet-core` testing `ConnectionCredentials::new()` +
`with_remote_identity()`.
| Test | Line | What it tests | Move target |
|------|------|---------------|-------------|
| `call_credentials_builder_methods` | 652 | `CallCredentials` builder (now `ConnectionCredentials`) | `alknet-core` |
| `remote_identity_none_is_load_bearing_not_defaulted` | 921 | `CallCredentials::new()` (now `ConnectionCredentials`) | `alknet-core` |
**Category B — TLS/verifier tests, move to `alknet-tls` (10 tests):** **Category B — TLS/verifier tests, move to `alknet-tls` (10 tests):**
@@ -529,28 +578,42 @@ use `spawn_dispatch(stub_connection())`.
**The `from_call.rs` tests (27 tests):** these use `CallConnection` **The `from_call.rs` tests (27 tests):** these use `CallConnection`
directly (constructed from `stub_connection()` or a mock), not directly (constructed from `stub_connection()` or a mock), not
`connect`. They're protocol-level and stay in `alknet-call` unchanged. `connect`. 25 are protocol-level and stay in `alknet-call` unchanged.
2 are removed: `build_forwarded_payload_sets_auth_token_when_provided`
and `streaming_forwarding_handler_sets_auth_token_when_provided` —
they test the `credentials_auth_token` `Some` path, which is removed
(the field was always `None`, never connected to `CallCredentials`).
The one reference to `connect()` is in a doc comment (line 76: The one reference to `connect()` is in a doc comment (line 76:
"the assembly layer calls `from_call` immediately after `connect()`") "the assembly layer calls `from_call` immediately after `connect()`")
— update the comment to say "after `AlknetClient::dial_*` + — update the comment to say "after `AlknetClient::dial_*` +
`spawn_dispatch`". `spawn_dispatch`".
**Net Phase 5 test impact:** 6 tests stay unchanged (Category A), 10 **Net Phase 5 test impact:** 4 tests stay unchanged (Category A), 2
tests move to `alknet-tls` in Phase 1 (Category B), 0 tests need tests move to `alknet-core` (Category A2), 10 tests move to
rewriting. The `from_call.rs` tests (27) stay unchanged. The prune `alknet-tls` in Phase 1 (Category B), 2 `from_call` dead-path tests
of `call_client.rs` is mechanical: delete `ClientError`, `connect`, removed. The `from_call.rs` tests: 25 stay unchanged, 2 removed. The
and all the TLS helpers (`build_*`, `select_*`, `load_*`, prune of `call_client.rs` is mechanical: delete `ClientError`,
`Ed25519SigningKey`, `RawKeyClientCertResolver`, `NoClientCertResolver`, `connect`, `CallCredentials` and its builder methods, and all the TLS
`FingerprintPinVerifier`); keep `CallClient` + `new` + `spawn_dispatch` helpers (`build_*`, `select_*`, `load_*`, `Ed25519SigningKey`,
unchanged; update imports. The test suite keeps the Category A tests, `RawKeyClientCertResolver`, `NoClientCertResolver`,
removes the Category B tests (moved in Phase 1), and updates the one `FingerprintPinVerifier`); keep `CallClient` + `new` +
doc comment. `spawn_dispatch` unchanged; update imports. Also remove `from_call`'s
`credentials_auth_token` dead path: the field on `OpSummary`, the
parameters on `make_forwarding_handler` /
`make_streaming_forwarding_handler`, and the `auth_token` parameter on
`build_forwarded_payload`. The test suite keeps the Category A tests,
removes the Category A2 tests (moved to core), removes the Category B
tests (moved in Phase 1), removes the 2 `from_call` dead-path tests,
and updates the one doc comment.
This is much simpler than the initial estimate of "~290 lines of test This is a larger prune than the initial estimate of "~140 lines of
restructuring." The actual test work is: move 10 tests to `alknet-tls` implementation + ~290 lines of test restructuring." The actual work:
in Phase 1 (adapted to the new API), keep 6 tests unchanged, update one delete `connect` + TLS helpers (~140 lines), delete `CallCredentials`
doc comment. The `connect` removal breaks zero tests because no test + builder methods (~50 lines), delete `from_call`'s
calls `connect`. `credentials_auth_token` dead path (~30 lines), move 10 tests to
`alknet-tls` (Phase 1), move 2 tests to `alknet-core`, keep 4 tests
unchanged, remove 2 `from_call` dead-path tests. The `connect` removal
breaks zero lib tests because no lib test calls `connect`.
## Resolved questions ## Resolved questions