/** * Canonical data-model helpers + JSDoc typedefs for metrics, anomalies, health. * * Wire shapes are JSON over protomux-rpc compact-encoding and REST /api/v*. */ /** * @typedef {{ * nodeId: string, * hostname: string, * publicKeyHex: string, * platform: string, * arch: string, * release: string, * cpus: number, * totalMemMiB: number, * agentVersion: string, * protocolVersion: number, * startedAt: number, * labels?: Record, * }} NodeInfo * * @typedef {{ * chart: string, * context: string, * ts: number, * values: Record, * }} MetricSample * * @typedef {{ * chart: string, * context: string, * labels: string[], * data: Array<[number, ...(number|null)[]]>, * view_update_every?: number, * }} QueryResult * * @typedef {{ * id: string, * chart: string, * context: string, * dimension: string, * severity: 'warning'|'critical', * score: number, * value: number, * threshold: number, * comparator: string, * message: string, * ts: number, * cleared?: boolean, * }} AnomalyEvent * * @typedef {{ * id: string, * name: string, * chart: string, * dimension: string, * status: 'CLEAR'|'WARNING'|'CRITICAL'|'UNDEFINED', * value: number|null, * units: string, * info: string, * lastStatusChange: number, * config: AlertConfig, * }} AlertState * * @typedef {{ * id: string, * chart: string, * dimension: string, * warn?: number|null, * crit?: number|null, * comparator?: '>'|'<'|'>='|'<=', * lookbackSec?: number, * enabled?: boolean, * info?: string, * }} AlertConfig * * @typedef {{ * status: 'ok'|'degraded'|'critical', * score: number, * checks: Array<{ id: string, ok: boolean, detail: string }>, * ts: number, * }} HealthSnapshot * * @typedef {{ * id: string, * name: string, * status: 'queued'|'running'|'done'|'failed'|'cancelled', * startedAt: number|null, * finishedAt: number|null, * result?: any, * error?: string, * }} JobRecord */ /** * @param {Partial} sample * @returns {MetricSample} */ export function normalizeSample(sample) { return { chart: String(sample.chart || ''), context: String(sample.context || sample.chart || ''), ts: Number(sample.ts) || Date.now(), values: sample.values && typeof sample.values === 'object' ? sample.values : {}, } } /** * @param {Partial} ev * @returns {AnomalyEvent} */ export function normalizeAnomaly(ev) { return { id: String(ev.id || `${ev.chart}:${ev.dimension}:${ev.ts}`), chart: String(ev.chart || ''), context: String(ev.context || ev.chart || ''), dimension: String(ev.dimension || ''), severity: ev.severity === 'critical' ? 'critical' : 'warning', score: Number(ev.score) || 0, value: Number(ev.value), threshold: Number(ev.threshold), comparator: String(ev.comparator || '>'), message: String(ev.message || ''), ts: Number(ev.ts) || Date.now(), cleared: Boolean(ev.cleared), } }