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

123 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](0002-north-star-and-fused-core.md) (the boundary this rule governs),
[ADR-0007](0007-trunk-rented-transport.md) (a green-zone call), [ADR-0005](0005-event-bus.md) (a
green-zone call), [ADR-0006](0006-ingress-posture.md) (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](0007-trunk-rented-transport.md)) 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](0005-event-bus.md) — 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](0007-trunk-rented-transport.md)) · 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](0007-trunk-rented-transport.md))
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](0002-north-star-and-fused-core.md) — fused per-call vertical + composable platform (the boundary this rule governs)
- [ADR-0007](0007-trunk-rented-transport.md) — rent the trunk (a green-zone classification)
- [ADR-0005](0005-event-bus.md) — Valkey (a green-zone classification)
- [ADR-0006](0006-ingress-posture.md) — out-of-tree adapters (arm's-length integration)