Merge pull request #1195 from pikasTech/fix/1190-monitor-root-switch
fix: switch monitor root via sentinel yaml
This commit is contained in:
@@ -653,6 +653,12 @@ lanes:
|
||||
observability:
|
||||
prometheusOperator: false
|
||||
webProbe:
|
||||
monitorRoot:
|
||||
enabled: true
|
||||
sentinelId: workbench-fake-echo-session-invariance-10x
|
||||
publicBaseUrl: https://monitor.pikapython.com
|
||||
routePrefix: /
|
||||
caddyManagedBlockOwner: hwlab-web-probe-sentinel-active-root
|
||||
sentinels:
|
||||
- id: workbench-dsflash-go-tool-call-10x
|
||||
enabled: true
|
||||
|
||||
@@ -159,7 +159,7 @@ createApp({
|
||||
|
||||
function currentHref(item) {
|
||||
if (!item || item.id === bootstrap.sentinelId) return bootstrap.basePath || "/";
|
||||
if (item.id === "workbench-dsflash-go-tool-call-10x") return "/";
|
||||
if (item.monitorRoot === true) return "/";
|
||||
return `/sentinels/${encodeURIComponent(item.id)}/`;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,14 @@ export interface HwlabRuntimeWebProbeSentinelRegistryItemSpec {
|
||||
readonly configRef: string;
|
||||
}
|
||||
|
||||
export interface HwlabRuntimeWebProbeMonitorRootSpec {
|
||||
readonly enabled: boolean;
|
||||
readonly sentinelId: string;
|
||||
readonly publicBaseUrl: string;
|
||||
readonly routePrefix: "/";
|
||||
readonly caddyManagedBlockOwner: string;
|
||||
}
|
||||
|
||||
export interface HwlabRuntimeWebProbeAlertThresholdsSpec {
|
||||
readonly sameOriginApiSlowMs: number;
|
||||
readonly partialApiSlowMs: number;
|
||||
@@ -206,6 +214,7 @@ export interface HwlabRuntimeObservabilitySpec {
|
||||
export interface HwlabRuntimeObservabilityWebProbeSpec {
|
||||
readonly sentinel?: HwlabRuntimeWebProbeSentinelSpec;
|
||||
readonly sentinels?: readonly HwlabRuntimeWebProbeSentinelRegistryItemSpec[];
|
||||
readonly monitorRoot?: HwlabRuntimeWebProbeMonitorRootSpec;
|
||||
}
|
||||
|
||||
export interface HwlabRuntimeObservabilityMetricsEndpointSpec {
|
||||
@@ -1141,14 +1150,56 @@ 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", "sentinels"]);
|
||||
const allowed = new Set(["sentinel", "sentinels", "monitorRoot"]);
|
||||
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/sentinels`);
|
||||
if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; observability.webProbe currently only owns sentinel/sentinels/monitorRoot`);
|
||||
}
|
||||
if (raw.sentinel !== undefined && raw.sentinels !== undefined) throw new Error(`${path} may declare sentinel or sentinels, not both`);
|
||||
const sentinel = raw.sentinel === undefined ? undefined : webProbeSentinelConfig(raw.sentinel, `${path}.sentinel`);
|
||||
const sentinels = raw.sentinels === undefined ? undefined : webProbeSentinelRegistryConfig(raw.sentinels, `${path}.sentinels`);
|
||||
const monitorRoot = raw.monitorRoot === undefined ? undefined : webProbeMonitorRootConfig(raw.monitorRoot, `${path}.monitorRoot`);
|
||||
if (monitorRoot !== undefined) {
|
||||
if (sentinels !== undefined && !sentinels.some((item) => item.id === monitorRoot.sentinelId)) {
|
||||
throw new Error(`${path}.monitorRoot.sentinelId must reference one entry from ${path}.sentinels`);
|
||||
}
|
||||
if (sentinel !== undefined && monitorRoot.sentinelId !== "workbench-dsflash-go-tool-call-10x") {
|
||||
throw new Error(`${path}.monitorRoot.sentinelId must be workbench-dsflash-go-tool-call-10x for legacy sentinel config`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
...(raw.sentinel === undefined ? {} : { sentinel: webProbeSentinelConfig(raw.sentinel, `${path}.sentinel`) }),
|
||||
...(raw.sentinels === undefined ? {} : { sentinels: webProbeSentinelRegistryConfig(raw.sentinels, `${path}.sentinels`) }),
|
||||
...(sentinel === undefined ? {} : { sentinel }),
|
||||
...(sentinels === undefined ? {} : { sentinels }),
|
||||
...(monitorRoot === undefined ? {} : { monitorRoot }),
|
||||
};
|
||||
}
|
||||
|
||||
function webProbeMonitorRootConfig(value: unknown, path: string): HwlabRuntimeWebProbeMonitorRootSpec {
|
||||
const raw = asRecord(value, path);
|
||||
const allowed = new Set(["enabled", "sentinelId", "publicBaseUrl", "routePrefix", "caddyManagedBlockOwner"]);
|
||||
for (const key of Object.keys(raw)) {
|
||||
if (!allowed.has(key)) throw new Error(`${path}.${key} is not allowed; monitorRoot may only contain enabled/sentinelId/publicBaseUrl/routePrefix/caddyManagedBlockOwner`);
|
||||
}
|
||||
const sentinelId = stringField(raw, "sentinelId", path);
|
||||
if (!/^[a-z0-9][a-z0-9-]{1,80}$/u.test(sentinelId)) throw new Error(`${path}.sentinelId must be a stable lowercase sentinel id`);
|
||||
const publicBaseUrl = stringField(raw, "publicBaseUrl", path).replace(/\/+$/u, "");
|
||||
try {
|
||||
const parsed = new URL(publicBaseUrl);
|
||||
if (parsed.protocol !== "https:") throw new Error("must use https");
|
||||
if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) throw new Error("must point to the public origin root");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`${path}.publicBaseUrl must be an https origin root URL: ${message}`);
|
||||
}
|
||||
const routePrefix = stringField(raw, "routePrefix", path);
|
||||
if (routePrefix !== "/") throw new Error(`${path}.routePrefix must be / for the monitor root switch`);
|
||||
const caddyManagedBlockOwner = stringField(raw, "caddyManagedBlockOwner", path);
|
||||
if (!/^[a-z0-9][a-z0-9-]{1,100}$/u.test(caddyManagedBlockOwner)) throw new Error(`${path}.caddyManagedBlockOwner must be a stable lowercase owner id`);
|
||||
return {
|
||||
enabled: booleanField(raw, "enabled", path),
|
||||
sentinelId,
|
||||
publicBaseUrl,
|
||||
routePrefix,
|
||||
caddyManagedBlockOwner,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { repoRoot, rootPath } from "./config";
|
||||
import { runCommand, type CommandResult } from "./command";
|
||||
import { startJob } from "./jobs";
|
||||
import { webProbeSentinelConfigPlan, withWebProbeSentinelConfigRendered } from "./hwlab-node-web-sentinel-config";
|
||||
import { requireSentinelIdForRegistry, resolveWebProbeSentinel } from "./hwlab-node-web-sentinel-resolver";
|
||||
import { effectiveWebProbeSentinelPublicExposure, requireSentinelIdForRegistry, resolveWebProbeSentinel } from "./hwlab-node-web-sentinel-resolver";
|
||||
import type { HwlabRuntimeLaneSpec } from "./hwlab-node-lanes";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
import { runWebProbeRemoteArtifactJob } from "./web-probe-remote-artifact";
|
||||
@@ -310,7 +310,8 @@ function loadSentinelCicdState(spec: HwlabRuntimeLaneSpec, sentinelId: string |
|
||||
const runtime = recordTarget(readConfigRefTarget(sentinel.configRefs.runtime), sentinel.configRefs.runtime);
|
||||
const cicd = recordTarget(readConfigRefTarget(sentinel.configRefs.cicd), sentinel.configRefs.cicd);
|
||||
const scenarios = readConfigRefTarget(sentinel.configRefs.scenarios);
|
||||
const publicExposure = recordTarget(readConfigRefTarget(sentinel.configRefs.publicExposure), sentinel.configRefs.publicExposure);
|
||||
const rawPublicExposure = recordTarget(readConfigRefTarget(sentinel.configRefs.publicExposure), sentinel.configRefs.publicExposure);
|
||||
const publicExposure = effectiveWebProbeSentinelPublicExposure(spec, sentinel.id, rawPublicExposure);
|
||||
const secrets = recordTarget(readConfigRefTarget(sentinel.configRefs.secrets), sentinel.configRefs.secrets);
|
||||
const controlPlaneRef = stringField(cicd, "controlPlaneConfigRef");
|
||||
const controlPlaneTarget = recordTarget(readConfigRefTarget(controlPlaneRef), controlPlaneRef);
|
||||
@@ -3584,6 +3585,9 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
|
||||
const responseHeaderTimeoutSeconds = numberAt(state.publicExposure, "caddy.responseHeaderTimeoutSeconds");
|
||||
const remotePort = numberAt(state.publicExposure, "frpc.httpProxy.remotePort");
|
||||
const routePrefix = normalizeRoutePrefix(stringAtNullable(state.publicExposure, "routePrefix"));
|
||||
const rootOrder = stringAtNullable(state.publicExposure, "caddy.rootOrder") ?? "normal";
|
||||
const monitorRoot = record(state.publicExposure.monitorRoot);
|
||||
const cleanupOwner = monitorRoot.enabled === false ? stringAtNullable(monitorRoot, "caddyManagedBlockOwner") : null;
|
||||
const proxyLines = [
|
||||
`reverse_proxy 127.0.0.1:${remotePort} {`,
|
||||
" transport http {",
|
||||
@@ -3605,6 +3609,8 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
|
||||
`config_path=${shellQuote(configPath)}`,
|
||||
`service=${shellQuote(serviceName)}`,
|
||||
`route_prefix=${shellQuote(routePrefix)}`,
|
||||
`root_order=${shellQuote(rootOrder)}`,
|
||||
`cleanup_owner=${shellQuote(cleanupOwner ?? "")}`,
|
||||
`block_b64=${shellQuote(blockB64)}`,
|
||||
"marker=\"unidesk managed $owner\"",
|
||||
"tmp=$(mktemp -d)",
|
||||
@@ -3613,17 +3619,21 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
|
||||
"next=\"$tmp/Caddyfile\"",
|
||||
"printf '%s' \"$block_b64\" | base64 -d >\"$block\"",
|
||||
"if [ -f \"$config_path\" ]; then cp \"$config_path\" \"$next\"; else : >\"$next\"; fi",
|
||||
"python3 - \"$next\" \"$block\" \"$marker\" \"$hostname\" \"$route_prefix\" <<'PY' >/tmp/web-probe-sentinel-caddy-python.out 2>/tmp/web-probe-sentinel-caddy-python.err",
|
||||
"python3 - \"$next\" \"$block\" \"$marker\" \"$hostname\" \"$route_prefix\" \"$root_order\" \"$cleanup_owner\" <<'PY' >/tmp/web-probe-sentinel-caddy-python.out 2>/tmp/web-probe-sentinel-caddy-python.err",
|
||||
"import pathlib, re, sys",
|
||||
"config = pathlib.Path(sys.argv[1])",
|
||||
"block = pathlib.Path(sys.argv[2]).read_text(encoding='utf-8')",
|
||||
"marker = sys.argv[3]",
|
||||
"hostname = sys.argv[4]",
|
||||
"route_prefix = sys.argv[5]",
|
||||
"root_order = sys.argv[6]",
|
||||
"cleanup_owner = sys.argv[7]",
|
||||
"text = config.read_text(encoding='utf-8') if config.exists() else ''",
|
||||
"begin = f'# BEGIN {marker}'",
|
||||
"end = f'# END {marker}'",
|
||||
"pattern = re.compile(rf'(?ms)^[ \\t]*# BEGIN {re.escape(marker)}\\n.*?^[ \\t]*# END {re.escape(marker)}\\n*')",
|
||||
"def managed_pattern(marker_text):",
|
||||
" return re.compile(rf'(?ms)^[ \\t]*# BEGIN {re.escape(marker_text)}\\n.*?^[ \\t]*# END {re.escape(marker_text)}\\n*')",
|
||||
"pattern = managed_pattern(marker)",
|
||||
"def collect_nested_managed(segment):",
|
||||
" preserved = []",
|
||||
" lines = segment.splitlines()",
|
||||
@@ -3647,6 +3657,10 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
|
||||
"for match in pattern.finditer(text):",
|
||||
" preserved_blocks.extend(collect_nested_managed(match.group(0)))",
|
||||
"text = pattern.sub('', text)",
|
||||
"if cleanup_owner:",
|
||||
" cleanup_marker = f'unidesk managed {cleanup_owner}'",
|
||||
" if cleanup_marker != marker:",
|
||||
" text = managed_pattern(cleanup_marker).sub('', text)",
|
||||
"def site_span(src, host):",
|
||||
" match = re.search(rf'(?m)^([ \\t]*){re.escape(host)}[ \\t]*\\{{[ \\t]*\\n', src)",
|
||||
" if not match:",
|
||||
@@ -3696,7 +3710,9 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
|
||||
" relative_open = open_end - start",
|
||||
" close_rel = close_index - start",
|
||||
" additions = ''.join(preserved_blocks) + managed",
|
||||
" if route_prefix == '/':",
|
||||
" if route_prefix == '/' and root_order == 'active':",
|
||||
" replacement = site[:relative_open] + additions + site[relative_open:]",
|
||||
" elif route_prefix == '/':",
|
||||
" replacement = append_before_close(site, close_rel, additions)",
|
||||
" else:",
|
||||
" insert_at = fallback_insert_pos(site, relative_open, close_rel)",
|
||||
@@ -3753,7 +3769,7 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
|
||||
].join("\n");
|
||||
const result = runCommand(["trans", stringAt(state.publicExposure, "caddy.route"), "sh", "--", script], repoRoot, { timeoutMs: Math.min(timeoutSeconds, 60) * 1000 });
|
||||
const parsed = parseJsonObject(result.stdout);
|
||||
return { ok: result.exitCode === 0 && parsed?.ok === true, routePrefix, ...record(parsed), result: compactCommand(result), valuesRedacted: true };
|
||||
return { ok: result.exitCode === 0 && parsed?.ok === true, routePrefix, rootOrder, ...record(parsed), result: compactCommand(result), valuesRedacted: true };
|
||||
}
|
||||
|
||||
function readAnalysisSummaryFromWorkspace(state: SentinelCicdState, stateDir: string, timeoutSeconds: number): Record<string, unknown> {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { rootPath } from "./config";
|
||||
import { HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS, type HwlabRuntimeLaneSpec, type HwlabRuntimeWebProbeSentinelConfigRefKey } from "./hwlab-node-lanes";
|
||||
import { resolveWebProbeSentinel, webProbeSentinelRegistryRows, type WebProbeSentinelRegistryRow } from "./hwlab-node-web-sentinel-resolver";
|
||||
import { effectiveWebProbeSentinelPublicExposure, resolveWebProbeSentinel, webProbeSentinelRegistryRows, type WebProbeSentinelRegistryRow } from "./hwlab-node-web-sentinel-resolver";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
|
||||
export type WebProbeSentinelConfigAction = "plan" | "status";
|
||||
@@ -211,7 +211,9 @@ export function webProbeSentinelConfigPlan(spec: HwlabRuntimeLaneSpec, action: W
|
||||
}
|
||||
|
||||
const selected = resolveWebProbeSentinel(spec, sentinelId);
|
||||
const refs = HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS.map((key) => readSentinelConfigRef(key, selected.configRefs[key]));
|
||||
const refs = HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS
|
||||
.map((key) => readSentinelConfigRef(key, selected.configRefs[key]))
|
||||
.map((ref) => effectiveConfigRefStatus(spec, selected.id, ref));
|
||||
const conflicts = selected.enabled ? crossReferenceConflicts(spec, refs) : [];
|
||||
const refBlocked = refs.some((ref) => !ref.present || !ref.targetPresent || ref.missingFields.length > 0 || ref.conflicts.length > 0 || ref.error !== null);
|
||||
const ok = selected.enabled && !refBlocked && conflicts.length === 0;
|
||||
@@ -232,6 +234,18 @@ export function webProbeSentinelConfigPlan(spec: HwlabRuntimeLaneSpec, action: W
|
||||
};
|
||||
}
|
||||
|
||||
function effectiveConfigRefStatus(spec: HwlabRuntimeLaneSpec, sentinelId: string, ref: InternalConfigRefStatus): InternalConfigRefStatus {
|
||||
if (ref.key !== "publicExposure" || !isRecord(ref.target)) return ref;
|
||||
const target = effectiveWebProbeSentinelPublicExposure(spec, sentinelId, ref.target);
|
||||
return {
|
||||
...ref,
|
||||
targetKind: targetKindOf(target),
|
||||
missingFields: missingFieldsForTarget(ref.key, target),
|
||||
summary: summarizeTarget(ref.key, target),
|
||||
target,
|
||||
};
|
||||
}
|
||||
|
||||
export function withWebProbeSentinelConfigRendered(value: WebProbeSentinelConfigPlan): RenderedCliResult {
|
||||
return {
|
||||
ok: value.ok,
|
||||
@@ -416,7 +430,15 @@ function summarizeTarget(key: HwlabRuntimeWebProbeSentinelConfigRefKey, target:
|
||||
if (key === "runtime") return `namespace=${textAt(target, "namespace")} service=${textAt(target, "serviceName")} image=${short(textAt(target, "imageRef"), 48)}`;
|
||||
if (key === "promptSet") return `id=${textAt(target, "id")} provider=${textAt(target, "providerProfile")} prompts=${textAt(target, "promptCount")} markers=${arrayAt(target, "expectedMarkers").slice(0, 12).join(",") || "-"} source=${textAt(target, "promptSourceRef")}:${textAt(target, "promptSourceKey")}`;
|
||||
if (key === "reportViews") return `default=${textAt(target, "defaultView")} views=${arrayAt(target, "views").length}`;
|
||||
if (key === "publicExposure") return `enabled=${textAt(target, "enabled")} mode=${textAt(target, "mode")} url=${textAt(target, "publicBaseUrl")}`;
|
||||
if (key === "publicExposure") {
|
||||
const monitorRoot = textAt(target, "monitorRoot.enabled");
|
||||
const rootOrder = textAt(target, "caddy.rootOrder");
|
||||
const suffix = [
|
||||
...(monitorRoot === "-" ? [] : [`monitorRoot=${monitorRoot}`]),
|
||||
...(rootOrder === "-" ? [] : [`rootOrder=${rootOrder}`]),
|
||||
].join(" ");
|
||||
return `enabled=${textAt(target, "enabled")} mode=${textAt(target, "mode")} url=${textAt(target, "publicBaseUrl")}${suffix.length === 0 ? "" : ` ${suffix}`}`;
|
||||
}
|
||||
if (key === "cicd") return `gitops=${textAt(target, "gitopsPath")} image=${textAt(target, "image.repository")}:${textAt(target, "image.tagSource")} confirmWait=${textAt(target, "confirmWait.maxSeconds")} targetValidation=${textAt(target, "targetValidation.maxSeconds")}`;
|
||||
if (key === "secrets") return `sources=${arrayAt(target, "sources").length} runtimeSecrets=${arrayAt(target, "runtimeSecrets").length}`;
|
||||
return `keys=${Object.keys(target).length}`;
|
||||
@@ -442,8 +464,8 @@ function renderWebProbeSentinelConfigPlan(value: WebProbeSentinelConfigPlan): st
|
||||
...(value.sentinels.length === 0 ? [] : [
|
||||
"",
|
||||
sentinelTable(
|
||||
["SENTINEL", "ENABLED", "CONFIG_REF"],
|
||||
value.sentinels.map((item) => [item.id, item.enabled, short(item.configRef, 110)]),
|
||||
["SENTINEL", "ENABLED", "ROOT", "CONFIG_REF"],
|
||||
value.sentinels.map((item) => [item.id, item.enabled, item.monitorRoot ? "monitor-root" : "-", short(item.configRef, 110)]),
|
||||
),
|
||||
]),
|
||||
...(value.refs.length === 0 ? [
|
||||
|
||||
@@ -61,10 +61,11 @@ export function renderWebProbeSentinelDashboardHtml(config: DashboardShellConfig
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function sentinelRegistryRows(config: DashboardShellConfig): Array<{ readonly id: string; readonly enabled: boolean }> {
|
||||
function sentinelRegistryRows(config: DashboardShellConfig): Array<{ readonly id: string; readonly enabled: boolean; readonly monitorRoot: boolean }> {
|
||||
return Array.isArray(config.plan.sentinels) ? config.plan.sentinels.map((item) => ({
|
||||
id: stringOrNull(item.id) ?? "",
|
||||
enabled: item.enabled !== false,
|
||||
monitorRoot: item.monitorRoot === true,
|
||||
})).filter((item) => item.id.length > 0) : [];
|
||||
}
|
||||
|
||||
|
||||
@@ -18,18 +18,57 @@ export interface WebProbeSentinelRegistryRow {
|
||||
readonly id: string;
|
||||
readonly enabled: boolean;
|
||||
readonly configRef: string;
|
||||
readonly monitorRoot: boolean;
|
||||
readonly publicBaseUrl?: string;
|
||||
readonly routePrefix?: string;
|
||||
}
|
||||
|
||||
export function webProbeSentinelRegistryRows(spec: HwlabRuntimeLaneSpec): readonly WebProbeSentinelRegistryRow[] {
|
||||
const registry = spec.observability.webProbe?.sentinels;
|
||||
if (registry !== undefined) return registry.map((item) => ({ id: item.id, enabled: item.enabled, configRef: item.configRef }));
|
||||
if (registry !== undefined) return registry.map((item) => sentinelRegistryRow(spec, item.id, item.enabled, item.configRef));
|
||||
const legacy = spec.observability.webProbe?.sentinel;
|
||||
if (legacy === undefined) return [];
|
||||
return [{
|
||||
id: "workbench-dsflash-go-tool-call-10x",
|
||||
enabled: legacy.enabled,
|
||||
configRef: `config/hwlab-node-lanes.yaml#lanes.${spec.lane}.targets.${spec.nodeId}.observability.webProbe.sentinel`,
|
||||
}];
|
||||
return [sentinelRegistryRow(
|
||||
spec,
|
||||
"workbench-dsflash-go-tool-call-10x",
|
||||
legacy.enabled,
|
||||
`config/hwlab-node-lanes.yaml#lanes.${spec.lane}.targets.${spec.nodeId}.observability.webProbe.sentinel`,
|
||||
)];
|
||||
}
|
||||
|
||||
export function effectiveWebProbeSentinelPublicExposure(spec: HwlabRuntimeLaneSpec, sentinelId: string, publicExposure: Record<string, unknown>): Record<string, unknown> {
|
||||
const monitorRoot = spec.observability.webProbe?.monitorRoot;
|
||||
if (monitorRoot === undefined || monitorRoot.sentinelId !== sentinelId) return publicExposure;
|
||||
if (!monitorRoot.enabled) {
|
||||
return {
|
||||
...publicExposure,
|
||||
monitorRoot: {
|
||||
enabled: false,
|
||||
sentinelId,
|
||||
caddyManagedBlockOwner: monitorRoot.caddyManagedBlockOwner,
|
||||
valuesRedacted: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
const caddy = isRecord(publicExposure.caddy) ? publicExposure.caddy : {};
|
||||
return {
|
||||
...publicExposure,
|
||||
publicBaseUrl: monitorRoot.publicBaseUrl,
|
||||
routePrefix: monitorRoot.routePrefix,
|
||||
caddy: {
|
||||
...caddy,
|
||||
managedBlockOwner: monitorRoot.caddyManagedBlockOwner,
|
||||
rootOrder: "active",
|
||||
},
|
||||
monitorRoot: {
|
||||
enabled: true,
|
||||
sentinelId,
|
||||
publicBaseUrl: monitorRoot.publicBaseUrl,
|
||||
routePrefix: monitorRoot.routePrefix,
|
||||
caddyManagedBlockOwner: monitorRoot.caddyManagedBlockOwner,
|
||||
valuesRedacted: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveWebProbeSentinel(spec: HwlabRuntimeLaneSpec, sentinelId: string | null | undefined): ResolvedWebProbeSentinel {
|
||||
@@ -87,6 +126,18 @@ function resolveRegistrySentinel(spec: HwlabRuntimeLaneSpec, registry: readonly
|
||||
};
|
||||
}
|
||||
|
||||
function sentinelRegistryRow(spec: HwlabRuntimeLaneSpec, id: string, enabled: boolean, configRef: string): WebProbeSentinelRegistryRow {
|
||||
const monitorRoot = spec.observability.webProbe?.monitorRoot;
|
||||
const isMonitorRoot = monitorRoot?.enabled === true && monitorRoot.sentinelId === id;
|
||||
return {
|
||||
id,
|
||||
enabled,
|
||||
configRef,
|
||||
monitorRoot: isMonitorRoot,
|
||||
...(isMonitorRoot ? { publicBaseUrl: monitorRoot.publicBaseUrl, routePrefix: monitorRoot.routePrefix } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSentinelConfigRefs(target: Record<string, unknown>, ref: string): Record<HwlabRuntimeWebProbeSentinelConfigRefKey, string> {
|
||||
const rawRefs = recordAt(target, "configRefs");
|
||||
const normalized: Record<string, string> = {};
|
||||
|
||||
@@ -13,7 +13,7 @@ import { rootPath } from "./config";
|
||||
import { renderWebProbeSentinelDashboardHtml, webProbeSentinelDashboardAssetResponse } from "./hwlab-node-web-sentinel-dashboard-assets";
|
||||
import { webProbeSentinelConfigPlan, type WebProbeSentinelConfigPlan } from "./hwlab-node-web-sentinel-config";
|
||||
import type { HwlabRuntimeLaneSpec } from "./hwlab-node-lanes";
|
||||
import { resolveWebProbeSentinel, readConfigRefTarget as readSentinelConfigRefTarget } from "./hwlab-node-web-sentinel-resolver";
|
||||
import { effectiveWebProbeSentinelPublicExposure, resolveWebProbeSentinel, readConfigRefTarget as readSentinelConfigRefTarget } from "./hwlab-node-web-sentinel-resolver";
|
||||
|
||||
const DASHBOARD_CONTRACT_VERSION = "draft-2026-06-27-p11-monitor-web-observability-dashboard";
|
||||
const DASHBOARD_MAX_TEXT_BYTES = 16_000;
|
||||
@@ -93,7 +93,8 @@ export function loadWebProbeSentinelServiceConfig(spec: HwlabRuntimeLaneSpec, op
|
||||
const runtime = recordTarget(readSentinelConfigRefTarget(sentinel.configRefs.runtime));
|
||||
const scenarios = scenarioArrayTarget(readSentinelConfigRefTarget(sentinel.configRefs.scenarios));
|
||||
const reportViews = recordTarget(readSentinelConfigRefTarget(sentinel.configRefs.reportViews));
|
||||
const publicExposure = recordTarget(readSentinelConfigRefTarget(sentinel.configRefs.publicExposure));
|
||||
const rawPublicExposure = recordTarget(readSentinelConfigRefTarget(sentinel.configRefs.publicExposure));
|
||||
const publicExposure = effectiveWebProbeSentinelPublicExposure(spec, sentinel.id, rawPublicExposure);
|
||||
const cicd = recordTarget(readSentinelConfigRefTarget(sentinel.configRefs.cicd));
|
||||
const stateRoot = options.stateRootOverride ?? stringAt(runtime, "stateRoot");
|
||||
const yamlSqlitePath = stringAt(runtime, "sqlite.path");
|
||||
|
||||
Reference in New Issue
Block a user