diff --git a/Makefile b/Makefile index 8cfda322..7afdca3a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # MarkiTect - Advanced Markdown Engine # Makefile for common development tasks -.PHONY: help setup install test build clean update status dev lint format check-deps venv-status update-digest add-diary-entry list-issues show-issue list-open-issues close-issue close-issue-enhanced close-issues-batch test-from-issue tdd-start tdd-add-test tdd-finish tdd-status test-status test-new test-coverage test-arch test-foundation test-infrastructure test-integration test-domain test-service test-application test-presentation test-quick test-layers test-random test-random-seed test-random-repeat test-install-randomly test-clean test-tdd test-changed test-module test-cache-clean test-efficient cli-help release-status release-validate release-prepare release-build release-publish release-dry-run +.PHONY: help setup install test build clean update status dev lint format check-deps venv-status update-digest add-diary-entry list-issues show-issue list-open-issues close-issue close-issue-enhanced close-issues-batch test-from-issue tdd-start tdd-add-test tdd-finish tdd-status test-status test-new test-coverage test-arch test-foundation test-infrastructure test-integration test-domain test-service test-application test-presentation test-quick test-layers test-random test-random-seed test-random-repeat test-install-randomly test-clean test-tdd test-changed test-module test-cache-clean test-efficient cli-help release-status release-validate release-prepare release-build release-publish release-dry-run chaos-validate chaos-matrix chaos-inject chaos-report # Default target help: @@ -34,6 +34,12 @@ help: @echo " release-publish VERSION=x.y.z - Publish complete release" @echo " release-dry-run VERSION=x.y.z - Test release preparation" @echo "" + @echo "Chaos Engineering:" + @echo " chaos-validate - Run architectural independence validation" + @echo " chaos-matrix - Show dependency matrix" + @echo " chaos-inject LAYER=X TYPE=Y - Inject chaos into specific layer" + @echo " chaos-report - Generate chaos engineering report" + @echo "" @echo "Architectural Testing:" @echo " test-arch - Run all tests in architectural order" @echo " test-foundation - Run foundation layer tests only" @@ -245,6 +251,27 @@ release-dry-run: fi $(VENV_PYTHON) release.py prepare --version $(VERSION) --dry-run +# Chaos Engineering targets +chaos-validate: + @echo "๐Ÿ”ฅ Running architectural independence validation..." + $(VENV_PYTHON) chaos_test_runner.py validate-independence + +chaos-matrix: + @echo "๐Ÿ—๏ธ Showing architectural dependency matrix..." + $(VENV_PYTHON) chaos_test_runner.py dependency-matrix + +chaos-inject: + @echo "๐Ÿ’ฅ Injecting chaos into layer..." + @if [ -z "$(LAYER)" ]; then \ + echo "โŒ Usage: make chaos-inject LAYER=L1_Presentation TYPE=import_failure"; \ + exit 1; \ + fi + $(VENV_PYTHON) chaos_test_runner.py inject-layer-failure --layer $(LAYER) $(if $(TYPE),--injection-type $(TYPE)) + +chaos-report: + @echo "๐Ÿ“„ Generating chaos engineering report..." + $(VENV_PYTHON) chaos_test_runner.py chaos-report + # Code linting lint: $(VENV)/bin/activate @echo "๐Ÿ” Running linting..." diff --git a/chaos_test_runner.py b/chaos_test_runner.py new file mode 100755 index 00000000..32c2c740 --- /dev/null +++ b/chaos_test_runner.py @@ -0,0 +1,748 @@ +#!/usr/bin/env python3 +""" +Architectural Layer Independence Test Runner with Chaos Engineering + +This module implements a sophisticated chaos engineering system that validates +architectural layer independence by injecting controlled failures and monitoring +the impact on dependent and independent layers. + +The system systematically tests that: +1. Failures in lower layers propagate only to dependent upper layers +2. Independent layers remain unaffected by failures in unrelated layers +3. The dependency matrix matches the intended architectural design + +Usage: + python chaos_test_runner.py [options] + +Commands: + validate-independence Run full architectural independence validation + inject-layer-failure Inject failure into specific layer + dependency-matrix Show architectural dependency matrix + chaos-report Generate comprehensive chaos test report +""" + +import os +import sys +import json +import time +import pytest +import tempfile +import traceback +import subprocess +from pathlib import Path +from datetime import datetime +from typing import Dict, List, Set, Optional, Tuple, Any +from dataclasses import dataclass, asdict +from contextlib import contextmanager +from unittest.mock import patch, MagicMock +import logging + + +@dataclass +class ArchitecturalLayer: + """Represents an architectural layer with its properties.""" + name: str + level: int + description: str + test_pattern: str + dependencies: List[str] # Layers this layer depends on + modules: List[str] # Python modules in this layer + + +@dataclass +class ChaosInjectionResult: + """Results from a chaos injection test.""" + target_layer: str + injection_type: str + start_time: datetime + end_time: datetime + affected_layers: List[str] + expected_affected: List[str] + dependency_violations: List[str] + test_results: Dict[str, Any] + success: bool + error_message: Optional[str] = None + + +@dataclass +class DependencyViolation: + """Represents a detected architectural dependency violation.""" + violating_layer: str + affected_layer: str + violation_type: str + expected_independence: bool + actual_impact: bool + severity: str + description: str + + +class ArchitecturalChaosEngine: + """ + Chaos engineering engine for validating architectural layer independence. + + This engine systematically injects failures into each architectural layer + and monitors the impact on other layers to validate the dependency matrix. + """ + + def __init__(self, project_root: Optional[Path] = None): + self.project_root = project_root or Path(__file__).parent + self.test_dir = self.project_root / "tests" + self.results_dir = self.project_root / "chaos_results" + self.results_dir.mkdir(exist_ok=True) + + # Set up logging + self.logger = self._setup_logging() + + # Define architectural layers with their dependencies + self.layers = self._define_architectural_layers() + self.dependency_matrix = self._build_dependency_matrix() + + # Chaos injection mechanisms + self.injection_strategies = { + 'import_failure': self._inject_import_failure, + 'function_failure': self._inject_function_failure, + 'class_failure': self._inject_class_failure, + 'module_unavailable': self._inject_module_unavailable, + 'database_failure': self._inject_database_failure, + 'network_failure': self._inject_network_failure, + 'filesystem_failure': self._inject_filesystem_failure + } + + def _setup_logging(self) -> logging.Logger: + """Set up logging for chaos engineering operations.""" + logger = logging.getLogger('chaos_engine') + logger.setLevel(logging.INFO) + + if not logger.handlers: + handler = logging.StreamHandler() + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + + return logger + + def _define_architectural_layers(self) -> Dict[str, ArchitecturalLayer]: + """Define the architectural layers and their relationships.""" + return { + 'L1_Presentation': ArchitecturalLayer( + name='L1_Presentation', + level=1, + description='CLI Interface and User Interaction', + test_pattern='test_l1_*.py', + dependencies=['L2_Application'], + modules=['cli', 'markitect.cli'] + ), + 'L2_Application': ArchitecturalLayer( + name='L2_Application', + level=2, + description='Feature Workflows and Use Cases', + test_pattern='test_l2_*.py', + dependencies=['L3_Domain', 'L4_Service'], + modules=['application', 'tddai', 'markitect.issues'] + ), + 'L3_Domain': ArchitecturalLayer( + name='L3_Domain', + level=3, + description='Business Logic and Domain Models', + test_pattern='test_l3_*.py', + dependencies=['L4_Service'], + modules=['domain', 'markitect.schema_generator', 'markitect.metaschema'] + ), + 'L4_Service': ArchitecturalLayer( + name='L4_Service', + level=4, + description='Application Services and Orchestration', + test_pattern='test_l4_*.py', + dependencies=['L5_Infrastructure'], + modules=['services', 'markitect.ast_service', 'markitect.document_manager'] + ), + 'L5_Infrastructure': ArchitecturalLayer( + name='L5_Infrastructure', + level=5, + description='Technical Infrastructure', + test_pattern='test_l5_*.py', + dependencies=['L6_Integration', 'L7_Foundation'], + modules=['infrastructure', 'markitect.cache_service', 'markitect.ast_cache'] + ), + 'L6_Integration': ArchitecturalLayer( + name='L6_Integration', + level=6, + description='External API and System Integration', + test_pattern='test_l6_*.py', + dependencies=['L7_Foundation'], + modules=['gitea', 'markitect.issues.plugins'] + ), + 'L7_Foundation': ArchitecturalLayer( + name='L7_Foundation', + level=7, + description='Core Components and Utilities', + test_pattern='test_l7_*.py', + dependencies=[], # Foundation depends on nothing + modules=['markitect.database', 'markitect.parser', 'markitect.frontmatter'] + ) + } + + def _build_dependency_matrix(self) -> Dict[str, Set[str]]: + """Build the complete dependency matrix including transitive dependencies.""" + matrix = {} + + for layer_name, layer in self.layers.items(): + # Start with direct dependencies + all_deps = set(layer.dependencies) + + # Add transitive dependencies + to_check = list(layer.dependencies) + while to_check: + dep = to_check.pop(0) + if dep in self.layers: + for transitive_dep in self.layers[dep].dependencies: + if transitive_dep not in all_deps: + all_deps.add(transitive_dep) + to_check.append(transitive_dep) + + matrix[layer_name] = all_deps + + return matrix + + def show_dependency_matrix(self): + """Display the architectural dependency matrix.""" + print("๐Ÿ—๏ธ Architectural Layer Dependency Matrix") + print("=" * 50) + + for layer_name in sorted(self.layers.keys()): + layer = self.layers[layer_name] + deps = self.dependency_matrix[layer_name] + + print(f"\n{layer.name} (L{layer.level})") + print(f" Description: {layer.description}") + print(f" Direct Dependencies: {layer.dependencies}") + print(f" All Dependencies: {sorted(deps)}") + print(f" Test Pattern: {layer.test_pattern}") + + @contextmanager + def _chaos_injection_context(self, layer_name: str, injection_type: str): + """Context manager for safe chaos injection with cleanup.""" + self.logger.info(f"๐Ÿ”ฅ Starting chaos injection: {injection_type} on {layer_name}") + + # Store original state for restoration + original_modules = sys.modules.copy() + import builtins + original_import = builtins.__import__ + + try: + yield + except Exception as e: + self.logger.error(f"โŒ Chaos injection failed: {e}") + raise + finally: + # Restore original state + builtins.__import__ = original_import + self._restore_system_state(original_modules, {}) + self.logger.info(f"โœ… Chaos injection cleanup completed for {layer_name}") + + def _restore_system_state(self, original_modules: Dict, original_builtins: Dict): + """Restore system state after chaos injection.""" + # Restore modules + modules_to_remove = set(sys.modules.keys()) - set(original_modules.keys()) + for module in modules_to_remove: + if module in sys.modules: + del sys.modules[module] + + # Restore modified modules + for module_name, module in original_modules.items(): + sys.modules[module_name] = module + + def _inject_import_failure(self, layer_name: str, **kwargs): + """Inject import failure for modules in the specified layer.""" + layer = self.layers[layer_name] + failed_modules = [] + + # Store original import function + import builtins + original_import = builtins.__import__ + + def patched_import(name, *args, **kwargs): + # Check if this import should fail + if any(name.startswith(mod) for mod in layer.modules): + raise ImportError(f"Chaos injection: {name} module failure") + return original_import(name, *args, **kwargs) + + # Apply the patch + builtins.__import__ = patched_import + failed_modules.extend(layer.modules) + + return {'failed_modules': failed_modules} + + def _inject_function_failure(self, layer_name: str, **kwargs): + """Inject function-level failures in the specified layer.""" + layer = self.layers[layer_name] + patched_functions = [] + + # This would patch specific functions based on the layer + # Implementation would depend on the specific layer's key functions + return {'patched_functions': patched_functions} + + def _inject_class_failure(self, layer_name: str, **kwargs): + """Inject class-level failures in the specified layer.""" + layer = self.layers[layer_name] + patched_classes = [] + + # This would patch specific classes based on the layer + return {'patched_classes': patched_classes} + + def _inject_module_unavailable(self, layer_name: str, **kwargs): + """Make entire modules unavailable for the specified layer.""" + layer = self.layers[layer_name] + + for module_name in layer.modules: + if module_name in sys.modules: + # Temporarily remove the module + del sys.modules[module_name] + + return {'removed_modules': layer.modules} + + def _inject_database_failure(self, layer_name: str, **kwargs): + """Inject database failures for infrastructure layer.""" + if layer_name != 'L5_Infrastructure': + return {'message': 'Database failure only applicable to Infrastructure layer'} + + # Patch database operations to fail + patches = [] + return {'database_patches': patches} + + def _inject_network_failure(self, layer_name: str, **kwargs): + """Inject network failures for integration layer.""" + if layer_name != 'L6_Integration': + return {'message': 'Network failure only applicable to Integration layer'} + + # Patch network operations to fail + patches = [] + return {'network_patches': patches} + + def _inject_filesystem_failure(self, layer_name: str, **kwargs): + """Inject filesystem failures.""" + patches = [] + return {'filesystem_patches': patches} + + def run_layer_tests(self, layer_name: str) -> Dict[str, Any]: + """Run tests for a specific layer and return results.""" + layer = self.layers[layer_name] + test_files = list(self.test_dir.glob(layer.test_pattern)) + + if not test_files: + return { + 'success': False, + 'error': f'No test files found for pattern {layer.test_pattern}', + 'test_count': 0, + 'failures': 0 + } + + # Run pytest for the layer + cmd = ['python', '-m', 'pytest'] + [str(f) for f in test_files] + [ + '--tb=short', '--quiet', '--disable-warnings' + ] + + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + cwd=self.project_root, + timeout=120 # 2 minute timeout per layer + ) + + # Parse pytest output for test counts + output = result.stdout + result.stderr + test_count = self._extract_test_count(output) + failures = self._extract_failure_count(output) + + return { + 'success': result.returncode == 0, + 'test_count': test_count, + 'failures': failures, + 'output': output[:1000], # Truncate for storage + 'return_code': result.returncode + } + + except subprocess.TimeoutExpired: + return { + 'success': False, + 'error': 'Test execution timeout', + 'test_count': 0, + 'failures': 1 + } + except Exception as e: + return { + 'success': False, + 'error': str(e), + 'test_count': 0, + 'failures': 1 + } + + def _extract_test_count(self, output: str) -> int: + """Extract total test count from pytest output.""" + import re + patterns = [ + r'(\d+) passed', + r'collected (\d+) items', + r'(\d+) failed', + ] + + for pattern in patterns: + match = re.search(pattern, output) + if match: + return int(match.group(1)) + + return 0 + + def _extract_failure_count(self, output: str) -> int: + """Extract failure count from pytest output.""" + import re + patterns = [ + r'(\d+) failed', + r'FAILED.*::.*(\d+)', + ] + + for pattern in patterns: + match = re.search(pattern, output) + if match: + return int(match.group(1)) + + return 0 + + def inject_chaos_and_test(self, target_layer: str, injection_type: str) -> ChaosInjectionResult: + """Inject chaos into a layer and run all tests to measure impact.""" + start_time = datetime.now() + + try: + with self._chaos_injection_context(target_layer, injection_type): + # Perform the chaos injection + injection_result = self.injection_strategies[injection_type](target_layer) + + # Run tests on all layers to see impact + test_results = {} + affected_layers = [] + + for layer_name in self.layers.keys(): + self.logger.info(f"๐Ÿงช Testing {layer_name} under chaos conditions") + layer_result = self.run_layer_tests(layer_name) + test_results[layer_name] = layer_result + + # Consider layer affected if tests fail + if not layer_result['success']: + affected_layers.append(layer_name) + + # Determine expected affected layers + expected_affected = self._calculate_expected_impact(target_layer) + + # Detect violations + violations = self._detect_dependency_violations( + target_layer, affected_layers, expected_affected + ) + + end_time = datetime.now() + + return ChaosInjectionResult( + target_layer=target_layer, + injection_type=injection_type, + start_time=start_time, + end_time=end_time, + affected_layers=affected_layers, + expected_affected=expected_affected, + dependency_violations=[v.violating_layer for v in violations], + test_results=test_results, + success=len(violations) == 0 + ) + + except Exception as e: + end_time = datetime.now() + return ChaosInjectionResult( + target_layer=target_layer, + injection_type=injection_type, + start_time=start_time, + end_time=end_time, + affected_layers=[], + expected_affected=[], + dependency_violations=[], + test_results={}, + success=False, + error_message=str(e) + ) + + def _calculate_expected_impact(self, target_layer: str) -> List[str]: + """Calculate which layers should be affected by failure in target layer.""" + expected_affected = [target_layer] # Target layer should always be affected + + # Find all layers that depend on the target layer + for layer_name, dependencies in self.dependency_matrix.items(): + if target_layer in dependencies: + expected_affected.append(layer_name) + + return expected_affected + + def _detect_dependency_violations(self, target_layer: str, + actual_affected: List[str], + expected_affected: List[str]) -> List[DependencyViolation]: + """Detect violations of architectural dependencies.""" + violations = [] + + # Check for unexpected impacts (layers that shouldn't be affected but were) + for layer in actual_affected: + if layer not in expected_affected: + violations.append(DependencyViolation( + violating_layer=layer, + affected_layer=target_layer, + violation_type='unexpected_dependency', + expected_independence=True, + actual_impact=True, + severity='HIGH', + description=f'{layer} was affected by {target_layer} failure but should be independent' + )) + + # Check for missing impacts (layers that should be affected but weren't) + for layer in expected_affected: + if layer not in actual_affected and layer != target_layer: + violations.append(DependencyViolation( + violating_layer=layer, + affected_layer=target_layer, + violation_type='missing_dependency', + expected_independence=False, + actual_impact=False, + severity='MEDIUM', + description=f'{layer} should be affected by {target_layer} failure but was not' + )) + + return violations + + def validate_architectural_independence(self) -> Dict[str, Any]: + """Run comprehensive architectural independence validation.""" + self.logger.info("๐Ÿš€ Starting comprehensive architectural independence validation") + + validation_results = { + 'start_time': datetime.now(), + 'layer_results': {}, + 'violations': [], + 'summary': { + 'total_injections': 0, + 'successful_injections': 0, + 'total_violations': 0, + 'layers_tested': len(self.layers) + } + } + + # Test each layer with different injection types + injection_types = ['import_failure', 'module_unavailable'] + + for layer_name in self.layers.keys(): + layer_results = {} + + for injection_type in injection_types: + self.logger.info(f"๐Ÿ”ฅ Testing {layer_name} with {injection_type}") + + result = self.inject_chaos_and_test(layer_name, injection_type) + layer_results[injection_type] = result + + validation_results['summary']['total_injections'] += 1 + if result.success: + validation_results['summary']['successful_injections'] += 1 + + validation_results['summary']['total_violations'] += len(result.dependency_violations) + + validation_results['layer_results'][layer_name] = layer_results + + validation_results['end_time'] = datetime.now() + validation_results['duration'] = ( + validation_results['end_time'] - validation_results['start_time'] + ).total_seconds() + + # Save results + self._save_validation_results(validation_results) + + return validation_results + + def _save_validation_results(self, results: Dict[str, Any]): + """Save validation results to file.""" + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + filename = f"chaos_validation_{timestamp}.json" + filepath = self.results_dir / filename + + # Convert datetime objects to strings for JSON serialization + serializable_results = self._make_json_serializable(results) + + with open(filepath, 'w') as f: + json.dump(serializable_results, f, indent=2) + + self.logger.info(f"๐Ÿ“„ Results saved to {filepath}") + + def _make_json_serializable(self, obj): + """Convert objects to JSON-serializable format.""" + if isinstance(obj, datetime): + return obj.isoformat() + elif isinstance(obj, dict): + return {k: self._make_json_serializable(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [self._make_json_serializable(item) for item in obj] + elif hasattr(obj, '__dict__'): + return self._make_json_serializable(asdict(obj)) + else: + return obj + + def generate_chaos_report(self, results_file: Optional[str] = None) -> str: + """Generate a comprehensive chaos engineering report.""" + if results_file: + with open(results_file, 'r') as f: + results = json.load(f) + else: + # Use the most recent results file + result_files = sorted(self.results_dir.glob("chaos_validation_*.json")) + if not result_files: + return "No chaos validation results found" + + with open(result_files[-1], 'r') as f: + results = json.load(f) + + report = self._build_text_report(results) + + # Save report + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + report_file = self.results_dir / f"chaos_report_{timestamp}.md" + with open(report_file, 'w') as f: + f.write(report) + + return report + + def _build_text_report(self, results: Dict[str, Any]) -> str: + """Build a formatted text report from results.""" + summary = results['summary'] + + report = f"""# Architectural Independence Chaos Engineering Report + +## Executive Summary + +**Validation Date**: {results['start_time']} +**Duration**: {results.get('duration', 0):.2f} seconds +**Layers Tested**: {summary['layers_tested']} +**Total Chaos Injections**: {summary['total_injections']} +**Successful Injections**: {summary['successful_injections']} +**Total Violations Detected**: {summary['total_violations']} + +**Overall Health**: {'โœ… PASS' if summary['total_violations'] == 0 else 'โŒ VIOLATIONS DETECTED'} + +## Architectural Layer Overview + +""" + + for layer_name, layer in self.layers.items(): + dependencies = ', '.join(layer.dependencies) if layer.dependencies else 'None' + report += f"- **{layer.name}**: {layer.description} (Dependencies: {dependencies})\n" + + report += "\n## Dependency Matrix\n\n" + for layer_name, deps in self.dependency_matrix.items(): + deps_str = ', '.join(sorted(deps)) if deps else 'None' + report += f"- **{layer_name}**: {deps_str}\n" + + report += "\n## Chaos Injection Results\n\n" + + layer_results = results.get('layer_results', {}) + for layer_name, injections in layer_results.items(): + report += f"### {layer_name}\n\n" + + for injection_type, result in injections.items(): + status = 'โœ… PASS' if result['success'] else 'โŒ FAIL' + violations = len(result['dependency_violations']) + + report += f"**{injection_type}**: {status}\n" + report += f"- Affected Layers: {', '.join(result['affected_layers'])}\n" + report += f"- Expected Affected: {', '.join(result['expected_affected'])}\n" + report += f"- Violations: {violations}\n" + + if result.get('error_message'): + report += f"- Error: {result['error_message']}\n" + + report += "\n" + + report += "\n## Recommendations\n\n" + + if summary['total_violations'] == 0: + report += "โœ… **Excellent**: All architectural boundaries are properly maintained.\n" + report += "โœ… **No violations detected**: The system demonstrates proper layer independence.\n" + else: + report += "โŒ **Action Required**: Architectural violations detected that need attention.\n" + report += "๐Ÿ”ง **Priority**: Review and refactor components with dependency violations.\n" + report += "๐Ÿ“Š **Monitor**: Run chaos tests regularly to prevent regression.\n" + + return report + + +def main(): + """Main entry point for the chaos test runner.""" + import argparse + + parser = argparse.ArgumentParser( + description="Architectural Layer Independence Test Runner with Chaos Engineering" + ) + + parser.add_argument('command', choices=[ + 'validate-independence', + 'inject-layer-failure', + 'dependency-matrix', + 'chaos-report' + ], help='Command to execute') + + parser.add_argument('--layer', type=str, help='Target layer for injection') + parser.add_argument('--injection-type', type=str, default='import_failure', + choices=['import_failure', 'module_unavailable', 'function_failure'], + help='Type of chaos injection') + parser.add_argument('--results-file', type=str, help='Results file for report generation') + parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output') + + args = parser.parse_args() + + # Set up logging level + if args.verbose: + logging.getLogger('chaos_engine').setLevel(logging.DEBUG) + + engine = ArchitecturalChaosEngine() + + if args.command == 'dependency-matrix': + engine.show_dependency_matrix() + + elif args.command == 'inject-layer-failure': + if not args.layer: + print("โŒ Error: --layer is required for inject-layer-failure") + sys.exit(1) + + print(f"๐Ÿ”ฅ Injecting {args.injection_type} into {args.layer}") + result = engine.inject_chaos_and_test(args.layer, args.injection_type) + + print(f"\n๐Ÿ“Š Results:") + print(f"Success: {result.success}") + print(f"Affected Layers: {result.affected_layers}") + print(f"Expected Affected: {result.expected_affected}") + print(f"Violations: {result.dependency_violations}") + + elif args.command == 'validate-independence': + print("๐Ÿš€ Starting comprehensive architectural independence validation") + results = engine.validate_architectural_independence() + + summary = results['summary'] + print(f"\n๐Ÿ“Š Validation Complete!") + print(f"Total Injections: {summary['total_injections']}") + print(f"Successful: {summary['successful_injections']}") + print(f"Violations: {summary['total_violations']}") + + if summary['total_violations'] == 0: + print("โœ… All architectural boundaries properly maintained!") + else: + print("โŒ Architectural violations detected - review results") + + elif args.command == 'chaos-report': + print("๐Ÿ“„ Generating chaos engineering report") + report = engine.generate_chaos_report(args.results_file) + print(report) + + +if __name__ == "__main__": + main() \ No newline at end of file