feat: add sentinel error detail deep links

This commit is contained in:
Codex
2026-07-07 00:36:26 +00:00
parent 46423e046b
commit 45c834004a
12 changed files with 595 additions and 17 deletions
+11 -1
View File
@@ -17,6 +17,7 @@ import { webProbeSentinelConfigPlan, type WebProbeSentinelConfigPlan } from "./h
import type { HwlabRuntimeLaneSpec } from "./hwlab-node-lanes";
import { effectiveWebProbeSentinelPublicExposure, resolveWebProbeSentinel, readConfigRefTarget as readSentinelConfigRefTarget } from "./hwlab-node-web-sentinel-resolver";
import { emitWebProbeSentinelSpan, webProbeSentinelOtelSummary } from "./hwlab-node-web-sentinel-otel";
import { dashboardErrorDetail, decorateRunFindingsWithErrorDetails } from "./hwlab-node-web-sentinel-error-detail";
const DASHBOARD_CONTRACT_VERSION = "draft-2026-06-27-p11-monitor-web-observability-dashboard";
const DASHBOARD_MAX_TEXT_BYTES = 16_000;
@@ -78,6 +79,7 @@ export interface WebProbeSentinelService {
overview(): Record<string, unknown>;
dashboardRuns(url: URL): Record<string, unknown>;
runDetail(runId: string): Record<string, unknown>;
errorDetail(errorId: string): Promise<Record<string, unknown>>;
findings(url: URL): Record<string, unknown>;
runViews(runId: string, view: string | null, url: URL): Record<string, unknown>;
maintenance(): MaintenanceState;
@@ -207,6 +209,9 @@ export function createWebProbeSentinelService(options: WebProbeSentinelServiceOp
runDetail(runId: string) {
return dashboardRunDetail(config, db, runId);
},
errorDetail(errorId: string) {
return dashboardErrorDetail(config, db, errorId, { findingsForRow: (row, limit) => visibleFindingsForRun(config, db, row, limit, readAnalysisArtifactSummaryForRun(config, row, limit)), runSummary: (row) => dashboardRunSummary(config, db, row), traceability: (row) => runTraceability(config, row) });
},
findings(url: URL) {
return dashboardFindings(config, db, url);
},
@@ -321,6 +326,11 @@ async function sentinelFetch(service: WebProbeSentinelService, request: Request)
if (request.method === "GET" && pathname === "/api/overview") return jsonResponse(service.overview());
if (request.method === "GET" && pathname === "/api/runs") return jsonResponse(service.dashboardRuns(url));
if (request.method === "GET" && pathname === "/api/findings") return jsonResponse(service.findings(url));
const errorDetailMatch = /^\/api\/errors\/([^/]+)$/u.exec(pathname);
if (request.method === "GET" && errorDetailMatch !== null) {
const result = await service.errorDetail(decodeURIComponent(errorDetailMatch[1]));
return jsonResponse(result, result.ok === false ? 404 : 200);
}
const runViewsMatch = /^\/api\/runs\/([^/]+)\/views$/u.exec(pathname);
if (request.method === "GET" && runViewsMatch !== null) {
const view = url.searchParams.get("view");
@@ -941,7 +951,7 @@ function dashboardRunDetail(config: WebProbeSentinelServiceConfig, db: Database,
if (row === null) return { ok: false, error: "run-not-found", runId, valuesRedacted: true };
const stored = readMetadata(db, `run.report.${runId}`) ?? {};
const artifactSummary = readAnalysisArtifactSummaryForRun(config, row, dashboardMaxPageSize(config));
const findings = visibleFindingsForRun(config, db, row, dashboardMaxPageSize(config), artifactSummary);
const findings = decorateRunFindingsWithErrorDetails(config, row, visibleFindingsForRun(config, db, row, dashboardMaxPageSize(config), artifactSummary));
const views = record(stored.views);
const reportJsonSha256 = stringOrNull(row.report_json_sha256) ?? stringOrNull(artifactSummary.reportJsonSha256);
return {