Files
rutster/docs/adr/0009-spend-gate-honest-rescope.md
Aaron D. Lee f8a8a2c21f
All checks were successful
CI / fmt (push) Successful in 1m4s
CI / clippy (push) Successful in 1m17s
CI / test (1.85) (push) Successful in 3m39s
CI / test (stable) (push) Successful in 5m42s
CI / deny (push) Successful in 2m4s
docs(adr): ratify ADR-0009 amendment (shared spend accounting) (#15)
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-05 05:03:18 +00:00

6.8 KiB
Raw Permalink Blame History

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, finding D4 / recommendation R3.
  • Amends: ADR-0002 (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 (rutster terminates the carrier trunk) that claim was structural: rutster held the wire.

ADR-0007 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: 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, since integrator unit economics run on cost-per-contained-call).

Amendment 2026-07-04 — enforcement locality ≠ accounting locality

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