feat: add web probe sentinel config plan (#895)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
|
||||
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-25-p0-web-probe-sentinel.
|
||||
// Responsibility: YAML source-of-truth parsing for HWLAB node/lane Workbench observability.
|
||||
import { readFileSync } from "node:fs";
|
||||
import { rootPath } from "./config";
|
||||
@@ -131,6 +132,15 @@ export interface HwlabRuntimeWebProbeSpec {
|
||||
readonly projectManagement?: HwlabRuntimeWebProbeProjectManagementSpec;
|
||||
}
|
||||
|
||||
export type HwlabRuntimeWebProbeSentinelConfigRefKey = "runtime" | "scenarios" | "promptSet" | "reportViews" | "publicExposure" | "cicd" | "secrets";
|
||||
|
||||
export const HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS = ["runtime", "scenarios", "promptSet", "reportViews", "publicExposure", "cicd", "secrets"] as const satisfies readonly HwlabRuntimeWebProbeSentinelConfigRefKey[];
|
||||
|
||||
export interface HwlabRuntimeWebProbeSentinelSpec {
|
||||
readonly enabled: boolean;
|
||||
readonly configRefs: Record<HwlabRuntimeWebProbeSentinelConfigRefKey, string>;
|
||||
}
|
||||
|
||||
export interface HwlabRuntimeWebProbeAlertThresholdsSpec {
|
||||
readonly sameOriginApiSlowMs: number;
|
||||
readonly partialApiSlowMs: number;
|
||||
@@ -163,10 +173,15 @@ export interface HwlabRuntimeObservabilitySpec {
|
||||
readonly traceExplorerUrlTemplate?: string;
|
||||
readonly metricsEndpoint?: HwlabRuntimeObservabilityMetricsEndpointSpec;
|
||||
readonly workbench?: HwlabRuntimeObservabilityWorkbenchSpec;
|
||||
readonly webProbe?: HwlabRuntimeObservabilityWebProbeSpec;
|
||||
readonly recordingRules: readonly HwlabRuntimeObservabilityRecordingRuleSpec[];
|
||||
readonly warningAlerts: readonly HwlabRuntimeObservabilityWarningAlertSpec[];
|
||||
}
|
||||
|
||||
export interface HwlabRuntimeObservabilityWebProbeSpec {
|
||||
readonly sentinel?: HwlabRuntimeWebProbeSentinelSpec;
|
||||
}
|
||||
|
||||
export interface HwlabRuntimeObservabilityMetricsEndpointSpec {
|
||||
readonly serviceName: string;
|
||||
readonly containerName: string;
|
||||
@@ -732,6 +747,41 @@ function webProbeConfig(value: unknown, path: string): HwlabRuntimeWebProbeSpec
|
||||
};
|
||||
}
|
||||
|
||||
function webProbeSentinelConfig(value: unknown, path: string): HwlabRuntimeWebProbeSentinelSpec {
|
||||
const raw = asRecord(value, path);
|
||||
const allowed = new Set(["enabled", "configRefs"]);
|
||||
for (const key of Object.keys(raw)) {
|
||||
if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; root sentinel YAML may only contain enabled/configRefs`);
|
||||
}
|
||||
const refs = asRecord(raw.configRefs, `${path}.configRefs`);
|
||||
const allowedRefKeys = new Set(HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS);
|
||||
for (const key of Object.keys(refs)) {
|
||||
if (!allowedRefKeys.has(key as HwlabRuntimeWebProbeSentinelConfigRefKey)) throw new Error(`${path}.configRefs.${key} is not a supported sentinel configRef`);
|
||||
}
|
||||
const configRefs = Object.fromEntries(HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS.map((key) => {
|
||||
const ref = stringField(refs, key, `${path}.configRefs`);
|
||||
validateConfigRef(ref, `${path}.configRefs.${key}`);
|
||||
return [key, ref];
|
||||
})) as Record<HwlabRuntimeWebProbeSentinelConfigRefKey, string>;
|
||||
return {
|
||||
enabled: booleanField(raw, "enabled", path),
|
||||
configRefs,
|
||||
};
|
||||
}
|
||||
|
||||
function validateConfigRef(ref: string, path: string): void {
|
||||
const [file, fragment, extra] = ref.split("#");
|
||||
if (extra !== undefined || file === undefined || fragment === undefined || file.length === 0 || fragment.length === 0) {
|
||||
throw new Error(`${path} must use path/to/file.yaml#object.path syntax`);
|
||||
}
|
||||
if (file.startsWith("/") || file.includes("\0") || file.includes("..") || !file.startsWith("config/") || !file.endsWith(".yaml")) {
|
||||
throw new Error(`${path} must reference a repo-relative config/*.yaml file without ..`);
|
||||
}
|
||||
if (!/^[A-Za-z0-9_.\-[\]]+$/u.test(fragment)) {
|
||||
throw new Error(`${path} has an unsupported YAML path fragment`);
|
||||
}
|
||||
}
|
||||
|
||||
function webProbeProjectManagementConfig(value: unknown, path: string): HwlabRuntimeWebProbeProjectManagementSpec {
|
||||
const raw = asRecord(value, path);
|
||||
const targetPaths = nonEmptyStringArrayField(raw, "targetPaths", path);
|
||||
@@ -853,11 +903,24 @@ function observabilityConfig(value: unknown, path: string): HwlabRuntimeObservab
|
||||
traceExplorerUrlTemplate,
|
||||
metricsEndpoint: observabilityMetricsEndpointConfig(raw.metricsEndpoint, `${path}.metricsEndpoint`),
|
||||
workbench: observabilityWorkbenchConfig(raw.workbench, `${path}.workbench`),
|
||||
webProbe: observabilityWebProbeConfig(raw.webProbe, `${path}.webProbe`),
|
||||
recordingRules,
|
||||
warningAlerts,
|
||||
};
|
||||
}
|
||||
|
||||
function observabilityWebProbeConfig(value: unknown, path: string): HwlabRuntimeObservabilityWebProbeSpec | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
const raw = asRecord(value, path);
|
||||
const allowed = new Set(["sentinel"]);
|
||||
for (const key of Object.keys(raw)) {
|
||||
if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; observability.webProbe currently only owns sentinel`);
|
||||
}
|
||||
return {
|
||||
...(raw.sentinel === undefined ? {} : { sentinel: webProbeSentinelConfig(raw.sentinel, `${path}.sentinel`) }),
|
||||
};
|
||||
}
|
||||
|
||||
function validateTraceExplorerUrlTemplate(template: string, path: string): void {
|
||||
if (!template.includes("{trace_id}")) throw new Error(`${path} must include {trace_id}`);
|
||||
if (template.startsWith("//")) throw new Error(`${path} must not be protocol-relative`);
|
||||
|
||||
Reference in New Issue
Block a user