re-baseline the four pending remediation tasks against the ADR-007/008 tree
The tasks were decomposed (d7db6b1) before 49d4432/ac440f3 landed;
both commits touched exactly the areas the tasks reference. Fixes
grounded in verified sources (vendored rustls-acme 0.12.1, rustls-pemfile
2.2.0, rustls-native-certs 0.8.4) and a fresh cargo llvm-cov run:
- acme-event-loop-test: the termination assertion was impossible —
rustls-acme's Stream for AcmeState never yields None
(state.rs:407-412, poll_next_infinite + 2^16s backoff); the review's
U-1 exit-condition premise is withdrawn and corrected in place.
Replaced with timeout-bounded event collection, a reachable-arm
inventory (Order warn, AccountCacheStore, Load/Parse error arms,
DeployedCachedCert/CertCacheStore via deterministic DirCache file
pre-seeding), and an explicit mark for the full-fake-CA arms.
server.rs:135 flagged as unreachable dead code (delete or accept).
- coverage-cheap-closes: re-baselined per-line ground truth — original
groups 1 and 5 are already closed by the handshake suites; group 2's
TLS 1.3 half is covered, leaving the TLS 1.2 else-arm (client.rs:316);
new group added for VerifyPresentedCertVerifier::verify_tls12_signature
(server.rs:349-366, opened by ADR-008; required for the >=98% bar);
AcceptAnyCertVerifier refs moved to server.rs:468-475 with the stale
OQ-TLS-09 coordination caveat retired.
- docs-pin-c1-c4-n3-n4: N-4 re-scoped (the mechanism analysis already
lives in ADR-007 + the resolver doc block; what remains is a short
server-verifier note covering both verifiers); C-4 updated for
ADR-007's negotiation-earlier failure point; added the
FingerprintPinVerifier pop cross-ref update (post-ADR-008 the default
verifier does verify possession).
- config-validation-and-trivia: added the feature-gate mechanics note
for the C-3 test (a non-gated test passes vacuously under default
features); refreshed drifted line refs with a re-grep advisory.
- review 001: Status block records OQ-TLS-09/-10 resolutions; U-1
carries the termination correction; U-2 carries the supersession
note. ADR-008 gains the suite-number-to-test-name mapping.
Verification: taskgraph validate 14 tasks; cargo doc --no-deps
warning-free; all edits docs-only (no code paths touched).
This commit is contained in:
@@ -12,17 +12,36 @@ tags: [tests, acme, review-001, u1]
|
||||
|
||||
## Description
|
||||
|
||||
The ACME event-loop body (src/server.rs:94-133) is unreachable by
|
||||
The ACME event-loop body (src/server.rs:94-136) is unreachable by
|
||||
tests: 23 uncovered lines — every `EventOk`/`EventError` arm, the
|
||||
`debug`/`warn`/`error` mapping, and the "state machine ended" log are
|
||||
dead code as far as the suite can prove. The `acme_lifecycle` tests
|
||||
construct the config and assert spawn + ALPN + resolver wiring; the
|
||||
spawned task runs against a blackhole URL and its events are never
|
||||
observed. A refactor that drops or mislevels an arm (e.g.
|
||||
`EventError::Order` warn → error) lands green; the loop's exit
|
||||
condition (`state.next() == None` → "ACME: state machine ended") is
|
||||
untested — if upstream changes the stream's termination semantics,
|
||||
nothing notices.
|
||||
`debug`/`warn`/`error` mapping. The `acme_lifecycle` tests construct
|
||||
the config and assert spawn + ALPN + resolver wiring; the spawned task
|
||||
runs against a blackhole URL and its events are never observed. A
|
||||
refactor that drops or mislevels an arm (e.g. `EventError::Order` warn
|
||||
→ error) lands green.
|
||||
|
||||
**Correction to this task (2026-09-12, verified against the vendored
|
||||
rustls-acme 0.12.1 sources) — the review's §U-1 termination premise is
|
||||
wrong, and so was this task's original work item 3.** `rustls-acme
|
||||
0.12.1`'s `Stream for AcmeState` never terminates:
|
||||
`poll_next` is `Poll::Ready(Some(ready!(self.poll_next_infinite(cx))))`
|
||||
(state.rs:407-412) — it can never yield `None`. Order errors enter an
|
||||
exponential backoff (`Timer::after(1 << backoff_cnt)`, capped at 2^16
|
||||
seconds — state.rs:371-375) and retry forever. Two consequences:
|
||||
|
||||
1. **There is no "loop ends when the stream ends"** — the
|
||||
`while let Some(event) = state.next().await` loop in `new_acme`
|
||||
never exits on its own. Do NOT assert that the `JoinHandle`
|
||||
resolves; such a test hangs until timeout. The original item 3 is
|
||||
removed.
|
||||
2. **`src/server.rs:135` ("ACME: state machine ended") is unreachable
|
||||
dead code under this dependency.** It cannot be covered by any test
|
||||
through the real stream. Either delete the line in this task (and
|
||||
note the removal) or explicitly accept it as uncovered — do not
|
||||
leave an implementer to burn hours rediscovering that it is
|
||||
unreachable. (The review's §U-1 framing "if upstream changes the
|
||||
stream's termination semantics, nothing notices" described a
|
||||
termination semantics that does not exist.)
|
||||
|
||||
The acme feature's only runtime surface is this loop; it deserves one
|
||||
real integration test.
|
||||
@@ -33,7 +52,11 @@ real integration test.
|
||||
directory: a stub HTTP server (tokio, `std::net::TcpListener` on an
|
||||
ephemeral port — no new deps) that serves a directory JSON with no
|
||||
usable endpoints, forcing the error path through the *real* event
|
||||
stream.
|
||||
stream. (Mechanically verified: rustls-acme's HTTP layer,
|
||||
`async_web_client` 0.6.3, is plain `async_net::TcpStream` for
|
||||
`http://` URLs and self-drives via async-io 2's reactor thread —
|
||||
it works under a tokio runtime, and a plain-HTTP localhost stub is
|
||||
reachable.)
|
||||
2. Assert the log events fire. Two shapes, pick one (or both):
|
||||
- `tracing` test subscriber capturing the `warn!`/`error!` events
|
||||
(tracing-subscriber with a test layer — add as dev-dependency
|
||||
@@ -43,16 +66,48 @@ real integration test.
|
||||
directly (no subscriber needed; cheaper, but the loop body
|
||||
itself stays uncovered — prefer the subscriber shape if the dev
|
||||
dep is acceptable).
|
||||
3. Assert termination: with the fake directory erroring out, the
|
||||
spawned task's `JoinHandle` resolves (the loop ends when the
|
||||
stream ends) — pins the exit condition.
|
||||
4. Keep `TlsError` out of it: per ADR-002/ADR-006, ACME runtime errors
|
||||
3. **Bound event collection by time, never by stream end**: collect
|
||||
events under `tokio::time::timeout` (or a bounded channel + a
|
||||
drop-with-timeout teardown) and assert on what arrived. The
|
||||
spawned task never resolves its `JoinHandle` — see the correction
|
||||
above — so any "await the handle" shape hangs. Note: with
|
||||
`#[tokio::test]` (current-thread runtime) the spawned loop runs on
|
||||
the test thread, so a thread-local default tracing subscriber is
|
||||
visible to it; keep the test single-threaded or the subscriber
|
||||
captures nothing.
|
||||
4. Which arms are reachable, per rustls-acme 0.12.1's state machine
|
||||
(state.rs `poll_next_infinite` / `order`):
|
||||
- `EventError::Order` (warn) — fires against the erroring stub.
|
||||
- `EventOk::AccountCacheStore` — the first event, generated before
|
||||
any network I/O (the account key is generated and stored
|
||||
eagerly, state.rs:377-395); fires even against a dead address.
|
||||
- `EventError::AccountCacheLoad` / `CertCacheLoad` /
|
||||
`CachedCertParse` (error) and `EventOk::DeployedCachedCert` /
|
||||
`CertCacheStore` (ok) — reached by pre-seeding the `DirCache`
|
||||
directory. The cache file names are deterministic
|
||||
(`caches/dir.rs:38-53`): `cached_account_{base64url(SHA256(
|
||||
contact-els…directory_url))}` and `cached_cert_{base64url(
|
||||
SHA256(domain-els…directory_url))}`. A corrupt pre-seeded file
|
||||
gives the Load/Parse error arms; a valid PEM (an ECDSA-P256
|
||||
chain, PKCS#8 key first — `parse_cert` wants
|
||||
key-then-cert-chain, state.rs:192-214) gives the deployed/stored
|
||||
ok arms. The PEM can be generated with rcgen in-test (rcgen is
|
||||
already a dependency on the acme feature path via the server's
|
||||
self-signed helper — it is a [dependencies] member).
|
||||
- `EventOk::DeployedNewCert` / `EventError::NewCertParse` —
|
||||
reachable only through a *successful* order, i.e. a full fake
|
||||
ACME CA (newNonce/newAccount/newOrder/authorizations/finalize/
|
||||
certificate endpoints). Out of scope at this effort level; mark
|
||||
explicitly not-covered rather than silent.
|
||||
5. Keep `TlsError` out of it: per ADR-002/ADR-006, ACME runtime errors
|
||||
are stream events, not error variants — the test asserts events,
|
||||
never a `TlsError`.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] server.rs 94-133 covered under `cargo llvm-cov --all-features`
|
||||
- [ ] server.rs 94-136 covered under `cargo llvm-cov --all-features`
|
||||
(line 135 either deleted or explicitly accepted as uncovered,
|
||||
per work item 2's second consequence)
|
||||
- [ ] The test performs no real network I/O (binds localhost only)
|
||||
- [ ] `cargo test --features acme`, `--all-features` green; default
|
||||
build unaffected
|
||||
@@ -60,17 +115,27 @@ real integration test.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Every `EventOk`/`EventError` arm is executed by a test
|
||||
- [ ] The "state machine ended" termination path is asserted
|
||||
- [ ] Every reachable arm per work item 4 is executed by a test
|
||||
(`Order` warn, `AccountCacheStore`, the Load/Parse error arms,
|
||||
`DeployedCachedCert`/`CertCacheStore`); `DeployedNewCert`/
|
||||
`NewCertParse` are explicitly marked not-covered (full fake CA
|
||||
needed — out of scope)
|
||||
- [ ] No test awaits the event loop's `JoinHandle` (it never resolves)
|
||||
- [ ] The ACME feature's runtime surface is no longer
|
||||
refactor-fragile
|
||||
|
||||
## References
|
||||
|
||||
- docs/reviews/001-implementation-review.md §U-1, Part C
|
||||
- src/server.rs:61-139 (`new_acme` + the spawned loop)
|
||||
- docs/reviews/001-implementation-review.md §U-1, Part C (§U-1's
|
||||
termination premise is corrected — see this task's Description)
|
||||
- src/server.rs:64-145 (`new_acme` + the spawned loop)
|
||||
- tests/acme_lifecycle.rs (the existing construction-level tests)
|
||||
- ADR-006 (acme feature layout), ADR-002 (`TlsError` scope boundary)
|
||||
- rustls-acme 0.12.1 sources (vendored):
|
||||
`state.rs:407-412` (the never-ending `Stream` impl),
|
||||
`state.rs:371-375` (order-error backoff + retry),
|
||||
`state.rs:192-214` (`parse_cert` — key first, then ≥2 PEMs),
|
||||
`caches/dir.rs:38-53` (the deterministic cache file names)
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user