Files
rutster/docs/PORT_PLAN.md
adlee-was-taken 075e984fb5 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.
2026-06-28 09:33:29 -04:00

249 lines
20 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.
# 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](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).
## 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)
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. **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](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 (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. |
| 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](adr/0005-event-bus.md)). |
---
## 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](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 (**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](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 / 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 | **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 → the thin-slice first proof + capability ladder
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-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.