Builds out the user-facing docs tree alongside the slice-1 build target. Kept the implementer's planned Task 7 'Slice 1 dev loop' README section untouched — these docs are the canonical destination for that pointer. - docs/QUICKSTART.md: 5-min path to 'hear the echo' (libopus install, cargo run, browser steps, troubleshooting, what's happening under the hood). - docs/DEVELOPMENT.md: dev loop — workspace layout, per-crate iteration, running tests, the 20 ms loop / 'drop + observe' rule, slice-1 boundaries (what NOT to add yet). - CONTRIBUTING.md (at repo root, conventional): trunk-based dev, CI gates, commit message style, atomic commits, code style + learner-facing documentation policy, terminology policy, PR workflow + review checklist, GPL-3.0-or-later license. - README.md: add a Quickstart pointer at the top, a Documentation table linking to every doc, and the slice-1 build-target status block.
7.7 KiB
Contributing
Thanks for considering a contribution to Rutster. This file is the short
form — read AGENTS.md for the full orientation any agent
(human, AI, hybrid) working in the repo should have.
Trunk-based development
- Branch from
mainfor any change. - Open a PR targeting
main. - CI gates (below) must pass before merge.
- Squash-merge to keep
mainlinear.
Never push directly to main. Branch protection is planned to
enforce this; until then, self-discipline.
Branch naming
Single-purpose branches named after the change:
slice-1-webrtc-loopback— long-running build-target branchdocs/quickstart— documentation additionsmedia/opus-roundtrip-fix— bug fix with crate scopeci/deny-toml— CI config
No strict scheme — short, descriptive, hyphen-separated.
CI gates
All four must pass before merge to main:
cargo fmt --check # formatting
cargo clippy -- -D warnings # lints (warnings = failures)
cargo test --all # all unit + integration tests
cargo deny check # licenses, advisories, bans, sources
CI runs on push + PR to main, matrix: latest stable + the MSRV pinned
in rust-toolchain.toml. The clippy + test jobs install libopus-dev
(the one system dependency, per PORT_PLAN §7).
If your change adds a new dependency, run cargo deny check locally
before pushing — a license conflict or duplicate-version ban will fail
CI, and it's faster to catch locally.
Commit messages
- Imperative mood: "Add X" / "Fix Y", not "Added X" / "Fixes Y".
- Subject ≤ 72 chars.
- Body wraps at 72, blank line between subject and body.
- Reference ADRs / specs by number when relevant:
ADR-0002,slice-1 spec §3.4. - Match the style of recent commits:
git log --oneline -10
Example shape (from the repo history):
media: PcmFrame + AudioSource/Sink + Opus codec pair
PcmFrame is the canonical tap format (16-bit mono @ 24 kHz, 480 samples
per 20 ms frame — ARCHITECTURE.md). AudioSource/AudioSink are the seam
step 2 splices the tap client into (spec §3.3); EchoAudioPipe is the
slice-1 wiring of that seam. OpusDecoder/OpusEncoder wrap the opus
crate's libopus FFI with hot-path match-and-continue.
Atomic commits
One logical change per commit. Doc ratifications, code, and tests each land as separate commits when practical. Don't bundle unrelated work.
A new feature + its tests can land in one commit if the test is part of the feature's correctness story (TDD). A refactor + a new feature should be two commits.
Never commit secrets
.gitignore covers .env*, *.pem, *.key. If a new secret pattern
appears in your work, extend .gitignore in the same commit.
Code style
See AGENTS.md "Code style" for the full guide. Highlights:
cargo fmtis the source of truth for formatting. Don't hand-format.clippy -D warningsis the lint bar. Fix the code, don't suppress with#[allow]unless the rationale is documented inline.snake_case(functions, variables, modules, crates),PascalCase(types),UPPER_SNAKE_CASE(constants).- Newtype wrappers over primitives for type-safety — e.g.
ChannelId(Uuid), not bareUuid. - Hot path (the 20 ms media loop): never
?-propagate. Match-and-continue. Dropped packet must not terminate the peer. - Cold path:
thiserror-derived error enums +?, converted to HTTP status codes at the axum boundary. - Never
unwrap()/expect()outside tests or const-init contexts.
Documentation comments (learner-facing — important)
This project overrides the default "no comments" convention. The user is learning Rust from this codebase. Code in slice 1 (and the spearhead steps) carries thorough educational comments:
//!module docs on everylib.rs/main.rs/ sub-module///item docs on every public struct / enum / fn / trait//inline comments on the mechanism, not the what — why this ownership pattern, whyArc<Mutex<>>vsArc<RwLock<>>, why anenumover a struct with akindfield, etc. Aim: a Rust learner reads the comment and learns a specific Rust concept they wouldn't have inferred from the code alone.
LEARNING.md at the repo root indexes "to learn concept X, read file Y"
pointers. Add to it when you introduce a new pattern worth surfacing.
This verbosity is a deliberate trade-off: more tokens now, compound educational value later. Once a pattern is established, later slices can be sparser on the well-trodden patterns.
Slice-1 boundaries — what NOT to add (yet)
If an agent (you, an AI pair, a contributor) proposes adding any of these in slice 1, the right answer is "no, see slice-1 spec §1.2":
- Dedicated timing thread for the media loop (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-driven playout kill (step 4)
- PSTN trunk / SIP client (step 5)
- Spend cap / abuse gate (step 6)
- Browser-based automated e2e tests / Selenium / Playwright (post-slice-1)
- Docker / compose (later rung)
- Event bus / Valkey / CDR emission (step 5)
- Transfer / park / pickup / barge features (escalation rung 2)
The spearhead depends on this sequencing. Adding things early breaks the ordering each step is its own proof.
Terminology policy
Avoid authoritarian / exclusionary terms in code, prose, identifiers, and endpoint names. Use equally-descriptive alternatives:
| Avoid | Use instead |
|---|---|
| police / policing (the verb) | enforce / gate / guard |
| master / slave | primary / replica, leader / follower, controller / worker |
| blacklist / whitelist | denylist / allowlist, blocklist / safelist |
| officer | operator / handler / controller |
| censor | suppress / filter |
Exception: protocol-convention names are kept verbatim when they come
from upstream specs or libraries — replacing them would hurt the
educational mapping to upstream docs. ICE (Interactive Connectivity
Establishment, RFC 8445) stays: it's the protocol name in str0m::ice,
RTCIceCandidate, and the cargo ecosystem.
See AGENTS.md "Terminology policy" for the full table.
Pull request workflow
- Branch from
main. - Make atomic commits per the guidance above.
- Push the branch + open a PR targeting
main. - CI runs: fmt, clippy, test, deny. All four must be green.
- Reviewer checks the diff against the spec / ADRs cited in the commit
message. New dependencies require scrutiny —
cargo deny checkenforces license + source bans, but reviewers should also sanity-check the dependency choice against the architecture. - Squash-merge once approved + green.
- Delete the branch post-merge (keeps the branch list tidy).
Reviewing
When reviewing a PR:
- Does it cite the relevant ADR / spec section? (For substantial changes.)
- Does it add anything from the slice-1 "what NOT to add" list? (Reject if so — refer to slice-1 spec §1.2.)
- Are educational comments present where a new pattern is introduced?
- Any
unwrap()/expect()outside tests? (Reject unless justified.) - Does the hot-path code use
?? (Reject — it must match-and-continue.) - Does the diff bundle unrelated work? (Ask for split commits.)
License
By contributing, you agree your contributions are licensed under GPL-3.0-or-later (ADR-0004). Strong copyleft in the Asterisk lineage.
If you contribute code with a different license header / SPDX expression
in a Cargo manifest, CI will reject it (cargo deny check licenses).
Don't introduce dependencies whose licenses conflict — check deny.toml
for the allow-list.