fix: split quick verify control findings
This commit is contained in:
@@ -8,6 +8,7 @@ import { repoRoot } from "./config";
|
||||
import { startJob } from "./jobs";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
import { runWebProbeRemoteArtifactJob } from "./web-probe-remote-artifact";
|
||||
import { readWebProbeSentinelConfigRefTarget } from "./hwlab-node-web-sentinel-config-ref";
|
||||
import type { SentinelCicdState, WebProbeSentinelOptions } from "./hwlab-node-web-sentinel-cicd";
|
||||
import {
|
||||
clipTail,
|
||||
@@ -32,7 +33,7 @@ import {
|
||||
text,
|
||||
withWarnings,
|
||||
} from "./hwlab-node-web-sentinel-cicd";
|
||||
import { metricNames, runSentinelQuickVerify, sentinelP5Next, serviceUnavailableBlocker, validationBlocker } from "./hwlab-node-web-sentinel-p5-observe";
|
||||
import { metricNames, reclassifyQuickVerifyControlFindings, runSentinelQuickVerify, sentinelP5Next, serviceUnavailableBlocker, validationBlocker } from "./hwlab-node-web-sentinel-p5-observe";
|
||||
|
||||
const SENTINEL_REPORT_ARTIFACT_READ_TIMEOUT_SECONDS = 55;
|
||||
|
||||
@@ -264,6 +265,7 @@ function compactSentinelReportRawPayload(
|
||||
const findings = Array.isArray(body.findings) ? body.findings.map(record) : [];
|
||||
const artifactFindings = Array.isArray(artifact.findings) ? artifact.findings.map(record) : [];
|
||||
const visibleFindings = mergeSentinelReportFindings(findings, artifactFindings);
|
||||
const offlineReclassify = offlineQuickVerifyReclassify(state, run, visibleFindings);
|
||||
const storedFindingCount = numberAtNullable(run, "finding_count") ?? numberAtNullable(run, "findingCount") ?? findings.length;
|
||||
const artifactFindingCount = numberAtNullable(artifact, "findingCount") ?? artifactFindings.length;
|
||||
const visibleFindingCount = Math.max(storedFindingCount, artifactFindingCount, visibleFindings.length);
|
||||
@@ -306,6 +308,7 @@ function compactSentinelReportRawPayload(
|
||||
},
|
||||
summary: pickFields(record(body.summary), ["reason", "status", "businessStatus", "failure", "valuesRedacted"]),
|
||||
findings: visibleFindings.slice(0, 12).map(compactSentinelReportFinding),
|
||||
offlineReclassify,
|
||||
artifactSummary: Object.keys(artifact).length === 0 ? null : {
|
||||
ok: artifact.ok === true,
|
||||
reason: artifact.reason ?? null,
|
||||
@@ -333,6 +336,53 @@ function compactSentinelReportRawPayload(
|
||||
};
|
||||
}
|
||||
|
||||
function offlineQuickVerifyReclassify(state: SentinelCicdState, run: Record<string, unknown>, findings: readonly Record<string, unknown>[]): Record<string, unknown> | null {
|
||||
const hasLegacyQuickVerifyControl = findings.some((item) => sentinelReportFindingIdentityCandidates(item).includes("quick-verify-no-business-turn"));
|
||||
if (!hasLegacyQuickVerifyControl) return null;
|
||||
const catalog = sentinelReportCheckCatalogById(state);
|
||||
const result = reclassifyQuickVerifyControlFindings(state, {
|
||||
runId: stringAtNullable(run, "id"),
|
||||
scenarioId: stringAtNullable(run, "scenario_id") ?? stringAtNullable(run, "scenarioId"),
|
||||
observerId: stringAtNullable(run, "observer_id") ?? stringAtNullable(run, "observerId"),
|
||||
failure: stringAtNullable(record(run.summary), "failure"),
|
||||
timeoutSeconds: SENTINEL_REPORT_ARTIFACT_READ_TIMEOUT_SECONDS,
|
||||
});
|
||||
const resultFindings = Array.isArray(result.findings) ? result.findings.map(record) : [];
|
||||
return {
|
||||
...pickFields(result, ["ok", "reason", "runId", "scenarioId", "observerId", "promptIndex", "findingCount", "turnSummary", "traceFrame", "valuesRedacted"]),
|
||||
source: "offline-existing-observe-artifact",
|
||||
note: "This is a local CLI reclassification of existing turn-summary/trace-frame artifacts; it does not mutate the runner index.",
|
||||
findings: resultFindings.slice(0, 8).map((item) => compactSentinelReportFinding(enrichSentinelReportFindingWithCatalog(item, catalog))),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function sentinelReportCheckCatalogById(state: SentinelCicdState): Map<string, Record<string, unknown>> {
|
||||
try {
|
||||
const reportViews = record(readWebProbeSentinelConfigRefTarget(state.spec, state.configRefs.reportViews));
|
||||
const catalogRef = stringAtNullable(reportViews, "checkCatalogRef");
|
||||
if (catalogRef === null) return new Map();
|
||||
const catalog = record(readWebProbeSentinelConfigRefTarget(state.spec, catalogRef));
|
||||
const items = Array.isArray(catalog.items) ? catalog.items.map(record) : Array.isArray(catalog.checks) ? catalog.checks.map(record) : [];
|
||||
return new Map(items.map((item) => [stringAtNullable(item, "id") ?? "", item]).filter((item): item is [string, Record<string, unknown>] => item[0].length > 0));
|
||||
} catch {
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
|
||||
function enrichSentinelReportFindingWithCatalog(item: Record<string, unknown>, catalog: ReadonlyMap<string, Record<string, unknown>>): Record<string, unknown> {
|
||||
const id = stringAtNullable(item, "id") ?? stringAtNullable(item, "kind") ?? stringAtNullable(item, "code");
|
||||
const check = id === null ? null : catalog.get(id) ?? null;
|
||||
if (check === null) return item;
|
||||
return {
|
||||
...item,
|
||||
check,
|
||||
checkCode: stringAtNullable(check, "code"),
|
||||
checkTitleZh: stringAtNullable(check, "titleZh"),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function mergeSentinelReportFindings(primary: readonly Record<string, unknown>[], artifact: readonly Record<string, unknown>[]): Record<string, unknown>[] {
|
||||
const merged: Record<string, unknown>[] = [];
|
||||
const seen = new Set<string>();
|
||||
@@ -423,8 +473,12 @@ function compactSentinelReportFinding(value: Record<string, unknown>): Record<st
|
||||
const rootCauseSignals = compactRootCauseSignals(value.rootCauseSignals);
|
||||
if (rootCauseSignals !== null) result.rootCauseSignals = rootCauseSignals;
|
||||
const check = record(value.check);
|
||||
const checkCodeFromValue = stringAtNullable(value, "checkCode");
|
||||
const checkTitleFromValue = stringAtNullable(value, "checkTitleZh");
|
||||
const checkCode = stringAtNullable(check, "code");
|
||||
const checkTitle = stringAtNullable(check, "titleZh");
|
||||
if (checkCodeFromValue !== null) result.checkCode = checkCodeFromValue;
|
||||
if (checkTitleFromValue !== null) result.checkTitleZh = checkTitleFromValue;
|
||||
if (checkCode !== null || checkTitle !== null) {
|
||||
result.check = pickFields(check, ["id", "code", "level", "titleZh", "blocking", "registered"]);
|
||||
}
|
||||
@@ -471,7 +525,9 @@ function renderSentinelReportSummary(payload: Record<string, unknown>, state: Se
|
||||
const run = record(payload.run);
|
||||
const summary = record(payload.summary);
|
||||
const artifactSummary = record(payload.artifactSummary);
|
||||
const offlineReclassify = record(payload.offlineReclassify);
|
||||
const findings = Array.isArray(payload.findings) ? payload.findings.map(record) : [];
|
||||
const offlineFindings = Array.isArray(offlineReclassify.findings) ? offlineReclassify.findings.map(record) : [];
|
||||
const reportSha = stringAtNullable(run, "reportJsonSha256") ?? stringAtNullable(artifactSummary, "reportJsonSha256");
|
||||
const findingCount = run.findingCount ?? findings.length;
|
||||
const analyzerArtifactCount = numberAtNullable(artifactSummary, "artifactCount") ?? numberAtNullable(record(artifactSummary.counts), "artifacts");
|
||||
@@ -498,26 +554,34 @@ function renderSentinelReportSummary(payload: Record<string, unknown>, state: Se
|
||||
"",
|
||||
"Findings",
|
||||
findingRows,
|
||||
offlineFindings.length === 0 ? "" : "",
|
||||
offlineFindings.length === 0 ? "" : "Offline Reclassify",
|
||||
offlineFindings.length === 0 ? "" : offlineFindings.map(formatSentinelReportFindingLine).join("\n"),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderSentinelReportFindings(payload: Record<string, unknown>): string {
|
||||
const run = record(payload.run);
|
||||
const artifactSummary = record(payload.artifactSummary);
|
||||
const offlineReclassify = record(payload.offlineReclassify);
|
||||
const findings = Array.isArray(payload.findings) ? payload.findings.map(record) : [];
|
||||
const offlineFindings = Array.isArray(offlineReclassify.findings) ? offlineReclassify.findings.map(record) : [];
|
||||
const reportSha = stringAtNullable(run, "reportJsonSha256") ?? stringAtNullable(artifactSummary, "reportJsonSha256");
|
||||
return [
|
||||
"Web Probe Sentinel Findings",
|
||||
"=======================================================",
|
||||
`run=${run.id ?? "-"} report=${reportSha ?? "-"} findings=${run.findingCount ?? findings.length}`,
|
||||
findings.length === 0 ? "-" : findings.map(formatSentinelReportFindingLine).join("\n"),
|
||||
offlineFindings.length === 0 ? "" : "",
|
||||
offlineFindings.length === 0 ? "" : "Offline Reclassify",
|
||||
offlineFindings.length === 0 ? "" : offlineFindings.map(formatSentinelReportFindingLine).join("\n"),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function formatSentinelReportFindingLine(item: Record<string, unknown>): string {
|
||||
const check = record(item.check);
|
||||
const code = stringAtNullable(check, "code") ?? stringAtNullable(item, "id") ?? "-";
|
||||
const title = stringAtNullable(check, "titleZh") ?? "";
|
||||
const code = stringAtNullable(item, "checkCode") ?? stringAtNullable(check, "code") ?? stringAtNullable(item, "id") ?? "-";
|
||||
const title = stringAtNullable(item, "checkTitleZh") ?? stringAtNullable(check, "titleZh") ?? "";
|
||||
const summary = reportText(item.summary, 180) ?? "";
|
||||
const rootCause = reportText(item.rootCause, 180);
|
||||
const evidence = reportText(item.evidenceSummary, 180);
|
||||
|
||||
Reference in New Issue
Block a user