Files
alktls/docs/architecture/decisions/003-noq-replaces-quinn.md
T
glm-5.3-flash 8a15978c41 generation 5: review-impl — spec-conformance gate passed, API frozen
Checklist (all six PASS):
1. API surface == ADR-004 — every accessor signature verified verbatim
2. TlsError == ADR-002 — six variants, #[non_exhaustive], typed sources,
   AcmeConfig holds exactly the two config-mismatch cases
3. Invariants: all five server invariants + client 0-RTT half +
   fail-closed structure, each with a passing behavioral test at unit
   and integration level
4. Deltas vs extraction: all ADR-pinned; two surfaced divergences
   recorded as ADR amendments — zero un-pinned divergences remain
5. Feature hygiene: default = [] lean, tokio subset (no full),
   doc comments on public API, no inline // comments, no panics
6. Docs sync: ADR-002 + ADR-003 amendment notes; overview/server/client
   Draft → Reviewed; README carries the API-freeze lifecycle note

5 findings, all low severity, all resolved forward (table in task Notes)

Verification: cargo test (81), cargo test --all-features (92),
clippy -D warnings, fmt --check, doc --no-deps,
publish --dry-run — all green. API FROZEN for the alknet rewrite.
2026-09-10 15:05:01 +00:00

5.5 KiB

status, last_updated
status last_updated
accepted 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:

[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<noq::ServerConfig, TlsError> (feature-gated on noq) — noq::ServerConfig::with_crypto(Arc::new( noq::crypto::rustls::QuicServerConfig::try_from(inner)?)).
  • TlsClientConfig::for_noq(self) -> Result<noq::ClientConfig, TlsError> (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)