Files
alknet/docs/architecture/questions/063-tlserror-shape.md
T
glm-5.2 43b8179304 docs(arch): TlsError shape — single enum, owned by alknet-tls (ADR-088, resolves OQ-63)
Grounded in the actual error-producing call sites (endpoint.rs server
side, call_client.rs client side) and the dependency-crate sources read
from the cargo cache (rustls 0.23.41, rustls-pemfile 2.2.0, rcgen 0.13.2,
quinn-proto 0.11.15, rustls-acme 0.12.1).

Decision: single #[non_exhaustive] enum, one variant per failure
category, owned by alknet-tls (not re-exported from core). Six variants:
CertLoad(io::Error), SelfSigned(rcgen::Error), Rustls(rustls::Error),
VerifierBuild(VerifierBuilderError), QuinnWrap(NoInitialCipherSuite)
[quinn-gated], AcmeConfig(String).

Three findings drove single-enum over thin wrapper: (1) for_quinn()
fails with NoInitialCipherSuite, not rustls::Error — a rustls::Error
wrapper cannot represent the for_quinn() failure; (2) rustls_pemfile::Error
is not a std::error::Error (no Display, no Error impl) so #[from] would
not compile — pemfile BufRead APIs return io::Error; (3)
WebPkiServerVerifier::build() returns VerifierBuilderError, not
rustls::Error — a thin wrapper cannot represent empty-CA-root-store as
a first-class failure.

Deliberately NOT variants: ACME EventError/OrderError (stream events,
logged not returned from new); unknown-raw-key fail-closed (handshake-
time rejection at dial time, not a config-construction error —
corrects OQ-63's original framing); provider init (infallible); resolver
construction (infallible).
2026-07-15 09:22:24 +00:00

5.3 KiB

OQ-63: TlsError Shape

  • Origin: docs/architecture/crates/tls/README.md (TlsError is referenced as the Result error type in TlsServerConfig::new, TlsClientConfig::new, for_quinn(), and the crate's public signatures, but is never sketched or defined); ADR-082 (same — TlsError in signatures, no shape); ADR-087 (extends the surface to TlsClientConfig::new — the client-side error variants).

  • Status: resolved (ADR-088)

  • Door type: one-way (the error type is the public API surface of alknet-tls; changing it after consumers exist is a breaking change to every assembly-layer call site)

  • Priority: high (an implementer cannot write the crate without deciding this; guessing produces divergent shapes — one thin rustls::Error wrapper vs a 10-variant enum with per-path context)

  • Impacts: Blocks alknet-tls implementation — both TlsServerConfig::new and TlsClientConfig::new reference TlsError in their signatures. Blocks the hub's assembly-layer wiring (which calls both). This is the next decision needed before the TLS crate can be implemented.

  • Resolution: Decided (ADR-088). TlsError is a single #[non_exhaustive] enum with one variant per failure category, owned by alknet-tls (not re-exported from core). The variants, grounded in the actual error-producing call sites and the dependency-crate sources (rustls 0.23.41, rustls-pemfile 2.2.0, rcgen 0.13.2, quinn-proto 0.11.15, rustls-acme 0.12.1):

    • Cert/key loading (X509: file read + PEM parse; SelfSigned: rcgen generation) — currently io::Error-wrapped in the core code.
    • rustls config construction (builder_with_provider / with_safe_default_protocol_versions / with_single_cert / with_cert_resolver) — currently rustls::Error-wrapped.
    • quinn wrap (QuicServerConfig::try_from(rustls::ServerConfig)) — the one path where for_quinn() can fail; currently io::Error::other(e)-wrapped.
    • ACME — rustls-acme has its own error types; the ACME task runs in the background and surfaces errors via events (logged, not returned from new), so new's ACME path may only need to cover "ACME feature not enabled but TlsIdentity::Acme configured" (currently an io::ErrorKind::Unsupported).
    • Client verifier construction (ADR-087) — TlsClientConfig::new builds a rustls::ClientConfig with ADR-034's verifier selection. Failure modes: verifier construction error (bad fingerprint format, CA store init failure), unknown-remote fail-closed (not an error to return — it's a Result::Err the caller gets for trying to connect to an unknown raw-key remote), provider init failure. These overlap with the server-side rustls-build errors but have client-specific context (verifier selection inputs).

    The open question was the granularity: a single TlsError enum with variants per failure category (cert-load, rustls-build, quinn-wrap, acme-disabled) vs a thin wrapper around rustls::Error / io::Error. Resolved: the single-enum shape. The thin wrapper is actively wrong here — three findings from the dependency-crate source drive the decision: (1) for_quinn() fails with NoInitialCipherSuite, a distinct type from rustls::Error — a thin rustls::Error wrapper cannot represent the for_quinn() failure; (2) rustls_pemfile::Error is not a std::error::Error (no Display, no Error impl), so pemfile's BufRead APIs return io::Error — a #[from] rustls_pemfile::Error would not compile; (3) WebPkiServerVerifier::build() returns VerifierBuilderError, not rustls::Error — a thin wrapper cannot represent "empty CA root store" as a first-class failure. The six variants: CertLoad(io::Error), SelfSigned(rcgen::Error), Rustls(rustls::Error), VerifierBuild(VerifierBuilderError), QuinnWrap(NoInitialCipherSuite) (quinn-gated), AcmeConfig(String). ACME EventError/OrderError are deliberately absent — they are stream events, logged not returned from new. The unknown-raw-key fail-closed is deliberately absent — it is a handshake-time rejection (dial time), not a config-construction error (new time). See ADR-088 for the full rationale, the variant definitions, and the "what is NOT a variant" list.

    Subsidiary question: does TlsError live in alknet-tls (owned by the crate that produces it) or is it re-exported from alknet-core? Resolved: alknet-tls, owned by the crate that produces it. alknet-core's EndpointError no longer has a TlsConfig variant after ADR-083 (the endpoint takes no TLS config); core does not need to know about TlsError. Re-exporting from core would invert the ownership (core re-exporting a type from a crate that depends on it).

  • Cross-references: ADR-088 (the resolution — single enum, owned by alknet-tls, six variants), ADR-082 (the extraction that introduces TlsError), ADR-083 (the endpoint refactor that removes EndpointError::TlsConfig, making TlsError the sole TLS error surface), ADR-087 (extends the surface to client-side variants), crates/alknet-core/src/endpoint.rs (the current io::Error-wrapping pattern the new type replaces), crates/alknet-call/src/client/call_client.rs (the current String-wrapping pattern on the client side)