/*
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
}
}
}