// src/recall-audit-anomaly.ts
var DEFAULT_ANOMALY_DETECTOR_CONFIG = Object.freeze({
  enabled: false,
  windowMs: 5 * 6e4,
  repeatQueryLimit: 5,
  namespaceWalkLimit: 3,
  highCardinalityReturnLimit: 50,
  rapidFireLimit: 30
});
function effectiveConfig(raw) {
  const base = { ...DEFAULT_ANOMALY_DETECTOR_CONFIG };
  if (!raw) return base;
  const out = { ...base };
  if (typeof raw.enabled === "boolean") out.enabled = raw.enabled;
  if (typeof raw.windowMs === "number" && Number.isFinite(raw.windowMs) && raw.windowMs > 0) {
    out.windowMs = raw.windowMs;
  }
  for (const key of [
    "repeatQueryLimit",
    "namespaceWalkLimit",
    "highCardinalityReturnLimit",
    "rapidFireLimit"
  ]) {
    const v = raw[key];
    if (typeof v === "number" && Number.isFinite(v) && v > 0) {
      const floored = Math.floor(v);
      out[key] = floored >= 1 ? floored : base[key];
    }
  }
  return out;
}
function normalizeQueryText(raw) {
  if (typeof raw !== "string") return "";
  return raw.toLowerCase().replace(/\s+/g, " ").trim();
}
var SEVERITY_ORDER = {
  alert: 0,
  warn: 1,
  info: 2
};
function parseTsMs(ts) {
  const n = new Date(ts).getTime();
  return Number.isFinite(n) ? n : null;
}
function detectRecallAnomalies(input) {
  const cfg = effectiveConfig(input.config);
  const cutoff = input.now - cfg.windowMs;
  const windowed = [];
  for (let i = 0; i < input.entries.length; i++) {
    const entry = input.entries[i];
    const tsMs = parseTsMs(entry.ts);
    if (tsMs === null) continue;
    if (tsMs >= cutoff && tsMs <= input.now) {
      windowed.push({ entry, index: i });
    }
  }
  if (!cfg.enabled) {
    return { flags: [], windowEntryCount: windowed.length, windowMs: cfg.windowMs };
  }
  const flags = [];
  {
    const byQuery = /* @__PURE__ */ new Map();
    for (const { entry, index } of windowed) {
      const key = normalizeQueryText(entry.queryText);
      if (key.length === 0) continue;
      const arr = byQuery.get(key);
      if (arr) arr.push(index);
      else byQuery.set(key, [index]);
    }
    for (const [query, indices] of byQuery) {
      if (indices.length > cfg.repeatQueryLimit) {
        flags.push({
          kind: "repeat-query",
          severity: "warn",
          message: `query "${query}" repeated ${indices.length} times in ${cfg.windowMs}ms`,
          entryIndices: indices.slice(),
          signal: query
        });
      }
    }
  }
  {
    const namespaces = /* @__PURE__ */ new Set();
    const indices = [];
    for (const { entry, index } of windowed) {
      if (!Array.isArray(entry.candidateMemoryIds)) continue;
      let touchedAny = false;
      for (const id of entry.candidateMemoryIds) {
        if (typeof id !== "string" || id.length === 0) continue;
        const slash = id.indexOf("/");
        if (slash > 0) {
          namespaces.add(id.slice(0, slash));
        } else {
          namespaces.add("__unprefixed__");
        }
        touchedAny = true;
      }
      if (touchedAny) indices.push(index);
    }
    if (namespaces.size > cfg.namespaceWalkLimit) {
      flags.push({
        kind: "namespace-walk",
        severity: "alert",
        message: `queries touched ${namespaces.size} distinct namespaces in ${cfg.windowMs}ms`,
        entryIndices: indices,
        signal: namespaces.size
      });
    }
  }
  {
    for (const { entry, index } of windowed) {
      const ids = Array.isArray(entry.candidateMemoryIds) ? entry.candidateMemoryIds : [];
      if (ids.length > cfg.highCardinalityReturnLimit) {
        flags.push({
          kind: "high-cardinality-return",
          severity: "alert",
          message: `single recall returned ${ids.length} candidate memory IDs`,
          entryIndices: [index],
          signal: ids.length
        });
      }
    }
  }
  if (windowed.length > cfg.rapidFireLimit) {
    flags.push({
      kind: "rapid-fire",
      severity: "warn",
      message: `${windowed.length} recall entries in ${cfg.windowMs}ms`,
      entryIndices: windowed.map((w) => w.index),
      signal: windowed.length
    });
  }
  flags.sort((a, b) => {
    const s = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];
    if (s !== 0) return s;
    return a.kind.localeCompare(b.kind);
  });
  return { flags, windowEntryCount: windowed.length, windowMs: cfg.windowMs };
}

export {
  DEFAULT_ANOMALY_DETECTOR_CONFIG,
  normalizeQueryText,
  detectRecallAnomalies
};
//# sourceMappingURL=chunk-RFYAYKTD.js.map