aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
710 lines (613 loc) • 19.3 kB
JavaScript
const fs = require('fs').promises;
const path = require('path');
const chalk = require('chalk');
const crypto = require('crypto');
const SecurityChecker = require('./security-checker');
const DependencyManager = require('./dependency-manager');
/**
* Validates self-improvement requests and plans with comprehensive safety checks
*/
class ImprovementValidator {
constructor(options = {}) {
this.rootPath = options.rootPath || process.cwd();
this.security = new SecurityChecker();
this.dependencies = new DependencyManager();
// Protected files and patterns
this.protectedFiles = [
'bootstrap.js',
'index.js',
'security-checker.js',
'improvement-validator.js',
'rollback-handler.js',
'backup-manager.js',
];
this.protectedPatterns = [
/^\.git/,
/node_modules/,
/\.env/,
/config\/security/,
/backup\//,
];
// Improvement history for recursive detection
this.improvementHistoryFile = path.join(
this.rootPath,
'.aios',
'improvement-history.json',
);
// Safety thresholds
this.thresholds = {
maxFilesPerImprovement: options.maxFiles || 20,
maxImprovementDepth: options.maxDepth || 1,
maxRiskScore: options.maxRisk || 7,
minTestCoverage: options.minCoverage || 80,
maxComplexityIncrease: options.maxComplexity || 10,
};
}
/**
* Validate improvement request
* @param {Object} params - Request parameters
* @returns {Promise<Object>} Validation result
*/
async validateRequest(params) {
const { request, scope, constraints = {} } = params;
console.log(chalk.blue('🔍 Validating improvement request...'));
const validation = {
valid: true,
reason: null,
suggestions: [],
warnings: [],
risk_assessment: {},
};
try {
// Check request format
if (!request || typeof request !== 'string' || request.length < 10) {
validation.valid = false;
validation.reason = 'Invalid request format';
validation.suggestions.push('Provide a clear description of the desired improvement');
return validation;
}
// Check for recursive improvements
const recursiveCheck = await this.detectRecursiveImprovement(request);
if (recursiveCheck.isRecursive) {
validation.valid = false;
validation.reason = 'Recursive improvement detected';
validation.warnings.push(recursiveCheck.message);
return validation;
}
// Validate scope
if (scope === 'general' && !constraints.explicit_approval) {
validation.warnings.push('General improvements require explicit approval');
}
// Check improvement patterns
const patterns = this.analyzeRequestPatterns(request);
if (patterns.suspicious) {
validation.valid = false;
validation.reason = 'Suspicious improvement pattern detected';
validation.warnings.push(...patterns.warnings);
return validation;
}
// Validate constraints
const constraintValidation = this.validateConstraints(constraints);
if (!constraintValidation.valid) {
validation.valid = false;
validation.reason = constraintValidation.reason;
return validation;
}
// Risk assessment
validation.risk_assessment = this.assessRequestRisk(request, scope);
if (validation.risk_assessment.score > this.thresholds.maxRiskScore) {
validation.valid = false;
validation.reason = 'Risk score exceeds threshold';
validation.warnings.push(`Risk score: ${validation.risk_assessment.score}/10`);
}
} catch (error) {
validation.valid = false;
validation.reason = `Validation error: ${error.message}`;
}
return validation;
}
/**
* Detect recursive improvement attempts
* @private
*/
async detectRecursiveImprovement(request) {
try {
// Load improvement history
const history = await this.loadImprovementHistory();
// Check current improvement depth
const currentDepth = this.getCurrentImprovementDepth();
if (currentDepth >= this.thresholds.maxImprovementDepth) {
return {
isRecursive: true,
message: `Maximum improvement depth (${this.thresholds.maxImprovementDepth}) reached`,
depth: currentDepth,
};
}
// Generate request fingerprint
const fingerprint = this.generateRequestFingerprint(request);
// Check for similar recent improvements
const recentImprovements = history.improvements.filter(imp => {
const ageInHours = (Date.now() - new Date(imp.timestamp)) / (1000 * 60 * 60);
return ageInHours < 24;
});
for (const improvement of recentImprovements) {
const similarity = this.calculateSimilarity(fingerprint, improvement.fingerprint);
if (similarity > 0.8) {
return {
isRecursive: true,
message: `Similar improvement attempted ${improvement.timestamp}`,
similarity,
previousId: improvement.id,
};
}
}
// Check for self-referential improvements
if (this.isSelfReferential(request)) {
return {
isRecursive: true,
message: 'Self-referential improvement detected',
pattern: 'self-modification of improvement system',
};
}
// Record this improvement attempt
await this.recordImprovementAttempt({
fingerprint,
request,
timestamp: new Date().toISOString(),
});
return { isRecursive: false };
} catch (error) {
console.error(chalk.red(`Recursive detection error: ${error.message}`));
// Fail safe - prevent improvement if detection fails
return {
isRecursive: true,
message: 'Could not verify non-recursive nature',
error: error.message,
};
}
}
/**
* Validate safety of improvement plan
* @param {Object} plan - Improvement plan
* @returns {Promise<Object>} Safety validation result
*/
async validateSafety(plan) {
console.log(chalk.blue('🛡️ Validating improvement safety...'));
const safety = {
safe: true,
risk_level: 'low',
risks: [],
mitigations: [],
interface_preserved: true,
breaking_changes: [],
};
try {
// Check protected files
for (const file of plan.affectedFiles) {
if (this.isProtectedFile(file)) {
safety.safe = false;
safety.risks.push({
type: 'protected_file',
file,
severity: 'critical',
message: `Cannot modify protected file: ${file}`,
});
}
}
// Validate each change
for (const change of plan.changes) {
const changeValidation = await this.validateChange(change);
if (!changeValidation.safe) {
safety.safe = false;
safety.risks.push(...changeValidation.risks);
}
if (changeValidation.breaking_changes.length > 0) {
safety.interface_preserved = false;
safety.breaking_changes.push(...changeValidation.breaking_changes);
}
}
// Check dependency impacts
const depImpact = await this.assessDependencyImpact(plan);
if (depImpact.hasIssues) {
safety.risks.push({
type: 'dependency_impact',
severity: 'medium',
message: 'Changes may affect dependent components',
details: depImpact.issues,
});
}
// Security validation
const securityCheck = await this.performSecurityCheck(plan);
if (!securityCheck.safe) {
safety.safe = false;
safety.risks.push(...securityCheck.risks);
}
// Calculate overall risk level
safety.risk_level = this.calculateRiskLevel(safety.risks);
// Generate mitigations
if (safety.risks.length > 0) {
safety.mitigations = this.generateMitigations(safety.risks);
}
} catch (error) {
safety.safe = false;
safety.risks.push({
type: 'validation_error',
severity: 'high',
message: error.message,
});
}
return safety;
}
/**
* Validate individual change
* @private
*/
async validateChange(change) {
const validation = {
safe: true,
risks: [],
breaking_changes: [],
};
// Check modification types
for (const mod of change.modifications) {
switch (mod.type) {
case 'api_change':
validation.breaking_changes.push({
type: 'api_change',
description: mod.description,
impact: 'Existing integrations may break',
});
break;
case 'signature_change':
validation.breaking_changes.push({
type: 'signature_change',
function: mod.function,
impact: 'Callers must be updated',
});
break;
case 'config_format_change':
validation.risks.push({
type: 'config_change',
severity: 'medium',
message: 'Configuration format changes require migration',
});
break;
}
}
// Validate test coverage
if (!change.tests || change.tests.length === 0) {
validation.risks.push({
type: 'missing_tests',
severity: 'medium',
message: 'No tests specified for changes',
});
}
return validation;
}
/**
* Load improvement history
* @private
*/
async loadImprovementHistory() {
try {
const content = await fs.readFile(this.improvementHistoryFile, 'utf-8');
return JSON.parse(content);
} catch (error) {
// Initialize if doesn't exist
const initialHistory = {
version: '1.0.0',
improvements: [],
statistics: {
total_attempts: 0,
successful: 0,
failed: 0,
rolled_back: 0,
},
};
await this.saveImprovementHistory(initialHistory);
return initialHistory;
}
}
/**
* Save improvement history
* @private
*/
async saveImprovementHistory(history) {
const dir = path.dirname(this.improvementHistoryFile);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(
this.improvementHistoryFile,
JSON.stringify(history, null, 2),
);
}
/**
* Record improvement attempt
* @private
*/
async recordImprovementAttempt(attempt) {
const history = await this.loadImprovementHistory();
history.improvements.push({
id: `imp-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
...attempt,
depth: this.getCurrentImprovementDepth(),
});
history.statistics.total_attempts++;
// Keep only last 100 improvements
if (history.improvements.length > 100) {
history.improvements = history.improvements.slice(-100);
}
await this.saveImprovementHistory(history);
}
/**
* Get current improvement depth
* @private
*/
getCurrentImprovementDepth() {
// Check if we're running within an improvement context
const depth = process.env.AIOS_IMPROVEMENT_DEPTH || '0';
return parseInt(depth, 10);
}
/**
* Generate request fingerprint
* @private
*/
generateRequestFingerprint(request) {
const normalized = request.toLowerCase()
.replace(/\s+/g, ' ')
.trim();
const hash = crypto.createHash('sha256')
.update(normalized)
.digest('hex');
// Extract key features
const features = {
hash,
length: request.length,
keywords: this.extractKeywords(normalized),
patterns: this.extractPatterns(normalized),
};
return features;
}
/**
* Calculate similarity between fingerprints
* @private
*/
calculateSimilarity(fp1, fp2) {
// Hash similarity
if (fp1.hash === fp2.hash) return 1.0;
// Keyword overlap
const keywords1 = new Set(fp1.keywords);
const keywords2 = new Set(fp2.keywords);
const intersection = [...keywords1].filter(k => keywords2.has(k));
const union = new Set([...keywords1, ...keywords2]);
const keywordSimilarity = union.size > 0
? intersection.length / union.size
: 0;
// Length similarity
const lengthSimilarity = 1 - Math.abs(fp1.length - fp2.length) / Math.max(fp1.length, fp2.length);
// Weighted combination
return (keywordSimilarity * 0.7) + (lengthSimilarity * 0.3);
}
/**
* Check if request is self-referential
* @private
*/
isSelfReferential(request) {
const selfPatterns = [
/improve.*improvement.*system/i,
/modify.*validator/i,
/change.*safety.*check/i,
/update.*recursive.*detection/i,
/enhance.*self.*modification/i,
];
return selfPatterns.some(pattern => pattern.test(request));
}
/**
* Analyze request patterns
* @private
*/
analyzeRequestPatterns(request) {
const analysis = {
suspicious: false,
warnings: [],
};
// Check for dangerous patterns
const dangerous = [
{ pattern: /disable.*safety/i, message: 'Attempting to disable safety features' },
{ pattern: /bypass.*validation/i, message: 'Attempting to bypass validation' },
{ pattern: /remove.*check/i, message: 'Attempting to remove checks' },
{ pattern: /unlimited|infinite|no.*limit/i, message: 'Attempting to remove limits' },
];
for (const check of dangerous) {
if (check.pattern.test(request)) {
analysis.suspicious = true;
analysis.warnings.push(check.message);
}
}
return analysis;
}
/**
* Validate constraints
* @private
*/
validateConstraints(constraints) {
const validation = { valid: true };
if (constraints.max_files && constraints.max_files > this.thresholds.maxFilesPerImprovement) {
validation.valid = false;
validation.reason = `Max files exceeds limit (${this.thresholds.maxFilesPerImprovement})`;
}
if (constraints.preserve_interfaces === false) {
validation.valid = false;
validation.reason = 'Interface preservation is mandatory';
}
return validation;
}
/**
* Assess request risk
* @private
*/
assessRequestRisk(request, scope) {
let score = 0;
const factors = [];
// Scope risk
if (scope === 'general') {
score += 3;
factors.push({ factor: 'general_scope', points: 3 });
}
// Pattern risk
const riskPatterns = [
{ pattern: /core|critical|system/i, points: 2 },
{ pattern: /all|every|entire/i, points: 2 },
{ pattern: /refactor|rewrite|redesign/i, points: 3 },
{ pattern: /performance|optimize/i, points: 1 },
{ pattern: /security|auth/i, points: 2 },
];
for (const risk of riskPatterns) {
if (risk.pattern.test(request)) {
score += risk.points;
factors.push({
factor: risk.pattern.source,
points: risk.points,
});
}
}
return { score, factors };
}
/**
* Check if file is protected
* @private
*/
isProtectedFile(file) {
const filename = path.basename(file);
// Check exact matches
if (this.protectedFiles.includes(filename)) {
return true;
}
// Check patterns
return this.protectedPatterns.some(pattern => pattern.test(file));
}
/**
* Assess dependency impact
* @private
*/
async assessDependencyImpact(plan) {
const impact = {
hasIssues: false,
issues: [],
};
try {
for (const file of plan.affectedFiles) {
const deps = await this.dependencies.getDependents(file);
if (deps.length > 0) {
impact.hasIssues = true;
impact.issues.push({
file,
dependents: deps.length,
samples: deps.slice(0, 3),
});
}
}
} catch (error) {
console.warn(`Dependency check failed: ${error.message}`);
}
return impact;
}
/**
* Perform security check
* @private
*/
async performSecurityCheck(plan) {
const check = {
safe: true,
risks: [],
};
// Check for security-sensitive modifications
for (const change of plan.changes) {
for (const mod of change.modifications) {
if (mod.type === 'auth_change' || mod.type === 'permission_change') {
check.safe = false;
check.risks.push({
type: 'security_modification',
severity: 'critical',
message: 'Changes affect security components',
detail: mod.description,
});
}
}
}
return check;
}
/**
* Calculate overall risk level
* @private
*/
calculateRiskLevel(risks) {
if (risks.some(r => r.severity === 'critical')) return 'critical';
if (risks.filter(r => r.severity === 'high').length > 1) return 'high';
if (risks.some(r => r.severity === 'high')) return 'medium';
if (risks.length > 3) return 'medium';
return 'low';
}
/**
* Generate risk mitigations
* @private
*/
generateMitigations(risks) {
const mitigations = [];
for (const risk of risks) {
switch (risk.type) {
case 'protected_file':
mitigations.push({
risk: risk.type,
mitigation: 'Create a copy of the protected file for modifications',
action: 'copy_and_modify',
});
break;
case 'missing_tests':
mitigations.push({
risk: risk.type,
mitigation: 'Generate comprehensive test suite before applying changes',
action: 'generate_tests',
});
break;
case 'dependency_impact':
mitigations.push({
risk: risk.type,
mitigation: 'Update dependent components to handle changes',
action: 'update_dependents',
});
break;
default:
mitigations.push({
risk: risk.type,
mitigation: 'Manual review required',
action: 'manual_review',
});
}
}
return mitigations;
}
/**
* Extract keywords from text
* @private
*/
extractKeywords(text) {
const stopWords = new Set(['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for']);
const words = text.split(/\s+/)
.filter(w => w.length > 3 && !stopWords.has(w));
return [...new Set(words)];
}
/**
* Extract patterns from text
* @private
*/
extractPatterns(text) {
const patterns = [];
// Action patterns
if (/improve|enhance|optimize/.test(text)) patterns.push('improvement');
if (/fix|repair|correct/.test(text)) patterns.push('bugfix');
if (/add|create|implement/.test(text)) patterns.push('feature');
if (/refactor|reorganize|restructure/.test(text)) patterns.push('refactor');
return patterns;
}
}
module.exports = ImprovementValidator;