Files
rutster/docs/adr/0005-event-bus.md

67 lines
3.6 KiB
Markdown

# ADR-0005 — Event bus & state store: Valkey
- **Status:** Accepted
- **Date:** 2026-06
- **Closes:** the event-bus open-decision carried in the founding docs (NATS vs. Kafka vs. Redis Streams)
## Context
The fused per-call core ([ADR-0002](0002-north-star-and-fused-core.md)) needs:
- a **bus** for cross-service control events (replacing Asterisk's internal Stasis bus for
anything crossing the boundary),
- a **KV state store** replacing `astdb` + sorcery / realtime config state,
- **presence sets** for MWI/BLF signaling aggregation.
The founding docs listed the choice as NATS vs. Kafka vs. Redis Streams.
## Decision
**Valkey** — the Linux-Foundation BSD-3-Clause fork of Redis 7.2.4 (wire-protocol-compatible drop-in).
### Why not "Redis"
Redis relicensed in March 2024 to **RSALv2 + SSPLv2****not OSI-open-source**, and license-incompatible with this project's posture (FOSS-clean + GPL-3.0-or-later per [ADR-0004](0004-license.md)).
Recommending "Redis" undercuts the data-ownership pillar ([ADR-0002](0002-north-star-and-fused-core.md)) and the clean-license story. Valkey is *Redis-the-thing* with a compatible license, maintained by the community (AWS, Google, Oracle, et al.).
### Why Valkey fits the wedge specifically
- **Collapses bus + state store + presence into one dependency.** A solo operator runs one process
with `compose up`. NATS is a cleaner pure-bus but doesn't also replace `astdb`/sorcery; Kafka is
structural overkill at self-hosting scale and violates the one-binary / operational-simplicity
pillar.
- One tool, three roles:
- **streams + consumer groups** → CDR/CEL/analytics pipeline fanout and replay,
- **pub/sub** → presence / MWI / BLF signaling,
- **KV** → state store (config + runtime state replacing `astdb`/sorcery).
### Alternatives
- **Kafka — rejected.** Structural overkill at self-hosting scale; operational weight violates
the operational-simplicity pillar.
- **NATS — retained as a config-pluggable alternative.** An operator outgrowing Valkey (notably
needing NATS JetStream's durable streaming model at larger scale) can swap it in. The bus
backend seam stays **pluggable** at the config boundary, not architecture-load-bearing.
## Constraints (load-bearing, not preferences)
1. **The 20ms media loop never rides the bus.** Media timing stays in-core on dedicated timing
threads ([ADR-0002](0002-north-star-and-fused-core.md) §7; vision-revision §7). The bus carries
control / cross-service events only — not latency-pinned media — so Valkey's sub-ms local
latency is plenty.
2. **The bus is NOT the source of truth for billing- or call-loss-critical state.** Valkey
persistence (RDB/AOF) is async-ish — fine for transport / replay / fleeting retention, **wrong**
for "the CDR that proves what we billed." CDR and recordings emit **durably to object storage**
in their own services; the bus only *flows* events into that pipeline and lets services *react*.
3. **Constraint 2 governs the durable CDR, not enforcement counters.** The spend gate's
live accounting state (spend/pacing/rate counters) is ephemeral control state and DOES
belong in Valkey KV at N>1 — see the
[ADR-0009 amendment 2026-07-04](0009-spend-gate-honest-rescope.md#amendment-2026-07-04--enforcement-locality--accounting-locality).
## Consequences
- **Positive:** one OSS dep for bus + state + presence; self-hostable and license-clean; pluggable
to NATS later without architecture changes; aligns with operational-simplicity.
- **Negative:** Valkey is "good enough" at each role, not best-of-breed for each — accepted
deliberately for the ops-simplicity pillar.