supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
636 lines • 28.7 kB
JavaScript
;
/**
* Constraint Enforcement System for Association Intelligence
* Phase 3, Checkpoint C2 - Advanced constraint validation and conflict resolution
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConstraintEnforcementEngine = void 0;
const logger_1 = require("../core/utils/logger");
class ConstraintEnforcementEngine {
constructor(config) {
this.rules = new Map();
this.config = config;
this.initializeBuiltInRules();
}
/**
* Add a custom constraint rule to the enforcement engine
*/
addRule(rule) {
this.rules.set(rule.id, rule);
logger_1.Logger.debug(`Added constraint rule: ${rule.name} (${rule.id})`);
}
/**
* Remove a constraint rule from the enforcement engine
*/
removeRule(ruleId) {
const removed = this.rules.delete(ruleId);
if (removed) {
logger_1.Logger.debug(`Removed constraint rule: ${ruleId}`);
}
return removed;
}
/**
* Validate all assignments against constraint rules
*/
validateAssignments(assignments, allAssets) {
const startTime = Date.now();
const violations = [];
const warnings = [];
const rulesApplied = [];
let assetsEvaluated = 0;
logger_1.Logger.info(`🔍 Validating ${assignments.length} assignments against ${this.rules.size} constraint rules`);
const context = {
allAssignments: assignments,
allAssets,
globalConfig: this.config,
metadata: {}
};
// Sort rules by priority for evaluation order
const sortedRules = Array.from(this.rules.values()).sort((a, b) => {
const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
for (const rule of sortedRules) {
try {
for (const assignment of assignments) {
const result = rule.validator(assignment, context);
if (!result.isValid) {
violations.push(...result.violations);
}
warnings.push(...result.warnings);
assetsEvaluated += result.metadata.assetsEvaluated;
}
rulesApplied.push(rule.id);
}
catch (error) {
logger_1.Logger.warn(`Constraint rule ${rule.id} failed: ${error.message}`);
warnings.push({
ruleId: rule.id,
message: `Rule evaluation failed: ${error.message}`,
targets: [],
recommendation: 'Review rule configuration'
});
}
}
const executionTime = Date.now() - startTime;
logger_1.Logger.info(`✅ Constraint validation complete: ${violations.length} violations, ${warnings.length} warnings (${executionTime}ms)`);
return {
isValid: violations.length === 0,
violations,
warnings,
metadata: {
executionTime,
assetsEvaluated,
rulesApplied
}
};
}
/**
* Enforce constraints by resolving violations
*/
enforceConstraints(assignments, allAssets) {
const startTime = Date.now();
let currentAssignments = [...assignments];
const appliedResolutions = [];
const unresolvableViolations = [];
logger_1.Logger.info(`⚖️ Enforcing constraints on ${assignments.length} assignments`);
// Initial validation
let validationResult = this.validateAssignments(currentAssignments, allAssets);
let resolutionAttempts = 0;
while (validationResult.violations.length > 0 && resolutionAttempts < this.config.maxResolutionAttempts) {
resolutionAttempts++;
logger_1.Logger.debug(`Constraint resolution attempt ${resolutionAttempts}/${this.config.maxResolutionAttempts}`);
const resolutionContext = {
allAssignments: currentAssignments,
allAssets,
globalConfig: this.config,
metadata: {},
conflictHistory: unresolvableViolations,
resolutionAttempts,
maxRetries: this.config.maxResolutionAttempts
};
let resolvedAny = false;
// Attempt to resolve violations in priority order
const sortedViolations = validationResult.violations.sort((a, b) => {
const severityOrder = { critical: 3, error: 2, warning: 1 };
return severityOrder[b.severity] - severityOrder[a.severity];
});
for (const violation of sortedViolations) {
const rule = this.rules.get(violation.ruleId);
if (rule?.conflictResolver) {
try {
const resolution = rule.conflictResolver(violation, resolutionContext);
if (resolution && resolution.confidence >= 70) { // Only apply high-confidence resolutions
const success = this.applyResolution(resolution, currentAssignments, allAssets);
if (success) {
appliedResolutions.push(resolution);
resolvedAny = true;
logger_1.Logger.debug(`Applied resolution: ${resolution.strategy} for rule ${violation.ruleId}`);
}
}
else {
unresolvableViolations.push(violation);
}
}
catch (error) {
logger_1.Logger.warn(`Resolution failed for rule ${violation.ruleId}: ${error.message}`);
unresolvableViolations.push(violation);
}
}
else {
// Try default resolution strategies
const defaultResolution = this.getDefaultResolution(violation, resolutionContext);
if (defaultResolution) {
const success = this.applyResolution(defaultResolution, currentAssignments, allAssets);
if (success) {
appliedResolutions.push(defaultResolution);
resolvedAny = true;
}
}
else {
unresolvableViolations.push(violation);
}
}
}
if (!resolvedAny) {
logger_1.Logger.warn('No progress made in constraint resolution, stopping attempts');
break;
}
// Re-validate after applying resolutions
validationResult = this.validateAssignments(currentAssignments, allAssets);
}
const executionTime = Date.now() - startTime;
const resolvedViolations = assignments.length > 0 ?
Math.max(0, validationResult.violations.length - unresolvableViolations.length) : 0;
const enforcementReport = this.generateEnforcementReport(validationResult, appliedResolutions, unresolvableViolations, executionTime, currentAssignments.length, new Set(currentAssignments.map(a => a.target.id)).size);
logger_1.Logger.info(`⚖️ Constraint enforcement complete: ${resolvedViolations} resolved, ${unresolvableViolations.length} unresolvable`);
return {
success: unresolvableViolations.length === 0,
resolvedViolations,
unresolvableViolations,
appliedResolutions,
finalAssignments: currentAssignments,
enforcementReport
};
}
/**
* Apply a constraint resolution to the assignments
*/
applyResolution(resolution, assignments, allAssets) {
try {
for (const action of resolution.actions) {
const success = this.executeResolutionAction(action, assignments, allAssets);
if (!success) {
logger_1.Logger.warn(`Failed to execute resolution action: ${action.type}`);
return false;
}
}
return true;
}
catch (error) {
logger_1.Logger.error(`Error applying resolution: ${error.message}`);
return false;
}
}
/**
* Execute a specific resolution action
*/
executeResolutionAction(action, assignments, allAssets) {
switch (action.type) {
case 'move_asset':
return this.moveAsset(action.parameters, assignments);
case 'generate_fallback':
return this.generateFallback(action.parameters, assignments, allAssets);
case 'modify_constraint':
return this.modifyConstraint(action.parameters, assignments);
case 'create_target':
return this.createTarget(action.parameters, assignments);
case 'remove_constraint':
return this.removeConstraintFromTarget(action.parameters, assignments);
default:
logger_1.Logger.warn(`Unknown resolution action type: ${action.type}`);
return false;
}
}
/**
* Move an asset from one target to another
*/
moveAsset(params, assignments) {
const { assetId, fromTargetId, toTargetId } = params;
const fromAssignment = assignments.find(a => a.target.id === fromTargetId);
const toAssignment = assignments.find(a => a.target.id === toTargetId);
if (!fromAssignment || !toAssignment) {
return false;
}
const assetIndex = fromAssignment.assets.findIndex(a => a.id === assetId);
if (assetIndex === -1) {
return false;
}
const asset = fromAssignment.assets.splice(assetIndex, 1)[0];
toAssignment.assets.push(asset);
return true;
}
/**
* Generate fallback assets when insufficient assets are available
*/
generateFallback(params, assignments, allAssets) {
// This would integrate with fallback generation system
// For now, we'll just mark that fallback is needed
const { targetId, count, assetType } = params;
const assignment = assignments.find(a => a.target.id === targetId);
if (!assignment)
return false;
// Mark assignment as needing fallback generation
assignment.assignmentReason = `fallback_needed_${count}_${assetType}`;
return true;
}
/**
* Modify constraint parameters for a target
*/
modifyConstraint(params, assignments) {
const { targetId, constraintType, newValue } = params;
const assignment = assignments.find(a => a.target.id === targetId);
if (!assignment || !assignment.target.constraints)
return false;
// Modify the constraint
assignment.target.constraints[constraintType] = newValue;
return true;
}
/**
* Create a new target to accommodate excess assets
*/
createTarget(params, assignments) {
const { targetId, targetName, constraints } = params;
const newTarget = {
id: targetId,
name: targetName,
constraints: constraints || {}
};
const newAssignment = {
target: newTarget,
assets: [],
fulfilled: false,
constraintViolations: [],
assignmentReason: 'created_for_resolution'
};
assignments.push(newAssignment);
return true;
}
/**
* Remove a specific constraint from a target
*/
removeConstraintFromTarget(params, assignments) {
const { targetId, constraintName } = params;
const assignment = assignments.find(a => a.target.id === targetId);
if (!assignment || !assignment.target.constraints)
return false;
delete assignment.target.constraints[constraintName];
return true;
}
/**
* Get default resolution strategy for a violation
*/
getDefaultResolution(violation, context) {
switch (violation.violationType) {
case 'insufficient':
return {
strategy: 'add_fallback',
description: 'Generate fallback assets to meet minimum requirements',
actions: [{
type: 'generate_fallback',
description: 'Create synthetic assets',
parameters: {
targetId: violation.affectedTargets[0],
count: 1,
assetType: 'generic'
},
reversible: true
}],
confidence: 80,
impact: 'low'
};
case 'excess':
// Find another target that can accept the asset
const potentialTargets = context.allAssignments.filter(a => a.target.id !== violation.affectedTargets[0] &&
(!a.target.constraints?.maxItems || a.assets.length < a.target.constraints.maxItems));
if (potentialTargets.length > 0) {
return {
strategy: 'redistribute',
description: 'Redistribute excess assets to other targets',
actions: violation.affectedAssets.slice(0, violation.metadata.excess).map(assetId => ({
type: 'move_asset',
description: `Move asset ${assetId} to another target`,
parameters: {
assetId,
fromTargetId: violation.affectedTargets[0],
toTargetId: potentialTargets[0].target.id
},
reversible: true
})),
confidence: 75,
impact: 'low'
};
}
else {
return {
strategy: 'relax_constraint',
description: 'No targets available for redistribution, relax maximum constraint',
actions: [{
type: 'modify_constraint',
description: 'Increase maximum items limit',
parameters: {
targetId: violation.affectedTargets[0],
constraintType: 'maxItems',
newValue: violation.metadata.actual
},
reversible: true
}],
confidence: 50,
impact: 'medium'
};
}
case 'invalid':
if (this.config.enforcementLevel === 'permissive') {
return {
strategy: 'relax_constraint',
description: 'Relax constraint to allow current assignment',
actions: [{
type: 'modify_constraint',
description: 'Adjust constraint parameters',
parameters: {
targetId: violation.affectedTargets[0],
constraintType: 'validation',
newValue: false
},
reversible: true
}],
confidence: 60,
impact: 'medium'
};
}
break;
}
return null;
}
/**
* Generate comprehensive enforcement report
*/
generateEnforcementReport(validationResult, appliedResolutions, unresolvableViolations, executionTime, assetsAffected, targetsAffected) {
const totalViolations = validationResult.violations.length;
const resolvedViolations = totalViolations - unresolvableViolations.length;
const violationsByType = {};
validationResult.violations.forEach(v => {
violationsByType[v.violationType] = (violationsByType[v.violationType] || 0) + 1;
});
const resolutionsByStrategy = {};
appliedResolutions.forEach(r => {
resolutionsByStrategy[r.strategy] = (resolutionsByStrategy[r.strategy] || 0) + 1;
});
const recommendations = [];
if (unresolvableViolations.length > 0) {
recommendations.push('Consider relaxing constraints or adding more assets to resolve remaining violations');
}
if (appliedResolutions.length > totalViolations * 0.5) {
recommendations.push('High number of resolutions applied - review constraint configuration');
}
const successRate = totalViolations > 0 ? (resolvedViolations / totalViolations) * 100 : 100;
return {
summary: `Constraint enforcement completed with ${successRate.toFixed(1)}% success rate`,
statistics: {
totalRulesEvaluated: this.rules.size,
violationsFound: totalViolations,
violationsResolved: resolvedViolations,
resolutionSuccessRate: successRate,
executionTime,
assetsAffected,
targetsAffected
},
violationsByType,
resolutionsByStrategy,
recommendations,
performanceMetrics: {
validationTime: validationResult.metadata.executionTime,
resolutionTime: executionTime - validationResult.metadata.executionTime,
memoryUsage: process.memoryUsage().heapUsed / 1024 / 1024 // MB
}
};
}
/**
* Initialize built-in constraint rules
*/
initializeBuiltInRules() {
// Minimum items constraint
this.addRule({
id: 'min-items-constraint',
name: 'Minimum Items Required',
type: 'count',
priority: 'high',
description: 'Ensures targets receive at least the minimum number of required items',
validator: (assignment, context) => {
const startTime = Date.now();
const violations = [];
const warnings = [];
const minItems = assignment.target.constraints?.minItems;
if (minItems && assignment.assets.length < minItems) {
violations.push({
ruleId: 'min-items-constraint',
ruleName: 'Minimum Items Required',
severity: 'error',
message: `Target ${assignment.target.id} has ${assignment.assets.length} items but requires minimum ${minItems}`,
affectedTargets: [assignment.target.id],
affectedAssets: assignment.assets.map(a => a.id),
violationType: 'insufficient',
suggestedResolutions: ['Generate fallback assets', 'Redistribute from other targets', 'Relax constraint'],
metadata: { required: minItems, actual: assignment.assets.length }
});
}
return {
isValid: violations.length === 0,
violations,
warnings,
metadata: {
executionTime: Date.now() - startTime,
assetsEvaluated: assignment.assets.length,
rulesApplied: ['min-items-constraint']
}
};
},
conflictResolver: (violation, context) => {
// Only offer fallback generation if it's enabled in config
if (context.globalConfig.fallbackGeneration) {
return {
strategy: 'add_fallback',
description: 'Generate synthetic assets to meet minimum requirement',
actions: [{
type: 'generate_fallback',
description: `Generate ${violation.metadata.required - violation.metadata.actual} fallback assets`,
parameters: {
targetId: violation.affectedTargets[0],
count: violation.metadata.required - violation.metadata.actual,
assetType: 'synthetic'
},
reversible: true
}],
confidence: 85,
impact: 'low'
};
}
else {
// Return null to indicate no resolution available
return null;
}
}
});
// Maximum items constraint
this.addRule({
id: 'max-items-constraint',
name: 'Maximum Items Limit',
type: 'count',
priority: 'medium',
description: 'Ensures targets do not exceed the maximum number of allowed items',
validator: (assignment, context) => {
const startTime = Date.now();
const violations = [];
const warnings = [];
const maxItems = assignment.target.constraints?.maxItems;
if (maxItems && assignment.assets.length > maxItems) {
violations.push({
ruleId: 'max-items-constraint',
ruleName: 'Maximum Items Limit',
severity: 'error',
message: `Target ${assignment.target.id} has ${assignment.assets.length} items but allows maximum ${maxItems}`,
affectedTargets: [assignment.target.id],
affectedAssets: assignment.assets.slice(maxItems).map(a => a.id),
violationType: 'excess',
suggestedResolutions: ['Move excess assets to other targets', 'Increase maximum limit', 'Remove excess assets'],
metadata: { limit: maxItems, actual: assignment.assets.length, excess: assignment.assets.length - maxItems }
});
}
return {
isValid: violations.length === 0,
violations,
warnings,
metadata: {
executionTime: Date.now() - startTime,
assetsEvaluated: assignment.assets.length,
rulesApplied: ['max-items-constraint']
}
};
},
conflictResolver: (violation, context) => ({
strategy: 'redistribute',
description: 'Redistribute excess assets to other targets',
actions: violation.affectedAssets.slice(0, violation.metadata.excess).map(assetId => ({
type: 'move_asset',
description: `Move asset ${assetId} to another target`,
parameters: {
assetId,
fromTargetId: violation.affectedTargets[0]
},
reversible: true
})),
confidence: 90,
impact: 'low'
})
});
// Asset type compatibility constraint
this.addRule({
id: 'asset-type-constraint',
name: 'Asset Type Compatibility',
type: 'content',
priority: 'medium',
description: 'Ensures targets only receive compatible asset types',
validator: (assignment, context) => {
const startTime = Date.now();
const violations = [];
const warnings = [];
const requiredTypes = assignment.target.constraints?.requiredTypes;
const excludedTypes = assignment.target.constraints?.excludedTypes;
if (requiredTypes) {
const incompatibleAssets = assignment.assets.filter(asset => !requiredTypes.includes(asset.type));
if (incompatibleAssets.length > 0) {
violations.push({
ruleId: 'asset-type-constraint',
ruleName: 'Asset Type Compatibility',
severity: 'error',
message: `Target ${assignment.target.id} has ${incompatibleAssets.length} assets with incompatible types`,
affectedTargets: [assignment.target.id],
affectedAssets: incompatibleAssets.map(a => a.id),
violationType: 'invalid',
suggestedResolutions: ['Remove incompatible assets', 'Update required types', 'Transform asset types'],
metadata: { requiredTypes, incompatibleTypes: incompatibleAssets.map(a => a.type) }
});
}
}
if (excludedTypes) {
const excludedAssets = assignment.assets.filter(asset => excludedTypes.includes(asset.type));
if (excludedAssets.length > 0) {
violations.push({
ruleId: 'asset-type-constraint',
ruleName: 'Asset Type Compatibility',
severity: 'error',
message: `Target ${assignment.target.id} has ${excludedAssets.length} assets with excluded types`,
affectedTargets: [assignment.target.id],
affectedAssets: excludedAssets.map(a => a.id),
violationType: 'invalid',
suggestedResolutions: ['Remove excluded assets', 'Update excluded types', 'Move to compatible targets'],
metadata: { excludedTypes, foundTypes: excludedAssets.map(a => a.type) }
});
}
}
return {
isValid: violations.length === 0,
violations,
warnings,
metadata: {
executionTime: Date.now() - startTime,
assetsEvaluated: assignment.assets.length,
rulesApplied: ['asset-type-constraint']
}
};
}
});
logger_1.Logger.info(`🔧 Initialized ${this.rules.size} built-in constraint rules`);
}
/**
* Get all available constraint rules
*/
getRules() {
return Array.from(this.rules.values());
}
/**
* Get constraint rule by ID
*/
getRule(ruleId) {
return this.rules.get(ruleId);
}
/**
* Update constraint configuration
*/
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
logger_1.Logger.debug('Updated constraint enforcement configuration');
}
/**
* Create a constraint enforcement engine with default configuration
*/
static createDefault() {
const defaultConfig = {
enforcementLevel: 'balanced',
allowPartialFulfillment: true,
autoResolveConflicts: true,
maxResolutionAttempts: 3,
fallbackGeneration: true,
priorityWeighting: {
critical: 10,
high: 5,
medium: 2,
low: 1
},
customResolvers: {}
};
return new ConstraintEnforcementEngine(defaultConfig);
}
}
exports.ConstraintEnforcementEngine = ConstraintEnforcementEngine;
//# sourceMappingURL=constraint-enforcement.js.map