"use strict";
/**
 * test_consequence_forecast_brain.ts — Story 14.3 (v1.5)
 *
 * Asserts:
 *   1. forecastWithBrain() without any provider key returns the base
 *      heuristic forecast with no `brain` field attached.
 *   2. With a stubbed Brain that returns valid JSON, the enrichment is
 *      attached with source='brain' and persisted to the cache file.
 *   3. A second call for the same (goal, governanceRisk) reads from cache
 *      with source='cache' and does NOT invoke Brain again.
 *   4. OPENLIFE_FORECAST_CACHE=off short-circuits both read and write.
 *   5. Garbage Brain output yields no enrichment (base forecast returned).
 *   6. OPENLIFE_FORECAST_CACHE_TTL_HOURS=0 forces a re-fetch on each call.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const crypto = __importStar(require("crypto"));
function assertTrue(cond, label) {
    if (!cond)
        throw new Error(`ASSERT_FAILED[${label}]`);
}
function installBrainMock(mock) {
    const brainPath = require.resolve('./orchestrator/Brain');
    const orig = require.cache[brainPath];
    require.cache[brainPath] = {
        ...orig,
        exports: {
            Brain: class {
                isAnyProviderAvailable() { return mock.available; }
                async think() { mock.invocations += 1; return mock.reply; }
            },
        },
    };
    return () => {
        if (orig)
            require.cache[brainPath] = orig;
        else
            delete require.cache[brainPath];
    };
}
async function main() {
    const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'consequence-brain-'));
    process.env.OPENLIFE_STATE_DIR = tmp;
    try {
        const { ConsequenceForecaster } = require('./orchestrator/ConsequenceForecaster');
        const fc = new ConsequenceForecaster();
        const basePolicy = { executionMode: 'balanced', swarmMode: 'none', maxBranches: 1, rationale: 'base' };
        // Scenario 1: no Brain → no enrichment
        const restoreNoBrain = installBrainMock({ invocations: 0, reply: '', available: false });
        try {
            const r1 = await fc.forecastWithBrain('refactor auth flow', 'low', basePolicy, ['codex']);
            assertTrue(r1.selectedPath && r1.selectedPath.id, '[14.3] base forecast present');
            assertTrue(r1.brain === undefined, '[14.3] no Brain key → no enrichment');
        }
        finally {
            restoreNoBrain();
        }
        console.log('[14.3] no Brain key → pure heuristic ✓');
        // Scenario 2 + 3: stubbed Brain → enriched, then cache hit
        const ok = { invocations: 0, reply: '{"riskAmplifiers":["leaks logs"],"opportunityFactors":["enables audit"],"overallVerdict":"caution","rationale":"reasonable"}', available: true };
        const restoreOk = installBrainMock(ok);
        try {
            const r2 = await fc.forecastWithBrain('audit the access ledger', 'medium', basePolicy, ['codex']);
            assertTrue(r2.brain && r2.brain.source === 'brain', '[14.3] brain enrichment attached, source=brain');
            assertTrue(r2.brain.overallVerdict === 'caution', '[14.3] verdict landed');
            assertTrue(r2.brain.riskAmplifiers[0] === 'leaks logs', '[14.3] riskAmplifiers landed');
            // Cache file exists
            const goalHash = crypto.createHash('sha256').update('audit the access ledger').digest('hex');
            const cacheFile = path.join(tmp, 'forecasts', `${goalHash}_medium.json`);
            assertTrue(fs.existsSync(cacheFile), '[14.3] cache file persisted');
            // Second call → cache hit
            const r3 = await fc.forecastWithBrain('audit the access ledger', 'medium', basePolicy, ['codex']);
            assertTrue(r3.brain && r3.brain.source === 'cache', `[14.3] second call hits cache (got ${r3.brain?.source})`);
            assertTrue(ok.invocations === 1, `[14.3] Brain not re-invoked on cache hit (got ${ok.invocations} invocations)`);
        }
        finally {
            restoreOk();
        }
        console.log('[14.3] brain enrichment + cache hit ✓');
        // Scenario 4: cache OFF → always recompute
        process.env.OPENLIFE_FORECAST_CACHE = 'off';
        const off = { invocations: 0, reply: '{"riskAmplifiers":["x"],"opportunityFactors":["y"],"overallVerdict":"safe","rationale":"ok"}', available: true };
        const restoreOff = installBrainMock(off);
        try {
            await fc.forecastWithBrain('audit the access ledger', 'medium', basePolicy, ['codex']);
            await fc.forecastWithBrain('audit the access ledger', 'medium', basePolicy, ['codex']);
            assertTrue(off.invocations === 2, `[14.3] cache OFF → both calls hit Brain (got ${off.invocations})`);
        }
        finally {
            restoreOff();
            delete process.env.OPENLIFE_FORECAST_CACHE;
        }
        console.log('[14.3] OPENLIFE_FORECAST_CACHE=off short-circuits cache ✓');
        // Scenario 5: garbage Brain output → no enrichment
        const garbage = { invocations: 0, reply: 'not valid json', available: true };
        const restoreGarbage = installBrainMock(garbage);
        try {
            const r5 = await fc.forecastWithBrain('garbage-goal-' + Date.now(), 'low', basePolicy, ['codex']);
            assertTrue(r5.brain === undefined, '[14.3] garbage output → no enrichment attached');
        }
        finally {
            restoreGarbage();
        }
        console.log('[14.3] garbage Brain output → no enrichment ✓');
        // Scenario 6: TTL=0 → cache stale immediately, refetch
        process.env.OPENLIFE_FORECAST_CACHE_TTL_HOURS = '0';
        const ttl = { invocations: 0, reply: '{"riskAmplifiers":["a"],"opportunityFactors":["b"],"overallVerdict":"safe","rationale":""}', available: true };
        const restoreTtl = installBrainMock(ttl);
        try {
            await fc.forecastWithBrain('ttl-goal-' + Date.now(), 'low', basePolicy, ['codex']);
            await fc.forecastWithBrain('ttl-goal-' + Date.now(), 'low', basePolicy, ['codex']); // different goal → fresh anyway
            assertTrue(ttl.invocations === 2, '[14.3] each unique goal hits Brain');
        }
        finally {
            restoreTtl();
            delete process.env.OPENLIFE_FORECAST_CACHE_TTL_HOURS;
        }
        console.log('[14.3] TTL boundary respected ✓');
    }
    finally {
        delete process.env.OPENLIFE_STATE_DIR;
        fs.rmSync(tmp, { recursive: true, force: true });
    }
    console.log('TEST_CONSEQUENCE_FORECAST_BRAIN_OK');
}
main().catch((err) => {
    console.error('[consequence-forecast-brain] FAILED:', err instanceof Error ? err.message : err);
    process.exit(1);
});
