"use strict";
/**
 * Plugin integrity enforcement (PLUGIN-INTEGRITY-01).
 *
 * Load-time SHA-256 integrity checking for plugins.
 * Every plugin is verified against a trusted manifest (plugins.lock.json)
 * before it is admitted into the governance runtime.
 *
 * Fail-closed by default: a plugin whose hash is absent from the manifest
 * or whose hash does not match is rejected. The `disableIntegrity` escape
 * hatch exists for development convenience but emits a WARN audit event so
 * auditors can see the bypass on the chain.
 *
 * Scope (PLUGIN-INTEGRITY-01): load-time only.
 * Runtime per-dispatch re-verification is tracked under PLUGIN-INTEGRITY-02.
 *
 * @connexum/ai-governance
 */
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 });
exports.PluginIntegrityError = void 0;
exports.computePluginSha256 = computePluginSha256;
exports.verifyPluginIntegrity = verifyPluginIntegrity;
exports.loadTrustedPluginManifest = loadTrustedPluginManifest;
exports.assertPluginTrusted = assertPluginTrusted;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
/**
 * Thrown when a plugin fails load-time integrity verification.
 * Extends Error directly (same base as GovernanceTamperError / PluginValidationError)
 * so catch(err instanceof Error) paths handle it without importing the hierarchy.
 */
class PluginIntegrityError extends Error {
    code = 'PLUGIN_INTEGRITY_FAILED';
    reason;
    pluginName;
    expected;
    actual;
    constructor(opts) {
        const detail = opts.reason === 'hash-mismatch'
            ? ` (expected ${opts.expected ?? '?'}, got ${opts.actual ?? '?'})`
            : opts.reason === 'unregistered-plugin'
                ? ' (not present in trusted manifest)'
                : ' (manifest map is empty -- all plugins are untrusted)';
        super(`Plugin integrity check failed for '${opts.pluginName}': ${opts.reason}${detail}`);
        this.name = 'PluginIntegrityError';
        this.reason = opts.reason;
        this.pluginName = opts.pluginName;
        this.expected = opts.expected;
        this.actual = opts.actual;
    }
}
exports.PluginIntegrityError = PluginIntegrityError;
// ---------------------------------------------------------------------------
// Core functions
// ---------------------------------------------------------------------------
/**
 * Compute the hex SHA-256 digest of plugin source code.
 * Input may be a string (UTF-8 encoded internally) or a raw Buffer.
 */
function computePluginSha256(pluginCode) {
    const buf = typeof pluginCode === 'string' ? Buffer.from(pluginCode, 'utf-8') : pluginCode;
    return crypto.createHash('sha256').update(buf).digest('hex');
}
/**
 * Verify a plugin's source against its trusted manifest entry.
 *
 * Returns `{ ok: true }` when the hash matches.
 * Returns a typed failure record otherwise -- callers decide whether to throw.
 */
function verifyPluginIntegrity(plugin, manifestEntry, manifestMap) {
    // Empty-manifest guard: if a map was supplied and it is empty, all plugins
    // are by definition unregistered. Surface this as a distinct reason so
    // operators can distinguish "manifest file missing" from "plugin just not
    // listed."
    if (manifestMap !== undefined && manifestMap.size === 0) {
        return { ok: false, reason: 'empty-manifest' };
    }
    if (!manifestEntry) {
        return { ok: false, reason: 'unregistered-plugin' };
    }
    const actual = computePluginSha256(plugin.source);
    if (actual !== manifestEntry.sha256) {
        return {
            ok: false,
            reason: 'hash-mismatch',
            expected: manifestEntry.sha256,
            actual,
        };
    }
    return { ok: true };
}
/**
 * Read `.governance/plugins.lock.json` (or an arbitrary path) and parse it
 * into a map keyed by plugin name.
 *
 * Rejects on:
 *   - File read failure
 *   - Malformed JSON
 *   - Root value that is not an array
 *   - Array entries that are missing `name` or `sha256`
 *
 * Returns an empty Map (not an error) when the file does not exist -- a
 * missing lockfile is a valid first-boot state. Callers that require a
 * non-empty manifest should call `assertPluginTrusted`, which will surface
 * `empty-manifest`.
 */
async function loadTrustedPluginManifest(manifestPath) {
    let raw;
    try {
        raw = await fs.promises.readFile(manifestPath, 'utf-8');
    }
    catch (err) {
        const code = err.code;
        if (code === 'ENOENT') {
            // File missing is NOT an error -- treat as empty manifest.
            return new Map();
        }
        throw new Error(`Failed to read plugin manifest at ${manifestPath}: ${err.message ?? String(err)}`);
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch (err) {
        throw new Error(`Plugin manifest at ${manifestPath} contains malformed JSON: ${err.message ?? String(err)}`);
    }
    if (!Array.isArray(parsed)) {
        throw new Error(`Plugin manifest at ${manifestPath} must be a JSON array, got ${typeof parsed}`);
    }
    const map = new Map();
    for (const entry of parsed) {
        if (typeof entry !== 'object' ||
            entry === null ||
            typeof entry.name !== 'string' ||
            typeof entry.sha256 !== 'string') {
            throw new Error(`Plugin manifest at ${manifestPath} contains an entry missing required fields 'name' (string) and 'sha256' (string)`);
        }
        const typed = entry;
        map.set(typed.name, typed);
    }
    return map;
}
/**
 * Assert that a plugin is trusted according to the manifest map.
 * Throws `PluginIntegrityError` if the check fails.
 *
 * This is the high-level convenience used by `PluginRegistry.register()`.
 */
function assertPluginTrusted(plugin, manifestMap) {
    const entry = manifestMap.get(plugin.name);
    const result = verifyPluginIntegrity(plugin, entry, manifestMap);
    if (!result.ok) {
        throw new PluginIntegrityError({
            reason: result.reason,
            pluginName: plugin.name,
            expected: result.expected,
            actual: result.actual,
        });
    }
}
//# sourceMappingURL=plugin-integrity.js.map