feat(monitor): surface workbench sentinel root causes
This commit is contained in:
@@ -650,6 +650,7 @@ function dashboardFindings(config: WebProbeSentinelServiceConfig, db: Database,
|
||||
`).all(...where.params, limit) as Record<string, unknown>[];
|
||||
const items = rows.map((row) => {
|
||||
const latestRun = latestRunForFinding(db, row);
|
||||
const latestDetail = latestRun === null ? null : storedFindingDetailForRow(db, row, stringOrNull(latestRun.id));
|
||||
return {
|
||||
code: stringOrNull(row.finding_id),
|
||||
findingId: stringOrNull(row.finding_id),
|
||||
@@ -661,6 +662,11 @@ function dashboardFindings(config: WebProbeSentinelServiceConfig, db: Database,
|
||||
latestRunId: latestRun === null ? null : stringOrNull(latestRun.id),
|
||||
latestReportJsonSha256: latestRun === null ? null : stringOrNull(latestRun.report_json_sha256),
|
||||
summary: stringOrNull(row.summary),
|
||||
rootCause: stringOrNull(latestDetail?.rootCause),
|
||||
rootCauseStatus: stringOrNull(latestDetail?.rootCauseStatus),
|
||||
rootCauseConfidence: stringOrNull(latestDetail?.rootCauseConfidence),
|
||||
nextAction: stringOrNull(latestDetail?.nextAction),
|
||||
evidenceSummary: stringOrNull(latestDetail?.evidenceSummary),
|
||||
traceability: latestRun === null ? null : runTraceability(config, latestRun),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
@@ -910,8 +916,88 @@ function readRunRow(db: Database, runId: string): Record<string, unknown> | null
|
||||
}
|
||||
|
||||
function findingsForRun(db: Database, runId: string, limit: number): readonly Record<string, unknown>[] {
|
||||
return db.query("SELECT finding_id, severity, count, summary, report_json_sha256, created_at FROM findings WHERE run_id = ? ORDER BY created_at DESC LIMIT ?")
|
||||
const rows = db.query("SELECT finding_id, severity, count, summary, report_json_sha256, created_at FROM findings WHERE run_id = ? ORDER BY created_at DESC LIMIT ?")
|
||||
.all(runId, limit) as Record<string, unknown>[];
|
||||
return rows.map((row) => enrichFindingRowWithStoredDetail(db, runId, row));
|
||||
}
|
||||
|
||||
function enrichFindingRowWithStoredDetail(db: Database, runId: string, row: Record<string, unknown>): Record<string, unknown> {
|
||||
const detail = storedFindingDetailForRow(db, row, runId);
|
||||
return {
|
||||
...row,
|
||||
code: stringOrNull(row.finding_id),
|
||||
findingId: stringOrNull(row.finding_id),
|
||||
rootCause: stringOrNull(detail?.rootCause),
|
||||
rootCauseStatus: stringOrNull(detail?.rootCauseStatus),
|
||||
rootCauseConfidence: stringOrNull(detail?.rootCauseConfidence),
|
||||
nextAction: stringOrNull(detail?.nextAction),
|
||||
evidenceSummary: stringOrNull(detail?.evidenceSummary),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function storedFindingDetailForRow(db: Database, row: Record<string, unknown>, runId: string | null): Record<string, unknown> | null {
|
||||
if (runId === null) return null;
|
||||
const stored = readMetadata(db, `run.report.${runId}`) ?? {};
|
||||
const details = arrayRecords(stored.findings);
|
||||
if (details.length === 0) return null;
|
||||
const findingId = stringOrNull(row.finding_id) ?? stringOrNull(row.findingId) ?? stringOrNull(row.code);
|
||||
const severity = stringOrNull(row.severity);
|
||||
return details.find((item) => {
|
||||
const itemId = stringOrNull(item.finding_id) ?? stringOrNull(item.findingId) ?? stringOrNull(item.id) ?? stringOrNull(item.code);
|
||||
if (itemId !== findingId) return false;
|
||||
const itemSeverity = stringOrNull(item.severity) ?? stringOrNull(item.level);
|
||||
return severity === null || itemSeverity === null || itemSeverity === severity;
|
||||
}) ?? null;
|
||||
}
|
||||
|
||||
function compactStoredFinding(value: unknown): Record<string, unknown> {
|
||||
const item = record(value);
|
||||
const findingId = stringOrNull(item.id) ?? stringOrNull(item.kind) ?? stringOrNull(item.code) ?? "finding";
|
||||
const severity = stringOrNull(item.severity) ?? stringOrNull(item.level) ?? "unknown";
|
||||
const summary = stringOrNull(item.summary) ?? stringOrNull(item.message) ?? findingId;
|
||||
return {
|
||||
id: findingId,
|
||||
finding_id: findingId,
|
||||
findingId,
|
||||
code: findingId,
|
||||
severity,
|
||||
count: numberOr(item.count, numberOr(item.sampleCount, 1)),
|
||||
summary: summary.slice(0, 500),
|
||||
rootCause: stringOrNull(item.rootCause),
|
||||
rootCauseStatus: stringOrNull(item.rootCauseStatus),
|
||||
rootCauseConfidence: stringOrNull(item.rootCauseConfidence),
|
||||
nextAction: stringOrNull(item.nextAction),
|
||||
evidenceSummary: stringOrNull(item.evidenceSummary) ?? compactFindingEvidenceSummary(item.evidence),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function compactFindingEvidenceSummary(value: unknown): string | null {
|
||||
if (value === null || value === undefined) return null;
|
||||
if (typeof value === "string") return value.slice(0, 240);
|
||||
const item = record(value);
|
||||
const keys = [
|
||||
"http404Count",
|
||||
"responseErrorCount",
|
||||
"requestFailedCount",
|
||||
"statuses",
|
||||
"afterProjectedSeqs",
|
||||
"sinceSeqs",
|
||||
"traceIds",
|
||||
"maxFallbackRatio",
|
||||
"maxFallbackTitleCount",
|
||||
"overThresholdSampleCount",
|
||||
"majorityFallbackSampleCount",
|
||||
];
|
||||
const compact: Record<string, unknown> = {};
|
||||
for (const key of keys) {
|
||||
const raw = item[key];
|
||||
if (raw === null || raw === undefined) continue;
|
||||
compact[key] = Array.isArray(raw) ? raw.slice(0, 6) : raw;
|
||||
}
|
||||
const text = Object.keys(compact).length > 0 ? JSON.stringify(compact) : JSON.stringify(item).slice(0, 240);
|
||||
return text.length > 240 ? `${text.slice(0, 239)}…` : text;
|
||||
}
|
||||
|
||||
function globalSeverityCounts(db: Database): Record<string, number> {
|
||||
@@ -1111,6 +1197,7 @@ function recordRunResult(config: WebProbeSentinelServiceConfig, db: Database, in
|
||||
reportJsonSha256,
|
||||
summary: record(input.summary),
|
||||
views: record(input.views),
|
||||
findings: findings.map(compactStoredFinding),
|
||||
publicOrigin: stringOrNull(input.publicOrigin),
|
||||
screenshot: record(input.screenshot),
|
||||
artifactCount,
|
||||
@@ -1131,8 +1218,7 @@ 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 = db.query("SELECT finding_id, severity, count, summary, report_json_sha256, created_at FROM findings WHERE run_id = ? ORDER BY created_at DESC LIMIT 50")
|
||||
.all(selectedRunId) as Record<string, unknown>[];
|
||||
const findings = findingsForRun(db, selectedRunId, 50);
|
||||
const views = record(stored.views);
|
||||
const storedView = record(views[view]);
|
||||
const renderedText = typeof storedView.renderedText === "string" ? storedView.renderedText : view === "summary" ? renderStoredSummary(row, stored, findings) : view === "findings" ? renderStoredFindings(row, findings) : null;
|
||||
@@ -1150,6 +1236,19 @@ function reportRunView(config: WebProbeSentinelServiceConfig, db: Database, view
|
||||
};
|
||||
}
|
||||
|
||||
function formatStoredFindingLine(item: Record<string, unknown>): string {
|
||||
const rootCause = stringOrNull(item.rootCause);
|
||||
const status = stringOrNull(item.rootCauseStatus);
|
||||
const nextAction = stringOrNull(item.nextAction);
|
||||
const evidence = stringOrNull(item.evidenceSummary);
|
||||
return [
|
||||
`${item.severity ?? "-"} ${item.finding_id ?? item.findingId ?? item.code ?? "-"} count=${item.count ?? "-"} ${item.summary ?? ""}`,
|
||||
rootCause === null ? null : `rootCause=${rootCause}${status === null ? "" : ` status=${status}`}`,
|
||||
evidence === null ? null : `evidence=${evidence}`,
|
||||
nextAction === null ? null : `next=${nextAction}`,
|
||||
].filter((part) => part !== null).join(" | ");
|
||||
}
|
||||
|
||||
function renderStoredSummary(row: Record<string, unknown>, stored: Record<string, unknown>, findings: readonly Record<string, unknown>[]): string {
|
||||
const summary = record(stored.summary);
|
||||
return [
|
||||
@@ -1162,7 +1261,7 @@ function renderStoredSummary(row: Record<string, unknown>, stored: Record<string
|
||||
`analysisWindow=${JSON.stringify(record(summary.analysisWindow))}`,
|
||||
"",
|
||||
"Findings",
|
||||
findings.length === 0 ? "-" : findings.slice(0, 12).map((item) => `${item.severity ?? "-"} ${item.finding_id ?? "-"} count=${item.count ?? "-"} ${item.summary ?? ""}`).join("\n"),
|
||||
findings.length === 0 ? "-" : findings.slice(0, 12).map(formatStoredFindingLine).join("\n"),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
@@ -1171,7 +1270,7 @@ function renderStoredFindings(row: Record<string, unknown>, findings: readonly R
|
||||
"Web Probe Sentinel Findings",
|
||||
"=======================================================",
|
||||
`run=${stringOrNull(row.id) ?? "-"} report=${stringOrNull(row.report_json_sha256) ?? "-"}`,
|
||||
findings.length === 0 ? "-" : findings.map((item) => `${item.severity ?? "-"} ${item.finding_id ?? "-"} count=${item.count ?? "-"} ${item.summary ?? ""}`).join("\n"),
|
||||
findings.length === 0 ? "-" : findings.map(formatStoredFindingLine).join("\n"),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user