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.
135 lines
5.5 KiB
Markdown
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`) |