/* * 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. */ /** * Pluralistic Deliberation Orchestrator Service * Facilitates multi-stakeholder deliberation when values conflict without imposing hierarchy * * Core Tractatus Service #6: Implements foundational pluralism where moral frameworks * (deontological, consequentialist, virtue ethics, care ethics, communitarian) are * irreducibly different with no supervalue. AI facilitates deliberation, humans decide. * * Core Principles (inst_033): * - Foundational Pluralism: No automatic value ranking (privacy > safety or safety > privacy) * - Legitimate Disagreement: Valid outcome when values genuinely incommensurable * - Moral Remainder: Document what's lost in decisions, not just what's gained * - Provisional Decisions: Reviewable when context changes * * Integration: * - Triggered by BoundaryEnforcer when value conflicts detected * - Uses AdaptiveCommunicationOrchestrator for culturally appropriate communication * - Stores precedents in precedent database (informative, not binding) */ const logger = require('../utils/logger.util'); const { getMemoryProxy } = require('./MemoryProxy.service'); const AdaptiveCommunicationOrchestrator = require('./AdaptiveCommunicationOrchestrator.service'); /** * Moral frameworks recognized by the orchestrator */ const MORAL_FRAMEWORKS = { DEONTOLOGICAL: { name: 'Rights-based (Deontological)', focus: 'Duties, rights, rules', keywords: ['duty', 'right', 'obligation', 'principle', 'rule', 'categorical'], typical_concerns: ['privacy', 'autonomy', 'dignity', 'consent'] }, CONSEQUENTIALIST: { name: 'Consequentialist (Utilitarian)', focus: 'Outcomes, aggregate welfare', keywords: ['outcome', 'benefit', 'harm', 'utility', 'maximize', 'welfare'], typical_concerns: ['safety', 'harm prevention', 'aggregate good'] }, VIRTUE_ETHICS: { name: 'Virtue Ethics', focus: 'Character, virtues, flourishing', keywords: ['virtue', 'character', 'flourish', 'excellence', 'integrity'], typical_concerns: ['honesty', 'courage', 'compassion', 'wisdom'] }, CARE_ETHICS: { name: 'Care Ethics', focus: 'Relationships, care, context', keywords: ['care', 'relationship', 'context', 'nurture', 'responsive'], typical_concerns: ['vulnerability', 'interdependence', 'particular needs'] }, COMMUNITARIAN: { name: 'Communitarian', focus: 'Community, tradition, shared goods', keywords: ['community', 'tradition', 'shared', 'collective', 'solidarity'], typical_concerns: ['social cohesion', 'common good', 'cultural values'] }, INDIGENOUS_RELATIONAL: { name: 'Indigenous Relational', focus: 'Interconnection, reciprocity, land', keywords: ['whanaungatanga', 'kaitiakitanga', 'reciprocity', 'interconnection'], typical_concerns: ['land stewardship', 'intergenerational responsibility', 'balance'] } }; /** * Urgency tiers for deliberation timeframes */ const URGENCY_TIERS = { CRITICAL: { timeframe: 'minutes to hours', process: 'Automated triage + rapid human review' }, URGENT: { timeframe: 'days', process: 'Expedited stakeholder consultation' }, IMPORTANT: { timeframe: 'weeks', process: 'Full deliberative process' }, ROUTINE: { timeframe: 'months', process: 'Precedent matching + lightweight review' } }; class PluralisticDeliberationOrchestrator { constructor() { this.moralFrameworks = MORAL_FRAMEWORKS; this.urgencyTiers = URGENCY_TIERS; this.communicationOrchestrator = AdaptiveCommunicationOrchestrator; // Initialize MemoryProxy for precedent storage this.memoryProxy = getMemoryProxy(); this.memoryProxyInitialized = false; // Statistics tracking this.stats = { total_deliberations: 0, frameworks_in_tension: {}, legitimate_disagreements: 0, consensus_reached: 0, precedents_created: 0, by_urgency: { CRITICAL: 0, URGENT: 0, IMPORTANT: 0, ROUTINE: 0 } }; logger.info('PluralisticDeliberationOrchestrator initialized'); } /** * Initialize MemoryProxy and load precedent database * @returns {Promise} Initialization result */ async initialize() { try { await this.memoryProxy.initialize(); // Load precedent database (stored in memory as inst_035) const precedentRule = await this.memoryProxy.getRule('inst_035'); if (precedentRule) { logger.info('Precedent database configuration loaded'); } else { logger.warn('Precedent database rule (inst_035) not found'); } this.memoryProxyInitialized = true; return { success: true, precedent_db_loaded: !!precedentRule }; } catch (error) { logger.error('Failed to initialize PluralisticDeliberationOrchestrator', { error: error.message }); return { success: false, error: error.message }; } } /** * Analyze a values conflict to identify moral frameworks in tension * @param {Object} decision - The decision involving value conflict * @param {Object} context - Decision context * @returns {Object} Conflict analysis */ analyzeConflict(decision, context = {}) { try { const decisionText = (decision.description || decision.text || '').toLowerCase(); // Detect which moral frameworks are in tension const frameworksInTension = this._detectFrameworks(decisionText); // Identify value trade-offs const valueTradeOffs = this._identifyTradeOffs(decisionText, decision); // Identify affected stakeholder groups const affectedStakeholders = this._identifyStakeholders(decision, context); // Determine urgency tier const urgency = this._determineUrgency(decision, context); // Check for precedents const relevantPrecedents = this._findPrecedents(decision); this.stats.total_deliberations++; this.stats.by_urgency[urgency]++; return { moral_frameworks_in_tension: frameworksInTension, value_trade_offs: valueTradeOffs, affected_stakeholder_groups: affectedStakeholders, urgency_tier: urgency, deliberation_timeframe: this.urgencyTiers[urgency].timeframe, deliberation_process: this.urgencyTiers[urgency].process, relevant_precedents: relevantPrecedents, requires_human_approval: true, // Always true per TRA-OPS-0002 ai_role: 'FACILITATE_ONLY', human_role: 'DECIDE', analysis_timestamp: new Date() }; } catch (error) { logger.error('Conflict analysis error:', error); return { error: error.message, requires_human_approval: true, ai_role: 'FACILITATE_ONLY' }; } } /** * Facilitate a deliberation round across stakeholder groups * @param {Object} conflict - Conflict analysis from analyzeConflict() * @param {Array} stakeholders - Stakeholder list (requires human approval per inst_034) * @param {Object} options - Deliberation options * @returns {Object} Deliberation facilitation result */ facilitateDeliberation(conflict, stakeholders, options = {}) { try { // Validate stakeholder list was human-approved if (!options.stakeholder_list_approved_by_human) { return { error: 'Stakeholder list requires human approval (inst_034)', action: 'REQUIRE_HUMAN_APPROVAL', reason: 'AI cannot determine affected stakeholders unilaterally' }; } // Generate deliberation structure const deliberationStructure = this._generateDeliberationStructure( conflict, stakeholders ); // Prepare culturally-adapted communication for each stakeholder const stakeholderCommunications = stakeholders.map(stakeholder => { return { stakeholder_id: stakeholder.id, stakeholder_group: stakeholder.group, communication: this.communicationOrchestrator.adaptCommunication( deliberationStructure.invitation_message, { audience: stakeholder.communication_style || 'formal_academic', cultural_context: stakeholder.cultural_context, language: stakeholder.preferred_language } ) }; }); return { deliberation_id: `delib_${Date.now()}`, structure: deliberationStructure, stakeholder_communications: stakeholderCommunications, process: conflict.deliberation_process, timeframe: conflict.deliberation_timeframe, next_steps: [ 'Send invitations to stakeholders', 'Collect initial positions from each framework', 'Facilitate structured discussion rounds', 'Document outcomes and dissent' ], ai_facilitation_role: 'Structure discussion, document positions, ensure all voices heard', human_decision_role: 'Make final decision, acknowledge moral remainder', timestamp: new Date() }; } catch (error) { logger.error('Deliberation facilitation error:', error); return { error: error.message, requires_human_fallback: true }; } } /** * Document deliberation outcome (AI facilitates, humans decide) * @param {Object} deliberation - The deliberation context * @param {Object} outcome - The human-decided outcome * @returns {Object} Documentation result */ documentOutcome(deliberation, outcome) { try { // Validate that outcome was human-decided if (!outcome.decided_by_human) { return { error: 'Outcome must be human-decided (TRA-OPS-0002)', action: 'REQUIRE_HUMAN_DECISION' }; } // Document what values were prioritized/deprioritized const valuesPrioritization = { prioritized: outcome.values_prioritized || [], deprioritized: outcome.values_deprioritized || [], moral_remainder: outcome.moral_remainder || 'Not documented', dissenting_views: outcome.dissenting_views || [], review_date: outcome.review_date || null }; // Create precedent (informative, not binding per inst_035) const precedent = this._createPrecedent(deliberation, outcome, valuesPrioritization); // Store precedent in memory if (this.memoryProxyInitialized) { this._storePrecedent(precedent); } this.stats.precedents_created++; if (outcome.consensus_reached) { this.stats.consensus_reached++; } else { this.stats.legitimate_disagreements++; } return { outcome_documented: true, precedent_created: precedent.id, precedent_scope: precedent.applicability_scope, precedent_binding: false, // Always informative, not binding values_prioritization: valuesPrioritization, review_date: outcome.review_date, documentation_timestamp: new Date() }; } catch (error) { logger.error('Outcome documentation error:', error); return { error: error.message, outcome_documented: false }; } } /** * Private helper methods */ _detectFrameworks(text) { const detected = []; for (const [key, framework] of Object.entries(this.moralFrameworks)) { let matchScore = 0; for (const keyword of framework.keywords) { if (text.includes(keyword)) { matchScore++; } } if (matchScore >= 2) { detected.push({ framework: framework.name, focus: framework.focus, typical_concerns: framework.typical_concerns, match_score: matchScore }); // Track statistics if (!this.stats.frameworks_in_tension[key]) { this.stats.frameworks_in_tension[key] = 0; } this.stats.frameworks_in_tension[key]++; } } return detected; } _identifyTradeOffs(text, decision) { const tradeoffs = []; // Common value conflicts const conflicts = [ { values: ['privacy', 'safety'], pattern: /privacy.*(?:vs|versus|or).*safety|safety.*(?:vs|versus|or).*privacy/i }, { values: ['privacy', 'convenience'], pattern: /privacy.*(?:vs|versus|or).*convenience|convenience.*(?:vs|versus|or).*privacy/i }, { values: ['individual', 'collective'], pattern: /individual.*(?:vs|versus|or).*collective|collective.*(?:vs|versus|or).*individual/i }, { values: ['freedom', 'security'], pattern: /freedom.*(?:vs|versus|or).*security|security.*(?:vs|versus|or).*freedom/i } ]; for (const conflict of conflicts) { if (conflict.pattern.test(text)) { tradeoffs.push(`${conflict.values[0]} vs. ${conflict.values[1]}`); } } // Check decision metadata if (decision.value_conflicts) { tradeoffs.push(...decision.value_conflicts); } return [...new Set(tradeoffs)]; // Remove duplicates } _identifyStakeholders(decision, context) { // AI can suggest stakeholders, but human must approve (inst_034) const suggestedStakeholders = []; // Suggest based on affected groups mentioned const text = (decision.description || decision.text || '').toLowerCase(); if (text.includes('user') || text.includes('customer')) { suggestedStakeholders.push('affected_users'); } if (text.includes('privacy')) { suggestedStakeholders.push('privacy_advocates'); } if (text.includes('safety') || text.includes('harm')) { suggestedStakeholders.push('harm_prevention_specialists'); } if (text.includes('legal') || text.includes('compliance')) { suggestedStakeholders.push('legal_team'); } // Add from context if provided if (context.affected_groups) { suggestedStakeholders.push(...context.affected_groups); } return [...new Set(suggestedStakeholders)]; } _determineUrgency(decision, context) { // Determine urgency tier based on context if (decision.urgency === 'critical' || context.imminent_harm) { return 'CRITICAL'; } if (decision.urgency === 'urgent' || context.time_sensitive) { return 'URGENT'; } if (decision.urgency === 'important' || context.significant_impact) { return 'IMPORTANT'; } return 'ROUTINE'; } _findPrecedents(decision) { // Placeholder for precedent matching // In full implementation, would query precedent database return []; } _generateDeliberationStructure(conflict, stakeholders) { return { invitation_message: `We are deliberating a decision involving ${conflict.value_trade_offs.join(', ')}. ` + `Multiple moral frameworks are in tension, and we need diverse perspectives.`, discussion_rounds: [ { round: 1, purpose: 'Initial positions from each moral framework', format: 'Written submissions' }, { round: 2, purpose: 'Respond to other frameworks, explore accommodations', format: 'Structured discussion' }, { round: 3, purpose: 'Identify areas of agreement and irreducible disagreement', format: 'Facilitated synthesis' } ], documentation_requirements: [ 'Values prioritized', 'Values deprioritized', 'Moral remainder (what is lost)', 'Dissenting views with full legitimacy', 'Review date for changed circumstances' ] }; } _createPrecedent(deliberation, outcome, valuesPrioritization) { return { id: `prec_${Date.now()}`, deliberation_id: deliberation.deliberation_id, decision_summary: outcome.decision_summary, values_prioritized: valuesPrioritization.prioritized, values_deprioritized: valuesPrioritization.deprioritized, moral_remainder: valuesPrioritization.moral_remainder, dissenting_views: valuesPrioritization.dissenting_views, frameworks_involved: deliberation.structure.frameworks_in_tension, context_factors: outcome.context_factors || [], applicability_scope: outcome.applicability_scope || 'Similar cases with same context factors', binding_status: 'INFORMATIVE_NOT_BINDING', // Per inst_035 review_date: outcome.review_date, created_at: new Date() }; } async _storePrecedent(precedent) { try { // Store precedent in memory for future reference await this.memoryProxy.storePrecedent(precedent); logger.info('Precedent stored', { precedent_id: precedent.id }); } catch (error) { logger.error('Failed to store precedent', { error: error.message, precedent_id: precedent.id }); } } /** * Get deliberation statistics * @returns {Object} Statistics object */ getStats() { return { ...this.stats, timestamp: new Date() }; } } // Singleton instance const orchestrator = new PluralisticDeliberationOrchestrator(); // Export both singleton (default) and class (for testing) module.exports = orchestrator; module.exports.PluralisticDeliberationOrchestrator = PluralisticDeliberationOrchestrator;