/** * Divergence dashboard and enforcement gate. * * Wraps a `PermissionSimulator` to provide: * - Trend history: periodic snapshots of divergence rates bucketed by time. * - Enforce gate: prevents transitioning to `enforce` mode when the * divergence rate exceeds the configured threshold. * - Aggregated dashboard data queryable by the diagnostics layer. * * The dashboard is passive, it reads from the simulator, does not own * the simulator's lifecycle, and never calls evaluate() itself. * * @remarks * Pre-positioned for feature-flag-gated production integration. * When the divergence dashboard is on (permissions.divergenceDashboard), this * dashboard is wired into the session emitter pipeline. Until that flag is active, this module * is exercised only via `DivergencePanel` in the diagnostics layer. */ import { GoodVibesSdkError } from '@pellux/goodvibes-errors'; import type { DivergenceReport, SimulationMode } from './types.js'; import type { PermissionSimulator } from './simulation.js'; /** * A single time-bucketed snapshot of divergence statistics. * * Captured periodically by `DivergenceDashboard` and stored in a bounded * FIFO array to provide trend history for UI display. */ export interface DivergenceTrendEntry { /** Unix epoch milliseconds when this snapshot was taken. */ readonly ts: number; /** Overall divergence rate at the time of the snapshot (0–1). */ readonly divergenceRate: number; /** Total evaluations recorded up to this point. */ readonly totalEvaluations: number; /** Total divergences recorded up to this point. */ readonly totalDivergences: number; /** Whether the gate was passing at the time of the snapshot. */ readonly gatePassing: boolean; } /** * Gate check result returned by `DivergenceDashboard.checkEnforceGate()`. * * - `allowed` , divergence is within threshold; enforce mode may be enabled. * - `blocked` , divergence exceeds threshold; enforce mode is blocked. * - `no_data` , not enough evaluations have been recorded to make a * determination; gate passes by default. */ export type EnforceGateStatus = 'allowed' | 'blocked' | 'no_data'; /** * Full result of an enforce gate check. */ export interface EnforceGateResult { /** Whether enforce mode transition is permitted. */ readonly status: EnforceGateStatus; /** Current divergence rate (0–1). Undefined when status is `no_data`. */ readonly divergenceRate?: number | undefined; /** Configured threshold (0–1). */ readonly threshold: number; /** Total evaluations at the time of the check. */ readonly totalEvaluations: number; /** Human-readable explanation of the result. */ readonly message: string; } /** * Full dashboard snapshot returned by `DivergenceDashboard.getSnapshot()`. * * Combines the current divergence report with trend history and gate status. */ export interface DivergenceDashboardSnapshot { /** Current divergence report from the simulator. */ readonly report: DivergenceReport; /** Active simulation mode at the time of the snapshot. */ readonly mode: SimulationMode; /** Current enforce gate result. */ readonly gate: EnforceGateResult; /** Trend history (oldest first). */ readonly trend: readonly DivergenceTrendEntry[]; /** Epoch ms when this snapshot was taken. */ readonly capturedAt: number; } /** * Configuration for `DivergenceDashboard`. */ export interface DivergenceDashboardConfig { /** * Maximum divergence rate (0–1) before enforce mode is blocked. * Defaults to 0.05 (5%). */ threshold?: number | undefined; /** * Minimum number of evaluations required before the gate makes a * `blocked` determination. Below this count the gate returns `no_data`. * Defaults to 10. */ minEvaluationsForGate?: number | undefined; /** * Maximum number of trend entries to retain in the bounded FIFO array. * Defaults to 100. */ maxTrendEntries?: number | undefined; } /** * DivergenceDashboard, divergence monitoring and enforce-mode gate. * * Attach to a running `PermissionSimulator` to gain: * - `checkEnforceGate()`, real-time gate check before mode transitions. * - `recordTrendEntry()`, capture a snapshot for trend history. * - `getSnapshot()`, full dashboard state for diagnostics rendering. * * @example * ```ts * const simulator = createPermissionSimulator(actual, simulated, 'warn-on-divergence'); * const dashboard = new DivergenceDashboard(simulator, 'warn-on-divergence'); * * // The setInterval and console.error below are caller-side integration * // examples only, the class itself does not use timers or console output. * // Periodically capture trend entries (e.g. every 30 seconds): * setInterval(() => dashboard.recordTrendEntry(), 30_000); * * // Before switching to enforce mode: * const gate = dashboard.checkEnforceGate(); * if (gate.status !== 'allowed') { * // Replace with your logging framework in production. * console.error('Enforce blocked:', gate.message); * } * ``` */ export declare class DivergenceDashboard { private readonly _simulator; /** * @remarks * Dashboard-layer mode for UI display only. Independent of the immutable * simulator mode, the underlying `PermissionSimulator` mode is fixed at * construction and cannot be changed. `_mode` tracks the caller's intended * operational state so the diagnostics view can render it accurately. */ private _mode; private readonly _threshold; private readonly _minEvals; private readonly _maxTrendEntries; private readonly _trend; private static readonly DEFAULT_THRESHOLD; private static readonly DEFAULT_MIN_EVALS; private static readonly DEFAULT_MAX_TREND; constructor(simulator: PermissionSimulator, mode: SimulationMode, config?: DivergenceDashboardConfig); /** * checkEnforceGate, Checks whether transitioning to `enforce` mode is safe. * * Returns `no_data` when fewer than `minEvaluationsForGate` evaluations have * been recorded (gate passes by default in this case). * Returns `blocked` when the overall divergence rate exceeds the threshold. * Returns `allowed` otherwise. */ checkEnforceGate(): EnforceGateResult; /** Compute gate result from pre-fetched stats to avoid double getDivergenceReport() calls. */ private _computeGate; /** * recordTrendEntry, Captures a snapshot of the current divergence state. * * Call periodically (e.g. on a timer) to build trend history. * Oldest entries are evicted when the buffer exceeds `maxTrendEntries`. */ recordTrendEntry(): DivergenceTrendEntry; /** * getTrend, Returns the full trend history (oldest first). */ getTrend(): readonly DivergenceTrendEntry[]; /** * getSnapshot, Returns a full dashboard snapshot for diagnostics rendering. */ getSnapshot(): DivergenceDashboardSnapshot; /** * setMode, Updates the tracked simulation mode. * * Does NOT modify the underlying simulator (which has an immutable mode). * This is for dashboard display purposes when the caller manages mode * transitions externally. * * Throws if an attempt is made to set `enforce` mode while the gate is * `blocked`. This is the primary enforcement mechanism for the divergence gate. * * @throws {DivergenceGateError} when attempting to enable enforce mode * while divergence exceeds the configured threshold. */ setMode(mode: SimulationMode): void; /** * getMode, Returns the currently tracked simulation mode. */ getMode(): SimulationMode; /** * getThreshold, Returns the configured divergence threshold. */ getThreshold(): number; /** * isGatePassing, Convenience accessor; true unless gate is `blocked`. */ isGatePassing(): boolean; } /** * Thrown by `DivergenceDashboard.setMode('enforce')` when the divergence * gate is in `blocked` state. */ export declare class DivergenceGateError extends GoodVibesSdkError { readonly code: 'DIVERGENCE_GATE_BLOCKED'; /** The full gate result at the time the error was thrown. */ readonly gate: EnforceGateResult; constructor(message: string, gate: EnforceGateResult); } //# sourceMappingURL=divergence-dashboard.d.ts.map