kailash 0.8.3__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 (84) 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/__init__.py +17 -0
  37. kailash/nodes/ai/a2a.py +1914 -43
  38. kailash/nodes/ai/a2a_backup.py +1807 -0
  39. kailash/nodes/ai/hybrid_search.py +972 -0
  40. kailash/nodes/ai/semantic_memory.py +558 -0
  41. kailash/nodes/ai/streaming_analytics.py +947 -0
  42. kailash/nodes/base.py +545 -0
  43. kailash/nodes/edge/__init__.py +36 -0
  44. kailash/nodes/edge/base.py +240 -0
  45. kailash/nodes/edge/cloud_node.py +710 -0
  46. kailash/nodes/edge/coordination.py +239 -0
  47. kailash/nodes/edge/docker_node.py +825 -0
  48. kailash/nodes/edge/edge_data.py +582 -0
  49. kailash/nodes/edge/edge_migration_node.py +392 -0
  50. kailash/nodes/edge/edge_monitoring_node.py +421 -0
  51. kailash/nodes/edge/edge_state.py +673 -0
  52. kailash/nodes/edge/edge_warming_node.py +393 -0
  53. kailash/nodes/edge/kubernetes_node.py +652 -0
  54. kailash/nodes/edge/platform_node.py +766 -0
  55. kailash/nodes/edge/resource_analyzer_node.py +378 -0
  56. kailash/nodes/edge/resource_optimizer_node.py +501 -0
  57. kailash/nodes/edge/resource_scaler_node.py +397 -0
  58. kailash/nodes/ports.py +676 -0
  59. kailash/runtime/local.py +344 -1
  60. kailash/runtime/validation/__init__.py +20 -0
  61. kailash/runtime/validation/connection_context.py +119 -0
  62. kailash/runtime/validation/enhanced_error_formatter.py +202 -0
  63. kailash/runtime/validation/error_categorizer.py +164 -0
  64. kailash/runtime/validation/metrics.py +380 -0
  65. kailash/runtime/validation/performance.py +615 -0
  66. kailash/runtime/validation/suggestion_engine.py +212 -0
  67. kailash/testing/fixtures.py +2 -2
  68. kailash/workflow/builder.py +234 -8
  69. kailash/workflow/contracts.py +418 -0
  70. kailash/workflow/edge_infrastructure.py +369 -0
  71. kailash/workflow/migration.py +3 -3
  72. kailash/workflow/type_inference.py +669 -0
  73. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/METADATA +44 -27
  74. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/RECORD +78 -28
  75. kailash/nexus/__init__.py +0 -21
  76. kailash/nexus/cli/__init__.py +0 -5
  77. kailash/nexus/cli/__main__.py +0 -6
  78. kailash/nexus/cli/main.py +0 -176
  79. kailash/nexus/factory.py +0 -413
  80. kailash/nexus/gateway.py +0 -545
  81. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/WHEEL +0 -0
  82. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/entry_points.txt +0 -0
  83. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/licenses/LICENSE +0 -0
  84. {kailash-0.8.3.dist-info → kailash-0.8.5.dist-info}/top_level.txt +0 -0
kailash/runtime/local.py CHANGED
@@ -37,13 +37,21 @@ Examples:
37
37
  import asyncio
38
38
  import logging
39
39
  from datetime import UTC, datetime
40
- from typing import Any, Optional
40
+ from typing import Any, Dict, Optional
41
41
 
42
42
  import networkx as nx
43
43
 
44
44
  from kailash.nodes import Node
45
45
  from kailash.runtime.parameter_injector import WorkflowParameterInjector
46
46
  from kailash.runtime.secret_provider import EnvironmentSecretProvider, SecretProvider
47
+ from kailash.runtime.validation.connection_context import ConnectionContext
48
+ from kailash.runtime.validation.enhanced_error_formatter import EnhancedErrorFormatter
49
+ from kailash.runtime.validation.error_categorizer import ErrorCategorizer
50
+ from kailash.runtime.validation.metrics import (
51
+ ValidationEventType,
52
+ get_metrics_collector,
53
+ )
54
+ from kailash.runtime.validation.suggestion_engine import ValidationSuggestionEngine
47
55
  from kailash.sdk_exceptions import (
48
56
  RuntimeExecutionError,
49
57
  WorkflowExecutionError,
@@ -53,6 +61,7 @@ from kailash.tracking import TaskManager, TaskStatus
53
61
  from kailash.tracking.metrics_collector import MetricsCollector
54
62
  from kailash.tracking.models import TaskMetrics
55
63
  from kailash.workflow import Workflow
64
+ from kailash.workflow.contracts import ConnectionContract, ContractValidator
56
65
  from kailash.workflow.cyclic_runner import CyclicWorkflowExecutor
57
66
 
58
67
  logger = logging.getLogger(__name__)
@@ -86,6 +95,7 @@ class LocalRuntime:
86
95
  enable_audit: bool = False,
87
96
  resource_limits: Optional[dict[str, Any]] = None,
88
97
  secret_provider: Optional[Any] = None,
98
+ connection_validation: str = "warn",
89
99
  ):
90
100
  """Initialize the unified runtime.
91
101
 
@@ -100,7 +110,19 @@ class LocalRuntime:
100
110
  enable_audit: Whether to enable audit logging.
101
111
  resource_limits: Resource limits (memory_mb, cpu_cores, etc.).
102
112
  secret_provider: Optional secret provider for runtime secret injection.
113
+ connection_validation: Connection parameter validation mode:
114
+ - "off": No validation (backward compatibility)
115
+ - "warn": Log warnings on validation errors (default)
116
+ - "strict": Raise errors on validation failures
103
117
  """
118
+ # Validate connection_validation parameter
119
+ valid_modes = {"off", "warn", "strict"}
120
+ if connection_validation not in valid_modes:
121
+ raise ValueError(
122
+ f"Invalid connection_validation mode: {connection_validation}. "
123
+ f"Must be one of: {valid_modes}"
124
+ )
125
+
104
126
  self.debug = debug
105
127
  self.enable_cycles = enable_cycles
106
128
  self.enable_async = enable_async
@@ -111,6 +133,7 @@ class LocalRuntime:
111
133
  self.enable_security = enable_security
112
134
  self.enable_audit = enable_audit
113
135
  self.resource_limits = resource_limits or {}
136
+ self.connection_validation = connection_validation
114
137
  self.logger = logger
115
138
 
116
139
  # Enterprise feature managers (lazy initialization)
@@ -403,6 +426,16 @@ class LocalRuntime:
403
426
  except Exception as e:
404
427
  self.logger.warning(f"Failed to update run status: {e}")
405
428
 
429
+ # Final cleanup of all node instances
430
+ for node_id, node_instance in workflow._node_instances.items():
431
+ if hasattr(node_instance, "cleanup"):
432
+ try:
433
+ await node_instance.cleanup()
434
+ except Exception as cleanup_error:
435
+ self.logger.warning(
436
+ f"Error during final cleanup of node {node_id}: {cleanup_error}"
437
+ )
438
+
406
439
  return results, run_id
407
440
 
408
441
  except WorkflowValidationError:
@@ -633,6 +666,15 @@ class LocalRuntime:
633
666
  f"Node {node_id} completed successfully in {performance_metrics.duration:.3f}s"
634
667
  )
635
668
 
669
+ # Clean up async resources if the node has a cleanup method
670
+ if hasattr(node_instance, "cleanup"):
671
+ try:
672
+ await node_instance.cleanup()
673
+ except Exception as cleanup_error:
674
+ self.logger.warning(
675
+ f"Error during node {node_id} cleanup: {cleanup_error}"
676
+ )
677
+
636
678
  except Exception as e:
637
679
  failed_nodes.append(node_id)
638
680
  self.logger.error(f"Node {node_id} failed: {e}", exc_info=self.debug)
@@ -646,6 +688,15 @@ class LocalRuntime:
646
688
  ended_at=datetime.now(UTC),
647
689
  )
648
690
 
691
+ # Clean up async resources even on failure
692
+ if hasattr(node_instance, "cleanup"):
693
+ try:
694
+ await node_instance.cleanup()
695
+ except Exception as cleanup_error:
696
+ self.logger.warning(
697
+ f"Error during node {node_id} cleanup after failure: {cleanup_error}"
698
+ )
699
+
649
700
  # Determine if we should continue
650
701
  if self._should_stop_on_error(workflow, node_id):
651
702
  error_msg = f"Node '{node_id}' failed: {e}"
@@ -816,8 +867,232 @@ class LocalRuntime:
816
867
  # Apply parameter overrides
817
868
  inputs.update(parameters)
818
869
 
870
+ # Connection parameter validation (TODO-121) with enhanced error messages and metrics
871
+ if self.connection_validation != "off":
872
+ metrics_collector = get_metrics_collector()
873
+ node_type = type(node_instance).__name__
874
+
875
+ # Start metrics collection
876
+ metrics_collector.start_validation(
877
+ node_id, node_type, self.connection_validation
878
+ )
879
+
880
+ try:
881
+ # Phase 2: Contract validation (if contracts exist in workflow metadata)
882
+ contract_violations = self._validate_connection_contracts(
883
+ workflow, node_id, inputs, node_outputs
884
+ )
885
+
886
+ if contract_violations:
887
+ contract_error_msg = "\n".join(
888
+ [
889
+ f"Contract '{violation['contract']}' violation on connection {violation['connection']}: {violation['error']}"
890
+ for violation in contract_violations
891
+ ]
892
+ )
893
+ raise WorkflowExecutionError(
894
+ f"Connection contract validation failed for node '{node_id}': {contract_error_msg}"
895
+ )
896
+
897
+ # Merge node config with inputs before validation (matches node.execute behavior)
898
+ # This ensures connection validation considers both runtime inputs AND node configuration
899
+ merged_inputs = {**node_instance.config, **inputs}
900
+
901
+ # Handle nested config case (same as in node.execute)
902
+ if "config" in merged_inputs and isinstance(
903
+ merged_inputs["config"], dict
904
+ ):
905
+ nested_config = merged_inputs["config"]
906
+ for key, value in nested_config.items():
907
+ if key not in inputs: # Runtime inputs take precedence
908
+ merged_inputs[key] = value
909
+
910
+ # Use the node's existing validate_inputs method with merged inputs
911
+ validated_inputs = node_instance.validate_inputs(**merged_inputs)
912
+
913
+ # Extract only the runtime inputs from validated results
914
+ # (exclude config parameters that were merged for validation)
915
+ validated_runtime_inputs = {}
916
+ for key, value in validated_inputs.items():
917
+ # Include if it was in original inputs OR not in node config
918
+ # This preserves validated/converted values from runtime inputs
919
+ if key in inputs or key not in node_instance.config:
920
+ validated_runtime_inputs[key] = value
921
+
922
+ # Record successful validation
923
+ metrics_collector.end_validation(node_id, node_type, success=True)
924
+
925
+ # Replace inputs with validated runtime inputs only
926
+ inputs = validated_runtime_inputs
927
+
928
+ except Exception as e:
929
+ # Categorize the error for metrics
930
+ categorizer = ErrorCategorizer()
931
+ error_category = categorizer.categorize_error(e, node_type)
932
+
933
+ # Build connection info for metrics
934
+ connection_info = {"source": "unknown", "target": node_id}
935
+ for connection in workflow.connections:
936
+ if connection.target_node == node_id:
937
+ connection_info["source"] = connection.source_node
938
+ break
939
+
940
+ # Record failed validation
941
+ metrics_collector.end_validation(
942
+ node_id,
943
+ node_type,
944
+ success=False,
945
+ error_category=error_category,
946
+ connection_info=connection_info,
947
+ )
948
+
949
+ # Check for security violations
950
+ if error_category.value == "security_violation":
951
+ metrics_collector.record_security_violation(
952
+ node_id,
953
+ node_type,
954
+ {"message": str(e), "category": "connection_validation"},
955
+ connection_info,
956
+ )
957
+
958
+ # Generate enhanced error message with connection tracing
959
+ error_msg = self._generate_enhanced_validation_error(
960
+ node_id, node_instance, e, workflow, parameters
961
+ )
962
+
963
+ if self.connection_validation == "strict":
964
+ # Strict mode: raise the error with enhanced message
965
+ raise WorkflowExecutionError(error_msg) from e
966
+ elif self.connection_validation == "warn":
967
+ # Warn mode: log enhanced warning and continue with unvalidated inputs
968
+ self.logger.warning(error_msg)
969
+ # Continue with original inputs
970
+ else:
971
+ # Record mode bypass for metrics
972
+ metrics_collector = get_metrics_collector()
973
+ metrics_collector.record_mode_bypass(
974
+ node_id, type(node_instance).__name__, self.connection_validation
975
+ )
976
+
819
977
  return inputs
820
978
 
979
+ def _generate_enhanced_validation_error(
980
+ self,
981
+ node_id: str,
982
+ node_instance: Node,
983
+ original_error: Exception,
984
+ workflow: "Workflow", # Type annotation as string to avoid circular import
985
+ parameters: dict,
986
+ ) -> str:
987
+ """Generate enhanced validation error message with connection tracing and suggestions.
988
+
989
+ Args:
990
+ node_id: ID of the target node that failed validation
991
+ node_instance: The node instance that failed
992
+ original_error: Original validation exception
993
+ workflow: The workflow being executed
994
+ parameters: Runtime parameters
995
+
996
+ Returns:
997
+ Enhanced error message with connection context and actionable suggestions
998
+ """
999
+ # Initialize error enhancement components
1000
+ categorizer = ErrorCategorizer()
1001
+ suggestion_engine = ValidationSuggestionEngine()
1002
+ formatter = EnhancedErrorFormatter()
1003
+
1004
+ # Categorize the error
1005
+ node_type = type(node_instance).__name__
1006
+ error_category = categorizer.categorize_error(original_error, node_type)
1007
+
1008
+ # Build connection context by finding the connections that feed into this node
1009
+ connection_context = self._build_connection_context(
1010
+ node_id, workflow, parameters
1011
+ )
1012
+
1013
+ # Generate suggestion for fixing the error
1014
+ suggestion = suggestion_engine.generate_suggestion(
1015
+ error_category, node_type, connection_context, str(original_error)
1016
+ )
1017
+
1018
+ # Format the enhanced error message
1019
+ if error_category.value == "security_violation":
1020
+ enhanced_msg = formatter.format_security_error(
1021
+ str(original_error), connection_context, suggestion
1022
+ )
1023
+ else:
1024
+ enhanced_msg = formatter.format_enhanced_error(
1025
+ str(original_error), error_category, connection_context, suggestion
1026
+ )
1027
+
1028
+ return enhanced_msg
1029
+
1030
+ def _build_connection_context(
1031
+ self, target_node_id: str, workflow: "Workflow", parameters: dict
1032
+ ) -> ConnectionContext:
1033
+ """Build connection context for error message enhancement.
1034
+
1035
+ Args:
1036
+ target_node_id: ID of the target node
1037
+ workflow: The workflow being executed
1038
+ parameters: Runtime parameters
1039
+
1040
+ Returns:
1041
+ ConnectionContext with source/target information
1042
+ """
1043
+ # Find the primary connection feeding into this node
1044
+ source_node = "unknown"
1045
+ source_port = None
1046
+ target_port = "input"
1047
+ parameter_value = None
1048
+
1049
+ # Look through workflow connections to find what feeds this node
1050
+ for connection in workflow.connections:
1051
+ if connection.target_node == target_node_id:
1052
+ source_node = connection.source_node
1053
+ source_port = connection.source_output
1054
+ target_port = connection.target_input
1055
+
1056
+ # Try to get the actual parameter value from runtime parameters
1057
+ if target_port in parameters:
1058
+ parameter_value = parameters[target_port]
1059
+ break
1060
+
1061
+ # If no connection found, this might be a direct parameter issue
1062
+ if source_node == "unknown" and parameters:
1063
+ # Find the first parameter that might have caused the issue
1064
+ for key, value in parameters.items():
1065
+ parameter_value = value
1066
+ target_port = key
1067
+ break
1068
+
1069
+ return ConnectionContext(
1070
+ source_node=source_node,
1071
+ source_port=source_port,
1072
+ target_node=target_node_id,
1073
+ target_port=target_port,
1074
+ parameter_value=parameter_value,
1075
+ validation_mode=self.connection_validation,
1076
+ )
1077
+
1078
+ def get_validation_metrics(self) -> Dict[str, Any]:
1079
+ """Get validation performance metrics for the runtime.
1080
+
1081
+ Returns:
1082
+ Dictionary containing performance and security metrics
1083
+ """
1084
+ metrics_collector = get_metrics_collector()
1085
+ return {
1086
+ "performance_summary": metrics_collector.get_performance_summary(),
1087
+ "security_report": metrics_collector.get_security_report(),
1088
+ "raw_metrics": metrics_collector.export_metrics() if self.debug else None,
1089
+ }
1090
+
1091
+ def reset_validation_metrics(self) -> None:
1092
+ """Reset validation metrics collector."""
1093
+ metrics_collector = get_metrics_collector()
1094
+ metrics_collector.reset_metrics()
1095
+
821
1096
  def _should_stop_on_error(self, workflow: Workflow, node_id: str) -> bool:
822
1097
  """Determine if execution should stop when a node fails.
823
1098
 
@@ -1200,3 +1475,71 @@ class LocalRuntime:
1200
1475
 
1201
1476
  # Default to workflow-level format
1202
1477
  return False
1478
+
1479
+ def _validate_connection_contracts(
1480
+ self,
1481
+ workflow: Workflow,
1482
+ target_node_id: str,
1483
+ target_inputs: dict[str, Any],
1484
+ node_outputs: dict[str, dict[str, Any]],
1485
+ ) -> list[dict[str, str]]:
1486
+ """
1487
+ Validate connection contracts for a target node.
1488
+
1489
+ Args:
1490
+ workflow: The workflow being executed
1491
+ target_node_id: ID of the target node
1492
+ target_inputs: Inputs being passed to the target node
1493
+ node_outputs: Outputs from all previously executed nodes
1494
+
1495
+ Returns:
1496
+ List of contract violations (empty if all valid)
1497
+ """
1498
+ violations = []
1499
+
1500
+ # Get connection contracts from workflow metadata
1501
+ connection_contracts = workflow.metadata.get("connection_contracts", {})
1502
+ if not connection_contracts:
1503
+ return violations # No contracts to validate
1504
+
1505
+ # Create contract validator
1506
+ validator = ContractValidator()
1507
+
1508
+ # Find all connections targeting this node
1509
+ for connection in workflow.connections:
1510
+ if connection.target_node == target_node_id:
1511
+ connection_id = f"{connection.source_node}.{connection.source_output} → {connection.target_node}.{connection.target_input}"
1512
+
1513
+ # Check if this connection has a contract
1514
+ if connection_id in connection_contracts:
1515
+ contract_dict = connection_contracts[connection_id]
1516
+
1517
+ # Reconstruct contract from dictionary
1518
+ contract = ConnectionContract.from_dict(contract_dict)
1519
+
1520
+ # Get source data from node outputs
1521
+ source_data = None
1522
+ if connection.source_node in node_outputs:
1523
+ source_outputs = node_outputs[connection.source_node]
1524
+ if connection.source_output in source_outputs:
1525
+ source_data = source_outputs[connection.source_output]
1526
+
1527
+ # Get target data from inputs
1528
+ target_data = target_inputs.get(connection.target_input)
1529
+
1530
+ # Validate the connection if we have data
1531
+ if source_data is not None or target_data is not None:
1532
+ is_valid, errors = validator.validate_connection(
1533
+ contract, source_data, target_data
1534
+ )
1535
+
1536
+ if not is_valid:
1537
+ violations.append(
1538
+ {
1539
+ "connection": connection_id,
1540
+ "contract": contract.name,
1541
+ "error": "; ".join(errors),
1542
+ }
1543
+ )
1544
+
1545
+ return violations
@@ -0,0 +1,20 @@
1
+ """
2
+ Runtime validation module for enhanced connection validation.
3
+
4
+ Provides connection context tracking, error categorization, suggestion generation,
5
+ and enhanced error message formatting for better developer experience.
6
+ """
7
+
8
+ from .connection_context import ConnectionContext
9
+ from .enhanced_error_formatter import EnhancedErrorFormatter
10
+ from .error_categorizer import ErrorCategorizer, ErrorCategory
11
+ from .suggestion_engine import ValidationSuggestion, ValidationSuggestionEngine
12
+
13
+ __all__ = [
14
+ "ConnectionContext",
15
+ "ErrorCategorizer",
16
+ "ErrorCategory",
17
+ "ValidationSuggestionEngine",
18
+ "ValidationSuggestion",
19
+ "EnhancedErrorFormatter",
20
+ ]
@@ -0,0 +1,119 @@
1
+ """
2
+ Connection context tracking for enhanced error messages.
3
+
4
+ Provides detailed information about connection sources and targets
5
+ to enable precise error message generation with connection paths.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Any, Optional
10
+
11
+
12
+ @dataclass
13
+ class ConnectionContext:
14
+ """Context information for a specific parameter connection.
15
+
16
+ Tracks the source and target of a connection to enable detailed
17
+ error message generation with connection path reconstruction.
18
+ """
19
+
20
+ source_node: str
21
+ """Source node ID in the workflow"""
22
+
23
+ source_port: Optional[str]
24
+ """Output port/parameter from source node (e.g., 'result.data')"""
25
+
26
+ target_node: str
27
+ """Target node ID in the workflow"""
28
+
29
+ target_port: str
30
+ """Input parameter on target node"""
31
+
32
+ parameter_value: Any
33
+ """The actual parameter value being passed through connection"""
34
+
35
+ validation_mode: str
36
+ """Validation mode: 'off', 'warn', or 'strict'"""
37
+
38
+ def get_connection_path(self) -> str:
39
+ """Generate human-readable connection path string.
40
+
41
+ Returns:
42
+ Connection path in format: source_node.source_port → target_node.target_port
43
+ """
44
+ source_part = self.source_node
45
+ if self.source_port:
46
+ source_part = f"{self.source_node}.{self.source_port}"
47
+
48
+ target_part = f"{self.target_node}.{self.target_port}"
49
+
50
+ return f"{source_part} → {target_part}"
51
+
52
+ def get_sanitized_value(self) -> str:
53
+ """Get sanitized representation of parameter value for error messages.
54
+
55
+ Sanitizes sensitive information like SQL injection attempts, passwords, etc.
56
+
57
+ Returns:
58
+ Safe string representation of the parameter value
59
+ """
60
+ if self.parameter_value is None:
61
+ return "None"
62
+
63
+ value_str = str(self.parameter_value)
64
+
65
+ # Detect and sanitize potential security issues
66
+ security_patterns = [
67
+ "drop table",
68
+ "delete from",
69
+ "insert into",
70
+ "update set",
71
+ "union select",
72
+ "exec(",
73
+ "eval(",
74
+ "script>",
75
+ "password",
76
+ "secret",
77
+ "token",
78
+ "key",
79
+ ]
80
+
81
+ for pattern in security_patterns:
82
+ if pattern.lower() in value_str.lower():
83
+ return "**SANITIZED**"
84
+
85
+ # Truncate very long values
86
+ if len(value_str) > 100:
87
+ return f"{value_str[:97]}..."
88
+
89
+ return value_str
90
+
91
+ def is_security_sensitive(self) -> bool:
92
+ """Check if the parameter value contains security-sensitive data.
93
+
94
+ Returns:
95
+ True if the value appears to contain sensitive information
96
+ """
97
+ if self.parameter_value is None:
98
+ return False
99
+
100
+ value_str = str(self.parameter_value).lower()
101
+
102
+ sensitive_indicators = [
103
+ "password",
104
+ "secret",
105
+ "token",
106
+ "key",
107
+ "auth",
108
+ "drop",
109
+ "delete",
110
+ "insert",
111
+ "update",
112
+ "exec",
113
+ "union",
114
+ "select",
115
+ "script",
116
+ "eval",
117
+ ]
118
+
119
+ return any(indicator in value_str for indicator in sensitive_indicators)