fix(cli): recover child json from dumps and artifacts
This commit is contained in:
@@ -11,6 +11,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
||||
import { dirname, join } from "node:path";
|
||||
import { repoRoot, rootPath, type Config } from "../config";
|
||||
import { runCommand, type CommandResult } from "../command";
|
||||
import { resolveCliChildJsonObject } from "../cli-child-json-recovery";
|
||||
import { startJob } from "../jobs";
|
||||
import { classifySshTcpPoolFailure } from "../ssh";
|
||||
import { HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, hwlabNodeControlPlaneInfraHelp, runHwlabNodeControlPlaneInfra } from "../hwlab-node-control-plane";
|
||||
@@ -2493,20 +2494,48 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption
|
||||
"exit \"$analyzer_exit\"",
|
||||
].join("\n");
|
||||
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
|
||||
const primaryAnalysis = recoverWebObserveAnalyzeTurnDetails(options, spec, parseJsonObject(result.stdout));
|
||||
const artifactAnalysis = (result.timedOut || result.exitCode !== 0 || primaryAnalysis === null)
|
||||
? recoverWebObserveAnalyzeFromArtifacts(options, spec, result)
|
||||
: null;
|
||||
const analysis = recoverWebObserveAnalyzeTurnDetails(options, spec, artifactAnalysis ?? primaryAnalysis);
|
||||
const analysisArtifactDir = options.stateDir ? join(options.stateDir, "analysis") : "";
|
||||
const reportJsonPath = analysisArtifactDir ? join(analysisArtifactDir, "report.json") : null;
|
||||
const collectReportCommand = webObserveAnalyzeCollectCommand(options, "analysis/report.json");
|
||||
const stdoutResolution = resolveCliChildJsonObject({
|
||||
stdout: result.stdout,
|
||||
stderr: result.stderr,
|
||||
requestedStdoutType: "web-probe observe analyze compact JSON",
|
||||
acceptParsed: isWebObserveAnalyzeJsonContract,
|
||||
forceArtifactFallbackReason: result.timedOut ? "remote-command-timeout" : result.exitCode !== 0 ? "remote-command-failed" : null,
|
||||
artifactFallback: {
|
||||
path: reportJsonPath,
|
||||
nextCommand: collectReportCommand,
|
||||
read: () => {
|
||||
const recovered = recoverWebObserveAnalyzeFromArtifacts(options, spec, result);
|
||||
if (recovered !== null) {
|
||||
return {
|
||||
ok: true,
|
||||
value: recovered,
|
||||
path: stringOrNullValue(recordValue(recovered).reportJsonPath) ?? reportJsonPath,
|
||||
reason: "analysis-artifact-contract",
|
||||
};
|
||||
}
|
||||
return { ok: false, reason: "analysis-artifact-missing-or-invalid", path: reportJsonPath };
|
||||
},
|
||||
},
|
||||
});
|
||||
const artifactAnalysis = stdoutResolution.source === "artifact" ? stdoutResolution.parsed : null;
|
||||
const analysis = recoverWebObserveAnalyzeTurnDetails(options, spec, stdoutResolution.parsed);
|
||||
const analysisOk = analysis?.ok === true;
|
||||
const stdoutDiagnostics = stdoutResolution.diagnostics;
|
||||
const failureReason = analysis === null
|
||||
? stringOrNullValue(recordValue(stdoutDiagnostics).fallbackReason) ?? "analyzer-output-not-json"
|
||||
: "analyzer-reported-not-ok";
|
||||
const analysisFailure = analysisOk
|
||||
? null
|
||||
: {
|
||||
reason: analysis === null ? "analyzer-output-not-json" : "analyzer-reported-not-ok",
|
||||
reason: failureReason,
|
||||
exitCode: result.exitCode,
|
||||
timedOut: result.timedOut,
|
||||
parsedJson: analysis !== null,
|
||||
recoveredFromArtifacts: artifactAnalysis !== null,
|
||||
stdoutRecovery: stdoutDiagnostics,
|
||||
stdoutBytes: result.stdout.length,
|
||||
stderrBytes: result.stderr.length,
|
||||
stdoutTail: result.stdout.trim().slice(-1200),
|
||||
@@ -2515,12 +2544,11 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption
|
||||
observerId: webObserveIdFromOptions(options),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
const analysisArtifactDir = options.stateDir ? join(options.stateDir, "analysis") : "";
|
||||
const failureAnalysis = analysis ?? (analysisFailure ? {
|
||||
ok: false,
|
||||
command: "web-probe-observe analyze",
|
||||
stateDir: options.stateDir,
|
||||
reportJsonPath: analysisArtifactDir ? join(analysisArtifactDir, "report.json") : null,
|
||||
reportJsonPath,
|
||||
reportMdPath: analysisArtifactDir ? join(analysisArtifactDir, "report.md") : null,
|
||||
analyzer: {
|
||||
exitCode: result.exitCode,
|
||||
@@ -2532,6 +2560,7 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption
|
||||
stdoutTail: result.stdout.trim().slice(-1200),
|
||||
stderrTail: result.stderr.trim().slice(-1200),
|
||||
recoveredFrom: "analyzer-timeout-failure-contract",
|
||||
stdoutRecovery: stdoutDiagnostics,
|
||||
valuesRedacted: true,
|
||||
},
|
||||
findings: [{
|
||||
@@ -2543,12 +2572,15 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption
|
||||
: "observe analyze failed before producing a compact report; inspect analyzer stdout/stderr artifacts under the observer analysis directory",
|
||||
}],
|
||||
next: {
|
||||
collectAnalyzerStdout: options.stateDir ? `bun scripts/cli.ts web-probe observe collect --node ${options.node} --lane ${options.lane} --state-dir ${options.stateDir} --file analysis/analyzer-stdout.json` : null,
|
||||
collectAnalyzerStderr: options.stateDir ? `bun scripts/cli.ts web-probe observe collect --node ${options.node} --lane ${options.lane} --state-dir ${options.stateDir} --file analysis/analyzer-stderr.log` : null,
|
||||
collectReportJson: collectReportCommand,
|
||||
collectAnalyzerStdout: webObserveAnalyzeCollectCommand(options, "analysis/analyzer-stdout.json"),
|
||||
collectAnalyzerStderr: webObserveAnalyzeCollectCommand(options, "analysis/analyzer-stderr.log"),
|
||||
valuesRedacted: true,
|
||||
},
|
||||
valuesRedacted: true,
|
||||
} : null);
|
||||
const compactResult = analysis === null ? compactCommandResultWithStdoutTail(result) : compactCommandResult(result);
|
||||
compactResult.stdoutRecovery = stdoutDiagnostics;
|
||||
const payload = {
|
||||
ok: analysisOk,
|
||||
status: analysisOk ? "analyzed" : "blocked",
|
||||
@@ -2562,13 +2594,28 @@ export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOption
|
||||
alertThresholds,
|
||||
browserFreezePolicy,
|
||||
wrapper: buildWebObserveWrapperForObserveOptions("analyze", options, spec.workspace),
|
||||
result: analysis === null ? compactCommandResultWithStdoutTail(result) : compactCommandResult(result),
|
||||
result: compactResult,
|
||||
full: options.full,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
return options.raw ? compactWebObserveAnalyzePayloadForRaw(payload, options.compactRaw) : withWebObserveAnalyzeRendered(payload);
|
||||
}
|
||||
|
||||
function webObserveAnalyzeCollectCommand(options: NodeWebProbeObserveOptions, file: string): string {
|
||||
const selector = options.stateDir ? `--state-dir ${shellQuote(options.stateDir)}` : webObserveIdFromOptions(options);
|
||||
return `bun scripts/cli.ts web-probe observe collect ${selector} --node ${options.node} --lane ${options.lane} --view files --file ${shellQuote(file)}`;
|
||||
}
|
||||
|
||||
function isWebObserveAnalyzeJsonContract(value: Record<string, unknown>): boolean {
|
||||
return value.ok === true
|
||||
|| value.ok === false
|
||||
|| typeof value.reportJsonPath === "string"
|
||||
|| typeof value.reportMdPath === "string"
|
||||
|| typeof value.error === "string"
|
||||
|| Object.keys(recordValue(value.analyzer)).length > 0
|
||||
|| Object.keys(recordValue(value.counts)).length > 0;
|
||||
}
|
||||
|
||||
function compactWebObserveAnalyzePayloadForRaw(payload: Record<string, unknown>, compactRaw: boolean): Record<string, unknown> {
|
||||
if (!compactRaw) return payload;
|
||||
const analysis = payload.analysis && typeof payload.analysis === "object" && !Array.isArray(payload.analysis)
|
||||
|
||||
Reference in New Issue
Block a user