"use strict";
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;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_ALLOW = void 0;
exports._setOperationFunctionMapForTest = _setOperationFunctionMapForTest;
exports._setChooseOperationForTest = _setChooseOperationForTest;
exports._setProcessLocalTransactionForTest = _setProcessLocalTransactionForTest;
exports.registerOperationsTools = registerOperationsTools;
/**
 * Operations-profile tool generation. Walks Harper's `OPERATION_FUNCTION_MAP`
 * and registers one MCP tool per operation that survives the
 * `mcp.operations.allow` / `deny` filter.
 *
 * The default v1 allow list is read-only and intentionally narrow:
 * `describe_*`, `list_*`, `search_*`, plus an explicit set of safe
 * getters (`get_job`, `get_status`, `get_analytics`, `get_metrics`),
 * `system_information`, `read_log`, `read_audit_log`. A `get_*` glob
 * was rejected because it would have matched `get_configuration`
 * (TLS/S3/auth secrets), `get_components` + `get_component_file` +
 * `get_custom_function*` (component source code, which can embed
 * secrets), `get_backup`, and `get_deployment{,_payload}` — none of
 * which should default into the MCP surface even though
 * `verifyPerms` still gates the actual call. Operators who want any
 * of those opt in via `mcp.operations.allow`. Destructive ops carry
 * `destructiveHint: true` so well-behaved MCP clients can surface a
 * confirmation prompt.
 *
 * Tool dispatch delegates to `chooseOperation` + `processLocalTransaction`
 * — the same path Harper's REST `/operation` endpoint uses. That means
 * `verifyPerms` runs unchanged, replication catchup runs unchanged, and
 * server-side validation errors surface as `isError: true` results without
 * the MCP layer needing to know what each operation expects.
 */
const env = __importStar(require("../../../utility/environment/environmentManager.js"));
const hdbTerms_ts_1 = require("../../../utility/hdbTerms.js");
const harper_logger_ts_1 = __importDefault(require("../../../utility/logging/harper_logger.js"));
const toolRegistry_ts_1 = require("../toolRegistry.js");
const operations_ts_1 = require("./schemas/operations.js");
const operationDescriptions_ts_1 = require("./schemas/operationDescriptions.js");
// Test seams. Avoids importing Harper's heavy server-helpers graph from unit
// tests that only want to exercise the registration logic.
let _opMapOverride;
let _chooseOperationOverride;
let _processLocalTransactionOverride;
function _setOperationFunctionMapForTest(m) {
    _opMapOverride = m;
}
function _setChooseOperationForTest(fn) {
    _chooseOperationOverride = fn;
}
function _setProcessLocalTransactionForTest(fn) {
    _processLocalTransactionOverride = fn;
}
function loadServerUtilities() {
    try {
        // Lazy require: Harper's server-helpers graph initializes eagerly
        // (RocksDB lock acquisition, schema preload). Loading it from a unit
        // test that hasn't booted Harper throws; treat that as "we're not in
        // a Harper process" and let callers gracefully no-op.
        return require('../../../server/serverHelpers/serverUtilities');
    }
    catch (err) {
        harper_logger_ts_1.default.trace(`MCP operations tools: serverUtilities unavailable (${err.message})`);
        return undefined;
    }
}
function getOperationFunctionMap() {
    if (_opMapOverride)
        return _opMapOverride;
    const utils = loadServerUtilities();
    return utils?.OPERATION_FUNCTION_MAP;
}
function getChooseOperation() {
    if (_chooseOperationOverride)
        return _chooseOperationOverride;
    return loadServerUtilities()?.chooseOperation;
}
function getProcessLocalTransaction() {
    if (_processLocalTransactionOverride)
        return _processLocalTransactionOverride;
    return loadServerUtilities()?.processLocalTransaction;
}
/**
 * Default v1 allow list — read-only operations only. Operators who want
 * destructive ops on the MCP surface opt in via `mcp.operations.allow`.
 */
exports.DEFAULT_ALLOW = [
    'describe_*',
    'list_*',
    'search_*',
    // Explicit safe getters only — see file-header rationale. `get_*` would
    // pull in `get_configuration`, `get_components`, `get_custom_function*`,
    // `get_backup`, and `get_deployment*`, all of which can leak secrets or
    // source code into the LLM context even with `verifyPerms` enforcing.
    'get_job',
    'get_status',
    'get_analytics',
    'get_metrics',
    'system_information',
    'read_log',
    'read_audit_log',
];
/**
 * Operations that carry `destructiveHint: true` when opted into the allow
 * list. The hint lets MCP clients surface a confirmation prompt before
 * calling. It is **not** an authorization check — Harper's `verifyPerms`
 * still runs at the actual dispatch site.
 */
const DESTRUCTIVE_OPERATIONS = new Set([
    'drop_schema',
    'drop_database',
    'drop_table',
    'drop_attribute',
    'delete',
    'delete_files_before',
    'delete_records_before',
    'delete_audit_logs_before',
    'delete_transaction_logs_before',
    'drop_user',
    'drop_role',
    'restart',
    'restart_service',
    'set_configuration',
    'remove_node',
]);
/**
 * Read-only operations carry `readOnlyHint: true`. The category is wider
 * than the default allow list (some custom-allowed ops are also read-only
 * — `system_information`, for example). Any op matching one of these
 * prefixes or names is treated as read-only.
 */
const READ_ONLY_PREFIXES = ['describe_', 'list_', 'search_', 'get_', 'read_'];
const READ_ONLY_NAMES = new Set(['system_information', 'status']);
/**
 * Operations annotated with `idempotentHint: true`. Under MCP semantics this
 * is a STRONGER claim than "doesn't crash on retry": the second call must
 * produce the same observable outcome as the first. `add_user` is NOT
 * idempotent in this sense — the second call returns an "already exists"
 * error rather than the created user.
 *
 * Default-empty. Entries are added only after verifying the handler's
 * repeat-call behavior end-to-end. Under-annotate before mis-annotate.
 *
 * Note: read-only operations (DESCRIBE_*, LIST_*, SEARCH_*, GET_*, READ_*,
 * system_information) are covered by `readOnlyHint: true` — that's the
 * stronger and correct signal for queries.
 */
const IDEMPOTENT_OPERATIONS = new Set([
// Intentionally empty for v1. Candidates that need pre-merge verification:
// - upsert (atomic insert-or-update; same payload should yield same state)
// - set_configuration (state-set semantics — confirm; if it's state-merge, NOT idempotent)
]);
function isReadOnly(operationName) {
    if (READ_ONLY_NAMES.has(operationName))
        return true;
    return READ_ONLY_PREFIXES.some((p) => operationName.startsWith(p));
}
function isDestructive(operationName) {
    return DESTRUCTIVE_OPERATIONS.has(operationName);
}
function isIdempotent(operationName) {
    return IDEMPOTENT_OPERATIONS.has(operationName);
}
/**
 * Translates a single glob pattern (only `*` is supported) into a regex.
 * `describe_*` matches `describe_schema`, `describe_table`, etc.; literals
 * like `system_information` match exactly. No escape hatch yet — operators
 * should use literals when they need them; the glob language is delibarately
 * minimal to keep behavior predictable in audit/security reviews.
 */
function globToRegex(pattern) {
    const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
    return new RegExp(`^${escaped}$`);
}
function matchesAny(operation, patterns) {
    if (!patterns || patterns.length === 0)
        return false;
    for (const p of patterns) {
        if (globToRegex(p).test(operation))
            return true;
    }
    return false;
}
function isOperationAllowed(operation, config) {
    const allowList = config.allow && config.allow.length > 0 ? config.allow : exports.DEFAULT_ALLOW;
    if (!matchesAny(operation, allowList))
        return false;
    if (matchesAny(operation, config.deny))
        return false;
    return true;
}
function getOperationsConfig() {
    const allow = env.get(hdbTerms_ts_1.CONFIG_PARAMS.MCP_OPERATIONS_ALLOW);
    const deny = env.get(hdbTerms_ts_1.CONFIG_PARAMS.MCP_OPERATIONS_DENY);
    return {
        allow: Array.isArray(allow) ? allow : undefined,
        deny: Array.isArray(deny) ? deny : undefined,
    };
}
function buildDescription(operationName, hasCuratedSchema) {
    const curated = operationDescriptions_ts_1.OPERATION_DESCRIPTIONS[operationName];
    if (curated)
        return curated;
    const base = `Harper operation '${operationName}'.`;
    const schemaNote = hasCuratedSchema
        ? ' Arguments validated against the curated schema below.'
        : ' Arguments forwarded as-is; the server validates and returns a structured error on rejection.';
    return base + schemaNote;
}
/**
 * Build the dispatch handler for one operation. Returns a function suitable
 * for `ToolDef.handler` that delegates to Harper's normal operation pipeline.
 *
 * Errors from `chooseOperation` (permission denied) or from the operation
 * itself surface as `isError: true` MCP results, not JSON-RPC errors —
 * matches the MCP spec's `tools/call` convention so the LLM sees and can
 * adapt to the failure.
 */
function makeOperationToolHandler(operationName) {
    return async function operationToolHandler(args, context) {
        const body = {
            ...(args && typeof args === 'object' ? args : {}),
            operation: operationName,
            hdb_user: context.user,
        };
        try {
            const chooseOperation = getChooseOperation();
            const processLocalTransaction = getProcessLocalTransaction();
            if (!chooseOperation || !processLocalTransaction) {
                throw new Error('Harper operations runtime unavailable');
            }
            const operationFn = chooseOperation(body);
            const data = await processLocalTransaction({ body }, operationFn);
            const text = typeof data === 'string' ? data : JSON.stringify(data ?? null);
            const result = {
                content: [{ type: 'text', text }],
            };
            if (data !== null && typeof data === 'object') {
                result.structuredContent = data;
            }
            return result;
        }
        catch (err) {
            const e = err;
            const message = e?.http_resp_msg ?? e?.message ?? `operation '${operationName}' failed`;
            harper_logger_ts_1.default.trace(`MCP operations/${operationName} threw: ${err.stack ?? message}`);
            return {
                isError: true,
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify({ kind: 'harper_error', operation: operationName, message }),
                    },
                ],
            };
        }
    };
}
/**
 * Idempotent registration: walk the op map, register every operation that
 * passes the allow/deny filter. Safe to invoke multiple times — `addTool`
 * is `Map.set`-backed.
 */
function registerOperationsTools() {
    const opMap = getOperationFunctionMap();
    if (!opMap) {
        harper_logger_ts_1.default.warn('MCP operations profile: OPERATION_FUNCTION_MAP not available; no tools registered');
        return;
    }
    const config = getOperationsConfig();
    let registered = 0;
    for (const operationName of opMap.keys()) {
        if (!isOperationAllowed(operationName, config))
            continue;
        const inputSchema = operations_ts_1.OPERATION_INPUT_SCHEMAS[operationName] ?? operations_ts_1.PERMISSIVE_SCHEMA;
        const annotations = {};
        if (isReadOnly(operationName))
            annotations.readOnlyHint = true;
        if (isDestructive(operationName))
            annotations.destructiveHint = true;
        if (isIdempotent(operationName))
            annotations.idempotentHint = true;
        (0, toolRegistry_ts_1.addTool)({
            name: operationName,
            description: buildDescription(operationName, operationName in operations_ts_1.OPERATION_INPUT_SCHEMAS),
            inputSchema,
            profile: 'operations',
            ...(Object.keys(annotations).length > 0 ? { annotations } : {}),
            visibleTo: (user) => (0, toolRegistry_ts_1.canRoleInvokeOperation)(user, operationName),
            handler: makeOperationToolHandler(operationName),
        });
        registered++;
    }
    harper_logger_ts_1.default.info(`MCP operations profile: registered ${registered} tool(s)`);
}
//# sourceMappingURL=operations.js.map