diff --git a/architecture/metrics.md b/architecture/metrics.md index 3c5d8ed..ae9344b 100644 --- a/architecture/metrics.md +++ b/architecture/metrics.md @@ -58,3 +58,5 @@ Middleware and recorder are complementary, not redundant. Middleware owns `consu - `nacked_terminal(reason="max_deliveries")` — fires before consume opens Each seam fires for events the other physically cannot observe. Don't collapse them. + +**But they overlap on the bus-scope series.** Complementary is not the whole story: the recorder adapters (`PrometheusRecorder` / `OpenTelemetryRecorder`) and the native middleware both emit the consume/publish duration+count series under the *same* names (the recorders mirror upstream verbatim). Registering both against one Prometheus `CollectorRegistry` raises `Duplicated timeseries` — at whichever seam the user constructs *second*, upstream of the broker — and on OpenTelemetry, live meters on both **double-count** every consume/publish. A both-seams deployment must give them **separate registries** and treat the **middleware as the source of truth** for consume/publish (run the OTel middleware span-only). The library does **not** guard this at construction: the Prometheus collision is raised before the broker sees either seam, and "both seams configured" is indistinguishable from the correct dual-registry setup, so a guard would false-positive on the recommended configuration. It stays an operator concern — see `docs/usage/setup-prometheus-opentelemetry.md`.