/**
 * parityMonitorStore.ts — durable persistence for the Phase-3 parity monitor.
 *
 * The zero-divergence window that gates cutover spans HOURS of real dual-forward traffic.
 * If the monitor's passes lived only in memory, a server restart mid-window would silently
 * reset the streak — at best re-blocking cutover (conservative, survivable), at worst making
 * the gate's `windowMs` meaningless across the very restart it must survive. So passes are
 * appended to a JSONL and reloaded on construction: the window is durable across restarts.
 *
 * Append-only JSONL (one MonitorPass per line) matches Instar's file-based-state convention
 * and is crash-safe (a torn final line is skipped on reload, never corrupting the rest).
 * The pure ParityMonitor stays the gate brain; this is the I/O shell around it.
 */
import { appendFileSync, readFileSync, mkdirSync, existsSync } from 'node:fs';
import { dirname } from 'node:path';
import { ParityMonitor } from './parityMonitor.js';
/** The default JSONL-on-disk persistence (append-only, torn-line tolerant). */
export class JsonlPassPersistence {
    path;
    constructor(path) {
        this.path = path;
    }
    load() {
        if (!existsSync(this.path))
            return [];
        const out = [];
        const lines = readFileSync(this.path, 'utf8').split('\n');
        for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed)
                continue;
            try {
                const p = JSON.parse(trimmed);
                // Defensive: only accept well-formed pass records.
                if (typeof p.at === 'string' && typeof p.clustersCompared === 'number' && typeof p.divergences === 'number') {
                    out.push({ at: p.at, clustersCompared: p.clustersCompared, divergences: p.divergences, divergent: !!p.divergent });
                }
            }
            catch {
                // A torn final line (crash mid-append) is skipped, never corrupting the prior window.
                // @silent-fallback-ok — durability is best-effort-forward; a bad line is dropped, loudly recoverable.
            }
        }
        return out;
    }
    append(pass) {
        mkdirSync(dirname(this.path), { recursive: true });
        appendFileSync(this.path, JSON.stringify(pass) + '\n');
    }
}
/**
 * A ParityMonitor whose passes survive a restart. Reloads persisted passes on construction,
 * then every record() both persists and feeds the in-memory monitor. gate() delegates to the
 * monitor over the full (reloaded + new) history.
 */
export class DurableParityMonitor {
    monitor;
    persistence;
    constructor(persistence, policy = {}) {
        this.persistence = persistence;
        this.monitor = new ParityMonitor(policy);
        for (const p of persistence.load())
            this.monitor.record(p);
    }
    record(pass) {
        this.persistence.append(pass);
        this.monitor.record(pass);
    }
    recordResult(result, at) {
        const divergences = result.fingerprintDivergences.length + result.outcomeDivergences.length;
        this.record({ at, clustersCompared: result.clustersCompared, divergences, divergent: divergences > 0 });
    }
    gate(now) {
        return this.monitor.gate(now);
    }
    get passes() {
        return this.monitor.passes;
    }
}
//# sourceMappingURL=parityMonitorStore.js.map