truthound-dashboard 1.3.0__py3-none-any.whl → 1.4.0__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 +258 -0
- truthound_dashboard/api/anomaly.py +1302 -0
- truthound_dashboard/api/cross_alerts.py +352 -0
- truthound_dashboard/api/deps.py +143 -0
- truthound_dashboard/api/drift_monitor.py +540 -0
- truthound_dashboard/api/lineage.py +1151 -0
- truthound_dashboard/api/maintenance.py +363 -0
- truthound_dashboard/api/middleware.py +373 -1
- truthound_dashboard/api/model_monitoring.py +805 -0
- truthound_dashboard/api/notifications_advanced.py +2452 -0
- truthound_dashboard/api/plugins.py +2096 -0
- truthound_dashboard/api/profile.py +211 -14
- truthound_dashboard/api/reports.py +853 -0
- truthound_dashboard/api/router.py +147 -0
- truthound_dashboard/api/rule_suggestions.py +310 -0
- truthound_dashboard/api/schema_evolution.py +231 -0
- truthound_dashboard/api/sources.py +47 -3
- truthound_dashboard/api/triggers.py +190 -0
- truthound_dashboard/api/validations.py +13 -0
- truthound_dashboard/api/validators.py +333 -4
- truthound_dashboard/api/versioning.py +309 -0
- truthound_dashboard/api/websocket.py +301 -0
- truthound_dashboard/core/__init__.py +27 -0
- truthound_dashboard/core/anomaly.py +1395 -0
- truthound_dashboard/core/anomaly_explainer.py +633 -0
- truthound_dashboard/core/cache.py +206 -0
- truthound_dashboard/core/cached_services.py +422 -0
- truthound_dashboard/core/charts.py +352 -0
- truthound_dashboard/core/connections.py +1069 -42
- truthound_dashboard/core/cross_alerts.py +837 -0
- truthound_dashboard/core/drift_monitor.py +1477 -0
- truthound_dashboard/core/drift_sampling.py +669 -0
- truthound_dashboard/core/i18n/__init__.py +42 -0
- truthound_dashboard/core/i18n/detector.py +173 -0
- truthound_dashboard/core/i18n/messages.py +564 -0
- truthound_dashboard/core/lineage.py +971 -0
- truthound_dashboard/core/maintenance.py +443 -5
- truthound_dashboard/core/model_monitoring.py +1043 -0
- truthound_dashboard/core/notifications/channels.py +1020 -1
- truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
- truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
- truthound_dashboard/core/notifications/deduplication/service.py +400 -0
- truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
- truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
- truthound_dashboard/core/notifications/dispatcher.py +43 -0
- truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
- truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
- truthound_dashboard/core/notifications/escalation/engine.py +429 -0
- truthound_dashboard/core/notifications/escalation/models.py +336 -0
- truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
- truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
- truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
- truthound_dashboard/core/notifications/events.py +49 -0
- truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
- truthound_dashboard/core/notifications/metrics/base.py +528 -0
- truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
- truthound_dashboard/core/notifications/routing/__init__.py +169 -0
- truthound_dashboard/core/notifications/routing/combinators.py +184 -0
- truthound_dashboard/core/notifications/routing/config.py +375 -0
- truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
- truthound_dashboard/core/notifications/routing/engine.py +382 -0
- truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
- truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
- truthound_dashboard/core/notifications/routing/rules.py +625 -0
- truthound_dashboard/core/notifications/routing/validator.py +678 -0
- truthound_dashboard/core/notifications/service.py +2 -0
- truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
- truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
- truthound_dashboard/core/notifications/throttling/builder.py +311 -0
- truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
- truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
- truthound_dashboard/core/openlineage.py +1028 -0
- truthound_dashboard/core/plugins/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/extractor.py +703 -0
- truthound_dashboard/core/plugins/docs/renderers.py +804 -0
- truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
- truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
- truthound_dashboard/core/plugins/hooks/manager.py +403 -0
- truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
- truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
- truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
- truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
- truthound_dashboard/core/plugins/loader.py +504 -0
- truthound_dashboard/core/plugins/registry.py +810 -0
- truthound_dashboard/core/plugins/reporter_executor.py +588 -0
- truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
- truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
- truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
- truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
- truthound_dashboard/core/plugins/sandbox.py +617 -0
- truthound_dashboard/core/plugins/security/__init__.py +68 -0
- truthound_dashboard/core/plugins/security/analyzer.py +535 -0
- truthound_dashboard/core/plugins/security/policies.py +311 -0
- truthound_dashboard/core/plugins/security/protocols.py +296 -0
- truthound_dashboard/core/plugins/security/signing.py +842 -0
- truthound_dashboard/core/plugins/security.py +446 -0
- truthound_dashboard/core/plugins/validator_executor.py +401 -0
- truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
- truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
- truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
- truthound_dashboard/core/plugins/versioning/semver.py +266 -0
- truthound_dashboard/core/profile_comparison.py +601 -0
- truthound_dashboard/core/report_history.py +570 -0
- truthound_dashboard/core/reporters/__init__.py +57 -0
- truthound_dashboard/core/reporters/base.py +296 -0
- truthound_dashboard/core/reporters/csv_reporter.py +155 -0
- truthound_dashboard/core/reporters/html_reporter.py +598 -0
- truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
- truthound_dashboard/core/reporters/i18n/base.py +494 -0
- truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
- truthound_dashboard/core/reporters/json_reporter.py +160 -0
- truthound_dashboard/core/reporters/junit_reporter.py +233 -0
- truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
- truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
- truthound_dashboard/core/reporters/registry.py +272 -0
- truthound_dashboard/core/rule_generator.py +2088 -0
- truthound_dashboard/core/scheduler.py +822 -12
- truthound_dashboard/core/schema_evolution.py +858 -0
- truthound_dashboard/core/services.py +152 -9
- truthound_dashboard/core/statistics.py +718 -0
- truthound_dashboard/core/streaming_anomaly.py +883 -0
- truthound_dashboard/core/triggers/__init__.py +45 -0
- truthound_dashboard/core/triggers/base.py +226 -0
- truthound_dashboard/core/triggers/evaluators.py +609 -0
- truthound_dashboard/core/triggers/factory.py +363 -0
- truthound_dashboard/core/unified_alerts.py +870 -0
- truthound_dashboard/core/validation_limits.py +509 -0
- truthound_dashboard/core/versioning.py +709 -0
- truthound_dashboard/core/websocket/__init__.py +59 -0
- truthound_dashboard/core/websocket/manager.py +512 -0
- truthound_dashboard/core/websocket/messages.py +130 -0
- truthound_dashboard/db/__init__.py +30 -0
- truthound_dashboard/db/models.py +3375 -3
- truthound_dashboard/main.py +22 -0
- truthound_dashboard/schemas/__init__.py +396 -1
- truthound_dashboard/schemas/anomaly.py +1258 -0
- truthound_dashboard/schemas/base.py +4 -0
- truthound_dashboard/schemas/cross_alerts.py +334 -0
- truthound_dashboard/schemas/drift_monitor.py +890 -0
- truthound_dashboard/schemas/lineage.py +428 -0
- truthound_dashboard/schemas/maintenance.py +154 -0
- truthound_dashboard/schemas/model_monitoring.py +374 -0
- truthound_dashboard/schemas/notifications_advanced.py +1363 -0
- truthound_dashboard/schemas/openlineage.py +704 -0
- truthound_dashboard/schemas/plugins.py +1293 -0
- truthound_dashboard/schemas/profile.py +420 -34
- truthound_dashboard/schemas/profile_comparison.py +242 -0
- truthound_dashboard/schemas/reports.py +285 -0
- truthound_dashboard/schemas/rule_suggestion.py +434 -0
- truthound_dashboard/schemas/schema_evolution.py +164 -0
- truthound_dashboard/schemas/source.py +117 -2
- truthound_dashboard/schemas/triggers.py +511 -0
- truthound_dashboard/schemas/unified_alerts.py +223 -0
- truthound_dashboard/schemas/validation.py +25 -1
- truthound_dashboard/schemas/validators/__init__.py +11 -0
- truthound_dashboard/schemas/validators/base.py +151 -0
- truthound_dashboard/schemas/versioning.py +152 -0
- truthound_dashboard/static/index.html +2 -2
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -18
- truthound_dashboard-1.4.0.dist-info/RECORD +239 -0
- truthound_dashboard/static/assets/index-BCA8H1hO.js +0 -574
- truthound_dashboard/static/assets/index-BNsSQ2fN.css +0 -1
- truthound_dashboard/static/assets/unmerged_dictionaries-CsJWCRx9.js +0 -1
- truthound_dashboard-1.3.0.dist-info/RECORD +0 -110
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""State machine for escalation incident lifecycle.
|
|
2
|
+
|
|
3
|
+
This module implements the state machine that controls valid
|
|
4
|
+
state transitions for escalation incidents.
|
|
5
|
+
|
|
6
|
+
State Transitions:
|
|
7
|
+
PENDING -> TRIGGERED (trigger)
|
|
8
|
+
TRIGGERED -> ACKNOWLEDGED (acknowledge)
|
|
9
|
+
TRIGGERED -> ESCALATED (escalate)
|
|
10
|
+
TRIGGERED -> RESOLVED (resolve)
|
|
11
|
+
ACKNOWLEDGED -> ESCALATED (escalate)
|
|
12
|
+
ACKNOWLEDGED -> RESOLVED (resolve)
|
|
13
|
+
ESCALATED -> ACKNOWLEDGED (acknowledge)
|
|
14
|
+
ESCALATED -> ESCALATED (escalate to next level)
|
|
15
|
+
ESCALATED -> RESOLVED (resolve)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from typing import Callable
|
|
22
|
+
|
|
23
|
+
from .models import (
|
|
24
|
+
EscalationEvent,
|
|
25
|
+
EscalationIncident,
|
|
26
|
+
EscalationState,
|
|
27
|
+
StateTransition,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class EscalationStateMachine:
|
|
32
|
+
"""State machine for escalation incident lifecycle.
|
|
33
|
+
|
|
34
|
+
Validates and executes state transitions for incidents.
|
|
35
|
+
Maintains transition history via events.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
machine = EscalationStateMachine()
|
|
39
|
+
|
|
40
|
+
# Trigger incident
|
|
41
|
+
incident = machine.trigger(incident)
|
|
42
|
+
|
|
43
|
+
# Acknowledge
|
|
44
|
+
incident = machine.acknowledge(incident, actor="user@example.com")
|
|
45
|
+
|
|
46
|
+
# Resolve
|
|
47
|
+
incident = machine.resolve(incident, actor="user@example.com")
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Define valid state transitions
|
|
51
|
+
VALID_TRANSITIONS: list[StateTransition] = [
|
|
52
|
+
# PENDING -> TRIGGERED
|
|
53
|
+
StateTransition(
|
|
54
|
+
from_states=[EscalationState.PENDING],
|
|
55
|
+
to_state=EscalationState.TRIGGERED,
|
|
56
|
+
),
|
|
57
|
+
# TRIGGERED -> ACKNOWLEDGED
|
|
58
|
+
StateTransition(
|
|
59
|
+
from_states=[EscalationState.TRIGGERED],
|
|
60
|
+
to_state=EscalationState.ACKNOWLEDGED,
|
|
61
|
+
requires_actor=True,
|
|
62
|
+
),
|
|
63
|
+
# TRIGGERED -> ESCALATED
|
|
64
|
+
StateTransition(
|
|
65
|
+
from_states=[EscalationState.TRIGGERED],
|
|
66
|
+
to_state=EscalationState.ESCALATED,
|
|
67
|
+
),
|
|
68
|
+
# TRIGGERED -> RESOLVED
|
|
69
|
+
StateTransition(
|
|
70
|
+
from_states=[EscalationState.TRIGGERED],
|
|
71
|
+
to_state=EscalationState.RESOLVED,
|
|
72
|
+
),
|
|
73
|
+
# ACKNOWLEDGED -> ESCALATED
|
|
74
|
+
StateTransition(
|
|
75
|
+
from_states=[EscalationState.ACKNOWLEDGED],
|
|
76
|
+
to_state=EscalationState.ESCALATED,
|
|
77
|
+
),
|
|
78
|
+
# ACKNOWLEDGED -> RESOLVED
|
|
79
|
+
StateTransition(
|
|
80
|
+
from_states=[EscalationState.ACKNOWLEDGED],
|
|
81
|
+
to_state=EscalationState.RESOLVED,
|
|
82
|
+
),
|
|
83
|
+
# ESCALATED -> ACKNOWLEDGED
|
|
84
|
+
StateTransition(
|
|
85
|
+
from_states=[EscalationState.ESCALATED],
|
|
86
|
+
to_state=EscalationState.ACKNOWLEDGED,
|
|
87
|
+
requires_actor=True,
|
|
88
|
+
),
|
|
89
|
+
# ESCALATED -> ESCALATED (next level)
|
|
90
|
+
StateTransition(
|
|
91
|
+
from_states=[EscalationState.ESCALATED],
|
|
92
|
+
to_state=EscalationState.ESCALATED,
|
|
93
|
+
),
|
|
94
|
+
# ESCALATED -> RESOLVED
|
|
95
|
+
StateTransition(
|
|
96
|
+
from_states=[EscalationState.ESCALATED],
|
|
97
|
+
to_state=EscalationState.RESOLVED,
|
|
98
|
+
),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
on_transition: Callable[[EscalationIncident, EscalationEvent], None] | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Initialize state machine.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
on_transition: Optional callback for transitions.
|
|
109
|
+
"""
|
|
110
|
+
self.on_transition = on_transition
|
|
111
|
+
|
|
112
|
+
def can_transition(
|
|
113
|
+
self,
|
|
114
|
+
incident: EscalationIncident,
|
|
115
|
+
to_state: EscalationState,
|
|
116
|
+
) -> bool:
|
|
117
|
+
"""Check if transition to state is valid.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
incident: The incident.
|
|
121
|
+
to_state: Target state.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
True if transition is valid.
|
|
125
|
+
"""
|
|
126
|
+
for transition in self.VALID_TRANSITIONS:
|
|
127
|
+
if incident.state in transition.from_states and transition.to_state == to_state:
|
|
128
|
+
return True
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def transition(
|
|
132
|
+
self,
|
|
133
|
+
incident: EscalationIncident,
|
|
134
|
+
to_state: EscalationState,
|
|
135
|
+
actor: str | None = None,
|
|
136
|
+
message: str = "",
|
|
137
|
+
metadata: dict | None = None,
|
|
138
|
+
) -> EscalationIncident:
|
|
139
|
+
"""Execute a state transition.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
incident: The incident to transition.
|
|
143
|
+
to_state: Target state.
|
|
144
|
+
actor: Who/what triggered the transition.
|
|
145
|
+
message: Optional transition message.
|
|
146
|
+
metadata: Optional additional data.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Updated incident.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
ValueError: If transition is invalid.
|
|
153
|
+
"""
|
|
154
|
+
# Find valid transition
|
|
155
|
+
valid_transition = None
|
|
156
|
+
for transition in self.VALID_TRANSITIONS:
|
|
157
|
+
if incident.state in transition.from_states and transition.to_state == to_state:
|
|
158
|
+
valid_transition = transition
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
if valid_transition is None:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"Invalid transition from {incident.state.value} to {to_state.value}"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Check actor requirement
|
|
167
|
+
if valid_transition.requires_actor and not actor:
|
|
168
|
+
raise ValueError(f"Transition to {to_state.value} requires an actor")
|
|
169
|
+
|
|
170
|
+
# Create event
|
|
171
|
+
event = EscalationEvent(
|
|
172
|
+
from_state=incident.state,
|
|
173
|
+
to_state=to_state,
|
|
174
|
+
level=incident.current_level,
|
|
175
|
+
actor=actor,
|
|
176
|
+
message=message,
|
|
177
|
+
metadata=metadata or {},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Update incident
|
|
181
|
+
incident.state = to_state
|
|
182
|
+
incident.updated_at = datetime.utcnow()
|
|
183
|
+
incident.events.append(event)
|
|
184
|
+
|
|
185
|
+
# Call callback
|
|
186
|
+
if self.on_transition:
|
|
187
|
+
self.on_transition(incident, event)
|
|
188
|
+
|
|
189
|
+
return incident
|
|
190
|
+
|
|
191
|
+
def trigger(
|
|
192
|
+
self,
|
|
193
|
+
incident: EscalationIncident,
|
|
194
|
+
message: str = "Escalation triggered",
|
|
195
|
+
) -> EscalationIncident:
|
|
196
|
+
"""Trigger an escalation.
|
|
197
|
+
|
|
198
|
+
Transitions from PENDING to TRIGGERED.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
incident: The incident.
|
|
202
|
+
message: Optional message.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Updated incident.
|
|
206
|
+
"""
|
|
207
|
+
incident.current_level = 1
|
|
208
|
+
return self.transition(
|
|
209
|
+
incident,
|
|
210
|
+
EscalationState.TRIGGERED,
|
|
211
|
+
actor="system",
|
|
212
|
+
message=message,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def acknowledge(
|
|
216
|
+
self,
|
|
217
|
+
incident: EscalationIncident,
|
|
218
|
+
actor: str,
|
|
219
|
+
message: str = "",
|
|
220
|
+
) -> EscalationIncident:
|
|
221
|
+
"""Acknowledge an incident.
|
|
222
|
+
|
|
223
|
+
Transitions to ACKNOWLEDGED state.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
incident: The incident.
|
|
227
|
+
actor: Who acknowledged.
|
|
228
|
+
message: Optional message.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Updated incident.
|
|
232
|
+
"""
|
|
233
|
+
incident.acknowledged_by = actor
|
|
234
|
+
incident.acknowledged_at = datetime.utcnow()
|
|
235
|
+
return self.transition(
|
|
236
|
+
incident,
|
|
237
|
+
EscalationState.ACKNOWLEDGED,
|
|
238
|
+
actor=actor,
|
|
239
|
+
message=message or f"Acknowledged by {actor}",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def escalate(
|
|
243
|
+
self,
|
|
244
|
+
incident: EscalationIncident,
|
|
245
|
+
to_level: int,
|
|
246
|
+
message: str = "",
|
|
247
|
+
) -> EscalationIncident:
|
|
248
|
+
"""Escalate to the next level.
|
|
249
|
+
|
|
250
|
+
Transitions to ESCALATED state.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
incident: The incident.
|
|
254
|
+
to_level: Target escalation level.
|
|
255
|
+
message: Optional message.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Updated incident.
|
|
259
|
+
"""
|
|
260
|
+
incident.current_level = to_level
|
|
261
|
+
incident.escalation_count += 1
|
|
262
|
+
return self.transition(
|
|
263
|
+
incident,
|
|
264
|
+
EscalationState.ESCALATED,
|
|
265
|
+
actor="system",
|
|
266
|
+
message=message or f"Escalated to level {to_level}",
|
|
267
|
+
metadata={"level": to_level},
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
def resolve(
|
|
271
|
+
self,
|
|
272
|
+
incident: EscalationIncident,
|
|
273
|
+
actor: str | None = None,
|
|
274
|
+
message: str = "",
|
|
275
|
+
auto: bool = False,
|
|
276
|
+
) -> EscalationIncident:
|
|
277
|
+
"""Resolve an incident.
|
|
278
|
+
|
|
279
|
+
Transitions to RESOLVED state.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
incident: The incident.
|
|
283
|
+
actor: Who resolved (None for auto-resolve).
|
|
284
|
+
message: Optional message.
|
|
285
|
+
auto: Whether this is auto-resolution.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Updated incident.
|
|
289
|
+
"""
|
|
290
|
+
incident.resolved_by = actor or ("system" if auto else None)
|
|
291
|
+
incident.resolved_at = datetime.utcnow()
|
|
292
|
+
incident.next_escalation_at = None
|
|
293
|
+
|
|
294
|
+
return self.transition(
|
|
295
|
+
incident,
|
|
296
|
+
EscalationState.RESOLVED,
|
|
297
|
+
actor=actor or "system",
|
|
298
|
+
message=message or ("Auto-resolved" if auto else f"Resolved by {actor}"),
|
|
299
|
+
metadata={"auto_resolved": auto},
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def get_valid_transitions(
|
|
303
|
+
self,
|
|
304
|
+
incident: EscalationIncident,
|
|
305
|
+
) -> list[EscalationState]:
|
|
306
|
+
"""Get valid target states from current state.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
incident: The incident.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
List of valid target states.
|
|
313
|
+
"""
|
|
314
|
+
valid = []
|
|
315
|
+
for transition in self.VALID_TRANSITIONS:
|
|
316
|
+
if incident.state in transition.from_states:
|
|
317
|
+
if transition.to_state not in valid:
|
|
318
|
+
valid.append(transition.to_state)
|
|
319
|
+
return valid
|
|
320
|
+
|
|
321
|
+
def is_terminal(self, incident: EscalationIncident) -> bool:
|
|
322
|
+
"""Check if incident is in a terminal state.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
incident: The incident.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
True if no more transitions are possible.
|
|
329
|
+
"""
|
|
330
|
+
return incident.state == EscalationState.RESOLVED
|