From be4443feb9de2a3641cd93f6030a8f630a9f580b Mon Sep 17 00:00:00 2001 From: "Matt Johnson (via Claude)" Date: Fri, 12 Jun 2026 17:19:44 +0000 Subject: [PATCH] fix: wire adapter name mismatch in satpass event path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Central publishes sat events with wire adapter names celestrak_tle, n2yo_visualpasses, and satpass_predict. Task C's routing matched on sat_tles/sat_tle/sat_passes — names that exist nowhere on the wire — so every sat event was silently dropped at three kill points: 1. CENTRAL_ADAPTER_TO_SOURCE map (consumer.py line ~216) 2. _normalize dispatch conditionals (consumer.py line ~549) 3. satpass_handler adapter guard (satpass_handler.py line ~118) All three now use the correct wire names. Both handlers also gain an enabled gate that returns None when satpass.enabled=false, logging at INFO once per process lifetime via a _disabled_logged function flag. Existing test fixtures updated for the new adapter names and the enabled gate (cfg.enabled=True in mock, _disabled_logged cleanup). Co-Authored-By: Claude Opus 4.6 --- meshai/central/consumer.py | 10 +++++----- meshai/central/satpass_handler.py | 15 ++++++++++----- meshai/central/tle_handler.py | 11 +++++++++++ tests/test_satpass_command.py | 23 ++++++++++++++++++++--- tests/test_satpass_handler.py | 6 +++++- 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/meshai/central/consumer.py b/meshai/central/consumer.py index 33f4fff..77f61cc 100644 --- a/meshai/central/consumer.py +++ b/meshai/central/consumer.py @@ -214,9 +214,9 @@ CENTRAL_ADAPTER_TO_SOURCE: dict[str, str] = { "itd_511": "roads511", "avalanche_org": "avalanche", "firms": "firms", - "sat_passes": "satpass", - "sat_tles": "satpass", - "sat_tle": "satpass", + "celestrak_tle": "satpass", + "n2yo_visualpasses": "satpass", + "satpass_predict": "satpass", } # Central hierarchical category prefix -> meshai flat category. @@ -546,10 +546,10 @@ class CentralConsumer: # commit #5 (env_reporter). Closes the v0.5.13 # silent-drop on central.fire.hotspot.> (audit doc # finding #2). - elif inner.get("adapter") in ("sat_tles", "sat_tle"): + elif inner.get("adapter") == "celestrak_tle": from meshai.central.tle_handler import handle_tle synthesized = handle_tle(envelope, subject, data=data) or None - elif inner.get("adapter") == "sat_passes": + elif inner.get("adapter") in ("n2yo_visualpasses", "satpass_predict"): from meshai.central.satpass_handler import handle_satpass synthesized = handle_satpass(envelope, subject, data=data) or None elif inner.get("adapter") == "firms": diff --git a/meshai/central/satpass_handler.py b/meshai/central/satpass_handler.py index 14992c2..de488f0 100644 --- a/meshai/central/satpass_handler.py +++ b/meshai/central/satpass_handler.py @@ -115,8 +115,16 @@ def handle_satpass(envelope: dict, subject: str, inner = envelope.get("data") or {} adapter = inner.get("adapter") or "" - # Only handle sat_passes adapter - if adapter != "sat_passes": + # Only handle pass prediction adapters (wire names from Central) + if adapter not in ("n2yo_visualpasses", "satpass_predict"): + return None + + # Enabled gate: silently drop when disabled, log once at INFO + cfg = adapter_config.satpass + if not getattr(cfg, "enabled", False): + if not getattr(handle_satpass, "_disabled_logged", False): + logger.info("satpass disabled; sat pass events dropped") + handle_satpass._disabled_logged = True return None d = inner.get("data") or {} @@ -142,9 +150,6 @@ def handle_satpass(envelope: dict, subject: str, logger.debug("satpass_handler: could not parse aos time") return None - # Config filters - cfg = adapter_config.satpass - # Observer filter (empty = all) observers = getattr(cfg, "observers", []) or [] if observers and observer not in observers: diff --git a/meshai/central/tle_handler.py b/meshai/central/tle_handler.py index edf980a..b0fc5bf 100644 --- a/meshai/central/tle_handler.py +++ b/meshai/central/tle_handler.py @@ -34,6 +34,17 @@ def handle_tle(envelope: dict, subject: str, inner = envelope.get("data") or {} adapter = inner.get("adapter") or "" + # Enabled gate: silently drop when disabled, log once at INFO + try: + from meshai.adapter_config import adapter_config + if not getattr(adapter_config.satpass, "enabled", False): + if not getattr(handle_tle, "_disabled_logged", False): + logger.info("satpass disabled; sat TLE events dropped") + handle_tle._disabled_logged = True + return None + except Exception: + pass # adapter_config may not be initialised in tests + # Accept both sat_tles and sat_passes adapter (Central may tag either) d = inner.get("data") or {} diff --git a/tests/test_satpass_command.py b/tests/test_satpass_command.py index 3ddf60c..4bac7ce 100644 --- a/tests/test_satpass_command.py +++ b/tests/test_satpass_command.py @@ -34,6 +34,20 @@ import pytest from meshai.persistence import get_db +def _enable_satpass(): + """Set satpass.enabled=true for TLE handler tests.""" + conn = get_db() + conn.execute( + "UPDATE adapter_config SET value_json='true' " + "WHERE adapter='satpass' AND key='enabled'" + ) + try: + from meshai.adapter_config import invalidate_cache + invalidate_cache() + except Exception: + pass + + # Well-known ISS TLE (epoch ~2024-06-15) ISS_LINE1 = "1 25544U 98067A 24167.54791667 .00016717 00000-0 10270-3 0 9003" ISS_LINE2 = "2 25544 51.6400 187.5200 0001234 35.0000 325.0000 15.49920000 07" @@ -56,6 +70,7 @@ class TestTLEUpsert: """T1: TLE upsert latest-wins on epoch.""" def test_newer_epoch_updates(self): + _enable_satpass() from meshai.central.tle_handler import handle_tle conn = get_db() now = int(time.time()) @@ -67,7 +82,7 @@ class TestTLEUpsert: # Send newer TLE env = { "data": { - "adapter": "sat_tles", + "adapter": "celestrak_tle", "data": { "norad_id": 25544, "satellite_name": "ISS (ZARYA)", @@ -84,6 +99,7 @@ class TestTLEUpsert: assert row["line2"] == "NEW2" def test_older_epoch_skipped(self): + _enable_satpass() from meshai.central.tle_handler import handle_tle conn = get_db() now = int(time.time()) @@ -95,7 +111,7 @@ class TestTLEUpsert: # Send older TLE — should be skipped env = { "data": { - "adapter": "sat_tles", + "adapter": "celestrak_tle", "data": { "norad_id": 25544, "satellite_name": "ISS (ZARYA)", @@ -112,10 +128,11 @@ class TestTLEUpsert: def test_returns_none_always(self): """TLE handler is storage-only, never returns wire.""" + _enable_satpass() from meshai.central.tle_handler import handle_tle env = { "data": { - "adapter": "sat_tles", + "adapter": "celestrak_tle", "data": { "norad_id": 99999, "satellite_name": "TEST", diff --git a/tests/test_satpass_handler.py b/tests/test_satpass_handler.py index 020028d..b763ee9 100644 --- a/tests/test_satpass_handler.py +++ b/tests/test_satpass_handler.py @@ -14,7 +14,7 @@ def _envelope(norad_id=25544, sat_name="ISS", observer="Boise", "source": "central", "id": f"pass-{norad_id}-{aos}", "data": { - "adapter": "sat_passes", + "adapter": "n2yo_visualpasses", "category": "sat.pass", "severity": 0, "data": { @@ -44,11 +44,15 @@ def mock_db(): def mock_adapter_config(): """Mock adapter_config.satpass.""" cfg = MagicMock() + cfg.enabled = True cfg.observers = [] # empty = all observers cfg.min_elevation = 30 cfg.norad_ids = [] # empty = all satellites with patch("meshai.central.satpass_handler.adapter_config") as mock: mock.satpass = cfg + from meshai.central.satpass_handler import handle_satpass + if hasattr(handle_satpass, "_disabled_logged"): + del handle_satpass._disabled_logged yield cfg