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
+127 -12
View File
@@ -916,8 +916,10 @@ function dashboardRunDetail(config: WebProbeSentinelServiceConfig, db: Database,
const row = readRunRow(config, db, runId);
if (row === null) return { ok: false, error: "run-not-found", runId, valuesRedacted: true };
const stored = readMetadata(db, `run.report.${runId}`) ?? {};
const findings = findingsForRun(config, db, runId, dashboardMaxPageSize(config));
const artifactSummary = readAnalysisArtifactSummaryForRun(config, row, dashboardMaxPageSize(config));
const findings = visibleFindingsForRun(config, db, row, dashboardMaxPageSize(config), artifactSummary);
const views = record(stored.views);
const reportJsonSha256 = stringOrNull(row.report_json_sha256) ?? stringOrNull(artifactSummary.reportJsonSha256);
return {
ok: true,
contractVersion: DASHBOARD_CONTRACT_VERSION,
@@ -929,7 +931,8 @@ function dashboardRunDetail(config: WebProbeSentinelServiceConfig, db: Database,
viewsAvailable: Object.keys(views),
artifacts: {
artifactCount: numberOr(row.artifact_count, 0),
reportJsonSha256: stringOrNull(row.report_json_sha256),
reportJsonSha256,
analysisReport: artifactSummary,
screenshot: compactArtifactRef(stored.screenshot),
publicOrigin: stringOrNull(stored.publicOrigin),
},
@@ -1264,14 +1267,17 @@ function shortDashboardText(value: string, max: number): string {
function dashboardRunSummary(config: WebProbeSentinelServiceConfig, db: Database, row: Record<string, unknown>): Record<string, unknown> {
const id = stringOrNull(row.id);
const severityCounts = id === null ? {} : severityCountsForRun(config, db, id);
const artifactSummary = readAnalysisArtifactSummaryForRun(config, row, 50);
const findings = visibleFindingsForRun(config, db, row, 50, artifactSummary);
const severityCounts = checkLevelCounts(config, findings);
const maxSeverity = maxSeverityFromCounts(severityCounts);
const findingTypeCount = numberOr(row.finding_count, 0);
const findingTypeCount = visibleFindingTypeCount(row, findings, artifactSummary);
const findingSampleCount = Object.values(severityCounts).reduce((sum, value) => sum + numberOr(value, 0), 0);
const findingAlertSampleCount = alertSeveritySampleCount(severityCounts);
const stored = id === null ? {} : record(readMetadata(db, `run.report.${id}`));
const scenarioId = stringOrNull(row.scenario_id);
const timing = dashboardRunTiming(config, row, stored);
const reportJsonSha256 = stringOrNull(row.report_json_sha256) ?? stringOrNull(artifactSummary.reportJsonSha256);
return {
id,
runId: id,
@@ -1285,8 +1291,8 @@ function dashboardRunSummary(config: WebProbeSentinelServiceConfig, db: Database
observerId: stringOrNull(row.observer_id),
state_dir: stringOrNull(row.state_dir),
stateDir: stringOrNull(row.state_dir),
report_json_sha256: stringOrNull(row.report_json_sha256),
reportJsonSha256: stringOrNull(row.report_json_sha256),
report_json_sha256: reportJsonSha256,
reportJsonSha256,
finding_count: findingTypeCount,
findingCount: findingTypeCount,
findingTypeCount,
@@ -1511,6 +1517,98 @@ function findingsForRun(config: WebProbeSentinelServiceConfig, db: Database, run
return rows.map((row) => enrichFindingRowWithStoredDetail(config, db, runId, row));
}
function visibleFindingsForRun(
config: WebProbeSentinelServiceConfig,
db: Database,
row: Record<string, unknown>,
limit: number,
artifactSummary: Record<string, unknown> = readAnalysisArtifactSummaryForRun(config, row, limit),
): readonly Record<string, unknown>[] {
const runId = stringOrNull(row.id);
const storedFindings = runId === null ? [] : findingsForRun(config, db, runId, limit);
const artifactFindings = arrayRecords(artifactSummary.findings)
.slice(0, limit)
.map((item) => enrichFindingWithCheck(config, compactStoredFinding(item)));
return mergeVisibleFindings(storedFindings, artifactFindings, limit);
}
function mergeVisibleFindings(primary: readonly Record<string, unknown>[], artifact: readonly Record<string, unknown>[], limit: number): readonly Record<string, unknown>[] {
const merged: Record<string, unknown>[] = [];
const seen = new Set<string>();
for (const item of [...primary, ...artifact]) {
const key = `${findingIdentity(item) ?? JSON.stringify(item).slice(0, 160)}:${stringOrNull(item.severity) ?? stringOrNull(item.level) ?? ""}`;
if (seen.has(key)) continue;
seen.add(key);
merged.push(item);
if (merged.length >= limit) break;
}
return merged;
}
function findingIdentity(item: Record<string, unknown>): string | null {
return stringOrNull(item.finding_id)
?? stringOrNull(item.findingId)
?? stringOrNull(item.id)
?? stringOrNull(item.kind)
?? stringOrNull(item.code);
}
function visibleFindingTypeCount(row: Record<string, unknown>, findings: readonly Record<string, unknown>[], artifactSummary: Record<string, unknown>): number {
return Math.max(
numberOr(row.finding_count, 0),
numberOr(artifactSummary.findingCount, 0),
findings.length,
);
}
function readAnalysisArtifactSummaryForRun(config: WebProbeSentinelServiceConfig, row: Record<string, unknown>, limit: number): Record<string, unknown> {
const stateDir = stringOrNull(row.state_dir) ?? stringOrNull(row.stateDir);
if (stateDir === null) return { ok: false, reason: "state-dir-missing", findings: [], valuesRedacted: true };
if (!isSafeDashboardStateDir(stateDir)) return { ok: false, reason: "unsafe-state-dir", stateDir, findings: [], valuesRedacted: true };
const reportJsonPath = join(config.stateRoot, stateDir, "analysis", "report.json");
const reportMdPath = join(config.stateRoot, stateDir, "analysis", "report.md");
const jsonBuffer = readFileBuffer(reportJsonPath);
const mdBuffer = readFileBuffer(reportMdPath);
let parsed: unknown = null;
if (jsonBuffer !== null) {
try {
parsed = JSON.parse(jsonBuffer.toString("utf8")) as unknown;
} catch {
parsed = null;
}
}
const report = record(parsed);
const reportFindings = arrayRecords(report.findings);
const archiveFindings = arrayRecords(record(report.archiveSummary).redFindings);
const findings = (reportFindings.length > 0 ? reportFindings : archiveFindings)
.slice(0, limit)
.map(compactStoredFinding);
return {
ok: parsed !== null,
reason: parsed === null ? "analysis-report-json-missing-or-invalid" : null,
stateDir,
reportJsonPath,
reportJsonSha256: sha256Buffer(jsonBuffer),
reportMdSha256: sha256Buffer(mdBuffer),
findingCount: numberOr(report.findingCount, findings.length),
findings,
counts: record(report.counts),
valuesRedacted: true,
};
}
function readFileBuffer(path: string): Buffer | null {
try {
return readFileSync(path);
} catch {
return null;
}
}
function sha256Buffer(value: Buffer | null): string | null {
return value === null ? null : `sha256:${createHash("sha256").update(value).digest("hex")}`;
}
function enrichFindingRowWithStoredDetail(config: WebProbeSentinelServiceConfig, db: Database, runId: string, row: Record<string, unknown>): Record<string, unknown> {
const detail = storedFindingDetailForRow(db, row, runId);
return enrichFindingWithCheck(config, {
@@ -1984,10 +2082,17 @@ function reportRunView(config: WebProbeSentinelServiceConfig, db: Database, view
const selectedRunId = stringOrNull(row.id);
if (selectedRunId === null) return { ok: false, error: "report-run-id-missing", view, valuesRedacted: true };
const stored = readMetadata(db, `run.report.${selectedRunId}`) ?? {};
const findings = findingsForRun(config, db, selectedRunId, 50);
const artifactSummary = readAnalysisArtifactSummaryForRun(config, row, 50);
const findings = visibleFindingsForRun(config, db, row, 50, artifactSummary);
const views = record(stored.views);
const storedView = record(views[view]);
const renderedText = view === "findings" ? renderStoredFindings(row, findings) : typeof storedView.renderedText === "string" ? storedView.renderedText : view === "summary" ? renderStoredSummary(config, row, stored, findings) : null;
const renderedText = view === "findings"
? renderStoredFindings(row, findings, artifactSummary)
: view === "summary"
? renderStoredSummary(config, row, stored, findings, artifactSummary)
: typeof storedView.renderedText === "string"
? storedView.renderedText
: null;
if (renderedText === null) {
return { ok: false, error: "report-view-not-indexed", runId: selectedRunId, view, availableViews: Object.keys(views), valuesRedacted: true };
}
@@ -1997,6 +2102,7 @@ function reportRunView(config: WebProbeSentinelServiceConfig, db: Database, view
run: dashboardRunSummary(config, db, row),
summary: record(stored.summary),
findings,
artifactSummary,
renderedText,
valuesRedacted: true,
};
@@ -2033,15 +2139,23 @@ function formatStoredFindingTiming(item: Record<string, unknown>): string | null
return `timing=${pieces.join(" ")}`;
}
function renderStoredSummary(config: WebProbeSentinelServiceConfig, row: Record<string, unknown>, stored: Record<string, unknown>, findings: readonly Record<string, unknown>[]): string {
function renderStoredSummary(
config: WebProbeSentinelServiceConfig,
row: Record<string, unknown>,
stored: Record<string, unknown>,
findings: readonly Record<string, unknown>[],
artifactSummary: Record<string, unknown>,
): string {
const summary = record(stored.summary);
const timing = dashboardRunTiming(config, row, stored);
const reportSha = stringOrNull(row.report_json_sha256) ?? stringOrNull(artifactSummary.reportJsonSha256);
const findingCount = visibleFindingTypeCount(row, findings, artifactSummary);
return [
"Web Probe Sentinel Report",
"=======================================================",
`run=${stringOrNull(row.id) ?? "-"} scenario=${stringOrNull(row.scenario_id) ?? "-"} status=${stringOrNull(row.status) ?? "-"}`,
`observer=${stringOrNull(row.observer_id) ?? "-"} stateDir=${stringOrNull(row.state_dir) ?? "-"}`,
`report=${stringOrNull(row.report_json_sha256) ?? "-"} artifacts=${String(row.artifact_count ?? 0)} findings=${String(row.finding_count ?? findings.length)}`,
`report=${reportSha ?? "-"} artifacts=${String(row.artifact_count ?? 0)} findings=${String(findingCount)}`,
`timing=${formatRunTimingSummary(timing)}`,
`publicOrigin=${stringOrNull(stored.publicOrigin) ?? "-"}`,
`analysisWindow=${formatStoredAnalysisWindow(summary.analysisWindow)}`,
@@ -2051,11 +2165,12 @@ function renderStoredSummary(config: WebProbeSentinelServiceConfig, row: Record<
].join("\n");
}
function renderStoredFindings(row: Record<string, unknown>, findings: readonly Record<string, unknown>[]): string {
function renderStoredFindings(row: Record<string, unknown>, findings: readonly Record<string, unknown>[], artifactSummary: Record<string, unknown>): string {
const reportSha = stringOrNull(row.report_json_sha256) ?? stringOrNull(artifactSummary.reportJsonSha256);
return [
"Web Probe Sentinel Findings",
"=======================================================",
`run=${stringOrNull(row.id) ?? "-"} report=${stringOrNull(row.report_json_sha256) ?? "-"}`,
`run=${stringOrNull(row.id) ?? "-"} report=${reportSha ?? "-"} findings=${String(visibleFindingTypeCount(row, findings, artifactSummary))}`,
findings.length === 0 ? "-" : findings.map(formatStoredFindingLine).join("\n"),
].join("\n");
}