ADR-0007 — Trunk/PSTN strategy: rent the transport, no first-party SIP stack.
Supersedes ADR-0003 (Rust-native trunk SIP). The 2026-06 strategic-relevance
review stress-tested ADR-0003 against the 2026 market + solo-build realities
and it did not survive: a trunk SIP/media core is the highest-cost,
lowest-differentiation square on the board (the perceived-quality battle and
the buying criteria both live above the transport), and no production-hardened
pure-Rust SIP stack exists to stand on (ezk is bus-factor-1 / pre-1.0;
str0m self-describes as 'not for production' for SIP).
rutster owns no SIP stack. PSTN reach is rented transport in three layers:
1. Primary (demo, most users) — CPaaS raw-media fork (Twilio Media Streams,
Telnyx). Media-leg ingress, core-as-server (parallel to WebRTC ingress
per ADR-0006). Use the raw-audio fork, *not* managed Voice-AI products
that would consume the reflex loop.
2. Graduation (on-prem) — out-of-tree SBC (Kamailio/FreeSWITCH/drachtio +
rtpengine) B2BUAs carrier SIP into rutster as clean RTP/tap media,
outside the trust boundary.
3. Never — a first-party Rust trunk SIP stack. rsip/ezk stay off the
critical path.
The only things rutster owns in Rust: the call model, the reflex loop, the
agent tap. Everything that touches a carrier is rented (layer 1) or
out-of-tree (layer 2).
ADR-0008 — The FOB and the green zone: the build-vs-reuse doctrine. Names
the boundary criterion implicit across ADRs 0002-0007 as one mechanical rule
every contributor (human or agent) applies the same way.
- FOB (build in Rust) — admitted only if it passes one of: hot path,
security-constitutive, differentiating. Current FOB: media termination
(rutster-media on str0m) · real-time reflexes (VAD/barge-in/jitter/pacing)
· call model (rutster-call-model) · agent tap (rutster-tap) · spend/abuse
gate (rutster-spend) · control API + state/bus trait (rutster).
- Green zone (reuse at arm's length) — its own process/container/trust
domain, never in the FOB's address space. Admitted when it fails all
three FOB tests AND a trusted, actively-maintained project already does
it well. Current green zone: Valkey (ADR-0005) · carrier trunk (ADR-0007)
· agent brain · reference GUI · object storage · KMS · OTel collector ·
container supervisor.
- 'Actively maintained' is the load-bearing gate: excludes a first-party
Rust SIP stack and webrtc-rs; admits Valkey and str0m-for-WebRTC.
- When in doubt, default to green zone. The FOB earns its members.
Restates the memory-safety pillar precisely: the FOB is 100% memory-safe
Rust; the green zone is trusted battle-tested OSS kept outside the boundary
— not an over-claim that every byte is Rust.
ADR-0003's status is flipped to Superseded with a forward-pointer to ADR-0007.
Kept as the historical record of the 'own trunk SIP + media termination in
Rust, no SBC shield' plan; reversed under the strategic-relevance review.
7.4 KiB
ADR-0008 — The FOB and the green zone: the build-vs-reuse doctrine
- Status: Accepted
- Date: 2026-06
- Origin: 2026-06 strategic-relevance review — names the boundary doctrine implicit across ADRs 0002–0007 and makes it a mechanical rule every contributor (human or agent) applies the same way.
- Amends:
docs/ARCHITECTURE.md(adds the organizing metaphor),AGENTS.md(adds it to "key decisions") - Related: ADR-0002 (the boundary this rule governs), ADR-0007 (a green-zone call), ADR-0005 (a green-zone call), ADR-0006 (arm's-length out-of-tree adapters)
Context
Across ADRs 0002–0007 one criterion kept deciding calls without ever being named: what does rutster build and own, and what does it reuse? Fused vertical (0002), rent the trunk (0007), bundle Valkey (0005), the out-of-tree SBC (0006) — all the same judgment, applied case by case. A solo build cannot re-derive that judgment per decision, and multiple coding agents now work this repo and need one explicit, mechanical rule so they classify consistently.
The failure mode this prevents is not-invented-here: rebuilding mature, actively-maintained OSS inside the core and drowning the scarce solo-years in undifferentiated plumbing. The from-scratch Rust trunk-SIP stack (superseded by ADR-0007) was exactly that mistake.
Stated as one image: stop building a fortress; build a secure base of operations. Harden and own the core — the FOB (forward operating base). Operate through a trusted, actively-maintained OSS perimeter — the green zone. Leverage, not autarky.
Decision
Every capability is classified FOB or green zone.
FOB — build it, own it, in Rust
Memory-safe Rust, owned, fuzzed, inside the trust boundary — the surface you would certify (PCI / HIPAA / TCPA). A capability is admitted to the FOB only if it passes at least one of three tests:
- Hot path — it runs on the per-call real-time loop, where a GC pause / jitter / an extra network hop would degrade call quality. (media termination, jitter buffer, pacing, the reflexes.)
- Security-constitutive — it is the auditable boundary: the spend/abuse gate, the trust-domain edge, the audit surface. A 3-vendor chain structurally cannot enforce it. (spend gate, the boundary itself.)
- Differentiating — it is the thing no competitor ships and a reason rutster exists. (the reflex loop, the agent tap, the contact-center domain logic.)
The FOB may link mature OSS for internals — libopus (codec), str0m (sans-IO WebRTC), speexdsp
(DSP) — as trusted vendored dependencies. Linking a maintained library to implement an FOB
capability is not a violation: you don't rewrite a codec. What makes the capability FOB is that rutster
owns it, it passes an FOB test, and the wire-facing parser is memory-safe Rust. Any vendored dep that
parses hostile bytes is fuzzed.
Current FOB: media termination (rutster-media, on str0m) · real-time reflexes (VAD/barge-in,
jitter, pacing) · call model (rutster-call-model) · the agent tap (rutster-tap) · the spend/abuse
gate (rutster-spend) · the control API + the state/bus trait (rutster).
Green zone — reuse it, at arm's length
Trusted, actively-maintained OSS or an external managed service, reused at arm's length — its own process, container, or trust domain, never in the FOB's address space, so its failures cannot corrupt the core. A capability belongs here if it fails all three FOB tests and a trusted, maintained project already does it well.
Current green zone: Valkey (state store + bus, ADR-0005 — bundled in the all-in-one image, external in the modular layout) · the carrier trunk (CPaaS raw-media fork / out-of-tree SBC, ADR-0007) · the agent brain (OpenAI Realtime or a self-hosted STT/LLM/TTS) · the reference GUI (admin portal + call-flow builder — a pure API client) · object storage (recording/CDR durability) · KMS/Vault (secrets) · the OTel collector · the container supervisor (tini/s6).
The "actively maintained" gate is load-bearing
A green-zone dependency the FOB relies on must be healthy: maintained, released, production-grade, not bus-factor-1. This single gate is what:
- excludes a first-party Rust SIP stack (
ezk, bus-factor-1 / pre-1.0 — ADR-0007) andwebrtc-rs(perpetual re-architecture); - admits Valkey (healthy), and
str0mfor WebRTC (its maintained sweet spot) — while rejectingstr0mfor SIP (out of scope there, "not for production").
Re-audit periodically — a project can go cold (e.g. FreeSWITCH's OSS line). A green-zone dep that stops being maintained is a defect to resolve, not a fixture.
How to apply (every contributor, human or agent)
Before building a subsystem or adding a dependency, run the test:
- Does it pass an FOB test? (hot-path or security-constitutive or differentiating)
- No → green zone. Find the trusted, actively-maintained OSS/service and integrate it at arm's length (separate process / container / trust domain). Do not rebuild it in-core.
- Yes → build it in the FOB, in Rust. For internals a mature library already solves (codecs, DSP), link that library as a trusted vendored dep — don't rewrite it. Fuzz any vendored parser that touches hostile bytes.
- Is the OSS you're about to depend on actively maintained / production-grade? If not, it is not eligible for anything the FOB depends on — find another, or scope the need away.
When in doubt, default to green zone. The FOB earns its members; it does not collect them.
Consequences
- Positive: one mechanical rule, applied identically by every human and agent; the solo-years concentrate on the FOB (the only part that differentiates); the memory-safety claim becomes precise rather than an over-claim; retroactively consistent with ADRs 0002–0007.
- Negative: the green zone is a dependency-trust surface that must be re-audited ("actively maintained" can decay); arm's-length integration costs some glue versus pulling things in-process.
- Restated pillar: memory-safe = the FOB (100% Rust at the boundary that holds the call), with mature OSS trusted at arm's length around it — not a claim that every byte in the deployment is Rust (libopus, Valkey, the SBC are not).
Worked example — deployment packaging
The all-in-one container is the doctrine made physical: the FOB binary + bundled valkey-server
(green zone) + the reference GUI (green zone), supervised in one container; point it at a brain + CPaaS,
docker run, taking calls. The modular/compose layout splits the same green-zone services out to scale.
Same FOB binary underneath; only the state/bus backend seam differs (bundled vs external). (The
deployment topology itself may get its own ADR once that design closes.)