From 344ca0677ddf22dfebf9d24ddba0e3e94607bfe1 Mon Sep 17 00:00:00 2001 From: K7ZVX Date: Thu, 14 May 2026 07:00:58 +0000 Subject: [PATCH] fix(notifications): complete severity cleanup to 3-level system - Replace 11 info fallbacks with routine in router.py + channels.py - Replace 2 warning min_severity defaults with priority - Update config.example.yaml rules to use routine/priority/immediate - Annotate config.example.yaml notifications section as transitional pending v0.3 8-toggle rewrite Phase 1.2 Co-Authored-By: Claude Opus 4.5 --- config.example.yaml | 22 ++++++++++++---------- meshai/config.py | 2 +- meshai/notifications/channels.py | 10 +++++----- meshai/notifications/router.py | 12 ++++++------ 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index 2585dde..da73362 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -217,11 +217,13 @@ environmental: proximity_km: 10.0 # km to match known fire perimeters -# === NOTIFICATION DELIVERY === +# === NOTIFICATION DELIVERY (TRANSITIONAL) === +# NOTE: This notifications schema will be replaced in v0.3 by the 8-toggle model. +# These rule examples are transitional until Phase 1.2 lands. Do not extend. +# Severity levels: routine (informational), priority (needs attention), immediate (act now) +# # Route alerts to channels (mesh, email, webhook) based on rules. # Categories match alert types from alert_engine.py. -# Severity levels: info, advisory, watch, warning, critical, emergency -# notifications: enabled: false quiet_hours_enabled: true # Master toggle for quiet hours feature @@ -236,7 +238,7 @@ notifications: enabled: true trigger_type: condition categories: [] # Empty = all categories - min_severity: "emergency" + min_severity: "immediate" delivery_type: mesh_broadcast broadcast_channel: 0 cooldown_minutes: 5 @@ -247,7 +249,7 @@ notifications: enabled: true trigger_type: condition categories: ["infra_offline", "critical_node_down"] - min_severity: "warning" + min_severity: "priority" delivery_type: mesh_broadcast broadcast_channel: 0 cooldown_minutes: 30 @@ -258,7 +260,7 @@ notifications: enabled: true trigger_type: condition categories: ["wildfire_proximity", "new_ignition"] - min_severity: "advisory" + min_severity: "routine" delivery_type: mesh_broadcast broadcast_channel: 0 cooldown_minutes: 60 @@ -269,7 +271,7 @@ notifications: enabled: true trigger_type: condition categories: ["weather_warning"] - min_severity: "warning" + min_severity: "priority" delivery_type: mesh_broadcast broadcast_channel: 0 cooldown_minutes: 30 @@ -280,7 +282,7 @@ notifications: # enabled: true # trigger_type: condition # categories: ["wildfire_proximity", "new_ignition"] - # min_severity: "advisory" + # min_severity: "routine" # delivery_type: email # smtp_host: "smtp.gmail.com" # smtp_port: 587 @@ -296,7 +298,7 @@ notifications: # enabled: true # trigger_type: condition # categories: [] - # min_severity: "warning" + # min_severity: "priority" # delivery_type: webhook # webhook_url: "https://discord.com/api/webhooks/..." # cooldown_minutes: 10 @@ -316,7 +318,7 @@ notifications: # enabled: true # trigger_type: condition # categories: ["battery_warning"] - # min_severity: "warning" + # min_severity: "priority" # delivery_type: "" # Empty = no delivery, just tracks matches # === WEB DASHBOARD === diff --git a/meshai/config.py b/meshai/config.py index 4225ae7..fb05317 100644 --- a/meshai/config.py +++ b/meshai/config.py @@ -578,7 +578,7 @@ def _migrate_legacy_channels(notifications, data: dict): enabled=ch.get("enabled", True), trigger_type="condition", categories=old_rule.get("categories", []), - min_severity=old_rule.get("min_severity", "warning"), + min_severity=old_rule.get("min_severity", "priority"), delivery_type=ch.get("type", "mesh_broadcast"), broadcast_channel=ch.get("channel_index", 0), node_ids=ch.get("node_ids", []), diff --git a/meshai/notifications/channels.py b/meshai/notifications/channels.py index 98b4b6d..8c6f917 100644 --- a/meshai/notifications/channels.py +++ b/meshai/notifications/channels.py @@ -292,7 +292,7 @@ class EmailChannel(NotificationChannel): return False alert_type = alert.get("type", "alert") - severity = alert.get("severity", "info").upper() + severity = alert.get("severity", "routine").upper() message = alert.get("message", "") subject = "[MeshAI %s] %s" % (severity, alert_type.replace("_", " ").title()) body = "MeshAI Alert\n\nType: %s\nSeverity: %s\nTime: %s\n\n%s\n\n---\nAutomated message from MeshAI." % ( @@ -518,7 +518,7 @@ class WebhookChannel(NotificationChannel): """POST alert to webhook URL.""" payload = { "type": alert.get("type"), - "severity": alert.get("severity", "info"), + "severity": alert.get("severity", "routine"), "message": alert.get("message", ""), "timestamp": time.time(), "node_name": alert.get("node_name"), @@ -527,7 +527,7 @@ class WebhookChannel(NotificationChannel): # Discord/Slack format if "discord.com" in self._url or "slack.com" in self._url: - severity = alert.get("severity", "info") + severity = alert.get("severity", "routine") color = { "immediate": 0xFF0000, "priority": 0xFFAA00, @@ -669,7 +669,7 @@ class WebhookChannel(NotificationChannel): else: payload = { "type": "test", - "severity": "info", + "severity": "routine", "message": "MeshAI channel connectivity test", "timestamp": time.time(), } @@ -730,7 +730,7 @@ class WebhookChannel(NotificationChannel): async def deliver_test(self, message: str) -> tuple[bool, str]: """Deliver a specific test message via webhook.""" try: - test_alert = {"type": "test", "severity": "info", "message": message} + test_alert = {"type": "test", "severity": "routine", "message": message} success = await self.deliver(test_alert, {}) if success: try: diff --git a/meshai/notifications/router.py b/meshai/notifications/router.py index 72bc40b..f58a185 100644 --- a/meshai/notifications/router.py +++ b/meshai/notifications/router.py @@ -150,7 +150,7 @@ class NotificationRouter: async def process_alert(self, alert: dict) -> bool: """Route an alert through matching rules.""" category = alert.get("type", "") - severity = alert.get("severity", "info") + severity = alert.get("severity", "routine") delivered = False for rule in self._rules: @@ -160,7 +160,7 @@ class NotificationRouter: if rule_categories and category not in rule_categories: continue - min_severity = rule.get("min_severity", "info") + min_severity = rule.get("min_severity", "routine") if not self._severity_meets(severity, min_severity): continue @@ -392,7 +392,7 @@ class NotificationRouter: rule_name = rule_dict.get("name", f"Rule {rule_index}") rule_categories = rule_dict.get("categories", []) - min_severity = rule_dict.get("min_severity", "info") + min_severity = rule_dict.get("min_severity", "routine") delivery_type = rule_dict.get("delivery_type", "") # Legacy support @@ -536,7 +536,7 @@ class NotificationRouter: for alert in alert_engine.get_pending_alerts(): all_events.append({ "type": alert.get("type", ""), - "severity": alert.get("severity", "info"), + "severity": alert.get("severity", "routine"), "message": alert.get("message", ""), "headline": alert.get("message", "")[:80], }) @@ -548,7 +548,7 @@ class NotificationRouter: for event in env_store.get_active(): all_events.append({ "type": event.get("type", event.get("category", "")), - "severity": event.get("severity", "info"), + "severity": event.get("severity", "routine"), "message": event.get("message", event.get("headline", str(event))), "headline": event.get("headline", event.get("message", "Event"))[:80], }) @@ -761,7 +761,7 @@ class NotificationRouter: "enabled": True, "trigger_type": "condition", "categories": categories if categories else [], - "min_severity": "warning", + "min_severity": "priority", "delivery_type": "mesh_dm", "node_ids": [node_id], "cooldown_minutes": 10,