/**
 * Council Decision System -- 4-voice adversarial review for high-stakes decisions.
 *
 * Each voice runs independently (anti-anchoring) and produces a verdict.
 * Verdicts are merged: 4 APPROVE = proceed, any REJECT = escalate to human.
 *
 * Voices:
 *   Architect  - Long-term design, scalability, maintainability
 *   Skeptic    - Questions assumptions, finds failure modes, identifies risk
 *   Pragmatist - Time-to-value, simplicity, "good enough" solutions
 *   Critic     - Code quality, security, edge cases, testing gaps
 *
 * @connexum/ai-governance
 */
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
const DECISION_CATEGORIES = [
    {
        name: 'governance',
        description: 'Changes to governance files, security policies, or agent permissions',
        triggers: ['governance', 'security', 'permission', 'SOUL.md', 'policy'],
    },
    {
        name: 'deployment',
        description: 'Production deployments, infrastructure changes',
        triggers: ['deploy', 'production', 'release', 'infrastructure', 'railway', 'docker'],
    },
    {
        name: 'architecture',
        description: 'Architecture decisions affecting 3+ modules',
        triggers: ['architecture', 'refactor', 'migration', 'schema change', 'breaking change'],
    },
    {
        name: 'financial',
        description: 'Financial transactions, pricing changes, cost decisions',
        triggers: ['pricing', 'payment', 'subscription', 'cost', 'budget', 'revenue'],
    },
    {
        name: 'agent',
        description: 'New agent creation, permission changes, capability modifications',
        triggers: ['new agent', 'permission change', 'capability', 'role change'],
    },
];
const VOICE_PROMPTS = {
    architect: `You are the Architect voice in a decision council. Your bias is toward clean abstractions, long-term maintainability, and scalable design. Evaluate this proposal for:
- Does it follow established patterns or introduce new ones?
- Will this scale to 10x the current load?
- Does it create technical debt?
- Is the abstraction level appropriate?
- How does it affect the overall system architecture?`,
    skeptic: `You are the Skeptic voice in a decision council. Your bias is toward caution and identifying hidden risks. Evaluate this proposal for:
- What could go wrong?
- What assumptions are being made?
- What failure modes exist?
- Is there a simpler alternative?
- What's the rollback plan if this fails?
- Are we solving the right problem?`,
    pragmatist: `You are the Pragmatist voice in a decision council. Your bias is toward shipping value quickly with acceptable trade-offs. Evaluate this proposal for:
- Does this deliver value to users?
- Is this the simplest solution that works?
- Are we over-engineering?
- What's the minimum viable version?
- Can we iterate on this later?
- Is "good enough" actually good enough here?`,
    critic: `You are the Critic voice in a decision council. Your bias is toward thoroughness, security, and edge case handling. Evaluate this proposal for:
- Are there security vulnerabilities?
- What edge cases are unhandled?
- Is there adequate testing?
- Does this meet compliance requirements?
- Are error paths handled correctly?
- Is input validation sufficient?`,
};
export class Council {
    config;
    logDir;
    constructor(config) {
        this.config = {
            requireUnanimous: true,
            minApproveVotes: 3,
            ...config,
        };
        this.logDir = config.logDir;
        if (!fs.existsSync(this.logDir)) {
            fs.mkdirSync(this.logDir, { recursive: true });
        }
    }
    /**
     * Submit a proposal for council deliberation.
     * Returns the structure for collecting opinions.
     * In production, each voice would run in a separate LLM context.
     */
    createDeliberation(proposal, requestedBy, category) {
        const id = `council_${crypto.randomBytes(8).toString('hex')}`;
        const resolvedCategory = category || this.detectCategory(proposal);
        // Build voice-specific prompts (each voice sees ONLY the proposal, not other opinions)
        const voicePrompts = {};
        for (const voice of Object.keys(VOICE_PROMPTS)) {
            voicePrompts[voice] =
                `${VOICE_PROMPTS[voice]}\n\n` +
                    `## Proposal\n${proposal}\n\n` +
                    `## Category\n${resolvedCategory}\n\n` +
                    `## Instructions\n` +
                    `Respond with EXACTLY this format:\n` +
                    `VERDICT: APPROVE | REJECT | CONDITIONAL\n` +
                    `REASONING: <your analysis>\n` +
                    `CONDITIONS: <if CONDITIONAL, list conditions>\n` +
                    `RISKS: <identified risks>\n`;
        }
        // Save deliberation start
        const deliberation = {
            id,
            proposal,
            category: resolvedCategory,
            requestedBy,
            startedAt: new Date().toISOString(),
            status: 'pending',
        };
        this.saveLog(id, deliberation);
        return { id, category: resolvedCategory, voicePrompts };
    }
    /**
     * Record a voice's opinion.
     */
    recordOpinion(deliberationId, opinion) {
        const logFile = path.join(this.logDir, `${deliberationId}.json`);
        if (!fs.existsSync(logFile))
            return;
        const deliberation = JSON.parse(fs.readFileSync(logFile, 'utf-8'));
        if (!deliberation.opinions)
            deliberation.opinions = [];
        deliberation.opinions.push(opinion);
        fs.writeFileSync(logFile, JSON.stringify(deliberation, null, 2));
    }
    /**
     * Parse a voice response into a structured opinion.
     */
    static parseVoiceResponse(voice, response) {
        const verdictMatch = response.match(/VERDICT:\s*(APPROVE|REJECT|CONDITIONAL)/i);
        const reasoningMatch = response.match(/REASONING:\s*([\s\S]*?)(?=CONDITIONS:|RISKS:|$)/i);
        const conditionsMatch = response.match(/CONDITIONS:\s*([\s\S]*?)(?=RISKS:|$)/i);
        const risksMatch = response.match(/RISKS:\s*([\s\S]*?)$/i);
        return {
            voice,
            verdict: verdictMatch?.[1]?.toUpperCase() || 'REJECT',
            reasoning: reasoningMatch?.[1]?.trim() || response.trim(),
            conditions: conditionsMatch?.[1]?.trim()
                ? conditionsMatch[1].trim().split('\n').map((s) => s.replace(/^[-*]\s*/, '').trim()).filter(Boolean)
                : undefined,
            risks: risksMatch?.[1]?.trim()
                ? risksMatch[1].trim().split('\n').map((s) => s.replace(/^[-*]\s*/, '').trim()).filter(Boolean)
                : undefined,
            timestamp: new Date().toISOString(),
        };
    }
    /**
     * Merge all opinions into a final decision.
     */
    mergeDecision(deliberationId, proposal, requestedBy, opinions, startTime) {
        const approvals = opinions.filter((o) => o.verdict === 'APPROVE').length;
        const rejections = opinions.filter((o) => o.verdict === 'REJECT').length;
        const conditionals = opinions.filter((o) => o.verdict === 'CONDITIONAL').length;
        let finalVerdict;
        if (rejections > 0) {
            finalVerdict = 'ESCALATE';
        }
        else if (this.config.requireUnanimous && approvals < 4) {
            finalVerdict = conditionals > 0 ? 'CONDITIONAL' : 'ESCALATE';
        }
        else if (approvals >= this.config.minApproveVotes) {
            finalVerdict = conditionals > 0 ? 'CONDITIONAL' : 'APPROVE';
        }
        else {
            finalVerdict = 'ESCALATE';
        }
        // Collect all conditions and risks
        const conditions = opinions
            .flatMap((o) => o.conditions || [])
            .filter((v, i, a) => a.indexOf(v) === i); // dedupe
        const risks = opinions
            .flatMap((o) => o.risks || [])
            .filter((v, i, a) => a.indexOf(v) === i); // dedupe
        const decision = {
            id: deliberationId,
            proposal,
            category: this.detectCategory(proposal),
            requestedBy,
            opinions,
            finalVerdict,
            conditions,
            risks,
            timestamp: new Date().toISOString(),
            durationMs: Date.now() - startTime,
        };
        // Save final decision
        this.saveLog(deliberationId, decision);
        return decision;
    }
    /**
     * Check if a proposal should trigger council review.
     */
    shouldReview(proposal) {
        const category = this.detectCategory(proposal);
        if (category !== 'unknown') {
            return {
                shouldReview: true,
                category,
                reason: `Proposal matches '${category}' decision category`,
            };
        }
        return {
            shouldReview: false,
            category: 'none',
            reason: 'No high-stakes category detected',
        };
    }
    /**
     * Get all past decisions.
     */
    getHistory(limit = 20) {
        if (!fs.existsSync(this.logDir))
            return [];
        const files = fs.readdirSync(this.logDir)
            .filter((f) => f.startsWith('council_') && f.endsWith('.json'))
            .sort()
            .reverse()
            .slice(0, limit);
        return files.map((f) => {
            try {
                return JSON.parse(fs.readFileSync(path.join(this.logDir, f), 'utf-8'));
            }
            catch {
                return null;
            }
        }).filter((d) => d !== null);
    }
    /**
     * Get voice prompt templates (for customization).
     */
    static getVoicePrompts() {
        return { ...VOICE_PROMPTS };
    }
    /**
     * Get decision categories.
     */
    static getCategories() {
        return [...DECISION_CATEGORIES];
    }
    detectCategory(proposal) {
        const lower = proposal.toLowerCase();
        for (const cat of DECISION_CATEGORIES) {
            if (cat.triggers.some((t) => lower.includes(t))) {
                return cat.name;
            }
        }
        return 'unknown';
    }
    saveLog(id, data) {
        const logFile = path.join(this.logDir, `${id}.json`);
        try {
            fs.writeFileSync(logFile, JSON.stringify(data, null, 2));
        }
        catch {
            // Fire-and-forget
        }
    }
}
//# sourceMappingURL=council.js.map