# 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<ChannelId, RtcSession>
│   │   ├── 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 <http://localhost:8080/> 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 <crate>` instead of `cargo build` for type-check-only.
