fix(satpass): default feed_source to native; correct misleading config comments (#139)

Central is retired -- its NATS broker (nats://central.echo6.mesh:4222) no
longer exists -- but SatpassConfig.feed_source still defaulted to "central",
overriding the _SourcedFeed mixin default of "native". A fresh install that
enabled satpass got a silently dead adapter. The dashboard reinforced it:
Environment.tsx seeded feed_source 'central' and labelled the adapter
"via Central".

Also corrects comments that documented the opposite of the code: the
adapter_config keys usgs_quake.global_mag_floor / regional_mag_floor were
labelled "CENTRAL-PATH ONLY", but notifications/gating/quake.py reads them
unconditionally in the native path. Following those comments would have led
someone to delete live config keys.

- config.py: SatpassConfig.feed_source "central" -> "native" + docstring
- adapter_config/defaults.py: correct the two "CENTRAL-PATH ONLY" comments
- Environment.tsx: seed 'native'; reword the satpass subtitle
- tests: assert the native default; pin central explicitly where a test
  exercises the central path or the feed_source flip

Suite: 2337 passed, 6 pre-existing failures (stale SCHEMA_VERSION x3,
expired TLE fixtures x2, one order-dependent), no new failures.

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
malice 2026-07-17 14:09:24 -06:00 committed by GitHub
commit e9daabfbff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 26 additions and 21 deletions

View file

@ -66,7 +66,7 @@ const SATPASS_NATIVE_DEFAULT: EnvConfig['satpass'] = {
window_hours: 24, window_hours: 24,
tle_refresh_seconds: 21600, tle_refresh_seconds: 21600,
broadcast_lead_seconds: 3600, broadcast_lead_seconds: 3600,
feed_source: 'central', feed_source: 'native',
} }
// Sane defaults for the ipaws block so a GET payload predating the IPAWS // Sane defaults for the ipaws block so a GET payload predating the IPAWS
@ -304,7 +304,7 @@ const META: Record<AdapterKey, AdapterMeta> = {
usgs_quake: { label: 'USGS Earthquakes', subtitle: 'Seismic events from the USGS feed', health: 'usgs_quake', hasCentral: true, nativeOnly: false, hasKey: true }, usgs_quake: { label: 'USGS Earthquakes', subtitle: 'Seismic events from the USGS feed', health: 'usgs_quake', hasCentral: true, nativeOnly: false, hasKey: true },
usgs: { label: 'USGS Stream Gauges', subtitle: 'River and stream water levels', health: 'usgs', hasCentral: true, nativeOnly: false, hasKey: true }, usgs: { label: 'USGS Stream Gauges', subtitle: 'River and stream water levels', health: 'usgs', hasCentral: true, nativeOnly: false, hasKey: true },
avalanche: { label: 'Avalanche Advisories', subtitle: 'Backcountry avalanche danger ratings', health: 'avalanche', hasCentral: true, nativeOnly: false, hasKey: true }, avalanche: { label: 'Avalanche Advisories', subtitle: 'Backcountry avalanche danger ratings', health: 'avalanche', hasCentral: true, nativeOnly: false, hasKey: true },
satpass: { label: 'Satellite Passes', subtitle: 'Observer pass alerts via Central', health: 'satpass', hasCentral: true, nativeOnly: false, hasKey: true }, satpass: { label: 'Satellite Passes', subtitle: 'Observer pass alerts from native TLE tracking', health: 'satpass', hasCentral: true, nativeOnly: false, hasKey: true },
ipaws: { label: 'FEMA IPAWS civil alerts', subtitle: 'Evacuations, AMBER, HazMat, 911 outages (non-weather)', health: 'ipaws', hasCentral: false, nativeOnly: true, hasKey: false }, ipaws: { label: 'FEMA IPAWS civil alerts', subtitle: 'Evacuations, AMBER, HazMat, 911 outages (non-weather)', health: 'ipaws', hasCentral: false, nativeOnly: true, hasKey: false },
} }

View file

@ -105,12 +105,12 @@ REGISTRY: dict[tuple[str, str], dict[str, Any]] = {
("usgs_quake", "global_mag_floor"): { ("usgs_quake", "global_mag_floor"): {
"default": 3.0, # quake_handler.py:69 "default": 3.0, # quake_handler.py:69
"type": "float", "type": "float",
"description": "Global magnitude floor for unconditional broadcasts. CENTRAL-PATH ONLY (central/quake_handler.py); the native feed_source gates on environmental.usgs_quake.min_magnitude instead.", "description": "Global magnitude floor for unconditional broadcasts. Gates the shared native gating decider (meshai.notifications.gating.quake.decide), read regardless of feed_source.",
}, },
("usgs_quake", "regional_mag_floor"): { ("usgs_quake", "regional_mag_floor"): {
"default": 2.5, # quake_handler.py:70 "default": 2.5, # quake_handler.py:70
"type": "float", "type": "float",
"description": "Reduced magnitude floor for quakes within regional_radius_mi of centroid. CENTRAL-PATH ONLY (central/quake_handler.py); the native feed_source gates on environmental.usgs_quake.min_magnitude instead.", "description": "Reduced magnitude floor for quakes within regional_radius_mi of centroid. Gates the shared native gating decider (meshai.notifications.gating.quake.decide), read regardless of feed_source.",
}, },
("usgs_quake", "escalate_mag_floor"): { ("usgs_quake", "escalate_mag_floor"): {
"default": 5.0, # quake_handler.py:76 "default": 5.0, # quake_handler.py:76

View file

@ -444,8 +444,10 @@ class USGSQuakeConfig(_SourcedFeed):
# Native-path broadcast magnitude floor: the native adapter # Native-path broadcast magnitude floor: the native adapter
# (env/usgs_quake.py) gates on this. THIS is the GUI-editable quake # (env/usgs_quake.py) gates on this. THIS is the GUI-editable quake
# magnitude floor for the native feed_source. (The adapter_config # magnitude floor for the native feed_source. (The adapter_config
# REGISTRY keys usgs_quake.global_mag_floor / regional_mag_floor apply # REGISTRY keys usgs_quake.global_mag_floor / regional_mag_floor are
# only to the Central-firehose path in central/quake_handler.py.) # separate: they gate the shared gating decider,
# meshai.notifications.gating.quake.decide(), which both the native
# path and central/quake_handler.py delegate to.)
min_magnitude: float = 2.5 min_magnitude: float = 2.5
# [west, south, east, north] -- Magic Valley -> Borah Peak -> Yellowstone # [west, south, east, north] -- Magic Valley -> Borah Peak -> Yellowstone
bbox: list = field(default_factory=lambda: [-115.5, 42.0, -110.0, 45.2]) bbox: list = field(default_factory=lambda: [-115.5, 42.0, -110.0, 45.2])
@ -522,15 +524,15 @@ class FIRMSConfig(_SourcedFeed):
class SatpassConfig(_SourcedFeed): class SatpassConfig(_SourcedFeed):
"""Satellite pass prediction settings. """Satellite pass prediction settings.
Historically a Central-only feed (`feed_source="central"`). The native Native (`feed_source="native"`) is the default and only supported path:
path (`feed_source="native"`) adds a Celestrak TLE fetcher a Celestrak TLE fetcher (`env.tle_fetch`) plus a native SGP4 predictor;
(`env.tle_fetch`) and a native SGP4 predictor; the fields below feed the fields below feed those. Central is RETIRED its NATS broker
those. `feed_source` default stays "central" the flip to "native" (`nats://central.echo6.mesh:4222`) no longer exists so this no longer
happens at cutover, not here. overrides the `_SourcedFeed` mixin default.
""" """
enabled: bool = False enabled: bool = False
feed_source: str = "central" feed_source: str = "native"
# -- native path (TLE fetch + SGP4 predictor) ----------------------------- # -- native path (TLE fetch + SGP4 predictor) -----------------------------
# Ground stations the predictor computes passes for; each entry is a # Ground stations the predictor computes passes for; each entry is a

View file

@ -114,9 +114,12 @@ def test_feed_source_flip_to_central_requires_restart_and_does_not_swap():
def test_feed_source_flip_from_central_to_native_requires_restart(): def test_feed_source_flip_from_central_to_native_requires_restart():
# satpass defaults feed_source="central"; flip it to native. # satpass now defaults to feed_source="native", so pin the starting state
store = EnvironmentalStore(_cfg()) # to "central" explicitly -- this exercises the flip, not the default.
assert "satpass" not in store._adapters # central by default -> no native instance base_cfg = _cfg()
base_cfg.satpass = dataclasses.replace(base_cfg.satpass, feed_source="central")
store = EnvironmentalStore(base_cfg)
assert "satpass" not in store._adapters # central -> no native instance
new_cfg = _cfg() new_cfg = _cfg()
new_cfg.satpass = dataclasses.replace( new_cfg.satpass = dataclasses.replace(

View file

@ -1,7 +1,7 @@
"""Tests for satpass adapter registration on EnvironmentalConfig. """Tests for satpass adapter registration on EnvironmentalConfig.
Verifies: Verifies:
- SatpassConfig exists and defaults to feed_source="central" - SatpassConfig exists and defaults to feed_source="native"
- EnvironmentalConfig.satpass field is present and correctly typed - EnvironmentalConfig.satpass field is present and correctly typed
- _subject_owned() includes central.sat.* subjects when satpass is registered - _subject_owned() includes central.sat.* subjects when satpass is registered
- adapter_config REGISTRY contains satpass keys with valid types - adapter_config REGISTRY contains satpass keys with valid types
@ -15,7 +15,7 @@ import pytest
def test_satpass_config_exists(): def test_satpass_config_exists():
from meshai.config import SatpassConfig from meshai.config import SatpassConfig
cfg = SatpassConfig() cfg = SatpassConfig()
assert cfg.feed_source == "central" assert cfg.feed_source == "native"
assert cfg.enabled is False assert cfg.enabled is False
@ -37,10 +37,10 @@ def test_environmental_config_has_satpass():
assert isinstance(env.satpass, SatpassConfig) assert isinstance(env.satpass, SatpassConfig)
def test_environmental_satpass_default_central(): def test_environmental_satpass_default_native():
from meshai.config import EnvironmentalConfig from meshai.config import EnvironmentalConfig
env = EnvironmentalConfig() env = EnvironmentalConfig()
assert env.satpass.feed_source == "central" assert env.satpass.feed_source == "native"
# -- adapter_config REGISTRY -------------------------------------------------- # -- adapter_config REGISTRY --------------------------------------------------
@ -87,10 +87,10 @@ def test_yaml_parsing_satpass():
from meshai.config import SatpassConfig, _dict_to_dataclass, EnvironmentalConfig from meshai.config import SatpassConfig, _dict_to_dataclass, EnvironmentalConfig
import yaml import yaml
yaml_str = "environmental:\n satpass:\n enabled: true\n feed_source: central\n" yaml_str = "environmental:\n satpass:\n enabled: true\n feed_source: native\n"
data = yaml.safe_load(yaml_str) data = yaml.safe_load(yaml_str)
env = _dict_to_dataclass(EnvironmentalConfig, data["environmental"]) env = _dict_to_dataclass(EnvironmentalConfig, data["environmental"])
assert isinstance(env.satpass, SatpassConfig) assert isinstance(env.satpass, SatpassConfig)
assert env.satpass.enabled is True assert env.satpass.enabled is True
assert env.satpass.feed_source == "central" assert env.satpass.feed_source == "native"