Commit graph

363 commits

Author SHA1 Message Date
Claude
f94cf20953 fix(satpass): coerce norad_ids to int set at comparison site
GUI saves norad_ids as JSON strings (["25544"]), wire delivers norad_id
as int.  Membership test `25544 in ["25544"]` was False — opt-in list
silently matched nothing.

Build allow_set as {int(x) for x in norad_ids_raw if str(x).isdigit()},
accept both string and int shapes forever.  Garbage entries silently
skipped.  satpass_cmd already coerces via [int(x) for x in cfg_ids].

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 23:36:18 +00:00
Claude
17808c3d65 feat(satpass): GUI safety controls panel — armed-state banner, dry-run toggle, rate cap
Tracking panel gains both new adapter-config keys:
- dry_run toggle (sky-blue accent, visually distinct from enabled)
- max_broadcasts_per_hour number input (1–60 range)

Armed-state banner at top of panel:
- OFF (grey) when disabled
- DRY RUN (sky-blue) when enabled + dry_run
- ⚠ LIVE (amber, pulsing) when enabled + !dry_run

Load/save through adapter-config API per 199929f pattern.
NORAD IDs helper updated to reflect opt-in semantics.

Bundle hash: index-DPN58SF4.js → index-Di1mw816.js
             index-CB06j1ej.css → index-WwNJt5S-.css

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 23:26:08 +00:00
Claude
e903444356 fix(satpass): broadcast safety controls — opt-in filter, rate cap, dry-run, wire format
Incident response for 343 broadcasts in 126s (2026-06-12 22:10 UTC).

Five safety controls:

1. OPT-IN BIRD FILTER: norad_ids=[] now means "broadcast nothing"
   (was "all birds"). Empty list logs once at INFO and suppresses all
   broadcasts. The !satpass DM command remains ungated — it queries
   any bird in the TLE cache using command_norad_ids as bare-command
   default. Two paths, two rules.

2. RATE CAP: new satpass.max_broadcasts_per_hour (int, default 4).
   Excess qualifying passes logged and suppressed. Broadcast path only.

3. DRY-RUN MODE: new satpass.dry_run (bool, default TRUE). Logs exact
   wire text at INFO prefixed "DRY-RUN would air:" without dispatching.
   Go-live: enabled=true + dry_run=true → observe → dry_run=false.

4. ELEVATION DEFAULT: min_elevation REGISTRY default already at 30
   (confirmed, no change needed).

5. BROADCAST WIRE FORMAT: two-line LoRa-tight format with buckets:
     🛰️ {name} {bucket}, {aos_compass}→{los_compass}
     {duration} minute window, {rise}–{set} {AM/PM} MDT
   Buckets: overhead (≥60°), high pass (30-59°), low pass (<30°).
   DM format keeps exact degrees. One format_pass() function with
   broadcast= mode switch — two callers, one function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 23:00:03 +00:00
0cb175d65b fix: correct satpass_handler wire field names + add pass. category prefix
satpass_handler.py: fix 6 field reads to match actual Central NATS
wire names (max_elevation_deg, satellite_name, observer_name/slug,
aos_time, los_time, azimuth_at_peak_compass).

consumer.py: add pass. prefix to _CATEGORY_MAP so
pass.n2yo_visualpasses routes to sat_pass instead of default-deny.
Add sat domain fallback to _SUBJECT_DOMAIN_CATEGORY.

Tests: 9 new tests using verbatim live NOAA-18 envelope from
central.sat.pass.us.id.filer (2026-06-10). Updates existing
test_satpass_handler.py fixtures to match corrected wire names.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 20:55:01 +00:00
199929ff1c fix: GUI satpass enabled toggle now persists through adapter-config API
The Tracking panel toggle for satpass.enabled was silently dropped on
save because SatpassConfig lacked the enabled field. The toggle wrote
to a phantom env.satpass.enabled (not in EnvConfig) which the
environmental config endpoint ignored. Handlers read enabled from the
adapter_config SQLite table, which was never updated.

Changes:
- Add enabled:boolean to SatpassConfig interface + initial state
- Convert GET /api/adapter-config/satpass array response to keyed dict
  so load actually reads saved values (fixes pre-existing load bug)
- Wire enabled into the save path via saveAdapterConfig PUT
- Override AdapterPanel enabled/onEnabled for satpass to use
  satpassConfig instead of the phantom env field
- Add test_bool_roundtrip.py: 6 tests proving PUT true/false round-
  trips through DB (value_json) and accessor (Python bool), plus
  second+third adapter boolean round-trips (wfigs, fires)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 20:16:07 +00:00
116e66369e test: satpass event path — wire adapter routing + enabled gate
13 tests covering the full sat event routing chain:

- CENTRAL_ADAPTER_TO_SOURCE maps celestrak_tle, n2yo_visualpasses,
  and satpass_predict to 'satpass' (3 tests)
- No stale adapter names (sat_passes, sat_tles, sat_tle) in map
- TLE handler inserts sat_tles row when enabled (integration)
- TLE handler drops when satpass.enabled=false
- Pass handler accepts n2yo_visualpasses and satpass_predict envelopes
- Pass handler rejects celestrak_tle envelope
- REGISTRY key is min_elevation (not min_elevation_deg)
- Handler source reads min_elevation (not min_elevation_deg)
- Consumer dispatch source has correct wire names

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 17:19:52 +00:00
be4443feb9 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>
2026-06-12 17:19:44 +00:00
4a17c7b702 fix: add satpass to region rewrite table in _subjects_for()
_subjects_for() consults a per-adapter region table when region is set
(e.g. us.id). The table had no satpass entry, so it returned [] and
Central silently subscribed to nothing for sat subjects.

Changes:
- Add satpass entry to the region table: pass alerts use Convention A
  (central.sat.pass.{region}.>), TLEs are global (central.sat.tle.>)
- Update test_central_region_default_propagates_to_consumer_subjects
  to expect satpass subjects (satpass defaults to feed_source=central)
- Add 3 tests in test_satpass_registration.py: _subjects_for with
  region, without region, and empty region fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 15:33:25 +00:00
aa2838fee3 fix: register satpass adapter on EnvironmentalConfig
_subject_owned() iterates _SUBJECTS_BARE keys and calls
getattr(self._env, attr) — but EnvironmentalConfig had no satpass
attribute, so Central never subscribed to central.sat.> subjects.

Changes:
- Add SatpassConfig(_SourcedFeed) with feed_source=central default
- Add satpass field to EnvironmentalConfig
- Add satpass YAML parsing handler in _dict_to_dataclass
- Add (satpass, enabled) to adapter_config REGISTRY
- Fix 3 satpass REGISTRY entries: type list -> json (vocabulary bug)
- Update stale count assertions (SCHEMA 16->17, REGISTRY 84->90,
  ADAPTER_META 21->23)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 07:39:44 +00:00
5418ed1f65 feat(satpass): Tier 2 on-demand satellite pass predictions (!satpass command)
Four-part implementation:
- Part 1: TLE cache handler subscribes central.sat.tle.>, upserts latest-wins
  on epoch, 14-day staleness exclusion at read time (tle_handler.py)
- Part 2: SGP4 pass predictor with 30s step propagation, ECI→topocentric
  look angles, grouped into PassInfo objects (pass_predictor.py)
- Part 3: ZCTA centroid CSV vendored (33,144 ZIPs), lazy-loaded for
  ZIP→lat/lon lookup with no runtime network dependency
- Part 4: !satpass command with three forms: bare (adapter_config defaults),
  name/NORAD ID, ZIP code observer. Location chain: GPS→ZIP→ask.
  Reply formatted as 'ISS 09:36-09:43 MDT max 64deg SW->NE'

Schema v16→v17 (sat_tles table). sgp4>=2.22 added to requirements.
24 new tests covering TLE upsert/staleness, pass prediction reference,
ZCTA lookup, command routing, location chain, and reply format.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 06:36:32 +00:00
886bb6510b fix: tombstone broadcast path — immediate severity + commit handles
Tombstone closure broadcasts were silently dropped because the handler
set severity to routine (below the dispatcher floor) and never called
_attach_commit_handles(), leaving event_log.handled=0 permanently.

Changes:
- wfigs_handler.py: tombstone severity routine → immediate
- wfigs_handler.py: attach commit handles with dedup suffix closed
- wfigs_handler.py: use _log_event_returning_id so commit callback
  can flip handled=1 on successful broadcast
- env_reporter.py: build_env_summary fire count excludes tombstoned
  and 100%-contained fires (matches build_fires_detail WHERE)
- tests/test_tombstone_broadcast.py: 11 tests covering severity,
  commit handles, dedup suffix, callback, closure wire, and
  env_summary exclusion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 05:48:25 +00:00
b5a8f5cf97 fix(fire_digest): trim lines to name + location, correct tail count after budget trim
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 04:39:57 +00:00
bf88bdc9e6 fix(fires): recency-only ordering, exclude contained/tombstoned in LLM context and digest
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 04:17:50 +00:00
fa79fa1c9e fix(env_reporter): fire context hybrid recent+largest, exclude tombstoned
Chat LLM context selected top-10 fires by acreage only; small fresh
incidents (IA/assist fires) ranked below the cutoff and were invisible
to chat. Also excluded: tombstoned fires were listed as active.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 03:14:14 +00:00
1f34f74e5a feat(satpass): add satellite pass GUI to Environment + Notifications pages
- Environment: add satpass adapter to Tracking tab with observers,
  min_elevation, and norad_ids controls via adapter_config API
- Notifications: add satpass family to master toggles
- Rebuild dashboard bundle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 00:10:51 +00:00
782899d4b1 feat(satpass): add satellite pass alert family (tier 1)
- Add satpass_handler.py with observer/elevation/NORAD filters
- Subscribe to central.sat.pass.> in consumer.py
- Add satpass toggle family to config.py
- Add sat_pass category mapping in categories.py
- Add satpass config section in adapter_config/defaults.py
- Add 9 unit tests for handler logic

Wire format: 3-line LoRa-tight (sat name, AOS/LOS times, observer)
Dedup: hourly bucket per sat/observer
Severity: 60deg+=immediate, 45deg+=priority, else routine
2026-06-11 23:23:24 +00:00
meshai-fix
40ad40f277 fix(dispatcher): commit guards only after successful delivery (B13) + dedup suffix for WFIGS lifecycle updates 2026-06-11 17:44:02 +00:00
98e3fcf675 Merge branch 'feature/mesh-intelligence' 2026-06-10 21:03:08 +00:00
b36223146f fix: harden EnvironmentalStore adapter init — isolate failures
- Wrap each adapter registration in try/except via _register_adapter()
- Track failed adapters in _failed_adapters dict with error message
- Add get_status() method for /api/env/status to report failed adapters
- No single adapter failure can abort loading of remaining adapters

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-10 20:25:50 +00:00
3dd48b6337 fix: Carbon sweep — TownAnchors + GaugeSites cyan/slate → Carbon tokens
- Replace bg-slate-800/60 and bg-slate-800 with bg-bg-card/bg-bg
- Replace border-slate-700 with border-border
- Replace divide-slate-700/60 with divide-border
- Replace hover:bg-slate-800/50 and hover:bg-slate-700 with hover:bg-bg-hover
- Remove all rounded classes for sharp Carbon edges

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-10 19:55:02 +00:00
72611cc148 feat: move Photon geocoder config to config.yaml — public komoot default, local override in deployment config 2026-06-10 19:49:26 +00:00
f65d1d2f59 fix: Carbon sweep — GaugeSites, TownAnchors, Notifications, Alerts — teal/cyan/blue → amber 2026-06-10 19:42:39 +00:00
bce11b2196 fix: Layout.tsx — logo container bg, Carbon nav colors, remove stale blue active states 2026-06-10 19:37:13 +00:00
e181d0c126 feat: MeshAI logo PNG in sidebar, favicon wired 2026-06-10 19:33:58 +00:00
768f6e72ac Merge branch 'feature/mesh-intelligence' 2026-06-10 15:44:54 +00:00
06a5cc23ef fix: Carbon theme — bump muted text tiers for legibility (#333/#444 → #666/#777)
text-[#333] → text-[#666] (63 hits), text-[#444] → text-[#777] (38 hits)
across all tsx/css. Scrollbar thumb #1e1e1e → #2a2a2a.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 15:44:48 +00:00
7de460804f Merge branch 'feature/mesh-intelligence' 2026-06-10 15:37:33 +00:00
d0bf298f89 feat: Carbon theme — amber accent, Inter/JetBrains Mono, sharp corners, traffic-light semantic colors across all components
Global: removed all rounded-lg/md/sm classes, replaced blue-500 with
sky-400 informational, cyan accents with amber across all tsx files.
Environment.tsx + AdapterConfig.tsx: full Carbon color sweep — slate
hierarchy replaced with #333/#444/white tokens, border-border tokens,
font-sans labels, font-mono values. Layout.tsx logo text-[15px].

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 15:37:27 +00:00
f054d80efc Merge branch 'feature/mesh-intelligence' 2026-06-10 14:37:49 +00:00
6b08cdc004 feat: apply Carbon theme — amber accent, Inter/JetBrains Mono, sharp corners, Lucide icons preserved
Carbon palette: #111111/#0d0d0d backgrounds, #1e1e1e borders, traffic-light
data colors (green/red/sky/amber), 10px uppercase card headers, StatCard
colored border-tops, Layout sidebar amber bar + right nav indicator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 14:37:33 +00:00
c0ceddff08 Merge branch 'feature/mesh-intelligence' 2026-06-10 07:19:48 +00:00
88554eb252 feat: dashboard visual overhaul — amber/blue/danger accents, Inter + JetBrains Mono, sharp corners, tighter density
- Tailwind config: semantic color tokens (amber/blue/danger), borderRadius all zero except full, Inter + JetBrains Mono font families
- index.css: Google Fonts imports, sharp scrollbar, scanline card texture, amber-glow utility
- Dashboard.tsx: three-color accent system (amber primary, blue informational, danger alerts), Inter for labels/prose, JetBrains Mono strictly for data values, all rounded corners removed, tighter spacing (p-4 cards, gap-4 grids, space-y-2 lists), underline-style tabs with amber active indicator, band condition dot indicators, source status dots remapped to semantic colors, StatCard supports per-instance value coloring, 36px minimum touch targets on interactive elements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 07:19:38 +00:00
d8867433a1 Merge branch 'feature/mesh-intelligence' 2026-06-10 06:53:59 +00:00
af51c51708 feat: dashboard layout — tabbed alerts/feed, tropo in middle row, fix emoji
- Active Alerts panel now has pill-style tab switcher (Active Alerts | Event Feed)
- LiveEventFeed supports embedded mode (no card wrapper) for tab use
- HepburnTropoCard moved from bottom row to middle row (replaces LiveEventFeed)
- Bottom row removed (empty after tropo card move)
- Fixed broken unicode escapes in BandConditionsCard (satellite, circle, moon emoji)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 06:53:48 +00:00
825df8a3a8 Merge branch 'feature/mesh-intelligence' 2026-06-10 06:15:43 +00:00
3e06aacc3f feat: hepburn tropo card — region selector dropdown persisted to adapter_config
Adds HepburnTropoCard to the dashboard with a 23-region dropdown
selector. Selected region is persisted to adapter_config via
(dashboard, tropo_region) key, defaulting to wam (Western North
America). Image loads from dxinfocentre.com/tr_map/fcst/{code}006.png
with date-based cache busting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 06:15:36 +00:00
ea5b6f5306 Merge branch 'feature/mesh-intelligence' 2026-06-10 06:05:01 +00:00
6989a49f64 fix: periodic health broadcast missing pillars — add full score payload + validity gate
The periodic health_update in main.py only sent score/tier/totals but
no pillars dict. Dashboard setHealth() replaced the full REST response
with this incomplete object, causing all pillar bars to drop to 0.0.

Now sends the complete payload matching mesh_routes.py and ws.py
(pillars, util_percent, flagged_nodes, battery_warnings, recommendations)
and gates on composite > 0 + infra_total > 0 to skip partial state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 06:04:56 +00:00
6d39da9a31 Merge branch 'feature/mesh-intelligence' 2026-06-10 05:58:03 +00:00
a119de7d86 fix: ws health_update — add coverage pillar + gate broadcast on valid computed score
Coverage pillar was added to mesh_routes.py in 15f2b6c but missed in
the WebSocket serializer, sending undefined for coverage on every WS
push. Also gates the initial health_update broadcast on composite > 0
and infra_total > 0 to prevent partially-initialized HealthScore from
overwriting good REST-fetched data on the frontend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 05:57:58 +00:00
2a8eb0282b Merge branch 'feature/mesh-intelligence' 2026-06-10 05:54:01 +00:00
5d8a277fa7 feat: dashboard — DB-backed live feed, active alerts, band conditions panel (replace env_store dependency)
- /api/env/active: direct DB queries for fires, nws_alerts, quake_events
  instead of env_store.get_active() (which depends on live NATS data)
- /api/env/swpc: reads band_conditions_broadcasts table, returns ratings
  with slot label (Day/Night Propagation) derived from Mountain Time
- Frontend: replace RFPropagationCard (SFI/Kp/R/S/G charts) with
  BandConditionsCard showing 4-band Good/Fair/Poor ratings
- Remove unused recharts dependency from Dashboard.tsx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 05:53:55 +00:00
ea22f2154a Merge branch 'feature/mesh-intelligence' 2026-06-10 05:40:26 +00:00
82567c6a90 fix: docker-compose — add 1.1.1.1 fallback to Tailscale MagicDNS resolver
100.100.100.100 resolves .echo6.mesh hostnames (including
central.echo6.mesh for the NATS bus). 1.1.1.1 provides fallback
for public DNS when MagicDNS is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 05:40:15 +00:00
c42db799f6 Merge branch 'feature/mesh-intelligence' 2026-06-10 04:26:53 +00:00
6f76c897e5 fix: add missing _dict_to_dataclass branch for EnvironmentalConfig
Without this branch, the environmental config dict falls through to the
bare else clause, storing a raw dict instead of an EnvironmentalConfig
dataclass. This causes AttributeError when accessing env_cfg.enabled or
any nested feed config attributes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 04:26:44 +00:00
c1df20b871 Merge branch 'feature/mesh-intelligence' 2026-06-10 03:56:24 +00:00
15f2b6c89a fix: dashboard — coverage pillar, active alerts env fallback
- Add coverage pillar to /api/health response and _serialize_health_score
- Add coverage to MeshHealth.pillars TypeScript interface
- Add Coverage PillarBar between Utilization and Behavior
- Active Alerts panel: show high-severity env events (immediate/priority)
  as fallback when mesh alerts are empty, with ENV badge

Issues 3 (Live Event Feed) and 4 (RF Propagation): diagnosed as
env feed configuration — SWPC adapter disabled, only ducting feed
loaded, /api/env/active returns 0 events. Not a code bug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 03:56:12 +00:00
26ba1d96a4 merge: test assertion updates from feature/mesh-intelligence 2026-06-10 03:43:29 +00:00
dcb53ae30c test: update stale assertions post feature/mesh-intelligence merge
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 03:43:06 +00:00