"use strict";
/**
 * circuit-breaker-preflight.ts
 *
 * ASI-08 Cascading Failures — wires CircuitBreaker into the PreToolUse
 * enforcement path by default.
 *
 * BEFORE this module existed, CircuitBreaker.allowExecution() was opt-in:
 * callers had to explicitly instantiate a breaker and call allowExecution()
 * themselves (the only caller was ClaudeCodeAdapter in llm-adapters/claude.ts).
 * This was an audit finding: the proxy's evaluatePreFlight() path had no
 * circuit-breaker check, so an OPEN breaker was silently bypassed on every
 * proxy-routed tool call.
 *
 * This module provides evaluatePreFlightWithBreaker() — a drop-in wrapper
 * around the proxy evaluatorFn that:
 *   1. Checks the per-agent CircuitBreaker BEFORE dispatching the evaluatorFn.
 *   2. Returns a structured DENY verdict (PackVerdictResult) when the breaker
 *      is OPEN — does NOT throw, does NOT call the evaluatorFn.
 *   3. Emits an audit event (OWASP-tagged ASI-08) through the optional
 *      onAuditEvent callback so the event surfaces in the audit chain.
 *   4. Fails ADVISORY (ALLOW) if the breaker state store itself is unavailable
 *      / throws — per Locked Invariant 3 (graceful degradation, no throw on
 *      infra error). An advisory audit event is still emitted so operators can
 *      see the degradation.
 *   5. Short-circuits gracefully when no circuitBreaker is provided (config
 *      opt-in not set), falling through to the evaluatorFn unchanged.
 *
 * Invariants honoured:
 *   - Invariant 2 (advisory, never authoritative): a genuine OPEN state DOES
 *     block (that is the point — the breaker tripped because consecutive
 *     failures were recorded). This is the customer's per-agent local policy,
 *     not the platform. The platform check itself (evaluatorFn) is never
 *     reached when the breaker is OPEN.
 *   - Invariant 3 (graceful degradation): infra error on the breaker → ALLOW
 *     with advisory audit event. The SDK never throws.
 *
 * Usage:
 *   import { evaluatePreFlightWithBreaker } from './circuit-breaker-preflight.js';
 *
 *   const result = await evaluatePreFlightWithBreaker(
 *     { circuitBreaker, agentId, evaluatorFn, onAuditEvent },
 *     tenantId,
 *     requestBody,
 *   );
 *
 * @connexum/ai-governance  ASI-08 Cascading Failures — OWASP Agentic Top 10
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluatePreFlightWithBreaker = evaluatePreFlightWithBreaker;
/**
 * Run the circuit-breaker check BEFORE the PreToolUse evaluatorFn.
 *
 * This is the default-on enforcement path for ASI-08.
 *
 * Behaviour matrix:
 *   circuitBreaker absent   → pass through to evaluatorFn (no-op).
 *   circuitBreaker CLOSED   → pass through to evaluatorFn.
 *   circuitBreaker OPEN     → DENY immediately; emit 'circuit:breaker:tripped'.
 *   circuitBreaker throws   → ALLOW (advisory fail-open); emit 'circuit:breaker:advisory'.
 */
async function evaluatePreFlightWithBreaker(opts, tenantId, requestBody) {
    const { circuitBreaker, agentId, evaluatorFn, onAuditEvent } = opts;
    // --- No breaker configured: transparent pass-through ---
    if (!circuitBreaker) {
        return runEvaluatorFn(evaluatorFn, tenantId, agentId, requestBody);
    }
    // --- Check breaker state ---
    let allowed;
    try {
        allowed = circuitBreaker.allowExecution();
    }
    catch (err) {
        // Invariant 3: breaker state store unavailable → advisory fail-open.
        // We must NOT throw and must NOT hard-block the agent's execution path.
        const errMsg = err instanceof Error ? err.message : String(err);
        onAuditEvent?.('circuit:breaker:advisory', {
            agentId,
            owaspTag: 'ASI-08',
            reason: `circuit breaker state store unavailable (advisory fail-open): ${errMsg}`,
            timestamp: new Date().toISOString(),
        });
        return {
            verdict: 'ALLOW',
            triggeringPackIds: [],
            triggeringClassifierIds: [],
            reason: `Circuit breaker check advisory fail-open: state store unavailable (${errMsg})`,
            timestamp: Date.now(),
        };
    }
    // --- Breaker is OPEN: block the tool call ---
    if (!allowed) {
        onAuditEvent?.('circuit:breaker:tripped', {
            agentId,
            owaspTag: 'ASI-08',
            reason: 'Circuit breaker is OPEN. Agent tool call blocked (ASI-08 cascading failure prevention).',
            timestamp: new Date().toISOString(),
        });
        return {
            verdict: 'DENY',
            triggeringPackIds: ['ASI-08'],
            triggeringClassifierIds: ['circuit-breaker'],
            reason: `Circuit breaker OPEN for agent "${agentId}". Tool call denied to prevent cascading failures (ASI-08).`,
            timestamp: Date.now(),
        };
    }
    // --- Breaker is CLOSED / HALF_OPEN: pass through to evaluatorFn ---
    return runEvaluatorFn(evaluatorFn, tenantId, agentId, requestBody);
}
// ---------------------------------------------------------------------------
// Internal
// ---------------------------------------------------------------------------
async function runEvaluatorFn(evaluatorFn, tenantId, agentId, requestBody) {
    const model = typeof requestBody['model'] === 'string' ? requestBody['model'] : 'unknown';
    const promptText = extractPromptText(requestBody);
    return evaluatorFn(tenantId, agentId, 'proxy:anthropic:messages:request', {
        model,
        promptText,
        contentLength: promptText.length,
        isStreaming: requestBody['stream'] === true,
    });
}
function extractPromptText(body) {
    try {
        const parts = [];
        if (typeof body['system'] === 'string')
            parts.push(body['system']);
        if (Array.isArray(body['messages'])) {
            for (const msg of body['messages']) {
                if (msg && typeof msg === 'object') {
                    const content = msg['content'];
                    if (typeof content === 'string') {
                        parts.push(content);
                    }
                    else if (Array.isArray(content)) {
                        for (const block of content) {
                            if (block && typeof block === 'object') {
                                const text = block['text'];
                                if (typeof text === 'string')
                                    parts.push(text);
                            }
                        }
                    }
                }
            }
        }
        return parts.join('\n');
    }
    catch {
        return '';
    }
}
//# sourceMappingURL=circuit-breaker-preflight.js.map