# 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.
