fix: split web sentinel ambiguous check codes
This commit is contained in:
@@ -1760,7 +1760,7 @@ function visibleFindingsForRun(
|
||||
const artifactFindings = arrayRecords(artifactSummary.findings)
|
||||
.slice(0, limit)
|
||||
.map((item) => enrichFindingWithCheck(config, compactStoredFinding(item)));
|
||||
return mergeVisibleFindings(config, storedFindings, artifactFindings, limit);
|
||||
return reclassifyVisibleFindings(config, row, mergeVisibleFindings(config, storedFindings, artifactFindings, limit), limit);
|
||||
}
|
||||
|
||||
function mergeVisibleFindings(config: WebProbeSentinelServiceConfig, primary: readonly Record<string, unknown>[], artifact: readonly Record<string, unknown>[], limit: number): readonly Record<string, unknown>[] {
|
||||
@@ -1793,6 +1793,126 @@ function canonicalFindingIdentity(config: WebProbeSentinelServiceConfig, item: R
|
||||
return stringOrNull(entry?.code) ?? stringOrNull(entry?.id) ?? findingId;
|
||||
}
|
||||
|
||||
function reclassifyVisibleFindings(config: WebProbeSentinelServiceConfig, row: Record<string, unknown>, findings: readonly Record<string, unknown>[], limit: number): readonly Record<string, unknown>[] {
|
||||
const hasObserverCommandFailure = findings.some((item) => findingIdentityCandidates(item).includes("observer-command-failed"));
|
||||
const failure = stringOrNull(record(row.summary).failure) ?? stringOrNull(row.failure) ?? stringOrNull(record(row.businessStatus).failure);
|
||||
return findings
|
||||
.map((item) => enrichFindingWithCheck(config, reclassifyVisibleFinding(item, failure, hasObserverCommandFailure)))
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
function findingIdentityCandidates(item: Record<string, unknown>): readonly string[] {
|
||||
const check = record(item.check);
|
||||
const candidates = [
|
||||
stringOrNull(item.finding_id),
|
||||
stringOrNull(item.findingId),
|
||||
stringOrNull(item.id),
|
||||
stringOrNull(item.kind),
|
||||
stringOrNull(item.code),
|
||||
stringOrNull(item.checkId),
|
||||
stringOrNull(item.checkCode),
|
||||
stringOrNull(check.id),
|
||||
stringOrNull(check.code),
|
||||
];
|
||||
return [...new Set(candidates.filter((value): value is string => value !== null))];
|
||||
}
|
||||
|
||||
function reclassifyVisibleFinding(item: Record<string, unknown>, failure: string | null, hasObserverCommandFailure: boolean): Record<string, unknown> {
|
||||
const ids = findingIdentityCandidates(item);
|
||||
if (ids.includes("quick-verify-command-sequence-failed")) {
|
||||
return retagVisibleFinding(item, "account-session-command-failed-before-observation", {
|
||||
summary: "account/session setup command failed before any business turn could be observed.",
|
||||
rootCause: `account-session-command-stage-failed:${failure ?? "unknown"}`,
|
||||
rootCauseStatus: "confirmed",
|
||||
rootCauseConfidence: "high",
|
||||
evidenceSummary: `stage=account-session-command failure=${failure ?? "-"}`,
|
||||
nextAction: "Inspect the first failed account/session/workbench command and rerun quick verify after that command succeeds.",
|
||||
});
|
||||
}
|
||||
if (ids.includes("observer-command-failed") && !hasCommandFailureEvidence(item)) {
|
||||
return retagVisibleFinding(item, "observer-command-failure-evidence-missing", {
|
||||
summary: "report says observer control commands failed, but the first failed command evidence is missing.",
|
||||
rootCause: "observer-command-failure-evidence-missing",
|
||||
rootCauseStatus: "evidence-gap",
|
||||
rootCauseConfidence: "high",
|
||||
evidenceSummary: "firstCommandFailure missing from indexed and artifact findings",
|
||||
nextAction: "Fix analyzer/report commandFailures compact output before using this finding as a page root cause.",
|
||||
});
|
||||
}
|
||||
if (ids.includes("quick-verify-observer-stop-failed")) {
|
||||
const evidence = `${stringOrNull(item.evidenceSummary) ?? ""} ${stringOrNull(item.summary) ?? ""}`;
|
||||
if (/aliveAfter\s*=\s*true/u.test(evidence)) {
|
||||
return retagVisibleFinding(item, "observer-force-stop-left-process-alive", {
|
||||
summary: "observer force stop completed but process liveness evidence still reports aliveAfter=true.",
|
||||
rootCause: "observer-process-still-alive-after-force-stop",
|
||||
rootCauseStatus: "confirmed",
|
||||
rootCauseConfidence: "high",
|
||||
});
|
||||
}
|
||||
if (/aliveAfter\s*=\s*false/u.test(evidence)) {
|
||||
return retagVisibleFinding(item, "observer-graceful-stop-timeout-force-stop-succeeded", {
|
||||
severity: "warning",
|
||||
level: "warning",
|
||||
summary: "observer graceful stop did not drain before timeout, but force stop confirmed aliveAfter=false.",
|
||||
rootCause: "observer-graceful-stop-timeout-force-stop-succeeded",
|
||||
rootCauseStatus: "confirmed",
|
||||
rootCauseConfidence: "high",
|
||||
});
|
||||
}
|
||||
return retagVisibleFinding(item, "observer-cleanup-evidence-missing", {
|
||||
summary: "observer cleanup stop-state evidence is missing or incomplete; this is an evidence gap, not confirmed process leakage.",
|
||||
rootCause: "observer-cleanup-evidence-missing",
|
||||
rootCauseStatus: "evidence-gap",
|
||||
rootCauseConfidence: "medium",
|
||||
evidenceSummary: stringOrNull(item.evidenceSummary) ?? "cleanup evidence missing",
|
||||
});
|
||||
}
|
||||
if (ids.includes("no-samples")) {
|
||||
if (hasObserverCommandFailure) {
|
||||
return retagVisibleFinding(item, "no-samples-after-observer-command-failed", {
|
||||
summary: "observer produced zero samples after a blocking control command failed; the command failure is the deterministic precondition.",
|
||||
rootCause: "observer-command-failed-before-sampling",
|
||||
rootCauseStatus: "confirmed",
|
||||
rootCauseConfidence: "high",
|
||||
});
|
||||
}
|
||||
return retagVisibleFinding(item, "no-samples-artifact-evidence-missing", {
|
||||
summary: "observer produced zero samples and analyzer has no failed command precondition; sampling/source evidence is missing.",
|
||||
rootCause: "sample-artifact-evidence-missing",
|
||||
rootCauseStatus: "evidence-gap",
|
||||
rootCauseConfidence: "medium",
|
||||
});
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
function hasCommandFailureEvidence(item: Record<string, unknown>): boolean {
|
||||
const first = record(item.firstCommandFailure ?? firstArrayRecord(item.commands));
|
||||
return stringOrNull(first.type) !== null
|
||||
|| stringOrNull(first.phase) !== null
|
||||
|| stringOrNull(first.status) !== null
|
||||
|| stringOrNull(first.stdoutPreview) !== null
|
||||
|| stringOrNull(first.stderrPreview) !== null
|
||||
|| numberOrNull(first.exitCode) !== null;
|
||||
}
|
||||
|
||||
function retagVisibleFinding(item: Record<string, unknown>, id: string, extra: Record<string, unknown>): Record<string, unknown> {
|
||||
return {
|
||||
...item,
|
||||
...extra,
|
||||
id,
|
||||
kind: id,
|
||||
code: id,
|
||||
finding_id: id,
|
||||
findingId: id,
|
||||
check: null,
|
||||
checkId: null,
|
||||
checkCode: null,
|
||||
checkTitleZh: null,
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function findingIdentity(item: Record<string, unknown>): string | null {
|
||||
return stringOrNull(item.finding_id)
|
||||
?? stringOrNull(item.findingId)
|
||||
@@ -1871,6 +1991,7 @@ function enrichFindingRowWithStoredDetail(config: WebProbeSentinelServiceConfig,
|
||||
rootCauseConfidence: stringOrNull(detail?.rootCauseConfidence),
|
||||
nextAction: stringOrNull(detail?.nextAction),
|
||||
evidenceSummary: stringOrNull(detail?.evidenceSummary),
|
||||
firstCommandFailure: compactCommandFailureEvidence(detail?.firstCommandFailure ?? firstArrayRecord(detail?.commands)),
|
||||
rootCauseSignals: record(detail?.rootCauseSignals),
|
||||
timingSourceOfTruth: stringOrNull(detail?.timingSourceOfTruth),
|
||||
timingStatus: stringOrNull(detail?.timingStatus),
|
||||
@@ -1918,6 +2039,7 @@ function compactStoredFinding(value: unknown): Record<string, unknown> {
|
||||
rootCauseConfidence: stringOrNull(item.rootCauseConfidence),
|
||||
nextAction: stringOrNull(item.nextAction),
|
||||
evidenceSummary: stringOrNull(item.evidenceSummary) ?? compactFindingEvidenceSummary(item.evidence),
|
||||
firstCommandFailure: compactCommandFailureEvidence(item.firstCommandFailure ?? firstArrayRecord(item.commands)),
|
||||
rootCauseSignals: compactFindingRootCauseSignals(item.rootCauseSignals),
|
||||
timingSourceOfTruth: stringOrNull(item.timingSourceOfTruth) ?? stringOrNull(item.expectedElapsedSource) ?? stringOrNull(item.evidenceKind),
|
||||
timingStatus: stringOrNull(item.timingStatus),
|
||||
@@ -1926,6 +2048,27 @@ function compactStoredFinding(value: unknown): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function compactCommandFailureEvidence(value: unknown): Record<string, unknown> | null {
|
||||
const item = record(value);
|
||||
if (Object.keys(item).length === 0) return null;
|
||||
return {
|
||||
commandId: stringOrNull(item.commandId),
|
||||
type: stringOrNull(item.type),
|
||||
phase: stringOrNull(item.phase),
|
||||
status: stringOrNull(item.status),
|
||||
exitCode: numberOrNull(item.exitCode),
|
||||
timedOut: item.timedOut === true ? true : item.timedOut === false ? false : null,
|
||||
stdoutPreview: stringOrNull(item.stdoutPreview)?.slice(0, 240) ?? null,
|
||||
stderrPreview: stringOrNull(item.stderrPreview)?.slice(0, 240) ?? null,
|
||||
artifactRef: stringOrNull(item.artifactRef),
|
||||
valuesRedacted: true,
|
||||
};
|
||||
}
|
||||
|
||||
function firstArrayRecord(value: unknown): Record<string, unknown> | null {
|
||||
return Array.isArray(value) && value.length > 0 ? record(value[0]) : null;
|
||||
}
|
||||
|
||||
function compactFindingRootCauseSignals(value: unknown): Record<string, unknown> | null {
|
||||
const item = record(value);
|
||||
const keys = [
|
||||
|
||||
Reference in New Issue
Block a user