petter2025 commited on
Commit
f517938
·
verified ·
1 Parent(s): 72db17b

Create healing_intent.py

Browse files
Files changed (1) hide show
  1. infrastructure/healing_intent.py +1535 -0
infrastructure/healing_intent.py ADDED
@@ -0,0 +1,1535 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agentic_reliability_framework/infrastructure/healing_intent.py
2
+ """
3
+ Healing Intent - OSS creates, Enterprise executes
4
+ Enhanced with probabilistic confidence, risk scoring, cost projection,
5
+ and full audit trail integration.
6
+
7
+ This is the core contract between OSS advisory and Enterprise execution.
8
+ All intents are immutable and self-validating, ensuring consistency
9
+ across the OSS/Enterprise boundary.
10
+
11
+ The design follows ARF governing principles:
12
+ - OSS = advisory intelligence only
13
+ - Enterprise = governed execution
14
+ - Immutable contracts between layers
15
+ - Full provenance and explainability
16
+ - Probabilistic uncertainty quantification
17
+
18
+ Copyright 2025 Juan Petter
19
+
20
+ Licensed under the Apache License, Version 2.0 (the "License");
21
+ you may not use this file except in compliance with the License.
22
+ You may obtain a copy of the License at
23
+
24
+ http://www.apache.org/licenses/LICENSE-2.0
25
+
26
+ Unless required by applicable law or agreed to in writing, software
27
+ distributed under the License is distributed on an "AS IS" BASIS,
28
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29
+ See the License for the specific language governing permissions and
30
+ limitations under the License.
31
+ """
32
+
33
+ from dataclasses import dataclass, field, asdict
34
+ from typing import Dict, Any, Optional, List, ClassVar, Tuple, Union
35
+ from datetime import datetime
36
+ import hashlib
37
+ import json
38
+ import time
39
+ import uuid
40
+ from enum import Enum
41
+ import numpy as np
42
+
43
+ # Import from local infrastructure modules
44
+ from .intents import InfrastructureIntent
45
+ from ..constants import (
46
+ OSS_EDITION,
47
+ OSS_LICENSE,
48
+ ENTERPRISE_UPGRADE_URL,
49
+ EXECUTION_ALLOWED,
50
+ MAX_SIMILARITY_CACHE,
51
+ SIMILARITY_THRESHOLD,
52
+ MAX_POLICY_VIOLATIONS,
53
+ MAX_RISK_FACTORS,
54
+ MAX_COST_PROJECTIONS,
55
+ MAX_DECISION_TREE_DEPTH,
56
+ MAX_ALTERNATIVE_ACTIONS,
57
+ OSSBoundaryError,
58
+ )
59
+
60
+
61
+ class HealingIntentError(Exception):
62
+ """Base exception for HealingIntent errors"""
63
+ pass
64
+
65
+
66
+ class SerializationError(HealingIntentError):
67
+ """Error during serialization/deserialization"""
68
+ pass
69
+
70
+
71
+ class ValidationError(HealingIntentError):
72
+ """Error during intent validation"""
73
+ pass
74
+
75
+
76
+ class IntentSource(str, Enum):
77
+ """Source of the healing intent - matches old ARF patterns"""
78
+ OSS_ANALYSIS = "oss_analysis"
79
+ HUMAN_OVERRIDE = "human_override"
80
+ AUTOMATED_LEARNING = "automated_learning" # Enterprise only
81
+ SCHEDULED_ACTION = "scheduled_action" # Enterprise only
82
+ RAG_SIMILARITY = "rag_similarity" # From RAG graph similarity
83
+ INFRASTRUCTURE_ANALYSIS = "infrastructure_analysis" # From infra module
84
+ POLICY_VIOLATION = "policy_violation" # From policy engine
85
+ COST_OPTIMIZATION = "cost_optimization" # From cost analysis
86
+
87
+
88
+ class IntentStatus(str, Enum):
89
+ """Status of the healing intent - enhanced with partial states"""
90
+ CREATED = "created"
91
+ PENDING_EXECUTION = "pending_execution"
92
+ EXECUTING = "executing"
93
+ EXECUTING_PARTIAL = "executing_partial"
94
+ COMPLETED = "completed"
95
+ COMPLETED_PARTIAL = "completed_partial"
96
+ FAILED = "failed"
97
+ REJECTED = "rejected"
98
+ CANCELLED = "cancelled"
99
+ ROLLED_BACK = "rolled_back"
100
+ OSS_ADVISORY_ONLY = "oss_advisory_only"
101
+ PENDING_APPROVAL = "pending_approval"
102
+ APPROVED = "approved"
103
+ APPROVED_WITH_OVERRIDES = "approved_with_overrides"
104
+
105
+
106
+ class RecommendedAction(str, Enum):
107
+ """
108
+ Advisory recommendation from the OSS engine.
109
+ Matches the infrastructure module's RecommendedAction.
110
+ """
111
+ APPROVE = "approve"
112
+ DENY = "deny"
113
+ ESCALATE = "escalate"
114
+ DEFER = "defer" # Wait for more information
115
+
116
+
117
+ class ConfidenceDistribution:
118
+ """
119
+ Probabilistic confidence representation.
120
+
121
+ Instead of a single confidence score, this represents a distribution
122
+ of possible confidence values, allowing for uncertainty quantification.
123
+ Matches patterns from the risk_engine.py module.
124
+ """
125
+
126
+ def __init__(self, mean: float, std: float = 0.05, samples: Optional[List[float]] = None):
127
+ self.mean = max(0.0, min(mean, 1.0))
128
+ self.std = max(0.0, min(std, 0.5))
129
+ self._samples = samples or list(np.random.normal(self.mean, self.std, 1000).clip(0, 1))
130
+
131
+ @property
132
+ def p5(self) -> float:
133
+ """5th percentile (pessimistic)"""
134
+ return float(np.percentile(self._samples, 5))
135
+
136
+ @property
137
+ def p50(self) -> float:
138
+ """50th percentile (median)"""
139
+ return float(np.percentile(self._samples, 50))
140
+
141
+ @property
142
+ def p95(self) -> float:
143
+ """95th percentile (optimistic)"""
144
+ return float(np.percentile(self._samples, 95))
145
+
146
+ @property
147
+ def confidence_interval(self) -> Tuple[float, float]:
148
+ """95% confidence interval"""
149
+ return (self.p5, self.p95)
150
+
151
+ def to_dict(self) -> Dict[str, float]:
152
+ """Serialize to dictionary"""
153
+ return {
154
+ "mean": self.mean,
155
+ "std": self.std,
156
+ "p5": self.p5,
157
+ "p50": self.p50,
158
+ "p95": self.p95
159
+ }
160
+
161
+ @classmethod
162
+ def from_dict(cls, data: Dict[str, float]) -> "ConfidenceDistribution":
163
+ """Deserialize from dictionary"""
164
+ return cls(
165
+ mean=data["mean"],
166
+ std=data.get("std", 0.05),
167
+ samples=None # Will regenerate on access
168
+ )
169
+
170
+ def __repr__(self) -> str:
171
+ return f"ConfidenceDistribution(mean={self.mean:.3f}, 95% CI=[{self.p5:.3f}, {self.p95:.3f}])"
172
+
173
+
174
+ @dataclass(frozen=True, slots=True)
175
+ class HealingIntent:
176
+ """
177
+ OSS-generated healing recommendation for Enterprise execution
178
+
179
+ Enhanced with:
180
+ - Probabilistic confidence distributions
181
+ - Risk score and cost projection integration
182
+ - Decision tree tracking for explainability
183
+ - Human override audit trail
184
+ - Partial execution support
185
+ - Integration with infrastructure governance module
186
+ - Full backward compatibility with old ARF patterns
187
+
188
+ This is the clean boundary between OSS intelligence and Enterprise execution:
189
+ - OSS creates HealingIntent through analysis (advisory only)
190
+ - Enterprise executes HealingIntent through execution gateway
191
+ - Immutable (frozen) to ensure consistency across OSS→Enterprise handoff
192
+ """
193
+
194
+ # === CORE ACTION FIELDS (Sent to Enterprise) ===
195
+ action: str # Tool name, e.g., "restart_container", "provision_vm"
196
+ component: str # Target component or resource
197
+ parameters: Dict[str, Any] = field(default_factory=dict) # Action parameters
198
+ justification: str = "" # OSS reasoning chain
199
+
200
+ # === CONFIDENCE & METADATA ===
201
+ confidence: float = 0.85 # OSS confidence score (0.0 to 1.0)
202
+ confidence_distribution: Optional[Dict[str, float]] = None # Probabilistic confidence
203
+ incident_id: str = "" # Source incident identifier
204
+ detected_at: float = field(default_factory=time.time) # When OSS detected
205
+
206
+ # === RISK AND COST INTEGRATION ===
207
+ risk_score: Optional[float] = None # From risk engine (0-1)
208
+ risk_factors: Optional[Dict[str, float]] = None # Breakdown by factor
209
+ cost_projection: Optional[float] = None # Estimated cost impact
210
+ cost_confidence_interval: Optional[Tuple[float, float]] = None # 95% CI
211
+ recommended_action: Optional[RecommendedAction] = None # From risk engine
212
+
213
+ # === DECISION TRACKING ===
214
+ decision_tree: Optional[List[Dict[str, Any]]] = None # How decision was reached
215
+ alternative_actions: Optional[List[Dict[str, Any]]] = None # Alternatives considered
216
+ risk_profile: Optional[str] = None # Risk tolerance used (conservative/moderate/aggressive)
217
+
218
+ # === OSS ANALYSIS CONTEXT (Stays in OSS) ===
219
+ reasoning_chain: Optional[List[Dict[str, Any]]] = None
220
+ similar_incidents: Optional[List[Dict[str, Any]]] = None
221
+ rag_similarity_score: Optional[float] = None
222
+ source: IntentSource = IntentSource.OSS_ANALYSIS
223
+
224
+ # === IMMUTABLE IDENTIFIERS ===
225
+ intent_id: str = field(default_factory=lambda: f"intent_{uuid.uuid4().hex[:16]}")
226
+ created_at: float = field(default_factory=time.time)
227
+
228
+ # === EXECUTION METADATA (Set by Enterprise) ===
229
+ status: IntentStatus = IntentStatus.CREATED
230
+ execution_id: Optional[str] = None
231
+ executed_at: Optional[float] = None
232
+ execution_result: Optional[Dict[str, Any]] = None
233
+ enterprise_metadata: Dict[str, Any] = field(default_factory=dict)
234
+
235
+ # === HUMAN INTERACTION TRACKING ===
236
+ human_overrides: List[Dict[str, Any]] = field(default_factory=list) # Audit trail
237
+ approvals: List[Dict[str, Any]] = field(default_factory=list) # Who approved what
238
+ comments: List[Dict[str, Any]] = field(default_factory=list) # Human comments
239
+
240
+ # === OSS EDITION METADATA ===
241
+ oss_edition: str = OSS_EDITION
242
+ oss_license: str = OSS_LICENSE
243
+ requires_enterprise: bool = True # Always True for OSS-generated intents
244
+ execution_allowed: bool = EXECUTION_ALLOWED # From OSS constants
245
+
246
+ # === INFRASTRUCTURE INTEGRATION ===
247
+ infrastructure_intent_id: Optional[str] = None # Link to infrastructure intent if any
248
+ policy_violations: List[str] = field(default_factory=list) # From policy engine
249
+ infrastructure_intent: Optional[Dict[str, Any]] = None # Original infrastructure intent
250
+
251
+ # Class constants for validation
252
+ MIN_CONFIDENCE: ClassVar[float] = 0.0
253
+ MAX_CONFIDENCE: ClassVar[float] = 1.0
254
+ MAX_JUSTIFICATION_LENGTH: ClassVar[int] = 5000
255
+ MAX_PARAMETERS_SIZE: ClassVar[int] = 100
256
+ MAX_SIMILAR_INCIDENTS: ClassVar[int] = MAX_SIMILARITY_CACHE
257
+ VERSION: ClassVar[str] = "2.0.0" # Major bump for probabilistic features
258
+
259
+ def __post_init__(self) -> None:
260
+ """Validate HealingIntent after initialization with OSS boundaries"""
261
+ self._validate_oss_boundaries()
262
+ self._validate_risk_integration()
263
+
264
+ def _validate_oss_boundaries(self) -> None:
265
+ """Validate all fields against OSS limits"""
266
+ errors: List[str] = []
267
+
268
+ # Validate confidence range
269
+ if not (self.MIN_CONFIDENCE <= self.confidence <= self.MAX_CONFIDENCE):
270
+ errors.append(
271
+ f"Confidence must be between {self.MIN_CONFIDENCE} and "
272
+ f"{self.MAX_CONFIDENCE}, got {self.confidence}"
273
+ )
274
+
275
+ # Validate justification length
276
+ if len(self.justification) > self.MAX_JUSTIFICATION_LENGTH:
277
+ errors.append(
278
+ f"Justification exceeds max length {self.MAX_JUSTIFICATION_LENGTH}"
279
+ )
280
+
281
+ # Validate action and component
282
+ if not self.action or not self.action.strip():
283
+ errors.append("Action cannot be empty")
284
+
285
+ if not self.component or not self.component.strip():
286
+ errors.append("Component cannot be empty")
287
+
288
+ # Validate parameters size
289
+ if len(self.parameters) > self.MAX_PARAMETERS_SIZE:
290
+ errors.append(
291
+ f"Too many parameters: {len(self.parameters)} > {self.MAX_PARAMETERS_SIZE}"
292
+ )
293
+
294
+ # Validate parameters are JSON serializable
295
+ try:
296
+ json.dumps(self.parameters)
297
+ except (TypeError, ValueError) as e:
298
+ errors.append(f"Parameters must be JSON serializable: {e}")
299
+
300
+ # Validate similar incidents
301
+ if self.similar_incidents:
302
+ if len(self.similar_incidents) > self.MAX_SIMILAR_INCIDENTS:
303
+ errors.append(
304
+ f"Too many similar incidents: {len(self.similar_incidents)} > "
305
+ f"{self.MAX_SIMILAR_INCIDENTS}"
306
+ )
307
+
308
+ # Validate OSS edition restrictions
309
+ if self.oss_edition == OSS_EDITION:
310
+ if self.execution_allowed:
311
+ errors.append("Execution not allowed in OSS edition")
312
+
313
+ if self.status == IntentStatus.EXECUTING:
314
+ errors.append("EXECUTING status not allowed in OSS edition")
315
+
316
+ if self.executed_at is not None:
317
+ errors.append("executed_at should not be set in OSS edition")
318
+
319
+ if self.execution_id is not None:
320
+ errors.append("execution_id should not be set in OSS edition")
321
+
322
+ if errors:
323
+ raise ValidationError(
324
+ f"HealingIntent validation failed:\n" +
325
+ "\n".join(f" • {error}" for error in errors)
326
+ )
327
+
328
+ def _validate_risk_integration(self) -> None:
329
+ """Validate that risk and cost fields are consistent"""
330
+ if self.risk_score is not None:
331
+ if not (0.0 <= self.risk_score <= 1.0):
332
+ raise ValidationError(f"Risk score must be between 0 and 1, got {self.risk_score}")
333
+
334
+ if self.cost_projection is not None and self.cost_projection < 0:
335
+ raise ValidationError(f"Cost projection cannot be negative, got {self.cost_projection}")
336
+
337
+ if self.cost_confidence_interval is not None:
338
+ low, high = self.cost_confidence_interval
339
+ if low > high:
340
+ raise ValidationError(f"Invalid confidence interval: [{low}, {high}]")
341
+
342
+ @property
343
+ def deterministic_id(self) -> str:
344
+ """
345
+ Deterministic ID for idempotency based on action + component + parameters
346
+
347
+ This ensures the same action on the same component with the same parameters
348
+ generates the same intent ID, preventing duplicate executions.
349
+ """
350
+ data = {
351
+ "action": self.action,
352
+ "component": self.component,
353
+ "parameters": self._normalize_parameters(self.parameters),
354
+ "incident_id": self.incident_id,
355
+ "detected_at": int(self.detected_at),
356
+ "oss_edition": self.oss_edition,
357
+ }
358
+
359
+ # Sort keys for deterministic JSON
360
+ json_str = json.dumps(data, sort_keys=True, default=str)
361
+
362
+ # Create hash-based ID
363
+ hash_digest = hashlib.sha256(json_str.encode()).hexdigest()
364
+ return f"intent_{hash_digest[:16]}"
365
+
366
+ @property
367
+ def age_seconds(self) -> float:
368
+ """Get age of intent in seconds"""
369
+ return time.time() - self.created_at
370
+
371
+ @property
372
+ def is_executable(self) -> bool:
373
+ """Check if intent is ready for execution"""
374
+ # In OSS edition, nothing is executable
375
+ if self.oss_edition == OSS_EDITION:
376
+ return False
377
+
378
+ return self.status in [
379
+ IntentStatus.CREATED,
380
+ IntentStatus.PENDING_EXECUTION,
381
+ IntentStatus.APPROVED
382
+ ]
383
+
384
+ @property
385
+ def is_oss_advisory(self) -> bool:
386
+ """Check if this is an OSS advisory-only intent"""
387
+ return self.oss_edition == OSS_EDITION and not self.execution_allowed
388
+
389
+ @property
390
+ def requires_enterprise_upgrade(self) -> bool:
391
+ """Check if intent requires Enterprise upgrade"""
392
+ return self.requires_enterprise and self.oss_edition == OSS_EDITION
393
+
394
+ @property
395
+ def confidence_interval(self) -> Optional[Tuple[float, float]]:
396
+ """Get confidence interval if distribution is available"""
397
+ if self.confidence_distribution:
398
+ return (self.confidence_distribution.get("p5", self.confidence),
399
+ self.confidence_distribution.get("p95", self.confidence))
400
+ return None
401
+
402
+ def to_enterprise_request(self) -> Dict[str, Any]:
403
+ """
404
+ Convert to Enterprise API request format
405
+
406
+ Returns only the data needed for Enterprise execution.
407
+ OSS analysis context stays in OSS.
408
+ """
409
+ return {
410
+ # Core execution fields
411
+ "intent_id": self.deterministic_id,
412
+ "action": self.action,
413
+ "component": self.component,
414
+ "parameters": self.parameters,
415
+ "justification": self.justification,
416
+
417
+ # OSS metadata for Enterprise context
418
+ "confidence": self.confidence,
419
+ "confidence_interval": self.confidence_interval,
420
+ "risk_score": self.risk_score,
421
+ "cost_projection": self.cost_projection,
422
+ "incident_id": self.incident_id,
423
+ "detected_at": self.detected_at,
424
+ "created_at": self.created_at,
425
+ "source": self.source.value,
426
+ "recommended_action": self.recommended_action.value if self.recommended_action else None,
427
+
428
+ # OSS edition information
429
+ "oss_edition": self.oss_edition,
430
+ "oss_license": self.oss_license,
431
+ "requires_enterprise": self.requires_enterprise,
432
+ "execution_allowed": self.execution_allowed,
433
+ "version": self.VERSION,
434
+
435
+ # Minimal OSS context (for debugging only)
436
+ "oss_metadata": {
437
+ "similar_incidents_count": len(self.similar_incidents) if self.similar_incidents else 0,
438
+ "rag_similarity_score": self.rag_similarity_score,
439
+ "has_reasoning_chain": self.reasoning_chain is not None,
440
+ "source": self.source.value,
441
+ "is_oss_advisory": self.is_oss_advisory,
442
+ "risk_factors": self.risk_factors,
443
+ "policy_violations_count": len(self.policy_violations) if self.policy_violations else 0,
444
+ "confidence_basis": self._get_confidence_basis(),
445
+ "learning_applied": False,
446
+ "learning_reason": "OSS advisory mode does not persist or learn from outcomes",
447
+ },
448
+
449
+ # Upgrade information
450
+ "upgrade_url": ENTERPRISE_UPGRADE_URL,
451
+ "enterprise_features": [
452
+ "autonomous_execution",
453
+ "approval_workflows",
454
+ "persistent_storage",
455
+ "learning_engine",
456
+ "audit_trails",
457
+ "compliance_reports",
458
+ "multi_tenant_support",
459
+ "sso_integration",
460
+ "24_7_support",
461
+ "probabilistic_confidence",
462
+ "risk_analytics",
463
+ "cost_optimization"
464
+ ]
465
+ }
466
+
467
+ def _get_confidence_basis(self) -> str:
468
+ """Determine confidence basis based on available data"""
469
+ if self.recommended_action == RecommendedAction.DENY and self.policy_violations:
470
+ return "policy_violation"
471
+ if self.rag_similarity_score and self.rag_similarity_score > SIMILARITY_THRESHOLD:
472
+ return "historical_similarity"
473
+ if self.risk_score is not None:
474
+ return "risk_based"
475
+ return "policy_only"
476
+
477
+ def to_dict(self, include_oss_context: bool = False) -> Dict[str, Any]:
478
+ """
479
+ Convert to dictionary for serialization
480
+
481
+ Args:
482
+ include_oss_context: Whether to include OSS analysis context
483
+ (should be False when sending to Enterprise)
484
+
485
+ Returns:
486
+ Dictionary representation of the intent
487
+ """
488
+ data = asdict(self)
489
+
490
+ # Convert enums to strings
491
+ if "source" in data and isinstance(data["source"], IntentSource):
492
+ data["source"] = self.source.value
493
+ if "status" in data and isinstance(data["status"], IntentStatus):
494
+ data["status"] = self.status.value
495
+ if "recommended_action" in data and isinstance(data["recommended_action"], RecommendedAction):
496
+ data["recommended_action"] = self.recommended_action.value if self.recommended_action else None
497
+
498
+ # Remove OSS context if not needed
499
+ if not include_oss_context:
500
+ data.pop("reasoning_chain", None)
501
+ data.pop("similar_incidents", None)
502
+ data.pop("rag_similarity_score", None)
503
+ data.pop("decision_tree", None)
504
+ data.pop("alternative_actions", None)
505
+ data.pop("infrastructure_intent", None)
506
+
507
+ # Add computed properties
508
+ data["deterministic_id"] = self.deterministic_id
509
+ data["age_seconds"] = self.age_seconds
510
+ data["is_executable"] = self.is_executable
511
+ data["is_oss_advisory"] = self.is_oss_advisory
512
+ data["requires_enterprise_upgrade"] = self.requires_enterprise_upgrade
513
+ data["version"] = self.VERSION
514
+ data["confidence_interval"] = self.confidence_interval
515
+
516
+ return data
517
+
518
+ def with_execution_result(
519
+ self,
520
+ execution_id: str,
521
+ executed_at: float,
522
+ result: Dict[str, Any],
523
+ status: IntentStatus = IntentStatus.COMPLETED,
524
+ metadata: Optional[Dict[str, Any]] = None
525
+ ) -> "HealingIntent":
526
+ """
527
+ Create a new HealingIntent with execution results (used by Enterprise)
528
+
529
+ This is how Enterprise updates the intent after execution.
530
+ Returns a new immutable intent with execution results.
531
+ """
532
+ # Create a new dataclass with updated fields
533
+ return HealingIntent(
534
+ # Core fields (copied)
535
+ action=self.action,
536
+ component=self.component,
537
+ parameters=self.parameters,
538
+ justification=self.justification,
539
+ confidence=self.confidence,
540
+ confidence_distribution=self.confidence_distribution,
541
+ incident_id=self.incident_id,
542
+ detected_at=self.detected_at,
543
+
544
+ # Risk and cost fields (copied)
545
+ risk_score=self.risk_score,
546
+ risk_factors=self.risk_factors,
547
+ cost_projection=self.cost_projection,
548
+ cost_confidence_interval=self.cost_confidence_interval,
549
+ recommended_action=self.recommended_action,
550
+
551
+ # Decision tracking (copied)
552
+ decision_tree=self.decision_tree,
553
+ alternative_actions=self.alternative_actions,
554
+ risk_profile=self.risk_profile,
555
+
556
+ # OSS context (copied)
557
+ reasoning_chain=self.reasoning_chain,
558
+ similar_incidents=self.similar_incidents,
559
+ rag_similarity_score=self.rag_similarity_score,
560
+ source=self.source,
561
+
562
+ # Identifiers (copied)
563
+ intent_id=self.intent_id,
564
+ created_at=self.created_at,
565
+
566
+ # OSS metadata (copied)
567
+ oss_edition=self.oss_edition,
568
+ oss_license=self.oss_license,
569
+ requires_enterprise=self.requires_enterprise,
570
+ execution_allowed=self.execution_allowed,
571
+
572
+ # Infrastructure integration (copied)
573
+ infrastructure_intent_id=self.infrastructure_intent_id,
574
+ policy_violations=self.policy_violations,
575
+ infrastructure_intent=self.infrastructure_intent,
576
+
577
+ # Updated execution fields
578
+ status=status,
579
+ execution_id=execution_id,
580
+ executed_at=executed_at,
581
+ execution_result=result,
582
+ enterprise_metadata={**(self.enterprise_metadata or {}), **(metadata or {})},
583
+
584
+ # Human interaction (copied)
585
+ human_overrides=self.human_overrides,
586
+ approvals=self.approvals,
587
+ comments=self.comments
588
+ )
589
+
590
+ def with_human_approval(
591
+ self,
592
+ approver: str,
593
+ approval_time: float,
594
+ comments: Optional[str] = None,
595
+ overrides: Optional[Dict[str, Any]] = None
596
+ ) -> "HealingIntent":
597
+ """
598
+ Record human approval with optional overrides.
599
+
600
+ Returns a new intent with approval recorded and status updated.
601
+ """
602
+ approval_record = {
603
+ "approver": approver,
604
+ "timestamp": approval_time,
605
+ "comments": comments,
606
+ "overrides": overrides
607
+ }
608
+
609
+ new_overrides = list(self.human_overrides)
610
+ if overrides:
611
+ new_overrides.append({
612
+ "overrider": approver,
613
+ "timestamp": approval_time,
614
+ "overrides": overrides,
615
+ "reason": comments
616
+ })
617
+
618
+ new_approvals = list(self.approvals)
619
+ new_approvals.append(approval_record)
620
+
621
+ new_comments = list(self.comments)
622
+ if comments:
623
+ new_comments.append({
624
+ "author": approver,
625
+ "timestamp": approval_time,
626
+ "comment": comments
627
+ })
628
+
629
+ return HealingIntent(
630
+ # Copy all fields
631
+ action=self.action,
632
+ component=self.component,
633
+ parameters=self.parameters,
634
+ justification=self.justification,
635
+ confidence=self.confidence,
636
+ confidence_distribution=self.confidence_distribution,
637
+ incident_id=self.incident_id,
638
+ detected_at=self.detected_at,
639
+ risk_score=self.risk_score,
640
+ risk_factors=self.risk_factors,
641
+ cost_projection=self.cost_projection,
642
+ cost_confidence_interval=self.cost_confidence_interval,
643
+ recommended_action=self.recommended_action,
644
+ decision_tree=self.decision_tree,
645
+ alternative_actions=self.alternative_actions,
646
+ risk_profile=self.risk_profile,
647
+ reasoning_chain=self.reasoning_chain,
648
+ similar_incidents=self.similar_incidents,
649
+ rag_similarity_score=self.rag_similarity_score,
650
+ source=self.source,
651
+ intent_id=self.intent_id,
652
+ created_at=self.created_at,
653
+ status=IntentStatus.APPROVED_WITH_OVERRIDES if overrides else IntentStatus.APPROVED,
654
+ execution_id=self.execution_id,
655
+ executed_at=self.executed_at,
656
+ execution_result=self.execution_result,
657
+ enterprise_metadata=self.enterprise_metadata,
658
+ human_overrides=new_overrides,
659
+ approvals=new_approvals,
660
+ comments=new_comments,
661
+ oss_edition=self.oss_edition,
662
+ oss_license=self.oss_license,
663
+ requires_enterprise=self.requires_enterprise,
664
+ execution_allowed=self.execution_allowed,
665
+ infrastructure_intent_id=self.infrastructure_intent_id,
666
+ policy_violations=self.policy_violations,
667
+ infrastructure_intent=self.infrastructure_intent
668
+ )
669
+
670
+ def mark_as_sent_to_enterprise(self) -> "HealingIntent":
671
+ """
672
+ Mark intent as sent to Enterprise (used by OSS)
673
+
674
+ Returns a new intent with status updated to PENDING_EXECUTION
675
+ """
676
+ return HealingIntent(
677
+ # Copy all fields
678
+ action=self.action,
679
+ component=self.component,
680
+ parameters=self.parameters,
681
+ justification=self.justification,
682
+ confidence=self.confidence,
683
+ confidence_distribution=self.confidence_distribution,
684
+ incident_id=self.incident_id,
685
+ detected_at=self.detected_at,
686
+ risk_score=self.risk_score,
687
+ risk_factors=self.risk_factors,
688
+ cost_projection=self.cost_projection,
689
+ cost_confidence_interval=self.cost_confidence_interval,
690
+ recommended_action=self.recommended_action,
691
+ decision_tree=self.decision_tree,
692
+ alternative_actions=self.alternative_actions,
693
+ risk_profile=self.risk_profile,
694
+ reasoning_chain=self.reasoning_chain,
695
+ similar_incidents=self.similar_incidents,
696
+ rag_similarity_score=self.rag_similarity_score,
697
+ source=self.source,
698
+ intent_id=self.intent_id,
699
+ created_at=self.created_at,
700
+ status=IntentStatus.PENDING_EXECUTION,
701
+ execution_id=self.execution_id,
702
+ executed_at=self.executed_at,
703
+ execution_result=self.execution_result,
704
+ enterprise_metadata=self.enterprise_metadata,
705
+ human_overrides=self.human_overrides,
706
+ approvals=self.approvals,
707
+ comments=self.comments,
708
+ oss_edition=self.oss_edition,
709
+ oss_license=self.oss_license,
710
+ requires_enterprise=self.requires_enterprise,
711
+ execution_allowed=self.execution_allowed,
712
+ infrastructure_intent_id=self.infrastructure_intent_id,
713
+ policy_violations=self.policy_violations,
714
+ infrastructure_intent=self.infrastructure_intent
715
+ )
716
+
717
+ def mark_as_oss_advisory(self) -> "HealingIntent":
718
+ """
719
+ Mark intent as OSS advisory only
720
+
721
+ Used when OSS creates an intent that can only be advisory
722
+ """
723
+ return HealingIntent(
724
+ # Copy all fields
725
+ action=self.action,
726
+ component=self.component,
727
+ parameters=self.parameters,
728
+ justification=self.justification,
729
+ confidence=self.confidence,
730
+ confidence_distribution=self.confidence_distribution,
731
+ incident_id=self.incident_id,
732
+ detected_at=self.detected_at,
733
+ risk_score=self.risk_score,
734
+ risk_factors=self.risk_factors,
735
+ cost_projection=self.cost_projection,
736
+ cost_confidence_interval=self.cost_confidence_interval,
737
+ recommended_action=self.recommended_action,
738
+ decision_tree=self.decision_tree,
739
+ alternative_actions=self.alternative_actions,
740
+ risk_profile=self.risk_profile,
741
+ reasoning_chain=self.reasoning_chain,
742
+ similar_incidents=self.similar_incidents,
743
+ rag_similarity_score=self.rag_similarity_score,
744
+ source=self.source,
745
+ intent_id=self.intent_id,
746
+ created_at=self.created_at,
747
+ status=IntentStatus.OSS_ADVISORY_ONLY,
748
+ execution_id=self.execution_id,
749
+ executed_at=self.executed_at,
750
+ execution_result=self.execution_result,
751
+ enterprise_metadata=self.enterprise_metadata,
752
+ human_overrides=self.human_overrides,
753
+ approvals=self.approvals,
754
+ comments=self.comments,
755
+ oss_edition=self.oss_edition,
756
+ oss_license=self.oss_license,
757
+ requires_enterprise=self.requires_enterprise,
758
+ execution_allowed=False, # Force no execution in OSS
759
+ infrastructure_intent_id=self.infrastructure_intent_id,
760
+ policy_violations=self.policy_violations,
761
+ infrastructure_intent=self.infrastructure_intent
762
+ )
763
+
764
+ @classmethod
765
+ def from_infrastructure_intent(
766
+ cls,
767
+ infrastructure_intent: Any, # InfrastructureIntent type
768
+ action: str,
769
+ component: str,
770
+ parameters: Dict[str, Any],
771
+ justification: str,
772
+ confidence: float = 0.85,
773
+ risk_score: Optional[float] = None,
774
+ risk_factors: Optional[Dict[str, float]] = None,
775
+ cost_projection: Optional[float] = None,
776
+ policy_violations: Optional[List[str]] = None,
777
+ recommended_action: Optional[RecommendedAction] = None,
778
+ source: IntentSource = IntentSource.INFRASTRUCTURE_ANALYSIS
779
+ ) -> "HealingIntent":
780
+ """
781
+ Create HealingIntent from infrastructure module analysis.
782
+
783
+ This bridges the infrastructure governance module with the healing system.
784
+ """
785
+ # Extract intent_id if available
786
+ infrastructure_intent_id = getattr(infrastructure_intent, 'intent_id', None)
787
+
788
+ # Convert infrastructure intent to dict for storage
789
+ if hasattr(infrastructure_intent, 'model_dump'):
790
+ intent_dict = infrastructure_intent.model_dump()
791
+ elif hasattr(infrastructure_intent, 'to_dict'):
792
+ intent_dict = infrastructure_intent.to_dict()
793
+ else:
794
+ intent_dict = {"type": str(type(infrastructure_intent))}
795
+
796
+ return cls(
797
+ action=action,
798
+ component=component,
799
+ parameters=parameters,
800
+ justification=justification,
801
+ confidence=confidence,
802
+ risk_score=risk_score,
803
+ risk_factors=risk_factors,
804
+ cost_projection=cost_projection,
805
+ policy_violations=policy_violations or [],
806
+ recommended_action=recommended_action,
807
+ source=source,
808
+ infrastructure_intent_id=infrastructure_intent_id,
809
+ infrastructure_intent=intent_dict,
810
+ oss_edition=OSS_EDITION,
811
+ requires_enterprise=True,
812
+ execution_allowed=False
813
+ )
814
+
815
+ @classmethod
816
+ def from_analysis(
817
+ cls,
818
+ action: str,
819
+ component: str,
820
+ parameters: Dict[str, Any],
821
+ justification: str,
822
+ confidence: float,
823
+ confidence_std: float = 0.05,
824
+ similar_incidents: Optional[List[Dict[str, Any]]] = None,
825
+ reasoning_chain: Optional[List[Dict[str, Any]]] = None,
826
+ incident_id: str = "",
827
+ source: IntentSource = IntentSource.OSS_ANALYSIS,
828
+ rag_similarity_score: Optional[float] = None,
829
+ risk_score: Optional[float] = None,
830
+ cost_projection: Optional[float] = None,
831
+ ) -> "HealingIntent":
832
+ """
833
+ Factory method for creating HealingIntent from OSS analysis
834
+
835
+ This is the primary way OSS creates intents.
836
+ Enhanced with probabilistic confidence and risk integration.
837
+ """
838
+ # Apply OSS limits to similar incidents
839
+ if similar_incidents and len(similar_incidents) > cls.MAX_SIMILAR_INCIDENTS:
840
+ similar_incidents = similar_incidents[:cls.MAX_SIMILAR_INCIDENTS]
841
+
842
+ # Create confidence distribution
843
+ conf_dist = ConfidenceDistribution(confidence, confidence_std)
844
+
845
+ # Calculate enhanced confidence based on similar incidents
846
+ enhanced_confidence = confidence
847
+ if similar_incidents:
848
+ similarity_scores = [
849
+ inc.get("similarity", 0.0)
850
+ for inc in similar_incidents
851
+ if "similarity" in inc
852
+ ]
853
+ if similarity_scores:
854
+ avg_similarity = sum(similarity_scores) / len(similarity_scores)
855
+ # Cap the boost to prevent overconfidence
856
+ confidence_boost = min(0.2, avg_similarity * 0.3)
857
+ enhanced_confidence = min(confidence * (1.0 + confidence_boost), cls.MAX_CONFIDENCE)
858
+
859
+ # Use provided RAG score or calculate from similar incidents
860
+ final_rag_score = rag_similarity_score
861
+ if final_rag_score is None and similar_incidents and len(similar_incidents) > 0:
862
+ # Take average of top 3 similarities
863
+ top_similarities = [
864
+ inc.get("similarity", 0.0)
865
+ for inc in similar_incidents[:3]
866
+ if "similarity" in inc
867
+ ]
868
+ if top_similarities:
869
+ final_rag_score = sum(top_similarities) / len(top_similarities)
870
+
871
+ return cls(
872
+ action=action,
873
+ component=component,
874
+ parameters=parameters,
875
+ justification=justification,
876
+ confidence=enhanced_confidence,
877
+ confidence_distribution=conf_dist.to_dict(),
878
+ incident_id=incident_id,
879
+ similar_incidents=similar_incidents,
880
+ reasoning_chain=reasoning_chain,
881
+ rag_similarity_score=final_rag_score,
882
+ source=source,
883
+ risk_score=risk_score,
884
+ cost_projection=cost_projection,
885
+ oss_edition=OSS_EDITION,
886
+ requires_enterprise=True,
887
+ execution_allowed=False,
888
+ )
889
+
890
+ @classmethod
891
+ def from_rag_recommendation(
892
+ cls,
893
+ action: str,
894
+ component: str,
895
+ parameters: Dict[str, Any],
896
+ rag_similarity_score: float,
897
+ similar_incidents: List[Dict[str, Any]],
898
+ justification_template: str = "Based on {count} similar historical incidents with {success_rate:.0%} success rate",
899
+ success_rate: Optional[float] = None,
900
+ risk_score: Optional[float] = None,
901
+ cost_projection: Optional[float] = None,
902
+ ) -> "HealingIntent":
903
+ """
904
+ Create HealingIntent from RAG graph recommendation
905
+
906
+ Specialized factory for RAG-based recommendations
907
+ """
908
+ if not similar_incidents:
909
+ raise ValidationError("RAG recommendation requires similar incidents")
910
+
911
+ # Calculate success rate if not provided
912
+ if success_rate is None:
913
+ if len(similar_incidents) == 0:
914
+ success_rate = 0.0
915
+ else:
916
+ successful = sum(1 for inc in similar_incidents if inc.get("success", False))
917
+ success_rate = successful / len(similar_incidents)
918
+
919
+ # Generate justification
920
+ justification = justification_template.format(
921
+ count=len(similar_incidents),
922
+ success_rate=success_rate or 0.0,
923
+ action=action,
924
+ component=component,
925
+ )
926
+
927
+ # Calculate confidence based on RAG similarity
928
+ base_confidence = rag_similarity_score * 0.8 # Scale similarity to confidence
929
+ if success_rate:
930
+ base_confidence = base_confidence * (0.7 + success_rate * 0.3)
931
+
932
+ return cls.from_analysis(
933
+ action=action,
934
+ component=component,
935
+ parameters=parameters,
936
+ justification=justification,
937
+ confidence=min(base_confidence, 0.95), # Cap at 95%
938
+ similar_incidents=similar_incidents,
939
+ incident_id=similar_incidents[0].get("incident_id", "") if similar_incidents else "",
940
+ source=IntentSource.RAG_SIMILARITY,
941
+ rag_similarity_score=rag_similarity_score,
942
+ risk_score=risk_score,
943
+ cost_projection=cost_projection,
944
+ )
945
+
946
+ @classmethod
947
+ def from_dict(cls, data: Dict[str, Any]) -> "HealingIntent":
948
+ """
949
+ Create from dictionary (deserialize)
950
+
951
+ Handles versioning and field conversion
952
+ """
953
+ # Handle versioning
954
+ version = data.get("version", "1.0.0")
955
+
956
+ # Create a copy to avoid mutating input
957
+ clean_data = data.copy()
958
+
959
+ # Convert string enums back to Enum instances
960
+ if "source" in clean_data and isinstance(clean_data["source"], str):
961
+ clean_data["source"] = IntentSource(clean_data["source"])
962
+
963
+ if "status" in clean_data and isinstance(clean_data["status"], str):
964
+ clean_data["status"] = IntentStatus(clean_data["status"])
965
+
966
+ if "recommended_action" in clean_data and isinstance(clean_data["recommended_action"], str):
967
+ try:
968
+ clean_data["recommended_action"] = RecommendedAction(clean_data["recommended_action"])
969
+ except ValueError:
970
+ clean_data["recommended_action"] = None
971
+
972
+ # Remove computed fields that shouldn't be in constructor
973
+ clean_data.pop("deterministic_id", None)
974
+ clean_data.pop("age_seconds", None)
975
+ clean_data.pop("is_executable", None)
976
+ clean_data.pop("is_oss_advisory", None)
977
+ clean_data.pop("requires_enterprise_upgrade", None)
978
+ clean_data.pop("version", None)
979
+ clean_data.pop("confidence_interval", None)
980
+
981
+ return cls(**clean_data)
982
+
983
+ def _normalize_parameters(self, params: Dict[str, Any]) -> Dict[str, Any]:
984
+ """
985
+ Normalize parameters for deterministic hashing
986
+
987
+ Ensures that parameter order and minor format differences
988
+ don't affect the deterministic ID.
989
+ """
990
+ normalized: Dict[str, Any] = {}
991
+
992
+ for key, value in sorted(params.items()):
993
+ normalized[key] = self._normalize_value(value)
994
+
995
+ return normalized
996
+
997
+ def _normalize_value(self, value: Any) -> Any:
998
+ """Normalize a single value for hashing"""
999
+ if isinstance(value, (int, float, str, bool, type(None))):
1000
+ return value
1001
+ elif isinstance(value, (list, tuple, set)):
1002
+ # Convert all iterables to sorted tuples
1003
+ normalized_items = tuple(
1004
+ sorted(
1005
+ self._normalize_value(v) for v in value
1006
+ )
1007
+ )
1008
+ return normalized_items
1009
+ elif isinstance(value, dict):
1010
+ # Recursively normalize dicts
1011
+ return self._normalize_parameters(value)
1012
+ elif hasattr(value, '__dict__'):
1013
+ # Handle objects with __dict__
1014
+ return self._normalize_parameters(value.__dict__)
1015
+ else:
1016
+ # Convert to string representation for other types
1017
+ try:
1018
+ return str(value)
1019
+ except Exception:
1020
+ # Fallback for objects that can't be stringified
1021
+ return f"<unserializable:{type(value).__name__}>"
1022
+
1023
+ def get_oss_context(self) -> Dict[str, Any]:
1024
+ """
1025
+ Get OSS analysis context (stays in OSS)
1026
+
1027
+ This data never leaves the OSS environment for privacy and IP protection.
1028
+ """
1029
+ return {
1030
+ "reasoning_chain": self.reasoning_chain,
1031
+ "similar_incidents": self.similar_incidents,
1032
+ "rag_similarity_score": self.rag_similarity_score,
1033
+ "decision_tree": self.decision_tree,
1034
+ "alternative_actions": self.alternative_actions,
1035
+ "analysis_timestamp": datetime.fromtimestamp(self.detected_at).isoformat(),
1036
+ "source": self.source.value,
1037
+ "created_at": datetime.fromtimestamp(self.created_at).isoformat(),
1038
+ "oss_edition": self.oss_edition,
1039
+ "is_oss_advisory": self.is_oss_advisory,
1040
+ "infrastructure_intent": self.infrastructure_intent,
1041
+ }
1042
+
1043
+ def get_execution_summary(self) -> Dict[str, Any]:
1044
+ """
1045
+ Get execution summary (public information)
1046
+
1047
+ Safe to share externally
1048
+ """
1049
+ summary = {
1050
+ "intent_id": self.deterministic_id,
1051
+ "action": self.action,
1052
+ "component": self.component,
1053
+ "confidence": self.confidence,
1054
+ "confidence_interval": self.confidence_interval,
1055
+ "risk_score": self.risk_score,
1056
+ "cost_projection": self.cost_projection,
1057
+ "status": self.status.value,
1058
+ "created_at": datetime.fromtimestamp(self.created_at).isoformat(),
1059
+ "age_seconds": self.age_seconds,
1060
+ "oss_edition": self.oss_edition,
1061
+ "requires_enterprise": self.requires_enterprise,
1062
+ "is_oss_advisory": self.is_oss_advisory,
1063
+ "source": self.source.value,
1064
+ "policy_violations_count": len(self.policy_violations) if self.policy_violations else 0,
1065
+ "confidence_basis": self._get_confidence_basis(),
1066
+ }
1067
+
1068
+ if self.executed_at:
1069
+ summary["executed_at"] = datetime.fromtimestamp(self.executed_at).isoformat()
1070
+ summary["execution_duration_seconds"] = self.executed_at - self.created_at
1071
+
1072
+ if self.execution_result:
1073
+ summary["execution_success"] = self.execution_result.get("success", False)
1074
+ summary["execution_message"] = self.execution_result.get("message", "")
1075
+
1076
+ if self.rag_similarity_score:
1077
+ summary["rag_similarity_score"] = self.rag_similarity_score
1078
+
1079
+ if self.similar_incidents:
1080
+ summary["similar_incidents_count"] = len(self.similar_incidents)
1081
+
1082
+ if self.approvals:
1083
+ summary["approvals_count"] = len(self.approvals)
1084
+ summary["approved_by"] = [a.get("approver") for a in self.approvals if a.get("approver")]
1085
+
1086
+ if self.human_overrides:
1087
+ summary["overrides_count"] = len(self.human_overrides)
1088
+
1089
+ return summary
1090
+
1091
+ def is_immutable(self) -> bool:
1092
+ """Check if the intent is truly immutable (frozen dataclass property)"""
1093
+ try:
1094
+ # Try to modify a field - should raise FrozenInstanceError
1095
+ object.__setattr__(self, '_test_immutable', True)
1096
+ return False
1097
+ except Exception:
1098
+ return True
1099
+
1100
+ def __repr__(self) -> str:
1101
+ return (
1102
+ f"HealingIntent("
1103
+ f"id={self.deterministic_id[:8]}..., "
1104
+ f"action={self.action}, "
1105
+ f"component={self.component}, "
1106
+ f"confidence={self.confidence:.2f}, "
1107
+ f"risk={self.risk_score:.2f if self.risk_score else 'N/A'}, "
1108
+ f"status={self.status.value}"
1109
+ f")"
1110
+ )
1111
+
1112
+
1113
+ class HealingIntentSerializer:
1114
+ """
1115
+ Versioned serialization for HealingIntent
1116
+
1117
+ Enhanced with:
1118
+ - Probabilistic confidence distribution support
1119
+ - Risk and cost field serialization
1120
+ - Backward compatibility with v1.x
1121
+ - OSS/Enterprise edition detection
1122
+ """
1123
+
1124
+ SCHEMA_VERSION: ClassVar[str] = "2.0.0"
1125
+
1126
+ @classmethod
1127
+ def serialize(cls, intent: HealingIntent, version: str = "2.0.0") -> Dict[str, Any]:
1128
+ """
1129
+ Serialize HealingIntent with versioning
1130
+
1131
+ Args:
1132
+ intent: HealingIntent to serialize
1133
+ version: Schema version to use
1134
+
1135
+ Returns:
1136
+ Versioned serialization dictionary
1137
+
1138
+ Raises:
1139
+ SerializationError: If serialization fails
1140
+ """
1141
+ try:
1142
+ if version == "2.0.0":
1143
+ return {
1144
+ "version": version,
1145
+ "schema_version": cls.SCHEMA_VERSION,
1146
+ "data": intent.to_dict(include_oss_context=True),
1147
+ "metadata": {
1148
+ "serialized_at": time.time(),
1149
+ "deterministic_id": intent.deterministic_id,
1150
+ "is_executable": intent.is_executable,
1151
+ "is_oss_advisory": intent.is_oss_advisory,
1152
+ "requires_enterprise_upgrade": intent.requires_enterprise_upgrade,
1153
+ "oss_edition": intent.oss_edition,
1154
+ "has_probabilistic_confidence": intent.confidence_distribution is not None,
1155
+ "has_risk_assessment": intent.risk_score is not None,
1156
+ "has_cost_projection": intent.cost_projection is not None,
1157
+ }
1158
+ }
1159
+ elif version == "1.1.0" or version == "1.0.0":
1160
+ # Backward compatibility with v1.x
1161
+ data = intent.to_dict(include_oss_context=True)
1162
+
1163
+ # Remove v2.0.0 fields for compatibility
1164
+ data.pop("confidence_distribution", None)
1165
+ data.pop("risk_score", None)
1166
+ data.pop("risk_factors", None)
1167
+ data.pop("cost_projection", None)
1168
+ data.pop("cost_confidence_interval", None)
1169
+ data.pop("recommended_action", None)
1170
+ data.pop("decision_tree", None)
1171
+ data.pop("alternative_actions", None)
1172
+ data.pop("risk_profile", None)
1173
+ data.pop("human_overrides", None)
1174
+ data.pop("approvals", None)
1175
+ data.pop("comments", None)
1176
+ data.pop("infrastructure_intent_id", None)
1177
+ data.pop("policy_violations", None)
1178
+ data.pop("infrastructure_intent", None)
1179
+
1180
+ # Ensure status is compatible
1181
+ if data.get("status") in [
1182
+ IntentStatus.EXECUTING_PARTIAL.value,
1183
+ IntentStatus.COMPLETED_PARTIAL.value,
1184
+ IntentStatus.ROLLED_BACK.value,
1185
+ IntentStatus.PENDING_APPROVAL.value,
1186
+ IntentStatus.APPROVED.value,
1187
+ IntentStatus.APPROVED_WITH_OVERRIDES.value
1188
+ ]:
1189
+ data["status"] = IntentStatus.PENDING_EXECUTION.value
1190
+
1191
+ return {
1192
+ "version": version,
1193
+ "schema_version": "1.1.0" if version == "1.1.0" else "1.0.0",
1194
+ "data": data,
1195
+ "metadata": {
1196
+ "serialized_at": time.time(),
1197
+ "deterministic_id": intent.deterministic_id,
1198
+ "is_executable": intent.is_executable,
1199
+ "is_oss_advisory": intent.is_oss_advisory,
1200
+ }
1201
+ }
1202
+ else:
1203
+ raise SerializationError(f"Unsupported version: {version}")
1204
+
1205
+ except Exception as e:
1206
+ raise SerializationError(f"Failed to serialize HealingIntent: {e}") from e
1207
+
1208
+ @classmethod
1209
+ def deserialize(cls, data: Dict[str, Any]) -> HealingIntent:
1210
+ """
1211
+ Deserialize HealingIntent with version detection
1212
+
1213
+ Args:
1214
+ data: Serialized data
1215
+
1216
+ Returns:
1217
+ Deserialized HealingIntent
1218
+
1219
+ Raises:
1220
+ SerializationError: If deserialization fails
1221
+ """
1222
+ try:
1223
+ version = data.get("version", "1.0.0")
1224
+ intent_data = data.get("data", data) # Handle both wrapped and unwrapped
1225
+
1226
+ if version in ["2.0.0", "1.1.0", "1.0.0"]:
1227
+ # Handle version differences
1228
+ if version.startswith("1."):
1229
+ # Add default values for v2 fields
1230
+ intent_data.setdefault("confidence_distribution", None)
1231
+ intent_data.setdefault("risk_score", None)
1232
+ intent_data.setdefault("risk_factors", None)
1233
+ intent_data.setdefault("cost_projection", None)
1234
+ intent_data.setdefault("cost_confidence_interval", None)
1235
+ intent_data.setdefault("recommended_action", None)
1236
+ intent_data.setdefault("decision_tree", None)
1237
+ intent_data.setdefault("alternative_actions", None)
1238
+ intent_data.setdefault("risk_profile", None)
1239
+ intent_data.setdefault("human_overrides", [])
1240
+ intent_data.setdefault("approvals", [])
1241
+ intent_data.setdefault("comments", [])
1242
+ intent_data.setdefault("infrastructure_intent_id", None)
1243
+ intent_data.setdefault("policy_violations", [])
1244
+ intent_data.setdefault("infrastructure_intent", None)
1245
+
1246
+ return HealingIntent.from_dict(intent_data)
1247
+ else:
1248
+ raise SerializationError(f"Unsupported version: {version}")
1249
+
1250
+ except KeyError as e:
1251
+ raise SerializationError(f"Missing required field in serialized data: {e}") from e
1252
+ except Exception as e:
1253
+ raise SerializationError(f"Failed to deserialize HealingIntent: {e}") from e
1254
+
1255
+ @classmethod
1256
+ def to_json(cls, intent: HealingIntent, pretty: bool = False) -> str:
1257
+ """Convert HealingIntent to JSON string"""
1258
+ try:
1259
+ serialized = cls.serialize(intent)
1260
+ if pretty:
1261
+ return json.dumps(serialized, indent=2, default=str)
1262
+ else:
1263
+ return json.dumps(serialized, default=str)
1264
+ except Exception as e:
1265
+ raise SerializationError(f"Failed to convert to JSON: {e}") from e
1266
+
1267
+ @classmethod
1268
+ def from_json(cls, json_str: str) -> HealingIntent:
1269
+ """Create HealingIntent from JSON string"""
1270
+ try:
1271
+ data = json.loads(json_str)
1272
+ return cls.deserialize(data)
1273
+ except json.JSONDecodeError as e:
1274
+ raise SerializationError(f"Invalid JSON: {e}") from e
1275
+ except Exception as e:
1276
+ raise SerializationError(f"Failed to parse JSON: {e}") from e
1277
+
1278
+ @classmethod
1279
+ def to_enterprise_json(cls, intent: HealingIntent) -> str:
1280
+ """
1281
+ Convert to Enterprise-ready JSON (excludes OSS context)
1282
+
1283
+ This is what should be sent to the Enterprise API
1284
+ """
1285
+ try:
1286
+ enterprise_request = intent.to_enterprise_request()
1287
+ return json.dumps(enterprise_request, default=str)
1288
+ except Exception as e:
1289
+ raise SerializationError(f"Failed to create Enterprise JSON: {e}") from e
1290
+
1291
+ @classmethod
1292
+ def validate_for_oss(cls, intent: HealingIntent) -> bool:
1293
+ """
1294
+ Validate that HealingIntent complies with OSS boundaries
1295
+
1296
+ Returns:
1297
+ True if intent is valid for OSS edition
1298
+ """
1299
+ try:
1300
+ # Check OSS edition
1301
+ if intent.oss_edition != OSS_EDITION:
1302
+ return False
1303
+
1304
+ # Check execution restrictions
1305
+ if intent.execution_allowed:
1306
+ return False
1307
+
1308
+ # Check similar incidents limit
1309
+ if intent.similar_incidents and len(intent.similar_incidents) > HealingIntent.MAX_SIMILAR_INCIDENTS:
1310
+ return False
1311
+
1312
+ # Check that frozen dataclass property is preserved
1313
+ if not intent.is_immutable():
1314
+ return False
1315
+
1316
+ # Check that no execution fields are set
1317
+ if intent.executed_at is not None or intent.execution_id is not None:
1318
+ return False
1319
+
1320
+ return True
1321
+
1322
+ except Exception:
1323
+ return False
1324
+
1325
+
1326
+ # Factory functions for common use cases
1327
+ def create_infrastructure_healing_intent(
1328
+ infrastructure_result: Any, # HealingIntent from infrastructure module
1329
+ action_mapping: Optional[Dict[str, str]] = None
1330
+ ) -> HealingIntent:
1331
+ """
1332
+ Create a healing intent from infrastructure module analysis result.
1333
+
1334
+ This bridges the infrastructure governance module with the main healing system.
1335
+
1336
+ Args:
1337
+ infrastructure_result: The HealingIntent from infrastructure.evaluate()
1338
+ action_mapping: Optional mapping from infrastructure actions to healing actions
1339
+
1340
+ Returns:
1341
+ HealingIntent ready for the healing system
1342
+ """
1343
+ # Default action mapping
1344
+ if action_mapping is None:
1345
+ action_mapping = {
1346
+ "approve": "execute",
1347
+ "deny": "block",
1348
+ "escalate": "escalate",
1349
+ "defer": "defer"
1350
+ }
1351
+
1352
+ # Extract fields from infrastructure result
1353
+ recommended_action = getattr(infrastructure_result, 'recommended_action', None)
1354
+ if recommended_action and hasattr(recommended_action, 'value'):
1355
+ action = action_mapping.get(recommended_action.value, "review")
1356
+ else:
1357
+ action = "review"
1358
+
1359
+ # Build parameters
1360
+ parameters = {
1361
+ "infrastructure_intent_id": getattr(infrastructure_result, 'intent_id', None),
1362
+ "risk_score": getattr(infrastructure_result, 'risk_score', None),
1363
+ "cost_projection": getattr(infrastructure_result, 'cost_projection', None),
1364
+ "policy_violations": getattr(infrastructure_result, 'policy_violations', []),
1365
+ "evaluation_details": getattr(infrastructure_result, 'evaluation_details', {})
1366
+ }
1367
+
1368
+ # Build justification
1369
+ justification_parts = [
1370
+ getattr(infrastructure_result, 'justification', "Infrastructure analysis completed"),
1371
+ ]
1372
+
1373
+ policy_violations = getattr(infrastructure_result, 'policy_violations', [])
1374
+ if policy_violations:
1375
+ justification_parts.append(f"Policy violations: {'; '.join(policy_violations)}")
1376
+
1377
+ return HealingIntent.from_infrastructure_intent(
1378
+ infrastructure_intent=getattr(infrastructure_result, 'infrastructure_intent', None),
1379
+ action=action,
1380
+ component="infrastructure",
1381
+ parameters=parameters,
1382
+ justification=" ".join(justification_parts),
1383
+ confidence=getattr(infrastructure_result, 'confidence_score', 0.85),
1384
+ risk_score=getattr(infrastructure_result, 'risk_score', None),
1385
+ policy_violations=policy_violations,
1386
+ recommended_action=recommended_action,
1387
+ source=IntentSource.INFRASTRUCTURE_ANALYSIS
1388
+ ).mark_as_oss_advisory()
1389
+
1390
+
1391
+ def create_rollback_intent(
1392
+ component: str,
1393
+ revision: str = "previous",
1394
+ justification: str = "",
1395
+ incident_id: str = "",
1396
+ similar_incidents: Optional[List[Dict[str, Any]]] = None,
1397
+ rag_similarity_score: Optional[float] = None,
1398
+ risk_score: Optional[float] = None,
1399
+ cost_projection: Optional[float] = None,
1400
+ ) -> HealingIntent:
1401
+ """Create a rollback healing intent with OSS limits"""
1402
+ if not justification:
1403
+ justification = f"Rollback {component} to {revision} revision"
1404
+
1405
+ return HealingIntent.from_analysis(
1406
+ action="rollback",
1407
+ component=component,
1408
+ parameters={"revision": revision},
1409
+ justification=justification,
1410
+ confidence=0.9,
1411
+ similar_incidents=similar_incidents,
1412
+ incident_id=incident_id,
1413
+ rag_similarity_score=rag_similarity_score,
1414
+ risk_score=risk_score,
1415
+ cost_projection=cost_projection,
1416
+ ).mark_as_oss_advisory()
1417
+
1418
+
1419
+ def create_restart_intent(
1420
+ component: str,
1421
+ container_id: Optional[str] = None,
1422
+ justification: str = "",
1423
+ incident_id: str = "",
1424
+ similar_incidents: Optional[List[Dict[str, Any]]] = None,
1425
+ rag_similarity_score: Optional[float] = None,
1426
+ risk_score: Optional[float] = None,
1427
+ cost_projection: Optional[float] = None,
1428
+ ) -> HealingIntent:
1429
+ """Create a container restart healing intent with OSS limits"""
1430
+ parameters = {}
1431
+ if container_id:
1432
+ parameters["container_id"] = container_id
1433
+
1434
+ if not justification:
1435
+ justification = f"Restart container for {component}"
1436
+
1437
+ return HealingIntent.from_analysis(
1438
+ action="restart_container",
1439
+ component=component,
1440
+ parameters=parameters,
1441
+ justification=justification,
1442
+ confidence=0.85,
1443
+ similar_incidents=similar_incidents,
1444
+ incident_id=incident_id,
1445
+ rag_similarity_score=rag_similarity_score,
1446
+ risk_score=risk_score,
1447
+ cost_projection=cost_projection,
1448
+ ).mark_as_oss_advisory()
1449
+
1450
+
1451
+ def create_scale_out_intent(
1452
+ component: str,
1453
+ scale_factor: int = 2,
1454
+ justification: str = "",
1455
+ incident_id: str = "",
1456
+ similar_incidents: Optional[List[Dict[str, Any]]] = None,
1457
+ rag_similarity_score: Optional[float] = None,
1458
+ risk_score: Optional[float] = None,
1459
+ cost_projection: Optional[float] = None,
1460
+ ) -> HealingIntent:
1461
+ """Create a scale-out healing intent with OSS limits"""
1462
+ if not justification:
1463
+ justification = f"Scale out {component} by factor {scale_factor}"
1464
+
1465
+ return HealingIntent.from_analysis(
1466
+ action="scale_out",
1467
+ component=component,
1468
+ parameters={"scale_factor": scale_factor},
1469
+ justification=justification,
1470
+ confidence=0.8,
1471
+ similar_incidents=similar_incidents,
1472
+ incident_id=incident_id,
1473
+ rag_similarity_score=rag_similarity_score,
1474
+ risk_score=risk_score,
1475
+ cost_projection=cost_projection,
1476
+ ).mark_as_oss_advisory()
1477
+
1478
+
1479
+ def create_oss_advisory_intent(
1480
+ action: str,
1481
+ component: str,
1482
+ parameters: Dict[str, Any],
1483
+ justification: str,
1484
+ confidence: float = 0.85,
1485
+ incident_id: str = "",
1486
+ risk_score: Optional[float] = None,
1487
+ cost_projection: Optional[float] = None,
1488
+ ) -> HealingIntent:
1489
+ """
1490
+ Create a generic OSS advisory-only intent
1491
+
1492
+ Used when OSS wants to recommend an action without execution capability
1493
+ """
1494
+ return HealingIntent(
1495
+ action=action,
1496
+ component=component,
1497
+ parameters=parameters,
1498
+ justification=justification,
1499
+ confidence=confidence,
1500
+ incident_id=incident_id,
1501
+ risk_score=risk_score,
1502
+ cost_projection=cost_projection,
1503
+ oss_edition=OSS_EDITION,
1504
+ requires_enterprise=True,
1505
+ execution_allowed=False,
1506
+ status=IntentStatus.OSS_ADVISORY_ONLY,
1507
+ )
1508
+
1509
+
1510
+ # Export
1511
+ __all__ = [
1512
+ # Main class
1513
+ "HealingIntent",
1514
+
1515
+ # Supporting classes
1516
+ "ConfidenceDistribution",
1517
+ "HealingIntentSerializer",
1518
+
1519
+ # Enums
1520
+ "IntentSource",
1521
+ "IntentStatus",
1522
+ "RecommendedAction",
1523
+
1524
+ # Exceptions
1525
+ "HealingIntentError",
1526
+ "SerializationError",
1527
+ "ValidationError",
1528
+
1529
+ # Factory functions
1530
+ "create_infrastructure_healing_intent",
1531
+ "create_rollback_intent",
1532
+ "create_restart_intent",
1533
+ "create_scale_out_intent",
1534
+ "create_oss_advisory_intent",
1535
+ ]