tractatus/src/services/CrossReferenceValidator.service.js
TheFlow 3f47273f2d feat(framework): implement Phase 3 bidirectional communication architecture
Phase 3.5: Cross-validation between prompt analysis and action analysis
- Added prompt-analyzer-hook.js to store prompt expectations in session state
- Modified framework-audit-hook.js to retrieve and compare prompt vs action
- Implemented cross-validation logic tracking agreements, disagreements, missed flags
- Added validation feedback to systemMessage for real-time guidance

Services enhanced with guidance generation:
- BoundaryEnforcer: _buildGuidance() provides systemMessage for enforcement decisions
- CrossReferenceValidator: Generates guidance for cross-reference conflicts
- MetacognitiveVerifier: Provides guidance on metacognitive verification
- PluralisticDeliberationOrchestrator: Offers guidance on values conflicts

Framework now communicates bidirectionally:
- TO Claude: systemMessage injection with proactive guidance
- FROM Claude: Audit logs with framework_backed_decision metadata

Integration testing: 92% success (23/25 tests passed)
Recent performance: 100% guidance generation for new decisions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 19:45:24 +13:00

920 lines
30 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright 2025 John G Stroh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Cross-Reference Validator Service
* Validates proposed AI actions against explicit user instructions
*
* Core Tractatus Service: Prevents the "27027 failure mode" where
* AI actions use cached patterns instead of explicit user instructions.
*
* Example failure prevented:
* - User says: "check port 27027"
* - AI action: mongosh --port 27017 (using MongoDB default instead of explicit instruction)
* - Validator: REJECTS action, requires using port 27027
*/
const classifier = require('./InstructionPersistenceClassifier.service');
const { getMemoryProxy } = require('./MemoryProxy.service');
const logger = require('../utils/logger.util');
/**
* Validation result statuses
*/
const VALIDATION_STATUS = {
APPROVED: 'APPROVED', // No conflicts, proceed
WARNING: 'WARNING', // Minor conflicts, notify user
REJECTED: 'REJECTED', // Critical conflicts, block action
ESCALATE: 'ESCALATE' // Requires human judgment
};
/**
* Conflict severity levels
*/
const CONFLICT_SEVERITY = {
CRITICAL: 'CRITICAL', // Explicit instruction violation
WARNING: 'WARNING', // Potential misalignment
MINOR: 'MINOR', // Acceptable deviation
INFO: 'INFO' // Informational only
};
class CrossReferenceValidator {
constructor() {
this.classifier = classifier;
this.lookbackWindow = 100; // How many recent messages to check
this.relevanceThreshold = 0.3; // Minimum relevance to consider (lowered for better detection)
this.instructionCache = new Map(); // Cache classified instructions
this.instructionHistory = []; // Recent instruction history
// Initialize MemoryProxy for governance rules and audit logging
this.memoryProxy = getMemoryProxy();
this.governanceRules = []; // Loaded from memory
this.memoryProxyInitialized = false;
// Statistics tracking
this.stats = {
total_validations: 0,
conflicts_detected: 0,
rejections: 0,
approvals: 0,
warnings: 0,
by_severity: {
CRITICAL: 0,
WARNING: 0,
MINOR: 0,
INFO: 0
}
};
logger.info('CrossReferenceValidator initialized');
}
/**
* Initialize MemoryProxy and load governance rules
* @returns {Promise<Object>} Initialization result
*/
async initialize() {
try {
await this.memoryProxy.initialize();
// Load all governance rules for validation reference
this.governanceRules = await this.memoryProxy.loadGovernanceRules();
this.memoryProxyInitialized = true;
logger.info('[CrossReferenceValidator] MemoryProxy initialized', {
governanceRulesLoaded: this.governanceRules.length
});
return {
success: true,
governanceRulesLoaded: this.governanceRules.length
};
} catch (error) {
logger.error('[CrossReferenceValidator] Failed to initialize MemoryProxy', {
error: error.message
});
// Continue with existing validation logic even if memory fails
return {
success: false,
error: error.message,
governanceRulesLoaded: 0
};
}
}
/**
* Validate a proposed action against conversation context
* @param {Object} action - The proposed action
* @param {Object} context - Conversation context with instructions
* @returns {Object} Validation result
*/
validate(action, context) {
try {
// Extract action parameters
const actionParams = this._extractActionParameters(action);
// Find relevant instructions from context
const relevantInstructions = this._findRelevantInstructions(
action,
context,
this.lookbackWindow
);
if (relevantInstructions.length === 0) {
const result = this._approvedResult('No relevant instructions to validate against');
// Audit even when no relevant instructions found
this._auditValidation(result, action, [], context);
return result;
}
// Check for conflicts with each relevant instruction
const conflicts = [];
for (const instruction of relevantInstructions) {
const instructionConflicts = this._checkConflict(actionParams, instruction);
if (instructionConflicts && instructionConflicts.length > 0) {
conflicts.push(...instructionConflicts);
}
}
// Make validation decision based on conflicts
const decision = this._makeValidationDecision(conflicts, action);
// Audit validation decision
this._auditValidation(decision, action, relevantInstructions, context);
return decision;
} catch (error) {
logger.error('Validation error:', error);
// Fail-safe: escalate on error
return this._escalateResult('Validation error occurred, requiring human review');
}
}
/**
* Batch validate multiple actions
*/
validateBatch(actions, context) {
return actions.map(action => this.validate(action, context));
}
/**
* Add instruction to cache for validation
*/
cacheInstruction(instruction) {
const classified = this.classifier.classify(instruction);
const key = `${instruction.timestamp.getTime()}_${instruction.text.substring(0, 50)}`;
this.instructionCache.set(key, classified);
// Cleanup old entries (keep last 200)
if (this.instructionCache.size > 200) {
const keys = Array.from(this.instructionCache.keys());
keys.slice(0, this.instructionCache.size - 200).forEach(k => {
this.instructionCache.delete(k);
});
}
return classified;
}
/**
* Private methods
*/
_extractActionParameters(action) {
const params = {};
// Common parameter types to extract
// Note: Using [:\s=] to match both structured (port: X) and free-form (port X) text
// This prevents false matches on unrelated text while catching explicit port mentions
const patterns = {
port: /port[:\s=]\s*(\d{4,5})/i,
host: /(?:host|server)[:=]\s*([\w.-]+)/i,
database: /(?:database|db)[:=]\s*([\w-]+)/i,
path: /(\/[\w./-]+)/,
url: /(https?:\/\/[\w.-]+(?::\d+)?[\w./-]*)/,
collection: /collection[:=]\s*([\w-]+)/i,
model: /model[:=]\s*([\w-]+)/i,
function: /function[:=]\s*([\w-]+)/i
};
const description = action.description || action.command || action.text || '';
for (const [paramType, pattern] of Object.entries(patterns)) {
const match = description.match(pattern);
if (match) {
params[paramType] = match[1];
}
}
// Extract from structured action data
if (action.parameters) {
Object.assign(params, action.parameters);
}
return params;
}
_findRelevantInstructions(action, context, lookback) {
const instructions = [];
// Handle two context formats:
// 1. recent_instructions: pre-classified instructions (for testing)
// 2. messages: raw conversation messages (for production)
if (context.recent_instructions && Array.isArray(context.recent_instructions)) {
// Test format: use pre-classified instructions
for (const instruction of context.recent_instructions) {
// Calculate relevance to this action
const relevance = this.classifier.calculateRelevance(instruction, action);
if (relevance >= this.relevanceThreshold) {
instructions.push({
...instruction,
relevance
});
}
}
} else if (context.messages && Array.isArray(context.messages)) {
// Production format: extract and classify messages
const recentMessages = context.messages.slice(-lookback);
for (const message of recentMessages) {
if (message.role === 'user') {
// Classify the instruction
const classified = this.cacheInstruction({
text: message.content,
timestamp: message.timestamp || new Date(),
source: 'user',
context
});
// Calculate relevance to this action
const relevance = this.classifier.calculateRelevance(classified, action);
if (relevance >= this.relevanceThreshold) {
instructions.push({
...classified,
relevance,
messageIndex: recentMessages.indexOf(message)
});
}
}
}
}
// Sort by relevance (highest first)
instructions.sort((a, b) => b.relevance - a.relevance);
logger.debug(`Found ${instructions.length} relevant instructions for action`, {
action: action.description?.substring(0, 50),
topRelevance: instructions[0]?.relevance
});
return instructions;
}
_checkConflict(actionParams, instruction) {
// Extract parameters from instruction
const instructionParams = instruction.parameters || {};
const conflicts = [];
// Check for parameter-level conflicts
const commonParams = Object.keys(actionParams).filter(key =>
instructionParams.hasOwnProperty(key)
);
for (const param of commonParams) {
const actionValue = actionParams[param];
const instructionValue = instructionParams[param];
// Normalize for comparison
const normalizedAction = String(actionValue).toLowerCase().trim();
const normalizedInstruction = String(instructionValue).toLowerCase().trim();
if (normalizedAction !== normalizedInstruction) {
// Found a parameter conflict
const severity = this._determineConflictSeverity(
param,
instruction.persistence,
instruction.explicitness,
instruction.recencyWeight
);
conflicts.push({
parameter: param,
actionValue,
instructionValue,
instruction: {
text: instruction.text,
timestamp: instruction.timestamp,
quadrant: instruction.quadrant,
persistence: instruction.persistence
},
severity,
relevance: instruction.relevance,
recencyWeight: instruction.recencyWeight
});
}
}
// Check for semantic conflicts (prohibitions in instruction text)
// Only check if instruction has HIGH persistence (strong prohibitions)
const instructionText = (instruction.text || '').toLowerCase();
if (instruction.persistence === 'HIGH') {
const prohibitionPatterns = [
/\bnot\s+(\w+)/gi,
/don't\s+use\s+(\w+)/gi,
/\bavoid\s+(\w+)/gi,
/\bnever\s+(\w+)/gi
];
for (const [key, value] of Object.entries(actionParams)) {
const valueStr = String(value).toLowerCase();
// Check if instruction prohibits this value
for (const pattern of prohibitionPatterns) {
const matches = instructionText.matchAll(pattern);
for (const match of matches) {
const prohibitedItem = match[1].toLowerCase();
if (valueStr.includes(prohibitedItem) || prohibitedItem.includes(valueStr)) {
// Found a semantic conflict
const severity = CONFLICT_SEVERITY.CRITICAL; // HIGH persistence prohibitions are always CRITICAL
conflicts.push({
parameter: key,
actionValue: value,
instructionValue: `prohibited: ${prohibitedItem}`,
instruction: {
text: instruction.text,
timestamp: instruction.timestamp,
quadrant: instruction.quadrant,
persistence: instruction.persistence
},
severity,
relevance: instruction.relevance || 0.9,
recencyWeight: instruction.recencyWeight || 0.9,
type: 'prohibition'
});
}
}
}
}
}
return conflicts;
}
_determineConflictSeverity(param, persistence, explicitness, recencyWeight) {
// Critical severity conditions
if (persistence === 'HIGH' && explicitness > 0.8) {
return CONFLICT_SEVERITY.CRITICAL;
}
// HIGH persistence alone should be WARNING at minimum
if (persistence === 'HIGH') {
return CONFLICT_SEVERITY.CRITICAL; // Changed from WARNING - HIGH persistence instructions should be enforced strictly
}
if (recencyWeight > 0.8 && explicitness > 0.7) {
return CONFLICT_SEVERITY.CRITICAL;
}
// Important parameters that should be explicit
const criticalParams = ['port', 'database', 'host', 'url', 'confirmed'];
if (criticalParams.includes(param) && explicitness > 0.6) {
return CONFLICT_SEVERITY.CRITICAL;
}
// Warning severity
if (explicitness > 0.6) {
return CONFLICT_SEVERITY.WARNING;
}
// Minor severity
if (persistence === 'MEDIUM') {
return CONFLICT_SEVERITY.WARNING;
}
return CONFLICT_SEVERITY.MINOR;
}
_makeValidationDecision(conflicts, action) {
if (conflicts.length === 0) {
return this._approvedResult('No conflicts detected');
}
// Check for critical conflicts
const criticalConflicts = conflicts.filter(c => c.severity === CONFLICT_SEVERITY.CRITICAL);
if (criticalConflicts.length > 0) {
return this._rejectedResult(criticalConflicts, action);
}
// Check for warning-level conflicts
const warningConflicts = conflicts.filter(c => c.severity === CONFLICT_SEVERITY.WARNING);
if (warningConflicts.length > 0) {
return this._warningResult(warningConflicts, action);
}
// Only minor conflicts
return this._approvedResult(
'Minor conflicts resolved in favor of user instruction',
conflicts
);
}
_approvedResult(message, conflicts = []) {
this.stats.total_validations++;
this.stats.approvals++;
// PHASE 3: Build structured guidance
const guidance = this._buildGuidance(
'APPROVE',
message,
'Action aligned with user instructions - proceed',
'INFO',
[], // No specific rules violated
{ conflicts_count: conflicts.length }
);
return {
status: VALIDATION_STATUS.APPROVED,
message,
conflicts,
action: 'PROCEED',
guidance, // PHASE 3: Include guidance
timestamp: new Date()
};
}
_warningResult(conflicts, action) {
this.stats.total_validations++;
this.stats.warnings++;
this.stats.conflicts_detected += conflicts.length;
conflicts.forEach(c => this.stats.by_severity[c.severity]++);
const primaryConflict = conflicts[0];
const timeAgo = this._formatTimeAgo(primaryConflict.instruction.timestamp);
const message = `Potential conflict in parameter '${primaryConflict.parameter}': ` +
`action uses '${primaryConflict.actionValue}' but user instruction ` +
`specified '${primaryConflict.instructionValue}' (${timeAgo} ago)`;
// PHASE 3: Build structured guidance
const guidance = this._buildGuidance(
'WARN',
message,
`Consider using '${primaryConflict.instructionValue}' instead`,
'MEDIUM',
[primaryConflict.instruction.id || 'user_instruction'],
{
parameter: primaryConflict.parameter,
action_value: primaryConflict.actionValue,
instruction_value: primaryConflict.instructionValue
}
);
return {
status: VALIDATION_STATUS.WARNING,
message,
conflicts,
action: 'NOTIFY_USER',
recommendation: `Consider using '${primaryConflict.instructionValue}' instead`,
guidance, // PHASE 3: Include guidance
timestamp: new Date()
};
}
_rejectedResult(conflicts, action) {
this.stats.total_validations++;
this.stats.rejections++;
this.stats.conflicts_detected += conflicts.length;
conflicts.forEach(c => this.stats.by_severity[c.severity]++);
const primaryConflict = conflicts[0];
const timeAgo = this._formatTimeAgo(primaryConflict.instruction.timestamp);
const message = `CRITICAL CONFLICT: Action parameter '${primaryConflict.parameter}' ` +
`uses '${primaryConflict.actionValue}' but user explicitly specified ` +
`'${primaryConflict.instructionValue}' ${timeAgo} ago`;
// PHASE 3: Build structured guidance
const guidance = this._buildGuidance(
'REJECT',
message,
'Verify with user before proceeding - explicit instruction conflict detected',
'CRITICAL',
[primaryConflict.instruction.id || 'user_instruction'],
{
parameter: primaryConflict.parameter,
action_value: primaryConflict.actionValue,
instruction_value: primaryConflict.instructionValue,
instruction_quote: primaryConflict.instruction.text
}
);
return {
status: VALIDATION_STATUS.REJECTED,
message,
conflicts,
action: 'REQUEST_CLARIFICATION',
required_action: 'REQUEST_CLARIFICATION',
recommendation: `Verify with user before proceeding`,
instructionQuote: primaryConflict.instruction.text,
requiredValue: primaryConflict.instructionValue,
guidance, // PHASE 3: Include guidance
timestamp: new Date(),
userPrompt: `I noticed a conflict:\n\n` +
`You instructed: "${primaryConflict.instruction.text}"\n` +
`But my proposed action would use ${primaryConflict.parameter}: ${primaryConflict.actionValue}\n\n` +
`Should I use ${primaryConflict.instructionValue} as you specified, or ${primaryConflict.actionValue}?`
};
}
_escalateResult(message) {
// PHASE 3: Build structured guidance
const guidance = this._buildGuidance(
'ESCALATE',
message,
'Human review required - complexity exceeds framework capabilities',
'HIGH',
[],
{}
);
return {
status: VALIDATION_STATUS.ESCALATE,
message,
action: 'REQUIRE_HUMAN_REVIEW',
guidance, // PHASE 3: Include guidance
timestamp: new Date()
};
}
_formatTimeAgo(timestamp) {
const seconds = Math.floor((new Date() - new Date(timestamp)) / 1000);
if (seconds < 60) return `${seconds} seconds`;
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours`;
return `${Math.floor(seconds / 86400)} days`;
}
/**
* Add instruction to history
* @param {Object} instruction - Classified instruction
*/
addInstruction(instruction) {
// Add to beginning of array (most recent first)
this.instructionHistory.unshift(instruction);
// Keep only lookbackWindow instructions
if (this.instructionHistory.length > this.lookbackWindow) {
this.instructionHistory = this.instructionHistory.slice(0, this.lookbackWindow);
}
}
/**
* Get recent instructions
* @param {number} limit - Optional limit on number of instructions
* @returns {Array} Recent instructions
*/
getRecentInstructions(limit = this.lookbackWindow) {
return this.instructionHistory.slice(0, limit);
}
/**
* Clear instruction history
*/
clearInstructions() {
this.instructionHistory = [];
this.instructionCache.clear();
}
/**
* Audit validation decision to memory (async, non-blocking)
* @private
*/
_auditValidation(decision, action, relevantInstructions, context = {}) {
// Only audit if MemoryProxy is initialized
if (!this.memoryProxyInitialized) {
return;
}
// Extract violation information
const violations = decision.conflicts
?.filter(c => c.severity === CONFLICT_SEVERITY.CRITICAL)
.map(c => c.instruction?.text || c.parameter) || [];
// PHASE 3: Include framework-backed decision indicator
const frameworkBacked = !!(decision.guidance && decision.guidance.systemMessage);
// Audit asynchronously (don't block validation)
this.memoryProxy.auditDecision({
sessionId: context.sessionId || 'validator-service',
action: 'cross_reference_validation',
service: 'CrossReferenceValidator',
rulesChecked: relevantInstructions.map(i => i.id || 'instruction'),
violations,
allowed: decision.status === VALIDATION_STATUS.APPROVED,
metadata: {
action_description: action.description?.substring(0, 100),
validation_status: decision.status,
conflicts_found: decision.conflicts?.length || 0,
critical_conflicts: violations.length,
relevant_instructions: relevantInstructions.length,
validation_action: decision.action,
framework_backed_decision: frameworkBacked, // PHASE 3: Track framework participation
guidance_provided: frameworkBacked,
guidance_severity: decision.guidance?.severity || null,
conflict_details: decision.conflicts?.slice(0, 3).map(c => ({
parameter: c.parameter,
severity: c.severity,
action_value: c.actionValue,
instruction_value: c.instructionValue
})) || []
}
}).catch(error => {
logger.error('[CrossReferenceValidator] Failed to audit validation', {
error: error.message,
action: action.description?.substring(0, 50)
});
});
}
/**
* Validate schema changes against governance rules (Phase 2: Semantic Understanding)
* @param {Object} action - Action to validate
* @param {Object} context - Validation context
* @returns {Promise<Object>} Validation result
*/
async validateSchemaChange(action, context = {}) {
try {
// Find all schema-related instructions
const schemaRules = this.governanceRules.filter(rule =>
rule.text.toLowerCase().includes('schema') ||
rule.text.toLowerCase().includes('database') ||
rule.text.toLowerCase().includes('model') ||
rule.text.toLowerCase().includes('collection') ||
rule.category === 'data_architecture' ||
rule.quadrant === 'SYSTEM'
);
// Find rules about specific sensitive data types
const sensitiveDataRules = this.governanceRules.filter(rule =>
rule.text.toLowerCase().includes('user') ||
rule.text.toLowerCase().includes('auth') ||
rule.text.toLowerCase().includes('credential') ||
rule.text.toLowerCase().includes('privacy') ||
rule.text.toLowerCase().includes('personal data')
);
// Combine relevant rules
const relevantRules = [...schemaRules, ...sensitiveDataRules];
// Check for conflicts with action
const conflicts = [];
for (const rule of relevantRules) {
// Simple conflict detection: if rule says "never" or "always" and action contradicts
const ruleText = rule.text.toLowerCase();
const actionDesc = (action.description || action.type || '').toLowerCase();
// Detect potential conflicts
if (ruleText.includes('never') && actionDesc.includes('modify')) {
conflicts.push({
rule,
severity: rule.persistence === 'HIGH' ? 'CRITICAL' : 'WARNING',
reason: 'Schema modification may conflict with protection rule'
});
}
// Check for approval requirements
if ((ruleText.includes('approval') || ruleText.includes('human')) &&
context.automated_approval) {
conflicts.push({
rule,
severity: 'HIGH',
reason: 'Human approval required for schema changes'
});
}
}
// Determine if action is allowed
const criticalConflicts = conflicts.filter(c => c.severity === 'CRITICAL');
const allowed = criticalConflicts.length === 0;
// Determine if human approval is required
const requiresApproval = conflicts.some(c =>
c.rule.persistence === 'HIGH' ||
c.rule.quadrant === 'STRATEGIC' ||
c.severity === 'CRITICAL'
);
const recommendation = this._getSchemaRecommendation(conflicts, requiresApproval);
// PHASE 3: Build structured guidance for schema validation
const severity = criticalConflicts.length > 0 ? 'CRITICAL' :
requiresApproval ? 'HIGH' :
conflicts.length > 0 ? 'MEDIUM' : 'INFO';
const decision = criticalConflicts.length > 0 ? 'REJECT' :
requiresApproval ? 'REQUIRE_APPROVAL' :
allowed ? 'APPROVE' : 'WARN';
const guidance = this._buildGuidance(
decision,
`Schema validation: ${recommendation}`,
recommendation,
severity,
relevantRules.map(r => r.id || r.ruleId).slice(0, 5), // Limit to 5 rules
{
schema_change: true,
sensitive_collection: context.sensitive_collection || false,
conflicts_count: conflicts.length,
critical_conflicts_count: criticalConflicts.length
}
);
const result = {
allowed,
conflicts,
criticalConflicts: criticalConflicts.length,
warningConflicts: conflicts.filter(c => c.severity === 'WARNING').length,
requiresApproval,
relevantRules: relevantRules.map(r => r.id || r.ruleId),
recommendation,
guidance // PHASE 3: Include guidance
};
// Log validation to audit
this._auditSchemaValidation(action, result, context);
return result;
} catch (error) {
logger.error('[CrossReferenceValidator] Schema validation error:', {
error: error.message,
action: action.description
});
return {
allowed: false,
conflicts: [],
error: error.message,
recommendation: 'Schema validation failed - manual review recommended'
};
}
}
/**
* Get recommendation based on schema validation conflicts
* @private
*/
_getSchemaRecommendation(conflicts, requiresApproval) {
if (conflicts.length === 0) {
return 'Schema change appears compliant with governance rules';
}
const criticalCount = conflicts.filter(c => c.severity === 'CRITICAL').length;
if (criticalCount > 0) {
return `BLOCK: ${criticalCount} critical conflict(s) detected. Human review required.`;
}
if (requiresApproval) {
return 'CAUTION: Human approval required before proceeding with schema change';
}
return `WARNING: ${conflicts.length} potential conflict(s) detected. Review recommended.`;
}
/**
* Audit schema validation decision
* @private
*/
async _auditSchemaValidation(action, result, context) {
if (!this.memoryProxyInitialized) {
return;
}
const violations = result.conflicts
.filter(c => c.severity === 'CRITICAL')
.map(c => c.rule.id || c.rule.ruleId);
// PHASE 3: Include framework-backed decision indicator
const frameworkBacked = !!(result.guidance && result.guidance.systemMessage);
this.memoryProxy.auditDecision({
sessionId: context.sessionId || 'schema-validator',
action: 'schema_change_validation',
service: 'CrossReferenceValidator',
rulesChecked: result.relevantRules,
violations,
allowed: result.allowed,
metadata: {
action_description: action.description,
validation_type: 'schema_change',
critical_conflicts: result.criticalConflicts,
warning_conflicts: result.warningConflicts,
requires_approval: result.requiresApproval,
recommendation: result.recommendation,
framework_backed_decision: frameworkBacked, // PHASE 3: Track framework participation
guidance_provided: frameworkBacked,
guidance_severity: result.guidance?.severity || null,
conflict_details: result.conflicts.slice(0, 5).map(c => ({
rule_id: c.rule.id || c.rule.ruleId,
severity: c.severity,
reason: c.reason
}))
}
}).catch(error => {
logger.error('[CrossReferenceValidator] Failed to audit schema validation', {
error: error.message
});
});
}
/**
* Get validation statistics
* @returns {Object} Statistics object
*/
/**
* PHASE 3: Build structured guidance for framework-to-Claude communication
*
* @param {string} decision - APPROVE | WARN | REJECT | ESCALATE
* @param {string} summary - One-line human-readable summary
* @param {string} recommendation - Actionable next step
* @param {string} severity - CRITICAL | HIGH | MEDIUM | LOW | INFO
* @param {Array} ruleIds - Relevant governance rule IDs
* @param {Object} metadata - Additional context
* @returns {Object} Structured guidance object
*/
_buildGuidance(decision, summary, recommendation, severity, ruleIds = [], metadata = {}) {
const severityEmojis = {
'CRITICAL': '🚨',
'HIGH': '⚠️',
'MEDIUM': '📋',
'LOW': '',
'INFO': '💡'
};
const emoji = severityEmojis[severity] || '';
// Build systemMessage for hook injection into Claude's context
let systemMessage = `\n${emoji} FRAMEWORK GUIDANCE (CrossReferenceValidator):\n`;
systemMessage += `Decision: ${decision}\n`;
systemMessage += `${summary}\n`;
if (recommendation) {
systemMessage += `\nRecommendation: ${recommendation}\n`;
}
if (ruleIds.length > 0) {
systemMessage += `\nRelevant Rules: ${ruleIds.join(', ')}\n`;
}
return {
summary,
systemMessage,
recommendation,
severity,
framework_service: 'CrossReferenceValidator',
rule_ids: ruleIds,
decision_type: decision,
metadata,
timestamp: new Date()
};
}
getStats() {
return {
...this.stats,
instruction_history_size: this.instructionHistory.length,
cache_size: this.instructionCache.size,
timestamp: new Date()
};
}
}
// Singleton instance
const validator = new CrossReferenceValidator();
module.exports = validator;