"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.ensureSessionTable = ensureSessionTable;
exports._setSessionTableForTest = _setSessionTableForTest;
exports.createSession = createSession;
exports.loadSession = loadSession;
exports.saveSession = saveSession;
exports.deleteSession = deleteSession;
exports.touchSession = touchSession;
/**
 * MCP session store backed by the `system.mcp_session` Harper table.
 *
 * Eviction is delegated to Harper's native TTL (`Table.setTTLExpiration`):
 * every write to a session record updates its `version`, which Harper uses
 * to determine expiration. So calling `saveSession(record)` on each request
 * gives sliding-window idle semantics for free — no custom timer, no sweep.
 *
 * Spec: when a request bears an `Mcp-Session-Id` the server doesn't
 * recognize (expired, terminated, or unknown), the server MUST return HTTP
 * 404 so the client can re-`initialize`. That decision lives in the
 * transport core; this module only reports `null` for "not found".
 */
const uuid_1 = require("uuid");
const databases_ts_1 = require("../../resources/databases.js");
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 rateLimit_ts_1 = require("./rateLimit.js");
const sessionRegistry_ts_1 = require("./sessionRegistry.js");
const toolRegistry_ts_1 = require("./toolRegistry.js");
const TABLE_NAME = 'mcp_session';
const DATABASE_NAME = 'system';
/** Default idle timeout when `mcp.session.idleTimeoutSeconds` is omitted. */
const DEFAULT_IDLE_TIMEOUT_SECONDS = 1800;
/**
 * Window between expiration and physical eviction. Short, but long enough
 * to absorb clock skew and let a late client request resolve to a tombstone
 * 404 rather than a "session never existed" 404. (The client recovery path
 * is identical either way.)
 */
const EVICTION_WINDOW_SECONDS = 60;
let _sessionTable;
/**
 * Lazily declare the system table. Called by `ensureSessionTable()` at
 * component-init. Declaring lazily lets unit tests that don't boot a real
 * Harper instance skip the table entirely.
 */
function declareSessionTable() {
    const idleTimeoutSeconds = env.get(hdbTerms_ts_1.CONFIG_PARAMS.MCP_SESSION_IDLETIMEOUTSECONDS) ?? DEFAULT_IDLE_TIMEOUT_SECONDS;
    return (0, databases_ts_1.table)({
        table: TABLE_NAME,
        database: DATABASE_NAME,
        expiration: idleTimeoutSeconds,
        eviction: idleTimeoutSeconds + EVICTION_WINDOW_SECONDS,
        attributes: [
            { name: 'id', isPrimaryKey: true },
            { name: 'protocolVersion' },
            { name: 'initialized' },
            { name: 'user' },
            { name: 'createdAt' },
            { name: 'lastActivity' },
        ],
    });
}
/**
 * Initialize the session table. Called from `handleApplication(scope)` and
 * `registerMcpProfile()` when the MCP component boots. Idempotent.
 */
function ensureSessionTable() {
    if (!_sessionTable) {
        _sessionTable = declareSessionTable();
        harper_logger_ts_1.default.trace(`MCP session table system.${TABLE_NAME} initialized`);
    }
    return _sessionTable;
}
/** Test seam: allow tests to inject a fake table without touching Harper. */
function _setSessionTableForTest(fake) {
    _sessionTable = fake;
}
function getTable() {
    if (!_sessionTable)
        throw new Error('MCP session table not initialized');
    return _sessionTable;
}
async function createSession({ user, protocolVersion, }) {
    const now = Date.now();
    const record = {
        id: (0, uuid_1.v4)(),
        protocolVersion,
        initialized: false,
        user,
        createdAt: now,
        lastActivity: now,
    };
    await getTable().put(record);
    return record;
}
/**
 * Look up a session by id. Returns the record if present and not expired,
 * else `null`. The transport core maps `null` to HTTP 404.
 */
async function loadSession(id) {
    const record = (await getTable().get(id));
    if (!record)
        return null;
    return record;
}
/**
 * Persist updated session state. Used to bump `lastActivity` (sliding-window
 * idle reset) and to flip `initialized` after `notifications/initialized`.
 */
async function saveSession(record) {
    await getTable().put(record);
}
async function deleteSession(id) {
    await getTable().delete(id);
    // Tear down ancillary per-session in-memory state — the `tools/list`
    // pagination cache and the per-session rate-limit buckets. Without
    // these, every session that ever paged or called a tool leaves orphan
    // entries until the process restarts.
    (0, toolRegistry_ts_1.clearSessionCache)(id);
    (0, rateLimit_ts_1.clearSessionRateState)(id);
    (0, sessionRegistry_ts_1.unregisterSession)(id);
}
/**
 * Convenience: touch `lastActivity` and persist. Returns the updated
 * record so the caller doesn't re-fetch.
 */
async function touchSession(record) {
    const touched = { ...record, lastActivity: Date.now() };
    await saveSession(touched);
    return touched;
}
//# sourceMappingURL=session.js.map