All files / lib/reflection evaluator.ts

0% Statements 0/102
0% Branches 0/1
0% Functions 0/1
0% Lines 0/102

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123                                                                                                                                                                                                                                                     
// lib/reflection/evaluator.ts
 
import { isVerbose } from './flags';
import { SIGNALS, type Signal, type SignalEvaluator, type EvaluationResult } from './types';
 
// ========================================
//   Configuration (SLOs)
// ========================================
 
const EVALUATORS: Record<string, SignalEvaluator & { direction?: 'higher' | 'lower' }> = {
  [SIGNALS.WINDOW_CHURN]: {
    signal: SIGNALS.WINDOW_CHURN,
    // higher churn is worse
    direction: 'higher',
    slos: [
      { threshold: 4, status: 'CRITICAL' }, // > 4 events/sec
      { threshold: 3, status: 'WARNING' }, // > 3 events/sec
    ],
  },
  [SIGNALS.FOCUS_STABILITY]: {
    signal: SIGNALS.FOCUS_STABILITY,
    // lower stability is worse (0..1 range)
    direction: 'lower',
    slos: [
      { threshold: 0.4, status: 'CRITICAL' }, // < 0.4 is critical
      { threshold: 0.6, status: 'WARNING' }, // < 0.6 is warning
    ],
  },
  [SIGNALS.LATENCY_SUMMARY]: {
    signal: SIGNALS.LATENCY_SUMMARY,
    slos: [],
  },
  [SIGNALS.IDLE_RATIO]: {
    signal: SIGNALS.IDLE_RATIO,
    slos: [],
  },
  [SIGNALS.WINDOW_LIFETIME]: {
    signal: SIGNALS.WINDOW_LIFETIME,
    slos: [],
  },
};
 
// ========================================
//   Public API
// ========================================
 
/**
 * Evaluates a given signal against its configured SLOs.
 * @param signal The signal to evaluate.
 * @returns An EvaluationResult object.
 */
export function evaluateSignal(signal: Signal): EvaluationResult {
  const evaluator = EVALUATORS[signal.type as string];
  if (!evaluator || evaluator.slos.length === 0) {
    return {
      signal,
      status: 'NORMAL',
      slo: null,
      timestamp: performance.now(),
    };
  }
 
  // Iterate through SLOs (ordered most to least critical). Honor direction.
  const direction = (evaluator as any).direction ?? 'higher';
  for (const slo of evaluator.slos) {
    const breached =
      direction === 'higher' ? signal.value > slo.threshold : signal.value < slo.threshold;
    if (breached) {
      const result: EvaluationResult = {
        signal,
        status: slo.status,
        slo,
        timestamp: performance.now(),
      };
 
      if (isVerbose()) {
        console.log(
          `[Reflection/Evaluator] Signal evaluated: ${signal.type} is ${slo.status}`,
          result
        );
      }
      return result;
    }
  }
 
  // If no threshold was breached, the status is normal
  const result: EvaluationResult = {
    signal,
    status: 'NORMAL',
    slo: null,
    timestamp: performance.now(),
  };
 
  if (isVerbose()) {
    console.log(`[Reflection/Evaluator] Signal evaluated: ${signal.type} is NORMAL`, result);
  }
 
  return result;
}
 
/**
 * Convenience: evaluate an array or object of signals and return EvaluationResult[]
 */
export function evaluateSignals(signals: any): EvaluationResult[] {
  const list: Signal[] = [];
  if (Array.isArray(signals)) {
    for (const s of signals) list.push(s);
  } else if (signals && typeof signals === 'object') {
    for (const key of Object.keys(signals)) {
      const val = (signals as any)[key];
      if (val && typeof val === 'object' && 'type' in val) list.push(val as Signal);
    }
  }
  return list.map((s) => evaluateSignal(s));
}
 
/**
 * Test helper (no-op for now) to reset evaluator-internal state.
 */
export function _resetEvaluatorForTests(): void {
  // reserved for future internal caches
}