fix: wire adapter name mismatch in satpass event path

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 <noreply@anthropic.com>
This commit is contained in:
Matt Johnson (via Claude) 2026-06-12 17:19:44 +00:00
commit be4443feb9
5 changed files with 51 additions and 14 deletions

View file

@ -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":

View file

@ -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:

View file

@ -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 {}

View file

@ -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",

View file

@ -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