From 282d70f5ec16108ada32b2c93596b4134c490687 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 28 Jun 2026 11:38:30 +0000 Subject: [PATCH] fix: surface sentinel timing detail in reports --- .../monitor-web.js | 15 +++++++++++ scripts/src/hwlab-node-web-sentinel-cicd.ts | 16 +++++++++--- .../src/hwlab-node-web-sentinel-service.ts | 25 ++++++++++++++++++- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.js b/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.js index cc7c340e..d1b3682a 100644 --- a/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.js +++ b/scripts/assets/web-probe-sentinel-monitor-web/monitor-web.js @@ -1110,6 +1110,8 @@ function findingSearchText(item) { item?.rootCause, item?.evidenceSummary, item?.nextAction, + item?.timingStatus, + item?.timingSourceOfTruth, item?.scenarioId, item?.latestRunId, ].filter((value) => value !== null && value !== undefined).join(" ").toLowerCase(); @@ -1146,6 +1148,7 @@ function checkDetailRows(item) { { key: "time", label: "时间", value: checkTimeText(item) }, { key: "scenario", label: "场景", value: safeDetailValue(item?.scenarioId || item?.scenario_id) }, { key: "observer", label: "观察任务", value: safeDetailValue(item?.observerId || item?.observer_id) }, + { key: "timing", label: "计时来源", value: checkTimingText(item) }, { key: "report", label: "报告", value: shortHash(item?.reportJsonSha256 || item?.report_json_sha256 || item?.reportSha256 || "") || "-" }, ].filter((row) => row.value !== ""); } @@ -1159,11 +1162,23 @@ function checkEvidenceRows(item) { { key: "page", label: "页面", value: safeDetailValue(item?.pageRole || evidence.pageRole) }, { key: "command", label: "命令编号", value: safeDetailValue(item?.commandId || evidence.commandId) }, { key: "range", label: "采集范围", value: safeDetailValue(item?.sentinelRange || evidence.sentinelRange) }, + { key: "timing", label: "计时状态", value: checkTimingText(item) }, { key: "blocking", label: "阻塞状态", value: item?.blocking === true ? "阻塞" : "非阻塞" }, ].filter((row) => row.value !== "" && row.value !== "-"); return rows.length > 0 ? rows : [{ key: "none", label: "证据摘要", value: "已记录到报告详情。" }]; } +function checkTimingText(item) { + const status = item?.timingStatus || ""; + const source = item?.timingSourceOfTruth || item?.expectedElapsedSource || item?.evidenceKind || ""; + if (!status && !source) return ""; + return [ + status ? `status=${safeDetailValue(status)}` : "", + source ? `source=${safeDetailValue(source)}` : "", + item?.timingAlert === true ? "alert=true" : "", + ].filter(Boolean).join(" "); +} + function safeDetailValue(value) { if (value === null || value === undefined || value === "") return "-"; const text = String(value).replace(/\s+/g, " ").trim(); diff --git a/scripts/src/hwlab-node-web-sentinel-cicd.ts b/scripts/src/hwlab-node-web-sentinel-cicd.ts index a967db32..116f98d0 100644 --- a/scripts/src/hwlab-node-web-sentinel-cicd.ts +++ b/scripts/src/hwlab-node-web-sentinel-cicd.ts @@ -3453,6 +3453,9 @@ function compactQuickVerifyRecordFinding(value: unknown): Record{const v=rec(item); return {id:clip(v.id??v.kind??v.code,80),kind:clip(v.kind??v.id??v.code,80),code:clip(v.code??v.kind??v.id,80),severity:clip(v.severity??v.level,32),level:clip(v.level??v.severity,32),count:Number(v.count??v.sampleCount??1),summary:clip(v.summary??v.message,220),message:clip(v.message??v.summary,220),rootCause:clip(v.rootCause,140),rootCauseStatus:clip(v.rootCauseStatus,90),rootCauseConfidence:clip(v.rootCauseConfidence,40),nextAction:clip(v.nextAction,240),evidenceSummary:v.evidence?clip(JSON.stringify(rec(v.evidence)),220):clip(v.evidenceSummary,220),blocking:v.blocking===true,afterRound:v.afterRound??null,canarySessionId:clip(v.canarySessionId,80),routeSessionId:clip(v.routeSessionId,80),activeSessionId:clip(v.activeSessionId,80),consecutiveUserMessageCount:v.consecutiveUserMessageCount??null,sentinelRange:clip(v.sentinelRange,80),sampleSeq:v.sampleSeq??null,traceIds:arr(v.traceIds).slice(0,8).map((x)=>clip(x,80)),pageRole:clip(v.pageRole,32),pageId:clip(v.pageId,80),observerId:clip(v.observerId,80),stateDir:clip(v.stateDir,160),commandId:clip(v.commandId,80),valuesRedacted:true};});", + "const findings=arr(report?.findings ?? report?.archiveSummary?.redFindings).slice(0,20).map((item)=>{const v=rec(item); return {id:clip(v.id??v.kind??v.code,80),kind:clip(v.kind??v.id??v.code,80),code:clip(v.code??v.kind??v.id,80),severity:clip(v.severity??v.level,32),level:clip(v.level??v.severity,32),count:Number(v.count??v.sampleCount??1),summary:clip(v.summary??v.message,220),message:clip(v.message??v.summary,220),rootCause:clip(v.rootCause,140),rootCauseStatus:clip(v.rootCauseStatus,90),rootCauseConfidence:clip(v.rootCauseConfidence,40),nextAction:clip(v.nextAction,240),evidenceSummary:v.evidence?clip(JSON.stringify(rec(v.evidence)),220):clip(v.evidenceSummary,220),timingSourceOfTruth:clip(v.timingSourceOfTruth??v.expectedElapsedSource??v.evidenceKind,100),timingStatus:clip(v.timingStatus,60),timingAlert:v.timingAlert===true,blocking:v.blocking===true,afterRound:v.afterRound??null,canarySessionId:clip(v.canarySessionId,80),routeSessionId:clip(v.routeSessionId,80),activeSessionId:clip(v.activeSessionId,80),consecutiveUserMessageCount:v.consecutiveUserMessageCount??null,sentinelRange:clip(v.sentinelRange,80),sampleSeq:v.sampleSeq??null,traceIds:arr(v.traceIds).slice(0,8).map((x)=>clip(x,80)),pageRole:clip(v.pageRole,32),pageId:clip(v.pageId,80),observerId:clip(v.observerId,80),stateDir:clip(v.stateDir,160),commandId:clip(v.commandId,80),valuesRedacted:true};});", "const slow=arr(report?.pagePerformanceSlowApi ?? report?.archivePagePerformanceSlowApi).slice(0,8).map((item)=>{const v=rec(item); return {path:clip(v.path??v.route,120),sampleCount:v.sampleCount??null,p95Ms:v.p95Ms??null,maxMs:v.maxMs??null,overFiveSecondCount:v.overFiveSecondCount??null};});", "console.log(JSON.stringify({ok:!!report,reportOk:!!report&&report.ok!==false,stateDir,reportJsonPath:reportPath,reportJsonSha256:sha(jsonBuf),reportMdPath,reportMdSha256:sha(read(reportMdPath)),findingCount:Number(report?.findingCount??findings.length),artifactCount,screenshot,findings,counts:rec(report?.counts),analysisWindow:rec(report?.analysisWindow??report?.windows?.recent?.summary),pagePerformanceSlowApi:slow,valuesRedacted:true}));", "NODE", @@ -4964,7 +4967,7 @@ function renderQuickVerifySummary(input: Record): string { `publicOrigin=${input.publicOrigin ?? "-"}`, "", "Findings", - findings.length === 0 ? "-" : findings.map((item) => `${item.severity ?? item.level ?? "-"} ${item.kind ?? item.id ?? item.code ?? "-"} count=${item.count ?? "-"} ${item.summary ?? item.message ?? ""}`).join("\n"), + findings.length === 0 ? "-" : findings.map((item) => `${item.severity ?? item.level ?? "-"} ${item.kind ?? item.id ?? item.code ?? "-"} count=${item.count ?? "-"}${formatQuickVerifyTimingSuffix(item)} ${item.summary ?? item.message ?? ""}`).join("\n"), ].join("\n"); } @@ -5000,10 +5003,17 @@ function renderAuthSessionSwitchQuickVerifySummary(input: Record `${item.severity ?? item.level ?? "-"} ${item.kind ?? item.id ?? item.code ?? "-"} count=${item.count ?? "-"} ${item.summary ?? item.message ?? ""}`).join("\n"), + findingRows.length === 0 ? "-" : findingRows.map((item) => `${item.severity ?? item.level ?? "-"} ${item.kind ?? item.id ?? item.code ?? "-"} count=${item.count ?? "-"}${formatQuickVerifyTimingSuffix(item)} ${item.summary ?? item.message ?? ""}`).join("\n"), ].join("\n"); } +function formatQuickVerifyTimingSuffix(item: Record): string { + const status = stringAtNullable(item, "timingStatus"); + const source = stringAtNullable(item, "timingSourceOfTruth") ?? stringAtNullable(item, "expectedElapsedSource") ?? stringAtNullable(item, "evidenceKind"); + if (status === null && source === null) return ""; + return ` timing=${[status === null ? null : `status=${status}`, source === null ? null : `source=${source}`].filter((part) => part !== null).join(" ")}`; +} + function renderMaintenanceResult(result: Record): string { const serviceHealth = record(result.serviceHealth); const maintenance = record(result.maintenance); diff --git a/scripts/src/hwlab-node-web-sentinel-service.ts b/scripts/src/hwlab-node-web-sentinel-service.ts index a00cc0f9..90793903 100644 --- a/scripts/src/hwlab-node-web-sentinel-service.ts +++ b/scripts/src/hwlab-node-web-sentinel-service.ts @@ -870,6 +870,9 @@ function dashboardFindings(config: WebProbeSentinelServiceConfig, db: Database, rootCauseConfidence: stringOrNull(latestDetail?.rootCauseConfidence), nextAction: stringOrNull(latestDetail?.nextAction), evidenceSummary: stringOrNull(latestDetail?.evidenceSummary), + timingSourceOfTruth: stringOrNull(latestDetail?.timingSourceOfTruth), + timingStatus: stringOrNull(latestDetail?.timingStatus), + timingAlert: latestDetail?.timingAlert === true, traceability: latestRun === null ? null : runTraceability(config, latestRun), valuesRedacted: true, }); @@ -1147,6 +1150,9 @@ function enrichFindingRowWithStoredDetail(config: WebProbeSentinelServiceConfig, rootCauseConfidence: stringOrNull(detail?.rootCauseConfidence), nextAction: stringOrNull(detail?.nextAction), evidenceSummary: stringOrNull(detail?.evidenceSummary), + timingSourceOfTruth: stringOrNull(detail?.timingSourceOfTruth), + timingStatus: stringOrNull(detail?.timingStatus), + timingAlert: detail?.timingAlert === true, valuesRedacted: true, }); } @@ -1184,6 +1190,9 @@ function compactStoredFinding(value: unknown): Record { rootCauseConfidence: stringOrNull(item.rootCauseConfidence), nextAction: stringOrNull(item.nextAction), evidenceSummary: stringOrNull(item.evidenceSummary) ?? compactFindingEvidenceSummary(item.evidence), + timingSourceOfTruth: stringOrNull(item.timingSourceOfTruth) ?? stringOrNull(item.expectedElapsedSource) ?? stringOrNull(item.evidenceKind), + timingStatus: stringOrNull(item.timingStatus), + timingAlert: item.timingAlert === true, valuesRedacted: true, }; } @@ -1540,7 +1549,7 @@ function reportRunView(config: WebProbeSentinelServiceConfig, db: Database, view const findings = findingsForRun(config, 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; + const renderedText = view === "findings" ? renderStoredFindings(row, findings) : typeof storedView.renderedText === "string" ? storedView.renderedText : view === "summary" ? renderStoredSummary(row, stored, findings) : null; if (renderedText === null) { return { ok: false, error: "report-view-not-indexed", runId: selectedRunId, view, availableViews: Object.keys(views), valuesRedacted: true }; } @@ -1564,14 +1573,28 @@ function formatStoredFindingLine(item: Record): string { const status = stringOrNull(item.rootCauseStatus); const nextAction = stringOrNull(item.nextAction); const evidence = stringOrNull(item.evidenceSummary); + const timing = formatStoredFindingTiming(item); return [ `${item.severity ?? "-"} ${code} ${title} count=${item.count ?? "-"} ${summary}`, + timing, 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 formatStoredFindingTiming(item: Record): string | null { + const source = stringOrNull(item.timingSourceOfTruth); + const status = stringOrNull(item.timingStatus); + if (source === null && status === null) return null; + const pieces = [ + status === null ? null : `status=${status}`, + source === null ? null : `source=${source}`, + item.timingAlert === true ? "alert=true" : null, + ].filter((part): part is string => part !== null); + return `timing=${pieces.join(" ")}`; +} + function renderStoredSummary(row: Record, stored: Record, findings: readonly Record[]): string { const summary = record(stored.summary); return [