"use strict";
/**
 * CycleTokenBudgetEnforcer -- Per-(agentId, cycleId) token budget gate.
 *
 * Tracks cumulative tokens consumed per cycle. Called from PostToolUse
 * when the LLM adapter reports token usage. On breach the gate either
 * pauses the cycle (default, requires operator approval-queue resume)
 * or terminates it immediately.
 *
 * P3-T1 coverage gap: Per-cycle token budgets with auto-pause.
 *
 * Compliance bindings:
 *   - HIPAA 164.312(b): Audit Controls -- bounded per-cycle scope
 *   - SOC 2 CC6.1: access control / resource limits
 *   - EU AI Act Art. 9: risk management (bounding inference spend per cycle)
 *   - ISO 42001 A.6.1.4: resource management for AI systems
 *
 * @connexum/ai-governance
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.CycleTokenBudgetEnforcer = exports.CycleTokenBudgetExceededError = void 0;
// ---------------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------------
class CycleTokenBudgetExceededError extends Error {
    agentId;
    cycleId;
    tokensUsed;
    tokenBudget;
    packBinding;
    constructor(opts) {
        super(`Cycle token budget exceeded: agent '${opts.agentId}' cycle '${opts.cycleId}' ` +
            `consumed ${opts.tokensUsed} tokens (budget: ${opts.tokenBudget}). ` +
            `Cycle is paused until operator resumes via approval queue.`);
        this.name = 'CycleTokenBudgetExceededError';
        this.agentId = opts.agentId;
        this.cycleId = opts.cycleId;
        this.tokensUsed = opts.tokensUsed;
        this.tokenBudget = opts.tokenBudget;
        this.packBinding = opts.packBinding ?? [];
    }
}
exports.CycleTokenBudgetExceededError = CycleTokenBudgetExceededError;
// ---------------------------------------------------------------------------
// CycleTokenBudgetEnforcer
// ---------------------------------------------------------------------------
class CycleTokenBudgetEnforcer {
    config;
    /** Map key: `${agentId}::${cycleId}` */
    cycles = new Map();
    constructor(config) {
        if (config.perCycleMaxTokens <= 0) {
            throw new Error('CycleTokenBudgetEnforcer: perCycleMaxTokens must be > 0');
        }
        this.config = config;
    }
    /**
     * Resolve the token budget for a given agent and active pack set.
     */
    resolveBudget(agentId, activePackIds = []) {
        // 1. Per-agent override (highest precedence).
        if (this.config.perAgentOverrides[agentId] !== undefined) {
            return this.config.perAgentOverrides[agentId];
        }
        // 2. Per-pack override -- first matching active pack.
        for (const packId of activePackIds) {
            if (this.config.perPackOverrides[packId] !== undefined) {
                return this.config.perPackOverrides[packId];
            }
        }
        // 3. Default.
        return this.config.perCycleMaxTokens;
    }
    /**
     * PostToolUse hook entry point.
     *
     * Records `tokensUsed` for the cycle. If the cumulative total exceeds the
     * budget the gate marks the cycle as breached and emits an audit event.
     *
     * @param agentId      Identity of the calling agent.
     * @param cycleId      Unique identifier for the current agent cycle.
     * @param tokensUsed   Token count reported by the LLM adapter for this call.
     * @param activePackIds Active compliance pack IDs for override resolution.
     */
    recordUsage(agentId, cycleId, tokensUsed, activePackIds = []) {
        const key = `${agentId}::${cycleId}`;
        let state = this.cycles.get(key);
        if (!state) {
            state = { tokensUsed: 0, breached: false, terminated: false };
            this.cycles.set(key, state);
        }
        // If already terminated, no further accumulation.
        if (state.terminated)
            return;
        state.tokensUsed += tokensUsed;
        const budget = this.resolveBudget(agentId, activePackIds);
        if (state.tokensUsed > budget && !state.breached) {
            state.breached = true;
            if (this.config.onBreach === 'terminate') {
                state.terminated = true;
            }
            const auditEvent = {
                type: this.config.onBreach === 'terminate'
                    ? 'agent:cycle-terminated-by-budget'
                    : 'agent:cycle-paused-by-budget',
                agentId,
                cycleId,
                tokensUsed: state.tokensUsed,
                tokenBudget: budget,
                action: this.config.onBreach,
                packBinding: activePackIds,
                timestamp: new Date().toISOString(),
            };
            this.config.onBreached?.(auditEvent);
        }
    }
    /**
     * PreToolUse hook entry point.
     *
     * Throws CycleTokenBudgetExceededError if the cycle has been breached and
     * the breach action is 'pause', or if the cycle was terminated.
     *
     * @param agentId      Identity of the calling agent.
     * @param cycleId      Unique identifier for the current agent cycle.
     * @param activePackIds Active compliance pack IDs for override resolution.
     */
    guardPreTool(agentId, cycleId, activePackIds = []) {
        const key = `${agentId}::${cycleId}`;
        const state = this.cycles.get(key);
        if (!state)
            return; // No usage recorded yet -- allow.
        if (state.breached || state.terminated) {
            const budget = this.resolveBudget(agentId, activePackIds);
            throw new CycleTokenBudgetExceededError({
                agentId,
                cycleId,
                tokensUsed: state.tokensUsed,
                tokenBudget: budget,
                packBinding: activePackIds,
            });
        }
    }
    /**
     * Operator resume: clears the breached flag so the cycle can continue.
     * Only meaningful when `onBreach === 'pause'`. Terminated cycles cannot
     * be resumed (immutable).
     */
    resume(agentId, cycleId) {
        const state = this.cycles.get(`${agentId}::${cycleId}`);
        if (!state)
            return;
        if (state.terminated) {
            throw new Error(`Cannot resume cycle '${cycleId}' for agent '${agentId}': cycle was terminated (immutable).`);
        }
        state.breached = false;
    }
    /**
     * Return the current token count for a cycle. Useful for monitoring and tests.
     */
    getUsage(agentId, cycleId) {
        return this.cycles.get(`${agentId}::${cycleId}`)?.tokensUsed ?? 0;
    }
    /**
     * Return whether a cycle is currently paused (breached but not terminated).
     */
    isPaused(agentId, cycleId) {
        const s = this.cycles.get(`${agentId}::${cycleId}`);
        return !!s && s.breached && !s.terminated;
    }
    /**
     * Return whether a cycle was terminated.
     */
    isTerminated(agentId, cycleId) {
        return !!this.cycles.get(`${agentId}::${cycleId}`)?.terminated;
    }
    /** Remove cycle state (for testing / operator cleanup). */
    clearCycle(agentId, cycleId) {
        this.cycles.delete(`${agentId}::${cycleId}`);
    }
}
exports.CycleTokenBudgetEnforcer = CycleTokenBudgetEnforcer;
//# sourceMappingURL=cycle-token-budget.js.map