/**
 * CircuitBreaker -- Prevents cascading agent failures.
 * OWASP ASI08 remediation.
 *
 * States:
 *   CLOSED  - Normal operation. Failures tracked but below threshold.
 *   OPEN    - Agent suspended. All operations blocked.
 *   HALF    - Probationary. One test cycle allowed.
 *
 * @connexum/ai-governance
 */
import * as fs from 'fs';
import * as path from 'path';
export class CircuitBreaker {
    stateFile;
    failureThreshold;
    halfOpenAfterMs;
    agentName;
    constructor(config) {
        this.agentName = config.agentName;
        this.failureThreshold = config.failureThreshold ?? 3;
        this.halfOpenAfterMs = config.halfOpenAfterMs ?? 10 * 60 * 1000;
        if (!fs.existsSync(config.stateDir)) {
            fs.mkdirSync(config.stateDir, { recursive: true });
        }
        this.stateFile = path.join(config.stateDir, `circuit-${config.agentName}.json`);
    }
    /**
     * Check if the agent is allowed to execute.
     */
    allowExecution() {
        const state = this.loadState();
        switch (state.state) {
            case 'CLOSED':
                return true;
            case 'OPEN': {
                // Check if enough time has passed for half-open
                if (state.openedAt) {
                    const elapsed = Date.now() - new Date(state.openedAt).getTime();
                    if (elapsed >= this.halfOpenAfterMs) {
                        state.state = 'HALF_OPEN';
                        this.saveState(state);
                        return true;
                    }
                }
                return false;
            }
            case 'HALF_OPEN':
                return true;
            default:
                return false;
        }
    }
    /**
     * Record a successful operation.
     */
    recordSuccess() {
        const state = this.loadState();
        state.consecutiveFailures = 0;
        state.lastSuccess = new Date().toISOString();
        state.totalSuccesses++;
        if (state.state === 'HALF_OPEN') {
            state.state = 'CLOSED';
            state.openedAt = null;
        }
        this.saveState(state);
    }
    /**
     * Record a failed operation.
     */
    recordFailure(reason) {
        const state = this.loadState();
        state.consecutiveFailures++;
        state.lastFailure = new Date().toISOString();
        state.totalFailures++;
        if (state.state === 'HALF_OPEN') {
            // Failed during probation -- back to OPEN
            state.state = 'OPEN';
            state.openedAt = new Date().toISOString();
        }
        else if (state.consecutiveFailures >= this.failureThreshold) {
            state.state = 'OPEN';
            state.openedAt = new Date().toISOString();
        }
        this.saveState(state);
    }
    /**
     * Get current state (read-only).
     */
    getState() {
        return this.loadState();
    }
    /**
     * Manually reset circuit breaker to CLOSED.
     * Only for human operator use.
     */
    reset() {
        const state = this.loadState();
        state.state = 'CLOSED';
        state.consecutiveFailures = 0;
        state.openedAt = null;
        this.saveState(state);
    }
    loadState() {
        if (!fs.existsSync(this.stateFile)) {
            return {
                state: 'CLOSED',
                consecutiveFailures: 0,
                lastFailure: null,
                lastSuccess: null,
                openedAt: null,
                totalFailures: 0,
                totalSuccesses: 0,
            };
        }
        try {
            return JSON.parse(fs.readFileSync(this.stateFile, 'utf-8'));
        }
        catch {
            return {
                state: 'CLOSED',
                consecutiveFailures: 0,
                lastFailure: null,
                lastSuccess: null,
                openedAt: null,
                totalFailures: 0,
                totalSuccesses: 0,
            };
        }
    }
    saveState(state) {
        try {
            fs.writeFileSync(this.stateFile, JSON.stringify(state, null, 2));
        }
        catch (err) {
            // State save failure is critical -- a suspended agent could resume on restart
            process.stderr.write(`[CIRCUIT BREAKER CRITICAL] Failed to save state for ${this.agentName}: ${err.message}\n`);
            // Fail-closed: if we can't persist the OPEN state, throw so caller knows
            if (state.state === 'OPEN') {
                throw new Error(`Failed to persist OPEN circuit state for ${this.agentName}. ` +
                    `Agent may resume unsafely on restart.`);
            }
        }
    }
}
//# sourceMappingURL=circuit-breaker.js.map