kailash 0.8.4__py3-none-any.whl → 0.8.5__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.
Files changed (79) hide show
  1. kailash/__init__.py +1 -7
  2. kailash/cli/__init__.py +11 -1
  3. kailash/cli/validation_audit.py +570 -0
  4. kailash/core/actors/supervisor.py +1 -1
  5. kailash/core/resilience/circuit_breaker.py +71 -1
  6. kailash/core/resilience/health_monitor.py +172 -0
  7. kailash/edge/compliance.py +33 -0
  8. kailash/edge/consistency.py +609 -0
  9. kailash/edge/coordination/__init__.py +30 -0
  10. kailash/edge/coordination/global_ordering.py +355 -0
  11. kailash/edge/coordination/leader_election.py +217 -0
  12. kailash/edge/coordination/partition_detector.py +296 -0
  13. kailash/edge/coordination/raft.py +485 -0
  14. kailash/edge/discovery.py +63 -1
  15. kailash/edge/migration/__init__.py +19 -0
  16. kailash/edge/migration/edge_migrator.py +832 -0
  17. kailash/edge/monitoring/__init__.py +21 -0
  18. kailash/edge/monitoring/edge_monitor.py +736 -0
  19. kailash/edge/prediction/__init__.py +10 -0
  20. kailash/edge/prediction/predictive_warmer.py +591 -0
  21. kailash/edge/resource/__init__.py +102 -0
  22. kailash/edge/resource/cloud_integration.py +796 -0
  23. kailash/edge/resource/cost_optimizer.py +949 -0
  24. kailash/edge/resource/docker_integration.py +919 -0
  25. kailash/edge/resource/kubernetes_integration.py +893 -0
  26. kailash/edge/resource/platform_integration.py +913 -0
  27. kailash/edge/resource/predictive_scaler.py +959 -0
  28. kailash/edge/resource/resource_analyzer.py +824 -0
  29. kailash/edge/resource/resource_pools.py +610 -0
  30. kailash/integrations/dataflow_edge.py +261 -0
  31. kailash/mcp_server/registry_integration.py +1 -1
  32. kailash/monitoring/__init__.py +18 -0
  33. kailash/monitoring/alerts.py +646 -0
  34. kailash/monitoring/metrics.py +677 -0
  35. kailash/nodes/__init__.py +2 -0
  36. kailash/nodes/ai/semantic_memory.py +2 -2
  37. kailash/nodes/base.py +545 -0
  38. kailash/nodes/edge/__init__.py +36 -0
  39. kailash/nodes/edge/base.py +240 -0
  40. kailash/nodes/edge/cloud_node.py +710 -0
  41. kailash/nodes/edge/coordination.py +239 -0
  42. kailash/nodes/edge/docker_node.py +825 -0
  43. kailash/nodes/edge/edge_data.py +582 -0
  44. kailash/nodes/edge/edge_migration_node.py +392 -0
  45. kailash/nodes/edge/edge_monitoring_node.py +421 -0
  46. kailash/nodes/edge/edge_state.py +673 -0
  47. kailash/nodes/edge/edge_warming_node.py +393 -0
  48. kailash/nodes/edge/kubernetes_node.py +652 -0
  49. kailash/nodes/edge/platform_node.py +766 -0
  50. kailash/nodes/edge/resource_analyzer_node.py +378 -0
  51. kailash/nodes/edge/resource_optimizer_node.py +501 -0
  52. kailash/nodes/edge/resource_scaler_node.py +397 -0
  53. kailash/nodes/ports.py +676 -0
  54. kailash/runtime/local.py +344 -1
  55. kailash/runtime/validation/__init__.py +20 -0
  56. kailash/runtime/validation/connection_context.py +119 -0
  57. kailash/runtime/validation/enhanced_error_formatter.py +202 -0
  58. kailash/runtime/validation/error_categorizer.py +164 -0
  59. kailash/runtime/validation/metrics.py +380 -0
  60. kailash/runtime/validation/performance.py +615 -0
  61. kailash/runtime/validation/suggestion_engine.py +212 -0
  62. kailash/testing/fixtures.py +2 -2
  63. kailash/workflow/builder.py +230 -4
  64. kailash/workflow/contracts.py +418 -0
  65. kailash/workflow/edge_infrastructure.py +369 -0
  66. kailash/workflow/migration.py +3 -3
  67. kailash/workflow/type_inference.py +669 -0
  68. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/METADATA +43 -27
  69. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/RECORD +73 -27
  70. kailash/nexus/__init__.py +0 -21
  71. kailash/nexus/cli/__init__.py +0 -5
  72. kailash/nexus/cli/__main__.py +0 -6
  73. kailash/nexus/cli/main.py +0 -176
  74. kailash/nexus/factory.py +0 -413
  75. kailash/nexus/gateway.py +0 -545
  76. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/WHEEL +0 -0
  77. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/entry_points.txt +0 -0
  78. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/licenses/LICENSE +0 -0
  79. {kailash-0.8.4.dist-info → kailash-0.8.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,418 @@
1
+ """
2
+ Connection Contract System for Kailash Workflows
3
+
4
+ Provides contract-based validation for workflow connections using JSON Schema.
5
+ Contracts define the expected data format, constraints, and security policies
6
+ for data flowing between nodes.
7
+
8
+ Design Goals:
9
+ 1. Declarative validation using JSON Schema
10
+ 2. Reusable contract definitions
11
+ 3. Security policy enforcement
12
+ 4. Backward compatibility with existing workflows
13
+ 5. Clear contract violation messages
14
+ """
15
+
16
+ import json
17
+ from dataclasses import dataclass, field
18
+ from enum import Enum
19
+ from typing import Any, Dict, List, Optional, Union
20
+
21
+ import jsonschema
22
+ from jsonschema import Draft7Validator
23
+ from jsonschema import ValidationError as JsonSchemaError
24
+
25
+ from kailash.sdk_exceptions import WorkflowValidationError
26
+
27
+
28
+ class SecurityPolicy(Enum):
29
+ """Security policies that can be applied to connections."""
30
+
31
+ NONE = "none"
32
+ """No special security policy"""
33
+
34
+ NO_PII = "no_pii"
35
+ """No personally identifiable information allowed"""
36
+
37
+ NO_CREDENTIALS = "no_credentials"
38
+ """No passwords, tokens, or keys allowed"""
39
+
40
+ NO_SQL = "no_sql"
41
+ """No SQL queries allowed (prevents injection)"""
42
+
43
+ SANITIZED = "sanitized"
44
+ """Data must be sanitized/escaped"""
45
+
46
+ ENCRYPTED = "encrypted"
47
+ """Data must be encrypted in transit"""
48
+
49
+
50
+ @dataclass
51
+ class ConnectionContract:
52
+ """
53
+ Defines a contract for data flowing through a connection.
54
+
55
+ A contract specifies:
56
+ - JSON Schema for data validation
57
+ - Security policies to enforce
58
+ - Transformation rules (optional)
59
+ - Audit requirements
60
+ """
61
+
62
+ name: str
63
+ """Human-readable name for the contract"""
64
+
65
+ description: str = ""
66
+ """Description of what this contract validates"""
67
+
68
+ source_schema: Optional[Dict[str, Any]] = None
69
+ """JSON Schema for validating source node output"""
70
+
71
+ target_schema: Optional[Dict[str, Any]] = None
72
+ """JSON Schema for validating target node input"""
73
+
74
+ security_policies: List[SecurityPolicy] = field(default_factory=list)
75
+ """Security policies to enforce on this connection"""
76
+
77
+ transformations: Optional[Dict[str, Any]] = None
78
+ """Optional transformations to apply (e.g., type coercion rules)"""
79
+
80
+ audit_level: str = "normal"
81
+ """Audit level: 'none', 'normal', 'detailed'"""
82
+
83
+ metadata: Dict[str, Any] = field(default_factory=dict)
84
+ """Additional metadata for the contract"""
85
+
86
+ def __post_init__(self):
87
+ """Validate contract after initialization."""
88
+ # Validate schemas if provided
89
+ if self.source_schema:
90
+ try:
91
+ Draft7Validator.check_schema(self.source_schema)
92
+ except Exception as e:
93
+ raise ValueError(f"Invalid source schema: {e}")
94
+
95
+ if self.target_schema:
96
+ try:
97
+ Draft7Validator.check_schema(self.target_schema)
98
+ except Exception as e:
99
+ raise ValueError(f"Invalid target schema: {e}")
100
+
101
+ def validate_source(self, data: Any) -> tuple[bool, Optional[str]]:
102
+ """
103
+ Validate data against source schema.
104
+
105
+ Args:
106
+ data: Data to validate
107
+
108
+ Returns:
109
+ Tuple of (is_valid, error_message)
110
+ """
111
+ if not self.source_schema:
112
+ return True, None
113
+
114
+ try:
115
+ validator = Draft7Validator(self.source_schema)
116
+ validator.validate(data)
117
+ return True, None
118
+ except JsonSchemaError as e:
119
+ return False, f"Source validation failed: {e.message}"
120
+
121
+ def validate_target(self, data: Any) -> tuple[bool, Optional[str]]:
122
+ """
123
+ Validate data against target schema.
124
+
125
+ Args:
126
+ data: Data to validate
127
+
128
+ Returns:
129
+ Tuple of (is_valid, error_message)
130
+ """
131
+ if not self.target_schema:
132
+ return True, None
133
+
134
+ try:
135
+ validator = Draft7Validator(self.target_schema)
136
+ validator.validate(data)
137
+ return True, None
138
+ except JsonSchemaError as e:
139
+ return False, f"Target validation failed: {e.message}"
140
+
141
+ def check_security_policies(self, data: Any) -> tuple[bool, Optional[str]]:
142
+ """
143
+ Check if data complies with security policies.
144
+
145
+ Args:
146
+ data: Data to check
147
+
148
+ Returns:
149
+ Tuple of (is_compliant, violation_message)
150
+ """
151
+ data_str = str(data).lower() if data is not None else ""
152
+
153
+ for policy in self.security_policies:
154
+ if policy == SecurityPolicy.NO_PII:
155
+ # Simple PII detection (should be more sophisticated in production)
156
+ pii_patterns = ["ssn", "social security", "credit card", "passport"]
157
+ for pattern in pii_patterns:
158
+ if pattern in data_str:
159
+ return False, f"PII detected: {pattern}"
160
+
161
+ elif policy == SecurityPolicy.NO_CREDENTIALS:
162
+ cred_patterns = [
163
+ "password",
164
+ "token",
165
+ "api_key",
166
+ "secret",
167
+ "private_key",
168
+ ]
169
+ for pattern in cred_patterns:
170
+ if pattern in data_str:
171
+ return False, f"Credential detected: {pattern}"
172
+
173
+ elif policy == SecurityPolicy.NO_SQL:
174
+ sql_patterns = [
175
+ "select ",
176
+ "insert ",
177
+ "update ",
178
+ "delete ",
179
+ "drop ",
180
+ "union ",
181
+ ]
182
+ for pattern in sql_patterns:
183
+ if pattern in data_str:
184
+ return False, f"SQL pattern detected: {pattern}"
185
+
186
+ return True, None
187
+
188
+ def to_dict(self) -> Dict[str, Any]:
189
+ """Convert contract to dictionary for serialization."""
190
+ return {
191
+ "name": self.name,
192
+ "description": self.description,
193
+ "source_schema": self.source_schema,
194
+ "target_schema": self.target_schema,
195
+ "security_policies": [p.value for p in self.security_policies],
196
+ "transformations": self.transformations,
197
+ "audit_level": self.audit_level,
198
+ "metadata": self.metadata,
199
+ }
200
+
201
+ @classmethod
202
+ def from_dict(cls, data: Dict[str, Any]) -> "ConnectionContract":
203
+ """Create contract from dictionary."""
204
+ return cls(
205
+ name=data["name"],
206
+ description=data.get("description", ""),
207
+ source_schema=data.get("source_schema"),
208
+ target_schema=data.get("target_schema"),
209
+ security_policies=[
210
+ SecurityPolicy(p) for p in data.get("security_policies", [])
211
+ ],
212
+ transformations=data.get("transformations"),
213
+ audit_level=data.get("audit_level", "normal"),
214
+ metadata=data.get("metadata", {}),
215
+ )
216
+
217
+
218
+ class ContractRegistry:
219
+ """Registry for reusable connection contracts."""
220
+
221
+ def __init__(self):
222
+ self._contracts: Dict[str, ConnectionContract] = {}
223
+ self._initialize_common_contracts()
224
+
225
+ def _initialize_common_contracts(self):
226
+ """Initialize commonly used contracts."""
227
+ # String data contract
228
+ self.register(
229
+ ConnectionContract(
230
+ name="string_data",
231
+ description="Basic string data contract",
232
+ source_schema={"type": "string"},
233
+ target_schema={"type": "string"},
234
+ )
235
+ )
236
+
237
+ # Numeric data contract
238
+ self.register(
239
+ ConnectionContract(
240
+ name="numeric_data",
241
+ description="Numeric data contract",
242
+ source_schema={"type": "number"},
243
+ target_schema={"type": "number"},
244
+ )
245
+ )
246
+
247
+ # File path contract
248
+ self.register(
249
+ ConnectionContract(
250
+ name="file_path",
251
+ description="File path validation",
252
+ source_schema={
253
+ "type": "string",
254
+ "pattern": "^[^\\0]+$", # No null bytes
255
+ },
256
+ target_schema={"type": "string", "pattern": "^[^\\0]+$"},
257
+ security_policies=[SecurityPolicy.NO_SQL],
258
+ )
259
+ )
260
+
261
+ # SQL query contract
262
+ self.register(
263
+ ConnectionContract(
264
+ name="sql_query",
265
+ description="SQL query with injection protection",
266
+ source_schema={"type": "string"},
267
+ target_schema={"type": "string"},
268
+ security_policies=[SecurityPolicy.SANITIZED],
269
+ )
270
+ )
271
+
272
+ # User data contract
273
+ self.register(
274
+ ConnectionContract(
275
+ name="user_data",
276
+ description="User data with PII protection",
277
+ source_schema={
278
+ "type": "object",
279
+ "properties": {
280
+ "id": {"type": "string"},
281
+ "name": {"type": "string"},
282
+ "email": {"type": "string", "format": "email"},
283
+ },
284
+ "required": ["id"],
285
+ },
286
+ target_schema={
287
+ "type": "object",
288
+ "properties": {
289
+ "id": {"type": "string"},
290
+ "name": {"type": "string"},
291
+ "email": {"type": "string", "format": "email"},
292
+ },
293
+ },
294
+ security_policies=[SecurityPolicy.NO_CREDENTIALS],
295
+ audit_level="detailed",
296
+ )
297
+ )
298
+
299
+ def register(self, contract: ConnectionContract) -> None:
300
+ """Register a contract in the registry."""
301
+ self._contracts[contract.name] = contract
302
+
303
+ def get(self, name: str) -> Optional[ConnectionContract]:
304
+ """Get a contract by name."""
305
+ return self._contracts.get(name)
306
+
307
+ def list_contracts(self) -> List[str]:
308
+ """List all available contract names."""
309
+ return list(self._contracts.keys())
310
+
311
+ def create_from_schema(
312
+ self,
313
+ name: str,
314
+ schema: Dict[str, Any],
315
+ security_policies: Optional[List[SecurityPolicy]] = None,
316
+ ) -> ConnectionContract:
317
+ """Create and register a contract from a JSON schema."""
318
+ contract = ConnectionContract(
319
+ name=name,
320
+ source_schema=schema,
321
+ target_schema=schema,
322
+ security_policies=security_policies or [],
323
+ )
324
+ self.register(contract)
325
+ return contract
326
+
327
+
328
+ class ContractValidator:
329
+ """Validates data against connection contracts."""
330
+
331
+ def __init__(self, registry: Optional[ContractRegistry] = None):
332
+ self.registry = registry or ContractRegistry()
333
+
334
+ def validate_connection(
335
+ self,
336
+ contract: Union[str, ConnectionContract],
337
+ source_data: Any,
338
+ target_data: Any,
339
+ ) -> tuple[bool, List[str]]:
340
+ """
341
+ Validate a connection against a contract.
342
+
343
+ Args:
344
+ contract: Contract name or instance
345
+ source_data: Data from source node
346
+ target_data: Data for target node
347
+
348
+ Returns:
349
+ Tuple of (is_valid, list_of_errors)
350
+ """
351
+ # Resolve contract
352
+ if isinstance(contract, str):
353
+ contract_obj = self.registry.get(contract)
354
+ if not contract_obj:
355
+ return False, [f"Contract '{contract}' not found"]
356
+ contract = contract_obj
357
+
358
+ errors = []
359
+
360
+ # Validate source
361
+ valid, error = contract.validate_source(source_data)
362
+ if not valid:
363
+ errors.append(f"Source validation: {error}")
364
+
365
+ # Validate target
366
+ valid, error = contract.validate_target(target_data)
367
+ if not valid:
368
+ errors.append(f"Target validation: {error}")
369
+
370
+ # Check security policies on both source and target
371
+ for data, label in [(source_data, "source"), (target_data, "target")]:
372
+ compliant, violation = contract.check_security_policies(data)
373
+ if not compliant:
374
+ errors.append(f"Security policy violation ({label}): {violation}")
375
+
376
+ return len(errors) == 0, errors
377
+
378
+ def suggest_contract(self, data: Any) -> Optional[str]:
379
+ """
380
+ Suggest a suitable contract based on data type.
381
+
382
+ Args:
383
+ data: Sample data
384
+
385
+ Returns:
386
+ Suggested contract name or None
387
+ """
388
+ if isinstance(data, str):
389
+ # Check for specific string patterns
390
+ if "@" in data and "." in data:
391
+ return "email_data"
392
+ elif "/" in data or "\\" in data:
393
+ return "file_path"
394
+ elif any(sql in data.lower() for sql in ["select", "insert", "update"]):
395
+ return "sql_query"
396
+ else:
397
+ return "string_data"
398
+
399
+ elif isinstance(data, (int, float)):
400
+ return "numeric_data"
401
+
402
+ elif isinstance(data, dict):
403
+ if "id" in data or "name" in data:
404
+ return "user_data"
405
+
406
+ return None
407
+
408
+
409
+ # Global contract registry instance
410
+ _global_registry = None
411
+
412
+
413
+ def get_contract_registry() -> ContractRegistry:
414
+ """Get the global contract registry."""
415
+ global _global_registry
416
+ if _global_registry is None:
417
+ _global_registry = ContractRegistry()
418
+ return _global_registry