fix: surface sentinel analyzer findings

This commit is contained in:
Codex
2026-07-01 09:05:44 +00:00
parent 6f70f162fa
commit c608746910
3 changed files with 232 additions and 20 deletions
+65 -2
View File
@@ -242,6 +242,11 @@ export function runSentinelReport(state: SentinelCicdState, options: Extract<Web
const payload = compactSentinelReportRawPayload(state, body, report, artifactSummary);
return rendered(report.ok && body.ok !== false, command, renderSentinelReportSummary(payload, state));
}
if (options.view === "findings") {
const artifactSummary = readSentinelReportArtifactSummary(state, body, Math.min(options.timeoutSeconds, 55));
const payload = compactSentinelReportRawPayload(state, body, report, artifactSummary);
return rendered(report.ok && body.ok !== false, command, renderSentinelReportFindings(payload));
}
const renderedText = typeof body.renderedText === "string" ? body.renderedText : renderReportResult({ command, node: state.spec.nodeId, lane: state.spec.lane, report, valuesRedacted: true });
return rendered(report.ok && body.ok !== false, command, renderedText);
}
@@ -256,6 +261,10 @@ function compactSentinelReportRawPayload(
const artifact = record(artifactSummary);
const findings = Array.isArray(body.findings) ? body.findings.map(record) : [];
const artifactFindings = Array.isArray(artifact.findings) ? artifact.findings.map(record) : [];
const visibleFindings = mergeSentinelReportFindings(findings, artifactFindings);
const storedFindingCount = numberAtNullable(run, "finding_count") ?? numberAtNullable(run, "findingCount") ?? findings.length;
const artifactFindingCount = numberAtNullable(artifact, "findingCount") ?? artifactFindings.length;
const visibleFindingCount = Math.max(storedFindingCount, artifactFindingCount, visibleFindings.length);
const rootCauseSignalFindings = artifactFindings
.filter((item) => Object.keys(record(item.rootCauseSignals)).length > 0)
.slice(0, 8)
@@ -279,7 +288,9 @@ function compactSentinelReportRawPayload(
observerId: run.observer_id ?? run.observerId ?? null,
stateDir: run.state_dir ?? run.stateDir ?? null,
reportJsonSha256: run.report_json_sha256 ?? run.reportJsonSha256 ?? artifact.reportJsonSha256 ?? null,
findingCount: run.finding_count ?? run.findingCount ?? findings.length,
findingCount: visibleFindingCount,
storedFindingCount,
artifactFindingCount,
artifactCount: run.artifact_count ?? run.artifactCount ?? artifact.artifactCount ?? null,
durationMinutes: run.durationMinutes ?? run.runDurationMinutes ?? record(run.timing).durationMinutes ?? null,
runDurationMinutes: run.runDurationMinutes ?? run.durationMinutes ?? record(run.timing).durationMinutes ?? null,
@@ -292,13 +303,15 @@ function compactSentinelReportRawPayload(
valuesRedacted: true,
},
summary: pickFields(record(body.summary), ["reason", "status", "businessStatus", "failure", "valuesRedacted"]),
findings: findings.slice(0, 12).map(compactSentinelReportFinding),
findings: visibleFindings.slice(0, 12).map(compactSentinelReportFinding),
artifactSummary: Object.keys(artifact).length === 0 ? null : {
ok: artifact.ok === true,
reportOk: artifact.reportOk === true ? true : artifact.reportOk === false ? false : null,
reportJsonPath: artifact.reportJsonPath ?? null,
reportJsonSha256: artifact.reportJsonSha256 ?? null,
reportMdSha256: artifact.reportMdSha256 ?? null,
findingCount: artifactFindingCount,
findings: artifactFindings.slice(0, 12).map(compactSentinelReportFinding),
screenshot: record(artifact.screenshot),
counts: record(artifact.counts),
analysisWindow: compactSentinelAnalysisWindow(artifact.analysisWindow),
@@ -315,6 +328,27 @@ function compactSentinelReportRawPayload(
};
}
function mergeSentinelReportFindings(primary: readonly Record<string, unknown>[], artifact: readonly Record<string, unknown>[]): Record<string, unknown>[] {
const merged: Record<string, unknown>[] = [];
const seen = new Set<string>();
for (const item of [...primary, ...artifact]) {
const id = sentinelReportFindingIdentity(item);
const key = `${id ?? JSON.stringify(item).slice(0, 160)}:${stringAtNullable(item, "severity") ?? stringAtNullable(item, "level") ?? ""}`;
if (seen.has(key)) continue;
seen.add(key);
merged.push(item);
}
return merged;
}
function sentinelReportFindingIdentity(item: Record<string, unknown>): string | null {
return stringAtNullable(item, "finding_id")
?? stringAtNullable(item, "findingId")
?? stringAtNullable(item, "id")
?? stringAtNullable(item, "kind")
?? stringAtNullable(item, "code");
}
function compactSentinelReportFinding(value: Record<string, unknown>): Record<string, unknown> {
const result: Record<string, unknown> = {
id: stringAtNullable(value, "finding_id") ?? stringAtNullable(value, "findingId") ?? stringAtNullable(value, "id") ?? stringAtNullable(value, "kind") ?? stringAtNullable(value, "code"),
@@ -415,6 +449,35 @@ function renderSentinelReportSummary(payload: Record<string, unknown>, state: Se
].join("\n");
}
function renderSentinelReportFindings(payload: Record<string, unknown>): string {
const run = record(payload.run);
const artifactSummary = record(payload.artifactSummary);
const findings = Array.isArray(payload.findings) ? payload.findings.map(record) : [];
const reportSha = stringAtNullable(run, "reportJsonSha256") ?? stringAtNullable(artifactSummary, "reportJsonSha256");
return [
"Web Probe Sentinel Findings",
"=======================================================",
`run=${run.id ?? "-"} report=${reportSha ?? "-"} findings=${run.findingCount ?? findings.length}`,
findings.length === 0 ? "-" : findings.map(formatSentinelReportFindingLine).join("\n"),
].join("\n");
}
function formatSentinelReportFindingLine(item: Record<string, unknown>): string {
const check = record(item.check);
const code = stringAtNullable(check, "code") ?? stringAtNullable(item, "id") ?? "-";
const title = stringAtNullable(check, "titleZh") ?? "";
const summary = reportText(item.summary, 180) ?? "";
const rootCause = reportText(item.rootCause, 180);
const evidence = reportText(item.evidenceSummary, 180);
const nextAction = reportText(item.nextAction, 180);
return [
`${item.severity ?? "-"} ${code} ${title} count=${item.count ?? "-"} ${summary}`.trim(),
rootCause === null ? null : `rootCause=${rootCause}`,
evidence === null ? null : `evidence=${evidence}`,
nextAction === null ? null : `next=${nextAction}`,
].filter((part): part is string => part !== null && part.length > 0).join(" | ");
}
function compactRootCauseSignals(value: unknown): Record<string, unknown> | null {
const item = record(value);
const keys = [