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.
20 KiB
Rutster — Asterisk Capability Checklist
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). 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).
Disposition legend
| Disposition | Meaning | |
|---|---|---|
| 🦀 | Core | In the hardened, memory-safe Rust core. Reserved for the hot media path, call model, and signaling glue — fast, always-present, trusted. |
| 🧩 | Plugin (WASM) | Sandboxed extension. Logic that benefits from isolation and third-party extensibility. Cannot crash the core or cross tenant boundaries. |
| ☁️ | Service | Independent microservice. Stateful, independently-scaled, or business/billing logic. Talks over API + event bus. |
| 🔌 | Edge / FFI | Wrap mature external C (pjproject/spandsp) or front with an SBC. Used where a pure-Rust rewrite is a tar pit. |
| ⛔ | Drop | Legacy hardware or dead protocol. Gone. |
| 🔁 | Replace | Capability survives but the mechanism is entirely redesigned (no 1:1 mapping). |
Design rules (the heuristics behind every row)
- Hot media path & the call model → Core. Trusted, fast, always-on. Inside the fused per-call vertical.
- Anything that benefits from isolation or third-party extension → Plugin (now softened — WASM demoted out of the core story per ADR-0002; the agent tap is the primary extension point for in-call logic).
- Stateful, independently-scaled, or business/billing logic → Service. Around the core, horizontal platform.
Mature C that's a tar pit to rewrite (SIP) → Edge/FFI, with a pure-Rust ambition.Reversed for SIP by ADR-0003: Rust-native trunk SIP, no C SBC shield. FFI retained only for mature codec/DSP/EQ libraries (libopus, speexdsp, WebRTC APM).- Legacy hardware / dead protocols → Drop.
- Security & multi-tenancy are cross-cutting — never a row, always a property. Spend / abuse control is constitutive, pulled into the boundary, not externalized.
1. Signaling & channel drivers (channels/, res/res_pjsip*)
| Asterisk subsystem | Module(s) | Disp. | Rationale |
|---|---|---|---|
| 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). |
| ↳ 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. |
| ↳ 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 (agent ingress) | chan_websocket, res_http_websocket |
🦀 Core | First-party human-participant ingress (ADR-0006). 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. |
| IAX2 | chan_iax2 |
⛔ Drop | Dead inter-Asterisk protocol. SIP trunks instead. |
| TDM / analog / ISDN | chan_dahdi, sig_analog, sig_pri, sig_ss7 |
⛔ Drop | No hardware era. PSTN via SIP trunk / SBC. |
| H.323 | addons/chan_ooh323 |
⛔ Drop | Dead protocol. |
| SCCP / Skinny, MGCP | (Skinny/MGCP) | ⛔ Drop | Dead protocols. |
| Unistim | chan_unistim |
⛔ Drop | Dead vendor protocol. |
| XMPP / Jingle | chan_motif, res_xmpp |
⛔ Drop | Dead path. |
2. Media plane (main/rtp_engine.c, res_rtp_asterisk, bridges/, main/dsp.c)
| Asterisk subsystem | Module(s) | Disp. | Rationale |
|---|---|---|---|
| RTP / RTCP engine | rtp_engine.c, res_rtp_asterisk |
🦀 Core | Crown jewel. Built on str0m/webrtc-rs, sans-IO, dedicated timing (not the shared tokio pool). |
| SRTP / DTLS | res_srtp |
🦀 Core | Mandatory-by-default encryption. |
| Bridging framework | main/bridge.c |
🦀 Core | The 2-party + N-party call topology engine. |
| ↳ softmix conference mixer | bridges/bridge_softmix |
🦀 Core | N-party mixing on the hot path. |
| ↳ simple / native-RTP bridge | bridge_simple, bridge_native_rtp |
🦀 Core | Direct media flow (re-INVITE peers together) when no mixing needed. |
| ↳ holding bridge | bridge_holding |
🦀 Core | Parking/hold media state. |
| ↳ native DAHDI bridge | bridge_native_dahdi |
⛔ Drop | Hardware. |
| DSP (DTMF/MF detect, silence, tone) | main/dsp.c |
🦀 Core | Media-plane primitive; exposed to plugins as events. |
| Echo cancellation | (hardware / sw) | 🔌 Edge/FFI | No mature pure-Rust EC; FFI speexdsp / WebRTC APM. |
| Audio hooks (tap/inject/volume) | main/audiohook.c |
🦀 Core | Primitive behind recording, ChanSpy, whisper/barge. |
| Music on hold | res_musiconhold |
🦀 Core | Just a media source; small, keep in core (asset from object storage). |
| Jitter buffer | func_jitterbuffer, abstract jb |
🦀 Core | Quality-critical; part of the RTP path. |
3. Call model & PBX core (main/)
| Asterisk subsystem | Module(s) | Disp. | Rationale |
|---|---|---|---|
| Channel abstraction | main/channel.c (ast_channel) |
🦀 Core | The unifying "leg" object (signaling + media state). Reimagined as a Channel/leg. |
| PBX / dialplan engine | main/pbx.c |
🔁 Replace | → declarative routing (data) + event-driven apps. No extensions.conf. |
| Call features (transfer/park/pickup) | res_features, res_parking |
🦀 Core | Call-control primitives, exposed via the API. |
| Scheduler / taskprocessor / threadpool | main/sched.c, taskprocessor.c, threadpool.c |
🦀 Core | tokio for control; dedicated timing threads + timer wheel for the 20ms media loop. |
| 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 (Valkey KV, ADR-0005). |
4. Dialplan & programmability (pbx/, res_agi, res_ari, manager)
| Asterisk subsystem | Module(s) | Disp. | Rationale |
|---|---|---|---|
extensions.conf dialplan |
pbx_config |
🔁 Replace | Declarative routing as data (GitOps) for the 90% case. |
| AEL / Lua dialplan | pbx_ael, pbx_lua |
⛔ Drop | Replaced by WASM call-flow + routing config. |
| Realtime dialplan | pbx_realtime |
🔁 Replace | Routing in the config/state layer. |
| Call files / spool | pbx_spool |
🔁 Replace | → originate via API. |
| Macro / Gosub / Stack | app_stack, app_macro |
🔁 Replace | → WASM call-flow logic. |
| AGI / FastAGI | res_agi |
🔁 Replace | → WASM plugins + gRPC. Same "external program controls a call," sandboxed and typed. |
| ARI (REST + stasis apps) | res_ari*, res_stasis* |
🦀 Core | Becomes THE API — the control-plane resource model. api-docs/*.json is the reusable spec. |
| AMI (manager) | main/manager*.c |
🔁 Replace | Drop the plaintext TCP protocol → mTLS gRPC + RBAC + audit log. |
| CLI | main/cli.c |
🔁 Replace | Thin CLI that's an API client, not an in-process console. |
| DUNDi (distributed routing) | pbx_dundi |
⛔ Drop | Dead/niche. |
| ENUM lookup | func_enum |
🧩 Plugin | Routing-time lookup; pluggable. |
5. Applications (apps/) — the contact-center domain
Scope lift (vision-revision §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 (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 (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 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 → 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. |
6. Telephony features / subscriptions
| Asterisk subsystem | Module(s) | Disp. | Rationale |
|---|---|---|---|
| Presence / BLF | res_pjsip_pubsub, res_pjsip_exten_state |
🦀 Core + ☁️ | Signaling in core; state aggregation service. |
| MWI (message-waiting) | res_mwi_external, app_voicemail MWI |
☁️ Service | Tied to the voicemail service. |
| STIR/SHAKEN (attestation) | res_stir_shaken |
☁️ Service | Keep — modern, anti-robocall/fraud. Core to the security story. |
| Call recording lifecycle | app_mixmonitor + storage |
☁️ Service | Object-storage backed, retention policy, tenant-scoped. |
| CDR (call detail records) | cdr/, cdr_* |
☁️ Service | Event stream → billing/analytics pipeline. |
| CEL (channel event logging) | cel/, cel_* |
☁️ Service | Folded into the unified event bus + analytics. |
7. Codecs & formats (codecs/, formats/)
| Codec / format | Module | Disp. | Rationale |
|---|---|---|---|
| G.711 a-law / µ-law | codec_alaw, codec_ulaw, codec_a_mu |
🦀 Core | Trivial, ubiquitous. Native Rust. |
| G.722 (HD) | codec_g722 |
🦀 Core | Common wideband. Native Rust. |
| Opus | (external codec_opus) |
🦀 Core (FFI) | WebRTC default; libopus via FFI. |
| Sample-rate resample | codec_resample |
🦀 Core | Needed for any transcoding/mixing. |
| GSM | codec_gsm |
🧩 Plugin (FFI) | Optional; FFI libgsm. |
| G.726 / G.729 | codec_g726 / (external) |
🧩 Plugin (FFI) | Optional; G.729 now patent-free. |
| Speex / iLBC / LPC10 / ADPCM / codec2 | codec_speex, codec_ilbc, … |
⛔ Drop | Obsolete; drop or community plugin. |
| Hardware transcode | codec_dahdi |
⛔ Drop | Hardware. |
| File formats (wav/sln/ogg-opus) | format_wav, format_sln, … |
🦀 Core | Small set for prompts/recordings in core; rest as plugins. |
| Legacy file formats (gsm/g729/g726 files) | format_* |
🧩 Plugin | Optional. |
8. Config, state & data (res_config_*, res_sorcery*)
| Asterisk subsystem | Module(s) | Disp. | Rationale |
|---|---|---|---|
.conf files |
main/config.c |
🔁 Replace | Config-as-data (declarative, validated, GitOps). |
| Sorcery (object config abstraction) | res_sorcery* |
🔁 Replace | Typed config + state-store layer. |
| Realtime backends (ODBC/PgSQL/SQLite/cURL/LDAP) | res_config_* |
🔁 Replace | One config/state service with pluggable backends. |
| HTTP media cache | res_http_media_cache |
🦀 Core | Prompt/asset fetch → object storage + cache. |
| Security event logging | res_security_log |
🦀 Core | Structured audit log; security-critical. |
9. Management & APIs
| Asterisk surface | Module(s) | Disp. | Rationale |
|---|---|---|---|
| ARI (REST resource model + event WS) | res_ari* |
🦀 Core | The control-plane API. Reuse the resource model. |
| AMI (plaintext TCP admin) | manager*.c |
🔁 Replace | → mTLS gRPC + RBAC + audit. No plaintext admin channel. |
| HTTP server | res_http* |
🦀 Core | TLS-only; axum/hyper. |
| CLI | cli.c |
🔁 Replace | API-client CLI. |
10. New subsystems (no Asterisk equivalent)
These are the "modern" deltas — load-bearing, not optional polish.
| Subsystem | Disp. | Purpose |
|---|---|---|
| WASM plugin host | 🧩 Demoted (candidate) | Out of the core story (ADR-0002). 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 / spend / abuse engine | 🦀 Core (promoted) | Constitutive, not a service (ADR-0002). 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 | Valkey (ADR-0005): 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 → the thin-slice first proof + capability ladder
The old Phase 0–4 capability-rolldown is replaced (see vision-revision §10–11) 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)
- WebRTC media loopback — terminate RTP/SRTP, echo audio to a browser. Proves the media core.
- Add the tap — route audio to an external echo process and back. Proves the tap interface.
- 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.
- Add barge-in — VAD-driven playout kill. Proves the reflex.
- Replace WebRTC ingress with a real PSTN trunk call — proves the trunk client (ADR-0003).
- Add the spend cap — hard-stop at threshold. Proves the boundary.
Steps 1–4 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 1–3 compounding |
Open decisions
SIP: FFI pjproject vs. SBC front vs. pure-Rust.Decided — ADR-0003: Rust-native trunk SIP, no SBC shield.LicenseDecided — 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 —
wasmtimecomponent model, if WASM is retained at all (demoted to candidate per ADR-0002). 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.