feat(web-probe): add multi-sentinel registry

This commit is contained in:
Codex
2026-06-26 12:42:04 +00:00
parent 7b3df965cc
commit 4e0f1cba21
25 changed files with 1038 additions and 140 deletions
+41 -2
View File
@@ -1,5 +1,6 @@
// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-25-p0-web-probe-sentinel.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-26-p9-multi-web-probe-sentinel.
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
// Responsibility: YAML source-of-truth parsing for HWLAB node/lane Workbench observability.
import { readFileSync } from "node:fs";
@@ -149,6 +150,12 @@ export interface HwlabRuntimeWebProbeSentinelSpec {
readonly configRefs: Record<HwlabRuntimeWebProbeSentinelConfigRefKey, string>;
}
export interface HwlabRuntimeWebProbeSentinelRegistryItemSpec {
readonly id: string;
readonly enabled: boolean;
readonly configRef: string;
}
export interface HwlabRuntimeWebProbeAlertThresholdsSpec {
readonly sameOriginApiSlowMs: number;
readonly partialApiSlowMs: number;
@@ -189,6 +196,7 @@ export interface HwlabRuntimeObservabilitySpec {
export interface HwlabRuntimeObservabilityWebProbeSpec {
readonly sentinel?: HwlabRuntimeWebProbeSentinelSpec;
readonly sentinels?: readonly HwlabRuntimeWebProbeSentinelRegistryItemSpec[];
}
export interface HwlabRuntimeObservabilityMetricsEndpointSpec {
@@ -793,6 +801,35 @@ function webProbeSentinelConfig(value: unknown, path: string): HwlabRuntimeWebPr
};
}
function webProbeSentinelRegistryItemConfig(value: unknown, path: string): HwlabRuntimeWebProbeSentinelRegistryItemSpec {
const raw = asRecord(value, path);
const allowed = new Set(["id", "enabled", "configRef"]);
for (const key of Object.keys(raw)) {
if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; sentinel registry items may only contain id/enabled/configRef`);
}
const id = stringField(raw, "id", path);
if (!/^[a-z0-9][a-z0-9-]{1,80}$/u.test(id)) throw new Error(`${path}.id must be a stable lowercase sentinel id`);
const configRef = stringField(raw, "configRef", path);
validateConfigRef(configRef, `${path}.configRef`);
return {
id,
enabled: booleanField(raw, "enabled", path),
configRef,
};
}
function webProbeSentinelRegistryConfig(value: unknown, path: string): readonly HwlabRuntimeWebProbeSentinelRegistryItemSpec[] {
if (!Array.isArray(value)) throw new Error(`${path} must be an array`);
if (value.length === 0) throw new Error(`${path} must contain at least one sentinel`);
const items = value.map((item, index) => webProbeSentinelRegistryItemConfig(item, `${path}[${index}]`));
const ids = new Set<string>();
for (const item of items) {
if (ids.has(item.id)) throw new Error(`${path} contains duplicate sentinel id ${item.id}`);
ids.add(item.id);
}
return items;
}
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) {
@@ -937,12 +974,14 @@ function observabilityConfig(value: unknown, path: string): HwlabRuntimeObservab
function observabilityWebProbeConfig(value: unknown, path: string): HwlabRuntimeObservabilityWebProbeSpec | undefined {
if (value === undefined) return undefined;
const raw = asRecord(value, path);
const allowed = new Set(["sentinel"]);
const allowed = new Set(["sentinel", "sentinels"]);
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`);
if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; observability.webProbe currently only owns sentinel/sentinels`);
}
if (raw.sentinel !== undefined && raw.sentinels !== undefined) throw new Error(`${path} may declare sentinel or sentinels, not both`);
return {
...(raw.sentinel === undefined ? {} : { sentinel: webProbeSentinelConfig(raw.sentinel, `${path}.sentinel`) }),
...(raw.sentinels === undefined ? {} : { sentinels: webProbeSentinelRegistryConfig(raw.sentinels, `${path}.sentinels`) }),
};
}