// ============================================================
// SPARDA ROUTER — Auto-generated. DO NOT EDIT.
// Regenerate: npx sparda-mcp init   •   Remove: npx sparda-mcp remove
// ============================================================
__IMPORT_LINE__

const SPARDA_TOOLS = __TOOLS_JSON__;

const SPARDA_LOCAL_KEY = "__LOCAL_KEY__";
const SPARDA_PORT = __PORT__;

const SPARDA_STATS__STATS_TYPE__ = {};
const SPARDA_EVENTS__EVENTS_TYPE__ = [];
let SPARDA_SEQ = 0;
// immune system: tools that keep failing are quarantined so the AI can't hammer a broken route
const SPARDA_QUARANTINE__STATS_TYPE__ = {};
const SPARDA_QUARANTINE_MS = Number(process.env.SPARDA_QUARANTINE_MS ?? 60000);
// recycling gauge: calls answered from SPARDA's own knowledge vs calls that paid the host route.
// Day 1 it reads 0% — the circle fills with usage (a measure, never a promise).
const SPARDA_RECYCLE__STATS_TYPE__ = { servedByCircle: 0, paidFull: 0 };
function spardaRecycleRate() {
  const total = SPARDA_RECYCLE.servedByCircle + SPARDA_RECYCLE.paidFull;
  return total ? Math.round((SPARDA_RECYCLE.servedByCircle * 100) / total) : 0;
}
// thermodynamic route classification: a GET whose repeated identical args keep
// returning identical bodies is observed-pure (its result pre-exists — recyclable);
// writes erase by definition. Observation only, never a guess.
const SPARDA_PURITY__STATS_TYPE__ = {};
function spardaHash(s__ANY_TYPE__) {
  let h = 0x811c9dc5; // FNV-1a, capped: a fingerprint, not a checksum of megabytes
  for (let i = 0; i < s.length && i < 65536; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193) >>> 0; }
  return h;
}
function spardaObservePurity(tool__ANY_TYPE__, argsig__ANY_TYPE__, body__ANY_TYPE__) {
  const pu = SPARDA_PURITY[tool] ?? (SPARDA_PURITY[tool] = { sigs: {}, repeats: 0, mismatches: 0 });
  const h = spardaHash(body);
  const known = pu.sigs[argsig];
  if (known === undefined) {
    if (Object.keys(pu.sigs).length >= 20) return; // bounded: enough sigs to judge
    pu.sigs[argsig] = h;
  } else if (known === h) pu.repeats += 1;
  else { pu.mismatches += 1; pu.sigs[argsig] = h; } // the latest real answer is the truth
}
function spardaPuritySnapshot() {
  const out__STATS_TYPE__ = {};
  for (const [name, spec] of Object.entries(SPARDA_TOOLS)) {
    if (spec.method !== 'GET') { out[name] = { class: 'erasing', repeats: 0, mismatches: 0 }; continue; }
    const pu = SPARDA_PURITY[name];
    out[name] = {
      class: !pu ? 'unknown' : pu.mismatches > 0 ? 'volatile' : pu.repeats >= 3 ? 'pure' : 'unknown',
      repeats: pu ? pu.repeats : 0,
      mismatches: pu ? pu.mismatches : 0,
    };
  }
  return out;
}
function spardaEvent(source__ANY_TYPE__, tool__ANY_TYPE__, status__ANY_TYPE__, message__ANY_TYPE__) {
  SPARDA_EVENTS.push({ seq: ++SPARDA_SEQ, ts: new Date().toISOString(), source, tool, status, message: String(message ?? '').slice(0, 500) });
  if (SPARDA_EVENTS.length > 100) SPARDA_EVENTS.shift();
}
// observe-only: uncaughtExceptionMonitor never changes the host app's crash behavior
process.on('uncaughtExceptionMonitor', (err__ANY_TYPE__) => spardaEvent('process', null, null, err && err.message ? err.message : err));

__ROUTER_DECL__

spardaRouter.use((req__REQ_TYPE__, res__RES_TYPE__, next__NEXT_TYPE__) => {
  if (req.headers['x-sparda-key'] !== SPARDA_LOCAL_KEY) {
    return res.status(401).json({ error: 'unauthorized' });
  }
  next();
});

spardaRouter.get('/tools', (_req__REQ_TYPE__, res__RES_TYPE__) => res.json(SPARDA_TOOLS));

spardaRouter.get('/stats', (_req__REQ_TYPE__, res__RES_TYPE__) => res.json({ uptimeSec: Math.round(process.uptime()), stats: SPARDA_STATS, quarantine: SPARDA_QUARANTINE, recycle: { servedByCircle: SPARDA_RECYCLE.servedByCircle, paidFull: SPARDA_RECYCLE.paidFull, ratePct: spardaRecycleRate() }, purity: spardaPuritySnapshot() }));

spardaRouter.get('/events', (req__REQ_TYPE__, res__RES_TYPE__) => {
  const since = Number(req.query.since ?? 0) || 0;
  res.json({ seq: SPARDA_SEQ, events: SPARDA_EVENTS.filter((e__ANY_TYPE__) => e.seq > since) });
});

spardaRouter.post('/invoke', __JSON_MW__, async (req__REQ_TYPE__, res__RES_TYPE__) => {
  const { tool, args = {} } = req.body ?? {};
  const t0 = Date.now();
  const spec = SPARDA_TOOLS[tool];
  if (!spec) return res.status(404).json({ error: `unknown tool: ${tool}` });
  if (!spec.enabled) {
    return res.status(403).json({ error: `tool disabled (write-safety): ${tool}`, hint: 'Enable it in sparda.json, then re-run: npx sparda-mcp init' });
  }
  if (spec.path.startsWith('/mcp')) {
    return res.status(400).json({ error: 'self-referential tool blocked (loop protection)' });
  }
  const quarantined = SPARDA_QUARANTINE[tool];
  if (quarantined) {
    if (Date.now() < quarantined.until) {
      SPARDA_RECYCLE.servedByCircle += 1; // answered from immune memory — the doomed host call was never paid
      return res.status(503).json({ error: `tool quarantined (immune system): ${tool}`, reason: quarantined.reason, retryInMs: quarantined.until - Date.now() });
    }
    // half-open: cooldown elapsed — allow one probe; a single new 5xx re-quarantines
    delete SPARDA_QUARANTINE[tool];
    if (SPARDA_STATS[tool]) SPARDA_STATS[tool].consecutive5xx = 2;
  }
  try {
    let url = spec.path.replace(/:(\w+)/g, (_, name) => {
      const v = args[name];
      if (v === undefined) throw Object.assign(new Error(`missing path param: ${name}`), { status: 400 });
      return encodeURIComponent(String(v));
    });
    const query = [];
    for (const [k, v] of Object.entries(args)) {
      if (k === 'body' || spec.pathParams.includes(k) || v === undefined) continue;
      query.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
    }
    if (query.length) url += `?${query.join('&')}`;

    const init = { method: spec.method, headers: {} };
    if (spec.method !== 'GET' && args.body !== undefined) {
      init.headers['content-type'] = 'application/json';
      init.body = JSON.stringify(args.body);
    }
    SPARDA_RECYCLE.paidFull += 1; // the host route is about to be exercised — full price
    const upstream = await fetch(`http://127.0.0.1:${SPARDA_PORT}${url}`, { ...init, signal: AbortSignal.timeout(30_000) });
    const text = await upstream.text();
    let data; try { data = JSON.parse(text); } catch { data = text; }
    spardaRecord(tool, upstream.status, Date.now() - t0);
    if (spec.method === 'GET' && upstream.status === 200) {
      // canonical argsig: sorted entries, so the AI's argument order never splits a signature
      spardaObservePurity(tool, JSON.stringify(Object.entries(args).filter((e__ANY_TYPE__) => e[1] !== undefined).sort()), text);
    }
    if (upstream.status >= 500) spardaEvent('invoke', tool, upstream.status, text.slice(0, 200));
    return res.status(200).json({ upstreamStatus: upstream.status, data });
  } catch (err__ANY_TYPE__) {
    spardaRecord(tool, err.status ?? 502, Date.now() - t0);
    spardaEvent('invoke', tool, err.status ?? 502, err.message);
    return res.status(err.status ?? 502).json({ error: err.message });
  }
});

function spardaRecord(tool__ANY_TYPE__, status__ANY_TYPE__, ms__ANY_TYPE__) {
  const st = SPARDA_STATS[tool] ?? (SPARDA_STATS[tool] = { calls: 0, errors: 0, clientErrors: 0, totalMs: 0, lastStatus: null, lastTs: null, consecutive5xx: 0 });
  // innate immunity: latency far beyond the learned baseline is an antigen
  if (st.calls >= 5 && ms > Math.max((st.totalMs / st.calls) * 10, 200)) {
    spardaEvent('immune', tool, status, `latency anomaly: ${ms}ms vs ~${Math.round(st.totalMs / st.calls)}ms baseline`);
  }
  st.calls += 1;
  st.totalMs += ms;
  // 5xx = server failure (feeds the immune system); 4xx = a valid client answer (404 etc), tracked apart so the count doesn't lie
  if (status >= 500) st.errors += 1;
  else if (status >= 400) st.clientErrors += 1;
  st.lastStatus = status;
  st.lastTs = new Date().toISOString();
  if (status >= 500) st.consecutive5xx += 1;
  else if (status < 400) st.consecutive5xx = 0;
  if (st.consecutive5xx >= 3 && !SPARDA_QUARANTINE[tool]) {
    SPARDA_QUARANTINE[tool] = { since: new Date().toISOString(), until: Date.now() + SPARDA_QUARANTINE_MS, reason: `${st.consecutive5xx} consecutive 5xx` };
    spardaEvent('immune', tool, 503, `quarantined after ${st.consecutive5xx} consecutive 5xx (cooldown ${SPARDA_QUARANTINE_MS}ms)`);
  }
}
