// 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. // Responsibility: Redacted YAML configRef graph for web-probe sentinel plan/status. import { readFileSync } from "node:fs"; import { rootPath } from "./config"; import { readWebProbeSentinelConfigRef } from "./hwlab-node-web-sentinel-config-ref"; import { HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS, type HwlabRuntimeLaneSpec, type HwlabRuntimeWebProbeSentinelConfigRefKey } from "./hwlab-node-lanes"; import { effectiveWebProbeSentinelPublicExposure, resolveWebProbeSentinel, webProbeSentinelRegistryRows, type WebProbeSentinelRegistryRow } from "./hwlab-node-web-sentinel-resolver"; import type { RenderedCliResult } from "./output"; export type WebProbeSentinelConfigAction = "plan" | "status"; export interface WebProbeSentinelConfigPlan { readonly ok: boolean; readonly command: string; readonly status: "ready" | "blocked" | "disabled"; readonly node: string; readonly lane: string; readonly rootPath: string; readonly sentinelId: string | null; readonly enabled: boolean; readonly sentinels: readonly WebProbeSentinelRegistryRow[]; readonly refs: readonly WebProbeSentinelConfigRefStatus[]; readonly conflicts: readonly string[]; readonly next: Record; readonly valuesRedacted: true; } export interface WebProbeSentinelConfigRefStatus { readonly key: HwlabRuntimeWebProbeSentinelConfigRefKey; readonly ref: string; readonly file: string; readonly path: string; readonly present: boolean; readonly targetPresent: boolean; readonly targetKind: "object" | "array" | "scalar" | "null" | "missing"; readonly sha256: string | null; readonly byteCount: number | null; readonly missingFields: readonly string[]; readonly conflicts: readonly string[]; readonly summary: string; readonly error: string | null; } interface InternalConfigRefStatus extends WebProbeSentinelConfigRefStatus { readonly target: unknown; } interface RequiredTargetShape { readonly kind: "object" | "array"; readonly requiredPaths: readonly string[]; } const REQUIRED_TARGET_SHAPES: Record = { runtime: { kind: "object", requiredPaths: [ "target.node", "target.lane", "target.publicOriginRef", "target.observeWrapperRef", "namespace", "serviceAccountName", "deploymentName", "serviceName", "listenHost", "servicePort", "pvcName", "pvcStorage", "stateRoot", "imageRef", "replicas", "healthPath", "metricsPath", "scheduler.intervalMs", "scheduler.heartbeatStaleSeconds", "scheduler.maxConcurrentRuns", "sqlite.path", "sqlite.busyTimeoutMs", ], }, scenarios: { kind: "array", requiredPaths: [ "id", "enabled", "cadence", "observeTargetPath", "sampleIntervalMs", "screenshotIntervalMs", "maxRunSeconds", "providerProfile", "providerProfileMode", "promptSetRef", "reportViewRef", "commandSequence[0].type", ], }, promptSet: { kind: "object", requiredPaths: ["id", "providerProfile", "providerProfileMode", "promptSourceRef", "promptSourceKey", "promptCount", "redaction"], }, reportViews: { kind: "object", requiredPaths: ["defaultView", "views[0]", "pageSize", "maxPageSize", "rawAccess", "checkCatalogRef", "detailMemory.maxPages", "detailMemory.maxSamplesPerPage", "redaction.prompt", "redaction.secrets"], }, publicExposure: { kind: "object", requiredPaths: [ "enabled", "mode", "publicBaseUrl", "hostname", "expectedA", "frpc.deploymentName", "frpc.image", "frpc.serverAddr", "frpc.serverPort", "frpc.tokenSourceRef", "frpc.tokenSourceKey", "frpc.secretName", "frpc.secretKey", "frpc.httpProxy.name", "frpc.httpProxy.localIP", "frpc.httpProxy.localPort", "caddy.route", "caddy.configPath", "caddy.serviceName", "caddy.managedBlockOwner", ], }, cicd: { kind: "object", requiredPaths: [ "controlPlaneConfigRef", "source.repository", "source.branch", "source.gitMirrorReadUrl", "source.buildContext", "source.entrypoint", "sourceAuthority.mode", "sourceAuthority.resolver", "sourceAuthority.allowHostGit", "sourceAuthority.allowGithubDirectInPipeline", "sourceSnapshot.stageRefPrefix", "sourceSnapshot.missingObjectPolicy", "sourceSnapshot.refreshPolicy", "sourceSnapshot.cacheRef", "gitopsPath", "argo.namespace", "argo.projectName", "argo.applicationName", "argo.repoURL", "argo.targetRevision", "image.repository", "image.tagSource", "image.baseImageRef", "image.envRecipeRef", "maintenance.startCommand", "maintenance.stopCommand", "confirmWait.maxSeconds", "targetValidation.scenarioId", "targetValidation.maxSeconds", "targetValidation.serviceUnavailablePolicy", ], }, secrets: { kind: "object", requiredPaths: [ "sources[0].purpose", "sources[0].sourceRef", "sources[0].sourceKey", "runtimeSecrets[0].name", "runtimeSecrets[0].namespace", "runtimeSecrets[0].data[0].sourcePurpose", "runtimeSecrets[0].data[0].targetKey", ], }, }; export function webProbeSentinelConfigPlan(spec: HwlabRuntimeLaneSpec, action: WebProbeSentinelConfigAction, sentinelId: string | null = null): WebProbeSentinelConfigPlan { const command = `web-probe sentinel ${action} --node ${spec.nodeId} --lane ${spec.lane}${sentinelId === null ? "" : ` --sentinel ${sentinelId}`}`; const registry = webProbeSentinelRegistryRows(spec); const registryPath = `config/hwlab-node-lanes.yaml#lanes.${spec.lane}.targets.${spec.nodeId}.observability.webProbe.sentinels`; if (sentinelId === null && registry.length > 1) { const enabled = registry.some((item) => item.enabled); return { ok: enabled, command, status: enabled ? "ready" : "disabled", node: spec.nodeId, lane: spec.lane, rootPath: registryPath, sentinelId: null, enabled, sentinels: registry, refs: [], conflicts: [], next: sentinelNext(spec.nodeId, spec.lane, registry[0]?.id ?? null), valuesRedacted: true, }; } if (registry.length === 0) { return { ok: false, command, status: "blocked", node: spec.nodeId, lane: spec.lane, rootPath: registryPath, sentinelId, enabled: false, sentinels: [], refs: [], conflicts: [`${registryPath} is missing`], next: sentinelNext(spec.nodeId, spec.lane, sentinelId), valuesRedacted: true, }; } const selected = resolveWebProbeSentinel(spec, sentinelId); const refs = HWLAB_WEB_PROBE_SENTINEL_CONFIG_REF_KEYS .map((key) => readSentinelConfigRef(spec, 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; return { ok, command, status: selected.enabled ? ok ? "ready" : "blocked" : "disabled", node: spec.nodeId, lane: spec.lane, rootPath: selected.rootPath, sentinelId: selected.id, enabled: selected.enabled, sentinels: registry, refs: refs.map(stripInternalTarget), conflicts, next: sentinelNext(spec.nodeId, spec.lane, selected.id), valuesRedacted: true, }; } 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, command: value.command, contentType: "text/plain", renderedText: renderWebProbeSentinelConfigPlan(value), }; } function readSentinelConfigRef(spec: HwlabRuntimeLaneSpec, key: HwlabRuntimeWebProbeSentinelConfigRefKey, ref: string): InternalConfigRefStatus { try { const resolved = readWebProbeSentinelConfigRef(spec, ref); const target = resolved.target; const targetKind = target === undefined ? "missing" : targetKindOf(target); const missingFields = target === undefined ? ["target"] : missingFieldsForTarget(key, target); return { key, ref, file: resolved.file, path: resolved.path, present: true, targetPresent: target !== undefined, targetKind, sha256: resolved.sha256, byteCount: resolved.byteCount, missingFields, conflicts: [], summary: summarizeTarget(key, target), error: null, target, }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return emptyRefStatus(key, ref, "", "", message); } } function emptyRefStatus(key: HwlabRuntimeWebProbeSentinelConfigRefKey, ref: string, file: string, path: string, error: string): InternalConfigRefStatus { return { key, ref, file, path, present: false, targetPresent: false, targetKind: "missing", sha256: null, byteCount: null, missingFields: ["target"], conflicts: [], summary: "-", error, target: undefined, }; } function missingFieldsForTarget(key: HwlabRuntimeWebProbeSentinelConfigRefKey, target: unknown): string[] { const shape = REQUIRED_TARGET_SHAPES[key]; if (shape.kind === "array") { const items = key === "scenarios" && isRecord(target) ? [target] : Array.isArray(target) ? target : null; if (items === null) return [`expected ${shape.kind}`]; if (items.length === 0) return ["[0]"]; return items.flatMap((item, index) => shape.requiredPaths .filter((path) => valueAtPath(item, path) === undefined) .map((path) => `[${index}].${path}`)); } if (!isRecord(target)) return [`expected ${shape.kind}`]; return shape.requiredPaths.filter((path) => valueAtPath(target, path) === undefined); } function crossReferenceConflicts(spec: HwlabRuntimeLaneSpec, refs: readonly InternalConfigRefStatus[]): string[] { const byKey = new Map(refs.map((ref) => [ref.key, ref])); const conflicts: string[] = []; const runtime = recordTarget(byKey.get("runtime")); const scenarios = scenarioTargets(byKey.get("scenarios")); const promptSet = recordTarget(byKey.get("promptSet")); const reportViews = recordTarget(byKey.get("reportViews")); const cicd = recordTarget(byKey.get("cicd")); const secrets = recordTarget(byKey.get("secrets")); if (runtime !== null) { requireEquals(conflicts, byKey.get("runtime"), "target.node", spec.nodeId, `selected node ${spec.nodeId}`); requireEquals(conflicts, byKey.get("runtime"), "target.lane", spec.lane, `selected lane ${spec.lane}`); requireEquals(conflicts, byKey.get("runtime"), "namespace", spec.runtimeNamespace, `selected namespace ${spec.runtimeNamespace}`); } const scenarioIds = new Set(); const scenarioProviders = new Set(); for (const [index, scenario] of scenarios.entries()) { const id = stringAt(scenario, "id"); if (id !== null) scenarioIds.add(id); const provider = stringAt(scenario, "providerProfile"); if (provider !== null) scenarioProviders.add(provider); requireReadableConfigRef(spec, conflicts, byKey.get("scenarios"), `[${index}].promptSetRef`, scenario.promptSetRef); requireReadableConfigRef(spec, conflicts, byKey.get("scenarios"), `[${index}].reportViewRef`, scenario.reportViewRef); } const promptProvider = promptSet === null ? null : stringAt(promptSet, "providerProfile"); if (promptProvider !== null && scenarioProviders.size > 0 && !scenarioProviders.has(promptProvider)) { conflicts.push(`${byKey.get("promptSet")?.file}#${byKey.get("promptSet")?.path}.providerProfile=${promptProvider} does not match scenario providerProfile set ${Array.from(scenarioProviders).join(",")}`); } const validationScenarioId = cicd === null ? null : stringAt(cicd, "targetValidation.scenarioId"); if (validationScenarioId !== null && !scenarioIds.has(validationScenarioId)) { conflicts.push(`${byKey.get("cicd")?.file}#${byKey.get("cicd")?.path}.targetValidation.scenarioId=${validationScenarioId} is not declared in scenarios`); } validateReportViewsCheckCatalog(spec, conflicts, byKey.get("reportViews"), reportViews); const runtimeNamespace = runtime === null ? null : stringAt(runtime, "namespace"); const runtimeSecrets = secrets === null ? [] : arrayAt(secrets, "runtimeSecrets"); if (runtimeNamespace !== null) { for (const [index, item] of runtimeSecrets.entries()) { const namespace = stringAt(item, "namespace"); if (namespace !== null && namespace !== runtimeNamespace) { conflicts.push(`${byKey.get("secrets")?.file}#${byKey.get("secrets")?.path}.runtimeSecrets[${index}].namespace=${namespace} does not match runtime namespace ${runtimeNamespace}`); } } } return conflicts; } function validateReportViewsCheckCatalog(spec: HwlabRuntimeLaneSpec, conflicts: string[], ref: InternalConfigRefStatus | undefined, reportViews: Record | null): void { if (reportViews === null) return; const catalogRef = stringAt(reportViews, "checkCatalogRef"); if (catalogRef === null) { conflicts.push(`${ref?.file ?? "-"}#${ref?.path ?? "-"}.checkCatalogRef is required`); return; } let catalog: Record; try { const target = readWebProbeSentinelConfigRef(spec, catalogRef).target; catalog = isRecord(target) ? target : {}; } catch (error) { conflicts.push(`${ref?.file ?? "-"}#${ref?.path ?? "-"}.checkCatalogRef=${catalogRef} is not readable: ${error instanceof Error ? error.message : String(error)}`); return; } const items = arrayAt(catalog, "items"); if (items.length === 0) { conflicts.push(`${catalogRef}.items is empty`); return; } const byId = new Map>(); const codes = new Set(); for (const [index, item] of items.entries()) { const itemId = stringAt(item, "id"); const code = stringAt(item, "code"); if (itemId === null) conflicts.push(`${catalogRef}.items[${index}].id is required`); else if (byId.has(itemId)) conflicts.push(`${catalogRef}.items id duplicated: ${itemId}`); else byId.set(itemId, item); if (code === null) conflicts.push(`${catalogRef}.items[${index}].code is required`); else if (codes.has(code)) conflicts.push(`${catalogRef}.items code duplicated: ${code}`); else codes.add(code); for (const field of ["titleZh", "summaryZh", "actionZh"]) { const text = stringAt(item, field); if (text === null) { conflicts.push(`${catalogRef}.items[${index}].${field} is required`); } else if (!containsChinese(text)) { conflicts.push(`${catalogRef}.items[${index}].${field} must contain Chinese text`); } } } const requiredIds = knownWebProbeFindingIds(); const missing = requiredIds.filter((id) => !byId.has(id)); if (missing.length > 0) { conflicts.push(`${catalogRef}.items missing analyzer finding ids: ${missing.slice(0, 24).join(",")}${missing.length > 24 ? `,+${missing.length - 24}` : ""}`); } } function knownWebProbeFindingIds(): string[] { const ids = new Set(); for (const file of [ "scripts/src/hwlab-node-web-observe-analyzer-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-api-dom-lag-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-browser-process-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-findings-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-io-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-performance-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-project-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-request-runtime-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-sample-metrics-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-session-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-window-page-source.ts", "scripts/src/hwlab-node-web-observe-analyzer-workbench-triad-source.ts", "scripts/src/hwlab-node-web-sentinel-p5-observe.ts", ]) { let source = ""; try { source = readFileSync(rootPath(file), "utf8"); } catch { continue; } for (const match of source.matchAll(/\bid:\s*["`]([A-Za-z0-9_.:-]+)["`]/gu)) ids.add(match[1]); } return Array.from(ids).sort(); } function containsChinese(value: string): boolean { return /[\u3400-\u9fff]/u.test(value); } function requireReadableConfigRef(spec: HwlabRuntimeLaneSpec, conflicts: string[], ref: InternalConfigRefStatus | undefined, path: string, value: unknown): void { if (typeof value !== "string" || value.length === 0) return; try { readWebProbeSentinelConfigRef(spec, value); } catch (error) { conflicts.push(`${ref?.file ?? "-"}#${ref?.path ?? "-"}.${path}=${value} is not readable: ${error instanceof Error ? error.message : String(error)}`); } } function requireEquals(conflicts: string[], ref: InternalConfigRefStatus | undefined, path: string, expected: string, expectedLabel: string): void { if (ref === undefined) return; const actual = stringAt(ref.target, path); if (actual !== null && actual !== expected) { conflicts.push(`${ref.file}#${ref.path}.${path}=${actual} does not match ${expectedLabel}`); } } function stripInternalTarget(ref: InternalConfigRefStatus): WebProbeSentinelConfigRefStatus { return { key: ref.key, ref: ref.ref, file: ref.file, path: ref.path, present: ref.present, targetPresent: ref.targetPresent, targetKind: ref.targetKind, sha256: ref.sha256, byteCount: ref.byteCount, missingFields: ref.missingFields, conflicts: ref.conflicts, summary: ref.summary, error: ref.error, }; } function summarizeTarget(key: HwlabRuntimeWebProbeSentinelConfigRefKey, target: unknown): string { if (target === undefined) return "target=missing"; if (key === "scenarios") { const items = isRecord(target) ? [target] : Array.isArray(target) ? target : []; const ids = items.map((item) => stringAt(item, "id")).filter((item): item is string => item !== null).slice(0, 4); const cadences = items.map((item) => stringAt(item, "cadence")).filter((item): item is string => item !== null).slice(0, 4); const checks = items.flatMap((item) => arrayAt(item, "sessionInvarianceChecks")); const afterRounds = checks .map((item) => { const value = isRecord(item) ? item.afterRound : null; return typeof value === "number" ? String(value) : null; }) .filter((item): item is string => item !== null) .slice(0, 8); return `items=${items.length} ids=${ids.join(",") || "-"} cadence=${cadences.join(",") || "-"} sessionInvarianceChecks=${checks.length} afterRound=${afterRounds.join(",") || "-"}`; } if (!isRecord(target)) return `kind=${targetKindOf(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") { 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}`; } function renderWebProbeSentinelConfigPlan(value: WebProbeSentinelConfigPlan): string { const blocked = value.ok ? [] : [ "", "Blocked detail:", sentinelTable(["KIND", "VALUE"], [ ...value.conflicts.map((item) => ["conflict", short(item, 140)]), ...value.refs.flatMap((ref) => [ ...(ref.error === null ? [] : [[`${ref.key}.error`, short(ref.error, 140)]]), ...(ref.missingFields.length === 0 ? [] : [[`${ref.key}.missing`, short(ref.missingFields.join(","), 140)]]), ...(ref.conflicts.length === 0 ? [] : [[`${ref.key}.conflict`, short(ref.conflicts.join(" | "), 140)]]), ]), ]), ]; return [ `web-probe sentinel ${commandAction(value.command)} (${value.status})`, "", sentinelTable(["NODE", "LANE", "SENTINEL", "ENABLED", "OK", "ROOT"], [[value.node, value.lane, value.sentinelId ?? "registry", value.enabled, value.ok, value.rootPath]]), ...(value.sentinels.length === 0 ? [] : [ "", sentinelTable( ["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 ? [ "", "DRILL_DOWN", ...value.sentinels.map((item) => ` ${item.id}: bun scripts/cli.ts web-probe sentinel ${commandAction(value.command)} --node ${value.node} --lane ${value.lane} --sentinel ${item.id}`), ] : [ "", sentinelTable( ["KEY", "PRESENT", "TARGET", "TYPE", "HASH", "MISSING", "SUMMARY"], value.refs.map((ref) => [ ref.key, ref.present, ref.targetPresent, ref.targetKind, ref.sha256 === null ? "-" : `${ref.sha256.slice(0, 19)}...`, ref.missingFields.length === 0 ? "-" : short(ref.missingFields.join(","), 52), short(ref.summary, 90), ]), ), "", sentinelTable( ["KEY", "FILE", "PATH", "BYTES"], value.refs.map((ref) => [ref.key, ref.file, ref.path, ref.byteCount ?? "-"]), ), ]), ...blocked, "", "NEXT", ` plan: ${value.next.plan}`, ` status: ${value.next.status}`, "DISCLOSURE", " valuesRedacted=true; secret values and full YAML objects are not printed.", ].join("\n"); } function sentinelNext(node: string, lane: string, sentinelId: string | null): Record { const suffix = sentinelId === null ? "" : ` --sentinel ${sentinelId}`; return { plan: `bun scripts/cli.ts web-probe sentinel plan --node ${node} --lane ${lane}${suffix} --dry-run`, status: `bun scripts/cli.ts web-probe sentinel status --node ${node} --lane ${lane}${suffix}`, }; } function valueAtPath(value: unknown, path: string): unknown { let current: unknown = value; for (const segment of path.split(".")) { if (segment.length === 0) return undefined; const match = /^(?:([A-Za-z0-9_-]+))?(?:\[(\d+)\])?$/u.exec(segment); if (match === null) return undefined; if (match[1] !== undefined) { if (!isRecord(current)) return undefined; current = current[match[1]]; } if (match[2] !== undefined) { if (!Array.isArray(current)) return undefined; current = current[Number(match[2])]; } } return current; } function stringAt(value: unknown, path: string): string | null { const found = valueAtPath(value, path); return typeof found === "string" && found.length > 0 ? found : null; } function textAt(value: unknown, path: string): string { const found = valueAtPath(value, path); if (typeof found === "string") return found; if (typeof found === "number" || typeof found === "boolean") return String(found); return "-"; } function arrayAt(value: unknown, path: string): unknown[] { const found = valueAtPath(value, path); return Array.isArray(found) ? found : []; } function recordTarget(ref: InternalConfigRefStatus | undefined): Record | null { return ref !== undefined && isRecord(ref.target) ? ref.target : null; } function arrayTarget(ref: InternalConfigRefStatus | undefined): Record[] { return ref !== undefined && Array.isArray(ref.target) ? ref.target.filter(isRecord) : []; } function scenarioTargets(ref: InternalConfigRefStatus | undefined): Record[] { if (ref === undefined) return []; if (Array.isArray(ref.target)) return ref.target.filter(isRecord); return isRecord(ref.target) ? [ref.target] : []; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function targetKindOf(value: unknown): "object" | "array" | "scalar" | "null" { if (value === null) return "null"; if (Array.isArray(value)) return "array"; if (isRecord(value)) return "object"; return "scalar"; } function commandAction(command: string): string { return command.includes(" status ") ? "status" : "plan"; } function sentinelTable(headers: string[], rows: unknown[][]): string { const normalized = [headers, ...rows.map((row) => row.map((cell) => sentinelText(cell)))]; const widths = headers.map((_, index) => Math.max(...normalized.map((row) => sentinelText(row[index] ?? "").length))); return normalized.map((row) => row.map((cell, index) => sentinelText(cell).padEnd(widths[index])).join(" ").trimEnd()).join("\n"); } function sentinelText(value: unknown): string { if (value === null || value === undefined || value === "") return "-"; if (typeof value === "boolean") return value ? "true" : "false"; return String(value).replace(/\s+/gu, " ").trim(); } function short(value: string, maxLength: number): string { if (value.length <= maxLength) return value; if (maxLength <= 1) return value.slice(0, maxLength); return `${value.slice(0, maxLength - 1)}~`; }