adr: +0009 spend-gate rescope +0010 spearhead 4.5 benchmark/sim; amend 0004
All checks were successful
CI / fmt (push) Successful in 1m5s
CI / clippy (push) Successful in 1m53s
CI / test (1.85) (push) Successful in 5m54s
CI / test (stable) (push) Successful in 6m36s
CI / deny (push) Successful in 2m12s

Maintainer-ratified from the 2026-07-03 review (R1-R3):
- ADR-0009: retire "structurally impossible for a 3-vendor stack" post-0007;
  restate the gate's true guarantees (credential isolation, unskippable
  mediation, media-plane enforcement, audit co-location). Propagated to
  README pillar 3, ARCHITECTURE, PORT_PLAN s10; amendment note on ADR-0002.
- ADR-0010: insert step 4.5 (benchmark + simulation harness, rutster-sim
  seed, CI-regressed p50/p99 + kill-time) after barge-in; pull rung-2
  escalation ahead of steps 5-6. Spearhead lists updated.
- ADR-0004: delete the legally-broken AGPL escape hatch; GPL-3.0-or-later
  permanent, no CLA; tap protocol/SDKs intended permissive (future ADR).
- README: add brain-vendor-direct competitor row (review D2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vNe64BBDkgo5oQVkBa3XF
This commit is contained in:
2026-07-04 00:00:31 -04:00
parent 4c36a4ded3
commit da6b7c9ff0
7 changed files with 208 additions and 9 deletions

View File

@@ -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 14 *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 56** — it is the white space no
competitor ships (see the [2026-07-03 reviews](docs/reviews/)).
## Capability ladder (the grand vision, incrementally)

View File

@@ -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).

View File

@@ -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 04 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 14 *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 56**.
### The capability ladder (the grand vision, incrementally)

View File

@@ -4,6 +4,9 @@
- **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)
- **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

View File

@@ -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

View File

@@ -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

View File

@@ -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 56.** 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 56 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