"use strict";
/**
 * SelfReflectionLoopDetector -- Anomaly detection for runaway self-reflection cycles.
 *
 * A self-reflecting agent (Tier 1, Agent Type Coverage Strategy) inspects its own
 * output, decides it is not good enough, regenerates, and inspects again. Without
 * bounds this loops indefinitely, burning tokens and never converging.
 *
 * Detection criteria (all configurable):
 *   1. N consecutive (output -> self-check -> regenerate) triplets within windowMs.
 *   2. Jaccard similarity on 5-gram shingles between successive 'output' entries
 *      >= similarityThreshold: agent is stuck, not improving (non-converging).
 *   3. Absolute output count (PT-REFLECT-LOOP-2): any cycle exceeding maxOutputCount
 *      text-bearing entries fires regardless of action-type label.
 *
 * Similarity computation:
 *   We compare successive 'output'-kind entries only (not 'regenerate' vs 'output'
 *   within the same triplet, which would be trivially identical). This measures
 *   whether each new primary output differs from the previous primary output.
 *
 * On detection:
 *   - Emits SELF_REFLECTION_LOOP audit event via AuditIntegrity (chained SHA-256).
 *   - Calls CircuitBreaker.recordFailure() to escalate toward OPEN state.
 *   - Does NOT terminate the cycle directly -- composition over duplication.
 *
 * Pack-binding invariant:
 *   recordAction() throws if packBinding is empty (UnboundDataError equivalent).
 *
 * BUILD_PLAN V3 line 258: anomaly detection for self-reflection loops.
 *
 * Similarity metric: Jaccard on 5-gram character shingles.
 *   Rationale: fast, zero external dependencies, and robust to minor surface
 *   variation (whitespace, punctuation) that would fool simpler equality checks.
 *   Cosine similarity requires vector embeddings -- explicitly out of scope.
 *
 * @connexum/ai-governance
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SelfReflectionLoopDetector = void 0;
exports.computeShingles = computeShingles;
exports.jaccardSimilarity = jaccardSimilarity;
const crypto = __importStar(require("node:crypto"));
// ---------------------------------------------------------------------------
// Detector
// ---------------------------------------------------------------------------
class SelfReflectionLoopDetector {
    tripletThreshold;
    windowMs;
    similarityThreshold;
    maxOutputCount;
    circuitBreaker;
    auditIntegrity;
    /**
     * Ring buffer: cycleId -> ordered list of recent actions.
     * Cleared by clearCycle() when the cycle ends.
     */
    buffer = new Map();
    /** Detection log (capped at 500) for inspection by tests and operators. */
    detectionLog = [];
    constructor(config) {
        this.circuitBreaker = config.circuitBreaker;
        this.auditIntegrity = config.auditIntegrity;
        this.tripletThreshold = config.tripletThreshold ?? 5;
        this.windowMs = config.windowMs ?? 60_000;
        this.similarityThreshold = config.similarityThreshold ?? 0.85;
        this.maxOutputCount = config.maxOutputCount ?? this.tripletThreshold * 3;
    }
    // -------------------------------------------------------------------------
    // Public API
    // -------------------------------------------------------------------------
    /**
     * Record an action in a cycle's ring buffer and check for loop patterns.
     *
     * @throws {Error} if packBinding is empty (UnboundDataError guard).
     */
    recordAction(orgId, agentId, cycleId, action, packBinding) {
        if (!packBinding || packBinding.length === 0) {
            throw new Error(`[SelfReflectionLoopDetector] UnboundDataError: packBinding must not be empty. ` +
                `orgId=${orgId} agentId=${agentId} cycleId=${cycleId}`);
        }
        const nowMs = Date.now();
        const textHash = action.text ? hashText(action.text) : '';
        const shingles = action.text ? computeShingles(action.text) : new Set();
        const entry = {
            kind: action.kind,
            textHash,
            shingles,
            verdict: action.verdict,
            timestampMs: nowMs,
            orgId,
            agentId,
            packBinding,
        };
        // Retrieve or create ring buffer for this cycle
        if (!this.buffer.has(cycleId)) {
            this.buffer.set(cycleId, []);
        }
        const ring = this.buffer.get(cycleId);
        ring.push(entry);
        // Purge entries outside window
        const cutoff = nowMs - this.windowMs;
        while (ring.length > 0 && ring[0].timestampMs < cutoff) {
            ring.shift();
        }
        // Run detection
        return this.checkPattern(orgId, agentId, cycleId, ring, packBinding);
    }
    /**
     * Release the ring buffer for a completed cycle.
     * Call this when the cycle ends (success or failure).
     */
    clearCycle(cycleId) {
        this.buffer.delete(cycleId);
    }
    /**
     * Get the detection log (read-only copy).
     */
    getDetectionLog() {
        return [...this.detectionLog];
    }
    /**
     * Get the current ring-buffer size across all active cycles.
     * Useful for memory leak detection tests.
     */
    activeBufferCount() {
        return this.buffer.size;
    }
    // -------------------------------------------------------------------------
    // Pattern detection
    // -------------------------------------------------------------------------
    checkPattern(orgId, agentId, cycleId, ring, packBinding) {
        // -- Guard: PT-REFLECT-LOOP-2 absolute output count (label-spoofing) -----
        // Count any action in the ring that has text (regardless of label).
        const textEntries = ring.filter(e => e.textHash !== '');
        if (textEntries.length >= this.maxOutputCount) {
            // For similarity: prefer 'output'-kind consecutive pairs; fall back to any text pairs.
            const similarity = computeCrossIterationSimilarity(ring);
            return this.fireDetection(orgId, agentId, cycleId, this.tripletThreshold, similarity, packBinding);
        }
        // -- Primary triplet pattern: output -> self-check(regenerate) -> regenerate --
        const tripletCount = countTriplets(ring);
        if (tripletCount < this.tripletThreshold) {
            return null;
        }
        // Compute similarity between successive 'output'-kind entries.
        // We compare output(i-1) vs output(i) across triplet boundaries,
        // NOT within-triplet (output(i) vs regenerate(i)) which would trivially be 1.0.
        const similarity = computeCrossIterationSimilarity(ring);
        return this.fireDetection(orgId, agentId, cycleId, tripletCount, similarity, packBinding);
    }
    fireDetection(orgId, agentId, cycleId, tripletCount, similarity, packBinding) {
        const detectedAt = new Date().toISOString();
        const convergenceState = similarity >= this.similarityThreshold ? 'non-converging' : 'evolving';
        const payload = { orgId, agentId, cycleId, tripletCount, similarity, convergenceState, detectedAt, packBinding };
        const contentHash = crypto
            .createHash('sha256')
            .update(JSON.stringify(payload))
            .digest('hex');
        const detection = { ...payload, contentHash };
        // Append to internal log (capped)
        this.detectionLog.push(detection);
        if (this.detectionLog.length > 500) {
            this.detectionLog.shift();
        }
        // Emit to audit chain (hash only -- never raw text)
        this.emitAuditEvent(detection);
        // Escalate via CircuitBreaker
        this.circuitBreaker.recordFailure(`SELF_REFLECTION_LOOP detected: agentId=${agentId} cycleId=${cycleId} ` +
            `triplets=${tripletCount} convergence=${convergenceState} similarity=${similarity.toFixed(3)}`);
        process.stderr.write(`[SelfReflectionLoopDetector] SELF_REFLECTION_LOOP: orgId=${orgId} agentId=${agentId} ` +
            `cycleId=${cycleId} triplets=${tripletCount} similarity=${similarity.toFixed(3)} ` +
            `convergence=${convergenceState} hash=${contentHash}\n`);
        return detection;
    }
    emitAuditEvent(detection) {
        const eventData = {
            type: 'SELF_REFLECTION_LOOP',
            severity: 'ERROR',
            orgId: detection.orgId,
            agentId: detection.agentId,
            cycleId: detection.cycleId,
            tripletCount: detection.tripletCount,
            similarity: detection.similarity,
            convergenceState: detection.convergenceState,
            packBinding: detection.packBinding,
            contentHash: detection.contentHash,
            timestamp: detection.detectedAt,
            // Raw output text is NEVER logged -- only hashes are recorded
        };
        if (this.auditIntegrity) {
            try {
                this.auditIntegrity.append(eventData);
            }
            catch (err) {
                process.stderr.write(`[SelfReflectionLoopDetector] Audit chain write failed: ${err.message}\n`);
            }
        }
        else {
            // Fallback: structured stderr (still captures prevHash concept via contentHash)
            process.stderr.write(`[AUDIT] SELF_REFLECTION_LOOP ${JSON.stringify(eventData)}\n`);
        }
    }
}
exports.SelfReflectionLoopDetector = SelfReflectionLoopDetector;
// ---------------------------------------------------------------------------
// Triplet counting
// ---------------------------------------------------------------------------
/**
 * Walk the ring left-to-right and count consecutive
 * (output -> self-check[verdict='regenerate'] -> regenerate) triplets.
 */
function countTriplets(ring) {
    let count = 0;
    let i = 0;
    while (i + 2 < ring.length) {
        const a = ring[i];
        const b = ring[i + 1];
        const c = ring[i + 2];
        if (a.kind === 'output' &&
            b.kind === 'self-check' &&
            b.verdict?.toLowerCase().includes('regenerate') &&
            c.kind === 'regenerate') {
            count++;
            i += 3; // Advance past this triplet
        }
        else {
            i++;
        }
    }
    return count;
}
// ---------------------------------------------------------------------------
// Cross-iteration similarity
// ---------------------------------------------------------------------------
/**
 * Compute Jaccard similarity between the last two successive 'output'-kind entries.
 *
 * We specifically compare 'output' entries from adjacent triplets (not 'regenerate'
 * within the same triplet against the prior 'output', which would trivially be 1.0
 * if both use the same source text). This measures whether the agent's primary
 * output is actually improving across self-check cycles.
 *
 * Falls back to any consecutive text-bearing pair if fewer than two 'output' entries
 * are available.
 */
function computeCrossIterationSimilarity(ring) {
    // Prefer output-kind entries (canonical primary outputs)
    const outputEntries = ring.filter(e => e.kind === 'output' && e.textHash !== '');
    if (outputEntries.length >= 2) {
        return jaccardSimilarity(outputEntries[outputEntries.length - 2].shingles, outputEntries[outputEntries.length - 1].shingles);
    }
    // Fall back to any text-bearing entries
    const textEntries = ring.filter(e => e.textHash !== '');
    if (textEntries.length >= 2) {
        return jaccardSimilarity(textEntries[textEntries.length - 2].shingles, textEntries[textEntries.length - 1].shingles);
    }
    return 0;
}
// ---------------------------------------------------------------------------
// Jaccard similarity on 5-gram character shingles
// ---------------------------------------------------------------------------
const SHINGLE_SIZE = 5;
/**
 * Compute 5-gram character shingle set for a text string.
 * Normalises whitespace to single spaces before shingling.
 */
function computeShingles(text) {
    // Normalise: lowercase, collapse whitespace
    const normalised = text.toLowerCase().replace(/\s+/g, ' ').trim();
    const shingles = new Set();
    for (let i = 0; i <= normalised.length - SHINGLE_SIZE; i++) {
        shingles.add(normalised.slice(i, i + SHINGLE_SIZE));
    }
    return shingles;
}
/**
 * Compute Jaccard similarity between two shingle sets.
 * J(A, B) = |A intersect B| / |A union B|
 * Returns 0 when both sets are empty.
 */
function jaccardSimilarity(a, b) {
    if (a.size === 0 && b.size === 0)
        return 0;
    let intersection = 0;
    for (const s of a) {
        if (b.has(s))
            intersection++;
    }
    const union = a.size + b.size - intersection;
    return union === 0 ? 0 : intersection / union;
}
// ---------------------------------------------------------------------------
// SHA-256 text fingerprint (never log raw text)
// ---------------------------------------------------------------------------
function hashText(text) {
    return crypto.createHash('sha256').update(text).digest('hex');
}
//# sourceMappingURL=anomaly-self-reflection.js.map