Files
rutster/docs/DEVELOPMENT.md
Aaron D. Lee 0eb20b729a
Some checks failed
CI / fmt (push) Successful in 1m32s
CI / clippy (push) Successful in 1m31s
CI / test (1.85) (push) Successful in 3m57s
CI / deny (push) Has been cancelled
CI / test (stable) (push) Has been cancelled
docs(reviews/2026-07-03): P4 fix — sweep status staleness + re-aim fuzz/README (#11)
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-04 01:44:27 +00:00

196 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Development
The dev loop: how to iterate on the Rust workspace, run tests, and find
your way around the codebase.
> **Status:** Slices 13 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.