feat: add Workbench observability rule summaries

This commit is contained in:
Codex
2026-06-17 11:56:02 +00:00
parent 07f852c3f0
commit d46bedf759
3 changed files with 261 additions and 1 deletions
+80
View File
@@ -87,6 +87,8 @@ export interface HwlabRuntimeObservabilitySpec {
readonly prometheusOperator: boolean;
readonly metricsEndpoint?: HwlabRuntimeObservabilityMetricsEndpointSpec;
readonly workbench?: HwlabRuntimeObservabilityWorkbenchSpec;
readonly recordingRules: readonly HwlabRuntimeObservabilityRecordingRuleSpec[];
readonly warningAlerts: readonly HwlabRuntimeObservabilityWarningAlertSpec[];
}
export interface HwlabRuntimeObservabilityMetricsEndpointSpec {
@@ -102,12 +104,34 @@ export interface HwlabRuntimeObservabilityMetricsEndpointSpec {
export interface HwlabRuntimeObservabilityWorkbenchSpec {
readonly enabled: boolean;
readonly summaryPath: string;
readonly lowSampleThreshold: number;
readonly metricPrefixes: readonly string[];
readonly requiredSeries: readonly string[];
readonly backendLabelDenylist: readonly string[];
readonly maxUnknownEventLines: number;
}
export interface HwlabRuntimeObservabilityRecordingRuleSpec {
readonly id: string;
readonly metric: string;
readonly sourceMetric: string;
readonly quantile: number;
readonly window: string;
readonly minSamples: number;
readonly groupBy: readonly string[];
readonly matchLabels: Record<string, string>;
}
export interface HwlabRuntimeObservabilityWarningAlertSpec {
readonly id: string;
readonly ruleId: string;
readonly severity: "warning";
readonly thresholdSeconds: number;
readonly minSamples: number;
readonly for: string;
readonly matchLabels: Record<string, string>;
}
export interface HwlabRuntimeImageRewriteSpec {
readonly source: string;
readonly target: string;
@@ -295,6 +319,12 @@ function nonNegativeIntegerField(obj: Record<string, unknown>, key: string, path
return value;
}
function positiveNumberField(obj: Record<string, unknown>, key: string, path: string): number {
const value = obj[key];
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new Error(`${path}.${key} must be a positive number`);
return value;
}
function sortedRecordEntries(value: unknown, path: string): Array<[string, Record<string, unknown>]> {
return Object.entries(asRecord(value, path)).map(([key, item]) => [key, asRecord(item, `${path}.${key}`)]);
}
@@ -584,10 +614,18 @@ function publicExposureConfig(value: unknown, path: string): HwlabRuntimePublicE
function observabilityConfig(value: unknown, path: string): HwlabRuntimeObservabilitySpec {
const raw = asRecord(value, path);
const recordingRules = observabilityRecordingRulesConfig(raw.recordingRules, `${path}.recordingRules`);
const warningAlerts = observabilityWarningAlertsConfig(raw.warningAlerts, `${path}.warningAlerts`);
const recordingRuleIds = new Set(recordingRules.map((rule) => rule.id));
for (const alert of warningAlerts) {
if (!recordingRuleIds.has(alert.ruleId)) throw new Error(`${path}.warningAlerts.${alert.id}.ruleId must reference a recordingRules id`);
}
return {
prometheusOperator: booleanField(raw, "prometheusOperator", path),
metricsEndpoint: observabilityMetricsEndpointConfig(raw.metricsEndpoint, `${path}.metricsEndpoint`),
workbench: observabilityWorkbenchConfig(raw.workbench, `${path}.workbench`),
recordingRules,
warningAlerts,
};
}
@@ -617,6 +655,7 @@ function observabilityWorkbenchConfig(value: unknown, path: string): HwlabRuntim
return {
enabled: booleanField(raw, "enabled", path),
summaryPath: stringField(raw, "summaryPath", path),
lowSampleThreshold: numberField(raw, "lowSampleThreshold", path),
metricPrefixes: stringArrayField(raw, "metricPrefixes", path),
requiredSeries: stringArrayField(raw, "requiredSeries", path),
backendLabelDenylist: stringArrayField(raw, "backendLabelDenylist", path),
@@ -624,6 +663,47 @@ function observabilityWorkbenchConfig(value: unknown, path: string): HwlabRuntim
};
}
function observabilityRecordingRulesConfig(value: unknown, path: string): HwlabRuntimeObservabilityRecordingRuleSpec[] {
if (value === undefined) return [];
if (!Array.isArray(value)) throw new Error(`${path} must be an array`);
return value.map((item, index) => {
const itemPath = `${path}[${index}]`;
const raw = asRecord(item, itemPath);
const quantile = positiveNumberField(raw, "quantile", itemPath);
if (quantile >= 1) throw new Error(`${itemPath}.quantile must be less than 1`);
return {
id: stringField(raw, "id", itemPath),
metric: stringField(raw, "metric", itemPath),
sourceMetric: stringField(raw, "sourceMetric", itemPath),
quantile,
window: stringField(raw, "window", itemPath),
minSamples: numberField(raw, "minSamples", itemPath),
groupBy: stringArrayField(raw, "groupBy", itemPath),
matchLabels: optionalStringRecord(raw.matchLabels, `${itemPath}.matchLabels`),
};
});
}
function observabilityWarningAlertsConfig(value: unknown, path: string): HwlabRuntimeObservabilityWarningAlertSpec[] {
if (value === undefined) return [];
if (!Array.isArray(value)) throw new Error(`${path} must be an array`);
return value.map((item, index) => {
const itemPath = `${path}[${index}]`;
const raw = asRecord(item, itemPath);
const severity = stringField(raw, "severity", itemPath);
if (severity !== "warning") throw new Error(`${itemPath}.severity must be warning`);
return {
id: stringField(raw, "id", itemPath),
ruleId: stringField(raw, "ruleId", itemPath),
severity,
thresholdSeconds: positiveNumberField(raw, "thresholdSeconds", itemPath),
minSamples: numberField(raw, "minSamples", itemPath),
for: stringField(raw, "for", itemPath),
matchLabels: optionalStringRecord(raw.matchLabels, `${itemPath}.matchLabels`),
};
});
}
function runtimeImageRewritesConfig(value: unknown, path: string): HwlabRuntimeImageRewriteSpec[] {
if (value === undefined) return [];
if (!Array.isArray(value)) throw new Error(`${path} must be an array`);