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).
6.9 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| acme-event-loop-test | ACME event-loop coverage — fake-directory integration test (U-1) | pending |
|
moderate | medium | component | implementation |
|
Description
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. 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:
- There is no "loop ends when the stream ends" — the
while let Some(event) = state.next().awaitloop innew_acmenever exits on its own. Do NOT assert that theJoinHandleresolves; such a test hangs until timeout. The original item 3 is removed. 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.
Work
- Drive a
DirCache-backedAcmeStateagainst a local fake directory: a stub HTTP server (tokio,std::net::TcpListeneron an ephemeral port — no new deps) that serves a directory JSON with no usable endpoints, forcing the error path through the real event stream. (Mechanically verified: rustls-acme's HTTP layer,async_web_client0.6.3, is plainasync_net::TcpStreamforhttp://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.) - Assert the log events fire. Two shapes, pick one (or both):
tracingtest subscriber capturing thewarn!/error!events (tracing-subscriber with a test layer — add as dev-dependency only), or- extract the event-mapping match into a helper fn taking the event, returning (level, message-class), and test the helper directly (no subscriber needed; cheaper, but the loop body itself stays uncovered — prefer the subscriber shape if the dev dep is acceptable).
- 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 itsJoinHandle— 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. - 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) andEventOk::DeployedCachedCert/CertCacheStore(ok) — reached by pre-seeding theDirCachedirectory. The cache file names are deterministic (caches/dir.rs:38-53):cached_account_{base64url(SHA256( contact-els…directory_url))}andcached_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_certwants 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.
- Keep
TlsErrorout of it: per ADR-002/ADR-006, ACME runtime errors are stream events, not error variants — the test asserts events, never aTlsError.
Verification
- 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-featuresgreen; default build unaffected- clippy/fmt/doc green
Acceptance Criteria
- Every reachable arm per work item 4 is executed by a test
(
Orderwarn,AccountCacheStore, the Load/Parse error arms,DeployedCachedCert/CertCacheStore);DeployedNewCert/NewCertParseare 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 (§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 (
TlsErrorscope boundary) - rustls-acme 0.12.1 sources (vendored):
state.rs:407-412(the never-endingStreamimpl),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
Agent fills this during implementation.
Summary
Agent fills this on completion.