fix(sentinel): harden post-deploy report and ssh summaries

This commit is contained in:
Codex
2026-07-01 08:16:53 +00:00
parent 6906037ba4
commit 6b73dcd0c1
6 changed files with 273 additions and 14 deletions
@@ -325,10 +325,14 @@ export function runSentinelQuickVerify(state: SentinelCicdState, reason: string,
cleanupFindings,
);
const blockingFindings = findings.filter(isQuickVerifyBlockingFinding);
const analysisWarnings = analysis.ok ? [] : ["quick verify analyze command returned non-zero but a readable analysis artifact was produced; targetValidation is using artifact severity plus control blockers."];
const ok = record(artifactSummary).ok === true && controlFindings.length === 0 && blockingFindings.length === 0;
const analysisReadable = stringAtNullable(artifactSummary, "reportJsonSha256") !== null;
const analysisWarnings = mergeWarnings(
analysis.ok ? [] : ["quick verify analyze command returned non-zero but a readable analysis artifact was produced; targetValidation is using artifact severity plus control blockers."],
analysisReadable && artifactSummaryRecord.ok !== true ? ["quick verify analysis report contains non-blocking findings; targetValidation keeps them in report but does not fail the CronJob without red/blocking findings."] : [],
);
const ok = analysisReadable && controlFindings.length === 0 && blockingFindings.length === 0;
const businessStatus = quickVerifyBusinessStatus(null, promptIndex, turnSummary, traceFrame, elapsedMs(), maxSeconds);
printQuickVerifyProgress(state, runId, "record-report", ok ? "succeeded" : "blocked", { observerId, reportJsonSha256: stringAtNullable(artifactSummary, "reportJsonSha256"), findingCount: findings.length, blockingFindingCount: blockingFindings.length, controlFindingCount: controlFindings.length, elapsedMs: elapsedMs() });
printQuickVerifyProgress(state, runId, "record-report", ok ? "succeeded" : "blocked", { observerId, reportJsonSha256: stringAtNullable(artifactSummary, "reportJsonSha256"), analysisReadable, findingCount: findings.length, blockingFindingCount: blockingFindings.length, controlFindingCount: controlFindings.length, elapsedMs: elapsedMs() });
return recordQuickVerify(state, {
ok,
runId,
@@ -342,7 +346,7 @@ export function runSentinelQuickVerify(state: SentinelCicdState, reason: string,
reportJsonSha256: stringAtNullable(artifactSummary, "reportJsonSha256"),
findingCount: findings.length,
artifactCount: numberAtNullable(artifactSummary, "artifactCount") ?? 0,
failure: controlFindings.length > 0 ? "quick-verify-no-business-turn" : blockingFindings.length > 0 ? "quick-verify-blocking-findings" : null,
failure: !analysisReadable ? "quick-verify-analysis-missing" : controlFindings.length > 0 ? "quick-verify-no-business-turn" : blockingFindings.length > 0 ? "quick-verify-blocking-findings" : null,
promptSource: prompts.summary,
accountEnv: accountEnv.summary,
steps: [...steps, cleanupStep],
@@ -611,7 +615,9 @@ function callSentinelService(state: SentinelCicdState, method: "GET" | "POST", p
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);
attempts.push({ attempt, ...compactCommand(result), parsedOk: parsed !== null, valuesRedacted: true });
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 });
if (result.exitCode === 0) break;
}
return {
@@ -631,6 +637,29 @@ 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 compactSentinelServiceBodyJson(value: Record<string, unknown> | null): unknown {
if (value === null || typeof value.renderedText !== "string") return value;
return {