docs: ratify vision revision across README/ARCHITECTURE/PORT_PLAN + ADRs 0002-0006

Folds the 2026-06-26 vision-revision pressure-test into the canonical
docs. The spec moves from Proposed to Ratified; the decisions it produced
land in the docs it said it would amend, with ADRs recording each
load-bearing reversal.

- README.md: reframe north star (AI-era contact-center engine, not
  Asterisk successor); persona; revised pillars (add data-ownership,
  demote WASM, promote spend-control); update 'what it is/isn't'.
- ARCHITECTURE.md: replace three-plane framing with fused per-call
  vertical + composable horizontal platform; remove control<->media
  hot-path gRPC hop; make the agent tap the central interface; add
  DX spine + GUI-as-API-client + k8s declarative/operational model.
  Also: 'too slow to police' -> 'too slow to enforce' (terminology).
- PORT_PLAN.md: recharacterize as capability checklist (not template);
  graduate contact-center capabilities to first-class domain; Rust-native
  trunk SIP rows; WASM demoted; thin-slice + capability ladder phasing.
- ADR-0001: marked Superseded by ADR-0003.
- ADR-0002 (new): north star + fused per-call core.
- ADR-0003 (new): Rust-native trunk SIP, no SBC shield.
- ADR-0004 (new): GPL-3.0-or-later license.
- ADR-0005 (new): Valkey as event bus + state store.
- ADR-0006 (new): WebRTC-first ingress; SIP endpoint deferred.
- vision-revision spec: status -> Ratified 2026-06-26.
This commit is contained in:
adlee-was-taken
2026-06-28 09:33:29 -04:00
parent 23cec14977
commit 075e984fb5
10 changed files with 674 additions and 109 deletions

View File

@@ -1,8 +1,13 @@
# ADR-0001 — SIP strategy: native Rust core behind a Kamailio + rtpengine edge
- **Status:** Accepted
- **Status:** ~~Accepted~~ **Superseded** (2026-06)
- **Date:** 2026-06
- **Supersedes:** the `🔌 Edge/FFI (pjproject)` disposition in PORT_PLAN.md §1
- **Supersedes:** the `🔌 Edge/FFI (pjproject)` disposition in PORT_PLAN.md §1 (historical)
- **Superseded by:** [ADR-0003](0003-sip-rust-native-trunk.md) — Rust-native trunk SIP, no SBC shield
> **Superseded.** Kept as the historical record of the layered "own the Rust parser, front with
> Kamailio + rtpengine" plan. Reversed under the AI-era contact-center scope (the device interop
> tail collapses to a few documented trunk providers). See [ADR-0003](0003-sip-rust-native-trunk.md).
## Context

View File

@@ -0,0 +1,93 @@
# ADR-0002 — North star & fused per-call core
- **Status:** Accepted
- **Date:** 2026-06
- **Origin:** [vision-revision](../superpowers/specs/2026-06-26-vision-revision-design.md) §14, 67
- **Amends:** `README.md`, `docs/ARCHITECTURE.md`, `docs/PORT_PLAN.md` (the founding identity framing)
## Context
The founding docs anchored rutster to "memory-safe successor to Asterisk." A pressure-test
of that anchor:
1. The PBX category is **already consolidated/eaten** — by UCaaS + Teams Phone. Feature-parity
with a 1.18M-LOC C monolith is a solo-multi-year death-march into a shrinking market.
2. Asterisk's durable value is its **place in the world** — *the self-hostable engine technical
builders stand contact centers on* (Vicidial, GOautodial, a thousand integrator builds) —
*not* its protocols or its channel/bridge/dialplan architecture.
3. AI is actively disrupting telephony (voice agents), opening a self-hostable + AI-native slot
that no incumbent fills: LiveKit has no contact-center domain; cloud CCaaS isn't
self-hostable and bolts AI on; Vapi/Retell put your calls/data on their infra; Vicidial/FreePBX
are Asterisk-era PHP+C.
"Spiritual successor to Asterisk" therefore means successor to its **role**, not its **form**.
## Decision
### 1. North star
rutster is **the open-source engine for building the AI-era contact center** — AI-native, not AI
bolted-on; self-hostable; owning the contact-center domain; memory-safe Rust. It is a
**framework/engine, not a turnkey product** (Asterisk never tried to *be* Five9; it was the thing
people built on). The moment it chases CCaaS GUI-parity, it dies. Asterisk's capability map
(the PORT_PLAN) is a **completeness checklist**, explicitly **not** an architecture template.
### 2. Persona
The 2006 Linux-nerd-who-stood-up-Asterisk-for-an-SMB's modern equivalent: the **CLI/IDE/AI-comfortable
self-hosting technical builder** who runs Claude Code in a terminal, versions everything in git,
and self-hosts on principle. *Not* the no-code admin clicking a flow-designer canvas. This
narrowing is deliberate for an engine, and it liberates the authoring layer — the audience codes
and has an AI pair, so there is no "dumb it down" tax (see §8 of the vision-revision).
### 3. Architecture — fused per-call vertical + composable horizontal platform
The core owns the **per-call vertical** end-to-end as one deterministic, auditable trust domain:
```
carrier SIP trunk ─► media termination (RTP/SRTP + local real-time reflexes)
├─► clean audio tap ──► external agent brain (STT/LLM/TTS)
└─► in-boundary spend / abuse gate
```
**Horizontal platform** concerns are services *around* the core, independently scaled: number
inventory, billing rollup, analytics, multi-region orchestration, the management API, and the
agent brain itself.
This **replaces the founding three-plane framing**:
- The **control↔media gRPC hop on the per-call hot path** is **removed**. Fusion where fusion
buys determinism + security + simplicity.
- The **spend gate** and the **agent tap** — which founding docs externalized (a `☁️ Service` and
the app plane) — are pulled **into** the boundary, because they are **constitutive** of the wedge
(a runtime can't structurally enforce spend/abuse control or barge-in if the media is elsewhere).
- It is neither monolith nor pure microservices: **fused where fusion buys the wedge; composable
where independent scaling matters.**
### 4. Pillars re-weighted
| Pillar | Change | Why |
|---|---|---|
| Memory-safety | ▲ stronger | Now **literally true at the wire** once the C SBC edge is dropped ([ADR-0003](0003-sip-rust-native-trunk.md)) |
| Security-as-product | ▲ stronger | The single auditable boundary *is* the moat; compliance (PCI/HIPAA/TCPA) is a buying criterion |
| In-boundary spend / abuse control | ▲ promoted | From "table-stakes service" to **constitutive** — structurally impossible for a 3-vendor stack |
| **Data ownership** | ✦ new | Calls + training data never leave the operator's infra — the self-host wedge *and* the ML-loop fuel |
| WASM plugin sandbox | ▼ demoted | Out of the core story. The agent **tap** is the extension point; ops-simplicity wants one binary. Softly retained as a *candidate* for community call-flow/routing plugins |
## Consequences
- **Positive:** sharper target than a consolidating PBX category; the wedge is a *coherent
combination* (no-GC determinism + one auditable boundary + ops-simplicity) no one-competitor
matches; the authoring layer can be powerful (the audience codes + has an AI pair).
- **Negative:** narrows TAM deliberately (an engine, not a product); must hold the line against
scope creep toward Five9-parity (the GUI-as-pure-API-client discipline is a scope guard); the
agent brain is necessarily external, so the real-time edge lives in the **local reflexes**
(VAD/barge-in, jitter, pacing) + the *whole* boundary, not the brain round-trip.
## References
- [ADR-0003](0003-sip-rust-native-trunk.md) — Rust-native trunk SIP (removes the C edge this reframe assumes)
- [ADR-0005](0005-event-bus.md) — Valkey as bus + state store
- [ADR-0006](0006-ingress-posture.md) — WebRTC-first ingress

View File

@@ -0,0 +1,76 @@
# ADR-0003 — SIP strategy: Rust-native trunk SIP, no SBC shield
- **Status:** Accepted
- **Date:** 2026-06
- **Supersedes:** [ADR-0001](0001-sip-strategy.md)
- **Origin:** [vision-revision](../superpowers/specs/2026-06-26-vision-revision-design.md) §5
## Context
ADR-0001 layered the SIP strategy: own the Rust parser, but front the public edge with a
proven **Kamailio + rtpengine** SBC to rent the "20-year device/carrier interop tail." That tail
is real for a generic PBX — thousands of desk-phone quirks, NAT behaviors, carrier glare.
Reconsidered under the AI-era contact-center scope ([ADR-0002](0002-north-star-and-fused-core.md)),
the tail **collapses**: rutster talks to a few **documented, cooperative SIP-trunk providers
(Telnyx, Bandwidth, Twilio SIP)**, IP-allowlisted, not thousands of far-end desk-phone UAs. An
inbound/outbound SIP-trunk client against cooperative carriers is tractable in `rsip` / `ezk`,
bounded by carrier documentation, not device quirks.
The C SBC shield, meanwhile, directly contradicts the wedge:
- It puts **C at the most-exposed seam** (the public internet) — gutting the memory-safety
headline *at the one place it matters most*.
- It **terminates media twice** — rtpengine at the edge, rutster's media plane inside —
breaking "terminate media once" / "one auditable boundary."
## Decision
Own trunk SIP **and** media termination directly in the Rust core; IP-allowlist the handful of
trunk providers. **No Kamailio + rtpengine shield. No pjproject FFI.**
- Parser: `rsip` (message types/parsing). Sans-IO transaction/dialog/core: the `ezk` family.
No hostile bytes ever hit a C parser. **Fuzzed.**
- The memory-safety thesis becomes **literally true at the wire**: hostile bytes hit a fuzzed
Rust parser first, at the edge of *our* trust boundary.
- Interop surface is **bounded by carrier docs** (a finite, knowable set), not the unbounded
device tail ADR-0001 rented the shield for.
### Scope boundary — this ADR is about *trunk* SIP, not *endpoint* SIP
Inbound SIP **endpoint** registration (desk/soft phones: `REGISTER`, BLF/MWI, DTMF variants,
per-device NAT/provisioning) is a **different axis** and is **deferred** — it re-imports the
unbounded device-interop tail this ADR deliberately closes. Human-participant ingress is
**WebRTC**. See [ADR-0006](0006-ingress-posture.md).
## Topology
```
hostile internet trusted core
───────────────► (carrier trunk) ──► rutster control + media plane
(SIP trunks, (native Rust SIP parser +
far-end UAs) transaction/dialog core,
owned end-to-end here)
```
WebRTC ingress ships **first** and is unaffected (WebRTC signaling is app-defined, not SIP), so
first-call never blocks on SIP.
## Consequences
- **Positive:** memory-safety headline *literally true* at the wire; one media-termination point;
no C operational dependency (no Kamailio/rtpengine config/deploy/expertise); no pjproject
license/threading/`unsafe` entanglement; the trunk client is bounded by carrier docs, not device
quirks.
- **Negative / cost:** we own a (bounded) SIP-trunk client early; carrier-specific interop must be
maintained as trunks are added.
- **Mitigation:** WebRTC-first ordering means first-call doesn't block on SIP; the thin-slice
steps 14 (media core → tap → brain → barge-in) all land before step 5 (real PSTN trunk).
- **Graduation:** unlike ADR-0001, there is no shield to retire — the question is only how the
trunk client matures in production.
## References
- [ADR-0001](0001-sip-strategy.md) — superseded (the layered Kamailio+rtpengine + Rust-core plan)
- [ADR-0002](0002-north-star-and-fused-core.md) — the fused-vertical reframe this SIP decision enables
- [ADR-0006](0006-ingress-posture.md) — WebRTC-first ingress; SIP endpoint deferred

48
docs/adr/0004-license.md Normal file
View File

@@ -0,0 +1,48 @@
# ADR-0004 — License: GPL-3.0-or-later
- **Status:** Accepted
- **Date:** 2026-06
- **Closes:** the license open-decision carried in the founding docs
## Context
The founding docs (and the [vision-revision](../superpowers/specs/2026-06-26-vision-revision-design.md)
open-decisions list) left license open. rutster is a self-hosted engine whose competitor set
([ADR-0002](0002-north-star-and-fused-core.md)) includes cloud CCaaS that could *wrap-and-resell*
it as a managed service — the classic strong-copyleft concern.
## Decision
**GPL-3.0-or-later.**
Strong copyleft (requires source-sharing on binary conveyance), OSI-clean, modernized one notch
over Asterisk's GPLv2+. `SPDX-License-Identifier: GPL-3.0-or-later`.
## Alternatives considered
- **AGPL-3.0-or-later** — *considered.* AGPL additionally closes the network-use / SaaS loophole:
GPLv3 §13 states network use is **not** conveyance (so a hosted service triggers no
source-sharing obligation); AGPL §13 fixes that, compelling source disclosure for network-served
use. Given that cloud CCaaS is a named competitor, AGPL would harder-prevent a vendor from
hosting rutster-as-managed-CCaaS without contributing back.
- **GPL-3.0-or-later was chosen** in favor of simplicity and lower downstream-embedding friction
(operators and tooling/library authors integrating rutster avoid AGPL's network-trigger), while
remaining strong copyleft against proprietary forking of the engine itself.
## Trade-off accepted deliberately
A cloud competitor *can* host GPL rutster and owe source only on binary distribution, which for a
hosted service may be nothing. The license is the **floor**, not the moat: the wedge — self-host
+ data-ownership + one auditable boundary + operational simplicity + data-ownership as a buying
criterion — is the actual reason operators self-host rather than wrap.
If cloud-wrap becomes a demonstrated threat, **re-evaluate AGPL-3.0-or-later** (this ADR's
"or-later" clause permits that transition cleanly, since GPL-3.0-or-later is a strict subset of
AGPL-3.0-or-later for recipients).
## Consequences
- **Positive:** OSI-clean strong copyleft in the Asterisk lineage; compatible with the project's
dependencies (Valkey BSD-3-Clause per [ADR-0005](0005-event-bus.md); Rust-ecosystem MIT/Apache crates).
- **Negative:** the network-use loophole is open **by design choice** — mitigated by the wedge, not
the license.

View File

@@ -0,0 +1,62 @@
# ADR-0005 — Event bus & state store: Valkey
- **Status:** Accepted
- **Date:** 2026-06
- **Closes:** the event-bus open-decision carried in the founding docs (NATS vs. Kafka vs. Redis Streams)
## Context
The fused per-call core ([ADR-0002](0002-north-star-and-fused-core.md)) needs:
- a **bus** for cross-service control events (replacing Asterisk's internal Stasis bus for
anything crossing the boundary),
- a **KV state store** replacing `astdb` + sorcery / realtime config state,
- **presence sets** for MWI/BLF signaling aggregation.
The founding docs listed the choice as NATS vs. Kafka vs. Redis Streams.
## Decision
**Valkey** — the Linux-Foundation BSD-3-Clause fork of Redis 7.2.4 (wire-protocol-compatible drop-in).
### Why not "Redis"
Redis relicensed in March 2024 to **RSALv2 + SSPLv2****not OSI-open-source**, and license-incompatible with this project's posture (FOSS-clean + GPL-3.0-or-later per [ADR-0004](0004-license.md)).
Recommending "Redis" undercuts the data-ownership pillar ([ADR-0002](0002-north-star-and-fused-core.md)) and the clean-license story. Valkey is *Redis-the-thing* with a compatible license, maintained by the community (AWS, Google, Oracle, et al.).
### Why Valkey fits the wedge specifically
- **Collapses bus + state store + presence into one dependency.** A solo operator runs one process
with `compose up`. NATS is a cleaner pure-bus but doesn't also replace `astdb`/sorcery; Kafka is
structural overkill at self-hosting scale and violates the one-binary / operational-simplicity
pillar.
- One tool, three roles:
- **streams + consumer groups** → CDR/CEL/analytics pipeline fanout and replay,
- **pub/sub** → presence / MWI / BLF signaling,
- **KV** → state store (config + runtime state replacing `astdb`/sorcery).
### Alternatives
- **Kafka — rejected.** Structural overkill at self-hosting scale; operational weight violates
the operational-simplicity pillar.
- **NATS — retained as a config-pluggable alternative.** An operator outgrowing Valkey (notably
needing NATS JetStream's durable streaming model at larger scale) can swap it in. The bus
backend seam stays **pluggable** at the config boundary, not architecture-load-bearing.
## Constraints (load-bearing, not preferences)
1. **The 20ms media loop never rides the bus.** Media timing stays in-core on dedicated timing
threads ([ADR-0002](0002-north-star-and-fused-core.md) §7; vision-revision §7). The bus carries
control / cross-service events only — not latency-pinned media — so Valkey's sub-ms local
latency is plenty.
2. **The bus is NOT the source of truth for billing- or call-loss-critical state.** Valkey
persistence (RDB/AOF) is async-ish — fine for transport / replay / fleeting retention, **wrong**
for "the CDR that proves what we billed." CDR and recordings emit **durably to object storage**
in their own services; the bus only *flows* events into that pipeline and lets services *react*.
## Consequences
- **Positive:** one OSS dep for bus + state + presence; self-hostable and license-clean; pluggable
to NATS later without architecture changes; aligns with operational-simplicity.
- **Negative:** Valkey is "good enough" at each role, not best-of-breed for each — accepted
deliberately for the ops-simplicity pillar.

View File

@@ -0,0 +1,65 @@
# ADR-0006 — Ingress posture: WebRTC-first; SIP endpoint deferred
- **Status:** Accepted
- **Date:** 2026-06
- **Related:** [ADR-0003](0003-sip-rust-native-trunk.md) (trunk SIP), [ADR-0002](0002-north-star-and-fused-core.md) (fused vertical)
## Context
The fused per-call vertical ([ADR-0002](0002-north-star-and-fused-core.md)) needs an answer for
**human-participant ingress** — agents and supervisors joining/taking calls. The thin-slice first
proof starts at WebRTC loopback (vision-revision §10). The question: which protocols carry humans
into calls, and is inbound **endpoint** SIP (desk/soft phones) in scope?
## Decision
### 1. WebRTC is the first-party human-participant ingress
Agents and supervisors use the browser as the softphone: SSO in, one peer-connection,
DTLS-SRTP + ICE handled by the browser, **zero device-provisioning tail**. This is also the modern
hosted UX. Escalation (capability rung 2) is a UX gesture: an agent clicks *take this call* in the
dashboard → a WebRTC leg joins → audiohook/barge handoff. Writes itself onto the audiohook primitive
(PORT_PLAN) and shares step-1 infrastructure (WebRTC ships first regardless).
### 2. SIP trunk client = core (per ADR-0003)
Bounded: a few documented, IP-allowlisted, cooperative carriers.
### 3. Inbound SIP *endpoint* registrar = deferred / community edge, NOT core
`REGISTER` from Polycoms/Yealinks/Zoiper, BLF/MWI subscribe-notify, RFC2833-vs-SIP-INFO DTMF,
per-device NAT keepalives and provisioning quirks are **deferred out of the core**:
- **The real reason** is not "SIP is hard" — it's that an inbound endpoint server **re-imports the
unbounded device-interop tail** [ADR-0003](0003-sip-rust-native-trunk.md) deliberately closed.
ADR-0003's "tail collapses" argument survives only because rutster talks to *documented carriers*,
not *thousands of desk-phone UAs*. Opening an endpoint server reopens that seam.
- In an SSO/browser-agent contact center the desk-phone demographic has collapsed to niche ops
floors already served by dated OSS (FreePBX/Vicidial). Wrong place to spend a solo multi-year
build — a pointlessly-and-diverting distraction in a modern hosted-UX/SSO world.
- **Don't burn the bridge:** if an operator genuinely needs desk phones, the right home is an
**optional, out-of-tree SBC-style adapter** (the Kamailio-flavored thing ADR-0003 chose not to put
in the core) fronting the public API or B2B-SIP'ing into rutster — community-owned, **outside the
trust boundary** so its tail can't compromise the wedge.
### 4. Core interop perimeter is deliberately bounded
**SIP trunk client + WebRTC ingress.** Nothing else inbound.
## Security seam — tap ≠ ingress
Two distinct extension points with **opposite** security postures; never unified into one model:
- **Tap** (agent brain): **egress**, core-as-**client**, **no inbound tap port** on the core. The
boundary dials out to the brain. (See ARCHITECTURE.md, "Agent tap.")
- **Ingress** (humans): **inbound**, core-as-**server**, with SSO + RBAC + per-tenant scoping.
Legitimately a server surface — that's fine, it's auth'd; the "core-as-client" move was
specifically about the egress brain tap, where *egress* is the dangerous direction.
## Consequences
- **Positive:** first-call via WebRTC ships before any SIP; escalation is a UX click not a new
protocol; the core's interop surface stays bounded; no device-provisioning tail in the trust
boundary.
- **Negative:** desk-phone-only operators are not served by the core — accepted, the community
adapter path exists.