/*
observability.conventions.validate command handler — offline lint that keeps every emitter inside the closed vocabulary (RFC-0337).
- Do not introduce app-specific runtime composition or deployment behavior into this reusable package source file.
- RFC-0337: initial implementation.
*/
import { join } from "node:path";
import type {
CheckResult,
Diagnostic,
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import { diagnosticsResult } from "@warpgogol/werkstatt-shared/checks";
import {
FORBIDDEN_LABEL_KEYS,
METRIC_NAME_PATTERN,
WARPGOGOL_METRIC_REGISTRY,
} from "@warpgogol/werkstatt-shared/observability";
const METRIC_NAME_LITERAL_PATTERN =
/["']((?:warpgogol_(?:factory|probe|delivery|workers)_[a-z0-9_]+))["']/g;
const PUSHER_CALL_PATTERN = /(?:counterAdd|gaugeSet|histogramRecord)\s*\(/;
const METRIC_REFS_ACCESS_PATTERN =
/METRIC_REFS\.((?:warpgogol_(?:factory|probe|delivery|workers)_[a-z0-9_]+))/g;
const OTLP_ENV_PATTERN = /(?:WARPGOGOL_OTLP_ENDPOINT|WARPGOGOL_OTLP_TOKEN)/;
export async function runObservabilityConventionsValidate(
_input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const diagnostics: Diagnostic[] = [];
// OBS-CONV-02: registry entry naming grammar
for (const spec of WARPGOGOL_METRIC_REGISTRY) {
if (!METRIC_NAME_PATTERN.test(spec.name)) {
diagnostics.push({
ruleId: "OBS-CONV-02",
severity: "error",
file: "packages/observability/src/metric-registry.ts",
message: `Metric name "${spec.name}" does not match the naming grammar ${METRIC_NAME_PATTERN.source}.`,
fixHint:
"Metric names must match ^warpgogol_(factory|probe|delivery|workers)_[a-z0-9_]+$, counters end in _total, durations in _seconds, sizes in _bytes.",
});
}
}
// OBS-CONV-03: forbidden label keys in registry
for (const spec of WARPGOGOL_METRIC_REGISTRY) {
for (const key of spec.labelKeys) {
if (FORBIDDEN_LABEL_KEYS.includes(key)) {
diagnostics.push({
ruleId: "OBS-CONV-03",
severity: "error",
file: "packages/observability/src/metric-registry.ts",
message: `Metric "${spec.name}" declares forbidden label key "${key}".`,
fixHint: `Forbidden label keys: ${FORBIDDEN_LABEL_KEYS.join(", ")}. Remove the label or use a bounded alternative.`,
});
}
}
}
// OBS-CONV-05: duplicate metric names
const seen = new Map();
for (const spec of WARPGOGOL_METRIC_REGISTRY) {
seen.set(spec.name, (seen.get(spec.name) ?? 0) + 1);
}
for (const [name, count] of seen) {
if (count > 1) {
diagnostics.push({
ruleId: "OBS-CONV-05",
severity: "error",
file: "packages/observability/src/metric-registry.ts",
message: `Duplicate metric name "${name}" appears ${count} times in WARPGOGOL_METRIC_REGISTRY.`,
fixHint: "Each metric name must be unique in the registry.",
});
}
}
// Build the set of declared metric names for OBS-CONV-01
const declaredNames = new Set(WARPGOGOL_METRIC_REGISTRY.map((s) => s.name));
// OBS-CONV-01: scan for undeclared metric name literals used with pusher calls or METRIC_REFS
const sourceGlobs = ["packages/**/*.ts", "services/**/*.ts"];
for (const glob of sourceGlobs) {
const files = await context.io.glob(glob, { cwd: context.workspaceRoot });
for (const file of files) {
const normalized = file.replace(/\\/g, "/");
// Skip the observability package itself (it declares the registry + typed refs)
if (normalized.startsWith("packages/observability/")) continue;
const text = await context.io.readFile(join(context.workspaceRoot, normalized));
const usedNames = new Set();
let match: RegExpExecArray | null;
// String-literal pusher calls
if (PUSHER_CALL_PATTERN.test(text)) {
METRIC_NAME_LITERAL_PATTERN.lastIndex = 0;
while ((match = METRIC_NAME_LITERAL_PATTERN.exec(text)) !== null) {
usedNames.add(match[1]!);
}
}
// METRIC_REFS property accesses (typed-ref consumers)
METRIC_REFS_ACCESS_PATTERN.lastIndex = 0;
while ((match = METRIC_REFS_ACCESS_PATTERN.exec(text)) !== null) {
usedNames.add(match[1]!);
}
for (const name of usedNames) {
if (!declaredNames.has(name)) {
diagnostics.push({
ruleId: "OBS-CONV-01",
severity: "error",
file: normalized,
message: `Metric name "${name}" is used with a pusher call or METRIC_REFS but is not declared in WARPGOGOL_METRIC_REGISTRY.`,
fixHint: `Add "${name}" to packages/observability/src/metric-registry.ts or use an existing declared metric.`,
});
}
}
}
}
// OBS-CONV-04: direct OTLP env reads outside @warpgogol/werkstatt-shared/observability
const envSourceGlobs = ["packages/**/*.ts", "services/**/*.ts"];
for (const glob of envSourceGlobs) {
const files = await context.io.glob(glob, { cwd: context.workspaceRoot });
for (const file of files) {
const normalized = file.replace(/\\/g, "/");
// Skip the observability package and its kernel module — they are the port
if (normalized.startsWith("packages/observability/")) continue;
if (normalized.startsWith("packages/os/site-kernel-observability/")) continue;
// Skip generated files
if (normalized.includes(".generated.")) continue;
const text = await context.io.readFile(join(context.workspaceRoot, normalized));
if (OTLP_ENV_PATTERN.test(text)) {
diagnostics.push({
ruleId: "OBS-CONV-04",
severity: "warning",
file: normalized,
message:
"Direct reference to WARPGOGOL_OTLP_ENDPOINT/WARPGOGOL_OTLP_TOKEN outside @warpgogol/werkstatt-shared/observability. Emitters must go through the port.",
fixHint:
"Use createMetricsPusher from @warpgogol/werkstatt-shared/observability which reads env vars internally.",
});
}
}
}
return diagnosticsResult("observability.conventions.validate", diagnostics);
}