/* * 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]++; // PHASE 3: Build structured guidance const frameworkNames = frameworksInTension.map(f => f.name || f); const severity = urgency === 'CRITICAL' ? 'CRITICAL' : urgency === 'HIGH' ? 'HIGH' : 'MEDIUM'; const summary = frameworksInTension.length > 0 ? `Value conflict detected: ${frameworkNames.join(', ')} in tension` : 'Potential value conflict - human deliberation required'; const recommendation = `${this.urgencyTiers[urgency].process} (${this.urgencyTiers[urgency].timeframe}) - Human decision required per Tractatus 12.1`; const guidance = this._buildGuidance( summary, recommendation, severity, frameworkNames, { urgency_tier: urgency, trade_offs_count: valueTradeOffs.length, stakeholders_count: affectedStakeholders.length, precedents_count: relevantPrecedents.length } ); const analysis = { 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', guidance, // PHASE 3: Include guidance analysis_timestamp: new Date() }; // Audit deliberation analysis this._auditDeliberation(analysis, decision, context); return analysis; } 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 }); } } /** * Audit deliberation analysis to MemoryProxy * @private */ _auditDeliberation(analysis, decision, context = {}) { if (!this.memoryProxyInitialized) { logger.debug('[PluralisticDeliberationOrchestrator] Audit skipped - MemoryProxy not initialized'); return; } logger.debug('[PluralisticDeliberationOrchestrator] Auditing deliberation', { urgency: analysis.urgency_tier, frameworks: analysis.moral_frameworks_in_tension.length, sessionId: context.sessionId || 'deliberation-orchestrator' }); // PHASE 3: Include framework-backed decision indicator const frameworkBacked = !!(analysis.guidance && analysis.guidance.systemMessage); // Audit asynchronously (don't block analysis) this.memoryProxy.auditDecision({ sessionId: context.sessionId || 'deliberation-orchestrator', action: 'pluralistic_deliberation', service: 'PluralisticDeliberationOrchestrator', rulesChecked: ['inst_033', 'inst_034', 'inst_035'], // Core pluralism rules violations: [], // Deliberation facilitates, doesn't enforce allowed: true, // Facilitation is always allowed metadata: { decision_description: decision.description?.substring(0, 100) || decision.text?.substring(0, 100), urgency_tier: analysis.urgency_tier, deliberation_timeframe: analysis.deliberation_timeframe, frameworks_count: analysis.moral_frameworks_in_tension.length, frameworks_involved: analysis.moral_frameworks_in_tension.map(f => f.framework), value_tradeoffs: analysis.value_trade_offs, stakeholder_groups: analysis.affected_stakeholder_groups, precedents_found: analysis.relevant_precedents.length, requires_human_approval: analysis.requires_human_approval, ai_role: analysis.ai_role, human_role: analysis.human_role, framework_backed_decision: frameworkBacked, // PHASE 3: Track framework participation guidance_provided: frameworkBacked, guidance_severity: analysis.guidance?.severity || null } }).catch(error => { logger.error('[PluralisticDeliberationOrchestrator] Failed to audit deliberation', { error: error.message, urgency: analysis.urgency_tier }); }); } /** * Assess cultural sensitivity of external communication content * Identifies culturally inappropriate messaging for diverse audiences * * @param {string} content - Content to assess * @param {Object} context - Audience/communication context * @param {Object} context.audience - Audience information (country, region, cultural_context) * @param {string} context.content_type - Type (media_response, blog_post, template, newsletter) * @returns {Promise} Cultural sensitivity assessment * * Core Principles: * - inst_081: Pluralism - different value frameworks are equally legitimate * - Detect Western-centric framings inappropriate for other cultures * - FLAG for review, never auto-block (human decides) * - Suggest adaptations, but preserve human agency */ async assessCulturalSensitivity(content, context = {}) { const { audience = {}, content_type = 'general', target_publications = [] } = context; logger.info('[PluralisticDeliberationOrchestrator] Assessing cultural sensitivity', { content_type, audience_country: audience.country, audience_region: audience.region }); const assessment = { culturally_sensitive: true, risk_level: 'LOW', concerns: [], suggestions: [], recommended_action: 'APPROVE', timestamp: new Date(), context }; // Check if audience is non-Western or culturally distinct const highRiskAudience = this._identifyHighRiskAudience(audience, target_publications); const sensitiveTopics = this._detectSensitiveTopics(content); // Western-centric governance language const westernGovernancePatterns = { democracy: { patterns: [ /(?:requires?|needs?|must\s+have|ensures?|guarantees?)\s+\w+\s+democrac(?:y|tic)/gi, // Prescriptive /\bdemocratic\s+(?:governance|oversight|control)\s+(?:is|ensures?|provides?|guarantees?)/gi // Prescriptive structure ], exclude_patterns: [ /(?:historical|traditional|examples?\s+(?:of|include)|such\s+as|like)\s+[^.]{0,100}democrac/gi // Descriptive/analytical uses ], concern: 'Prescriptive democratic framing may have political connotations in autocratic contexts', suggestion: 'Consider "participatory governance", "stakeholder input", or "inclusive decision-making"' }, individual_rights: { patterns: [/\bindividual\s+(?:rights|freedom|autonomy)\b/gi, /\bpersonal\s+freedom\b/gi], concern: 'Individualistic framing may not resonate in collectivist cultures', suggestion: 'Balance with "community wellbeing", "collective benefit", or "shared responsibility"' }, western_ethics_only: { patterns: [/\bethics\b(?!.*(?:diverse|pluralistic|multiple|indigenous))/gi], concern: 'Implies universal Western ethics without acknowledging other frameworks', suggestion: 'Reference "diverse ethical frameworks" or "culturally-grounded values"' }, freedom_emphasis: { patterns: [/\bfreedom\s+of\s+(?:speech|expression|press)\b/gi], concern: 'Western rights discourse may be politically sensitive in some regions', suggestion: 'Frame as "open communication" or "information access" if contextually appropriate' } }; // Indigenous/cultural insensitivity patterns const culturalInsensitivityPatterns = { western_only_governance: { patterns: [/\bgovernance\b(?!.*(?:indigenous|te\s+tiriti|care\s+principles))/gi], concern: 'Western governance frameworks only, excludes Indigenous perspectives', suggestion: 'Acknowledge Indigenous governance (Te Tiriti, CARE principles, relational sovereignty)' }, data_ownership_western: { patterns: [/\bdata\s+(?:ownership|rights)\b(?!.*(?:sovereignty|collective))/gi], concern: 'Individual data ownership framing conflicts with collective Indigenous data sovereignty', suggestion: 'Reference "data sovereignty", "collective data rights", or "community data governance"' } }; // Check patterns if (highRiskAudience || sensitiveTopics.length > 0) { // Scan for Western-centric patterns for (const [key, config] of Object.entries(westernGovernancePatterns)) { for (const pattern of config.patterns) { if (pattern.test(content)) { // Check exclude_patterns if they exist let shouldExclude = false; if (config.exclude_patterns) { for (const excludePattern of config.exclude_patterns) { if (excludePattern.test(content)) { shouldExclude = true; break; } } } // Only flag if not excluded if (!shouldExclude) { assessment.culturally_sensitive = false; assessment.concerns.push({ type: 'western_centric_framing', pattern_key: key, detail: config.concern, audience_context: this._formatAudienceContext(audience, target_publications) }); assessment.suggestions.push({ type: 'reframing', original_concern: key, suggestion: config.suggestion }); } } } } // Scan for Indigenous insensitivity (if audience includes Indigenous communities) if (this._isIndigenousAudience(audience, target_publications)) { for (const [key, config] of Object.entries(culturalInsensitivityPatterns)) { for (const pattern of config.patterns) { if (pattern.test(content)) { assessment.culturally_sensitive = false; assessment.concerns.push({ type: 'indigenous_exclusion', pattern_key: key, detail: config.concern, audience_context: 'Indigenous community or Te Tiriti context' }); assessment.suggestions.push({ type: 'indigenous_inclusion', original_concern: key, suggestion: config.suggestion }); } } } } } // Determine risk level and recommended action const highSeverityConcerns = assessment.concerns.filter(c => c.type === 'western_centric_framing' && highRiskAudience ); if (assessment.concerns.length === 0) { assessment.risk_level = 'LOW'; assessment.recommended_action = 'APPROVE'; } else if (highSeverityConcerns.length > 0 || assessment.concerns.length >= 3) { assessment.risk_level = 'HIGH'; assessment.recommended_action = 'HUMAN_REVIEW'; } else { assessment.risk_level = 'MEDIUM'; assessment.recommended_action = 'SUGGEST_ADAPTATION'; } // Audit log this._auditCulturalSensitivity(assessment).catch(error => { logger.error('[PluralisticDeliberationOrchestrator] Failed to audit cultural sensitivity check', { error: error.message }); }); logger.info('[PluralisticDeliberationOrchestrator] Cultural sensitivity assessment complete', { risk_level: assessment.risk_level, concerns_count: assessment.concerns.length, recommended_action: assessment.recommended_action }); return assessment; } /** * Identify if audience is high-risk for cultural insensitivity * @private */ _identifyHighRiskAudience(audience, target_publications = []) { // Non-Western countries const nonWesternCountries = ['CN', 'RU', 'SA', 'IR', 'VN', 'TH', 'ID', 'MY', 'PH']; if (audience.country && nonWesternCountries.includes(audience.country)) { return true; } // Indigenous communities if (audience.cultural_context && audience.cultural_context.includes('indigenous')) { return true; } // Publications in non-Western regions if (target_publications.some(pub => pub.region && !['Western', 'US', 'EU', 'NZ', 'AU', 'CA'].includes(pub.region))) { return true; } return false; } /** * Detect sensitive topics in content * @private */ _detectSensitiveTopics(content) { const topics = []; const topicPatterns = { values: /\b(?:values|ethics|morals?|principles)\b/gi, governance: /\b(?:governance|democracy|rights|freedom)\b/gi, religion: /\b(?:religion|spiritual|faith|belief)\b/gi, politics: /\b(?:political|government|state|regime)\b/gi, culture: /\b(?:culture|tradition|indigenous|tribal)\b/gi }; for (const [topic, pattern] of Object.entries(topicPatterns)) { if (pattern.test(content)) { topics.push(topic); } } return topics; } /** * Check if audience is Indigenous * @private */ _isIndigenousAudience(audience, target_publications = []) { if (audience.cultural_context && /indigenous|māori|aboriginal|first\s+nations/i.test(audience.cultural_context)) { return true; } if (target_publications.some(pub => pub.audience && /indigenous|māori/i.test(pub.audience))) { return true; } return false; } /** * Format audience context for reporting * @private */ _formatAudienceContext(audience, target_publications) { const parts = []; if (audience.country) parts.push(`Country: ${audience.country}`); if (audience.region) parts.push(`Region: ${audience.region}`); if (audience.outlet) parts.push(`Outlet: ${audience.outlet}`); if (target_publications.length > 0) { parts.push(`Publications: ${target_publications.map(p => p.name).join(', ')}`); } return parts.join(' | ') || 'General audience'; } /** * Audit cultural sensitivity check * @private */ async _auditCulturalSensitivity(assessment) { try { const memoryProxy = await getMemoryProxy(); const collection = await memoryProxy.getCollection('auditLogs'); await collection.insertOne({ timestamp: new Date(), service: 'PluralisticDeliberationOrchestrator', action: 'cultural_sensitivity_check', decision: assessment.recommended_action, context: { content_type: assessment.context.content_type, audience: assessment.context.audience, risk_level: assessment.risk_level, concerns_count: assessment.concerns.length, culturally_sensitive: assessment.culturally_sensitive }, metadata: { concerns: assessment.concerns, suggestions: assessment.suggestions } }); } catch (error) { logger.error('[PluralisticDeliberationOrchestrator] Failed to create audit log', { error: error.message }); } } /** * Get deliberation statistics * @returns {Object} Statistics object */ /** * PHASE 3: Build structured guidance for framework-to-Claude communication * * @param {string} summary - One-line human-readable summary * @param {string} recommendation - Actionable next step * @param {string} severity - CRITICAL | HIGH | MEDIUM | LOW | INFO * @param {Array} frameworks - Moral frameworks in tension * @param {Object} metadata - Additional context * @returns {Object} Structured guidance object */ _buildGuidance(summary, recommendation, severity, frameworks = [], 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 (PluralisticDeliberationOrchestrator):\n`; systemMessage += `${summary}\n`; if (frameworks.length > 0) { systemMessage += `\nFrameworks in Tension: ${frameworks.join(', ')}\n`; } if (recommendation) { systemMessage += `\nRecommendation: ${recommendation}\n`; } systemMessage += `\nAI Role: FACILITATE ONLY | Human Role: DECIDE\n`; return { summary, systemMessage, recommendation, severity, framework_service: 'PluralisticDeliberationOrchestrator', frameworks_in_tension: frameworks, metadata, timestamp: new Date() }; } 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;