mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-21 23:24:44 +02:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
95ec7d5351
commit
344ca0677d
4 changed files with 24 additions and 22 deletions
|
|
@ -217,11 +217,13 @@ environmental:
|
||||||
proximity_km: 10.0 # km to match known fire perimeters
|
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.
|
# Route alerts to channels (mesh, email, webhook) based on rules.
|
||||||
# Categories match alert types from alert_engine.py.
|
# Categories match alert types from alert_engine.py.
|
||||||
# Severity levels: info, advisory, watch, warning, critical, emergency
|
|
||||||
#
|
|
||||||
notifications:
|
notifications:
|
||||||
enabled: false
|
enabled: false
|
||||||
quiet_hours_enabled: true # Master toggle for quiet hours feature
|
quiet_hours_enabled: true # Master toggle for quiet hours feature
|
||||||
|
|
@ -236,7 +238,7 @@ notifications:
|
||||||
enabled: true
|
enabled: true
|
||||||
trigger_type: condition
|
trigger_type: condition
|
||||||
categories: [] # Empty = all categories
|
categories: [] # Empty = all categories
|
||||||
min_severity: "emergency"
|
min_severity: "immediate"
|
||||||
delivery_type: mesh_broadcast
|
delivery_type: mesh_broadcast
|
||||||
broadcast_channel: 0
|
broadcast_channel: 0
|
||||||
cooldown_minutes: 5
|
cooldown_minutes: 5
|
||||||
|
|
@ -247,7 +249,7 @@ notifications:
|
||||||
enabled: true
|
enabled: true
|
||||||
trigger_type: condition
|
trigger_type: condition
|
||||||
categories: ["infra_offline", "critical_node_down"]
|
categories: ["infra_offline", "critical_node_down"]
|
||||||
min_severity: "warning"
|
min_severity: "priority"
|
||||||
delivery_type: mesh_broadcast
|
delivery_type: mesh_broadcast
|
||||||
broadcast_channel: 0
|
broadcast_channel: 0
|
||||||
cooldown_minutes: 30
|
cooldown_minutes: 30
|
||||||
|
|
@ -258,7 +260,7 @@ notifications:
|
||||||
enabled: true
|
enabled: true
|
||||||
trigger_type: condition
|
trigger_type: condition
|
||||||
categories: ["wildfire_proximity", "new_ignition"]
|
categories: ["wildfire_proximity", "new_ignition"]
|
||||||
min_severity: "advisory"
|
min_severity: "routine"
|
||||||
delivery_type: mesh_broadcast
|
delivery_type: mesh_broadcast
|
||||||
broadcast_channel: 0
|
broadcast_channel: 0
|
||||||
cooldown_minutes: 60
|
cooldown_minutes: 60
|
||||||
|
|
@ -269,7 +271,7 @@ notifications:
|
||||||
enabled: true
|
enabled: true
|
||||||
trigger_type: condition
|
trigger_type: condition
|
||||||
categories: ["weather_warning"]
|
categories: ["weather_warning"]
|
||||||
min_severity: "warning"
|
min_severity: "priority"
|
||||||
delivery_type: mesh_broadcast
|
delivery_type: mesh_broadcast
|
||||||
broadcast_channel: 0
|
broadcast_channel: 0
|
||||||
cooldown_minutes: 30
|
cooldown_minutes: 30
|
||||||
|
|
@ -280,7 +282,7 @@ notifications:
|
||||||
# enabled: true
|
# enabled: true
|
||||||
# trigger_type: condition
|
# trigger_type: condition
|
||||||
# categories: ["wildfire_proximity", "new_ignition"]
|
# categories: ["wildfire_proximity", "new_ignition"]
|
||||||
# min_severity: "advisory"
|
# min_severity: "routine"
|
||||||
# delivery_type: email
|
# delivery_type: email
|
||||||
# smtp_host: "smtp.gmail.com"
|
# smtp_host: "smtp.gmail.com"
|
||||||
# smtp_port: 587
|
# smtp_port: 587
|
||||||
|
|
@ -296,7 +298,7 @@ notifications:
|
||||||
# enabled: true
|
# enabled: true
|
||||||
# trigger_type: condition
|
# trigger_type: condition
|
||||||
# categories: []
|
# categories: []
|
||||||
# min_severity: "warning"
|
# min_severity: "priority"
|
||||||
# delivery_type: webhook
|
# delivery_type: webhook
|
||||||
# webhook_url: "https://discord.com/api/webhooks/..."
|
# webhook_url: "https://discord.com/api/webhooks/..."
|
||||||
# cooldown_minutes: 10
|
# cooldown_minutes: 10
|
||||||
|
|
@ -316,7 +318,7 @@ notifications:
|
||||||
# enabled: true
|
# enabled: true
|
||||||
# trigger_type: condition
|
# trigger_type: condition
|
||||||
# categories: ["battery_warning"]
|
# categories: ["battery_warning"]
|
||||||
# min_severity: "warning"
|
# min_severity: "priority"
|
||||||
# delivery_type: "" # Empty = no delivery, just tracks matches
|
# delivery_type: "" # Empty = no delivery, just tracks matches
|
||||||
|
|
||||||
# === WEB DASHBOARD ===
|
# === WEB DASHBOARD ===
|
||||||
|
|
|
||||||
|
|
@ -578,7 +578,7 @@ def _migrate_legacy_channels(notifications, data: dict):
|
||||||
enabled=ch.get("enabled", True),
|
enabled=ch.get("enabled", True),
|
||||||
trigger_type="condition",
|
trigger_type="condition",
|
||||||
categories=old_rule.get("categories", []),
|
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"),
|
delivery_type=ch.get("type", "mesh_broadcast"),
|
||||||
broadcast_channel=ch.get("channel_index", 0),
|
broadcast_channel=ch.get("channel_index", 0),
|
||||||
node_ids=ch.get("node_ids", []),
|
node_ids=ch.get("node_ids", []),
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,7 @@ class EmailChannel(NotificationChannel):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
alert_type = alert.get("type", "alert")
|
alert_type = alert.get("type", "alert")
|
||||||
severity = alert.get("severity", "info").upper()
|
severity = alert.get("severity", "routine").upper()
|
||||||
message = alert.get("message", "")
|
message = alert.get("message", "")
|
||||||
subject = "[MeshAI %s] %s" % (severity, alert_type.replace("_", " ").title())
|
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." % (
|
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."""
|
"""POST alert to webhook URL."""
|
||||||
payload = {
|
payload = {
|
||||||
"type": alert.get("type"),
|
"type": alert.get("type"),
|
||||||
"severity": alert.get("severity", "info"),
|
"severity": alert.get("severity", "routine"),
|
||||||
"message": alert.get("message", ""),
|
"message": alert.get("message", ""),
|
||||||
"timestamp": time.time(),
|
"timestamp": time.time(),
|
||||||
"node_name": alert.get("node_name"),
|
"node_name": alert.get("node_name"),
|
||||||
|
|
@ -527,7 +527,7 @@ class WebhookChannel(NotificationChannel):
|
||||||
|
|
||||||
# Discord/Slack format
|
# Discord/Slack format
|
||||||
if "discord.com" in self._url or "slack.com" in self._url:
|
if "discord.com" in self._url or "slack.com" in self._url:
|
||||||
severity = alert.get("severity", "info")
|
severity = alert.get("severity", "routine")
|
||||||
color = {
|
color = {
|
||||||
"immediate": 0xFF0000,
|
"immediate": 0xFF0000,
|
||||||
"priority": 0xFFAA00,
|
"priority": 0xFFAA00,
|
||||||
|
|
@ -669,7 +669,7 @@ class WebhookChannel(NotificationChannel):
|
||||||
else:
|
else:
|
||||||
payload = {
|
payload = {
|
||||||
"type": "test",
|
"type": "test",
|
||||||
"severity": "info",
|
"severity": "routine",
|
||||||
"message": "MeshAI channel connectivity test",
|
"message": "MeshAI channel connectivity test",
|
||||||
"timestamp": time.time(),
|
"timestamp": time.time(),
|
||||||
}
|
}
|
||||||
|
|
@ -730,7 +730,7 @@ class WebhookChannel(NotificationChannel):
|
||||||
async def deliver_test(self, message: str) -> tuple[bool, str]:
|
async def deliver_test(self, message: str) -> tuple[bool, str]:
|
||||||
"""Deliver a specific test message via webhook."""
|
"""Deliver a specific test message via webhook."""
|
||||||
try:
|
try:
|
||||||
test_alert = {"type": "test", "severity": "info", "message": message}
|
test_alert = {"type": "test", "severity": "routine", "message": message}
|
||||||
success = await self.deliver(test_alert, {})
|
success = await self.deliver(test_alert, {})
|
||||||
if success:
|
if success:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ class NotificationRouter:
|
||||||
async def process_alert(self, alert: dict) -> bool:
|
async def process_alert(self, alert: dict) -> bool:
|
||||||
"""Route an alert through matching rules."""
|
"""Route an alert through matching rules."""
|
||||||
category = alert.get("type", "")
|
category = alert.get("type", "")
|
||||||
severity = alert.get("severity", "info")
|
severity = alert.get("severity", "routine")
|
||||||
delivered = False
|
delivered = False
|
||||||
|
|
||||||
for rule in self._rules:
|
for rule in self._rules:
|
||||||
|
|
@ -160,7 +160,7 @@ class NotificationRouter:
|
||||||
if rule_categories and category not in rule_categories:
|
if rule_categories and category not in rule_categories:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
min_severity = rule.get("min_severity", "info")
|
min_severity = rule.get("min_severity", "routine")
|
||||||
if not self._severity_meets(severity, min_severity):
|
if not self._severity_meets(severity, min_severity):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -392,7 +392,7 @@ class NotificationRouter:
|
||||||
|
|
||||||
rule_name = rule_dict.get("name", f"Rule {rule_index}")
|
rule_name = rule_dict.get("name", f"Rule {rule_index}")
|
||||||
rule_categories = rule_dict.get("categories", [])
|
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", "")
|
delivery_type = rule_dict.get("delivery_type", "")
|
||||||
|
|
||||||
# Legacy support
|
# Legacy support
|
||||||
|
|
@ -536,7 +536,7 @@ class NotificationRouter:
|
||||||
for alert in alert_engine.get_pending_alerts():
|
for alert in alert_engine.get_pending_alerts():
|
||||||
all_events.append({
|
all_events.append({
|
||||||
"type": alert.get("type", ""),
|
"type": alert.get("type", ""),
|
||||||
"severity": alert.get("severity", "info"),
|
"severity": alert.get("severity", "routine"),
|
||||||
"message": alert.get("message", ""),
|
"message": alert.get("message", ""),
|
||||||
"headline": alert.get("message", "")[:80],
|
"headline": alert.get("message", "")[:80],
|
||||||
})
|
})
|
||||||
|
|
@ -548,7 +548,7 @@ class NotificationRouter:
|
||||||
for event in env_store.get_active():
|
for event in env_store.get_active():
|
||||||
all_events.append({
|
all_events.append({
|
||||||
"type": event.get("type", event.get("category", "")),
|
"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))),
|
"message": event.get("message", event.get("headline", str(event))),
|
||||||
"headline": event.get("headline", event.get("message", "Event"))[:80],
|
"headline": event.get("headline", event.get("message", "Event"))[:80],
|
||||||
})
|
})
|
||||||
|
|
@ -761,7 +761,7 @@ class NotificationRouter:
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"trigger_type": "condition",
|
"trigger_type": "condition",
|
||||||
"categories": categories if categories else [],
|
"categories": categories if categories else [],
|
||||||
"min_severity": "warning",
|
"min_severity": "priority",
|
||||||
"delivery_type": "mesh_dm",
|
"delivery_type": "mesh_dm",
|
||||||
"node_ids": [node_id],
|
"node_ids": [node_id],
|
||||||
"cooldown_minutes": 10,
|
"cooldown_minutes": 10,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue