# Development The dev loop: how to iterate on the Rust workspace, run tests, and find your way around the codebase. > **Status:** Slices 1–3 are merged to `main`. Slice 4 (barge-in / VAD-driven playout > kill) is the active build target, in flight on the `slice-4-dev-a-reflex` + > `slice-4-dev-b-tap` branches. Everything below applies to the merged code on > `main`; to follow slice-4 in flight, check out either dev branch. > Implementation plan: [`docs/superpowers/plans/2026-07-01-slice-4-barge-in.md`](superpowers/plans/2026-07-01-slice-4-barge-in.md). --- ## Workspace layout Cargo workspace at the repo root, shaped to ADR-0002's fused per-call vertical. One binary crate + five library crates. ``` rutster/ ├── Cargo.toml # [workspace] + [workspace.dependencies] ├── deny.toml # cargo-deny config (licenses/bans/sources) ├── rust-toolchain.toml # pinned stable ├── LEARNING.md # index of "to learn concept X, read file Y" ├── crates/ │ ├── rutster/ # binary: axum signaling server + media driver │ │ ├── src/main.rs │ │ ├── src/session_map.rs # DashMap │ │ ├── src/routes.rs # HTTP routes │ │ └── static/index.html # browser test client │ ├── rutster-media/ # str0m WebRTC + Opus⇄PCM codec boundary │ │ ├── src/pcm.rs # PcmFrame, AudioSource/Sink traits, EchoAudioPipe │ │ ├── src/opus_codec.rs # OpusDecoder / OpusEncoder wrappers │ │ ├── src/rtc_session.rs # RtcSession (per-peer owner) │ │ └── src/loop_driver.rs # str0m poll loop │ ├── rutster-call-model/ # the Channel/Leg object embryo │ ├── rutster-trunk/ # stub until spearhead step 5 │ ├── rutster-tap/ # stub until spearhead step 2 │ └── rutster-spend/ # stub until spearhead step 6 └── fuzz/ # placeholder cargo-fuzz dir (real harnesses: step 5) ``` The three stub crates (`rutster-trunk`, `rutster-tap`, `rutster-spend`) exist to lock the ADR-0002 boundary shape without anticipating code. Each is a `lib.rs` with a `//!` module doc comment describing what will land there and when. Don't fill them in early — see [`AGENTS.md`](../AGENTS.md) "Slice-1 boundaries — what NOT to add." ### Dependency direction - `rutster` (binary) → `rutster-media`, `rutster-call-model` - `rutster-media` → `rutster-call-model` - `rutster-call-model` is a leaf (depends on nothing in the workspace) - The three stubs depend on nothing in slice 1 --- ## Build / test / lint commands The four CI gates — all must pass before merge. Run them locally before pushing: ```bash cargo fmt --check # formatting check cargo clippy -- -D warnings # lints (warnings = failures) cargo test --all # all unit + integration tests cargo deny check # licenses, advisories, bans, sources ``` To auto-fix formatting + lint warnings: ```bash cargo fmt # apply formatting cargo clippy --fix # apply lint suggestions cargo clippy --fix --all-features # apply across all features ``` ### Per-crate iteration When working on one crate, skip the rest for faster cycles: ```bash cargo test -p rutster-media # one crate's tests cargo test -p rutster-media -- --nocapture # see println! output cargo run -p rutster # run the binary cargo build -p rutster-call-model # type-check one crate cargo doc -p rutster-media --open # render one crate's docs ``` ### Render full API docs ```bash cargo doc --no-deps --open ``` Every module has `//!` module docs explaining what it does + why it exists in the architecture. `cargo doc` is genuinely useful here, not just ceremony — the user is learning Rust from this codebase and slice 1 carries thorough educational comments per spec §7. --- ## Running the binary ```bash cargo run -p rutster # listening on http://0.0.0.0:8080 ``` Open in a browser → click "Start call" → grant mic → hear yourself echo. See [`QUICKSTART.md`](QUICKSTART.md) for the full walkthrough + troubleshooting. Verbose tracing: ```bash RUST_LOG=rutster=debug cargo run -p rutster ``` Filter to one module: ```bash RUST_LOG=rutster_media::loop_driver=trace,rutster=info cargo run -p rutster ``` --- ## The 20 ms media loop & the "drop + observe" rule The hot path is the str0m poll loop in `crates/rutster-media/src/loop_driver.rs`. It runs every ~10 ms (tokio interval, slice-1 deviation per spec §3.4 — step 4 lands a dedicated timing thread). Error policy on the hot path: **never** `?`-propagate. Match-and-continue. A dropped packet must not terminate the peer. Policy: "drop + observe (log + counter), don't crash." The eventual fuzz harness (step 5) will test against this exact posture. Cold path (signaling, setup, request handlers) uses `thiserror`-derived error enums + `?` propagation, converted to HTTP status codes at the axum boundary. See [`AGENTS.md`](../AGENTS.md) "Error handling" for the full policy. --- ## Shipped vs. deferred Slice 1 proves the **media core** only: WebRTC termination + the Opus⇄PCM codec boundary. The tap, the brain, barge-in, the trunk, and the spend cap are all explicitly deferred — each lands in a subsequent spearhead step. The full "what's deferred + when it returns" table is in [`docs/superpowers/specs/2026-06-28-slice-1-webrtc-loopback-design.md` §1.2](superpowers/specs/2026-06-28-slice-1-webrtc-loopback-design.md). The short version — **don't add these in slice 1:** - ❌ Dedicated timing thread (step 4) - ❌ TLS on the HTTP signaling surface (step 5) - ❌ Authn / authz / multi-tenancy (step 6) - ❌ Trickle ICE (when NATs demand it) - ❌ The tap itself (step 2 — slice 1 only *pre-paves* the seam) - ❌ The brain / STT / LLM / TTS (step 3) - ❌ Barge-in / VAD (step 4) - ❌ PSTN trunk / SIP (step 5) - ❌ Spend cap (step 6) - ❌ Event bus / Valkey / CDR emission (step 5) - ❌ Transfer / park / pickup / barge features (escalation rung 2) - ❌ Browser automation e2e tests (post-slice-1) - ❌ Docker / compose (later rung) If you find yourself reaching for any of these, the right answer is "no, see slice-1 spec §1.2." --- ## Dev loop gotchas - **libopus system dependency.** The `opus` crate links system libopus via FFI. If you see `error: linking with cc failed: exit code`, install `libopus-dev` (Debian/Ubuntu) / `opus-devel` (Fedora) / `brew install opus` (macOS). See [`QUICKSTART.md`](QUICKSTART.md) for platform-specific commands. - **`cargo deny` first-time setup.** Run `cargo install cargo-deny --locked` once. `cargo deny check` then validates licenses, advisories, and duplicate-version bans. - **Editor IDE.** `rust-analyzer` is recommended — it's what the plan was written against. Open the repo root in your editor; `rust-analyzer` picks up `rust-toolchain.toml` and the workspace manifest automatically. - **Slow first build.** str0m + axum + tokio compile fresh on first build (~2 minutes). Incremental builds are fast. Use `cargo check -p ` instead of `cargo build` for type-check-only.