- ADR-001: inherit the alknet TLS design as the baseline; deviations recorded as alktls ADRs - ADR-002: TlsError ships the ADR-088 six-variant shape from day one (typed #[from] sources; NoqWrap; no string catch-all) - ADR-003: the QUIC feature is noq (iroh's extracted fork), pre- consumer rename; default = [] per the lean-crate convention (corrects the extracted code's default = ["quinn"]) - ADR-004: complete accessors — for_tcp_tls() adopted, rustls_config() adopted; server accessors borrow (&self), client accessors consume - ADR-005: identity + credentials + fingerprint types move into alktls; auth layer stays out - ADR-006: eight-module layout; seed tests + integration invariant pins (exact nine-scheme list, client enable_early_data) - specs: overview (transport picture, terminology), server.md (ACME lifecycle, invariants), client.md (verifier selection matrix, root- store fallback); open-questions.md promotes OQ-TLS-01..08 (all resolved at entry) - Cargo.toml: quinn feature -> noq (per ADR-003); AGENTS.md aligned Architecture review pass done: 0 critical, 2 major (ADR-002 AcmeConfig doc comment contradiction; ADR-003 unrecorded default deviation) and 8 minors all addressed; cross-references verified against alknet ADRs, rustls/noq/iroh sources. Verified: cargo test, test --all-features, clippy -D warnings, fmt --check, doc --no-deps
5.2 KiB
5.2 KiB
status, last_updated
| status | last_updated |
|---|---|
| accepted | 2026-09-10 |
ADR-004: Complete the accessors — for_tcp_tls(), rustls_config(), and borrow-vs-consume
Status
Accepted (2026-09-10)
Context
The alknet spec (ADR-082's API table and the crates/tls README)
pins four server-side accessors and three client-side accessors. The
extracted code implements a subset and diverges in shape:
| Accessor | Spec | Extracted code |
|---|---|---|
TlsServerConfig::new |
async, (&TlsIdentity, &[Vec<u8>]) |
same |
TlsServerConfig::for_quinn |
&self |
self (consumes) |
TlsServerConfig::for_tcp_tls |
-> TlsAcceptor, infallible |
missing — callers wrap tokio_rustls::TlsAcceptor::from(Arc::new(cfg.rustls_config.clone())) themselves |
TlsServerConfig::rustls_config |
&self -> &ServerConfig |
missing — the field is pub(crate) |
TlsClientConfig::new |
sync, (&ConnectionCredentials, &[u8]) |
same |
TlsClientConfig::for_quinn |
self (consumes) |
self |
TlsClientConfig::into_rustls_config |
self (consumes) |
same |
The missing server accessors force the assembly layer to reach into
crate internals (pub(crate) field access is impossible for external
consumers) or re-derive the acceptor wrap. The self-consuming
server accessor prevents the literal ADR-082 story — one
TlsServerConfig feeding both a QUIC endpoint and a TCP+TLS acceptor —
without contortions.
Decision
The public API surface is the spec's surface, with borrow-vs-consume decided per accessor:
impl TlsServerConfig {
pub async fn new(identity: &TlsIdentity, alpns: &[Vec<u8>])
-> Result<Self, TlsError>;
/// `&self` — the inner rustls config is Clone (Arc-shared
/// resolvers); one TlsServerConfig can feed a noq endpoint AND a
/// TCP+TLS acceptor without contortions.
#[cfg(feature = "noq")]
pub fn for_noq(&self) -> Result<noq::ServerConfig, TlsError>;
/// Infallible — `TlsAcceptor::from(Arc<ServerConfig>)` cannot
/// fail. Feature-gated on `tcp`.
#[cfg(feature = "tcp")]
pub fn for_tcp_tls(&self) -> tokio_rustls::TlsAcceptor;
/// Borrow the inner config for transport wrappers the crate does
/// not cover.
pub fn rustls_config(&self) -> &rustls::ServerConfig;
}
impl TlsClientConfig {
pub fn new(credentials: &ConnectionCredentials, alpn: &[u8])
-> Result<Self, TlsError>;
/// Consumes — one dial = one config build; a `TlsClientConfig` is
/// not reused across dials.
#[cfg(feature = "noq")]
pub fn for_noq(self) -> Result<noq::ClientConfig, TlsError>;
/// Consumes — the TCP+TLS dial wraps the returned config in a
/// `TlsConnector` itself.
pub fn into_rustls_config(self) -> rustls::ClientConfig;
}
Rationale per accessor:
- Server accessors take
&self. The ADR-082 story is "one identity, N transports": the assembly layer builds oneTlsServerConfigper endpoint type and hands it to every transport that endpoint serves.&selfplus the Clone inner config makes the multi-transport sharing direct; the extractedself-consuming shape is a vestige of the quinn-only extraction era (the TCP path was re-derivable only because the field happened to bepub(crate)in the same workspace). - Client accessors consume
self. ATlsClientConfigis built per dial (dial_quic,dial_tcp_tlsbuild fresh configs per ADR-089's pattern); nothing reuses it. Consuming makesinto_rustls_configzero-cost (no clone behind the scenes) and keeps the API honest about reuse. for_tcp_tls()is adopted (Phase 0 gap #2). It is one line, infallible, and the spec is unambiguous; leaving it out would keep the de facto "callers wrap the acceptor" shape and force the rewrite to duplicate it.rustls_config()is adopted for any transport wrapper beyondfor_noq/for_tcp_tls(iroh does not need it — key-not-config — but the escape hatch costs nothing and stays true to the spec).newstaysasync fnfor API uniformity with the ACME path (which spawns the state-machine task); the non-ACME paths have no await point (sync file I/O) — this is the spec's recorded posture, and the uniform signature is worth more than the await-free purity.
Consequences
Positive:
- The assembly layer builds configs and transports without reaching into crate internals or hand-rolling acceptor wraps.
- The API freeze matches the spec the rewrite was specced against — zero translation for the rewrite's consumers.
- The multi-transport story (one config → noq + TCP+TLS) is directly expressible.
Negative:
&selfserver accessors require the inner rustls config to stayClone-able — already true and load-bearing (Arc-shared resolvers); a future config shape that is not Clone would break the accessor contract (acceptable: Clone is structural to the sharing story).
References
- alknet ADR-082 API table and the
crates/tlsREADME §Architecture — the spec surface this ADR adopts docs/research/phase-0.md§Gaps #2, OQ-TLS-03/OQ-TLS-04 — the gap and the accessor-shape question- ADR-002 —
TlsError(thefor_noqfailure variant) - ADR-003 — the
noqfeature