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

135 lines
5.5 KiB
Markdown

---
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<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`)