/**
 * Gate Integrity Guard — runtime tamper detection for protected files
 *
 * The applier's protectedFiles filter only constrains what the MODEL writes
 * through file blocks. Gate execution runs project code — including test
 * files the model just created — and that code can `writeFileSync` over a
 * protected spec or helper at runtime, defeating the static filter.
 *
 * This guard snapshots the bytes of every protected file after the baseline
 * run, then re-verifies after each gate run: any mutated protected file is
 * restored from the snapshot and the run's gate result is discarded as a
 * GATE INTEGRITY VIOLATION, with feedback telling the model exactly why.
 * Protected paths that did not exist at snapshot time ("reserved" oracle
 * paths) are restored to absence — a runtime-fabricated golden is removed.
 */
import { createHash } from 'crypto';
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
import { dirname, resolve } from 'path';
/** Per-file restoration budget; larger files are hash-verified only. */
const MAX_RESTORE_BYTES = 1_000_000;
/** Total bytes of restoration content kept in memory. */
const MAX_TOTAL_RESTORE_BYTES = 32_000_000;
function sha256(buf) {
    return createHash('sha256').update(buf).digest('hex');
}
/**
 * Capture the on-disk state of every protected file (original-case relative
 * paths). Absent paths are recorded as reserved. Fail-soft per file.
 */
export function captureIntegrity(projectRoot, files) {
    const root = resolve(projectRoot);
    const snapshot = new Map();
    let totalBytes = 0;
    for (const rel of files) {
        const abs = resolve(root, rel);
        try {
            if (!existsSync(abs)) {
                snapshot.set(rel, { hash: null, content: null });
                continue;
            }
            const bytes = readFileSync(abs);
            const keep = bytes.length <= MAX_RESTORE_BYTES && totalBytes + bytes.length <= MAX_TOTAL_RESTORE_BYTES;
            if (keep)
                totalBytes += bytes.length;
            snapshot.set(rel, { hash: sha256(bytes), content: keep ? bytes : null });
        }
        catch {
            // Unreadable — skip; the applier-level protection still applies.
        }
    }
    return snapshot;
}
/**
 * Verify every captured file against its snapshot; restore what changed.
 * Returns what was tampered with so the caller can discard gate results.
 */
export function verifyAndRestore(projectRoot, snapshot) {
    const root = resolve(projectRoot);
    const check = { tampered: [], restored: [], unrecoverable: [] };
    for (const [rel, record] of snapshot) {
        const abs = resolve(root, rel);
        try {
            const existsNow = existsSync(abs);
            if (record.hash === null) {
                // Reserved path: must stay absent. A runtime-fabricated oracle is removed.
                if (existsNow) {
                    check.tampered.push(rel);
                    rmSync(abs, { force: true });
                    check.restored.push(rel);
                }
                continue;
            }
            if (!existsNow) {
                check.tampered.push(rel);
                if (record.content) {
                    mkdirSync(dirname(abs), { recursive: true });
                    writeFileSync(abs, record.content);
                    check.restored.push(rel);
                }
                else {
                    check.unrecoverable.push(rel);
                }
                continue;
            }
            const bytes = readFileSync(abs);
            if (sha256(bytes) !== record.hash) {
                check.tampered.push(rel);
                if (record.content) {
                    writeFileSync(abs, record.content);
                    check.restored.push(rel);
                }
                else {
                    check.unrecoverable.push(rel);
                }
            }
        }
        catch {
            check.unrecoverable.push(rel);
            if (!check.tampered.includes(rel))
                check.tampered.push(rel);
        }
    }
    return check;
}
/** Render the violation feedback prepended to discarded gate results. */
export function integrityViolationFeedback(check) {
    const lines = [
        `GATE INTEGRITY VIOLATION: test execution modified protected file(s): ${check.tampered.join(', ')}.`,
        'Gate results for this turn are DISCARDED. Protected test/oracle files must never be written —',
        'not via file blocks and not at runtime from test code. Implement the source instead.',
    ];
    if (check.unrecoverable.length > 0) {
        lines.push(`Could not restore: ${check.unrecoverable.join(', ')} — manual attention needed.`);
    }
    else {
        lines.push('All modified files were restored to their original state.');
    }
    return lines.join('\n');
}
//# sourceMappingURL=integrity.js.map