--- status: accepted last_updated: 2026-09-10 --- # ADR-003: The QUIC feature is `noq`, not `quinn` ## Status Accepted (2026-09-10) ## Context The extracted crate's `quinn` feature (`for_quinn()` accessors, quinn-gated wrap) predates the noq split: iroh has extracted its internal quinn fork into the standalone, published `noq` project (`noq` / `noq-proto` / `noq-udp`; noq 1.2.0 on crates.io), and iroh 1.1.0 is built on noq 1.2.0, re-exporting `noq::*` as public API. The alknet rewrite will standardize on noq for QUIC — both the bare QUIC transport and iroh's internal one — eliminating the duplicate QUIC implementations. Phase 0 verified the seam is API-compatible with quinn 0.11 (`docs/research/phase-0.md` §Prior art: noq): - `noq_proto::crypto::rustls::QuicServerConfig::try_from( rustls::ServerConfig)` / `QuicClientConfig::try_from( rustls::ClientConfig)` — same constructor shape, same `NoInitialCipherSuite` failure type, different crate path. - noq 1.2 pins `rustls ^0.23.33`; iroh 1.1 pins `rustls 0.23.33` — the same 0.23 line this crate pins. One rustls tree across all paths. - noq's `rustls-aws-lc-rs` feature selects the aws-lc-rs provider; iroh's `tls-aws-lc-rs` does the same on the iroh side. The ADR-084 posture composes unchanged. - noq's MSRV is 1.88 — the ecosystem floor (adopted 2026-09-10), so no MSRV tension remains. The `quinn` feature has no published consumer: alktls is pre-consumer, and the alknet rewrite (its only planned consumer) targets noq. Carrying both features would double the accessor surface with nothing to transition. ## Decision **The QUIC feature is `noq`.** The `quinn` feature never existed in a published alktls, so the rename is pre-consumer and free: ```toml [features] default = [] noq = ["dep:noq", "dep:noq-proto"] tcp = ["dep:tokio-rustls"] acme = ["dep:rustls-acme"] [dependencies] noq = { version = "1.2", optional = true, default-features = false, features = ["rustls"] } noq-proto = { version = "1.2", optional = true, default-features = false } ``` `default = []` — all transport features opt-in, matching the inherited alknet spec's feature-gate shape and AGENTS.md convention 9 (the default crate compiles lean; a consumer that never runs QUIC should not pull `noq` + `noq-proto`). Note the extracted code's `Cargo.toml` carries `default = ["quinn"]` — a silent deviation from the alknet spec that this port corrects rather than carries forward. The `noq` dep is enabled with `default-features = false, features = ["rustls"]` so noq's provider-selection features do not fight the crate's explicit provider posture; `noq-proto` rides along for the `NoqWrap` error source. - `TlsServerConfig::for_noq(&self) -> Result` (feature-gated on `noq`) — `noq::ServerConfig::with_crypto(Arc::new( noq::crypto::rustls::QuicServerConfig::try_from(inner)?))`. - `TlsClientConfig::for_noq(self) -> Result` (feature-gated on `noq`) — the client-side mirror. - `TlsError::NoqWrap(#[from] noq_proto::crypto::rustls::NoInitialCipherSuite)` (ADR-002). - There is no `for_quinn()`, no `quinn` dependency, no quinn feature. - iroh remains key-not-config (ADR-001): iroh's endpoint is fed the Ed25519 secret key; the `noq` feature does not attempt to serve iroh's TLS, and iroh does not appear in this crate's dependency tree. - The `tcp` feature (`tokio-rustls`) and `acme` feature are unaffected. Provider wiring on the QUIC path: the rustls configs this crate builds already carry the aws-lc-rs provider (every path constructs its own provider via `builder_with_provider`); noq's `rustls` feature consumes that provider from the config. ## Consequences **Positive:** - One QUIC implementation across the bare-QUIC and iroh transports; no duplicate quinn/noq stacks in the rewrite's binaries. - `TlsError` never has a dead `QuinnWrap` variant. - The crate's MSRV floor (1.88) is honest for the whole tree rather than feature-dependent. **Negative:** - If a future consumer needs quinn (none is known), that is a new feature alongside `noq` — additive, cheap, and only paid if real. - noq is young (1.2.0, first release Feb 2026); its API may churn faster than quinn 0.11's frozen line (accepted — n0-computer owns both noq and iroh, and the rewrite rides their stack; pin `noq = "1.2"` and bump deliberately). **Amendment (2026-09-10, from the Phase 2 implementation):** the pinned TOML block's `features = ["rustls"]` was written against the noq 1.2 API; the lockfile resolves `noq-proto 1.3.0`, where `ServerConfig::with_crypto(crypto)` — the single-arg constructor the `for_noq` accessor calls — is `#[cfg(any(feature = "aws-lc-rs", feature = "ring"))]` (the retry-token key lives in noq's `ring_like` module, which needs one of the two provider features). The `noq` dependency therefore ships with `default-features = false, features = ["rustls", "aws-lc-rs"]`. This does not reopen the provider decision: the config's internal provider is still the crate's explicit `rustls::crypto::aws_lc_rs::default_provider()` on every path (ADR-084 via ADR-003's provider paragraph — noq consumes the provider from the config), and `aws-lc-rs` (not `ring`) is the feature matching that posture. ## References - `docs/research/phase-0.md` §Prior art: noq — the verified seam facts - `/workspace/noq`, `/workspace/iroh` — the upstream clones inspected - ADR-001 — the inheritance baseline (quinn-era accessors inherited with deviations recorded here) - ADR-002 — `TlsError` (`NoqWrap`) - ADR-004 — accessor shapes (`&self` vs `self`)