UNPKG

@sailboat-computer/resilience

Version:

Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations

613 lines 23.9 kB
"use strict"; /** * Graceful degradation strategies with marine-specific adaptations */ Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultDegradationManager = exports.DegradationManager = exports.FeaturePriority = exports.ServiceQualityLevel = void 0; const marine_constants_1 = require("../types/marine-constants"); /** * Service quality level */ var ServiceQualityLevel; (function (ServiceQualityLevel) { /** * Full functionality with optimal performance */ ServiceQualityLevel["OPTIMAL"] = "optimal"; /** * Full functionality with reduced performance */ ServiceQualityLevel["REDUCED"] = "reduced"; /** * Core functionality only */ ServiceQualityLevel["MINIMAL"] = "minimal"; /** * Critical functionality only */ ServiceQualityLevel["CRITICAL"] = "critical"; /** * Emergency functionality only */ ServiceQualityLevel["EMERGENCY"] = "emergency"; })(ServiceQualityLevel || (exports.ServiceQualityLevel = ServiceQualityLevel = {})); /** * Feature priority for degradation decisions */ var FeaturePriority; (function (FeaturePriority) { /** * Critical for vessel safety */ FeaturePriority["CRITICAL"] = "critical"; /** * Essential for operation */ FeaturePriority["ESSENTIAL"] = "essential"; /** * Important but can be degraded */ FeaturePriority["IMPORTANT"] = "important"; /** * Useful but non-essential */ FeaturePriority["USEFUL"] = "useful"; /** * Comfort or convenience only */ FeaturePriority["COMFORT"] = "comfort"; })(FeaturePriority || (exports.FeaturePriority = FeaturePriority = {})); /** * Degradation manager for graceful service degradation */ class DegradationManager { constructor(config) { this.featureToggles = new Map(); this.eventListeners = []; this.qualityLevelTimestamps = new Map(); this.lastQualityUpdateTime = Date.now(); this.config = { defaultQualityLevel: ServiceQualityLevel.OPTIMAL, adaptToMarineConditions: true, powerAware: true, connectivityAware: true, qualityThresholds: { seaState: { rough: ServiceQualityLevel.REDUCED, veryRough: ServiceQualityLevel.MINIMAL }, powerStatus: { conservation: ServiceQualityLevel.REDUCED, critical: ServiceQualityLevel.CRITICAL }, connectivityQuality: { low: 0.3, lowQualityLevel: ServiceQualityLevel.REDUCED, veryLow: 0.1, veryLowQualityLevel: ServiceQualityLevel.MINIMAL } }, featureToggles: { autoToggleFeatures: true, qualityLevelMinPriority: { [ServiceQualityLevel.OPTIMAL]: FeaturePriority.COMFORT, [ServiceQualityLevel.REDUCED]: FeaturePriority.USEFUL, [ServiceQualityLevel.MINIMAL]: FeaturePriority.IMPORTANT, [ServiceQualityLevel.CRITICAL]: FeaturePriority.ESSENTIAL, [ServiceQualityLevel.EMERGENCY]: FeaturePriority.CRITICAL } }, resourceLimits: { cpu: { [ServiceQualityLevel.OPTIMAL]: 80, [ServiceQualityLevel.REDUCED]: 60, [ServiceQualityLevel.MINIMAL]: 40, [ServiceQualityLevel.CRITICAL]: 30, [ServiceQualityLevel.EMERGENCY]: 20 }, memory: { [ServiceQualityLevel.OPTIMAL]: 80, [ServiceQualityLevel.REDUCED]: 60, [ServiceQualityLevel.MINIMAL]: 40, [ServiceQualityLevel.CRITICAL]: 30, [ServiceQualityLevel.EMERGENCY]: 20 }, bandwidth: { [ServiceQualityLevel.OPTIMAL]: 1000, [ServiceQualityLevel.REDUCED]: 500, [ServiceQualityLevel.MINIMAL]: 200, [ServiceQualityLevel.CRITICAL]: 50, [ServiceQualityLevel.EMERGENCY]: 10 }, storageIops: { [ServiceQualityLevel.OPTIMAL]: 1000, [ServiceQualityLevel.REDUCED]: 500, [ServiceQualityLevel.MINIMAL]: 200, [ServiceQualityLevel.CRITICAL]: 50, [ServiceQualityLevel.EMERGENCY]: 10 } }, ...config }; this.currentQualityLevel = this.config.defaultQualityLevel; this.metrics = this.initializeMetrics(); // Initialize quality level timestamps Object.values(ServiceQualityLevel).forEach(level => { this.qualityLevelTimestamps.set(level, 0); }); this.qualityLevelTimestamps.set(this.currentQualityLevel, Date.now()); } /** * Register a feature toggle */ registerFeature(feature) { this.featureToggles.set(feature.id, feature); this.updateMetrics(); this.emitEvent('recovery', `feature-${feature.id}`, { action: 'feature_registered', featureId: feature.id, featureName: feature.name, priority: feature.priority, enabled: feature.enabled }, 'info'); // Apply current quality level to the feature this.applyQualityLevelToFeature(feature); } /** * Unregister a feature toggle */ unregisterFeature(featureId) { const feature = this.featureToggles.get(featureId); if (!feature) { return false; } this.featureToggles.delete(featureId); this.updateMetrics(); this.emitEvent('recovery', `feature-${featureId}`, { action: 'feature_unregistered', featureId }, 'info'); return true; } /** * Check if a feature is enabled */ isFeatureEnabled(featureId) { const feature = this.featureToggles.get(featureId); return feature ? feature.enabled : false; } /** * Manually enable a feature */ enableFeature(featureId) { const feature = this.featureToggles.get(featureId); if (!feature || !feature.manualToggleAllowed) { return false; } if (!feature.enabled) { feature.enabled = true; this.updateMetrics(); this.emitEvent('recovery', `feature-${featureId}`, { action: 'feature_enabled', featureId, featureName: feature.name, manual: true }, 'info'); } return true; } /** * Manually disable a feature */ disableFeature(featureId) { const feature = this.featureToggles.get(featureId); if (!feature || !feature.manualToggleAllowed) { return false; } // Don't disable critical features in emergency mode if (this.currentQualityLevel === ServiceQualityLevel.EMERGENCY && feature.priority === FeaturePriority.CRITICAL) { return false; } if (feature.enabled) { feature.enabled = false; this.updateMetrics(); this.emitEvent('recovery', `feature-${featureId}`, { action: 'feature_disabled', featureId, featureName: feature.name, manual: true }, 'info'); } return true; } /** * Get current service quality level */ getQualityLevel() { return this.currentQualityLevel; } /** * Set service quality level manually */ setQualityLevel(level) { if (level === this.currentQualityLevel) { return; } const previousLevel = this.currentQualityLevel; this.currentQualityLevel = level; // Update quality level timestamps const now = Date.now(); const timeInPreviousLevel = now - this.lastQualityUpdateTime; const previousTime = this.qualityLevelTimestamps.get(previousLevel) || 0; this.qualityLevelTimestamps.set(previousLevel, previousTime + timeInPreviousLevel); this.lastQualityUpdateTime = now; // Apply quality level to all features this.applyQualityLevelToAllFeatures(); // Update metrics this.metrics.previousQualityLevel = previousLevel; this.metrics.currentQualityLevel = level; this.metrics.qualityLevelChanges++; this.updateMetrics(); this.emitEvent('recovery', 'degradation-manager', { action: 'quality_level_changed', previousLevel, newLevel: level, manual: true }, 'warning'); } /** * Update marine environment for adaptive degradation */ updateMarineEnvironment(environment) { this.marineEnvironment = environment; // Update marine-specific metrics if (environment.seaState === 'rough' || environment.seaState === 'very_rough') { this.metrics.marineMetrics.timeInRoughSeas += Date.now() - this.lastQualityUpdateTime; } if (environment.powerStatus === 'conservation' || environment.powerStatus === 'critical') { this.metrics.marineMetrics.timeInPowerConservation += Date.now() - this.lastQualityUpdateTime; } if (environment.connectivityQuality < this.config.qualityThresholds.connectivityQuality.low) { this.metrics.marineMetrics.timeWithPoorConnectivity += Date.now() - this.lastQualityUpdateTime; } if (environment.criticalOperationsOnly) { this.metrics.marineMetrics.timeInEmergencyMode += Date.now() - this.lastQualityUpdateTime; } this.lastQualityUpdateTime = Date.now(); // Determine appropriate quality level based on marine conditions const newQualityLevel = this.determineQualityLevelFromEnvironment(environment); if (newQualityLevel !== this.currentQualityLevel) { const previousLevel = this.currentQualityLevel; this.currentQualityLevel = newQualityLevel; // Update quality level timestamps const now = Date.now(); const timeInPreviousLevel = now - this.lastQualityUpdateTime; const previousTime = this.qualityLevelTimestamps.get(previousLevel) || 0; this.qualityLevelTimestamps.set(previousLevel, previousTime + timeInPreviousLevel); this.lastQualityUpdateTime = now; // Apply quality level to all features this.applyQualityLevelToAllFeatures(); // Update metrics this.metrics.previousQualityLevel = previousLevel; this.metrics.currentQualityLevel = newQualityLevel; this.metrics.qualityLevelChanges++; this.emitEvent('recovery', 'degradation-manager', { action: 'quality_level_changed', previousLevel, newLevel: newQualityLevel, manual: false, environmentalCause: true, seaState: environment.seaState, powerStatus: environment.powerStatus, connectivityQuality: environment.connectivityQuality, criticalOperationsOnly: environment.criticalOperationsOnly }, 'warning'); } this.emitEvent('recovery', 'degradation-manager', { action: 'marine_environment_updated', seaState: environment.seaState, powerStatus: environment.powerStatus, connectivityQuality: environment.connectivityQuality, criticalOperationsOnly: environment.criticalOperationsOnly }, 'info'); } /** * Get current degradation metrics */ getMetrics() { this.updateMetrics(); return { ...this.metrics }; } /** * Get all registered features */ getAllFeatures() { return Array.from(this.featureToggles.values()); } /** * Get enabled features */ getEnabledFeatures() { return Array.from(this.featureToggles.values()).filter(feature => feature.enabled); } /** * Get disabled features */ getDisabledFeatures() { return Array.from(this.featureToggles.values()).filter(feature => !feature.enabled); } /** * Add event listener */ onEvent(listener) { this.eventListeners.push(listener); } /** * Remove event listener */ removeEventListener(listener) { const index = this.eventListeners.indexOf(listener); if (index > -1) { this.eventListeners.splice(index, 1); } } /** * Determine quality level from marine environment */ determineQualityLevelFromEnvironment(environment) { // Start with default quality level let qualityLevel = this.config.defaultQualityLevel; // Emergency mode takes precedence if (environment.criticalOperationsOnly) { return ServiceQualityLevel.EMERGENCY; } // Check power status if configured if (this.config.powerAware) { if (environment.powerStatus === 'critical') { qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.powerStatus.critical); } else if (environment.powerStatus === 'conservation') { qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.powerStatus.conservation); } } // Check sea state if configured if (this.config.adaptToMarineConditions) { if (environment.seaState === 'very_rough') { qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.seaState.veryRough); } else if (environment.seaState === 'rough') { qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.seaState.rough); } } // Check connectivity quality if configured if (this.config.connectivityAware) { if (environment.connectivityQuality < this.config.qualityThresholds.connectivityQuality.veryLow) { qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.connectivityQuality.veryLowQualityLevel); } else if (environment.connectivityQuality < this.config.qualityThresholds.connectivityQuality.low) { qualityLevel = this.getMoreRestrictiveLevel(qualityLevel, this.config.qualityThresholds.connectivityQuality.lowQualityLevel); } } return qualityLevel; } /** * Get the more restrictive of two quality levels */ getMoreRestrictiveLevel(level1, level2) { const levelOrder = [ ServiceQualityLevel.OPTIMAL, ServiceQualityLevel.REDUCED, ServiceQualityLevel.MINIMAL, ServiceQualityLevel.CRITICAL, ServiceQualityLevel.EMERGENCY ]; const index1 = levelOrder.indexOf(level1); const index2 = levelOrder.indexOf(level2); // Ensure we don't return undefined return levelOrder[Math.max(index1, index2)] || ServiceQualityLevel.OPTIMAL; } /** * Apply quality level to all features */ applyQualityLevelToAllFeatures() { if (!this.config.featureToggles.autoToggleFeatures) { return; } for (const feature of this.featureToggles.values()) { this.applyQualityLevelToFeature(feature); } } /** * Apply quality level to a specific feature */ applyQualityLevelToFeature(feature) { if (!this.config.featureToggles.autoToggleFeatures || !feature.manualToggleAllowed) { return; } const minPriority = this.config.featureToggles.qualityLevelMinPriority[this.currentQualityLevel]; const priorityOrder = [ FeaturePriority.COMFORT, FeaturePriority.USEFUL, FeaturePriority.IMPORTANT, FeaturePriority.ESSENTIAL, FeaturePriority.CRITICAL ]; const featurePriorityIndex = priorityOrder.indexOf(feature.priority); const minPriorityIndex = priorityOrder.indexOf(minPriority); const shouldBeEnabled = featurePriorityIndex >= minPriorityIndex; // Special case for marine-specific features if (this.marineEnvironment) { // Always enable safety features in rough seas if (feature.marineSettings.requiredForSafety && (this.marineEnvironment.seaState === 'rough' || this.marineEnvironment.seaState === 'very_rough')) { if (!feature.enabled) { feature.enabled = true; this.emitEvent('recovery', `feature-${feature.id}`, { action: 'feature_enabled', featureId: feature.id, featureName: feature.name, reason: 'safety_required_in_rough_seas', manual: false }, 'info'); } return; } // Always enable navigation features when sailing or motoring if (feature.marineSettings.requiredForNavigation && (this.marineEnvironment.seaState === 'rough' || this.marineEnvironment.seaState === 'very_rough')) { if (!feature.enabled) { feature.enabled = true; this.emitEvent('recovery', `feature-${feature.id}`, { action: 'feature_enabled', featureId: feature.id, featureName: feature.name, reason: 'navigation_required_in_rough_seas', manual: false }, 'info'); } return; } // Disable connectivity-dependent features when connectivity is poor if (feature.marineSettings.requiresGoodConnectivity && this.marineEnvironment.connectivityQuality < this.config.qualityThresholds.connectivityQuality.low) { if (feature.enabled) { feature.enabled = false; this.emitEvent('recovery', `feature-${feature.id}`, { action: 'feature_disabled', featureId: feature.id, featureName: feature.name, reason: 'poor_connectivity', manual: false }, 'info'); } return; } } // Apply standard priority-based rules if (feature.enabled !== shouldBeEnabled) { feature.enabled = shouldBeEnabled; this.emitEvent('recovery', `feature-${feature.id}`, { action: shouldBeEnabled ? 'feature_enabled' : 'feature_disabled', featureId: feature.id, featureName: feature.name, reason: 'quality_level_change', qualityLevel: this.currentQualityLevel, featurePriority: feature.priority, minPriority, manual: false }, 'info'); } } /** * Update metrics */ updateMetrics() { // Update time at quality level const now = Date.now(); const timeInCurrentLevel = now - this.lastQualityUpdateTime; const currentTime = this.qualityLevelTimestamps.get(this.currentQualityLevel) || 0; this.qualityLevelTimestamps.set(this.currentQualityLevel, currentTime + timeInCurrentLevel); this.lastQualityUpdateTime = now; // Update time at quality level metrics Object.values(ServiceQualityLevel).forEach(level => { this.metrics.timeAtQualityLevel[level] = this.qualityLevelTimestamps.get(level) || 0; }); // Update feature toggle metrics const features = Array.from(this.featureToggles.values()); this.metrics.featureToggles.total = features.length; this.metrics.featureToggles.enabled = features.filter(f => f.enabled).length; this.metrics.featureToggles.disabled = features.filter(f => !f.enabled).length; // Update resource usage metrics let totalCpu = 0; let totalMemory = 0; let totalBandwidth = 0; let totalStorageIops = 0; let totalPower = 0; for (const feature of features) { if (feature.enabled) { totalCpu += feature.cpuUsage; totalMemory += feature.memoryUsage; totalBandwidth += feature.bandwidthUsage; totalStorageIops += feature.storageIops; totalPower += feature.powerConsumption; } } this.metrics.resourceUsage.cpu = totalCpu; this.metrics.resourceUsage.memory = totalMemory; this.metrics.resourceUsage.bandwidth = totalBandwidth; this.metrics.resourceUsage.storageIops = totalStorageIops; this.metrics.resourceUsage.power = totalPower; } /** * Initialize metrics */ initializeMetrics() { return { currentQualityLevel: ServiceQualityLevel.OPTIMAL, previousQualityLevel: ServiceQualityLevel.OPTIMAL, timeAtQualityLevel: { [ServiceQualityLevel.OPTIMAL]: 0, [ServiceQualityLevel.REDUCED]: 0, [ServiceQualityLevel.MINIMAL]: 0, [ServiceQualityLevel.CRITICAL]: 0, [ServiceQualityLevel.EMERGENCY]: 0 }, qualityLevelChanges: 0, featureToggles: { total: 0, enabled: 0, disabled: 0, autoToggled: 0, manuallyToggled: 0 }, resourceUsage: { cpu: 0, memory: 0, bandwidth: 0, storageIops: 0, power: 0 }, marineMetrics: { timeInRoughSeas: 0, timeInPowerConservation: 0, timeWithPoorConnectivity: 0, timeInEmergencyMode: 0 } }; } /** * Emit resilience event */ emitEvent(eventType, component, details, severity) { const event = { timestamp: new Date(), eventType, component, details, severity, marineContext: { operationalContext: this.marineEnvironment?.powerStatus === 'critical' ? marine_constants_1.OperationalContext.EMERGENCY : marine_constants_1.OperationalContext.SAILING, environmentalImpact: this.marineEnvironment?.seaState === 'very_rough', safetyImpact: false } }; this.eventListeners.forEach(listener => { try { listener(event); } catch (error) { console.error('Error in degradation manager event listener:', error); } }); } } exports.DegradationManager = DegradationManager; /** * Default degradation manager instance */ exports.defaultDegradationManager = new DegradationManager(); //# sourceMappingURL=DegradationManager.js.map