diff --git a/README.md b/README.md index ad708e8..3b5fa38 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ The white space no incumbent fills: | **LiveKit** | Horizontal real-time media infra (Go) | rutster owns the **contact-center domain** (ACD, IVR, queues, recording, CDR, dialer, supervisor) LiveKit will never ship | | **Cloud CCaaS** (Five9, Genesys, NICE, Amazon Connect, Twilio Flex) | Proprietary, AI bolted on | Self-hostable, AI-**native**, no per-seat/minute lock-in | | **Cloud AI-voice** (Vapi, Retell, Bland) | Cheap managed voice bots | You own your **calls and training data**; it's a *contact center* (escalation, queues), not a single bot | +| **The brain vendor direct** (OpenAI Realtime SIP, Gemini Live) | Point a SIP trunk straight at the model API | Everything *around* the call: queues/ACD, human takeover, recording/CDR you own, containment analytics, multi-vendor brains — and the phone still answers when the vendor is down | | **Dated OSS** (Vicidial, FreePBX) | Self-hostable, Asterisk-era | Modern, AI-native, memory-safe Rust | The wedge is a **coherent combination, not a silver bullet**: @@ -132,8 +133,11 @@ exactly as integrators did on top of Asterisk. deny-by-default routing, built-in toll-fraud controls, mTLS gRPC admin (no plaintext AMI), hard multi-tenancy. Compliance is a buying criterion, not a row. 3. **In-boundary spend / abuse control** — spend caps and abuse/pacing control live **inside the - trust boundary**, co-located with trunk termination. A runaway brain can't exceed pacing or - spend because it doesn't hold the wire — structurally impossible for a 3-vendor stack. + trust boundary**, co-located with the tap and the provider call-control client. The brain + never holds provider credentials, and the gate is unskippable for everything rutster mediates + — which is everything the brain can do + ([ADR-0009](docs/adr/0009-spend-gate-honest-rescope.md); pair with provider-side caps for + defense in depth). 4. **Data ownership** — calls and training data never leave the operator's infra. The self-host wedge *and* the fuel for the ML self-improvement loop. 5. **Degradation, deterministic, observable** — no-GC real-time loop; OpenTelemetry traces that @@ -153,6 +157,8 @@ exactly as integrators did on top of Asterisk. - [0004](docs/adr/0004-license.md) — GPL-3.0-or-later - [0005](docs/adr/0005-event-bus.md) — Valkey (bus + state store) - [0006](docs/adr/0006-ingress-posture.md) — WebRTC-first ingress; SIP endpoint deferred + - [0009](docs/adr/0009-spend-gate-honest-rescope.md) — spend-gate guarantees re-scoped post-0007 (amends 0002) + - [0010](docs/adr/0010-spearhead-benchmark-sim-harness.md) — spearhead step 4½: benchmark + simulation harness; escalation pulled forward ## Status @@ -172,13 +178,19 @@ The full thin slice, sequenced so each step is its own proof — never a big ban 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 + - **4½ — Benchmark + simulation harness** ([ADR-0010](docs/adr/0010-spearhead-benchmark-sim-harness.md)): + synthetic callers drive p50/p99 mouth-to-ear latency + barge-in kill-time under concurrent + load, in CI — proves the reflex *measurably*; seeds `rutster-sim` 5. **Add a real phone number via rented transport** (a CPaaS raw-media fork, e.g. Twilio Media Streams) — proves a PSTN call reaches the reflex loop, no first-party SIP ([ADR-0007](docs/adr/0007-trunk-rented-transport.md)) 6. **Add the spend cap** (hard-stop at threshold) — proves the boundary Steps 1–4 *are* the reflex loop — the hard, most-differentiating part proves itself before trunk integration piles on. "I called my Rust box and an AI answered the phone" is the momentum fuel a -solo multi-year build needs. +solo multi-year build needs. Post-review re-sequencing +([ADR-0010](docs/adr/0010-spearhead-benchmark-sim-harness.md)): after step 4½, **rung-2 +escalation (human takeover) is pulled forward ahead of steps 5–6** — it is the white space no +competitor ships (see the [2026-07-03 reviews](docs/reviews/)). ## Capability ladder (the grand vision, incrementally) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5fec39c..5c27d75 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -84,8 +84,11 @@ trusted, battle-tested OSS kept outside the boundary** — not an over-claim tha 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. + rate-limits, toll-fraud pattern detection. **Co-located with the tap + the provider + call-control client**: the brain never holds provider credentials, so every brain-initiated + action passes the gate; pacing/half-duplex/playout are enforced over media the core terminates + ([ADR-0009](adr/0009-spend-gate-honest-rescope.md) — pair with provider-side caps for defense + in depth). - **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). diff --git a/docs/PORT_PLAN.md b/docs/PORT_PLAN.md index aeaab7f..60ac374 100644 --- a/docs/PORT_PLAN.md +++ b/docs/PORT_PLAN.md @@ -206,7 +206,7 @@ These are the "modern" deltas — load-bearing, not optional polish. |---|---|---| | 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 call origination + the tap inside the boundary** so a runaway brain structurally can't exceed spend/pacing: rutster mediates both the provider call-control API and the brain tap, so the brain never holds the wire ([ADR-0007](adr/0007-trunk-rented-transport.md)). This is what makes the boundary *auditable* on spend. | +| Toll-fraud / spend / abuse engine | 🦀 Core (**promoted**) | **Constitutive, not a service** ([ADR-0002](adr/0002-north-star-and-fused-core.md), re-scoped by [ADR-0009](adr/0009-spend-gate-honest-rescope.md)). Spend caps, pacing caps, deny-by-default routing, rate limits, pattern detection — **co-located with the tap + the provider call-control client inside the boundary**: the brain never holds provider credentials, the gate is unskippable on everything rutster mediates (which is everything the brain can do), and pacing/playout are enforced over media the core terminates ([ADR-0007](adr/0007-trunk-rented-transport.md)). Not a physical wire monopoly — deployments pair it with provider-side caps. 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. | @@ -225,10 +225,15 @@ The old Phase 0–4 capability-rolldown is **replaced** (see [vision-revision § 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. + - **4½ — Benchmark + simulation harness** ([ADR-0010](adr/0010-spearhead-benchmark-sim-harness.md)): + synthetic-caller ingress drives p50/p99 mouth-to-ear + barge-in kill-time under concurrent + load, CI-regressed. Proves the reflex *measurably*; seeds `rutster-sim`. 5. **Add a real phone number via rented transport** — a CPaaS raw-media fork (e.g. Twilio Media Streams) lands a PSTN call on the reflex loop; no first-party SIP ([ADR-0007](adr/0007-trunk-rented-transport.md)). 6. **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. +Post-review re-sequencing ([ADR-0010](adr/0010-spearhead-benchmark-sim-harness.md)): after +step 4½, **rung-2 escalation is pulled forward ahead of steps 5–6**. ### The capability ladder (the grand vision, incrementally) diff --git a/docs/adr/0002-north-star-and-fused-core.md b/docs/adr/0002-north-star-and-fused-core.md index cce54cc..acf375f 100644 --- a/docs/adr/0002-north-star-and-fused-core.md +++ b/docs/adr/0002-north-star-and-fused-core.md @@ -4,6 +4,9 @@ - **Date:** 2026-06 - **Origin:** [vision-revision](../superpowers/specs/2026-06-26-vision-revision-design.md) §1–4, 6–7 - **Amends:** `README.md`, `docs/ARCHITECTURE.md`, `docs/PORT_PLAN.md` (the founding identity framing) +- **Amended by:** [ADR-0009](0009-spend-gate-honest-rescope.md) — the pillar table's spend-gate + phrasing ("structurally impossible for a 3-vendor stack") is retired post-0007; see 0009 for + the gate's precise guarantees (2026-07-03) ## Context diff --git a/docs/adr/0004-license.md b/docs/adr/0004-license.md index 5ea3e44..5f6e48e 100644 --- a/docs/adr/0004-license.md +++ b/docs/adr/0004-license.md @@ -36,9 +36,20 @@ hosted service may be nothing. The license is the **floor**, not the moat: the w + 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). +> **Amended 2026-07-03** (maintainer-ratified; origin: +> [adversarial review D5](../reviews/2026-07-03-adversarial-review.md)). The paragraph this note +> replaces claimed the "or-later" clause permits a clean later transition to AGPL. **That claim +> was wrong:** "or-later" covers later versions of the *GNU GPL* (GPLv3 §14); AGPL is a different +> license, not a later GPL version — GPLv3 §13 permits *combining* with AGPL works, not +> relicensing GPL code as AGPL. Relicensing requires the consent of every copyright holder, so +> the option would die at the first external contribution accepted without a CLA. +> +> **Resolution:** rutster accepts **GPL-3.0-or-later permanently** for the engine. No CLA — the +> contributor friction and community optics were judged worse than the loss of a relicensing +> option. Cloud-wrap remains a named, accepted risk mitigated by the wedge, exactly as the +> trade-off paragraph above already argued. (The tap *protocol spec* and client SDKs are +> intended to be permissively licensed for adoption — engine GPL, protocol permissive — to be +> ratified in the tap-protocol ADR when that interface hardens.) ## Consequences diff --git a/docs/adr/0009-spend-gate-honest-rescope.md b/docs/adr/0009-spend-gate-honest-rescope.md new file mode 100644 index 0000000..ff7e16a --- /dev/null +++ b/docs/adr/0009-spend-gate-honest-rescope.md @@ -0,0 +1,90 @@ +# ADR-0009 — Spend/abuse gate: honest re-scope of the structural claim post-0007 + +- **Status:** Accepted (maintainer-ratified 2026-07-03) +- **Date:** 2026-07-03 +- **Origin:** [2026-07-03 adversarial review](../reviews/2026-07-03-adversarial-review.md), + finding D4 / recommendation R3. +- **Amends:** [ADR-0002](0002-north-star-and-fused-core.md) (pillar table), `README.md` + (pillar 3), `docs/PORT_PLAN.md` (§10 toll-fraud row), `docs/ARCHITECTURE.md` (in-boundary + spend/abuse gate bullet) + +## Context + +ADR-0002 promoted the spend/abuse gate to *constitutive* with the argument: "a runaway brain +can't exceed pacing or spend because it doesn't hold the wire — **structurally impossible for a +3-vendor stack**." Under [ADR-0003](0003-sip-rust-native-trunk.md) (rutster terminates the +carrier trunk) that claim was structural: rutster held the wire. + +[ADR-0007](0007-trunk-rented-transport.md) changed the topology. Under rented transport, +**rutster does not hold the wire either — the CPaaS does.** The gate's mechanism became +"rutster holds the provider call-control credential and the brain doesn't," which is an +IAM/configuration property, not a structural one: + +1. Any orchestrator that holds the provider credentials can make the same claim (a Python + framework with a spend counter and scoped credentials). +2. The CPaaS itself ships spend limits. +3. A provider credential scoped too widely, or leaked outside rutster, bypasses the gate + entirely — rutster cannot see, let alone stop, provider-API calls it doesn't mediate. + +Meanwhile the ADR-0007 architecture *is* a 3-vendor stack (CPaaS + rutster + brain), making the +unamended pillar self-describing. The claim must be narrowed to what is actually true before it +is repeated in public-facing material. + +## Decision + +The spend/abuse gate remains **FOB and constitutive** ([ADR-0008](0008-fob-and-green-zone.md): +security-constitutive), but its guarantees are restated precisely: + +**What the gate structurally guarantees (true post-0007):** + +1. **Credential isolation** — the brain never holds provider credentials. Every + call-origination, transfer, or hangup the brain wants happens through rutster's API, where + the gate sits. The brain cannot spend *through rutster* beyond the gate, ever. +2. **Unskippable on mediated egress** — spend/pacing checks are in-process with the tap and the + provider call-control client. There is no deployment topology in which brain-initiated + actions reach the provider without passing the gate. (Contrast: a bolt-on spend service in a + 3-vendor chain can be *routed around*; an in-boundary gate cannot — for traffic rutster + mediates.) +3. **Media-plane enforcement** — pacing, half-duplex, and playout caps are enforced over media + rutster terminates. The brain *proposes* audio; the core *disposes*. A flooding or + overlapping brain is throttled at the playout ring regardless of what it sends. +4. **Audit co-location** — every allowed/denied spend decision is recorded at the same trust + boundary that made it, so the audit log and the enforcement point cannot diverge. + +**What the gate does NOT guarantee (stated so we don't kid ourselves):** + +- It cannot constrain spend on provider credentials used *outside* rutster (mis-scoped keys, + console access, a second integration holding the same key). **Deployment guidance must pair + the gate with provider-side caps** (defense in depth) and per-integration credential scoping. +- The "structurally impossible for a 3-vendor stack" phrasing is **retired**. The honest + comparative claim: *in-boundary co-location makes the gate unskippable for everything rutster + mediates, and rutster mediates everything the brain can do* — a property a bolt-on spend + service cannot offer, but not a physical monopoly on the wire. + +## Propagation (on acceptance) + +| Doc | Change | +|---|---| +| ADR-0002 | Add amendment note to the pillar table pointing here | +| `README.md` pillar 3 | Replace "doesn't hold the wire — structurally impossible for a 3-vendor stack" with the credential-isolation + unskippable-mediation phrasing | +| `docs/ARCHITECTURE.md` | Same fix in the "In-boundary spend / abuse gate" bullet | +| `docs/PORT_PLAN.md` §10 | Same fix in the toll-fraud/spend row | + +## Consequences + +- **Positive:** the security narrative survives adversarial scrutiny (the previous phrasing + invited a one-tweet rebuttal); the gate's real guarantees — credential isolation, unskippable + mediation, media-plane throttling, audit co-location — are strong and defensible; deployment + guidance gains the provider-side-caps pairing, which operators need anyway. +- **Negative:** the pitch loses a superlative ("structurally impossible") in exchange for + precision; step 6's scope grows slightly (the gate should also *meter* — per-call cost + attribution into the CDR — per the + [market feature scan F5](../reviews/2026-07-03-market-feature-scan.md), since integrator unit + economics run on cost-per-contained-call). + +## References + +- [ADR-0002](0002-north-star-and-fused-core.md) — the pillar this amends +- [ADR-0007](0007-trunk-rented-transport.md) — the topology change that invalidated the phrasing +- [ADR-0008](0008-fob-and-green-zone.md) — FOB test the gate still passes (security-constitutive) +- [Adversarial review 2026-07-03](../reviews/2026-07-03-adversarial-review.md) — finding D4 diff --git a/docs/adr/0010-spearhead-benchmark-sim-harness.md b/docs/adr/0010-spearhead-benchmark-sim-harness.md new file mode 100644 index 0000000..617bfb4 --- /dev/null +++ b/docs/adr/0010-spearhead-benchmark-sim-harness.md @@ -0,0 +1,75 @@ +# ADR-0010 — Spearhead re-sequencing: benchmark + simulation harness at step 4½; escalation pulled forward + +- **Status:** Accepted (maintainer-ratified 2026-07-03) +- **Date:** 2026-07-03 +- **Origin:** [2026-07-03 adversarial review](../reviews/2026-07-03-adversarial-review.md) + (recommendations R1, R2) + [market feature scan](../reviews/2026-07-03-market-feature-scan.md) + (F1 — simulation testing as the standout engine-fit feature). +- **Amends:** `README.md` (spearhead list), `docs/PORT_PLAN.md` (phasing section), + the vision-revision spearhead sequence (design record; unmodified — this ADR is the amendment) + +## Context + +Three findings converged on the spearhead's back half: + +1. **The wedge is unmeasured (review P3/R2).** The no-GC determinism pillar — the only claim + justifying Rust over a weekend of Pipecat — has no proof artifact. README latency figures are + arithmetic estimates; `TapMetrics` counts drops, not latency. Slice 4's design sets a ≤60 ms + barge-in kill budget but ships only a synthetic e2e test, not a measurement harness. +2. **The step-5 demo was commoditized (review D2).** OpenAI Realtime SIP is GA: a trunk pointed + at `sip.api.openai.com` reproduces "I called my box and an AI answered" with zero + infrastructure. The momentum value of rushing to step 5 has collapsed; the differentiating + value of *measured* reflex quality and of escalation (rung 2 — which no framework or brain + vendor ships) has risen. +3. **Simulation is the standout engine-fit feature (scan F1).** Parloa sells pre-deployment + simulation + evals as an enterprise phase; Cekura/Coval sell it standalone. Nobody ships it + self-hosted. rutster's tap symmetry (a synthetic caller is just another media-leg ingress) + and the slice-3 mock brain mean the substrate is half-built. + +One build serves all three: the benchmark *is* a simulation harness pointed at latency metrics. + +## Decision + +**Insert step 4½ into the spearhead — the benchmark + simulation harness — after barge-in (4), +before rented transport (5).** Numbered 4½ deliberately: existing "step 5"/"step 6" references +(stub-crate docs, PORT_PLAN rows, fuzz plan) stay valid. + +**Step 4½ scope (the `rutster-sim` seed):** + +- **Synthetic-caller ingress** — scripted scenario playback (WAV/PCM segments with timing + directives: speak, pause, interrupt-at-T) driving the same media-leg ingress path a real + caller uses. LLM-driven callers are a later extension, not this step. +- **Latency measurement** — p50/p99 mouth-to-ear round-trip and barge-in kill-time + (caller-speech-onset → playout-ring-flushed), measured against slice-4's ≤60 ms budget. +- **Concurrency sweep** — the same measurements at 1 / 10 / 50 concurrent synthetic calls, + which also surfaces the single-poll-task head-of-line-blocking debt (review P2) with data + instead of doctrine. +- **CI-regressed** — thresholds asserted per commit; a latency regression fails the build the + same way a broken test does. + +**Sequencing after 4½:** **rung-2 escalation (human takeover) is pulled forward ahead of +steps 5–6.** It is the white space no competitor ships (warm-handoff artifact + whisper — scan +F2), it works entirely on WebRTC ingress (no PSTN dependency), and it is the capability that +answers "why not point the trunk at OpenAI directly?" before step 5 makes that question live. +Steps 5 (rented transport) and 6 (spend cap) follow, unchanged in content. + +## Consequences + +- **Positive:** the wedge gains its proof artifact (and the only differentiating demo available + this year: reproducible p99 kill-latency vs. the GC'd frameworks); the doctrine-drift detector + for the timing-thread debt exists before scale makes it urgent; the eval-infrastructure + foundation (scenario packs, rung-3/4 regression evals) is laid as a side effect; the spearhead's + next public moment is a capability no one else has, not a commoditized one. +- **Negative:** steps 5–6 slip by two slices; "a real phone number" — still the most *visceral* + demo — waits. Accepted deliberately: the demo it enables is no longer scarce (D2), and the + reflex claims stay unsubstantiated until measured. +- **Debt paired:** if the concurrency sweep shows the shared-tokio poll loop breaching budget at + realistic call counts, the dedicated-timing-thread work (ARCHITECTURE.md doctrine, review P2) + gets scheduled on data, not vibes. + +## References + +- [Adversarial review 2026-07-03](../reviews/2026-07-03-adversarial-review.md) — findings D1, D2, P2, P3; recommendations R1, R2 +- [Market feature scan 2026-07-03](../reviews/2026-07-03-market-feature-scan.md) — F1 (simulation harness), F2 (handoff protocol), A8 (scenario packs) +- [ADR-0007](0007-trunk-rented-transport.md) — the trunk posture step 5 implements +- Slice-4 design (`docs/superpowers/specs/2026-07-01-slice-4-barge-in-design.md`) — the ≤60 ms kill budget this harness measures