/* Factory telemetry hook for kernel command execution (RFC-0340). Env-gated, fire-and-forget, never throws. Never throw — telemetry failures must not affect command results. No network when env vars are absent — pusher is null. Telemetry is env-gated and fire-and-forget — it never blocks command execution. RFC-0340: initial implementation. RFC-1097: step 6 — compass.migrate codemod run Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order. RFC-1097: sweep — werkstatt-engine clean Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics. */ import type { KernelExecutionReport } from "@warpgogol/werkstatt-shared/kernel"; import type { MetricsPusher } from "@warpgogol/werkstatt-shared/observability"; import { createMetricsPusher, METRIC_REFS } from "@warpgogol/werkstatt-shared/observability"; let pusher: MetricsPusher | null | undefined; function getOrCreatePusher(): MetricsPusher | null { if (pusher !== undefined) return pusher; pusher = createMetricsPusher({ serviceName: "werkstatt", layer: "factory", environment: undefined, // auto-detect from env (CI → ci, else development) }); return pusher; } export function getFactoryTelemetryPusher(): MetricsPusher | null { return getOrCreatePusher(); } function deriveStatus(report: KernelExecutionReport): string { if (report.timing?.exceededTimeout) return "timeout"; if (report.exitCode === 0) return "pass"; return report.exitCode === 1 ? "fail" : "error"; } function countDiagnostics( report: KernelExecutionReport, ): Array<{ severity: string; count: number }> { const data = report.data as Record | undefined; if (!data) return []; const diagnostics = data["diagnostics"] as Array<{ severity: string }> | undefined; if (!Array.isArray(diagnostics)) return []; const counts = new Map(); for (const d of diagnostics) { const sev = d.severity ?? "info"; counts.set(sev, (counts.get(sev) ?? 0) + 1); } return [...counts.entries()].map(([severity, count]) => ({ severity, count })); } export function recordCommandTelemetry(pusher: MetricsPusher, report: KernelExecutionReport): void { try { const command = report.commandName; const status = deriveStatus(report); const siteId = report.siteName ?? ""; const labels: Record = { command, status }; if (siteId) labels["site_id"] = siteId; // warpgogol_factory_command_runs_total{command, status, site_id} METRIC_REFS.warpgogol_factory_command_runs_total.add(pusher, 1, labels); // warpgogol_factory_command_duration_seconds{command, site_id} const durationSeconds = (report.timing?.durationMs ?? 0) / 1000; const histLabels: Record = { command }; if (siteId) histLabels["site_id"] = siteId; METRIC_REFS.warpgogol_factory_command_duration_seconds.record( pusher, durationSeconds, histLabels, ); // warpgogol_factory_diagnostics_total{command, severity, site_id} for (const { severity, count } of countDiagnostics(report)) { const diagLabels: Record = { command, severity }; if (siteId) diagLabels["site_id"] = siteId; METRIC_REFS.warpgogol_factory_diagnostics_total.add(pusher, count, diagLabels); } } catch { // Never let telemetry affect command results } } export async function flushFactoryTelemetry(): Promise { const p = getOrCreatePusher(); if (p) { try { await p.flush(); } catch { // Swallow all errors } } }