Two reconnect-eviction edges were logically handled but had no explicit
assertion:
- A graceful ConnEvent::Left must emit PeerLeft *immediately* (not wait on
the grace timer). New graceful_leave_evicts_immediately asserts the leave
lands well inside the grace window.
- After a grace-timer eviction scrubs the peer from seen_connected, a later
rejoin's initial dial must be a fresh first-dial, not a reconnect that
arms a new eviction. New rejoin_after_grace_eviction_dials_cleanly covers
this (distinct from the existing graceful-leave scrub path).
Also reorder the eviction task to scrub timers/seen_connected before
emitting PeerConnectionFailed, so an observer (or a redial racing the
eviction) always sees clean internal state — this also makes the new
rejoin-after-eviction test deterministic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>