Adversarial review F6 (Low, confirmed): Channel.created_at: Instant was
drafted 'for the idle timeout' (spec §5.1) but the timer keys off
RtcSession.last_rtp_rx (and now last_rtp_rx after F4), never creation
time. The field was never read; the Instant import it pulled is dropped
with it.
Spec §5.1 already reconciled in 8680f2b (struct sketch + comment).
Spec §4.5 already specified 'no RTP' (60 s); the contradiction with
the old 5-min lineage is closed.
Refactor phase — full test suite + clippy green; no behavior change.
- pub struct TapHandle(()) — zero-sized marker (Option<TapHandle> compiles
to a bool, no allocation). The live tap connection lives in the binary's
DashMap<ChannelId, TapConn>, keyed by the channel's existing ChannelId
(== wire session_id per spec §5.3).
- Channel grows pub tap: Option<TapHandle>. None until Connected, set on
Connected, cleared before state advances to Closed.
- State invariant: tap and ChannelState are tied (Connected+None=bug).
- Slice-1 backwards compatible: new field is Option<...>;
Multi-tap-per-channel is the future trigger for a TapId newtype (YAGNI).
Spec ref: 2026-06-28-slice-2-agent-tap-design.md §5.2, §6.
rutster-call-model is real-but-minimal (spec §5): the unifying leg
object the future API exposes. ChannelId is a Uuid newtype for
type-safety (the slice-1 worked example of the newtype pattern).
Channel is signaling-state only — media lives in rutster-media as a
leaf concern of the Channel, surfaced only when a second consumer needs
to observe it (spec §5.3). ChannelState matches the New→Connecting→
Connected→Closing→Closed flow from §5.4.