/**
 * MemoryChainVerifier -- SHA-256 hash-chaining for memory state integrity.
 *
 * Every memory write produces a chained hash entry. On session start,
 * the verifier confirms that the current memory state matches the chain
 * head from the last session. A mismatch indicates memory poisoning
 * across sessions -- the session is refused to load and a CRITICAL audit
 * event is emitted.
 *
 * Each chain entry contains:
 *   { agentId, sessionId, previousHash, currentHash, writeOp, pseudonymized, timestamp }
 *
 * The genesis entry (cold start) is a well-known empty hash so that the
 * chain can be bootstrapped without a prior session.
 *
 * P3-T1 coverage gap: Memory-state audit chain verification.
 *
 * Compliance bindings:
 *   - SOC 2 CC7.2: monitoring of system components for tampering
 *   - HIPAA 164.312(c)(1): integrity controls
 *   - EU AI Act Art. 12: record-keeping requirements
 *   - ISO 42001 A.9.1: AI system monitoring
 *
 * @connexum/ai-governance
 */
import * as crypto from 'crypto';
// ---------------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------------
export class MemoryPoisoningDetectedError extends Error {
    agentId;
    sessionId;
    expectedHash;
    actualHash;
    constructor(opts) {
        super(`Memory poisoning detected for agent '${opts.agentId}' session '${opts.sessionId}'. ` +
            `Expected chain head '${opts.expectedHash.slice(0, 16)}...' but got '${opts.actualHash.slice(0, 16)}...'. ` +
            `Session load refused. Escalate to security team immediately.`);
        this.name = 'MemoryPoisoningDetectedError';
        this.agentId = opts.agentId;
        this.sessionId = opts.sessionId;
        this.expectedHash = opts.expectedHash;
        this.actualHash = opts.actualHash;
    }
}
// ---------------------------------------------------------------------------
// Genesis hash (empty state sentinel)
// ---------------------------------------------------------------------------
const GENESIS_HASH = crypto.createHash('sha256').update('connexum:memory-chain:genesis').digest('hex');
// ---------------------------------------------------------------------------
// MemoryChainVerifier
// ---------------------------------------------------------------------------
export class MemoryChainVerifier {
    config;
    /**
     * In-memory chain tail per agentId. Used when no persistAdapter is
     * configured or as a fast-path before persisting.
     */
    chainHeads = new Map();
    auditSink;
    constructor(config = {}, auditSink) {
        this.config = config;
        this.auditSink = auditSink ?? (() => undefined);
    }
    // -------------------------------------------------------------------------
    // Session start verification
    // -------------------------------------------------------------------------
    /**
     * Verify that memory state matches the chain at session start.
     *
     * `currentMemoryHash` is the hash the caller computed over the current
     * in-memory state (e.g. SHA-256 of sorted JSON of all keys+values).
     *
     * If a prior chain head exists and does not match, throws
     * `MemoryPoisoningDetectedError` and emits a CRITICAL audit event.
     * Never silently passes a mismatch.
     *
     * Cold start (no prior chain) always passes.
     */
    async verifyOnSessionStart(agentId, sessionId, currentMemoryHash) {
        const expectedHash = await this.loadHead(agentId);
        if (expectedHash === undefined) {
            // Cold start: record the genesis entry and proceed.
            await this.appendEntry({
                agentId,
                sessionId,
                previousHash: GENESIS_HASH,
                currentHash: currentMemoryHash,
                writeOp: 'session-open',
                pseudonymized: false,
            });
            return;
        }
        if (expectedHash !== currentMemoryHash) {
            const poisonEvent = {
                type: 'governance:memory-poisoning-detected',
                severity: 'CRITICAL',
                agentId,
                sessionId,
                expectedHash,
                actualHash: currentMemoryHash,
                timestamp: new Date().toISOString(),
            };
            this.auditSink(poisonEvent);
            throw new MemoryPoisoningDetectedError({
                agentId,
                sessionId,
                expectedHash,
                actualHash: currentMemoryHash,
            });
        }
        // Hash matches -- record the verified session-open entry.
        await this.appendEntry({
            agentId,
            sessionId,
            previousHash: expectedHash,
            currentHash: currentMemoryHash,
            writeOp: 'session-open',
            pseudonymized: false,
        });
    }
    // -------------------------------------------------------------------------
    // Write recording
    // -------------------------------------------------------------------------
    /**
     * Record a memory write operation. Must be called after every successful
     * write to `SessionMemoryStore` or `CrossSessionMemoryStore` so the chain
     * stays in sync with the memory state.
     */
    async recordWrite(agentId, sessionId, writeOp, newMemoryHash, pseudonymized) {
        const previousHash = this.chainHeads.get(agentId) ?? GENESIS_HASH;
        await this.appendEntry({
            agentId,
            sessionId,
            previousHash,
            currentHash: newMemoryHash,
            writeOp,
            pseudonymized,
        });
    }
    // -------------------------------------------------------------------------
    // Hash helpers
    // -------------------------------------------------------------------------
    /**
     * Compute a deterministic SHA-256 hash over a memory snapshot.
     * Keys are sorted to ensure determinism.
     */
    static hashMemorySnapshot(snapshot) {
        const sorted = Object.fromEntries(Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)));
        return crypto.createHash('sha256').update(JSON.stringify(sorted)).digest('hex');
    }
    /** Expose genesis hash for test introspection. */
    static get genesisHash() {
        return GENESIS_HASH;
    }
    // -------------------------------------------------------------------------
    // Internal helpers
    // -------------------------------------------------------------------------
    async loadHead(agentId) {
        if (this.config.persistAdapter) {
            const entry = await this.config.persistAdapter.loadHead(agentId);
            if (entry) {
                this.chainHeads.set(agentId, entry.currentHash);
                return entry.currentHash;
            }
            return undefined;
        }
        return this.chainHeads.get(agentId);
    }
    async appendEntry(opts) {
        const entry = {
            type: 'governance:memory-write',
            ...opts,
            timestamp: new Date().toISOString(),
        };
        this.chainHeads.set(opts.agentId, opts.currentHash);
        if (this.config.persistAdapter) {
            await this.config.persistAdapter.append(entry);
        }
        this.auditSink(entry);
    }
}
//# sourceMappingURL=memory-chain.js.map