import fsp from 'node:fs/promises';
import path from 'node:path';
import { mutationLedgerPath } from './mutation-ledger.js';
export const SIDE_EFFECT_RUNTIME_REPORT_SCHEMA = 'sks.side-effect-runtime-report.v1';
const GLOBAL_KINDS = new Set(['global_config_write', 'codex_app_flag_change', 'codex_lb_auth_change', 'skill_snapshot_promotion']);
export async function buildSideEffectRuntimeReport(root) {
    const ledgerPaths = await discoverLedgerPaths(root);
    const entries = (await Promise.all(ledgerPaths.map(readLedger))).flat();
    const applied = entries.filter((entry) => entry.applied === true);
    const unexpected = applied.filter((entry) => entry.violation === true || entry.requested_scope_allowed !== true);
    const globalMutations = applied.filter((entry) => GLOBAL_KINDS.has(entry.kind));
    const globalWithoutConfirmation = globalMutations.filter((entry) => entry.requested_scope_allowed !== true);
    const configWithoutBackup = globalMutations.filter((entry) => !entry.backup_path && !entry.no_op_reason);
    return {
        schema: SIDE_EFFECT_RUNTIME_REPORT_SCHEMA,
        ok: unexpected.length === 0 && globalWithoutConfirmation.length === 0 && configWithoutBackup.length === 0,
        generated_at: new Date().toISOString(),
        ledger_paths: ledgerPaths.map((file) => path.relative(root, file)),
        total_entries: entries.length,
        applied_entries: applied.length,
        unexpected_applied_mutations: unexpected.length,
        global_mutations_without_confirmation: globalWithoutConfirmation.length,
        config_mutations_without_backup_or_noop: configWithoutBackup.length,
        global_mutations: globalMutations.map((entry) => ({
            route: entry.route,
            kind: entry.kind,
            target: entry.target,
            backup_path: entry.backup_path ?? null,
            no_op_reason: entry.no_op_reason ?? null,
            ...(entry.reason ? { reason: entry.reason } : {})
        }))
    };
}
async function discoverLedgerPaths(root) {
    const found = new Set();
    await addIfExists(found, mutationLedgerPath(root));
    await walkForLedgers(path.join(root, '.sneakoscope', 'missions'), found);
    return [...found].sort();
}
async function walkForLedgers(dir, found) {
    let entries;
    try {
        entries = await fsp.readdir(dir, { withFileTypes: true });
    }
    catch {
        return;
    }
    for (const entry of entries) {
        const file = path.join(dir, entry.name);
        if (entry.isDirectory())
            await walkForLedgers(file, found);
        else if (entry.isFile() && entry.name === 'mutation-ledger.jsonl')
            found.add(file);
    }
}
async function addIfExists(found, file) {
    try {
        await fsp.access(file);
        found.add(file);
    }
    catch { }
}
async function readLedger(file) {
    let text = '';
    try {
        text = await fsp.readFile(file, 'utf8');
    }
    catch {
        return [];
    }
    return text
        .split('\n')
        .map((line) => line.trim())
        .filter(Boolean)
        .flatMap((line) => {
        try {
            return [JSON.parse(line)];
        }
        catch {
            return [];
        }
    });
}
//# sourceMappingURL=side-effect-runtime-report.js.map