feat: wire web probe sentinel validation (#912)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-26 00:46:45 +08:00
committed by GitHub
parent 4bd179e898
commit db3fed012e
6 changed files with 1205 additions and 13 deletions
+66 -5
View File
@@ -21,7 +21,7 @@ import { nodeWebObserveCollectViewNodeScript, parseNodeWebProbeObserveCollectVie
import { withWebObserveCollectRendered, withWebObserveCommandRendered, withWebObserveStatusRendered } from "../hwlab-node-web-observe-render";
import { buildWebObserveWrapperForObserveOptions, webObserveWrapperStateDirFromStatus } from "../hwlab-node-web-observe-wrapper";
import { renderWebObserveWrapperContract } from "../hwlab-node-web-observe-wrapper-render";
import { runWebProbeSentinelCommand, type WebProbeSentinelOptions } from "../hwlab-node-web-sentinel-cicd";
import { runWebProbeSentinelCommand, type WebProbeSentinelOptions, type WebProbeSentinelReportView } from "../hwlab-node-web-sentinel-cicd";
import { hwlabNodeHelp, hwlabNodeObservabilityHelp, hwlabNodeWebProbeHelp } from "../hwlab-node-help";
import { compactWebProbeResult, compactWebProbeScriptResult } from "../hwlab-node-web-probe-summary";
import { nodeObservabilityRecordingRuleExpression, nodeObservabilityRecordingRuleSummaries, nodeObservabilityWarningAlertExpression, nodeObservabilityWarningAlertSummaries } from "../hwlab-node-observability-promql";
@@ -38,10 +38,29 @@ import { displayRepoPath, readBootstrapAdminPasswordMaterial, sleepSync } from "
export function parseNodeWebProbeSentinelOptions(args: string[]): NodeWebProbeSentinelOptions {
const [sentinelActionRaw] = args;
if (sentinelActionRaw !== "plan" && sentinelActionRaw !== "status" && sentinelActionRaw !== "image" && sentinelActionRaw !== "control-plane") {
throw new Error("web-probe sentinel usage: sentinel plan|status|image|control-plane --node NODE --lane vNN [--dry-run|--confirm]");
if (
sentinelActionRaw !== "plan"
&& sentinelActionRaw !== "status"
&& sentinelActionRaw !== "image"
&& sentinelActionRaw !== "control-plane"
&& sentinelActionRaw !== "validate"
&& sentinelActionRaw !== "maintenance"
&& sentinelActionRaw !== "report"
) {
throw new Error("web-probe sentinel usage: sentinel plan|status|image|control-plane|validate|maintenance|report --node NODE --lane vNN [--dry-run|--confirm]");
}
assertKnownOptions(args, new Set(["--node", "--lane", "--timeout-seconds"]), new Set(["--dry-run", "--confirm", "--wait"]));
assertKnownOptions(args, new Set([
"--node",
"--lane",
"--timeout-seconds",
"--release-id",
"--reason",
"--view",
"--run",
"--run-id",
"--trace-id",
"--sample-seq",
]), new Set(["--dry-run", "--confirm", "--wait", "--quick-verify", "--raw"]));
const node = requiredOption(args, "--node");
assertNodeId(node);
const lane = requiredOption(args, "--lane");
@@ -58,12 +77,49 @@ export function parseNodeWebProbeSentinelOptions(args: string[]): NodeWebProbeSe
const imageAction = args[1];
if (imageAction !== "status" && imageAction !== "build") throw new Error("web-probe sentinel image usage: image status|build --node NODE --lane vNN [--dry-run|--confirm]");
sentinel = { kind: "image", action: imageAction, node, lane, dryRun: imageAction === "build" ? dryRun || !confirm : dryRun, confirm, wait: args.includes("--wait"), timeoutSeconds };
} else {
} else if (sentinelActionRaw === "control-plane") {
const controlPlaneAction = args[1];
if (controlPlaneAction !== "plan" && controlPlaneAction !== "apply" && controlPlaneAction !== "status" && controlPlaneAction !== "trigger-current") {
throw new Error("web-probe sentinel control-plane usage: control-plane plan|apply|status|trigger-current --node NODE --lane vNN [--dry-run|--confirm]");
}
sentinel = { kind: "control-plane", action: controlPlaneAction, node, lane, dryRun: controlPlaneAction === "apply" || controlPlaneAction === "trigger-current" ? dryRun || !confirm : dryRun, confirm, wait: args.includes("--wait"), timeoutSeconds };
} else if (sentinelActionRaw === "maintenance") {
const maintenanceAction = args[1];
if (maintenanceAction !== "status" && maintenanceAction !== "start" && maintenanceAction !== "stop") {
throw new Error("web-probe sentinel maintenance usage: maintenance status|start|stop --node NODE --lane vNN [--dry-run|--confirm]");
}
sentinel = {
kind: "maintenance",
action: maintenanceAction,
node,
lane,
dryRun: maintenanceAction === "status" ? dryRun : dryRun || !confirm,
confirm,
wait: args.includes("--wait"),
timeoutSeconds,
releaseId: optionValue(args, "--release-id") ?? null,
reason: optionValue(args, "--reason") ?? null,
quickVerify: maintenanceAction === "stop" || args.includes("--quick-verify"),
};
} else if (sentinelActionRaw === "validate") {
sentinel = { kind: "validate", action: "validate", node, lane, dryRun, confirm, wait: args.includes("--wait"), timeoutSeconds, quickVerify: args.includes("--quick-verify") };
} else {
const view = parseWebProbeSentinelReportView(optionValue(args, "--view") ?? "summary");
const sampleSeqRaw = optionValue(args, "--sample-seq") ?? null;
const sampleSeq = sampleSeqRaw === null ? null : Number(sampleSeqRaw);
if (sampleSeq !== null && (!Number.isInteger(sampleSeq) || sampleSeq < 1)) throw new Error("web-probe sentinel report --sample-seq must be a positive integer");
sentinel = {
kind: "report",
action: "report",
node,
lane,
view,
runId: optionValue(args, "--run") ?? optionValue(args, "--run-id") ?? null,
traceId: optionValue(args, "--trace-id") ?? null,
sampleSeq,
raw: args.includes("--raw"),
timeoutSeconds,
};
}
return {
action: "sentinel",
@@ -73,6 +129,11 @@ export function parseNodeWebProbeSentinelOptions(args: string[]): NodeWebProbeSe
};
}
function parseWebProbeSentinelReportView(value: string): WebProbeSentinelReportView {
if (value === "summary" || value === "turn-summary" || value === "findings" || value === "trace-frame") return value;
throw new Error(`web-probe sentinel report --view must be summary, turn-summary, findings, or trace-frame; got ${value}`);
}
export function normalizeNodeWebProbeObserveArgs(args: string[]): { args: string[]; id: string | null } {
const [observeActionRaw, maybeId, ...rest] = args;
if (observeActionRaw !== "start" && maybeId !== undefined && !maybeId.startsWith("--")) {