import { describe, expect, it } from 'vitest';
import { runFalsificationHarness } from './miner-harness.js';
const sha = (n) => String(n).padStart(40, '0');
/**
 * A contract-clean miner run: corpus [1,2,3,4], train [1,2] (both emitted),
 * heldOut [3,4] (controls 3/4), no drops, no held-out fetches, seed-blind.
 */
function greenLedgers() {
    return {
        emission: {
            entries: [
                {
                    candidateRef: 'c1',
                    provenance: { mergedPr: 1, reviewThread: 'rt-1', commitSha: sha(1) },
                    classifierDisposition: 'structural',
                    routing: 'compile',
                    classifierLedgerRef: 'cl-1',
                    unverified: true,
                },
                {
                    candidateRef: 'c2',
                    provenance: { mergedPr: 2, reviewThread: 'rt-2', commitSha: sha(2) },
                    classifierDisposition: 'structural',
                    routing: 'compile',
                    classifierLedgerRef: 'cl-2',
                    unverified: true,
                },
            ],
            extractionInputsAttestation: { seedClassesProvided: false },
        },
        drop: { entries: [] },
        classifier: {
            entries: [
                {
                    candidateRef: 'cl-1',
                    disposition: 'structural',
                    stage4Confirmed: true,
                    dispositionSource: 'classified',
                    stage4Outcome: 'confirmed',
                },
                {
                    candidateRef: 'cl-2',
                    disposition: 'structural',
                    stage4Confirmed: true,
                    dispositionSource: 'classified',
                    stage4Outcome: 'confirmed',
                },
            ],
        },
        split: {
            split: {
                asOfCommit: sha(100),
                trainPrs: [1, 2],
                heldOutPrs: [3, 4],
                excludedPrs: [],
                positiveControlPrs: [3],
                negativeControlPrs: [4],
                splitRule: { predicate: 'code-touching non-bot', cutIndex: 2 },
            },
            corpus: [1, 2, 3, 4],
            corpusMergeCommits: [1, 2, 3, 4].map((pr) => ({ pr, mergeCommit: sha(pr) })),
        },
        apiUsage: {
            entries: [
                { targetPr: 1, slice: 'train', fetchKind: 'review-thread' },
                { targetPr: 2, slice: 'train', fetchKind: 'review-thread' },
            ],
            heldOutFetchCount: 0,
        },
    };
}
// Deep clone via JSON so mutations don't bleed across cases (no Dates/fns here).
function clone(x) {
    return JSON.parse(JSON.stringify(x));
}
/** Distinct FM clauses the harness reports for a raw ledger object, sorted. */
function clauses(raw) {
    const r = runFalsificationHarness(raw);
    return [...new Set(r.violations.map((v) => v.clause))].sort();
}
describe('runFalsificationHarness — green', () => {
    it('passes a contract-clean miner run (no FM clause holds)', () => {
        const r = runFalsificationHarness(greenLedgers());
        expect(r.ok).toBe(true);
        expect(r.violations).toEqual([]);
    });
    it('allows a train PR in BOTH the emission and drop ledgers (FM(i) is at-least-one, not XOR)', () => {
        const g = clone(greenLedgers());
        // PR 2 already has an emitted candidate (c2); a second candidate from the same
        // PR's review thread is dropped — in-both is legitimate, not a violation.
        g.drop.entries.push({ sourcePr: 2, reasonCode: 'truncated' });
        expect(runFalsificationHarness(g).ok).toBe(true);
    });
});
describe('runFalsificationHarness — each red fixture fails on EXACTLY its clause', () => {
    it('(a) incomplete provenance tuple', () => {
        const g = clone(greenLedgers());
        delete g.emission.entries[0].provenance.commitSha;
        expect(clauses(g)).toEqual(['a']);
    });
    it('(b) candidate minted non-unverified', () => {
        const g = clone(greenLedgers());
        g.emission.entries[0].unverified = false;
        expect(clauses(g)).toEqual(['b']);
    });
    it('(c) behavioral candidate routed to compile', () => {
        const g = clone(greenLedgers());
        g.emission.entries[0].classifierDisposition = 'behavioral';
        expect(clauses(g)).toEqual(['c']);
    });
    it('(c) emission disposition disagrees with the classifier ledger', () => {
        const g = clone(greenLedgers());
        g.classifier.entries[0].disposition = 'behavioral'; // emission cl-1 attests structural
        g.emission.entries[0].routing = 'rag-only'; // isolate the mismatch from compile-routing
        expect(clauses(g)).toEqual(['c']);
    });
    it('(d) out-of-corpus split member', () => {
        const g = clone(greenLedgers());
        g.split.split.heldOutPrs = [3, 4, 99];
        g.split.corpusMergeCommits.push({ pr: 99, mergeCommit: sha(99) });
        expect(clauses(g)).toEqual(['d']);
    });
    it('(e-split) control outside heldOut (slice disjointness)', () => {
        const g = clone(greenLedgers());
        g.split.split.positiveControlPrs = [1]; // a train PR, not in heldOut
        expect(clauses(g)).toEqual(['e-split']);
    });
    it('(e-split) a PR tagged as both positive and negative control', () => {
        const g = clone(greenLedgers());
        g.split.split.negativeControlPrs = [3, 4]; // 3 is already a positive control
        expect(clauses(g)).toEqual(['e-split']);
    });
    it('(e-emission) candidate sourced from a held-out PR', () => {
        const g = clone(greenLedgers());
        g.emission.entries.push({
            candidateRef: 'c3',
            provenance: { mergedPr: 3, reviewThread: 'rt-3', commitSha: sha(3) }, // PR 3 is held-out
            classifierDisposition: 'structural',
            routing: 'compile',
            classifierLedgerRef: 'cl-3',
            unverified: true,
        });
        g.classifier.entries.push({
            candidateRef: 'cl-3',
            disposition: 'structural',
            stage4Confirmed: true,
            dispositionSource: 'classified',
            stage4Outcome: 'confirmed',
        });
        expect(clauses(g)).toEqual(['e-emission']);
    });
    it('(e-emission) provenance commitSha does not match the PR frozen merge commit', () => {
        const g = clone(greenLedgers());
        g.emission.entries[0].provenance.commitSha = sha(99); // PR 1's frozen merge commit is sha(1)
        expect(clauses(g)).toEqual(['e-emission']);
    });
    it('(f) seed class supplied to extraction', () => {
        const g = clone(greenLedgers());
        g.emission.extractionInputsAttestation.seedClassesProvided = true;
        expect(clauses(g)).toEqual(['f']);
    });
    it('(g) silent corpus drop', () => {
        const g = clone(greenLedgers());
        g.split.split.heldOutPrs = [3];
        g.split.split.negativeControlPrs = [];
        expect(clauses(g)).toEqual(['g']);
    });
    it('(h) held-out content fetch', () => {
        const g = clone(greenLedgers());
        g.apiUsage.heldOutFetchCount = 1;
        expect(clauses(g)).toEqual(['h']);
    });
    it('(h) a held-out PR fetched under a mislabeled slice:train', () => {
        const g = clone(greenLedgers());
        // PR 3 is held-out; labeling the fetch "train" must NOT hide it (partition is derived).
        g.apiUsage.entries.push({ targetPr: 3, slice: 'train', fetchKind: 'review-thread' });
        expect(clauses(g)).toEqual(['h']);
    });
    it('(i) train PR processed by neither emission nor drop', () => {
        const g = clone(greenLedgers());
        g.emission.entries.pop(); // remove the PR 2 candidate, do not drop it either
        g.classifier.entries.pop();
        expect(clauses(g)).toEqual(['i']);
    });
});
describe('runFalsificationHarness — stage4 consistency (slice 4; NOT an FM letter)', () => {
    it('flags a confirmed outcome whose stage4Confirmed is false (the OQ4 red craft)', () => {
        const g = clone(greenLedgers());
        g.classifier.entries[0].stage4Confirmed = false; // still stage4Outcome: 'confirmed'
        expect(clauses(g)).toEqual(['stage4-consistency']);
    });
    it('flags stage4Confirmed:true with no recorded outcome (cannot be confirmed without evidence)', () => {
        const g = clone(greenLedgers());
        delete g.classifier.entries[0].stage4Outcome;
        expect(clauses(g)).toEqual(['stage4-consistency']);
    });
    it('flags a compile-rejected outcome marked stage4Confirmed:true', () => {
        const g = clone(greenLedgers());
        g.classifier.entries[0].stage4Outcome = 'compile-rejected'; // stage4Confirmed still true
        expect(clauses(g)).toEqual(['stage4-consistency']);
    });
    it('passes archived-out-of-scope with stage4Confirmed:false (legitimately unconfirmed — agy fold-3)', () => {
        const g = clone(greenLedgers());
        g.classifier.entries[0].stage4Confirmed = false;
        g.classifier.entries[0].stage4Outcome = 'archived-out-of-scope';
        expect(runFalsificationHarness(g).ok).toBe(true);
    });
    it('passes untested-no-matches with stage4Confirmed:false (legitimately unconfirmed)', () => {
        const g = clone(greenLedgers());
        g.classifier.entries[0].stage4Confirmed = false;
        g.classifier.entries[0].stage4Outcome = 'untested-no-matches';
        expect(runFalsificationHarness(g).ok).toBe(true);
    });
});
describe('runFalsificationHarness — schema completeness', () => {
    it('rejects a split ledger whose corpusMergeCommits omits a corpus PR', () => {
        const g = clone(greenLedgers());
        g.split.corpusMergeCommits = g.split.corpusMergeCommits.filter((e) => e.pr !== 3);
        const r = runFalsificationHarness(g);
        expect(r.ok).toBe(false);
        expect(r.violations.some((v) => v.clause === 'schema')).toBe(true);
    });
    it('rejects a non-SHA mergeCommit in the split ledger', () => {
        const g = clone(greenLedgers());
        g.split.corpusMergeCommits[0].mergeCommit = 'not-a-sha';
        const r = runFalsificationHarness(g);
        expect(r.ok).toBe(false);
        expect(r.violations.some((v) => v.clause === 'schema')).toBe(true);
    });
});
//# sourceMappingURL=miner-harness.test.js.map