Files
rutster/docs/adr/0008-fob-and-green-zone.md
opencode controller 272aa07acd adr: +0007 rent trunk transport +0008 FOB/green-zone doctrine; supersede 0003
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.
2026-06-29 20:26:24 -04:00

7.4 KiB
Raw Blame History

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 00020007 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 00020007 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:

  1. 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.)
  2. 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.)
  3. 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) and webrtc-rs (perpetual re-architecture);
  • admits Valkey (healthy), and str0m for WebRTC (its maintained sweet spot) — while rejecting str0m for 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:

  1. 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.
  2. 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 00020007.
  • 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.)

References

  • ADR-0002 — fused per-call vertical + composable platform (the boundary this rule governs)
  • ADR-0007 — rent the trunk (a green-zone classification)
  • ADR-0005 — Valkey (a green-zone classification)
  • ADR-0006 — out-of-tree adapters (arm's-length integration)