UNPKG

router-mcp-server

Version:

Execution engine for GAFF workflows - executes intent graphs with memory-backed state management, parallel execution, and HITL support

228 lines 7.99 kB
/** * Quality & Safety Hooks for Router * * Provides pre-execution safety validation and post-execution quality checks * with automatic rerun capabilities. */ import { spawn } from 'child_process'; /** * Pre-execution safety validation */ export async function validateSafetyPre(inputData, safetyReqs, orchestrationCard) { const errors = []; if (!safetyReqs.enabled) { return { passed: true, errors: [] }; } // Input validation if (safetyReqs.input_validation) { const dataSize = JSON.stringify(inputData).length; const maxSize = safetyReqs.input_validation.max_size_bytes || 1000000; if (dataSize > maxSize) { errors.push(`Input size ${dataSize} bytes exceeds maximum ${maxSize} bytes`); } // Check required fields if (safetyReqs.input_validation.required_fields) { for (const field of safetyReqs.input_validation.required_fields) { if (!inputData[field]) { errors.push(`Required input field missing: ${field}`); } } } } // Compliance validation if (safetyReqs.compliance_standards && safetyReqs.compliance_standards.length > 0) { try { const complianceResult = await callSafetyMCP('validate_compliance', { orchestration_card: orchestrationCard || {}, compliance_requirements: safetyReqs.compliance_standards }); if (!complianceResult.is_compliant) { errors.push(...complianceResult.violations.map((v) => `Compliance violation: ${v}`)); } } catch (error) { console.error('[Safety] Compliance check failed:', error); errors.push(`Compliance check error: ${error}`); } } return { passed: errors.length === 0, errors, sanitized_data: safetyReqs.input_validation?.sanitize_input ? inputData : undefined }; } /** * Post-execution quality validation */ export async function validateQualityPost(executionResult, qualityReqs, intentGraph) { if (!qualityReqs.enabled || !qualityReqs.auto_validate) { return { is_acceptable: true, quality_score: 1.0, rerun_required: false, issues: [] }; } try { const validationResult = await callQualityMCP('validate_execution_result', { execution_result: executionResult, quality_criteria: { accuracy_threshold: qualityReqs.accuracy_threshold || 0.85, completeness_required: qualityReqs.completeness_required !== false, required_fields: qualityReqs.required_fields || [] }, intent_graph: intentGraph }); return { is_acceptable: validationResult.is_acceptable || false, quality_score: validationResult.quality_score || 0, rerun_required: validationResult.rerun_required || false, rerun_nodes: validationResult.rerun_nodes || [], issues: validationResult.issues || [] }; } catch (error) { console.error('[Quality] Validation failed:', error); return { is_acceptable: false, quality_score: 0, rerun_required: false, issues: [{ type: 'validation_error', message: String(error) }] }; } } /** * Post-execution safety validation (output) */ export async function validateSafetyPost(outputData, safetyReqs) { const errors = []; if (!safetyReqs.enabled || !safetyReqs.output_validation) { return { passed: true, errors: [] }; } try { const validationResult = await callSafetyMCP('validate_output', { output_data: outputData, validation_rules: safetyReqs.output_validation }); if (!validationResult.is_valid) { errors.push(...validationResult.errors.map((e) => `Output validation: ${e}`)); } return { passed: errors.length === 0, errors, sanitized_data: validationResult.sanitized_output }; } catch (error) { console.error('[Safety] Output validation failed:', error); return { passed: false, errors: [`Output validation error: ${error}`] }; } } /** * Create audit log entry */ export async function logAuditEntry(executionId, userId, qualityScore, complianceStandards) { try { await callSafetyMCP('audit_log', { event_type: 'workflow_execution', user_id: userId, action: 'execute_intent_graph', metadata: { execution_id: executionId, quality_score: qualityScore, compliance_standards: complianceStandards, timestamp: new Date().toISOString() } }); } catch (error) { console.error('[Safety] Audit logging failed:', error); } } /** * Helper: Call quality-check MCP server */ async function callQualityMCP(toolName, args) { return callMCPServer('quality-check-mcp-server', toolName, args); } /** * Helper: Call safety-protocols MCP server */ async function callSafetyMCP(toolName, args) { return callMCPServer('safety-protocols-mcp-server', toolName, args); } /** * Generic MCP server call via npx */ async function callMCPServer(packageName, toolName, args) { return new Promise((resolve, reject) => { const child = spawn('npx', ['-y', packageName], { stdio: ['pipe', 'pipe', 'pipe'], shell: true, env: process.env }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.on('error', (error) => { reject(new Error(`Failed to spawn ${packageName}: ${error.message}`)); }); child.on('close', (code) => { if (code !== 0) { reject(new Error(`${packageName} exited with code ${code}: ${stderr}`)); return; } try { // Parse JSON-RPC responses from stdout const lines = stdout.split('\n').filter(l => l.trim() && l.includes('"jsonrpc"')); if (lines.length === 0) { reject(new Error(`No JSON-RPC response from ${packageName}`)); return; } const lastLine = lines[lines.length - 1]; const response = JSON.parse(lastLine); if (response.error) { reject(new Error(response.error.message || 'MCP server error')); return; } if (response.result && response.result.content) { const content = response.result.content[0]; if (content.type === 'text') { resolve(JSON.parse(content.text)); return; } } resolve(response.result); } catch (error) { reject(new Error(`Failed to parse ${packageName} response: ${error}`)); } }); // Send JSON-RPC request const request = { jsonrpc: '2.0', id: Date.now(), method: 'tools/call', params: { name: toolName, arguments: args } }; child.stdin.write(JSON.stringify(request) + '\n'); child.stdin.end(); // Timeout after 30 seconds setTimeout(() => { child.kill(); reject(new Error(`${packageName} timed out after 30s`)); }, 30000); }); } //# sourceMappingURL=quality-safety-hooks.js.map