Adversarial review F1 (High) + F3 (Med): per the user-locked Option B
direction (slice-1 spec §3.4), one atomic change closing both findings.
F1 (input): feed Input::Timeout(now) at the top of every drive() tick.
str0m's internal clock (DTLS retransmit, ICE consent freshness RFC 7675,
RTCP scheduling) only advances via Input::Timeout — the previous loop
only fed Input::Receive, so sustained inbound silence (muted mic, hold,
brain thinking mid-step-3) froze the clock; consent checks stopped; the
browser tore the call down (~15-30 s) before the 60 s idle timeout fired.
F3 (output): drive() returns Option<Duration> = str0m's next-deadline -
now. The return was previously discarded via `let _ =` in
session_map::drive_all_sessions. Now the poll task sleeps
`min(per-session deadline, 10 ms)` between ticks: honoring the
deadline saves wakeups when str0m has nothing pending; the 10 ms cap
guarantees the outbound 20 ms encode tick fires on time.
TDD via the PacketIo harness + a new observability counter:
- session.timeouts_fed: u64 — increments each drive() that feeds
Input::Timeout. Doubles as the F1 regression test's seam and the
embryo of a real metric per AGENTS.md's OTel aspirations.
- RED: f1_drive_feeds_input_timeout_to_advance_str0m_clock asserted
timeouts_fed > 0; pre-fix the counter stayed at 0.
- GREEN: added Step 0 in drive() (Input::Timeout feed + counter
increment). Test passes.
- Refactor: session_map::drive_all_sessions returns Duration (was ());
poll task uses tokio::time::sleep instead of fixed
tokio::time::interval(10ms).