fix: surface sentinel analyzer findings
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user