feat: 增加 L1 native Web 单命令验收

This commit is contained in:
Codex
2026-07-17 19:44:05 +02:00
parent f38090da6f
commit 3333e6aa49
9 changed files with 373 additions and 2 deletions
@@ -639,7 +639,9 @@ export function runManagedNodeWebProbeScript(
result: reportResult,
requestedStdoutType: "web-probe script report JSON",
acceptParsed: isWebProbeScriptReportContract,
forceArtifactFallbackReason: commandTimedOut ? "remote-command-timeout" : null,
forceArtifactFallbackReason: commandTimedOut
? "remote-command-timeout"
: asyncJob !== null ? "remote-artifact-job-report" : null,
artifactFallback: {
path: runPaths.reportPath,
nextCommand: runPaths.reportPath === null ? null : `trans ${options.node}:${spec.workspace} cat ${shellQuote(runPaths.reportPath)}`,
+57 -1
View File
@@ -16,6 +16,7 @@ import { CliInputError } from "../output";
import { HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, hwlabNodeControlPlaneCiGitWorkspaceSecret, hwlabNodeControlPlaneInfraHelp, runHwlabNodeControlPlaneInfra } from "../hwlab-node-control-plane";
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, hwlabRuntimeLaneSpecForNode, hwlabRuntimeNodeIds, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec, type HwlabRuntimeObservabilityRecordingRuleSpec, type HwlabRuntimeObservabilitySpec, type HwlabRuntimeObservabilityWarningAlertSpec, type HwlabRuntimePublicExposureSpec, type HwlabRuntimeWebProbeAlertThresholdsSpec, type HwlabRuntimeWebProbeProjectManagementSpec } from "../hwlab-node-lanes";
import { nodeWebProbeConsoleVerifyScript } from "../hwlab-node-web-probe-console-verify";
import { nodeWebProbeNativeReadinessScript } from "../hwlab-node-web-probe-native-readiness";
import { nodeWebProbeScriptRunnerSource } from "../hwlab-node-web-probe-runner-source";
import { nodeWebObserveAnalyzerSource } from "../hwlab-node-web-observe-analyzer-source";
import { nodeWebObserveRunnerSource } from "../hwlab-node-web-observe-runner-source";
@@ -2087,7 +2088,7 @@ export function rewriteDelegatedNodeString(value: string, scoped: ReturnType<typ
export function parseNodeWebProbeOptions(args: string[]): NodeWebProbeOptions {
const [actionRaw] = args;
if (actionRaw !== "run" && actionRaw !== "script" && actionRaw !== "screenshot" && actionRaw !== "observe" && actionRaw !== "sentinel" && actionRaw !== "opencode-smoke" && actionRaw !== "console-verify") throw new Error("web-probe usage: run|script|screenshot|observe|sentinel|opencode-smoke|console-verify --node NODE --lane vNN [--origin internal|public]");
if (actionRaw !== "run" && actionRaw !== "script" && actionRaw !== "screenshot" && actionRaw !== "observe" && actionRaw !== "sentinel" && actionRaw !== "opencode-smoke" && actionRaw !== "console-verify" && actionRaw !== "native-readiness") throw new Error("web-probe usage: run|script|screenshot|observe|sentinel|opencode-smoke|console-verify|native-readiness --node NODE --lane vNN [--origin internal|public]");
if (actionRaw === "sentinel") return parseNodeWebProbeSentinelOptions(args.slice(1));
if (actionRaw === "observe") {
const normalized = normalizeNodeWebProbeObserveArgs(args.slice(1));
@@ -2122,6 +2123,61 @@ export function parseNodeWebProbeOptions(args: string[]): NodeWebProbeOptions {
const browserProxyMode = browserProxyModeRaw === undefined ? undefined : parseWebProbeBrowserProxyMode(browserProxyModeRaw);
return resolveNodeWebProbeCliOrigin(spec, optionValue(args, "--origin"), optionValue(args, "--url"), browserProxyMode);
};
if (actionRaw === "native-readiness") {
assertKnownOptions(args.slice(1), new Set([
"--node",
"--lane",
"--profile",
"--timeout-ms",
"--command-timeout-seconds",
]), new Set([]));
const native = spec.nativeDevelopment?.workbench;
if (native === undefined) throw new Error(`${hwlabRuntimeLaneConfigPath()} node=${node} lane=${lane} does not declare nativeDevelopment.workbench`);
const profiles = spec.webProbe?.nativeReadinessProfiles;
const profileName = optionValue(args, "--profile") ?? spec.webProbe?.defaultNativeReadinessProfile;
if (profiles === undefined || profileName === undefined) {
throw new Error(`${hwlabRuntimeLaneConfigPath()} node=${node} lane=${lane} does not declare a default native readiness profile`);
}
const profile = profiles[profileName];
if (profile === undefined) {
throw new Error(`web-probe native-readiness unknown --profile ${profileName}; configured profiles: ${Object.keys(profiles).sort().join(", ")}`);
}
const timeoutMs = positiveIntegerOption(args, "--timeout-ms", profile.navigationTimeoutMs, 120_000);
const commandTimeoutSeconds = positiveIntegerOption(args, "--command-timeout-seconds", profile.commandTimeoutSeconds, 3600);
const configRef = `${hwlabRuntimeLaneConfigPath()}#templates.*.webProbeWorkbench.nativeReadinessProfiles.${profileName}`;
const scriptText = nodeWebProbeNativeReadinessScript(profileName, profile, configRef);
const url = `${profile.scheme}://${native.publicHost}:${native.web.port}`;
const selectedOrigin = resolveNodeWebProbeCliOrigin(spec, undefined, url, "direct");
const commandLabel = `web-probe native-readiness --node ${node} --lane ${lane} --profile ${profileName}`;
return {
action: "script",
node,
lane,
url: selectedOrigin.url,
originName: selectedOrigin.originName,
originMode: selectedOrigin.originMode,
originConfigPath: `${hwlabRuntimeLaneConfigPath()}#lanes.${lane}.targets.${node}.nativeDevelopment.workbench`,
timeoutMs,
viewport: `${profile.viewport.width}x${profile.viewport.height}`,
browserProxyMode: selectedOrigin.browserProxyMode,
browserProxyModeSource: selectedOrigin.browserProxyModeSource,
commandTimeoutSeconds,
scriptText,
commandLabel,
suppressAdHocWarning: true,
generatedHints: [
"L1 native Web readiness is a repo-owned typed command; public IP, port, path, DOM, interaction and failure rules come from owning YAML.",
"Pass requires mounted DOM, a completed interaction, no browser errors, no critical failed responses and a screenshot artifact.",
],
generatedPreferredCommands: { rerun: commandLabel },
scriptSource: {
kind: "generated",
path: `builtin:native-readiness/${profileName}`,
byteCount: Buffer.byteLength(scriptText),
sha256: `sha256:${createHash("sha256").update(scriptText).digest("hex")}`,
},
};
}
if (actionRaw === "console-verify") {
assertKnownOptions(args.slice(1), new Set([
"--node",