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

@@ -2,55 +2,160 @@
## The reframe
Asterisk's power was: *one process, load any `.so`, wire anything to anything in the
dialplan.* That composability is the thing to match — but it does **not** require a
1.2M-LOC monolith. Rutster delivers the same "build anything" through a different
substrate:
Asterisk's power was: *one process, load any `.so`, wire anything to anything in the dialplan.*
rutster delivers the same "build anything" through a different substrate — but **the engine is
the goal, not the PBX**. Where Asterisk matched composability through a 1.2M-LOC monolith + in-process
modules, rutster matches it through:
- a **small hardened core** (media + signaling glue + call model),
- a **WASM plugin runtime** for safe, third-party-extensible logic,
- a **small hardened core** owning the per-call vertical end-to-end (media + signaling + call model
+ reflexes + spend gate) as one deterministic, auditable trust domain,
- a **clean audio tap** as the external brain's extension point (safely extensible by people you
don't fully trust, because the brain is out-of-process and the core authoritatively controls
playout),
- **declarative routing** as data for the common path,
- a **programmable API** (REST/gRPC + event stream) modeled on Asterisk's ARI.
- a **programmable API** (REST/gRPC + event stream) modeled on Asterisk's ARI — and that ARI lineage
is where the dialplan *goes*: external services reacting to call events.
More extensible than Asterisk, because extensions are safe to run from people you
don't fully trust.
More extensible than Asterisk, because extensions are safe to run from people you don't fully trust
— and they can be AI brains, not just `.so` files.
## Three planes
## The fused per-call vertical + composable horizontal platform
### Control plane (stateless-ish, horizontally scalable)
The ARI-style resource API (channels / bridges / endpoints / recordings / playbacks)
over REST + gRPC + a WebSocket/SSE event stream. Registrar, routing, auth. This is
where "the dialplan" disappears — replaced by declarative routing + external services
reacting to call events (the Twilio / ARI-Stasis model). Asterisk's
`rest-api/api-docs/*.json` is a reusable spec for the resource model.
The core owns the **per-call vertical** end-to-end as one deterministic, auditable trust domain:
### Media plane (stateful, latency-pinned, scaled separately)
RTP/SRTP termination, mixing/bridging (softmix), transcoding, record/playback. A
controllable media node driven over gRPC by the control plane. Built on the Rust
WebRTC media ecosystem (`str0m` sans-IO design, `webrtc-rs`). **The media datapath
stays tight** — do not over-decompose it across service hops; latency and failure
modes compound.
```
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
```
### App plane (your services + plugins, outside the core)
IVR, queues, voicemail, dialers, custom routing — driven via the API, deployed
independently. WASM plugins for in-call logic that needs to run close to the core;
microservices for stateful/business/billing logic.
**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.
## Cross-cutting
This **replaces the founding three-plane framing**:
- **Event bus** (NATS / Redis Streams / Kafka) replaces Asterisk's internal Stasis bus
for cross-service events; a lightweight in-core dispatcher handles intra-core.
- **State store** replaces `astdb` + realtime/sorcery.
- **Security is load-bearing, not a row:** memory-safe fuzzed parsers, TLS/SRTP
mandatory, deny-by-default routing + toll-fraud engine, mTLS gRPC admin (no AMI),
WASM tenant isolation, SBOM + KMS/Vault for secrets.
- **Observability:** OpenTelemetry traces that follow a single call across
signaling → media → app services.
- 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 — 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).
- Neither monolith nor pure microservices: **fused where fusion buys the wedge; composable where
independent scaling matters.** ([ADR-0002](adr/0002-north-star-and-fused-core.md))
## Inside the boundary
- **Trunk SIP termination** — Rust-native; IP-allowlisted cooperative carriers; no C SBC.
([ADR-0003](adr/0003-sip-rust-native-trunk.md))
- **Media plane** — RTP/SRTP termination, mixing/bridging (softmix), transcoding, record/playback.
Built on the Rust WebRTC ecosystem (`str0m` sans-IO design, `webrtc-rs`). The media datapath
stays tight — do not over-decompose it across hops or into the bus; latency and failure modes
compound. Dedicated timing threads for the 20ms loop, **never the shared tokio pool**.
- **Local real-time reflexes** — VAD-driven barge-in / playout kill, half-duplex gating, jitter
buffer, pacing. These live in-core because the brain round-trip is too slow to enforce them. The
tap carries the *results* of reflexes to the brain, not the responsibility.
- **Call model** — the unifying `Channel`/leg object (signaling + media state); call-control
primitives (transfer/park/pickup, answer/originate); the ARI-modeled resource API surfaced as
REST/gRPC + a WebSocket/SSE event stream. "The dialplan" disappears — replaced by declarative
routing + external services reacting to call events (the ARI/Stasis model).
- **In-boundary spend / abuse gate** — spend caps, pacing caps, deny-by-default routing,
rate-limits, toll-fraud pattern detection. **Co-located with trunk termination** so a runaway
brain structurally cannot exceed spend or pacing — it doesn't hold the wire.
- **Audiohook primitive** — tap/inject/volume behind recording, ChanSpy/whisper/barge, and
human-agent escalation (rung 2: an agent takes over a call via WebRTC + audiohook handoff).
## Agent tap (the central interface)
The single most important interface in the system — where audio leaves the boundary to an external
brain. **Presumptive shape** (to harden against the thin-slice first proof, not a decided ADR yet):
- **Bidirectional WSS stream per call**, small versioned typed framing protocol. WSS (not gRPC)
because the consumer is a Python script / a browser / an OpenAI-Realtime-style speech-to-speech
API for which event-framed WSS is already the de-facto protocol — making the OpenAI adapter a thin
shim, not a gRPC-bridge project.
- **Core-as-client; brain-as-server. Always.** The audited boundary dials out to the brain (to
OpenAI, to a self-hosted brain process on localhost). **No inbound tap port on the core.** One
design choice deletes a whole attack class ("something connected to my tap port and read audio"),
simplifies firewall posture, and gives clean symmetry — every connection is initiated by the thing
you trust.
- **One canonical PCM format at the tap** — 16-bit, mono, a speech-model-friendly rate (24kHz
default, 16kHz fallback). The core terminates the codec soup on the PSTN side and exposes one clean
format. Brains never touch codecs.
- **Core-authoritative playout.** The brain *proposes* audio (`AudioOut` frames, advisory); the
core *disposes* — owning the playout buffer + the VAD that kills TTS on caller speech + the
half-duplex/pacing caps. A brain that can push audio straight to the PSTN wire is a brain that can
overlap, flood, or exfiltrate-via-timing. **Make `AudioOut` advisory / core-authoritative.**
> **Tap ≠ ingress — opposite security postures; never unified.** The tap is **egress**,
> core-as-**client**, no inbound port. Human ingress is **inbound**, core-as-**server**, with SSO +
> RBAC + per-tenant scoping — legitimately a server surface, fine because auth'd. The "core-as-client"
> move is specifically about egress, where the dangerous direction is. See
> [ADR-0006](adr/0006-ingress-posture.md).
For a brain that wants to be a **full media peer** (an existing SIP IVR, another media server, a
WebRTC client), the path is **media-leg ingress** (a real participant that speaks RTP/SRTP itself),
*not* the tap. Forcing a clean-audio brain into a media re-termination re-introduces a second media
termination, contradicting "terminate media once."
## Cross-cutting (outside the per-call boundary)
- **Event bus** — **Valkey** ([ADR-0005](adr/0005-event-bus.md)): streams + consumer groups for
CDR/CEL/analytics fanout; pub/sub for presence/MWI/BLF; KV for the state store (replacing `astdb`
+ sorcery). **The 20ms media loop never rides the bus.** The bus is **not** the source of truth
for billing- or call-loss-critical state — CDR/recordings emit durably to object storage; the bus
only *flows* events into that pipeline and lets services react. Pluggable to NATS later at a
config seam.
- **State store** — Valkey KV, replacing `astdb` + realtime/sorcery.
- **Observability** — OpenTelemetry traces that follow a single call across the boundary and out
to services.
- **Secrets / KMS** — Vault/KMS; no plaintext credentials in config.
- **Supply chain** — `cargo-deny`, SBOM generation, reproducible builds; continuous fuzzing of every
wire parser (SIP/SDP/RTP).
## DX spine — developer-first authoring
Headless and API-complete. The persona authors via **code + config-as-text**: git-versioned,
CLI-driven, IDE-native, AI-assistant-friendly (typed, schema'd, LSP-friendly). **Terraform/Rails
for call centers, not Squarespace.**
- **The AEL lesson** (DCAP author's): *better isn't enough.* AEL was superior to `extensions.conf`
and still lost — it arrived after the muscle memory had set. An authoring layer must win **on
contact** and meet people where their muscle memory is. In 2026 that's **code + an AI pair**, not
a config syntax or a visual canvas.
- **"Boom" + swiss-army-knife, reconciled:** an opinionated **batteries-included reference distro**
(`compose up`, point a trunk at it, start taking calls) on top of a **composable framework** (build
anything in code). Home Assistant model, not raw dialplan.
- **Call-flow authoring is a first-class design surface.** The AI-era twist: part of the old
dialplan **dissolves into the agent** (the model improvises the conversation). The authoring layer
becomes the **routing, escalation, and business scaffolding *around* an AI that writes half the
flow at runtime** — genuinely new design space, not "dialplan 2.0."
## GUI & extension architecture
**The GUI is a pure API client, never an insider** — FreePBX↔Asterisk, modernized.
- **Not a plugin in the core.** A web GUI must not share an address space / lifecycle / attack
surface with the latency-pinned media engine inside the one secure boundary.
- **A separate application.** The **official reference GUI** ships in the batteries-included distro
(so "boom" includes a usable UI) but holds **no privilege a third party couldn't get.** The
ecosystem can build rival GUIs.
- **Discipline:** the official GUI is built **only** on the public API — no backdoors. That
guarantees API completeness and prevents FreePBX's leaky "don't hand-edit config or it'll clobber
you" coupling.
- **Config-as-code vs. GUI-mutation tension → the Kubernetes model.** Declarative desired-state
(config-as-code, git, reconciled by the engine) *plus* an API/CLI/GUI for live operational state
and ad-hoc actions, all through **one API**, single source of truth. The GUI is the *dashboard*,
not a side-channel. *Manifests + kubectl + dashboard, for call centers.* Bonus scope guard: the GUI
can only surface what the API already does, so it can't drag the engine toward Five9-parity.
## Biggest technical risk
The **SIP stack****decided in [ADR-0001](adr/0001-sip-strategy.md)**: own the Rust
parser from day one (the security thesis depends on it), front the public edge with a
proven **Kamailio + rtpengine** SBC to absorb the interop tail, and grow the native Rust
transaction/dialog core behind that shield. No pjproject FFI. Everything else builds on
the existing Rust media ecosystem.
**The SIP stack****decided: Rust-native trunk SIP** ([ADR-0003](adr/0003-sip-rust-native-trunk.md)).
Own the parser from day one (the security thesis depends on it); the *trunk* interop surface is
bounded by carrier docs (a few IP-allowlisted providers), so no C SBC shield is rented. The single
biggest risk item, de-risked by maturing behind WebRTC-first ordering — first-call never blocks on SIP.
Everything else builds on the existing Rust media ecosystem; the agent **brain** is external by
necessity and reached via the tap.

View File

@@ -1,9 +1,12 @@
# Rutster — Asterisk → Rust Capability Port Plan
# Rutster — Asterisk Capability Checklist
This is the spine of the project. It walks the Asterisk subsystem surface and assigns
every capability a **disposition** in the rutster architecture, with a one-line
rationale. The crate/service boundaries fall out of the "Core / Service" rows; the
threat model hangs off the "attack surface" of each row.
This is the **capability checklist**, not the architecture spine. It walks the Asterisk
subsystem surface as a completeness audit — *"what must a telephony system handle"* — and
assigns every capability a **disposition** in the rutster architecture, with a one-line
rationale. We keep the checklist; we **reject** the Asterisk channel/bridge/dialplan model
as the architecture (see [ADR-0002](adr/0002-north-star-and-fused-core.md)). The crate/service
boundaries fall out of the "Core / Service" rows; the threat model hangs off the "attack
surface" of each row.
Grounded against the Asterisk 22.10.1 tree (~1.18M LOC C/H).
@@ -20,12 +23,19 @@ Grounded against the Asterisk 22.10.1 tree (~1.18M LOC C/H).
## Design rules (the heuristics behind every row)
1. Hot media path & the call model → **Core**. Trusted, fast, always-on.
2. Anything that benefits from isolation or third-party extension → **Plugin**.
3. Stateful, independently-scaled, or business/billing logic → **Service**.
4. Mature C that's a tar pit to rewrite (SIP) → **Edge/FFI**, with a pure-Rust ambition.
1. Hot media path & the call model → **Core**. Trusted, fast, always-on. Inside the fused
per-call vertical.
2. Anything that benefits from isolation or third-party extension → **Plugin** *(now softened —
WASM demoted out of the core story per [ADR-0002](adr/0002-north-star-and-fused-core.md); the
agent **tap** is the primary extension point for in-call logic).*
3. Stateful, independently-scaled, or business/billing logic → **Service**. Around the core,
horizontal platform.
4. ~~Mature C that's a tar pit to rewrite (SIP) → **Edge/FFI**, with a pure-Rust ambition.~~
*Reversed for SIP by [ADR-0003](adr/0003-sip-rust-native-trunk.md): Rust-native trunk SIP, no
C SBC shield. FFI retained only for mature codec/DSP/EQ libraries (libopus, speexdsp, WebRTC APM).*
5. Legacy hardware / dead protocols → **Drop**.
6. Security & multi-tenancy are cross-cutting — never a row, always a property.
6. Security & multi-tenancy are cross-cutting — never a row, always a property. **Spend / abuse
control is constitutive**, pulled into the boundary, not externalized.
---
@@ -33,14 +43,15 @@ Grounded against the Asterisk 22.10.1 tree (~1.18M LOC C/H).
| Asterisk subsystem | Module(s) | Disp. | Rationale |
|---|---|---|---|
| SIP signaling | `chan_pjsip` + `res_pjsip*` (48 mods) | 🔌 Edge → 🦀 Core | **Decided (ADR-0001):** public SIP edge = **Kamailio + rtpengine** (proven SBC shield); native **Rust** parser + transaction/dialog core (`rsip`/`ezk-sip`) grows behind it. **No pjproject FFI** — it would put C at the most exposed seam and break the memory-safety thesis. The single biggest risk item. |
| ↳ registration (in/out) | `res_pjsip_registrar`, `_outbound_registration` | 🦀 Core | Registrar state is core control-plane; back it with the state store. |
| SIP signaling (trunk) | `chan_pjsip` + `res_pjsip*` (48 mods) | 🦀 Core | **Decided (ADR-0003):** **Rust-native trunk SIP**, no C SBC shield. `rsip` + `ezk` sans-IO transaction/dialog core. The *trunk* interop surface is bounded by carrier docs (Telnyx/Bandwidth/Twilio SIP, IP-allowlisted) — the device interop tail ADR-0001 rented a shield for *collapses* under the contact-center scope. Hostile bytes hit a fuzzed Rust parser first → memory-safety thesis literally true at the wire. |
| ↳ outbound registration (to carriers) | `res_pjsip_outbound_registration` | 🦀 Core | Trunk side; bounded register-to-carrier. State in the state store (Valkey, [ADR-0005](adr/0005-event-bus.md)). |
| ↳ **inbound endpoint registration (desk/soft phones)** | `res_pjsip_registrar` | ⛔ **Deferred — community edge** | **Split from trunk.** An inbound endpoint server re-imports the *unbounded device interop tail* ADR-0003 deliberately closed (Polycom/Yealink/Zoiper `REGISTER`, BLF/MWI, DTMF variants, per-device NAT/provisioning). In an SSO/browser-agent UX, the desk-phone demographic has collapsed. Out-of-tree SBC-style adapter (fronting the public API / B2B-SIP) for anyone who needs it — **outside the trust boundary**, so its tail can't compromise the wedge. See [ADR-0006](adr/0006-ingress-posture.md). |
| ↳ SDP / media negotiation | `res_pjsip_session`, `_sdp_rtp` | 🦀 Core | Offer/answer drives the media plane directly. |
| ↳ auth | `res_pjsip_outbound_authenticator_digest`, etc. | 🦀 Core | Digest + token auth, deny-by-default, rate-limited. Security-critical. |
| ↳ NAT / ICE / rtcp | `res_pjsip_nat`, `res_rtp_asterisk` ICE | 🦀 Core | ICE/STUN/TURN via `str0m`/`webrtc-rs`. |
| ↳ pub/sub (presence, MWI, BLF) | `res_pjsip_pubsub`, `_exten_state`, `_mwi` | 🦀 Core + ☁️ | Signaling in core; presence/MWI state aggregation as a service. |
| ↳ T.38 fax | `res_pjsip_t38` | ☁️ Service | Isolate fax entirely (see §5). |
| WebRTC signaling | `chan_websocket`, `res_http_websocket` | 🦀 Core | First-class ingress. WSS + DTLS-SRTP via the media stack. |
| WebRTC signaling (agent ingress) | `chan_websocket`, `res_http_websocket` | 🦀 Core | **First-party human-participant ingress** ([ADR-0006](adr/0006-ingress-posture.md)). The browser is the softphone: SSO in, one peer-connection, DTLS-SRTP + ICE handled by the browser, zero device-provisioning tail. Also the modern hosted UX and the escalation path (rung 2): agent clicks *take this call* → WebRTC leg + audiohook/barge handoff. WSS + DTLS-SRTP via the media stack. |
| External media | `chan_audiosocket` | 🦀 Core | Modern, relevant pattern (stream media to an external process); keep as a media tap/source API. |
| Local console audio | `chan_console` | 🧩 Plugin | Dev/testing convenience; not core. |
| RTP-only pseudo-channel | `chan_rtp`, `chan_bridge_media` | 🦀 Core | Media-plane primitives. |
@@ -83,7 +94,7 @@ Grounded against the Asterisk 22.10.1 tree (~1.18M LOC C/H).
| Internal message bus (Stasis) | `main/stasis*.c` | 🔁 Replace | In-core dispatcher for intra-core; external event bus (NATS/Kafka) for cross-service. |
| Module loader | `main/loader.c` | 🔁 Replace | → WASM host + service registry. No in-process `.so`. |
| ACL / named ACL | `main/acl.c`, `res_named_acl` | 🦀 Core | Security primitive; policy-driven. |
| astdb (internal KV) | `main/db.c` | 🔁 Replace | → state store (embedded KV or external). |
| astdb (internal KV) | `main/db.c` | 🔁 Replace | → state store (**Valkey** KV, [ADR-0005](adr/0005-event-bus.md)). |
---
@@ -105,23 +116,30 @@ Grounded against the Asterisk 22.10.1 tree (~1.18M LOC C/H).
---
## 5. Applications (`apps/`)
## 5. Applications (`apps/`) — the contact-center domain
> **Scope lift** ([vision-revision](superpowers/specs/2026-06-26-vision-revision-design.md) §4): the
> contact-center capabilities Asterisk kept as apps are **rutster's core domain**, not peripheral.
> This is the difference between "Asterisk successor" and "AI-era contact-center engine." Every row
> here is load-bearing for the wedge (the thing LiveKit will never ship).
| Asterisk app | Module | Disp. | Rationale |
|---|---|---|---|
| Dial / originate | `app_dial`, `app_originate` | 🦀 Core | Core call origination, exposed via the API. |
| Queues / ACD | `app_queue` | ☁️ Service | Stateful, independently scaled, business logic. |
| Queues / ACD | `app_queue` | ☁️ Service (**domain core**) | Stateful, independently scaled. **First-class** — ACD is the beating heart of a contact center, not a peripheral app. The AI self-serve + human-escalation loop (capability ladder) is an ACD-shaped problem. |
| Voicemail | `app_voicemail` | ☁️ Service | Stateful storage + business logic; isolate it. |
| Conferencing | `app_confbridge` | 🦀 Core + ☁️ | Mixing in core (softmix); orchestration/features as a service. |
| MeetMe | `app_meetme` | ⛔ Drop | DAHDI-timing legacy; ConfBridge supersedes. |
| Recording | `app_mixmonitor`, `app_monitor` | 🦀 Core + ☁️ | Audiohook tap in core; storage/lifecycle as a recording service. |
| ChanSpy / whisper / barge | `app_chanspy` | 🦀 Core | Audiohook primitive; gated by RBAC. |
| Recording | `app_mixmonitor`, `app_monitor` | 🦀 Core + ☁️ | Audiohook tap in core; storage/lifecycle as a recording service (object-storage-backed, retention policy, tenant-scoped — **durable, not on the bus**). |
| ChanSpy / whisper / barge | `app_chanspy` | 🦀 Core | Audiohook primitive; gated by RBAC. **The escalation primitive** (rung 2). |
| Answering-machine detect | `app_amd` | 🦀 Core + 🧩 | DSP primitive in core; policy/thresholds as a plugin. |
| IVR primitives (playback/read/say) | `app_playback`, `app_read`, `app_sayunixtime`, `app_directory` | 🦀 Core | Media primitives (play/collect-digits/say) exposed to WASM apps. |
| IVR primitives (playback/read/say) | `app_playback`, `app_read`, `app_sayunixtime`, `app_directory` | 🦀 Core | Media primitives (play/collect-digits/say) exposed via the API/tap. The IVR *flow* dissolves into the agent; these are the primitives left over. |
| Echo / test | `app_echo`, `app_milliwatt` | 🦀 Core | Trivial diagnostics. |
| FollowMe | `app_followme` | 🧩 Plugin | Routing policy → WASM/service. |
| FollowMe | `app_followme` | 🧩 Plugin | Routing policy → plugin/service. |
| Speech recognition | `app_speech_utils`, `res_speech` | ☁️ Service | Modern ASR is external; expose as a service integration. |
| Fax | `res_fax`, `res_fax_spandsp` (T.38) | ☁️ Service (FFI) | Niche, isolate fully; spandsp via FFI. Optional. |
| **Supervisor / agent-state** | (scattered manager/AMI surfaces) | ☁️ Service (**domain core**) | **First-class.** Agent presence, state, real-time monitoring, coaching/whisper — the contact-center operator surface. Pure API client (the dashboard). |
| **Dialer (predictive/progressive)** | (custom/`app_dial`-derived) | ☁️ Service (**domain core**) | **First-class.** Outbound campaign dialing with pacing — where the in-boundary spend/abuse gate (§10) *bites hardest*; structurally co-located. |
---
@@ -184,28 +202,47 @@ These are the "modern" deltas — load-bearing, not optional polish.
| Subsystem | Disp. | Purpose |
|---|---|---|
| WASM plugin host | 🦀 Core | Sandboxed runtime (`wasmtime`) for extensions. The isolation boundary. |
| WASM plugin host | 🧩 **Demoted (candidate)** | **Out of the core story** ([ADR-0002](adr/0002-north-star-and-fused-core.md)). The agent **tap** is the extension point for in-call logic; ops-simplicity wants one binary. Softly retained as a *candidate* mechanism for community call-flow/routing plugins — undecided vs. webhooks/scripting. |
| Multi-tenancy / isolation | 🦀 Core | Per-tenant keys, quotas, media separation, RBAC. |
| Toll-fraud / anomaly engine | ☁️ Service | Deny-by-default routing, spend caps, rate limits, pattern detection. The #1 real-world Asterisk wound. |
| Toll-fraud / spend / abuse engine | 🦀 Core (**promoted**) | **Constitutive, not a service** ([ADR-0002](adr/0002-north-star-and-fused-core.md)). Spend caps, pacing caps, deny-by-default routing, rate limits, pattern detection**co-located with trunk termination inside the boundary** so a runaway brain structurally can't exceed spend/pacing (it doesn't hold the wire). This is what makes the boundary *auditable* on spend. |
| Secrets / KMS | 🦀 Core | Vault/KMS integration; no plaintext credentials in config. |
| Observability (OTel) | 🦀 Core | Distributed traces that follow a single call across services. |
| Event bus | ☁️ Service | NATS/Kafka/Redis Streams; replaces cross-service Stasis. |
| Event bus | ☁️ Service | **Valkey** ([ADR-0005](adr/0005-event-bus.md)): streams + consumer groups (CDR/CEL/analytics), pub/sub (presence/MWI/BLF), KV (state store). Pluggable to NATS later at a config seam. The 20ms media loop never rides the bus. |
| SBOM / supply chain | — | cargo-deny, SBOM generation, reproducible builds. |
| Fuzzing harness | — | Continuous fuzzing of every wire parser (SIP/SDP/RTP). |
---
## Phasing (maps to the roadmap)
## Phasing → the thin-slice first proof + capability ladder
- **Phase 0 — Media core.** RTP/SRTP endpoint, G.711+Opus, 2-party bridge, record+playback. (§2, §7). The riskiest/most valuable proof.
- **Phase 1 — Control plane + API.** ARI-modeled REST/gRPC + event stream, auth, event bus, multi-tenancy. (§4, §9, §10).
- **Phase 2 — Signaling.** WebRTC ingress first, then SIP via FFI/SBC. (§1).
- **Phase 3 — Conferencing + DSP + IVR primitives.** Softmix, transcoding matrix, DTMF/IVR. (§2, §5).
- **Phase 4 — Feature services.** Registrar, presence/MWI, queues, voicemail, recording, CDR, STIR/SHAKEN — as services/plugins. (§5, §6).
The old Phase 04 capability-rolldown is **replaced** (see [vision-revision §1011](superpowers/specs/2026-06-26-vision-revision-design.md)) by a **brutally thin vertical slice** that proves the *combination* (the wedge) first, then a **capability ladder** that grows the contact center feature by feature. The spearhead proves the hardest, most-differentiating part before trunk integration piles on.
### The thin-slice first proof (spearhead)
1. **WebRTC media loopback** — terminate RTP/SRTP, echo audio to a browser. Proves the media core.
2. **Add the tap** — route audio to an external echo process and back. Proves the tap interface.
3. **Swap echo for the brain** — ideally a single speech-to-speech API (e.g. OpenAI Realtime) to collapse STT+LLM+TTS into one integration. Proves agent integration.
4. **Add barge-in** — VAD-driven playout kill. Proves the reflex.
5. **Replace WebRTC ingress with a real PSTN trunk call** — proves the trunk client ([ADR-0003](adr/0003-sip-rust-native-trunk.md)).
6. **Add the spend cap** — hard-stop at threshold. Proves the boundary.
Steps 14 *are* the reflex loop. Keep the dev loop on WebRTC ingress until step 5.
### The capability ladder (the grand vision, incrementally)
| Rung | Capability | Reuses |
|---|---|---|
| 1 | **Self-serve** — AI answers, contains the call | the thin-slice first proof |
| 2 | **Escalation** — human agent barges in / takes over when AI breaks down | the audiohook/barge primitive (§5) |
| 3 | **Measurement** — containment rate, where/why AI failed | CDR + analytics on calls *you own* |
| 4 | **Self-improvement** — every takeover → auto-labeled training data → loop | rungs 13 compounding |
## Open decisions
- ~~**SIP:** FFI pjproject vs. SBC front vs. pure-Rust.~~ **Decided — ADR-0001:** native Rust core behind a **Kamailio + rtpengine** edge.
- **WASM runtime:** `wasmtime` component model (presumptive).
- **Event bus:** NATS vs. Kafka vs. Redis Streams.
- **License** (see README).
- ~~**SIP:** FFI pjproject vs. SBC front vs. pure-Rust.~~ **Decided — ADR-0003:** Rust-native trunk SIP, no SBC shield.
- ~~**License**~~ **Decided — ADR-0004:** GPL-3.0-or-later.
- ~~**Event bus:** NATS vs. Kafka vs. Redis Streams.~~ **Decided — ADR-0005:** Valkey.
- ~~**Ingress:** WebRTC vs. inbound SIP endpoint.~~ **Decided — ADR-0006:** WebRTC-first; SIP endpoint deferred.
- **WASM runtime** — `wasmtime` component model, *if* WASM is retained at all (demoted to candidate per [ADR-0002](adr/0002-north-star-and-fused-core.md)). Uncoupled from the critical path.
- **Plugin mechanism** — WASM vs. webhooks vs. scripting for community call-flow/routing extensions. Deferred.
- **Agent-tap protocol** — presumptively WSS + core-as-client + clean PCM + core-authoritative playout (see ARCHITECTURE.md, "Agent tap"). To harden against the thin-slice first proof, *not* a decided ADR yet.

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.

View File

@@ -1,6 +1,7 @@
# Rutster — Vision Revision: the self-hosted AI-era contact-center engine
- **Status:** Proposed — for review
- **Status:** Ratified 2026-06-26 — folded into `README.md`, `docs/ARCHITECTURE.md`,
`docs/PORT_PLAN.md`, and ADRs 00020006. Kept as the design record of the pressure-test.
- **Date:** 2026-06-26
- **Origin:** A pressure-test of the four founding docs (README, ARCHITECTURE, PORT_PLAN, ADR-0001). This document records the decisions that pressure-test produced.
- **Amends:** `README.md`, `docs/ARCHITECTURE.md`, `docs/PORT_PLAN.md`