feat(cli): migrate child json recovery callers

This commit is contained in:
Codex
2026-07-02 19:25:27 +00:00
parent 51512f3d7b
commit 146ecc5e82
9 changed files with 254 additions and 142 deletions
+24 -28
View File
@@ -1,9 +1,9 @@
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-27-p11-monitor-web-observability-dashboard.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-07-01-p15-cadence-otel.
// Responsibility: P5 web-probe sentinel service validation, maintenance, report and dashboard commands.
import { existsSync, readFileSync } from "node:fs";
import type { CommandResult } from "./command";
import { runCommand } from "./command";
import { resolveCliChildJsonCommandResult } from "./cli-child-json-recovery";
import { repoRoot } from "./config";
import { startJob } from "./jobs";
import type { RenderedCliResult } from "./output";
@@ -513,8 +513,13 @@ function readSentinelReportArtifactSummary(state: SentinelCicdState, body: Recor
"NODE",
].join("\n");
const result = runCommand(["trans", `${state.spec.nodeId}:${state.spec.workspace}`, "sh"], repoRoot, { input: script, timeoutMs: Math.max(5, Math.min(timeoutSeconds, 55)) * 1000 });
const parsed = parseJsonObject(result.stdout);
return { ok: result.exitCode === 0 && parsed?.ok === true, ...record(parsed), result: compactCommand(result), valuesRedacted: true };
const parsedResolution = resolveCliChildJsonCommandResult({
result,
requestedStdoutType: "web-probe sentinel report summary JSON",
acceptParsed: (value) => value.ok === true || value.ok === false || typeof value.reason === "string" || typeof value.reportJsonPath === "string" || Object.keys(record(value.counts)).length > 0,
});
const parsed = parsedResolution.parsed;
return { ok: result.exitCode === 0 && parsed?.ok === true, ...record(parsed), result: compactCommand(result), stdoutRecovery: parsedResolution.diagnostics, valuesRedacted: true };
}
function isSafeSentinelReportStateDir(value: string): boolean {
@@ -1468,10 +1473,13 @@ function callSentinelService(state: SentinelCicdState, method: "GET" | "POST", p
let parsed: Record<string, unknown> | null = null;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
result = runCommand(["trans", stringAt(state.controlPlaneNode, "kubeRoute"), "sh", "--", script], repoRoot, { timeoutMs: attemptTimeoutSeconds * 1000 });
parsed = parseJsonObject(result.stdout);
const recovered = parsed === null ? recoverTruncatedSshStdoutJson(result) : null;
if (recovered !== null) parsed = recovered.parsed;
attempts.push({ attempt, ...compactCommand(result), parsedOk: parsed !== null, parsedFromDump: recovered !== null, dumpPath: recovered?.dumpPath ?? null, valuesRedacted: true });
const parsedResolution = resolveCliChildJsonCommandResult({
result,
requestedStdoutType: "web-probe sentinel service proxy response JSON",
acceptParsed: isSentinelServiceResponseContract,
});
parsed = parsedResolution.parsed;
attempts.push({ attempt, ...compactCommand(result), parsedOk: parsed !== null, stdoutRecovery: parsedResolution.diagnostics, valuesRedacted: true });
if (result.exitCode === 0) break;
}
const compactBodyJson = compactSentinelServiceBodyJson(parsed);
@@ -1492,27 +1500,15 @@ function callSentinelService(state: SentinelCicdState, method: "GET" | "POST", p
};
}
function recoverTruncatedSshStdoutJson(result: CommandResult): { parsed: Record<string, unknown>; dumpPath: string } | null {
const dumpPath = sshStdoutDumpPathFromStderr(result.stderr);
if (dumpPath === null || !existsSync(dumpPath)) return null;
const parsed = parseJsonObject(readFileSync(dumpPath, "utf8"));
return parsed === null ? null : { parsed, dumpPath };
}
function sshStdoutDumpPathFromStderr(value: string): string | null {
for (const rawLine of value.split(/\r?\n/u)) {
const line = rawLine.trim();
const prefix = "UNIDESK_SSH_STDOUT_TRUNCATED ";
if (!line.startsWith(prefix)) continue;
try {
const payload = JSON.parse(line.slice(prefix.length)) as unknown;
const dumpPath = stringAtNullable(record(payload), "dumpPath");
if (dumpPath !== null) return dumpPath;
} catch {
return null;
}
}
return null;
function isSentinelServiceResponseContract(value: Record<string, unknown>): boolean {
return value.ok === false
|| typeof value.view === "string"
|| typeof value.renderedText === "string"
|| typeof value.error === "string"
|| Array.isArray(value.availableViews)
|| Object.keys(record(value.run)).length > 0
|| Object.keys(record(value.summary)).length > 0
|| Array.isArray(value.findings);
}
function callSentinelServiceDirect(method: "GET" | "POST", pathWithQuery: string, body: Record<string, unknown> | null, timeoutSeconds: number, url: string): Record<string, unknown> {