tasks: decompose phases 0-1 of crate extraction into implementation tasks
Phase 0 (core/connection-credentials): purely additive — add ConnectionCredentials + RemoteIdentity to alknet-core. No call crate changes. ~40 lines, zero breakage. Phase 1 (tls/*): greenfield alknet-tls crate in 5 tasks: - tls/crate-init: Cargo.toml, deps, module skeleton - tls/server-extract: TlsServerConfig + server TLS code from endpoint.rs - tls/client-extract: TlsClientConfig + client TLS code from call_client.rs - tls/tests: 32 TLS tests moved and adapted - tls/review-tls: phase gate review checkpoint All old code stays duplicated — purely additive phases. Prunes in 4-5.
This commit is contained in:
1 parent
e91d943857
commit
4ced71f44a
6 files changed
+815
No files matched your search
@@ -0,0 +1,177 @@
|
||||
---
|
||||
id: core/connection-credentials
|
||||
name: Add ConnectionCredentials + RemoteIdentity to alknet-core (purely additive)
|
||||
status: pending
|
||||
depends_on: []
|
||||
scope: narrow
|
||||
risk: low
|
||||
impact: component
|
||||
level: implementation
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Phase 0 of the crate extraction (per `docs/research/alknet-crate-extraction/findings.md`).
|
||||
Add `ConnectionCredentials` + `RemoteIdentity` to a new `crates/alknet-core/src/credentials.rs`.
|
||||
This is **purely additive** — core gains two small types, nothing else changes, no breakage.
|
||||
|
||||
`ConnectionCredentials` is the transport-level credential bundle (ADR-091) — it carries
|
||||
`tls_identity` + `remote_identity` (the two dimensions the dial consumes). It is the
|
||||
transport-level equivalent of `CallCredentials` (which lives in `alknet-call` and carries
|
||||
an additional `auth_token` field). `ConnectionCredentials` is what `alknet-client` (Phase 3)
|
||||
and `alknet-tls` (Phase 1) will consume — the dep graph is clean from the start, with no
|
||||
temporary dep on `alknet-call`.
|
||||
|
||||
`alknet-call` is **not touched** in this phase. Its `CallCredentials` + `RemoteIdentity`
|
||||
stay as-is. The call crate refactor (removing `CallCredentials`, importing from core,
|
||||
moving tests) happens in Phase 5 when the full prune is done. This keeps Phase 0 truly
|
||||
additive and avoids touching tests that may be removed later.
|
||||
|
||||
### Step 1: Create `crates/alknet-core/src/credentials.rs`
|
||||
|
||||
New file with two types:
|
||||
|
||||
```rust
|
||||
//! Transport-level credential bundle for outbound connections (ADR-091).
|
||||
//!
|
||||
//! `ConnectionCredentials` carries the two dimensions the dial consumes:
|
||||
//! the local node's TLS identity and the expected remote identity.
|
||||
//! It is transport-agnostic — consumed by `alknet-tls` (TLS setup) and
|
||||
//! `alknet-client` (dial).
|
||||
|
||||
use crate::config::TlsIdentity;
|
||||
|
||||
/// 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` for the remote (the known-peer case →
|
||||
/// fingerprint pin).
|
||||
///
|
||||
/// `remote_identity: None` is the **public X.509 endpoint** case: the local
|
||||
/// node has no `PeerEntry` for the remote, so there is no fingerprint to pin.
|
||||
/// Combined with an X.509 transport, `None` selects CA verification
|
||||
/// (`WebPkiServerVerifier`) per the verifier-selection rule in ADR-034 §3.
|
||||
/// Combined with an Ed25519 raw-key transport, `None` fails closed (raw-key
|
||||
/// remotes are always known peers — no CA to fall back to).
|
||||
///
|
||||
/// The `Option` is therefore load-bearing, not cosmetic: `Some(fingerprint)`
|
||||
/// means "pin this" (known peer), `None` means "trust the CA or fail"
|
||||
/// (unknown remote). An implementer must not default `remote_identity` to a
|
||||
/// placeholder value to "satisfy" the field — `None` is a real state that
|
||||
/// drives verifier selection.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemoteIdentity {
|
||||
pub fingerprint: String,
|
||||
}
|
||||
|
||||
/// Credentials for an outbound connection (ADR-091). All dimensions come from
|
||||
/// `Capabilities` (ADR-014), never from environment variables — see the
|
||||
/// No-Env-Vars Invariant in
|
||||
/// `docs/architecture/crates/call/client-and-adapters.md`.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ConnectionCredentials {
|
||||
/// The local node's TLS identity (RFC 7250 raw key or X.509), derived
|
||||
/// from the vault at startup.
|
||||
pub tls_identity: Option<TlsIdentity>,
|
||||
/// Expected fingerprint/cert of the remote node, stored as a capability.
|
||||
/// `Some` → fingerprint pin (known peer with a `PeerEntry`); `None` → CA
|
||||
/// verification for X.509 remotes, fail-closed for Ed25519 raw-key remotes
|
||||
/// (ADR-034 §2/§3). `None` is the public-X.509-endpoint state, not a
|
||||
/// missing field — must not be defaulted to a placeholder.
|
||||
pub remote_identity: Option<RemoteIdentity>,
|
||||
}
|
||||
|
||||
impl ConnectionCredentials {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_tls_identity(mut self, tls_identity: TlsIdentity) -> Self {
|
||||
self.tls_identity = Some(tls_identity);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_remote_identity(mut self, remote: RemoteIdentity) -> Self {
|
||||
self.remote_identity = Some(remote);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn connection_credentials_builder_methods() {
|
||||
let creds = ConnectionCredentials::new().with_remote_identity(RemoteIdentity {
|
||||
fingerprint: "SHA256:abc".to_string(),
|
||||
});
|
||||
assert_eq!(
|
||||
creds.remote_identity.as_ref().unwrap().fingerprint,
|
||||
"SHA256:abc"
|
||||
);
|
||||
assert!(creds.tls_identity.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_credentials_none_is_load_bearing_not_defaulted() {
|
||||
let creds = ConnectionCredentials::new();
|
||||
assert!(
|
||||
creds.remote_identity.is_none(),
|
||||
"ConnectionCredentials::new() must keep remote_identity as None (the load-bearing \
|
||||
public-X.509-endpoint state), not default it to a placeholder"
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Update `alknet-core/src/lib.rs`
|
||||
|
||||
Add `pub mod credentials;` and re-export the types:
|
||||
|
||||
```rust
|
||||
pub mod credentials;
|
||||
// ... existing modules ...
|
||||
|
||||
pub use credentials::{ConnectionCredentials, RemoteIdentity};
|
||||
```
|
||||
|
||||
### What does NOT change
|
||||
|
||||
- `alknet-call` — completely untouched. `CallCredentials` + `RemoteIdentity` stay as-is.
|
||||
- `endpoint.rs` — unchanged.
|
||||
- All existing tests — unchanged.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `crates/alknet-core/src/credentials.rs` exists with `ConnectionCredentials` + `RemoteIdentity` as specified
|
||||
- [ ] `ConnectionCredentials` has `tls_identity` and `remote_identity` fields (no `auth_token`)
|
||||
- [ ] `ConnectionCredentials` has `new()`, `with_tls_identity()`, `with_remote_identity()` builder methods
|
||||
- [ ] `alknet-core/src/lib.rs` has `pub mod credentials;` and re-exports both types
|
||||
- [ ] Unit tests for `ConnectionCredentials` builder and `None`-is-load-bearing invariant pass
|
||||
- [ ] `cargo test -p alknet-core` passes (all feature combos)
|
||||
- [ ] `cargo test --workspace` passes (no regressions)
|
||||
- [ ] `cargo clippy --workspace` passes with no warnings
|
||||
- [ ] `cargo fmt --check --workspace` passes
|
||||
|
||||
## References
|
||||
|
||||
- docs/research/alknet-crate-extraction/findings.md — Phase 0
|
||||
- docs/architecture/decisions/091-connection-credentials.md — ADR-091 (amended 2026-07-17)
|
||||
- docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md — ADR-034
|
||||
- crates/alknet-call/src/client/call_client.rs — current `CallCredentials` + `RemoteIdentity` definitions (reference for field shapes)
|
||||
|
||||
## Notes
|
||||
|
||||
> This is Phase 0 of the crate extraction — the smallest and most independent
|
||||
> phase. ~40 lines of new code in core, zero changes anywhere else. Purely
|
||||
> additive — `alknet-call` is not touched. The field name stays `tls_identity`
|
||||
> (not `local_identity`) to match the existing code and avoid unnecessary churn
|
||||
> — the rename can happen later if desired. `ConnectionCredentials` has no
|
||||
> `auth_token` field because `auth_token` is a per-request payload field, not a
|
||||
> transport-level credential (ADR-091). The call crate refactor (removing
|
||||
> `CallCredentials`, importing from core, moving tests) happens in Phase 5.
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Reference in new issue
Block a user