Files
rutster/CONTRIBUTING.md
adlee-was-taken 4c0898cd49 docs: QUICKSTART + DEVELOPMENT + CONTRIBUTING, polish README index
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.
2026-06-28 12:32:12 -04:00

216 lines
7.7 KiB
Markdown

# Contributing
Thanks for considering a contribution to Rutster. This file is the short
form — read [`AGENTS.md`](AGENTS.md) for the full orientation any agent
(human, AI, hybrid) working in the repo should have.
---
## Trunk-based development
1. Branch from `main` for any change.
2. Open a PR targeting `main`.
3. CI gates (below) must pass before merge.
4. Squash-merge to keep `main` linear.
**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 branch
- `docs/quickstart` — documentation additions
- `media/opus-roundtrip-fix` — bug fix with crate scope
- `ci/deny-toml` — CI config
No strict scheme — short, descriptive, hyphen-separated.
---
## CI gates
All four must pass before merge to `main`:
```bash
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:
```bash
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`](AGENTS.md) "Code style" for the full guide. Highlights:
- `cargo fmt` is the source of truth for formatting. Don't hand-format.
- `clippy -D warnings` is 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 bare `Uuid`.
- 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 every `lib.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, why `Arc<Mutex<>>` vs `Arc<RwLock<>>`, why an
`enum` over a struct with a `kind` field, 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`](AGENTS.md) "Terminology policy" for the full table.
---
## Pull request workflow
1. Branch from `main`.
2. Make atomic commits per the guidance above.
3. Push the branch + open a PR targeting `main`.
4. CI runs: fmt, clippy, test, deny. All four must be green.
5. Reviewer checks the diff against the spec / ADRs cited in the commit
message. New dependencies require scrutiny — `cargo deny check`
enforces license + source bans, but reviewers should also sanity-check
the dependency choice against the architecture.
6. Squash-merge once approved + green.
7. 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.