All checks were successful
Maintainer accepted 2026-07-05. Step 6 builds the gate against a check-and-reserve ledger trait: in-memory at N=1, Valkey at N>1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012QndwfhjyTiZcUYp87dwW8 Signed-off-by: Aaron D. Lee <himself@adlee.work>
117 lines
6.8 KiB
Markdown
117 lines
6.8 KiB
Markdown
# 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).
|
||
|
||
## Amendment 2026-07-04 — enforcement locality ≠ accounting locality
|
||
|
||
- **Status:** Accepted (maintainer-ratified 2026-07-05)
|
||
- **Origin:** [2026-07-04 scalability & infra-fit review](../reviews/2026-07-04-scalability-infra-review.md),
|
||
finding M5.
|
||
|
||
Guarantee 2 above prescribes where the **check** sits — in-process with the tap and the
|
||
provider call-control client. It is silent on where the **accounting state** lives, and at
|
||
N>1 core instances that silence becomes a correctness trap: per-instance counters make
|
||
every spend/pacing cap silently N× the fleet intent, and toll-fraud thresholds never trip
|
||
because attempts spread across instances.
|
||
|
||
Clarification, binding on the step-6 implementation:
|
||
|
||
1. **Enforcement is in-process** (unchanged — constitutive; guarantee 2 stands).
|
||
2. **Accounting is shared.** The gate is built against a ledger trait with atomic
|
||
check-and-reserve semantics from day one: an in-memory implementation for single-node,
|
||
a Valkey-backed one ([ADR-0005](0005-event-bus.md)) for fleets. The check path never
|
||
assumes counter locality.
|
||
3. **ADR-0005 constraint 2 is not a counter-argument.** "The bus is not the source of
|
||
truth for billing- or call-loss-critical state" governs the *durable CDR*. Live
|
||
enforcement counters (spend/pacing/rate state) are ephemeral control state — exactly
|
||
what Valkey KV is for. Losing them on a Valkey restart degrades fail-safe (re-count
|
||
from zero, provider-side caps as the backstop per this ADR's deployment guidance) —
|
||
not to billing corruption.
|
||
|
||
## 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
|