Commit graph

361 commits

Author SHA1 Message Date
f0b1a0de19 test(toggles): update severity_threshold for v0.7 matrix-only semantics
v0.7 removed min_severity as a guard; the severity_channels matrix IS
the threshold. Empty matrix row = no delivery.

Updated tests to use empty matrix rows instead of min_severity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 00:50:49 -06:00
b4817e9ee9 fix(deps): add missing python-dotenv dependency
config_loader.py imports dotenv_values from python-dotenv but the package
was not listed in requirements.txt.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 00:40:20 -06:00
942e053542 docs: add Amendment A3 for B13 guard reorder + backwards compatibility
Documents:
- Guard reorder fix (B13): region filter and matrix resolution before cooldown/dedup
- Sink-name routing: severity_channels values are sink names
- Backwards compatibility (TEMPORARY): legacy channel types trigger _toggle_to_rule
- Deprecation warning logged once per toggle per boot
- Removal slated for session 3

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 00:40:13 -06:00
3e29ff5eb2 feat(gui): Destinations section + simplified family cards
Destinations section:
- Full CRUD for sinks (add/edit/delete/test)
- Validates sink config before save
- Shows reference count in delete confirmation

Family cards simplified:
- Replaced channel checkbox matrix with sink name multi-select buttons
- Added freshness_seconds and cooldown_seconds inputs
- Added honest digest-include checkbox (wired to actual digest.include)
- Removed threshold dropdown, channel config block, SMTP fields, digest column

Fixes:
- B12: Changed "Warning" to "Priority" in helper text
- Swept 102 slate-* occurrences to gray-* (Carbon design tokens)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 00:40:04 -06:00
d86b7d3515 feat(migration): add Phase B matrix rewrite
- synthesize_sinks() now returns (sinks, hash_to_name) for matrix rewrite
- migrate_toggle_matrix() converts channel types to sink names, blanks rows below old min_severity
- migrate_all_matrices() processes all toggles with dry-run support
- apply_matrix_updates() writes updated toggles back to config

Tests added for matrix migration scenarios.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 00:39:52 -06:00
1c6922896f feat(api): add sink CRUD endpoints
- GET /sinks - list all configured sinks
- GET /sinks/{name} - get a specific sink
- PUT /sinks/{name} - create or update a sink (validates via SinkConfig.validate())
- DELETE /sinks/{name} - delete a sink (409 if referenced by any toggle's severity_channels)
- POST /sinks/{name}/test - test sink connectivity via channel.test_connection()

Persists through /api/config write path (save_section). Updates live config
in-memory after successful write.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 00:39:43 -06:00
bde839344f fix(dispatcher): reorder guards to fix B13 suppression trap + sink-name routing
B13 fix: region filter and matrix resolution now run BEFORE cooldown/dedup.
Previously, an event failing these later checks would still arm cooldown and
record dedup, suppressing later events that WOULD deliver.

New guard order:
1. Cold-start grace
2. Staleness
3. Region filter (moved before cooldown)
4. Matrix resolution (moved before cooldown)
5. IF empty sink list → return WITHOUT arming cooldown/dedup
6. Cooldown
7. Dedup
8. Deliver

v0.7 sink-name routing: severity_channels values are now sink names resolved
against config.notifications.sinks. Backwards compatibility: legacy channel
type names (mesh_broadcast, mesh_dm, email, webhook) trigger _toggle_to_rule
fallback with deprecation warning (once per toggle per boot).

TEMPORARY: Legacy fallback will be removed in session 3 after migration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 00:39:34 -06:00
3954c0a523 Merge branch 'feature/routing-simplification'
Routing simplification session 1: sinks + migration script

- Add SinkConfig dataclass for named notification destinations
- Add create_channel_from_sink() factory function
- Add migrate_config_routing.py with layout-aware append-only write
- Add docs/routing-simplification.md with defect analysis (B1-B13)
- Add 36 tests for sinks and migration

No behavior change: existing toggles/rules work unchanged when sinks absent.
2026-06-10 23:47:37 -06:00
8a30a2aef7 docs: add B13 guard-ordering defect + session-2 reorder requirement + Amendment A2
- B13: cooldown/dedup armed before region/severity/matrix checks, causing
  below-threshold events to burn dedup slots and suppress future deliveries
- Session 2 requirement: move cooldown commit and dedup record to after
  delivery decision to prevent matrix-only semantics from inheriting the trap
- Amendment A2: document layout-aware append-only write fix with post-write
  verification (shared detect_config_layout helper, backup restore on failure)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-10 23:14:33 -06:00
14916bdf3e test(sinks): multi-file/monolithic write coverage, abort-restore, dry-run layout report
Add TestWriteSinksToConfig class with:
- test_write_sinks_multifile_layout: comments preserved, no wrapper created
- test_write_sinks_monolithic_layout: sinks nested under notifications
- test_write_aborts_and_restores_on_bad_result: backup restored on verify fail
- test_dry_run_reports_layout: render function produces correct indentation
- test_generate_sink_name_int_node_ids: handles int/string node IDs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-10 23:14:21 -06:00
bba7b6ae55 fix(migration): layout-aware append-only sinks write + post-write verification (A1 compliance)
- Add detect_config_layout() shared helper for both load and write
- Multifile: append sinks at root level, preserving original content
- Monolithic: insert sinks within notifications block at correct position
- Add verify_sinks_written() post-write check: YAML parses, sinks accessible,
  original keys intact; auto-restore from backup on failure
- --dry-run shows detected layout, target path, and exact text to append
- Fix generate_sink_name to handle int node_ids (str() before lstrip)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-10 23:14:10 -06:00
f935b7702c chore: sweep Phase A framing from comments/docstrings
Replace "Phase A/B", "routing revamp" references with
"routing simplification" in docstrings and comments.
No code logic changes.

Files updated:
- meshai/config.py (SinkConfig docstring)
- meshai/notifications/channels.py (create_channel_from_sink docstring)
- meshai/scripts/migrate_config_routing.py (module docstring + argparse)
- meshai/dashboard/api/notification_routes.py (endpoint comments)
- tests/test_sinks.py (module docstring)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 02:48:31 +00:00
2858b5166d docs: reframe as routing simplification, remove phased plan
Replace meshai-routing-revamp-plan.md with routing-simplification.md:
- Keep: Part 1 (how routing works) + Part 2 (defect table) as reference
- Keep: Sinks design + matrix-only design as two halves of THIS project
- Keep: Amendment A1 (multi-file config layout)
- Remove: routing_log, dry-run endpoint, legacy-path retirement,
  scheduled-broadcast sinks, five-phase plan structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 02:48:24 +00:00
612b76cd10 docs: add routing revamp architecture plan with Phase A amendment
Includes multi-file config layout finding (Amendment A1) discovered
during Phase A implementation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-11 00:14:22 +00:00
3d7a0349a4 feat: Phase A routing revamp — SinkConfig + sink utilities
- Add SinkConfig dataclass with type, channel, node_ids, smtp_*, webhook_* fields
- Add sinks: dict[str, SinkConfig] to NotificationsConfig
- Extend _dict_to_dataclass to convert nested sink dicts (avoids 6f76c89 silent-dict bug)
- Add create_channel_from_sink() factory in channels.py
- Add migrate_config_routing.py script (manual, --dry-run support, idempotent)
- Add GET /api/notifications/sinks and POST /api/notifications/sinks/test endpoints
- Add test_sinks.py with 31 tests covering dataclass, factory, migration synthesis

Channel validation: channel >= 0 (fixes B6 falsy-zero bug class from plan)
No behavior change: existing toggles/rules work unchanged when sinks absent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-10 23:10:30 +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
858439c87a merge: feature/mesh-intelligence → main (SWPC standardization, fire halt/tombstone/spotting, NWS truncation + Update detection) 2026-06-10 02:41:10 +00:00
d88c6273ec nws: prefix Update: when incoming alert supersedes a prior broadcast
Add _is_update() helper that checks CAP references field against
nws_alerts table. When any referenced CAP id has last_broadcast_at
set, the wire gets an Update: prefix via _render(prefix=). Applied
in both the new-alert and cold-start-race branches.

References field shape: list of dicts with identifier key containing
the superseded CAP id (urn:oid:...).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 02:40:07 +00:00