"use strict";
/**
 * ChainTamperDetector -- P1-TAMPER-A
 *
 * Periodic full-chain re-verification worker. Runs independently of agent
 * activity so dormant periods are not unobserved windows. Emits signed
 * liveness attestations on success and CRITICAL events on failure.
 *
 * Events emitted:
 *   chain.verified      -- every interval on success
 *   chain.tamper.detected -- on any failure (CRITICAL severity)
 *   chain.agent.paused  -- when pauseAgentsOnTamper=true and tamper fires
 *
 * Config floors (strictest-wins):
 *   HIPAA / HITECH binding: intervalMs floor = 60 000 ms
 *   SOC 2 binding:          intervalMs floor = 300 000 ms
 *   Dev (intervalMs === 0): disables the loop
 *
 * @connexum/ai-governance P1-TAMPER-A
 */
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.PolicyWeakeningRefusedError = exports.ChainTamperDetector = exports.SOC2_INTERVAL_FLOOR_MS = exports.HIPAA_INTERVAL_FLOOR_MS = void 0;
exports.resolveChainVerificationInterval = resolveChainVerificationInterval;
const crypto = __importStar(require("node:crypto"));
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/** HIPAA/HITECH pack floor: 60 seconds */
exports.HIPAA_INTERVAL_FLOOR_MS = 60_000;
/** SOC 2 pack floor: 300 seconds */
exports.SOC2_INTERVAL_FLOOR_MS = 300_000;
// ---------------------------------------------------------------------------
// ChainTamperDetector
// ---------------------------------------------------------------------------
class ChainTamperDetector {
    cfg;
    timer = null;
    paused = false;
    constructor(config) {
        this.cfg = {
            detectorId: 'chain-tamper-detector',
            hmacSecret: '',
            ...config,
        };
    }
    /**
     * Begin the periodic verification loop.
     * Silently skips starting if intervalMs === 0 (dev mode).
     */
    start() {
        if (this.cfg.intervalMs === 0)
            return;
        if (this.timer !== null)
            return; // Already running
        this.timer = setInterval(async () => {
            await this._runCycle();
        }, this.cfg.intervalMs);
        // Allow the Node.js process to exit even if this timer is still pending
        if (this.timer.unref)
            this.timer.unref();
    }
    /**
     * Stop the verification loop cleanly.
     */
    stop() {
        if (this.timer !== null) {
            clearInterval(this.timer);
            this.timer = null;
        }
    }
    /**
     * Run a verification cycle on-demand (used by AuditNarrativeGenerator
     * and the on-boot guard).
     */
    async verifyNow() {
        const result = this.cfg.auditIntegrity.verify();
        const stats = this.cfg.auditIntegrity.getStats();
        const verifiedAt = new Date().toISOString();
        const verification = {
            valid: result.valid,
            chainHash: stats.lastHash,
            eventCount: result.totalEntries,
            verifiedAt,
            brokenAt: result.brokenAt,
        };
        if (!result.valid) {
            // Extract hash mismatch detail from the first error message
            const firstError = result.errors[0] ?? '';
            const expectedMatch = firstError.match(/Expected ([0-9a-f]+)/);
            const observedMatch = firstError.match(/got ([0-9a-f]+)/);
            verification.firstBadEventId = `seq-${result.brokenAt}`;
            verification.expectedHash = expectedMatch?.[1] ?? '';
            verification.observedHash = observedMatch?.[1] ?? '';
        }
        return verification;
    }
    /**
     * Check whether agents are currently paused due to detected tampering.
     */
    isPaused() {
        return this.paused;
    }
    /**
     * Clear the paused state (called when a T2 reviewer appends chain.cleared).
     */
    clearPause() {
        this.paused = false;
    }
    // ---------------------------------------------------------------------------
    // Internal
    // ---------------------------------------------------------------------------
    async _runCycle() {
        const verification = await this.verifyNow();
        if (verification.valid) {
            if (this.cfg.emitLivenessAttestations) {
                this._emitVerified(verification);
            }
        }
        else {
            this._emitTamperDetected(verification);
            if (this.cfg.pauseAgentsOnTamper && !this.paused) {
                this.paused = true;
                this._emitAgentPaused(verification);
            }
        }
    }
    _hmacSign(payload) {
        if (!this.cfg.hmacSecret)
            return '';
        return crypto
            .createHmac('sha256', this.cfg.hmacSecret)
            .update(payload)
            .digest('hex');
    }
    _emitVerified(v) {
        const data = {
            eventType: 'chain.verified',
            chainHash: v.chainHash,
            eventCount: v.eventCount,
            verifiedAt: v.verifiedAt,
            verifierId: this.cfg.detectorId,
            intervalMs: this.cfg.intervalMs,
        };
        if (this.cfg.hmacSecret) {
            data.signature = this._hmacSign(JSON.stringify(data));
        }
        this._emit('chain.verified', data);
    }
    _emitTamperDetected(v) {
        const data = {
            eventType: 'chain.tamper.detected',
            severity: 'CRITICAL',
            firstBadEventId: v.firstBadEventId ?? `seq-${v.brokenAt}`,
            expectedHash: v.expectedHash ?? '',
            observedHash: v.observedHash ?? '',
            brokenAt: v.brokenAt,
            detectorId: this.cfg.detectorId,
        };
        this._emit('chain.tamper.detected', data);
    }
    _emitAgentPaused(v) {
        const data = {
            eventType: 'chain.agent.paused',
            reason: 'CHAIN_TAMPER_DETECTED',
            firstBadEventId: v.firstBadEventId ?? `seq-${v.brokenAt}`,
            detectorId: this.cfg.detectorId,
            pausedAt: new Date().toISOString(),
        };
        this._emit('chain.agent.paused', data);
    }
    _emit(type, data) {
        try {
            // EventBus.emit accepts a known EventType union; we use 'custom' for
            // tamper-specific events and embed the real eventType in data.
            this.cfg.eventBus.emit('custom', { ...data, _subtype: type });
        }
        catch {
            // Never let emit failure crash the detector
            process.stderr.write(`[CHAIN-TAMPER-DETECTOR] Failed to emit ${type} event\n`);
        }
    }
}
exports.ChainTamperDetector = ChainTamperDetector;
// ---------------------------------------------------------------------------
// Strictest-wins interval resolver
// ---------------------------------------------------------------------------
/**
 * Resolve the effective interval given a requested value and active pack IDs.
 * HIPAA/HITECH floor: 60 000 ms.
 * SOC 2 floor: 300 000 ms.
 * Any attempt to set an interval above the pack floor is silently clamped down.
 *
 * @throws PolicyWeakeningRefusedError if requestedMs exceeds the pack floor
 *         and the caller explicitly opted into strict mode.
 */
function resolveChainVerificationInterval(requestedMs, activePackIds) {
    if (requestedMs === 0)
        return 0; // Dev mode passthrough
    let floor = 0; // No floor by default
    const lowerIds = activePackIds.map((id) => id.toLowerCase());
    const hasHipaa = lowerIds.some((id) => id === 'hipaa' || id === 'hitech');
    const hasSoc2 = lowerIds.some((id) => id === 'soc2' || id === 'soc_2');
    if (hasHipaa)
        floor = Math.max(floor, exports.HIPAA_INTERVAL_FLOOR_MS);
    if (hasSoc2)
        floor = Math.max(floor, exports.SOC2_INTERVAL_FLOOR_MS);
    // HIPAA is stricter (shorter interval = more frequent = stricter)
    // "Strictest-wins" means the smallest (most frequent) floor wins
    const strictestFloor = hasHipaa ? exports.HIPAA_INTERVAL_FLOOR_MS : hasSoc2 ? exports.SOC2_INTERVAL_FLOOR_MS : 0;
    if (strictestFloor > 0 && requestedMs > strictestFloor) {
        throw new PolicyWeakeningRefusedError(`chainVerificationInterval ${requestedMs}ms exceeds pack floor ${strictestFloor}ms ` +
            `(active packs: ${activePackIds.join(', ')}). Reducing interval is required.`);
    }
    return requestedMs;
}
class PolicyWeakeningRefusedError extends Error {
    constructor(message) {
        super(message);
        this.name = 'PolicyWeakeningRefusedError';
    }
}
exports.PolicyWeakeningRefusedError = PolicyWeakeningRefusedError;
//# sourceMappingURL=chain-tamper-detector.js.map