Files
rutster/docs/adr/0009-spend-gate-honest-rescope.md
Aaron D. Lee cf49618abe
All checks were successful
CI / fmt (pull_request) Successful in 1m11s
CI / clippy (pull_request) Successful in 1m21s
CI / test (1.85) (pull_request) Successful in 3m41s
CI / test (stable) (pull_request) Successful in 4m27s
CI / deny (pull_request) Successful in 1m10s
docs(adr): ratify ADR-0009 amendment — shared spend accounting
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>
2026-07-05 00:39:08 -04:00

117 lines
6.8 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.
# 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