fix: split web sentinel ambiguous check codes

This commit is contained in:
Codex
2026-07-06 16:17:07 +00:00
parent 79a35183aa
commit cc694e9d20
7 changed files with 453 additions and 19 deletions
+142 -3
View File
@@ -264,7 +264,7 @@ function compactSentinelReportRawPayload(
const artifact = record(artifactSummary);
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 visibleFindings = reclassifySentinelReportFindings(state, run, 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;
@@ -337,7 +337,14 @@ 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"));
const hasLegacyQuickVerifyControl = findings.some((item) => sentinelReportFindingIdentityCandidates(item).some((id) => [
"quick-verify-no-business-turn",
"quick-verify-command-sequence-failed",
"account-session-command-failed-before-observation",
"no-samples",
"no-samples-after-observer-command-failed",
"no-samples-artifact-evidence-missing",
].includes(id)));
if (!hasLegacyQuickVerifyControl) return null;
const catalog = sentinelReportCheckCatalogById(state);
const result = reclassifyQuickVerifyControlFindings(state, {
@@ -357,6 +364,114 @@ function offlineQuickVerifyReclassify(state: SentinelCicdState, run: Record<stri
};
}
function reclassifySentinelReportFindings(state: SentinelCicdState, run: Record<string, unknown>, findings: readonly Record<string, unknown>[]): Record<string, unknown>[] {
const hasObserverCommandFailure = findings.some((item) => sentinelReportFindingIdentityCandidates(item).includes("observer-command-failed"));
const catalog = sentinelReportCheckCatalogById(state);
return findings.map((item) => enrichSentinelReportFindingWithCatalog(reclassifySentinelReportFinding(rowFailure(run), item, hasObserverCommandFailure), catalog));
}
function rowFailure(row: Record<string, unknown>): string | null {
return stringAtNullable(record(row.summary), "failure")
?? stringAtNullable(row, "failure")
?? stringAtNullable(record(row.businessStatus), "failure");
}
function reclassifySentinelReportFinding(failure: string | null, item: Record<string, unknown>, hasObserverCommandFailure: boolean): Record<string, unknown> {
const ids = sentinelReportFindingIdentityCandidates(item);
if (ids.includes("quick-verify-command-sequence-failed")) {
return retagSentinelReportFinding(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 retagSentinelReportFinding(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 = `${stringAtNullable(item, "evidenceSummary") ?? ""} ${stringAtNullable(item, "summary") ?? ""}`;
if (/aliveAfter\s*=\s*true/u.test(evidence)) {
return retagSentinelReportFinding(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 retagSentinelReportFinding(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 retagSentinelReportFinding(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: stringAtNullable(item, "evidenceSummary") ?? "cleanup evidence missing",
});
}
if (ids.includes("no-samples")) {
if (hasObserverCommandFailure) {
return retagSentinelReportFinding(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 retagSentinelReportFinding(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 stringAtNullable(first, "type") !== null
|| stringAtNullable(first, "phase") !== null
|| stringAtNullable(first, "status") !== null
|| stringAtNullable(first, "stdoutPreview") !== null
|| stringAtNullable(first, "stderrPreview") !== null
|| numberAtNullable(first, "exitCode") !== null;
}
function retagSentinelReportFinding(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 sentinelReportCheckCatalogById(state: SentinelCicdState): Map<string, Record<string, unknown>> {
try {
const reportViews = record(readWebProbeSentinelConfigRefTarget(state.spec, state.configRefs.reportViews));
@@ -465,6 +580,8 @@ function compactSentinelReportFinding(value: Record<string, unknown>): Record<st
if (nextAction !== null) result.nextAction = nextAction;
const evidenceSummary = reportText(value.evidenceSummary, 240);
if (evidenceSummary !== null) result.evidenceSummary = evidenceSummary;
const firstCommandFailure = compactSentinelReportCommandFailure(value.firstCommandFailure ?? firstArrayRecord(value.commands));
if (firstCommandFailure !== null) result.firstCommandFailure = firstCommandFailure;
const timingSourceOfTruth = stringAtNullable(value, "timingSourceOfTruth");
if (timingSourceOfTruth !== null) result.timingSourceOfTruth = timingSourceOfTruth;
const timingStatus = stringAtNullable(value, "timingStatus");
@@ -485,6 +602,27 @@ function compactSentinelReportFinding(value: Record<string, unknown>): Record<st
return result;
}
function compactSentinelReportCommandFailure(value: unknown): Record<string, unknown> | null {
const item = record(value);
if (Object.keys(item).length === 0) return null;
return {
commandId: stringAtNullable(item, "commandId"),
type: stringAtNullable(item, "type"),
phase: stringAtNullable(item, "phase"),
status: stringAtNullable(item, "status"),
exitCode: numberAtNullable(item, "exitCode"),
timedOut: item.timedOut === true ? true : item.timedOut === false ? false : null,
stdoutPreview: reportText(item.stdoutPreview, 160),
stderrPreview: reportText(item.stderrPreview, 160),
artifactRef: stringAtNullable(item, "artifactRef"),
valuesRedacted: true,
};
}
function firstArrayRecord(value: unknown): Record<string, unknown> | null {
return Array.isArray(value) && value.length > 0 ? record(value[0]) : null;
}
function readSentinelReportArtifactSummary(state: SentinelCicdState, body: Record<string, unknown>, timeoutSeconds: number): Record<string, unknown> | null {
const run = record(body.run);
const stateDir = stringAtNullable(run, "state_dir") ?? stringAtNullable(run, "stateDir");
@@ -501,13 +639,14 @@ function readSentinelReportArtifactSummary(state: SentinelCicdState, body: Recor
"const sha=(buf)=>buf?`sha256:${crypto.createHash('sha256').update(buf).digest('hex')}`:null;",
"const rec=(v)=>v&&typeof v==='object'&&!Array.isArray(v)?v:{}; const arr=(v)=>Array.isArray(v)?v:[]; const clip=(v,n=180)=>v==null?null:String(v).slice(0,n);",
"const compactRootCauseSignals=(value)=>{const v=rec(value); const keys=['sessionListReadCount','traceEventsReadCount','webPerformanceBeaconFailureCount','eventSourceFailureCount','requestFailedCount','httpErrorCount','consoleAlertCount','requestfailedTop','httpStatusTop']; const out={}; for(const key of keys){if(v[key]!=null)out[key]=Array.isArray(v[key])?v[key].slice(0,8):v[key];} if(Object.keys(out).length===0)return null; out.valuesRedacted=true; return out;};",
"const compactCommandFailure=(value)=>{const v=rec(value); if(Object.keys(v).length===0)return null; return {commandId:clip(v.commandId,80),type:clip(v.type,48),phase:clip(v.phase,80),status:clip(v.status,80),exitCode:Number.isFinite(Number(v.exitCode))?Number(v.exitCode):null,timedOut:v.timedOut===true?true:v.timedOut===false?false:null,stdoutPreview:clip(v.stdoutPreview,160),stderrPreview:clip(v.stderrPreview,160),artifactRef:clip(v.artifactRef,160),valuesRedacted:true};};",
"const sleep=(ms)=>{try{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,ms)}catch{}};",
"let jsonBuf=read(reportPath); let report=null; let reportParseError=null; const deadline=Date.now()+waitMs;",
"while(true){ if(jsonBuf){try{report=JSON.parse(jsonBuf.toString('utf8')); break}catch(err){reportParseError=String(err&&err.message?err.message:err)}} if(Date.now()>=deadline)break; sleep(Math.min(500, Math.max(50, deadline-Date.now()))); jsonBuf=read(reportPath); }",
"let artifactCount=0; let screenshot=null;",
"function walk(dir){let entries=[]; try{entries=fs.readdirSync(dir,{withFileTypes:true})}catch{return}; for(const e of entries){const p=path.join(dir,e.name); if(e.isDirectory()) walk(p); else { artifactCount++; if(/\\.png$/i.test(e.name)){const b=read(p); screenshot={path:p,sha256:sha(b),bytes:b?b.length:0}; } } }}",
"walk(stateDir);",
"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,rootCauseSignals:compactRootCauseSignals(v.rootCauseSignals),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),firstCommandFailure:compactCommandFailure(v.firstCommandFailure || arr(v.commands)[0]),timingSourceOfTruth:clip(v.timingSourceOfTruth??v.expectedElapsedSource??v.evidenceKind,100),timingStatus:clip(v.timingStatus,60),timingAlert:v.timingAlert===true,rootCauseSignals:compactRootCauseSignals(v.rootCauseSignals),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,reason:report?null:(jsonBuf?'report-json-parse-failed':'report-json-missing'),reportReadWaitMs:waitMs,reportParseError:clip(reportParseError,220),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",