import net from 'node:net'; // Doc/infra sanity check. Pure functions with an injected `probe(host, port) -> // Promise` so they're testable offline; the default tcpProbe is used in prod. const LAN_RE = /(? { const sock = new net.Socket(); let done = false; const finish = (ok) => { if (done) return; done = true; sock.destroy(); resolve(ok); }; sock.setTimeout(timeoutMs); sock.once('connect', () => finish(true)); sock.once('timeout', () => finish(false)); sock.once('error', () => finish(false)); sock.connect(port, host); }); } // Cross-check every IP:port referenced in the wiki against live reachability. // Flags stale references (e.g. a CT that moved off an old IP) grouped by page. export async function auditDocs({ pages, probe }) { const map = new Map(); // host:port -> { host, port, pages:Set } for (const p of pages || []) { for (const ep of extractEndpoints(p.body_md)) { const key = `${ep.host}:${ep.port ?? ''}`; if (!map.has(key)) map.set(key, { host: ep.host, port: ep.port, pages: new Set() }); map.get(key).pages.add(p.title); } } const all = [...map.values()]; const probable = all.filter(e => e.port != null); const unprobed = all.filter(e => e.port == null).map(e => ({ host: e.host, port: null, pages: [...e.pages] })); const unreachable = []; for (const e of probable) { if (!(await probe(e.host, e.port))) unreachable.push({ host: e.host, port: e.port, pages: [...e.pages] }); } return { ok: unreachable.length === 0, summary: { endpoints: all.length, probed: probable.length, reachable: probable.length - unreachable.length, unreachable: unreachable.length }, unreachable, unprobed }; } // Probe each registered service's LAN url; flag any that don't answer. export async function auditServices({ services, probe }) { let probed = 0; const unreachable = []; for (const s of services || []) { const hp = parseUrl(s.url); if (!hp) continue; probed++; if (!(await probe(hp.host, hp.port))) unreachable.push({ id: s.id, url: s.url, host: hp.host, port: hp.port }); } return { ok: unreachable.length === 0, summary: { probed, unreachable: unreachable.length }, unreachable }; } // Full sanity sweep used by the API route / MCP tool. export async function runAudit({ pages = [], services = [], probe = tcpProbe }) { const docs = await auditDocs({ pages, probe }); const svc = await auditServices({ services, probe }); return { ok: docs.ok && svc.ok, docs, services: svc }; }