truthound-dashboard 1.4.4__py3-none-any.whl → 1.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- truthound_dashboard/api/alerts.py +75 -86
- truthound_dashboard/api/anomaly.py +7 -13
- truthound_dashboard/api/cross_alerts.py +38 -52
- truthound_dashboard/api/drift.py +49 -59
- truthound_dashboard/api/drift_monitor.py +234 -79
- truthound_dashboard/api/enterprise_sampling.py +498 -0
- truthound_dashboard/api/history.py +57 -5
- truthound_dashboard/api/lineage.py +3 -48
- truthound_dashboard/api/maintenance.py +104 -49
- truthound_dashboard/api/mask.py +1 -2
- truthound_dashboard/api/middleware.py +2 -1
- truthound_dashboard/api/model_monitoring.py +435 -311
- truthound_dashboard/api/notifications.py +227 -191
- truthound_dashboard/api/notifications_advanced.py +21 -20
- truthound_dashboard/api/observability.py +586 -0
- truthound_dashboard/api/plugins.py +2 -433
- truthound_dashboard/api/profile.py +199 -37
- truthound_dashboard/api/quality_reporter.py +701 -0
- truthound_dashboard/api/reports.py +7 -16
- truthound_dashboard/api/router.py +66 -0
- truthound_dashboard/api/rule_suggestions.py +5 -5
- truthound_dashboard/api/scan.py +17 -19
- truthound_dashboard/api/schedules.py +85 -50
- truthound_dashboard/api/schema_evolution.py +6 -6
- truthound_dashboard/api/schema_watcher.py +667 -0
- truthound_dashboard/api/sources.py +98 -27
- truthound_dashboard/api/tiering.py +1323 -0
- truthound_dashboard/api/triggers.py +14 -11
- truthound_dashboard/api/validations.py +12 -11
- truthound_dashboard/api/versioning.py +1 -6
- truthound_dashboard/core/__init__.py +129 -3
- truthound_dashboard/core/actions/__init__.py +62 -0
- truthound_dashboard/core/actions/custom.py +426 -0
- truthound_dashboard/core/actions/notifications.py +910 -0
- truthound_dashboard/core/actions/storage.py +472 -0
- truthound_dashboard/core/actions/webhook.py +281 -0
- truthound_dashboard/core/anomaly.py +262 -67
- truthound_dashboard/core/anomaly_explainer.py +4 -3
- truthound_dashboard/core/backends/__init__.py +67 -0
- truthound_dashboard/core/backends/base.py +299 -0
- truthound_dashboard/core/backends/errors.py +191 -0
- truthound_dashboard/core/backends/factory.py +423 -0
- truthound_dashboard/core/backends/mock_backend.py +451 -0
- truthound_dashboard/core/backends/truthound_backend.py +718 -0
- truthound_dashboard/core/checkpoint/__init__.py +87 -0
- truthound_dashboard/core/checkpoint/adapters.py +814 -0
- truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
- truthound_dashboard/core/checkpoint/runner.py +270 -0
- truthound_dashboard/core/connections.py +645 -23
- truthound_dashboard/core/converters/__init__.py +14 -0
- truthound_dashboard/core/converters/truthound.py +620 -0
- truthound_dashboard/core/cross_alerts.py +540 -320
- truthound_dashboard/core/datasource_factory.py +1672 -0
- truthound_dashboard/core/drift_monitor.py +216 -20
- truthound_dashboard/core/enterprise_sampling.py +1291 -0
- truthound_dashboard/core/interfaces/__init__.py +225 -0
- truthound_dashboard/core/interfaces/actions.py +652 -0
- truthound_dashboard/core/interfaces/base.py +247 -0
- truthound_dashboard/core/interfaces/checkpoint.py +676 -0
- truthound_dashboard/core/interfaces/protocols.py +664 -0
- truthound_dashboard/core/interfaces/reporters.py +650 -0
- truthound_dashboard/core/interfaces/routing.py +646 -0
- truthound_dashboard/core/interfaces/triggers.py +619 -0
- truthound_dashboard/core/lineage.py +407 -71
- truthound_dashboard/core/model_monitoring.py +431 -3
- truthound_dashboard/core/notifications/base.py +4 -0
- truthound_dashboard/core/notifications/channels.py +501 -1203
- truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
- truthound_dashboard/core/notifications/deduplication/service.py +131 -348
- truthound_dashboard/core/notifications/dispatcher.py +202 -11
- truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
- truthound_dashboard/core/notifications/escalation/engine.py +168 -358
- truthound_dashboard/core/notifications/routing/__init__.py +88 -128
- truthound_dashboard/core/notifications/routing/engine.py +90 -317
- truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
- truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
- truthound_dashboard/core/notifications/throttling/builder.py +117 -255
- truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
- truthound_dashboard/core/phase5/collaboration.py +1 -1
- truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
- truthound_dashboard/core/quality_reporter.py +1359 -0
- truthound_dashboard/core/report_history.py +0 -6
- truthound_dashboard/core/reporters/__init__.py +175 -14
- truthound_dashboard/core/reporters/adapters.py +943 -0
- truthound_dashboard/core/reporters/base.py +0 -3
- truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
- truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
- truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
- truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
- truthound_dashboard/core/reporters/compat.py +266 -0
- truthound_dashboard/core/reporters/csv_reporter.py +2 -35
- truthound_dashboard/core/reporters/factory.py +526 -0
- truthound_dashboard/core/reporters/interfaces.py +745 -0
- truthound_dashboard/core/reporters/registry.py +1 -10
- truthound_dashboard/core/scheduler.py +165 -0
- truthound_dashboard/core/schema_evolution.py +3 -3
- truthound_dashboard/core/schema_watcher.py +1528 -0
- truthound_dashboard/core/services.py +595 -76
- truthound_dashboard/core/store_manager.py +810 -0
- truthound_dashboard/core/streaming_anomaly.py +169 -4
- truthound_dashboard/core/tiering.py +1309 -0
- truthound_dashboard/core/triggers/evaluators.py +178 -8
- truthound_dashboard/core/truthound_adapter.py +2620 -197
- truthound_dashboard/core/unified_alerts.py +23 -20
- truthound_dashboard/db/__init__.py +8 -0
- truthound_dashboard/db/database.py +8 -2
- truthound_dashboard/db/models.py +944 -25
- truthound_dashboard/db/repository.py +2 -0
- truthound_dashboard/main.py +15 -0
- truthound_dashboard/schemas/__init__.py +177 -16
- truthound_dashboard/schemas/base.py +44 -23
- truthound_dashboard/schemas/collaboration.py +19 -6
- truthound_dashboard/schemas/cross_alerts.py +19 -3
- truthound_dashboard/schemas/drift.py +61 -55
- truthound_dashboard/schemas/drift_monitor.py +67 -23
- truthound_dashboard/schemas/enterprise_sampling.py +653 -0
- truthound_dashboard/schemas/lineage.py +0 -33
- truthound_dashboard/schemas/mask.py +10 -8
- truthound_dashboard/schemas/model_monitoring.py +89 -10
- truthound_dashboard/schemas/notifications_advanced.py +13 -0
- truthound_dashboard/schemas/observability.py +453 -0
- truthound_dashboard/schemas/plugins.py +0 -280
- truthound_dashboard/schemas/profile.py +154 -247
- truthound_dashboard/schemas/quality_reporter.py +403 -0
- truthound_dashboard/schemas/reports.py +2 -2
- truthound_dashboard/schemas/rule_suggestion.py +8 -1
- truthound_dashboard/schemas/scan.py +4 -24
- truthound_dashboard/schemas/schedule.py +11 -3
- truthound_dashboard/schemas/schema_watcher.py +727 -0
- truthound_dashboard/schemas/source.py +17 -2
- truthound_dashboard/schemas/tiering.py +822 -0
- truthound_dashboard/schemas/triggers.py +16 -0
- truthound_dashboard/schemas/unified_alerts.py +7 -0
- truthound_dashboard/schemas/validation.py +0 -13
- truthound_dashboard/schemas/validators/base.py +41 -21
- truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
- truthound_dashboard/schemas/validators/localization_validators.py +273 -0
- truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
- truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
- truthound_dashboard/schemas/validators/referential_validators.py +312 -0
- truthound_dashboard/schemas/validators/registry.py +93 -8
- truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
- truthound_dashboard/schemas/versioning.py +1 -6
- truthound_dashboard/static/index.html +2 -2
- truthound_dashboard-1.5.1.dist-info/METADATA +312 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/RECORD +149 -148
- truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
- truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
- truthound_dashboard/core/plugins/hooks/manager.py +0 -403
- truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
- truthound_dashboard/core/reporters/junit_reporter.py +0 -233
- truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
- truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
- truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
- truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
- truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
- truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
- truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
- truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
- truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
- truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
- truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
- truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
- truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
- truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
- truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
- truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
- truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
- truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
- truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
- truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
- truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
- truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
- truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
- truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
- truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
- truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
- truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
- truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
- truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
- truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
- truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
- truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
- truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
- truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
- truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
- truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
- truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
- truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
- truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
- truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
- truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
- truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
- truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
- truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
- truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
- truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
- truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
- truthound_dashboard-1.4.4.dist-info/METADATA +0 -507
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
The dispatcher coordinates between events, rules, and channels to
|
|
4
4
|
deliver notifications based on configured triggers.
|
|
5
5
|
|
|
6
|
-
Architecture:
|
|
7
|
-
Event -> Dispatcher ->
|
|
6
|
+
Architecture (with truthound integration):
|
|
7
|
+
Event -> Dispatcher -> Truthound Adapter (Routing/Dedup/Throttle) -> Channels -> Delivery -> Escalation
|
|
8
|
+
|
|
9
|
+
The dispatcher now integrates with truthound library for:
|
|
10
|
+
- Routing: ActionRouter with 11+ rule types
|
|
11
|
+
- Deduplication: NotificationDeduplicator with time windows
|
|
12
|
+
- Throttling: NotificationThrottler with rate limits
|
|
13
|
+
- Escalation: EscalationEngine with multi-level policies
|
|
8
14
|
|
|
9
15
|
Example:
|
|
10
16
|
dispatcher = get_dispatcher()
|
|
11
17
|
|
|
12
|
-
# Notify about validation failure
|
|
18
|
+
# Notify about validation failure (with truthound processing)
|
|
13
19
|
await dispatcher.notify_validation_failed(
|
|
14
20
|
source_id="source-123",
|
|
15
21
|
source_name="My Source",
|
|
@@ -24,7 +30,7 @@ from __future__ import annotations
|
|
|
24
30
|
|
|
25
31
|
import logging
|
|
26
32
|
from collections.abc import Sequence
|
|
27
|
-
from typing import Any
|
|
33
|
+
from typing import TYPE_CHECKING, Any
|
|
28
34
|
|
|
29
35
|
from sqlalchemy import select
|
|
30
36
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
@@ -50,6 +56,9 @@ from .events import (
|
|
|
50
56
|
ValidationFailedEvent,
|
|
51
57
|
)
|
|
52
58
|
|
|
59
|
+
if TYPE_CHECKING:
|
|
60
|
+
from .truthound_adapter import TruthoundNotificationAdapter
|
|
61
|
+
|
|
53
62
|
logger = logging.getLogger(__name__)
|
|
54
63
|
|
|
55
64
|
|
|
@@ -58,10 +67,12 @@ class NotificationDispatcher:
|
|
|
58
67
|
|
|
59
68
|
The dispatcher:
|
|
60
69
|
1. Receives notification events
|
|
61
|
-
2.
|
|
62
|
-
3.
|
|
63
|
-
4.
|
|
64
|
-
5.
|
|
70
|
+
2. Routes through truthound ActionRouter (if enabled)
|
|
71
|
+
3. Checks deduplication via truthound NotificationDeduplicator
|
|
72
|
+
4. Checks throttling via truthound NotificationThrottler
|
|
73
|
+
5. Delivers notifications through channels
|
|
74
|
+
6. Logs delivery results
|
|
75
|
+
7. Triggers escalation via truthound EscalationEngine (for critical events)
|
|
65
76
|
|
|
66
77
|
Usage:
|
|
67
78
|
dispatcher = NotificationDispatcher(session)
|
|
@@ -69,17 +80,37 @@ class NotificationDispatcher:
|
|
|
69
80
|
# Send test notification
|
|
70
81
|
results = await dispatcher.test_channel(channel_id)
|
|
71
82
|
|
|
72
|
-
# Notify about events
|
|
83
|
+
# Notify about events (with truthound processing)
|
|
73
84
|
await dispatcher.notify_validation_failed(...)
|
|
85
|
+
|
|
86
|
+
# Disable truthound for direct sending
|
|
87
|
+
dispatcher = NotificationDispatcher(session, use_truthound=False)
|
|
74
88
|
"""
|
|
75
89
|
|
|
76
|
-
def __init__(
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
session: AsyncSession,
|
|
93
|
+
use_truthound: bool = True,
|
|
94
|
+
) -> None:
|
|
77
95
|
"""Initialize the dispatcher.
|
|
78
96
|
|
|
79
97
|
Args:
|
|
80
98
|
session: Database session for accessing rules and channels.
|
|
99
|
+
use_truthound: Whether to use truthound library for routing,
|
|
100
|
+
deduplication, throttling, and escalation. Default True.
|
|
81
101
|
"""
|
|
82
102
|
self.session = session
|
|
103
|
+
self.use_truthound = use_truthound
|
|
104
|
+
self._truthound_adapter: TruthoundNotificationAdapter | None = None
|
|
105
|
+
|
|
106
|
+
async def _get_truthound_adapter(self) -> TruthoundNotificationAdapter:
|
|
107
|
+
"""Get or create the truthound adapter lazily."""
|
|
108
|
+
if self._truthound_adapter is None:
|
|
109
|
+
from .truthound_adapter import TruthoundNotificationAdapter
|
|
110
|
+
|
|
111
|
+
self._truthound_adapter = TruthoundNotificationAdapter(self.session)
|
|
112
|
+
await self._truthound_adapter.initialize()
|
|
113
|
+
return self._truthound_adapter
|
|
83
114
|
|
|
84
115
|
async def dispatch(
|
|
85
116
|
self,
|
|
@@ -91,7 +122,15 @@ class NotificationDispatcher:
|
|
|
91
122
|
"""Dispatch a notification event to matching channels.
|
|
92
123
|
|
|
93
124
|
If channel_ids is provided, sends directly to those channels.
|
|
94
|
-
Otherwise, matches event against rules to find
|
|
125
|
+
Otherwise, matches event against rules (or truthound routes) to find
|
|
126
|
+
target channels.
|
|
127
|
+
|
|
128
|
+
When truthound is enabled:
|
|
129
|
+
1. Routes through ActionRouter to get matched channels
|
|
130
|
+
2. Checks deduplication to suppress duplicate notifications
|
|
131
|
+
3. Checks throttling to enforce rate limits
|
|
132
|
+
4. Sends to channels that pass all checks
|
|
133
|
+
5. Triggers escalation for high severity unacknowledged events
|
|
95
134
|
|
|
96
135
|
Args:
|
|
97
136
|
event: The notification event to dispatch.
|
|
@@ -101,6 +140,18 @@ class NotificationDispatcher:
|
|
|
101
140
|
Returns:
|
|
102
141
|
List of delivery results for each channel.
|
|
103
142
|
"""
|
|
143
|
+
if self.use_truthound and not channel_ids:
|
|
144
|
+
return await self._dispatch_via_truthound(event, rule_id)
|
|
145
|
+
|
|
146
|
+
return await self._dispatch_legacy(event, channel_ids, rule_id)
|
|
147
|
+
|
|
148
|
+
async def _dispatch_legacy(
|
|
149
|
+
self,
|
|
150
|
+
event: NotificationEvent,
|
|
151
|
+
channel_ids: list[str] | None = None,
|
|
152
|
+
rule_id: str | None = None,
|
|
153
|
+
) -> list[NotificationResult]:
|
|
154
|
+
"""Legacy dispatch without truthound integration."""
|
|
104
155
|
# Get target channels
|
|
105
156
|
if channel_ids:
|
|
106
157
|
channels = await self._get_channels_by_ids(channel_ids)
|
|
@@ -119,6 +170,146 @@ class NotificationDispatcher:
|
|
|
119
170
|
|
|
120
171
|
return results
|
|
121
172
|
|
|
173
|
+
async def _dispatch_via_truthound(
|
|
174
|
+
self,
|
|
175
|
+
event: NotificationEvent,
|
|
176
|
+
rule_id: str | None = None,
|
|
177
|
+
) -> list[NotificationResult]:
|
|
178
|
+
"""Dispatch using truthound library for routing, dedup, throttle, escalation.
|
|
179
|
+
|
|
180
|
+
This method integrates with truthound's checkpoint modules:
|
|
181
|
+
- ActionRouter: Matches events to channels via configurable rules
|
|
182
|
+
- NotificationDeduplicator: Suppresses duplicate notifications
|
|
183
|
+
- NotificationThrottler: Enforces rate limits per channel
|
|
184
|
+
- EscalationEngine: Escalates unacknowledged critical events
|
|
185
|
+
"""
|
|
186
|
+
adapter = await self._get_truthound_adapter()
|
|
187
|
+
|
|
188
|
+
# Step 1: Route event to channels via truthound ActionRouter
|
|
189
|
+
matched_channel_ids = await adapter.match_routes(event)
|
|
190
|
+
|
|
191
|
+
# Fall back to legacy rule matching if no routes configured
|
|
192
|
+
if not matched_channel_ids:
|
|
193
|
+
channels = await self._get_channels_for_event(event)
|
|
194
|
+
matched_channel_ids = [ch.id for ch in channels]
|
|
195
|
+
|
|
196
|
+
if not matched_channel_ids:
|
|
197
|
+
logger.debug(f"No channels matched for event: {event.event_type}")
|
|
198
|
+
return []
|
|
199
|
+
|
|
200
|
+
# Get channel models
|
|
201
|
+
channels = await self._get_channels_by_ids(matched_channel_ids)
|
|
202
|
+
if not channels:
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
results: list[NotificationResult] = []
|
|
206
|
+
|
|
207
|
+
for channel_model in channels:
|
|
208
|
+
channel_id = channel_model.id
|
|
209
|
+
|
|
210
|
+
# Step 2: Check deduplication via truthound NotificationDeduplicator
|
|
211
|
+
if await adapter.is_duplicate(event, channel_id):
|
|
212
|
+
logger.debug(
|
|
213
|
+
f"Notification deduplicated for channel {channel_id}: {event.event_type}"
|
|
214
|
+
)
|
|
215
|
+
results.append(
|
|
216
|
+
NotificationResult(
|
|
217
|
+
success=True,
|
|
218
|
+
channel_id=channel_id,
|
|
219
|
+
channel_type=channel_model.type,
|
|
220
|
+
message="",
|
|
221
|
+
error=None,
|
|
222
|
+
suppressed=True,
|
|
223
|
+
suppression_reason="duplicate",
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
# Step 3: Check throttling via truthound NotificationThrottler
|
|
229
|
+
if await adapter.is_throttled(channel_id):
|
|
230
|
+
logger.debug(
|
|
231
|
+
f"Notification throttled for channel {channel_id}: {event.event_type}"
|
|
232
|
+
)
|
|
233
|
+
results.append(
|
|
234
|
+
NotificationResult(
|
|
235
|
+
success=True,
|
|
236
|
+
channel_id=channel_id,
|
|
237
|
+
channel_type=channel_model.type,
|
|
238
|
+
message="",
|
|
239
|
+
error=None,
|
|
240
|
+
suppressed=True,
|
|
241
|
+
suppression_reason="throttled",
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
# Step 4: Send notification
|
|
247
|
+
result = await self._send_to_channel(channel_model, event, rule_id)
|
|
248
|
+
results.append(result)
|
|
249
|
+
|
|
250
|
+
# Step 5: Mark as sent for deduplication tracking
|
|
251
|
+
if result.success:
|
|
252
|
+
await adapter.mark_notification_sent(event, channel_id)
|
|
253
|
+
|
|
254
|
+
# Step 6: Check escalation for high severity events
|
|
255
|
+
await self._check_escalation(event, adapter)
|
|
256
|
+
|
|
257
|
+
return results
|
|
258
|
+
|
|
259
|
+
async def _check_escalation(
|
|
260
|
+
self,
|
|
261
|
+
event: NotificationEvent,
|
|
262
|
+
adapter: TruthoundNotificationAdapter,
|
|
263
|
+
) -> None:
|
|
264
|
+
"""Check and trigger escalation for high severity events.
|
|
265
|
+
|
|
266
|
+
Escalation is triggered for:
|
|
267
|
+
- Critical validation failures
|
|
268
|
+
- High severity drift detection
|
|
269
|
+
- Schedule failures
|
|
270
|
+
|
|
271
|
+
The escalation engine will notify secondary/tertiary channels
|
|
272
|
+
if the primary notification is not acknowledged within timeout.
|
|
273
|
+
"""
|
|
274
|
+
severity = self._get_event_severity(event)
|
|
275
|
+
|
|
276
|
+
if severity in ("critical", "high"):
|
|
277
|
+
try:
|
|
278
|
+
escalation_id = await adapter.trigger_escalation(
|
|
279
|
+
event,
|
|
280
|
+
policy_name=f"{severity}_alert",
|
|
281
|
+
)
|
|
282
|
+
if escalation_id:
|
|
283
|
+
logger.info(
|
|
284
|
+
f"Escalation triggered for {event.event_type}: {escalation_id}"
|
|
285
|
+
)
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.warning(f"Failed to trigger escalation: {e}")
|
|
288
|
+
|
|
289
|
+
def _get_event_severity(self, event: NotificationEvent) -> str:
|
|
290
|
+
"""Determine severity level of an event."""
|
|
291
|
+
if isinstance(event, ValidationFailedEvent):
|
|
292
|
+
if event.has_critical:
|
|
293
|
+
return "critical"
|
|
294
|
+
if event.has_high:
|
|
295
|
+
return "high"
|
|
296
|
+
return "medium"
|
|
297
|
+
|
|
298
|
+
if isinstance(event, DriftDetectedEvent):
|
|
299
|
+
if event.has_high_drift:
|
|
300
|
+
return "high"
|
|
301
|
+
return "medium"
|
|
302
|
+
|
|
303
|
+
if isinstance(event, ScheduleFailedEvent):
|
|
304
|
+
return "high"
|
|
305
|
+
|
|
306
|
+
if isinstance(event, SchemaChangedEvent):
|
|
307
|
+
if event.has_breaking_changes:
|
|
308
|
+
return "high"
|
|
309
|
+
return "low"
|
|
310
|
+
|
|
311
|
+
return "low"
|
|
312
|
+
|
|
122
313
|
async def _get_channels_by_ids(
|
|
123
314
|
self, channel_ids: list[str]
|
|
124
315
|
) -> Sequence[NotificationChannel]:
|
|
@@ -1,149 +1,162 @@
|
|
|
1
|
-
"""Escalation engine
|
|
1
|
+
"""Escalation engine using truthound.checkpoint.escalation.
|
|
2
2
|
|
|
3
3
|
This module provides a multi-level escalation system for managing
|
|
4
|
-
alerts
|
|
4
|
+
alerts using truthound's escalation infrastructure.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
6
|
+
Key Components from truthound.checkpoint.escalation:
|
|
7
|
+
- EscalationEngine: Main escalation engine
|
|
8
|
+
- EscalationEngineConfig: Engine configuration
|
|
9
|
+
- EscalationPolicy: Policy definition
|
|
10
|
+
- EscalationLevel: Level definition
|
|
11
|
+
- EscalationTarget: Target definition
|
|
12
|
+
- EscalationRecord: Escalation state record
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
Target Types from truthound:
|
|
15
|
+
- USER: Individual user
|
|
16
|
+
- TEAM: Team
|
|
17
|
+
- CHANNEL: Channel (Slack, etc.)
|
|
18
|
+
- SCHEDULE: On-call schedule
|
|
19
|
+
- WEBHOOK: Webhook URL
|
|
20
|
+
- EMAIL: Email
|
|
21
|
+
- PHONE: Phone
|
|
22
|
+
- CUSTOM: Custom
|
|
23
|
+
|
|
24
|
+
Escalation States from truthound:
|
|
25
|
+
- PENDING: Initial state, waiting to start
|
|
26
|
+
- ACTIVE: Currently notifying at level
|
|
27
|
+
- ESCALATING: Escalating to next level
|
|
28
|
+
- ACKNOWLEDGED: Responder acknowledged
|
|
29
|
+
- RESOLVED: Issue resolved
|
|
30
|
+
- CANCELLED: Manually cancelled
|
|
31
|
+
- TIMED_OUT: Max escalation reached
|
|
32
|
+
- FAILED: System error
|
|
33
|
+
|
|
34
|
+
Storage Backends from truthound:
|
|
35
|
+
- InMemoryEscalationStore: Single-process storage
|
|
36
|
+
- RedisEscalationStore: Distributed storage
|
|
37
|
+
- SQLiteEscalationStore: Persistent storage
|
|
18
38
|
|
|
19
39
|
Example:
|
|
20
|
-
from
|
|
40
|
+
from truthound.checkpoint.escalation import (
|
|
21
41
|
EscalationEngine,
|
|
42
|
+
EscalationEngineConfig,
|
|
22
43
|
EscalationPolicy,
|
|
23
44
|
EscalationLevel,
|
|
24
45
|
EscalationTarget,
|
|
25
|
-
|
|
46
|
+
EscalationTrigger,
|
|
26
47
|
)
|
|
27
48
|
|
|
49
|
+
# Define policy
|
|
28
50
|
policy = EscalationPolicy(
|
|
29
51
|
name="critical_alerts",
|
|
30
52
|
levels=[
|
|
31
53
|
EscalationLevel(
|
|
32
54
|
level=1,
|
|
33
55
|
delay_minutes=0,
|
|
34
|
-
targets=[
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
targets=[EscalationTarget.user("team-lead", "Team Lead")],
|
|
57
|
+
repeat_count=2,
|
|
58
|
+
repeat_interval_minutes=5,
|
|
37
59
|
),
|
|
38
60
|
EscalationLevel(
|
|
39
61
|
level=2,
|
|
40
62
|
delay_minutes=15,
|
|
41
|
-
targets=[
|
|
42
|
-
EscalationTarget(type=TargetType.GROUP, identifier="managers", channel="pagerduty")
|
|
43
|
-
],
|
|
63
|
+
targets=[EscalationTarget.user("manager", "Manager")],
|
|
44
64
|
),
|
|
45
65
|
],
|
|
66
|
+
triggers=[EscalationTrigger.UNACKNOWLEDGED],
|
|
67
|
+
severity_filter=["critical", "high"],
|
|
46
68
|
)
|
|
47
69
|
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
# Create engine
|
|
71
|
+
config = EscalationEngineConfig(store_type="memory")
|
|
72
|
+
engine = EscalationEngine(config)
|
|
73
|
+
engine.register_policy(policy)
|
|
74
|
+
|
|
75
|
+
# Trigger escalation
|
|
76
|
+
await engine.start()
|
|
77
|
+
result = await engine.trigger(
|
|
78
|
+
incident_id="incident-123",
|
|
79
|
+
context={"severity": "critical"},
|
|
80
|
+
policy_name="critical_alerts",
|
|
81
|
+
)
|
|
50
82
|
"""
|
|
51
83
|
|
|
52
|
-
from .
|
|
53
|
-
from .
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
EscalationLevel,
|
|
84
|
+
# Re-export from truthound.checkpoint.escalation
|
|
85
|
+
from truthound.checkpoint.escalation import (
|
|
86
|
+
EscalationEngine,
|
|
87
|
+
EscalationEngineConfig,
|
|
57
88
|
EscalationPolicy,
|
|
58
|
-
|
|
89
|
+
EscalationLevel,
|
|
59
90
|
EscalationTarget,
|
|
60
|
-
|
|
91
|
+
EscalationRecord,
|
|
92
|
+
EscalationTrigger,
|
|
93
|
+
EscalationState,
|
|
61
94
|
TargetType,
|
|
95
|
+
EscalationPolicyManager,
|
|
96
|
+
EscalationPolicyConfig,
|
|
62
97
|
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
JobState,
|
|
69
|
-
MisfirePolicy,
|
|
70
|
-
SchedulerBackend,
|
|
71
|
-
SchedulerBackendConfig,
|
|
72
|
-
SQLAlchemySchedulerBackend,
|
|
73
|
-
create_scheduler_backend,
|
|
98
|
+
|
|
99
|
+
# Storage backends
|
|
100
|
+
from truthound.checkpoint.escalation import (
|
|
101
|
+
InMemoryEscalationStore,
|
|
102
|
+
create_store,
|
|
74
103
|
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
|
|
105
|
+
# Redis/SQLite stores (optional)
|
|
106
|
+
try:
|
|
107
|
+
from truthound.checkpoint.escalation import RedisEscalationStore
|
|
108
|
+
REDIS_AVAILABLE = True
|
|
109
|
+
except ImportError:
|
|
110
|
+
RedisEscalationStore = None # type: ignore
|
|
111
|
+
REDIS_AVAILABLE = False
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
from truthound.checkpoint.escalation import SQLiteEscalationStore
|
|
115
|
+
SQLITE_AVAILABLE = True
|
|
116
|
+
except ImportError:
|
|
117
|
+
SQLiteEscalationStore = None # type: ignore
|
|
118
|
+
SQLITE_AVAILABLE = False
|
|
119
|
+
|
|
120
|
+
# Routing integration
|
|
121
|
+
from truthound.checkpoint.escalation import (
|
|
122
|
+
EscalationRule,
|
|
123
|
+
EscalationRuleConfig,
|
|
124
|
+
EscalationAction,
|
|
125
|
+
create_escalation_route,
|
|
89
126
|
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
InMemoryEscalationStore,
|
|
96
|
-
RedisEscalationStore,
|
|
97
|
-
SQLiteEscalationStore,
|
|
98
|
-
create_escalation_store,
|
|
127
|
+
|
|
128
|
+
# Dashboard-specific adapters
|
|
129
|
+
from .engine import (
|
|
130
|
+
DashboardEscalationService,
|
|
131
|
+
create_policy_from_db,
|
|
99
132
|
)
|
|
100
133
|
|
|
101
134
|
__all__ = [
|
|
102
|
-
#
|
|
135
|
+
# truthound core
|
|
136
|
+
"EscalationEngine",
|
|
137
|
+
"EscalationEngineConfig",
|
|
138
|
+
"EscalationPolicy",
|
|
139
|
+
"EscalationLevel",
|
|
140
|
+
"EscalationTarget",
|
|
141
|
+
"EscalationRecord",
|
|
142
|
+
"EscalationTrigger",
|
|
103
143
|
"EscalationState",
|
|
104
144
|
"TargetType",
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
|
|
108
|
-
"EscalationIncident",
|
|
109
|
-
"EscalationEvent",
|
|
110
|
-
"StateTransition",
|
|
111
|
-
# State Machine
|
|
112
|
-
"EscalationStateMachine",
|
|
113
|
-
# Stores
|
|
114
|
-
"BaseEscalationStore",
|
|
115
|
-
"EscalationMetrics",
|
|
116
|
-
"EscalationStoreType",
|
|
145
|
+
"EscalationPolicyManager",
|
|
146
|
+
"EscalationPolicyConfig",
|
|
147
|
+
# Storage
|
|
117
148
|
"InMemoryEscalationStore",
|
|
149
|
+
"create_store",
|
|
118
150
|
"RedisEscalationStore",
|
|
119
151
|
"SQLiteEscalationStore",
|
|
120
|
-
"
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"JobExecutionResult",
|
|
131
|
-
"SchedulerBackend",
|
|
132
|
-
"InMemorySchedulerBackend",
|
|
133
|
-
"SQLAlchemySchedulerBackend",
|
|
134
|
-
"create_scheduler_backend",
|
|
135
|
-
# Scheduler
|
|
136
|
-
"EscalationSchedulerService",
|
|
137
|
-
"EscalationSchedulerConfig",
|
|
138
|
-
"EscalationHandler",
|
|
139
|
-
"EscalationResult",
|
|
140
|
-
"EscalationStrategy",
|
|
141
|
-
"DefaultEscalationHandler",
|
|
142
|
-
"LoggingEscalationHandler",
|
|
143
|
-
"TimeBasedEscalationStrategy",
|
|
144
|
-
"ImmediateEscalationStrategy",
|
|
145
|
-
"get_escalation_scheduler",
|
|
146
|
-
"reset_escalation_scheduler",
|
|
147
|
-
"start_escalation_scheduler",
|
|
148
|
-
"stop_escalation_scheduler",
|
|
152
|
+
"REDIS_AVAILABLE",
|
|
153
|
+
"SQLITE_AVAILABLE",
|
|
154
|
+
# Routing integration
|
|
155
|
+
"EscalationRule",
|
|
156
|
+
"EscalationRuleConfig",
|
|
157
|
+
"EscalationAction",
|
|
158
|
+
"create_escalation_route",
|
|
159
|
+
# Dashboard adapters
|
|
160
|
+
"DashboardEscalationService",
|
|
161
|
+
"create_policy_from_db",
|
|
149
162
|
]
|