kailash 0.5.0__py3-none-any.whl → 0.6.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.
Files changed (57) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/client/__init__.py +12 -0
  3. kailash/client/enhanced_client.py +306 -0
  4. kailash/core/actors/__init__.py +16 -0
  5. kailash/core/actors/connection_actor.py +566 -0
  6. kailash/core/actors/supervisor.py +364 -0
  7. kailash/edge/__init__.py +16 -0
  8. kailash/edge/compliance.py +834 -0
  9. kailash/edge/discovery.py +659 -0
  10. kailash/edge/location.py +582 -0
  11. kailash/gateway/__init__.py +33 -0
  12. kailash/gateway/api.py +289 -0
  13. kailash/gateway/enhanced_gateway.py +357 -0
  14. kailash/gateway/resource_resolver.py +217 -0
  15. kailash/gateway/security.py +227 -0
  16. kailash/middleware/auth/models.py +2 -2
  17. kailash/middleware/database/base_models.py +1 -7
  18. kailash/middleware/gateway/__init__.py +22 -0
  19. kailash/middleware/gateway/checkpoint_manager.py +398 -0
  20. kailash/middleware/gateway/deduplicator.py +382 -0
  21. kailash/middleware/gateway/durable_gateway.py +417 -0
  22. kailash/middleware/gateway/durable_request.py +498 -0
  23. kailash/middleware/gateway/event_store.py +459 -0
  24. kailash/nodes/admin/permission_check.py +817 -33
  25. kailash/nodes/admin/role_management.py +1242 -108
  26. kailash/nodes/admin/schema_manager.py +438 -0
  27. kailash/nodes/admin/user_management.py +1124 -1582
  28. kailash/nodes/code/__init__.py +8 -1
  29. kailash/nodes/code/async_python.py +1035 -0
  30. kailash/nodes/code/python.py +1 -0
  31. kailash/nodes/data/async_sql.py +9 -3
  32. kailash/nodes/data/sql.py +20 -11
  33. kailash/nodes/data/workflow_connection_pool.py +643 -0
  34. kailash/nodes/rag/__init__.py +1 -4
  35. kailash/resources/__init__.py +40 -0
  36. kailash/resources/factory.py +533 -0
  37. kailash/resources/health.py +319 -0
  38. kailash/resources/reference.py +288 -0
  39. kailash/resources/registry.py +392 -0
  40. kailash/runtime/async_local.py +711 -302
  41. kailash/testing/__init__.py +34 -0
  42. kailash/testing/async_test_case.py +353 -0
  43. kailash/testing/async_utils.py +345 -0
  44. kailash/testing/fixtures.py +458 -0
  45. kailash/testing/mock_registry.py +495 -0
  46. kailash/workflow/__init__.py +8 -0
  47. kailash/workflow/async_builder.py +621 -0
  48. kailash/workflow/async_patterns.py +766 -0
  49. kailash/workflow/cyclic_runner.py +107 -16
  50. kailash/workflow/graph.py +7 -2
  51. kailash/workflow/resilience.py +11 -1
  52. {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/METADATA +7 -4
  53. {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/RECORD +57 -22
  54. {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/WHEEL +0 -0
  55. {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/entry_points.txt +0 -0
  56. {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/licenses/LICENSE +0 -0
  57. {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,834 @@
1
+ """Compliance-aware routing for data sovereignty and regulatory requirements."""
2
+
3
+ import asyncio
4
+ import json
5
+ import logging
6
+ from dataclasses import dataclass
7
+ from datetime import UTC, datetime, timedelta
8
+ from enum import Enum
9
+ from typing import Any, Dict, List, Optional, Set, Tuple, Union
10
+
11
+ from .location import ComplianceZone, EdgeLocation, EdgeRegion, GeographicCoordinates
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class DataClassification(Enum):
17
+ """Data classification levels for compliance routing."""
18
+
19
+ PUBLIC = "public" # No restrictions
20
+ INTERNAL = "internal" # Organization internal
21
+ CONFIDENTIAL = "confidential" # Restricted access
22
+ RESTRICTED = "restricted" # Highly restricted
23
+
24
+ # Personal data types
25
+ PII = "pii" # Personally Identifiable Information
26
+ PHI = "phi" # Protected Health Information
27
+ PCI = "pci" # Payment Card Information
28
+
29
+ # Industry-specific
30
+ FINANCIAL = "financial" # Financial data
31
+ HEALTHCARE = "healthcare" # Healthcare data
32
+ EDUCATIONAL = "educational" # Educational records
33
+
34
+ # Regional data types
35
+ EU_PERSONAL = "eu_personal" # GDPR-protected data
36
+ CALIFORNIA_RESIDENT = "california_resident" # CCPA-protected data
37
+ CANADIAN_PERSONAL = "canadian_personal" # PIPEDA-protected data
38
+
39
+
40
+ class ComplianceRule(Enum):
41
+ """Compliance rules and requirements."""
42
+
43
+ # Geographic restrictions
44
+ DATA_RESIDENCY = "data_residency" # Data must stay in specific region
45
+ CROSS_BORDER_TRANSFER = "cross_border_transfer" # Restrictions on transfers
46
+
47
+ # Encryption requirements
48
+ ENCRYPTION_AT_REST = "encryption_at_rest"
49
+ ENCRYPTION_IN_TRANSIT = "encryption_in_transit"
50
+ KEY_MANAGEMENT = "key_management"
51
+
52
+ # Access controls
53
+ RBAC_REQUIRED = "rbac_required" # Role-based access control
54
+ MFA_REQUIRED = "mfa_required" # Multi-factor authentication
55
+ AUDIT_LOGGING = "audit_logging"
56
+
57
+ # Data lifecycle
58
+ RETENTION_PERIOD = "retention_period"
59
+ RIGHT_TO_DELETE = "right_to_delete" # GDPR Article 17
60
+ DATA_PORTABILITY = "data_portability" # GDPR Article 20
61
+
62
+ # Industry-specific
63
+ HIPAA_SAFEGUARDS = "hipaa_safeguards"
64
+ SOX_CONTROLS = "sox_controls"
65
+ PCI_DSS_REQUIREMENTS = "pci_dss_requirements"
66
+
67
+
68
+ @dataclass
69
+ class ComplianceRequirement:
70
+ """Specific compliance requirement with enforcement details."""
71
+
72
+ rule: ComplianceRule
73
+ description: str
74
+ enforcement_level: str # "required", "recommended", "optional"
75
+ applicable_data_types: List[DataClassification]
76
+ applicable_regions: List[EdgeRegion] = None
77
+ exceptions: List[str] = None
78
+
79
+ def __post_init__(self):
80
+ if self.applicable_regions is None:
81
+ self.applicable_regions = []
82
+ if self.exceptions is None:
83
+ self.exceptions = []
84
+
85
+
86
+ @dataclass
87
+ class ComplianceContext:
88
+ """Context for compliance routing decisions."""
89
+
90
+ # Data characteristics
91
+ data_classification: DataClassification
92
+ data_size_gb: float = 0.0
93
+ contains_personal_data: bool = False
94
+ subject_countries: List[str] = None # ISO country codes
95
+
96
+ # User context
97
+ user_location: Optional[GeographicCoordinates] = None
98
+ user_citizenship: Optional[str] = None # ISO country code
99
+ user_residence: Optional[str] = None # ISO country code
100
+
101
+ # Operation context
102
+ operation_type: str = "read" # "read", "write", "process", "store"
103
+ retention_period_days: Optional[int] = None
104
+ sharing_scope: str = "internal" # "internal", "third_party", "public"
105
+
106
+ # Compliance overrides
107
+ explicit_compliance_zones: List[ComplianceZone] = None
108
+ override_data_residency: bool = False
109
+
110
+ def __post_init__(self):
111
+ if self.subject_countries is None:
112
+ self.subject_countries = []
113
+ if self.explicit_compliance_zones is None:
114
+ self.explicit_compliance_zones = []
115
+
116
+
117
+ @dataclass
118
+ class ComplianceDecision:
119
+ """Result of compliance routing decision."""
120
+
121
+ allowed_locations: List[EdgeLocation]
122
+ prohibited_locations: List[EdgeLocation]
123
+ recommended_location: Optional[EdgeLocation]
124
+
125
+ # Decision reasoning
126
+ compliance_requirements: List[ComplianceRequirement]
127
+ applied_rules: List[str]
128
+ warnings: List[str]
129
+ violations: List[str]
130
+
131
+ # Metadata
132
+ decision_timestamp: datetime
133
+ decision_confidence: float # 0.0 to 1.0
134
+
135
+ def __post_init__(self):
136
+ if self.decision_timestamp is None:
137
+ self.decision_timestamp = datetime.now(UTC)
138
+
139
+
140
+ class ComplianceRouter:
141
+ """Compliance-aware router for data sovereignty and regulatory requirements.
142
+
143
+ Routes data processing requests to appropriate edge locations based on
144
+ regulatory compliance requirements and data classification.
145
+ """
146
+
147
+ def __init__(self):
148
+ """Initialize compliance router with standard rules."""
149
+ self.compliance_rules = self._load_default_compliance_rules()
150
+ self.country_to_region_mapping = self._load_country_region_mapping()
151
+ self.audit_log: List[Dict] = []
152
+
153
+ logger.info("Initialized ComplianceRouter with standard compliance rules")
154
+
155
+ def _load_default_compliance_rules(
156
+ self,
157
+ ) -> Dict[ComplianceZone, List[ComplianceRequirement]]:
158
+ """Load default compliance rules for each zone."""
159
+ rules = {
160
+ ComplianceZone.GDPR: [
161
+ ComplianceRequirement(
162
+ rule=ComplianceRule.DATA_RESIDENCY,
163
+ description="Personal data of EU residents must be processed within EU/EEA",
164
+ enforcement_level="required",
165
+ applicable_data_types=[
166
+ DataClassification.PII,
167
+ DataClassification.EU_PERSONAL,
168
+ ],
169
+ applicable_regions=[
170
+ EdgeRegion.EU_WEST,
171
+ EdgeRegion.EU_CENTRAL,
172
+ EdgeRegion.EU_NORTH,
173
+ ],
174
+ ),
175
+ ComplianceRequirement(
176
+ rule=ComplianceRule.RIGHT_TO_DELETE,
177
+ description="Data subjects have right to erasure (Article 17)",
178
+ enforcement_level="required",
179
+ applicable_data_types=[
180
+ DataClassification.PII,
181
+ DataClassification.EU_PERSONAL,
182
+ ],
183
+ ),
184
+ ComplianceRequirement(
185
+ rule=ComplianceRule.ENCRYPTION_AT_REST,
186
+ description="Personal data must be encrypted at rest",
187
+ enforcement_level="required",
188
+ applicable_data_types=[
189
+ DataClassification.PII,
190
+ DataClassification.EU_PERSONAL,
191
+ ],
192
+ ),
193
+ ComplianceRequirement(
194
+ rule=ComplianceRule.AUDIT_LOGGING,
195
+ description="All data processing must be logged for accountability",
196
+ enforcement_level="required",
197
+ applicable_data_types=[
198
+ DataClassification.PII,
199
+ DataClassification.EU_PERSONAL,
200
+ ],
201
+ ),
202
+ ],
203
+ ComplianceZone.CCPA: [
204
+ ComplianceRequirement(
205
+ rule=ComplianceRule.DATA_RESIDENCY,
206
+ description="California residents' data should be processed in compliant facilities",
207
+ enforcement_level="recommended",
208
+ applicable_data_types=[DataClassification.CALIFORNIA_RESIDENT],
209
+ applicable_regions=[EdgeRegion.US_WEST],
210
+ ),
211
+ ComplianceRequirement(
212
+ rule=ComplianceRule.RIGHT_TO_DELETE,
213
+ description="Consumers have right to delete personal information",
214
+ enforcement_level="required",
215
+ applicable_data_types=[DataClassification.CALIFORNIA_RESIDENT],
216
+ ),
217
+ ComplianceRequirement(
218
+ rule=ComplianceRule.DATA_PORTABILITY,
219
+ description="Consumers have right to data portability",
220
+ enforcement_level="required",
221
+ applicable_data_types=[DataClassification.CALIFORNIA_RESIDENT],
222
+ ),
223
+ ],
224
+ ComplianceZone.HIPAA: [
225
+ ComplianceRequirement(
226
+ rule=ComplianceRule.ENCRYPTION_AT_REST,
227
+ description="PHI must be encrypted at rest",
228
+ enforcement_level="required",
229
+ applicable_data_types=[
230
+ DataClassification.PHI,
231
+ DataClassification.HEALTHCARE,
232
+ ],
233
+ ),
234
+ ComplianceRequirement(
235
+ rule=ComplianceRule.ENCRYPTION_IN_TRANSIT,
236
+ description="PHI must be encrypted in transit",
237
+ enforcement_level="required",
238
+ applicable_data_types=[
239
+ DataClassification.PHI,
240
+ DataClassification.HEALTHCARE,
241
+ ],
242
+ ),
243
+ ComplianceRequirement(
244
+ rule=ComplianceRule.AUDIT_LOGGING,
245
+ description="All PHI access must be logged",
246
+ enforcement_level="required",
247
+ applicable_data_types=[
248
+ DataClassification.PHI,
249
+ DataClassification.HEALTHCARE,
250
+ ],
251
+ ),
252
+ ComplianceRequirement(
253
+ rule=ComplianceRule.MFA_REQUIRED,
254
+ description="Multi-factor authentication required for PHI access",
255
+ enforcement_level="required",
256
+ applicable_data_types=[
257
+ DataClassification.PHI,
258
+ DataClassification.HEALTHCARE,
259
+ ],
260
+ ),
261
+ ],
262
+ ComplianceZone.PCI_DSS: [
263
+ ComplianceRequirement(
264
+ rule=ComplianceRule.ENCRYPTION_AT_REST,
265
+ description="Cardholder data must be encrypted at rest",
266
+ enforcement_level="required",
267
+ applicable_data_types=[DataClassification.PCI],
268
+ ),
269
+ ComplianceRequirement(
270
+ rule=ComplianceRule.ENCRYPTION_IN_TRANSIT,
271
+ description="Cardholder data must be encrypted in transit",
272
+ enforcement_level="required",
273
+ applicable_data_types=[DataClassification.PCI],
274
+ ),
275
+ ComplianceRequirement(
276
+ rule=ComplianceRule.RBAC_REQUIRED,
277
+ description="Role-based access control required",
278
+ enforcement_level="required",
279
+ applicable_data_types=[DataClassification.PCI],
280
+ ),
281
+ ],
282
+ ComplianceZone.SOX: [
283
+ ComplianceRequirement(
284
+ rule=ComplianceRule.AUDIT_LOGGING,
285
+ description="All financial data access must be logged",
286
+ enforcement_level="required",
287
+ applicable_data_types=[DataClassification.FINANCIAL],
288
+ ),
289
+ ComplianceRequirement(
290
+ rule=ComplianceRule.RETENTION_PERIOD,
291
+ description="Financial records must be retained for 7 years",
292
+ enforcement_level="required",
293
+ applicable_data_types=[DataClassification.FINANCIAL],
294
+ ),
295
+ ],
296
+ ComplianceZone.PUBLIC: [
297
+ ComplianceRequirement(
298
+ rule=ComplianceRule.ENCRYPTION_IN_TRANSIT,
299
+ description="Data should be encrypted in transit",
300
+ enforcement_level="recommended",
301
+ applicable_data_types=[
302
+ DataClassification.PUBLIC,
303
+ DataClassification.INTERNAL,
304
+ ],
305
+ )
306
+ ],
307
+ }
308
+
309
+ return rules
310
+
311
+ def _load_country_region_mapping(self) -> Dict[str, EdgeRegion]:
312
+ """Load mapping of ISO country codes to edge regions."""
313
+ return {
314
+ # North America
315
+ "US": EdgeRegion.US_EAST, # Default US region
316
+ "CA": EdgeRegion.CANADA,
317
+ # Europe
318
+ "DE": EdgeRegion.EU_CENTRAL,
319
+ "FR": EdgeRegion.EU_WEST,
320
+ "GB": EdgeRegion.UK,
321
+ "IE": EdgeRegion.EU_WEST,
322
+ "NL": EdgeRegion.EU_WEST,
323
+ "ES": EdgeRegion.EU_WEST,
324
+ "IT": EdgeRegion.EU_WEST,
325
+ "SE": EdgeRegion.EU_NORTH,
326
+ "NO": EdgeRegion.EU_NORTH,
327
+ "FI": EdgeRegion.EU_NORTH,
328
+ "DK": EdgeRegion.EU_NORTH,
329
+ # Asia Pacific
330
+ "JP": EdgeRegion.JAPAN,
331
+ "SG": EdgeRegion.ASIA_SOUTHEAST,
332
+ "AU": EdgeRegion.AUSTRALIA,
333
+ "KR": EdgeRegion.ASIA_EAST,
334
+ "HK": EdgeRegion.ASIA_EAST,
335
+ "IN": EdgeRegion.ASIA_SOUTH,
336
+ "TH": EdgeRegion.ASIA_SOUTHEAST,
337
+ "MY": EdgeRegion.ASIA_SOUTHEAST,
338
+ "ID": EdgeRegion.ASIA_SOUTHEAST,
339
+ # Other regions
340
+ "BR": EdgeRegion.SOUTH_AMERICA,
341
+ "MX": EdgeRegion.SOUTH_AMERICA,
342
+ "ZA": EdgeRegion.AFRICA,
343
+ "AE": EdgeRegion.MIDDLE_EAST,
344
+ "SA": EdgeRegion.MIDDLE_EAST,
345
+ }
346
+
347
+ async def route_compliant(
348
+ self, context: ComplianceContext, available_locations: List[EdgeLocation]
349
+ ) -> ComplianceDecision:
350
+ """Route data processing to compliant edge locations.
351
+
352
+ Args:
353
+ context: Compliance context with data and user information
354
+ available_locations: List of available edge locations
355
+
356
+ Returns:
357
+ Compliance decision with allowed/prohibited locations
358
+ """
359
+ logger.info(
360
+ f"Routing compliance decision for {context.data_classification.value} data"
361
+ )
362
+
363
+ # Determine applicable compliance zones
364
+ applicable_zones = self._determine_applicable_zones(context)
365
+
366
+ # Get compliance requirements
367
+ requirements = self._get_compliance_requirements(applicable_zones, context)
368
+
369
+ # Evaluate each location
370
+ allowed_locations = []
371
+ prohibited_locations = []
372
+ violations = []
373
+ warnings = []
374
+ applied_rules = []
375
+
376
+ for location in available_locations:
377
+ evaluation = await self._evaluate_location_compliance(
378
+ location, context, requirements
379
+ )
380
+
381
+ if evaluation["compliant"]:
382
+ allowed_locations.append(location)
383
+ else:
384
+ prohibited_locations.append(location)
385
+ violations.extend(evaluation["violations"])
386
+
387
+ warnings.extend(evaluation["warnings"])
388
+ applied_rules.extend(evaluation["applied_rules"])
389
+
390
+ # Select recommended location
391
+ recommended_location = self._select_recommended_location(
392
+ allowed_locations, context, requirements
393
+ )
394
+
395
+ # Calculate decision confidence
396
+ confidence = self._calculate_decision_confidence(
397
+ len(allowed_locations), len(prohibited_locations), len(violations)
398
+ )
399
+
400
+ decision = ComplianceDecision(
401
+ allowed_locations=allowed_locations,
402
+ prohibited_locations=prohibited_locations,
403
+ recommended_location=recommended_location,
404
+ compliance_requirements=requirements,
405
+ applied_rules=list(set(applied_rules)),
406
+ warnings=list(set(warnings)),
407
+ violations=list(set(violations)),
408
+ decision_timestamp=datetime.now(UTC),
409
+ decision_confidence=confidence,
410
+ )
411
+
412
+ # Log decision for audit trail
413
+ await self._log_compliance_decision(context, decision)
414
+
415
+ logger.info(
416
+ f"Compliance routing: {len(allowed_locations)} allowed, "
417
+ f"{len(prohibited_locations)} prohibited locations"
418
+ )
419
+
420
+ return decision
421
+
422
+ def _determine_applicable_zones(
423
+ self, context: ComplianceContext
424
+ ) -> List[ComplianceZone]:
425
+ """Determine which compliance zones apply to the data and context."""
426
+ zones = []
427
+
428
+ # Explicit zones override everything
429
+ if context.explicit_compliance_zones:
430
+ return context.explicit_compliance_zones
431
+
432
+ # Data classification-based zones
433
+ if context.data_classification in [
434
+ DataClassification.PII,
435
+ DataClassification.EU_PERSONAL,
436
+ ]:
437
+ # Check if EU resident data
438
+ if any(
439
+ country
440
+ in [
441
+ "DE",
442
+ "FR",
443
+ "IT",
444
+ "ES",
445
+ "NL",
446
+ "BE",
447
+ "AT",
448
+ "SE",
449
+ "DK",
450
+ "FI",
451
+ "IE",
452
+ "PT",
453
+ "GR",
454
+ "LU",
455
+ "MT",
456
+ "CY",
457
+ "EE",
458
+ "LV",
459
+ "LT",
460
+ "SI",
461
+ "SK",
462
+ "HR",
463
+ "BG",
464
+ "RO",
465
+ "HU",
466
+ "CZ",
467
+ "PL",
468
+ ]
469
+ for country in context.subject_countries
470
+ ):
471
+ zones.append(ComplianceZone.GDPR)
472
+
473
+ if context.data_classification == DataClassification.CALIFORNIA_RESIDENT:
474
+ zones.append(ComplianceZone.CCPA)
475
+
476
+ if context.data_classification in [
477
+ DataClassification.PHI,
478
+ DataClassification.HEALTHCARE,
479
+ ]:
480
+ zones.append(ComplianceZone.HIPAA)
481
+
482
+ if context.data_classification == DataClassification.PCI:
483
+ zones.append(ComplianceZone.PCI_DSS)
484
+
485
+ if context.data_classification == DataClassification.FINANCIAL:
486
+ zones.append(ComplianceZone.SOX)
487
+
488
+ # Geographic-based zones
489
+ if context.user_location:
490
+ # Add region-specific compliance based on user location
491
+ pass # Could add geo-based logic here
492
+
493
+ # Default to public if no specific zones identified
494
+ if not zones:
495
+ zones.append(ComplianceZone.PUBLIC)
496
+
497
+ return zones
498
+
499
+ def _get_compliance_requirements(
500
+ self, zones: List[ComplianceZone], context: ComplianceContext
501
+ ) -> List[ComplianceRequirement]:
502
+ """Get all compliance requirements for the given zones and context."""
503
+ requirements = []
504
+
505
+ for zone in zones:
506
+ zone_requirements = self.compliance_rules.get(zone, [])
507
+ for requirement in zone_requirements:
508
+ # Check if requirement applies to this data type
509
+ if context.data_classification in requirement.applicable_data_types:
510
+ requirements.append(requirement)
511
+
512
+ return requirements
513
+
514
+ async def _evaluate_location_compliance(
515
+ self,
516
+ location: EdgeLocation,
517
+ context: ComplianceContext,
518
+ requirements: List[ComplianceRequirement],
519
+ ) -> Dict[str, Any]:
520
+ """Evaluate if a location meets compliance requirements."""
521
+ compliant = True
522
+ violations = []
523
+ warnings = []
524
+ applied_rules = []
525
+
526
+ for requirement in requirements:
527
+ rule_result = await self._evaluate_compliance_rule(
528
+ location, context, requirement
529
+ )
530
+
531
+ applied_rules.append(requirement.rule.value)
532
+
533
+ if not rule_result["compliant"]:
534
+ if requirement.enforcement_level == "required":
535
+ compliant = False
536
+ violations.append(rule_result["message"])
537
+ else:
538
+ warnings.append(rule_result["message"])
539
+
540
+ return {
541
+ "compliant": compliant,
542
+ "violations": violations,
543
+ "warnings": warnings,
544
+ "applied_rules": applied_rules,
545
+ }
546
+
547
+ async def _evaluate_compliance_rule(
548
+ self,
549
+ location: EdgeLocation,
550
+ context: ComplianceContext,
551
+ requirement: ComplianceRequirement,
552
+ ) -> Dict[str, Any]:
553
+ """Evaluate a specific compliance rule for a location."""
554
+ rule = requirement.rule
555
+
556
+ if rule == ComplianceRule.DATA_RESIDENCY:
557
+ return await self._check_data_residency(location, context, requirement)
558
+ elif rule == ComplianceRule.ENCRYPTION_AT_REST:
559
+ return self._check_encryption_at_rest(location, requirement)
560
+ elif rule == ComplianceRule.ENCRYPTION_IN_TRANSIT:
561
+ return self._check_encryption_in_transit(location, requirement)
562
+ elif rule == ComplianceRule.AUDIT_LOGGING:
563
+ return self._check_audit_logging(location, requirement)
564
+ elif rule == ComplianceRule.MFA_REQUIRED:
565
+ return self._check_mfa_support(location, requirement)
566
+ elif rule == ComplianceRule.RBAC_REQUIRED:
567
+ return self._check_rbac_support(location, requirement)
568
+ else:
569
+ # Default pass for unimplemented rules
570
+ return {"compliant": True, "message": f"Rule {rule.value} not evaluated"}
571
+
572
+ async def _check_data_residency(
573
+ self,
574
+ location: EdgeLocation,
575
+ context: ComplianceContext,
576
+ requirement: ComplianceRequirement,
577
+ ) -> Dict[str, Any]:
578
+ """Check data residency compliance."""
579
+ if context.override_data_residency:
580
+ return {
581
+ "compliant": True,
582
+ "message": "Data residency requirement overridden",
583
+ }
584
+
585
+ # Check if location region is in allowed regions
586
+ if requirement.applicable_regions:
587
+ if location.region not in requirement.applicable_regions:
588
+ return {
589
+ "compliant": False,
590
+ "message": f"Data residency violation: {location.region.value} not in allowed regions {[r.value for r in requirement.applicable_regions]}",
591
+ }
592
+
593
+ return {"compliant": True, "message": "Data residency requirement met"}
594
+
595
+ def _check_encryption_at_rest(
596
+ self, location: EdgeLocation, requirement: ComplianceRequirement
597
+ ) -> Dict[str, Any]:
598
+ """Check encryption at rest support."""
599
+ if location.capabilities.encryption_at_rest:
600
+ return {"compliant": True, "message": "Encryption at rest supported"}
601
+ else:
602
+ return {"compliant": False, "message": "Encryption at rest not supported"}
603
+
604
+ def _check_encryption_in_transit(
605
+ self, location: EdgeLocation, requirement: ComplianceRequirement
606
+ ) -> Dict[str, Any]:
607
+ """Check encryption in transit support."""
608
+ if location.capabilities.encryption_in_transit:
609
+ return {"compliant": True, "message": "Encryption in transit supported"}
610
+ else:
611
+ return {
612
+ "compliant": False,
613
+ "message": "Encryption in transit not supported",
614
+ }
615
+
616
+ def _check_audit_logging(
617
+ self, location: EdgeLocation, requirement: ComplianceRequirement
618
+ ) -> Dict[str, Any]:
619
+ """Check audit logging support."""
620
+ if location.capabilities.audit_logging:
621
+ return {"compliant": True, "message": "Audit logging supported"}
622
+ else:
623
+ return {"compliant": False, "message": "Audit logging not supported"}
624
+
625
+ def _check_mfa_support(
626
+ self, location: EdgeLocation, requirement: ComplianceRequirement
627
+ ) -> Dict[str, Any]:
628
+ """Check multi-factor authentication support."""
629
+ # For now, assume all locations support MFA
630
+ return {"compliant": True, "message": "MFA support available"}
631
+
632
+ def _check_rbac_support(
633
+ self, location: EdgeLocation, requirement: ComplianceRequirement
634
+ ) -> Dict[str, Any]:
635
+ """Check role-based access control support."""
636
+ # For now, assume all locations support RBAC
637
+ return {"compliant": True, "message": "RBAC support available"}
638
+
639
+ def _select_recommended_location(
640
+ self,
641
+ allowed_locations: List[EdgeLocation],
642
+ context: ComplianceContext,
643
+ requirements: List[ComplianceRequirement],
644
+ ) -> Optional[EdgeLocation]:
645
+ """Select the best recommended location from allowed locations."""
646
+ if not allowed_locations:
647
+ return None
648
+
649
+ if len(allowed_locations) == 1:
650
+ return allowed_locations[0]
651
+
652
+ # Simple scoring: prefer locations with better compliance coverage
653
+ def score_location(location):
654
+ score = 0
655
+
656
+ # Prefer locations with more compliance zones
657
+ score += len(location.compliance_zones) * 10
658
+
659
+ # Prefer locations closer to user (if known)
660
+ if context.user_location:
661
+ distance = location.coordinates.distance_to(context.user_location)
662
+ score += max(0, 1000 - distance) # Closer = higher score
663
+
664
+ # Prefer locations with better health
665
+ if location.is_healthy:
666
+ score += 100
667
+
668
+ # Prefer locations with lower load
669
+ score += (1.0 - location.get_load_factor()) * 50
670
+
671
+ return score
672
+
673
+ # Return location with highest score
674
+ return max(allowed_locations, key=score_location)
675
+
676
+ def _calculate_decision_confidence(
677
+ self, allowed_count: int, prohibited_count: int, violation_count: int
678
+ ) -> float:
679
+ """Calculate confidence in the compliance decision."""
680
+ total_locations = allowed_count + prohibited_count
681
+
682
+ if total_locations == 0:
683
+ return 0.0
684
+
685
+ # Base confidence on ratio of allowed to total
686
+ base_confidence = allowed_count / total_locations
687
+
688
+ # Reduce confidence if violations found
689
+ violation_penalty = min(0.3, violation_count * 0.1)
690
+
691
+ # Ensure we have at least one allowed location for high confidence
692
+ if allowed_count == 0:
693
+ confidence = 0.0
694
+ elif allowed_count >= 3:
695
+ confidence = min(1.0, base_confidence - violation_penalty + 0.2)
696
+ else:
697
+ confidence = base_confidence - violation_penalty
698
+
699
+ return max(0.0, min(1.0, confidence))
700
+
701
+ async def _log_compliance_decision(
702
+ self, context: ComplianceContext, decision: ComplianceDecision
703
+ ):
704
+ """Log compliance decision for audit trail."""
705
+ audit_entry = {
706
+ "timestamp": decision.decision_timestamp.isoformat(),
707
+ "data_classification": context.data_classification.value,
708
+ "operation_type": context.operation_type,
709
+ "allowed_locations": [
710
+ loc.location_id for loc in decision.allowed_locations
711
+ ],
712
+ "prohibited_locations": [
713
+ loc.location_id for loc in decision.prohibited_locations
714
+ ],
715
+ "recommended_location": (
716
+ decision.recommended_location.location_id
717
+ if decision.recommended_location
718
+ else None
719
+ ),
720
+ "violations": decision.violations,
721
+ "confidence": decision.decision_confidence,
722
+ "compliance_zones": (
723
+ [zone.value for zone in context.explicit_compliance_zones]
724
+ if context.explicit_compliance_zones
725
+ else []
726
+ ),
727
+ "subject_countries": context.subject_countries,
728
+ }
729
+
730
+ self.audit_log.append(audit_entry)
731
+
732
+ # Keep only last 1000 entries to prevent memory growth
733
+ if len(self.audit_log) > 1000:
734
+ self.audit_log = self.audit_log[-1000:]
735
+
736
+ def classify_data(self, data: Dict[str, Any]) -> DataClassification:
737
+ """Automatically classify data based on content patterns."""
738
+ # Simple pattern-based classification
739
+ data_str = json.dumps(data).lower()
740
+
741
+ # Check for PII patterns
742
+ pii_patterns = ["email", "ssn", "social_security", "phone", "address", "name"]
743
+ if any(pattern in data_str for pattern in pii_patterns):
744
+ return DataClassification.PII
745
+
746
+ # Check for healthcare patterns
747
+ health_patterns = ["medical", "diagnosis", "treatment", "patient", "health"]
748
+ if any(pattern in data_str for pattern in health_patterns):
749
+ return DataClassification.PHI
750
+
751
+ # Check for payment patterns
752
+ payment_patterns = ["credit_card", "card_number", "cvv", "payment", "billing"]
753
+ if any(pattern in data_str for pattern in payment_patterns):
754
+ return DataClassification.PCI
755
+
756
+ # Check for financial patterns (but not payment patterns)
757
+ financial_patterns = ["account_number", "routing", "bank", "financial"]
758
+ if any(pattern in data_str for pattern in financial_patterns) and not any(
759
+ pattern in data_str for pattern in payment_patterns
760
+ ):
761
+ return DataClassification.FINANCIAL
762
+
763
+ # Default to public
764
+ return DataClassification.PUBLIC
765
+
766
+ def get_applicable_regulations(
767
+ self, data_classification: DataClassification
768
+ ) -> List[ComplianceZone]:
769
+ """Get applicable regulations for a data classification."""
770
+ regulations = []
771
+
772
+ classification_mapping = {
773
+ DataClassification.PII: [ComplianceZone.GDPR, ComplianceZone.CCPA],
774
+ DataClassification.EU_PERSONAL: [ComplianceZone.GDPR],
775
+ DataClassification.CALIFORNIA_RESIDENT: [ComplianceZone.CCPA],
776
+ DataClassification.PHI: [ComplianceZone.HIPAA],
777
+ DataClassification.PCI: [ComplianceZone.PCI_DSS],
778
+ DataClassification.FINANCIAL: [ComplianceZone.SOX],
779
+ DataClassification.PUBLIC: [ComplianceZone.PUBLIC],
780
+ }
781
+
782
+ return classification_mapping.get(data_classification, [ComplianceZone.PUBLIC])
783
+
784
+ def get_audit_log(self, limit: int = 100) -> List[Dict[str, Any]]:
785
+ """Get recent compliance decisions from audit log."""
786
+ return self.audit_log[-limit:]
787
+
788
+ def get_compliance_summary(self) -> Dict[str, Any]:
789
+ """Get summary of compliance decisions and performance."""
790
+ if not self.audit_log:
791
+ return {
792
+ "total_decisions": 0,
793
+ "compliance_rate": 1.0,
794
+ "common_violations": [],
795
+ "data_classifications": {},
796
+ }
797
+
798
+ total_decisions = len(self.audit_log)
799
+ decisions_with_violations = sum(
800
+ 1 for entry in self.audit_log if entry["violations"]
801
+ )
802
+ compliance_rate = 1.0 - (decisions_with_violations / total_decisions)
803
+
804
+ # Analyze common violations
805
+ all_violations = []
806
+ for entry in self.audit_log:
807
+ all_violations.extend(entry["violations"])
808
+
809
+ violation_counts = {}
810
+ for violation in all_violations:
811
+ violation_counts[violation] = violation_counts.get(violation, 0) + 1
812
+
813
+ common_violations = sorted(
814
+ violation_counts.items(), key=lambda x: x[1], reverse=True
815
+ )[:5]
816
+
817
+ # Analyze data classifications
818
+ classification_counts = {}
819
+ for entry in self.audit_log:
820
+ classification = entry["data_classification"]
821
+ classification_counts[classification] = (
822
+ classification_counts.get(classification, 0) + 1
823
+ )
824
+
825
+ return {
826
+ "total_decisions": total_decisions,
827
+ "compliance_rate": compliance_rate,
828
+ "common_violations": [
829
+ {"violation": v[0], "count": v[1]} for v in common_violations
830
+ ],
831
+ "data_classifications": classification_counts,
832
+ "average_confidence": sum(entry["confidence"] for entry in self.audit_log)
833
+ / total_decisions,
834
+ }