/**
 * PerAgentDailyBudgetEnforcer -- Per-(orgId, agentId) 24-hour rolling token
 * budget with hard FREEZE on breach.
 *
 * Freeze semantics (different from per-cycle pause):
 *   - Pause (CycleTokenBudgetEnforcer, PR #12): a single cycle is held. An
 *     operator can extend the cycle budget and resume. The agent keeps running.
 *   - Freeze (this module): the AGENT is frozen. All in-flight cycles are
 *     terminated. No new cycles can start. No re-extension is allowed.
 *     The freeze persists until either (a) the 24-hour window elapses and
 *     auto-thaw applies (lenient packs only), or (b) an OPERATOR_ADMIN
 *     unfreezes with a documented attestation.
 *
 * Rationale: a per-cycle overrun is a recoverable operational event. A
 * per-agent-daily overrun signals systemic misuse and warrants a harder stop
 * that surfaces to a human with documented reasoning before the agent resumes.
 *
 * Rolling 24-hour window (NOT calendar-day):
 *   Window start = first usage timestamp recorded in the current 24h period.
 *   Avoids the midnight-UTC cliff that would allow an agent to spend its full
 *   budget just before and just after midnight, doubling effective spend within
 *   minutes. Rolling windows are timezone-neutral for international operators.
 *
 * Auto-thaw after 24h:
 *   For lenient packs: when now > windowStartsAt + 24h the window resets and
 *   the freeze clears automatically.
 *   For strict packs (HIPAA, GDPR, PCI DSS, HMDA, DORA): the freeze REMAINS
 *   even after the daily window elapses. An OPERATOR_ADMIN must unfreeze with
 *   a documented attestation. Rationale: in regulated environments an agent
 *   that burned its entire daily budget is a compliance event. Auto-resuming
 *   it after midnight would be a gap auditors would flag -- the human must
 *   review the root cause before re-enabling the agent.
 *
 * v0 storage: in-memory map. State is lost on process restart.
 * v1 plan: persist agent state snapshots to a durable KV store (Redis or a
 *   lightweight SQLite sidecar) on every write. On startup, hydrate from
 *   store. Freeze state must survive process restart in regulated deployments
 *   because a frozen agent that auto-resumes after a pod restart is a
 *   compliance bypass. The v1 store key: `agent-daily-budget:{orgId}:{agentId}`.
 *
 * P3-T2 coverage gap: BUILD_PLAN_V3_FULL_LIFECYCLE.md line 273.
 *
 * Compliance bindings:
 *   - HIPAA 164.312(b): Audit Controls -- bounded inference spend per agent
 *   - HIPAA 164.308(a)(5): Security Awareness -- agent-level budget as
 *     anomaly signal
 *   - SOC 2 CC6.1: Access control / resource limits
 *   - EU AI Act Art. 9: Risk management (bounded daily agent scope)
 *   - ISO 42001 A.6.1.4: Resource management for AI systems
 *   - DORA Art. 17: ICT incident management -- freeze as incident trigger
 *   - PCI DSS Req 7: Restrict agent resource access by business need
 *
 * @connexum/ai-governance
 */
import * as crypto from 'node:crypto';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
/** 24-hour window in milliseconds. */
export const DAILY_WINDOW_MS = 24 * 60 * 60 * 1000;
/**
 * Strict packs require explicit operator unfreeze even after the daily window
 * elapses. Auto-thaw is blocked for these pack IDs.
 */
export const STRICT_PACK_IDS = new Set([
    'hipaa',
    'hipaa-security',
    'hipaa-privacy',
    '45-cfr-164',
    'gdpr',
    'gdpr-eu',
    'eu-gdpr',
    'pci-dss',
    'pcidss',
    'hmda',
    'dora',
    'dora-eu',
]);
// ---------------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------------
export class AgentFrozenError extends Error {
    orgId;
    agentId;
    frozenSince;
    frozenReason;
    packBinding;
    constructor(opts) {
        super(`Agent '${opts.agentId}' in org '${opts.orgId}' is frozen due to daily token budget breach ` +
            `(since ${opts.frozenSince}). No new cycles may start. An OPERATOR_ADMIN must unfreeze ` +
            `with a documented attestation, or the daily window must elapse (strict-pack agents require ` +
            `explicit unfreeze regardless of window).`);
        this.name = 'AgentFrozenError';
        this.orgId = opts.orgId;
        this.agentId = opts.agentId;
        this.frozenSince = opts.frozenSince;
        this.frozenReason = opts.frozenReason;
        this.packBinding = opts.packBinding;
    }
}
export class UnfreezePermissionError extends Error {
    operatorId;
    reason;
    constructor(operatorId, reason) {
        super(`Unfreeze denied for operator '${operatorId}': ${reason}. ` +
            `Only OPERATOR_ADMIN identities may unfreeze a frozen agent.`);
        this.name = 'UnfreezePermissionError';
        this.operatorId = operatorId;
        this.reason = reason;
    }
}
export class UnfreezeNonceReplayError extends Error {
    nonce;
    constructor(nonce) {
        super(`Unfreeze nonce '${nonce}' has already been used. ` +
            `Replay of a prior unfreeze call is rejected. ` +
            `Generate a new attestation with a fresh nonce for each unfreeze action.`);
        this.name = 'UnfreezeNonceReplayError';
        this.nonce = nonce;
    }
}
export class EmptyPackBindingError extends Error {
    operation;
    constructor(operation) {
        super(`Operation '${operation}' requires a non-empty packBinding. ` +
            `Per the Connexum AI Governance pack-binding-primacy invariant, ` +
            `every record must carry at least one compliance pack binding.`);
        this.name = 'EmptyPackBindingError';
        this.operation = operation;
    }
}
// ---------------------------------------------------------------------------
// PerAgentDailyBudgetEnforcer
// ---------------------------------------------------------------------------
export class PerAgentDailyBudgetEnforcer {
    /**
     * Agent configurations. Key: `${orgId}::${agentId}`
     */
    configs = new Map();
    /**
     * Runtime state. Key: `${orgId}::${agentId}`
     *
     * v0: in-memory only. v1 plan: hydrate from durable store on startup,
     * persist on every write. Freeze state must survive process restart.
     */
    state = new Map();
    /**
     * Set of operator IDs that hold ADMIN role.
     * The enforcer maintains its own ADMIN set rather than calling out to
     * OperatorRoles to avoid runtime coupling -- callers are expected to pass
     * admin IDs at construction time or via registerAdmin().
     *
     * Alternatively, inject an OperatorRoles.checkPermission function.
     */
    adminOperatorIds = new Set();
    /**
     * Audit event callback. Called for every event emitted by this enforcer.
     */
    onAudit;
    /**
     * Seen unfreeze nonces -- replay prevention.
     * Key: nonce string. Value: timestamp it was used.
     */
    usedNonces = new Map();
    /**
     * SHA chain state (simple in-memory chain for the enforcer's own events).
     * In production wire to the external AuditIntegrity chain.
     */
    lastSha = '';
    constructor(opts = {}) {
        this.onAudit = opts.onAudit ?? (() => undefined);
        if (opts.checkPermission) {
            this._checkPermission = opts.checkPermission;
        }
        if (opts.adminOperatorIds) {
            for (const id of opts.adminOperatorIds) {
                this.adminOperatorIds.add(id);
            }
        }
    }
    /** Optional injected permission checker. */
    _checkPermission;
    // ---------------------------------------------------------------------------
    // Admin management
    // ---------------------------------------------------------------------------
    /** Register an operator ID as having ADMIN authority over this enforcer. */
    registerAdmin(operatorId) {
        this.adminOperatorIds.add(operatorId);
    }
    // ---------------------------------------------------------------------------
    // Public API
    // ---------------------------------------------------------------------------
    /**
     * Configure the daily token budget for an agent.
     *
     * Operator-only. Verifies the approverIdentity holds governance.configure
     * permission (ADMIN role). Empty packBinding throws EmptyPackBindingError.
     *
     * @param orgId           Tenant / organization ID.
     * @param agentId         Agent identifier.
     * @param dailyBudgetTokens  Daily token budget (input + output).
     * @param approverIdentity   Operator identity performing this configuration.
     * @param packBinding     Non-empty compliance pack binding.
     */
    configure(orgId, agentId, dailyBudgetTokens, approverIdentity, packBinding) {
        if (!packBinding || packBinding.length === 0) {
            throw new EmptyPackBindingError('configure');
        }
        this.assertAdminPermission(approverIdentity, 'configure');
        if (dailyBudgetTokens <= 0) {
            throw new Error(`PerAgentDailyBudgetEnforcer.configure: dailyBudgetTokens must be > 0 ` +
                `(got ${dailyBudgetTokens})`);
        }
        const key = this.stateKey(orgId, agentId);
        this.configs.set(key, {
            dailyBudgetTokens,
            packBinding,
            configuredBy: approverIdentity,
            configuredAt: new Date().toISOString(),
        });
        this.emit({
            type: 'AGENT_DAILY_BUDGET_CONFIGURED',
            severity: 'INFO',
            orgId,
            agentId,
            packBinding,
            dailyBudgetTokens,
            configuredBy: approverIdentity,
        });
    }
    /**
     * Record token usage after an LLM call.
     *
     * Called post-LLM-call alongside the per-cycle counter. Returns remaining
     * tokens and current status. If the budget is exceeded on this call, the
     * agent is FROZEN, all in-flight cycles are terminated, and
     * AGENT_DAILY_BUDGET_FROZEN + CYCLE_TERMINATED_BY_DAILY_FREEZE events are
     * emitted.
     *
     * PT-DAILY-FREEZE-2 (token-count understatement): if the reported output
     * token count is below the byte-length / 8 floor, the byte-derived estimate
     * is used and a TOKEN_COUNT_ANOMALY WARNING event is emitted.
     *
     * @param orgId         Tenant / organization ID.
     * @param agentId       Agent identifier.
     * @param inputTokens   Input token count for this LLM call.
     * @param outputTokens  Output token count for this LLM call.
     * @param packBinding   Non-empty compliance pack binding.
     * @param cycleId       Optional cycle ID to register as active.
     * @param outputBytes   Optional byte length of LLM output (for anomaly detection).
     */
    recordUsage(orgId, agentId, inputTokens, outputTokens, packBinding, cycleId, outputBytes) {
        if (!packBinding || packBinding.length === 0) {
            throw new EmptyPackBindingError('recordUsage');
        }
        const cfg = this.requireConfig(orgId, agentId);
        const key = this.stateKey(orgId, agentId);
        const agentState = this.getOrCreateState(key);
        // Check if window has expired and auto-thaw applies.
        this.maybeResetWindow(orgId, agentId, agentState, cfg, packBinding);
        // If already frozen (and window has not reset), return frozen immediately.
        if (agentState.frozen) {
            return {
                remainingTokens: Math.max(0, cfg.dailyBudgetTokens - agentState.consumedTokens),
                status: 'frozen',
            };
        }
        // Initialise window on first usage of this 24h period.
        if (agentState.windowStartMs === null) {
            agentState.windowStartMs = Date.now();
        }
        // Register cycle as active if provided.
        if (cycleId) {
            agentState.activeCycleIds.add(cycleId);
        }
        // PT-DAILY-FREEZE-2: anomaly detection for token-count understatement.
        const effectiveOutputTokens = this.resolveOutputTokens(orgId, agentId, outputTokens, outputBytes, packBinding);
        const tokensThisCall = inputTokens + effectiveOutputTokens;
        agentState.consumedTokens += tokensThisCall;
        const remaining = cfg.dailyBudgetTokens - agentState.consumedTokens;
        if (agentState.consumedTokens > cfg.dailyBudgetTokens) {
            // Trigger freeze.
            const frozenSince = new Date().toISOString();
            const frozenReason = `Daily token budget exceeded: consumed ${agentState.consumedTokens} ` +
                `of ${cfg.dailyBudgetTokens} tokens in the current 24-hour window.`;
            agentState.frozen = true;
            agentState.frozenSince = frozenSince;
            agentState.frozenReason = frozenReason;
            // Terminate all in-flight cycles.
            const cyclesTerminated = Array.from(agentState.activeCycleIds);
            for (const cid of cyclesTerminated) {
                this.emit({
                    type: 'CYCLE_TERMINATED_BY_DAILY_FREEZE',
                    severity: 'ERROR',
                    orgId,
                    agentId,
                    cycleId: cid,
                    packBinding,
                    frozenSince,
                    reason: frozenReason,
                });
            }
            agentState.activeCycleIds.clear();
            this.emit({
                type: 'AGENT_DAILY_BUDGET_FROZEN',
                severity: 'ERROR',
                orgId,
                agentId,
                packBinding,
                consumedTokens: agentState.consumedTokens,
                dailyBudgetTokens: cfg.dailyBudgetTokens,
                cyclesTerminated,
                frozenSince,
                reason: frozenReason,
            });
            return { remainingTokens: 0, status: 'frozen' };
        }
        return { remainingTokens: Math.max(0, remaining), status: 'ok' };
    }
    /**
     * Pre-call gate. Call before each LLM request.
     *
     * Throws AgentFrozenError if the agent is currently frozen.
     * Returns { allowed: true } otherwise.
     *
     * Position in the hook chain: AFTER cycle-level gates (per-cycle token
     * budget, cycle wall-clock timeout, tool rate limit) but BEFORE the LLM
     * call. Per-cycle gates have tighter scope and fire first; the daily budget
     * is the broader bound that survives across multiple cycles.
     *
     * @param orgId       Tenant / organization ID.
     * @param agentId     Agent identifier.
     * @param packBinding Non-empty compliance pack binding.
     */
    guardPreCall(orgId, agentId, packBinding) {
        if (!packBinding || packBinding.length === 0) {
            throw new EmptyPackBindingError('guardPreCall');
        }
        const cfg = this.requireConfig(orgId, agentId);
        const key = this.stateKey(orgId, agentId);
        const agentState = this.getOrCreateState(key);
        // Check window expiry and auto-thaw before gate decision.
        this.maybeResetWindow(orgId, agentId, agentState, cfg, packBinding);
        if (agentState.frozen) {
            throw new AgentFrozenError({
                orgId,
                agentId,
                frozenSince: agentState.frozenSince ?? new Date().toISOString(),
                frozenReason: agentState.frozenReason ??
                    'Daily token budget exceeded.',
                packBinding,
            });
        }
        return { allowed: true };
    }
    /**
     * Operator-attested unfreeze.
     *
     * Only OPERATOR_ADMIN identities may call this. Agent-runtime identities
     * are explicitly rejected and an AGENT_SELF_UNFREEZE_ATTEMPT audit event
     * (severity ERROR) is emitted.
     *
     * Attestation must be non-empty. Each unfreeze call carries a unique nonce
     * (PT-DAILY-FREEZE-3 replay prevention).
     *
     * Emits AGENT_DAILY_BUDGET_UNFROZEN with severity ERROR (unfreezing a
     * frozen agent is itself a notable posture change).
     *
     * @param orgId           Tenant / organization ID.
     * @param agentId         Agent identifier.
     * @param approverIdentity  Operator identity performing the unfreeze.
     * @param attestation     Non-empty explanation of why the agent is being
     *                        unfrozen (e.g., root-cause summary, mitigation steps).
     * @param packBinding     Non-empty compliance pack binding.
     * @param nonce           Unique nonce for this unfreeze action. Must not
     *                        have been used in a prior unfreeze call.
     */
    unfreeze(orgId, agentId, approverIdentity, attestation, packBinding, nonce) {
        if (!packBinding || packBinding.length === 0) {
            throw new EmptyPackBindingError('unfreeze');
        }
        // Generate nonce if not supplied (for backward compat in non-adversarial tests).
        const effectiveNonce = nonce ?? crypto.randomUUID();
        // PT-DAILY-FREEZE-3: replay prevention.
        if (this.usedNonces.has(effectiveNonce)) {
            throw new UnfreezeNonceReplayError(effectiveNonce);
        }
        if (!attestation || attestation.trim().length === 0) {
            // Emit before throwing -- the attempt is auditable even if it fails.
            this.emit({
                type: 'AGENT_SELF_UNFREEZE_ATTEMPT',
                severity: 'ERROR',
                orgId,
                agentId,
                packBinding,
                operatorId: approverIdentity,
                reason: 'Empty attestation provided',
            });
            throw new Error(`PerAgentDailyBudgetEnforcer.unfreeze: attestation must be non-empty. ` +
                `Provide a documented explanation of the root cause and mitigation steps.`);
        }
        // Check if this is an agent-runtime identity attempting self-unfreeze.
        if (this.isAgentRuntimeIdentity(approverIdentity)) {
            this.emit({
                type: 'AGENT_SELF_UNFREEZE_ATTEMPT',
                severity: 'ERROR',
                orgId,
                agentId,
                packBinding,
                operatorId: approverIdentity,
                reason: 'Agent-runtime identity attempted self-unfreeze',
            });
            throw new UnfreezePermissionError(approverIdentity, 'agent-runtime identities cannot unfreeze agents. Only OPERATOR_ADMIN identities are permitted.');
        }
        // Verify admin permission.
        this.assertAdminPermission(approverIdentity, 'unfreeze');
        // Mark nonce as used.
        this.usedNonces.set(effectiveNonce, new Date().toISOString());
        const key = this.stateKey(orgId, agentId);
        const agentState = this.getOrCreateState(key);
        const wasForzen = agentState.frozen;
        agentState.frozen = false;
        agentState.frozenSince = null;
        agentState.frozenReason = null;
        // Reset window so the agent starts fresh.
        agentState.windowStartMs = null;
        agentState.consumedTokens = 0;
        this.emit({
            type: 'AGENT_DAILY_BUDGET_UNFROZEN',
            severity: 'ERROR', // Unfreezing a frozen agent is a notable posture change.
            orgId,
            agentId,
            packBinding,
            approverIdentity,
            attestation,
            nonce: effectiveNonce,
            wasFrozen: wasForzen,
        });
    }
    /**
     * Return current status for monitoring and tests.
     */
    getStatus(orgId, agentId) {
        const key = this.stateKey(orgId, agentId);
        const cfg = this.configs.get(key);
        const agentState = this.state.get(key);
        if (!agentState) {
            return {
                frozen: false,
                dailyBudget: cfg?.dailyBudgetTokens ?? null,
                consumedToday: 0,
                windowStartsAt: null,
                windowEndsAt: null,
                frozenSince: null,
                frozenReason: null,
                activeCycleCount: 0,
                configured: !!cfg,
            };
        }
        const windowStartsAt = agentState.windowStartMs !== null
            ? new Date(agentState.windowStartMs).toISOString()
            : null;
        const windowEndsAt = agentState.windowStartMs !== null
            ? new Date(agentState.windowStartMs + DAILY_WINDOW_MS).toISOString()
            : null;
        return {
            frozen: agentState.frozen,
            dailyBudget: cfg?.dailyBudgetTokens ?? null,
            consumedToday: agentState.consumedTokens,
            windowStartsAt,
            windowEndsAt,
            frozenSince: agentState.frozenSince,
            frozenReason: agentState.frozenReason,
            activeCycleCount: agentState.activeCycleIds.size,
            configured: !!cfg,
        };
    }
    /**
     * Register a cycle ID as active for the given agent.
     * Called by the cycle management layer when a new cycle starts.
     */
    registerCycle(orgId, agentId, cycleId) {
        const key = this.stateKey(orgId, agentId);
        const agentState = this.getOrCreateState(key);
        agentState.activeCycleIds.add(cycleId);
    }
    /**
     * Deregister a cycle ID (called when a cycle ends normally).
     */
    deregisterCycle(orgId, agentId, cycleId) {
        const key = this.stateKey(orgId, agentId);
        const agentState = this.state.get(key);
        if (agentState) {
            agentState.activeCycleIds.delete(cycleId);
        }
    }
    // ---------------------------------------------------------------------------
    // Internal helpers
    // ---------------------------------------------------------------------------
    stateKey(orgId, agentId) {
        return `${orgId}::${agentId}`;
    }
    getOrCreateState(key) {
        let s = this.state.get(key);
        if (!s) {
            s = {
                windowStartMs: null,
                consumedTokens: 0,
                frozen: false,
                frozenSince: null,
                frozenReason: null,
                activeCycleIds: new Set(),
            };
            this.state.set(key, s);
        }
        return s;
    }
    requireConfig(orgId, agentId) {
        const cfg = this.configs.get(this.stateKey(orgId, agentId));
        if (!cfg) {
            throw new Error(`PerAgentDailyBudgetEnforcer: no configuration found for agent '${agentId}' ` +
                `in org '${orgId}'. Call configure() first.`);
        }
        return cfg;
    }
    /**
     * Check whether the daily window has elapsed and, if so, apply auto-thaw
     * logic for lenient packs (strict packs do NOT auto-thaw).
     */
    maybeResetWindow(orgId, agentId, agentState, cfg, packBinding) {
        if (agentState.windowStartMs === null)
            return;
        const now = Date.now();
        const windowExpired = now >= agentState.windowStartMs + DAILY_WINDOW_MS;
        if (!windowExpired)
            return;
        // Window has elapsed. Check if any bound pack requires operator unfreeze.
        const boundToStrictPack = packBinding.some((pid) => STRICT_PACK_IDS.has(pid)) ||
            cfg.packBinding.some((pid) => STRICT_PACK_IDS.has(pid));
        if (agentState.frozen && boundToStrictPack) {
            // Strict pack: auto-thaw is blocked. Freeze persists until operator unfreeze.
            return;
        }
        // Lenient pack (or not frozen): reset window.
        const previousConsumed = agentState.consumedTokens;
        const wasFrozen = agentState.frozen;
        agentState.windowStartMs = null;
        agentState.consumedTokens = 0;
        agentState.frozen = false;
        agentState.frozenSince = null;
        agentState.frozenReason = null;
        this.emit({
            type: 'AGENT_DAILY_BUDGET_WINDOW_RESET',
            severity: 'INFO',
            orgId,
            agentId,
            packBinding,
            previousConsumed,
            wasFrozen,
            autoThawed: wasFrozen,
        });
    }
    /**
     * PT-DAILY-FREEZE-2: resolve effective output token count.
     * If the reported count is below byte_length / 8, emit TOKEN_COUNT_ANOMALY
     * and use the byte-derived estimate.
     */
    resolveOutputTokens(orgId, agentId, reportedOutputTokens, outputBytes, packBinding) {
        if (outputBytes === undefined || outputBytes <= 0) {
            return reportedOutputTokens;
        }
        const byteDerivedFloor = Math.ceil(outputBytes / 8);
        if (reportedOutputTokens < byteDerivedFloor) {
            this.emit({
                type: 'TOKEN_COUNT_ANOMALY',
                severity: 'WARNING',
                orgId,
                agentId,
                packBinding: packBinding ?? [],
                reportedOutputTokens,
                byteDerivedFloor,
                outputBytes,
                action: 'using-byte-derived-estimate',
                detail: 'Reported output token count is below byte_length/8 floor. ' +
                    'Possible token-count understatement attack. Using byte-derived estimate.',
            });
            return byteDerivedFloor;
        }
        return reportedOutputTokens;
    }
    /**
     * PT-DAILY-FREEZE-1: window-start immutability.
     *
     * The windowStartMs is stored in the private state map and can only be
     * written by this class. It cannot be overwritten by runtime context,
     * agent messages, or external callers -- the Map key is derived from
     * orgId + agentId and the state reference is never exposed.
     *
     * This method exists for documentation and is called by tests to verify
     * that the immutability contract holds.
     */
    getWindowStartMs(orgId, agentId) {
        const s = this.state.get(this.stateKey(orgId, agentId));
        return s?.windowStartMs ?? null;
    }
    /**
     * Determine if a given identity is an agent-runtime identity rather than
     * an operator identity. Heuristic: agent-runtime IDs are prefixed with
     * 'agent:' or 'runtime:'. Operators use 'op_' prefix (per OperatorRoles).
     *
     * In production wire to OperatorRoles.getOperator() for a hard check.
     */
    isAgentRuntimeIdentity(identity) {
        return identity.startsWith('agent:') || identity.startsWith('runtime:');
    }
    /**
     * Assert that the given operator holds admin permission.
     * Uses injected checkPermission if available, falls back to the local
     * adminOperatorIds set.
     */
    assertAdminPermission(operatorId, operation) {
        if (this._checkPermission) {
            const check = this._checkPermission(operatorId, 'governance.configure');
            if (!check.allowed) {
                throw new UnfreezePermissionError(operatorId, check.reason);
            }
            return;
        }
        // Fall back to local admin set.
        if (!this.adminOperatorIds.has(operatorId)) {
            throw new UnfreezePermissionError(operatorId, `Operator '${operatorId}' is not registered as ADMIN. ` +
                `Call registerAdmin() or inject checkPermission to grant access.`);
        }
    }
    /**
     * Emit an audit event with SHA chaining.
     * Swallows errors from the onAudit callback to avoid breaking the
     * enforcement path.
     */
    emit(fields) {
        const payload = {
            ...fields,
            timestamp: new Date().toISOString(),
            prevSha: this.lastSha,
        };
        // Compute SHA for this event.
        const entryStr = JSON.stringify(payload);
        const sha = crypto.createHash('sha256').update(entryStr).digest('hex');
        this.lastSha = sha;
        try {
            this.onAudit(payload);
        }
        catch {
            // Swallow -- enforcement path must not fail due to audit errors.
        }
    }
}
//# sourceMappingURL=per-agent-daily-budget.js.map