UNPKG

supa-seed

Version:

A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support

576 lines 24.3 kB
"use strict"; /** * Fallback and Error Handling System for Association Intelligence * Phase 3, Checkpoint C3 - Comprehensive error recovery and progress reporting */ Object.defineProperty(exports, "__esModule", { value: true }); exports.FallbackErrorHandler = void 0; const logger_1 = require("../core/utils/logger"); class FallbackErrorHandler { constructor(options) { this.fallbackStrategies = new Map(); this.options = options; this.currentProgress = this.createInitialProgress(); this.initializeBuiltInStrategies(); } /** * Handle insufficient assets by generating fallbacks */ async handleInsufficientAssets(target, currentAssets, requiredCount) { if (!this.options.enableFallbackGeneration) { return []; } const shortfall = requiredCount - currentAssets.length; if (shortfall <= 0) { return []; } logger_1.Logger.info(`🔄 Generating ${shortfall} fallback assets for target ${target.id}`); const requirements = { targetType: this.inferTargetType(target, currentAssets), requiredCount: shortfall, constraints: target.constraints, existingAssets: currentAssets, targetContext: target, templatePreferences: this.getTemplatePreferences(target) }; // Try fallback strategies in priority order const sortedStrategies = Array.from(this.fallbackStrategies.values()) .filter(strategy => strategy.applicableTypes.includes('*') || strategy.applicableTypes.includes(requirements.targetType)) .sort((a, b) => b.priority - a.priority); const generatedAssets = []; for (const strategy of sortedStrategies) { if (generatedAssets.length >= shortfall) break; try { const remaining = shortfall - generatedAssets.length; const strategyRequirements = { ...requirements, requiredCount: remaining }; const assets = await strategy.generator(strategyRequirements); generatedAssets.push(...assets); logger_1.Logger.debug(`Generated ${assets.length} assets using strategy: ${strategy.name}`); } catch (error) { logger_1.Logger.warn(`Fallback strategy ${strategy.name} failed: ${error.message}`); } } this.updateProgress('error_recovery', `Generated ${generatedAssets.length}/${shortfall} fallback assets for ${target.id}`); return generatedAssets; } /** * Attempt to recover from association errors */ async recoverFromErrors(distributionResult, enforcementResult, originalAssets) { const startTime = Date.now(); const recoveryAttempts = []; const generatedFallbacks = []; const unrecoverableErrors = []; logger_1.Logger.info('🚑 Starting association error recovery process'); this.updateProgress('error_recovery', 'Analyzing errors and violations'); // Convert enforcement violations to recoverable errors const errors = this.analyzeErrors(distributionResult, enforcementResult); let currentAssignments = [...enforcementResult.finalAssignments]; let attemptNumber = 0; for (const error of errors) { if (attemptNumber >= this.options.maxRetryAttempts) { logger_1.Logger.warn(`Maximum retry attempts (${this.options.maxRetryAttempts}) reached`); unrecoverableErrors.push(...errors.slice(errors.indexOf(error))); break; } if (!error.recoverable) { unrecoverableErrors.push(error); continue; } attemptNumber++; const recoveryAttempt = await this.attemptErrorRecovery(error, currentAssignments, originalAssets, attemptNumber); recoveryAttempts.push(recoveryAttempt); if (recoveryAttempt.success) { logger_1.Logger.info(`✅ Successfully recovered from error: ${error.message}`); // Update assignments based on recovery if (error.type === 'asset_insufficient') { const fallbacks = await this.handleInsufficientAssets(currentAssignments.find(a => a.target.id === error.context.targetId)?.target, currentAssignments.find(a => a.target.id === error.context.targetId)?.assets || [], recoveryAttempt.recoveredItems); generatedFallbacks.push(...fallbacks); // Add fallbacks to assignments const targetAssignment = currentAssignments.find(a => a.target.id === error.context.targetId); if (targetAssignment) { targetAssignment.assets.push(...fallbacks); targetAssignment.fulfilled = this.checkFulfillment(targetAssignment); } } } else { logger_1.Logger.warn(`❌ Failed to recover from error: ${error.message}`); unrecoverableErrors.push(error); } } const totalRecoveryTime = Date.now() - startTime; const success = unrecoverableErrors.length === 0; this.updateProgress('finalization', 'Recovery process completed'); const finalReport = this.generateFinalReport(currentAssignments, generatedFallbacks, recoveryAttempts, unrecoverableErrors); const recommendations = this.generateRecoveryRecommendations(recoveryAttempts, unrecoverableErrors, generatedFallbacks); logger_1.Logger.info(`🏁 Recovery completed: ${success ? 'SUCCESS' : 'PARTIAL'} (${totalRecoveryTime}ms)`); return { success, finalAssignments: currentAssignments, recoveryAttempts, generatedFallbacks, unrecoverableErrors, finalReport, recommendations, performanceMetrics: { totalRecoveryTime, fallbackGenerationTime: recoveryAttempts .filter(r => r.strategy === 'fallback_generation') .reduce((sum, r) => sum + r.executionTime, 0), constraintRelaxationTime: recoveryAttempts .filter(r => r.strategy === 'constraint_relaxation') .reduce((sum, r) => sum + r.executionTime, 0), finalValidationTime: 0 // Would be set by validation step } }; } /** * Generate progress reports for long-running operations */ reportProgress() { return { ...this.currentProgress }; } /** * Update progress with new information */ updateProgress(phase, currentStep, progressIncrement = 0) { this.currentProgress.phase = phase; this.currentProgress.currentStep = currentStep; this.currentProgress.overallProgress = Math.min(100, this.currentProgress.overallProgress + progressIncrement); this.currentProgress.completedSteps.push(currentStep); if (this.options.reportingLevel === 'verbose') { logger_1.Logger.info(`📊 Progress: ${this.currentProgress.overallProgress}% - ${currentStep}`); } } /** * Attempt to recover from a specific error */ async attemptErrorRecovery(error, assignments, originalAssets, attemptNumber) { const startTime = Date.now(); let success = false; let errorMessage; let recoveredItems = 0; let strategy = 'unknown'; let action = 'unknown'; try { switch (error.type) { case 'asset_insufficient': strategy = 'fallback_generation'; action = 'Generate synthetic assets'; recoveredItems = await this.recoverInsufficientAssets(error, assignments); success = recoveredItems > 0; break; case 'constraint_violation': strategy = 'constraint_relaxation'; action = 'Relax constraints'; success = await this.recoverConstraintViolation(error, assignments); recoveredItems = success ? 1 : 0; break; case 'distribution_failure': strategy = 'redistribution'; action = 'Redistribute assets'; success = await this.recoverDistributionFailure(error, assignments, originalAssets); recoveredItems = success ? 1 : 0; break; default: strategy = 'manual_intervention'; action = 'Manual intervention required'; success = false; errorMessage = 'Error type requires manual intervention'; } } catch (recoveryError) { success = false; errorMessage = recoveryError.message; } const executionTime = Date.now() - startTime; return { attemptNumber, strategy, action, success, errorMessage, recoveredItems, executionTime, confidence: success ? 85 : 0 }; } /** * Recover from insufficient assets error */ async recoverInsufficientAssets(error, assignments) { const targetId = error.context.targetId; if (!targetId) return 0; const assignment = assignments.find(a => a.target.id === targetId); if (!assignment) return 0; const minItems = assignment.target.constraints?.minItems || 0; const currentCount = assignment.assets.length; const needed = Math.max(0, minItems - currentCount); if (needed === 0) return 0; const fallbacks = await this.handleInsufficientAssets(assignment.target, assignment.assets, minItems); return fallbacks.length; } /** * Recover from constraint violation */ async recoverConstraintViolation(error, assignments) { if (!this.options.enableConstraintRelaxation) { return false; } const targetId = error.context.targetId; if (!targetId) return false; const assignment = assignments.find(a => a.target.id === targetId); if (!assignment || !assignment.target.constraints) return false; // Simple constraint relaxation - increase limits by 50% if (assignment.target.constraints.maxItems) { assignment.target.constraints.maxItems = Math.ceil(assignment.target.constraints.maxItems * 1.5); } if (assignment.target.constraints.minItems) { assignment.target.constraints.minItems = Math.max(1, Math.floor(assignment.target.constraints.minItems * 0.8)); } return true; } /** * Recover from distribution failure */ async recoverDistributionFailure(error, assignments, originalAssets) { // Simple redistribution - move unassigned assets to targets with capacity const availableTargets = assignments.filter(a => !a.target.constraints?.maxItems || a.assets.length < a.target.constraints.maxItems); if (availableTargets.length === 0) return false; // This is a simplified recovery - in practice would need more sophisticated logic return availableTargets.length > 0; } /** * Check if an assignment is fulfilled based on its constraints */ checkFulfillment(assignment) { const constraints = assignment.target.constraints; if (!constraints) return true; if (constraints.minItems && assignment.assets.length < constraints.minItems) { return false; } if (constraints.maxItems && assignment.assets.length > constraints.maxItems) { return false; } return true; } /** * Analyze distribution and enforcement results to identify recoverable errors */ analyzeErrors(distributionResult, enforcementResult) { const errors = []; // Convert unresolvable violations to errors for (const violation of enforcementResult.unresolvableViolations) { errors.push({ id: `violation-${violation.ruleId}-${Date.now()}`, type: this.mapViolationToErrorType(violation), severity: violation.severity === 'critical' ? 'critical' : violation.severity === 'error' ? 'high' : 'medium', message: violation.message, context: { targetId: violation.affectedTargets[0], assetIds: violation.affectedAssets, ruleName: violation.ruleName, operation: 'constraint_enforcement', timestamp: new Date() }, recoverable: this.isViolationRecoverable(violation), suggestedActions: violation.suggestedResolutions, metadata: violation.metadata }); } // Add general distribution errors if (distributionResult.unassigned.length > 0) { errors.push({ id: `unassigned-assets-${Date.now()}`, type: 'distribution_failure', severity: 'medium', message: `${distributionResult.unassigned.length} assets could not be assigned`, context: { assetIds: distributionResult.unassigned.map(a => a.id), operation: 'distribution', timestamp: new Date() }, recoverable: true, suggestedActions: ['Add more targets', 'Relax constraints', 'Generate additional capacity'], metadata: { unassignedCount: distributionResult.unassigned.length } }); } return errors; } /** * Map constraint violation to error type */ mapViolationToErrorType(violation) { switch (violation.violationType) { case 'insufficient': return 'asset_insufficient'; case 'invalid': return 'validation_error'; default: return 'constraint_violation'; } } /** * Check if a violation is recoverable */ isViolationRecoverable(violation) { return violation.violationType === 'insufficient' || violation.violationType === 'excess' || (violation.violationType === 'invalid' && this.options.enableConstraintRelaxation); } /** * Generate final recovery report */ generateFinalReport(assignments, fallbacks, attempts, errors) { const fulfilledTargets = assignments.filter(a => a.fulfilled).length; const assignedAssets = assignments.reduce((sum, a) => sum + a.assets.length, 0); return { ...this.currentProgress, phase: 'finalization', overallProgress: 100, currentStep: 'Recovery completed', statistics: { totalTargets: assignments.length, fulfilledTargets, totalAssets: assignedAssets, assignedAssets, generatedFallbacks: fallbacks.length, recoveryAttempts: attempts.length, executionTime: Date.now() - this.currentProgress.statistics.executionTime }, errors: errors, canContinue: errors.length === 0 }; } /** * Generate recovery recommendations */ generateRecoveryRecommendations(attempts, errors, fallbacks) { const recommendations = []; if (errors.length > 0) { recommendations.push(`${errors.length} errors remain unresolved - consider manual intervention`); } if (fallbacks.length > 10) { recommendations.push('High number of fallback assets generated - review asset pool quality'); } const failedAttempts = attempts.filter(a => !a.success).length; if (failedAttempts > attempts.length * 0.5) { recommendations.push('Many recovery attempts failed - review recovery strategy configuration'); } if (recommendations.length === 0) { recommendations.push('All errors successfully recovered - association process completed successfully'); } return recommendations; } /** * Infer the target type from constraints and existing assets */ inferTargetType(target, assets) { // Check constraints for type hints if (target.constraints?.requiredTypes && target.constraints.requiredTypes.length > 0) { return target.constraints.requiredTypes[0]; } // Infer from existing assets if (assets.length > 0) { const typeFreq = assets.reduce((freq, asset) => { freq[asset.type] = (freq[asset.type] || 0) + 1; return freq; }, {}); return Object.entries(typeFreq).sort(([, a], [, b]) => b - a)[0][0]; } return 'generic'; } /** * Get template preferences for a target */ getTemplatePreferences(target) { // This would analyze target metadata to suggest appropriate templates return ['default', 'basic']; } /** * Create initial progress report */ createInitialProgress() { return { operationId: `recovery-${Date.now()}`, phase: 'initialization', overallProgress: 0, currentStep: 'Initializing recovery process', completedSteps: [], remainingSteps: ['analyze_errors', 'attempt_recovery', 'generate_fallbacks', 'finalize'], errors: [], warnings: [], statistics: { totalTargets: 0, fulfilledTargets: 0, totalAssets: 0, assignedAssets: 0, generatedFallbacks: 0, recoveryAttempts: 0, executionTime: Date.now() }, canContinue: true }; } /** * Initialize built-in fallback strategies */ initializeBuiltInStrategies() { // Synthetic data generation strategy this.addFallbackStrategy({ id: 'synthetic-generator', name: 'Synthetic Data Generator', type: 'generate_synthetic', priority: 80, applicableTypes: ['*'], confidence: 70, costWeight: 1, generator: async (requirements) => { const fallbacks = []; for (let i = 0; i < requirements.requiredCount; i++) { const asset = { id: `synthetic-${Date.now()}-${i}`, filePath: `/synthetic/${requirements.targetType}_${i}.json`, type: this.normalizeAssetType(requirements.targetType), content: JSON.stringify({ title: `Synthetic ${requirements.targetType} ${i + 1}`, description: `Auto-generated ${requirements.targetType} for testing`, generated: new Date().toISOString() }), metadata: { filename: `synthetic_${requirements.targetType}_${i}`, title: `Synthetic ${requirements.targetType} ${i + 1}`, synthetic: true }, fileSize: 100, lastModified: new Date(), isValid: true, fallbackType: 'synthetic', generatedBy: 'synthetic-generator', fallbackReason: 'Insufficient assets for target requirements', confidence: 70 }; fallbacks.push(asset); } return fallbacks; } }); // Template-based generation strategy this.addFallbackStrategy({ id: 'template-generator', name: 'Template-Based Generator', type: 'use_template', priority: 90, applicableTypes: ['markdown', 'json'], confidence: 85, costWeight: 1.5, generator: async (requirements) => { // This would use predefined templates to generate assets // For now, return basic template-based assets const fallbacks = []; for (let i = 0; i < requirements.requiredCount; i++) { const asset = { id: `template-${Date.now()}-${i}`, filePath: `/templates/${requirements.targetType}_${i}.md`, type: this.normalizeAssetType(requirements.targetType), content: `# Template ${requirements.targetType}\n\nThis is a template-generated asset.`, metadata: { filename: `template_${requirements.targetType}_${i}`, title: `Template ${requirements.targetType} ${i + 1}`, template: true }, fileSize: 150, lastModified: new Date(), isValid: true, fallbackType: 'template', generatedBy: 'template-generator', sourceTemplate: 'basic-template', fallbackReason: 'Using template to meet asset requirements', confidence: 85 }; fallbacks.push(asset); } return fallbacks; } }); logger_1.Logger.info(`🔧 Initialized ${this.fallbackStrategies.size} built-in fallback strategies`); } /** * Add a custom fallback strategy */ addFallbackStrategy(strategy) { this.fallbackStrategies.set(strategy.id, strategy); logger_1.Logger.debug(`Added fallback strategy: ${strategy.name}`); } /** * Remove a fallback strategy */ removeFallbackStrategy(strategyId) { return this.fallbackStrategies.delete(strategyId); } /** * Get all available fallback strategies */ getFallbackStrategies() { return Array.from(this.fallbackStrategies.values()); } /** * Update error recovery options */ updateOptions(newOptions) { this.options = { ...this.options, ...newOptions }; logger_1.Logger.debug('Updated error recovery options'); } /** * Normalize asset type to ensure it's a valid LoadedAsset type */ normalizeAssetType(targetType) { switch (targetType.toLowerCase()) { case 'markdown': case 'md': return 'markdown'; case 'json': return 'json'; case 'image': case 'img': case 'png': case 'jpg': case 'jpeg': return 'image'; case 'csv': return 'csv'; default: return 'json'; // Default fallback type } } /** * Create a fallback error handler with default options */ static createDefault() { const defaultOptions = { enableFallbackGeneration: true, enableConstraintRelaxation: true, enablePartialFulfillment: true, maxRetryAttempts: 3, fallbackStrategies: [], recoveryPriority: 'completeness', reportingLevel: 'detailed' }; return new FallbackErrorHandler(defaultOptions); } } exports.FallbackErrorHandler = FallbackErrorHandler; //# sourceMappingURL=fallback-error-handling.js.map