From 3c567f7c84a7e12c1429f5eb16ca58d7bb65d04c Mon Sep 17 00:00:00 2001 From: Victor Kyriazakos Date: Sat, 25 Jul 2026 01:58:28 +0000 Subject: [PATCH] test(monitoring): assert background_work in snapshot + metric_names registration invariant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the cron-export test to assert background_work membership (behavior contract, not a frozen list), and add a regression guard that every gauge emitted in the runtime snapshot is also registered in the observable metric_names list — the silent-drop trap the extension guide documents. --- tests/monitoring/test_cron_health_export.py | 52 ++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/monitoring/test_cron_health_export.py b/tests/monitoring/test_cron_health_export.py index a44fa5ef2fc..2b78b020e11 100644 --- a/tests/monitoring/test_cron_health_export.py +++ b/tests/monitoring/test_cron_health_export.py @@ -190,12 +190,62 @@ def test_gateway_export_includes_cron_metrics_and_only_accepted_event_planes(mon snapshot = gateway_health_export._read_runtime_snapshot({}) - assert [metric.name for metric in snapshot.metrics] == ["hermes.cron.jobs.enabled"] + names = [metric.name for metric in snapshot.metrics] + # Cron metrics are folded into the gateway snapshot... + assert "hermes.cron.jobs.enabled" in names + # ...and the background/subagent-work gauge is appended (distinct from + # active_agents). Assert the relationship, not a frozen exact list. + assert "hermes.gateway.background_work" in names assert gateway_health_export._gateway_health_event({"event": "cron_execution"}) is True assert gateway_health_export._gateway_health_event({"event": "gateway_health"}) is True assert gateway_health_export._gateway_health_event({"event": "run"}) is False +def test_registered_observable_metric_names_cover_snapshot_metrics(monkeypatch): + """Every gauge emitted in the runtime snapshot must also be registered in the + observable-gauge metric_names list, or the OTLP exporter never observes it. + + This asserts the vocabulary-registration invariant documented in + docs/observability/monitoring.md: an emitted-but-unregistered gauge is + silently dropped. Regression guard for background_work / cron additions. + """ + import inspect + from agent.monitoring import gateway_health_export + + # Build a representative snapshot (gateway + cron + background_work) without + # a live gateway by stubbing the gateway snapshot to the real metric names. + class _M: + def __init__(self, name): + self.name = name + self.value = 0 + self.attributes = {} + + gateway_snapshot = type("S", (), {"metrics": [ + _M("hermes.gateway.up"), _M("hermes.gateway.active_agents"), + _M("hermes.gateway.busy"), _M("hermes.gateway.drainable"), + _M("hermes.gateway.restart_requested"), + _M("hermes.platform.up"), _M("hermes.platform.degraded"), + ]})() + cron_snapshot = type("S", (), {"metrics": [ + _M("hermes.cron.scheduler.heartbeat_age_seconds"), + _M("hermes.cron.scheduler.last_success_age_seconds"), + _M("hermes.cron.scheduler.catch_up_occurrences"), + _M("hermes.cron.jobs.enabled"), _M("hermes.cron.jobs.running"), + _M("hermes.cron.jobs.overdue"), + ]})() + monkeypatch.setattr(gateway_health_export, "_read_gateway_snapshot", lambda config: gateway_snapshot) + monkeypatch.setattr(gateway_health_export, "_read_cron_snapshot", lambda: cron_snapshot) + + snapshot_names = {m.name for m in gateway_health_export._read_runtime_snapshot({}).metrics} + + # Extract the registered metric_names list literal from _start_metric_provider. + src = inspect.getsource(gateway_health_export._start_metric_provider) + registered = {n for n in snapshot_names if f'"{n}"' in src} + + missing = snapshot_names - registered + assert not missing, f"gauges emitted but NOT registered in metric_names (will be silently dropped): {sorted(missing)}" + + def test_monitoring_docs_distinguish_relay_health_scope_and_terminal_flush(): from pathlib import Path