The dual-store architecture

Two independent stores hold time series, and the relationship between them is a
deliberate design rather than a migration artefact.

The in-process ring buffer is filled by the sampler on every five-second
tick. It holds two resolutions: a fast bucket at five-second spacing covering one
hour, and a slow bucket at five-minute spacing covering thirty days. Per-subscriber
series live in the fast bucket only, with four hours of retention. The buffer is
periodically persisted so a process restart does not lose the recent window.

Prometheus runs as a container bound to loopback, scraping QuantumTouch's own
/metrics endpoint every fifteen seconds, with thirty days or five gigabytes of
retention, whichever binds first. Every series carries two external labels
identifying the plane and the box.

The rule governing the two is precise and worth internalising:

Prometheus is authoritative for history. The controller live path is
authoritative for "right now". The ring buffer is the standby for both, and the
permanent owner of per-subscriber series.

Queries route through a facade that reads Prometheus first and falls back to the
ring buffer. The fallback tree distinguishes environmental failure from our
failure:

Condition Behaviour
Prometheus not scraping us Serve from sampler, reason: "prom_down"
Prometheus returned empty, sampler has data Serve from sampler, reason: "prom_empty"
Prometheus unreachable or timed out Serve from sampler, reason names the fault
Prometheus returned a query error Raise 502 — do not fall back

The last row is the important one. A malformed query or a Prometheus 5xx is a
defect on our side, and silently serving older numbers from the fallback store
would mask it. Degradation falls back; bugs surface.

Which store answered is plumbed to the client, not hidden — responses carry a
source field. The UI renders a badge distinguishing historical from recent data
and a banner when it is running degraded. An integrating script should record
source alongside the data if provenance matters to it.

Cardinality is bounded by construction. Per-subscriber series are
architecturally excluded from Prometheus; they live only in the ring buffer and
on the controller's on-demand endpoints. The total series count on a BNG is
around sixty-five, roughly five megabytes at thirty days. This bounds storage,
bounds query cost, and bounds the blast radius of an over-broad query or a leaked
scrape credential — there is no per-subscriber data in the TSDB to expose.