# 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.