/**
 * dryRunCompare.ts — the Phase-1/3 dry-run/compare runner.
 *
 * docs/specs/feedback-factory-migration.md §2.5: the ported Instar processor runs
 * read-only against Portal's LIVE canonical DB and its decisions are compared to
 * Portal's over the order-independent invariants (parity.ts). Portal stays the SOLE
 * writer through cutover (the ReadOnlyShadowStore guard guarantees Instar never
 * mutates the curated history). This runner drives that comparison and emits a
 * durable JSONL audit trail; `result.divergent` is the structural signal that
 * blocks Phase 4 cutover.
 *
 * Two seams keep it buildable + testable today while the live pieces are
 * credentials-gated:
 *   - {@link ParitySource} is the read-only window source. In production it's a
 *     thin Prisma adapter over Portal's read-only Postgres (built once Dawn hands
 *     off read-credentials, via `prisma db pull` so it matches the live schema
 *     exactly). In tests it's {@link InMemoryParitySource} seeded with a corpus.
 *   - Invariant 1 (fingerprint) is fully live-ready: it recomputes each Portal
 *     cluster's fingerprint and diffs against the stored value — pure, no replay.
 *     Invariants 2 & 3 (status/recurrence) compare two outcome lists; Instar's side
 *     requires a faithful replay from window-START cluster state, which a
 *     current-state read cannot supply — so the runner accepts those outcome lists
 *     as inputs (supplied from a snapshot / full-history export) rather than
 *     deriving them from a point-in-time read. This is a documented data dependency,
 *     not an omission: the comparator and emit path handle all three invariants.
 */
import { appendFileSync, mkdirSync } from 'node:fs';
import { dirname } from 'node:path';
import { compareInvariants, } from '../processor/parity.js';
/** Flatten a {@link ParityResult} into the JSONL record stream (divergences then summary). */
export function toRecords(result, now) {
    const records = [];
    for (const d of result.fingerprintDivergences) {
        records.push({ kind: 'fingerprint-divergence', clusterId: d.clusterId, instar: d.instar, portal: d.portal });
    }
    for (const d of result.outcomeDivergences) {
        records.push({
            kind: 'outcome-divergence',
            fingerprint: d.fingerprint,
            divergence: d.kind,
            instar: d.instar,
            portal: d.portal,
        });
    }
    records.push({
        kind: 'summary',
        at: now,
        clustersCompared: result.clustersCompared,
        clustersWithFingerprint: result.clustersWithFingerprint,
        outcomesCompared: result.outcomesCompared,
        fingerprintDivergences: result.fingerprintDivergences.length,
        outcomeDivergences: result.outcomeDivergences.length,
        divergent: result.divergent,
    });
    return records;
}
/**
 * Run one dry-run/compare pass: read Portal's clusters, compare invariants, append
 * the JSONL audit trail, and return the verdict. NEVER writes to Portal's DB — the
 * source is read-only and the runner only reads + compares + appends a local log.
 */
export function runDryRunCompare(source, opts = {}) {
    const now = opts.now ?? new Date().toISOString();
    const portalClusters = source.readPortalClusters();
    const result = compareInvariants({
        portalClusters,
        instarOutcomes: opts.instarOutcomes,
        portalOutcomes: opts.portalOutcomes,
    });
    if (opts.outPath) {
        const lines = toRecords(result, now)
            .map((r) => JSON.stringify(r))
            .join('\n');
        mkdirSync(dirname(opts.outPath), { recursive: true });
        appendFileSync(opts.outPath, lines + '\n');
    }
    return result;
}
/** In-memory ParitySource — the test implementation + the shape the Prisma adapter mirrors. */
export class InMemoryParitySource {
    clusters;
    constructor(clusters) {
        this.clusters = clusters;
    }
    readPortalClusters() {
        return this.clusters.map((c) => ({ ...c }));
    }
}
//# sourceMappingURL=dryRunCompare.js.map