Files
rutster/LEARNING.md
opencode controller 411276bfde test(slice-2): integration test + Python echo brain + LEARNING pointers
- Integration test: smoke test against in-process EchoServer (hello
  handshake round-trip). Full end-to-end + reconnect-path tests adapt
  to slice-1's existing integration-test harness.
- examples/echo_brain/: Python reference brain (~80 lines, websockets lib).
  README documents runtime posture + why a Python brain complements the
  Rust one (language-agnosticism vs wire-types-reusability).
- LEARNING.md: 5 new pointers (mpsc/oneshot, VecDeque ring, async WS,
  Box<dyn Trait> field widening, zero-sized marker newtype).

Spec ref: 2026-06-28-slice-2-agent-tap-design.md §8.4, §8.5 #7.
2026-06-28 17:52:16 -04:00

4.8 KiB

LEARNING.md — to learn concept X, read file Y

This index maps a Rust concept you might be learning to the file where slice 1 makes the concept concrete. Each entry is a worked example you can read in cargo doc --open plus the source file itself.

Concepts + pointers

  • Newtype pattern (type-safety via single-field wrappers)crates/rutster-call-model/src/lib.rsChannelId(Uuid). The newtype stops us from mixing up a ChannelId with some future SessionId at the type-system level. Compile-enforced where a comment could only ask.

  • enum for closed state sets + exhaustive matchcrates/rutster-call-model/src/lib.rsChannelState (New → Connecting → Connected → Closing → Closed). Exhaustiveness checking forces every match to consider each state; adding a state later surfaces every site that needs handling.

  • Sans-IO pattern (no I/O inside the library; input via method calls, output via return values)crates/rutster-media/src/loop_driver.rs — the str0m poll loop. Rtc::handle_input takes a network packet as a struct argument, not from a socket the library owns; poll_output returns Transmit packets the caller sends. Fully testable without a network — str0m integration tests use this property to drive faster than realtime.

  • Trait design for extension points (a futures-compatible seam)crates/rutster-media/src/pcm.rs — the AudioSource / AudioSink traits. Slice 1 wires an EchoAudioPipe between them; step 2 swaps that for a real WSS tap client without touching RtcSession. The traits describe what the splice point does, not how it's filled.

  • Error enums with thiserror + hot-path match-and-continuecrates/rutster-media/src/lib.rs (MediaError) and crates/rutster-media/src/opus_codec.rs (OpusDecoder::decode returns Option<PcmFrame>). Cold path: thiserror-derived enum + ?. Hot path: match-and-continue, never ?, never panic — "drop + observe, don't crash" (spec §3.8).

  • Arc<Mutex<T>> vs Arc<RwLock<T>> — when each is rightcrates/rutster/src/session_map.rs. The RtcSession lives behind Arc<Mutex<...>> because every access mutates it (str0m's &mut self contract) — RwLock's read-mode would be useless. Comment on the struct explains the trade-off.

  • DashMap for sharded concurrent mapscrates/rutster/src/session_map.rs. DashMap shards its inner map so two handlers operating on different ChannelIds don't contend; HashMap wrapped in a single Mutex would serialize every access.

  • str0m 0.21's single-mutation invariantcrates/rutster-media/src/loop_driver.rs. Mutate (handle_input / Writer::write) → drain poll_output to Output::Timeout → next mutate. Violating this leaves str0m in an inconsistent state.

  • tokio graceful shutdown via signal handlerscrates/rutster/src/main.rs (shutdown_signal). Ctrl-C / SIGTERM drops the AppState; the AppState drops the DashMap; the DashMap drops every RtcSession. No in-flight call preservation in slice 1.

  • include_str! for embedding static assetscrates/rutster/src/routes.rs (include_str!("../static/index.html")). The HTML test client is compiled into the binary at build time — no separate file to ship, no disk IO to serve it.

  • mpsc + oneshot for cold-path task supervisioncrates/rutster/src/tap_engine.rs — how a spawned tokio task is supervised + cancelled via oneshot::Receiver.

  • VecDeque as a bounded playout ring with drop-oldest policycrates/rutster-tap/src/tap_audio_pipe.rs — why a manual ring (not mpsc) when the overflow policy is drop-oldest, not drop-newest.

  • Async WS connect + Sink/Stream traitscrates/rutster-tap/src/tap_client.rstokio_tungstenite::connect_async, WebSocketStream, the SinkExt/StreamExt extension traits, tokio::select! over inbound + outbound + close.

  • Box<dyn Trait + Send> field widening (the seam test)crates/rutster-media/src/rtc_session.rs — why the pipe field type changed from EchoAudioPipe to Box<dyn AudioSource + AudioSink + Send> so loop_driver's call sites are byte-identical (slice-2 §8.5 #6).

  • Zero-sized marker newtype for state flagscrates/rutster-call-model/src/lib.rsTapHandle(()) compiles Option<TapHandle> to a single bool; no runtime cost for the type-system marker.

How to read

  1. cargo doc --open — every module has a //! doc comment; the doc tree is the high-level map.
  2. Pick a concept above; open the named file. The first occurrence of each non-obvious pattern has a // comment explaining why.
  3. Cross-ref back to the spec sections cited inline (spec §3.8, ADR-0002, etc.) for the architecture-level rationale.