fix: surface sentinel analyzer findings

This commit is contained in:
Codex
2026-07-01 09:05:44 +00:00
parent 6f70f162fa
commit c608746910
3 changed files with 232 additions and 20 deletions
@@ -318,10 +318,14 @@ export function runSentinelQuickVerify(state: SentinelCicdState, reason: string,
const controlFindings = quickVerifyControlFindings(null, promptIndex, turnSummary, traceFrame);
const artifactSummaryRecord = record(artifactSummary);
const artifactFindings = Array.isArray(artifactSummaryRecord.findings) ? artifactSummaryRecord.findings.map(record) : [];
const visibilityFindings = quickVerifyAnalysisVisibilityFindings(analysis, artifactSummary);
const cleanupStep = stopQuickVerifyObserver(state, observerId, "observe-stop-after-terminal");
const cleanupFindings = quickVerifyCleanupFindings(cleanupStep);
const findings = mergeFindingRecords(
mergeFindingRecords(artifactFindings, controlFindings),
mergeFindingRecords(
mergeFindingRecords(artifactFindings, controlFindings),
visibilityFindings,
),
cleanupFindings,
);
const blockingFindings = findings.filter(isQuickVerifyBlockingFinding);
@@ -352,7 +356,7 @@ export function runSentinelQuickVerify(state: SentinelCicdState, reason: string,
steps: [...steps, cleanupStep],
analysis: artifactSummary,
views: {
summary: { renderedText: renderQuickVerifySummary({ runId, scenarioId, observerId, artifactSummary, steps, publicOrigin: stringAt(state.publicExposure, "publicBaseUrl") }) },
summary: { renderedText: renderQuickVerifySummary({ runId, scenarioId, observerId, artifactSummary, findings, findingCount: findings.length, steps, publicOrigin: stringAt(state.publicExposure, "publicBaseUrl") }) },
"auth-session-switch-summary": { renderedText: renderAuthSessionSwitchQuickVerifySummary({ runId, scenarioId, observerId, artifactSummary, steps, findings, accountEnv: accountEnv.summary, publicOrigin: stringAt(state.publicExposure, "publicBaseUrl") }) },
"turn-summary": { renderedText: typeof turnSummary.renderedText === "string" ? turnSummary.renderedText : null, ok: turnSummary.ok },
"trace-frame": { renderedText: typeof traceFrame.renderedText === "string" ? traceFrame.renderedText : null, ok: traceFrame.ok },
@@ -513,7 +517,11 @@ function finalizeQuickVerifyFailure(state: SentinelCicdState, input: {
const controlFindings = quickVerifyControlFindings(input.failure, input.promptIndex, turnSummary, traceFrame);
const artifactSummaryRecord = record(artifactSummary);
const artifactFindings = Array.isArray(artifactSummaryRecord.findings) ? artifactSummaryRecord.findings.map(record) : [];
const findings = mergeFindingRecords(artifactFindings, controlFindings);
const visibilityFindings = quickVerifyAnalysisVisibilityFindings(analysis, artifactSummary);
const findings = mergeFindingRecords(
mergeFindingRecords(artifactFindings, controlFindings),
visibilityFindings,
);
const blockingFindings = findings.filter(isQuickVerifyBlockingFinding);
const recoveredWaitFailure = durableBusinessTurn
&& isRecoverableQuickVerifyWaitFailure(input.failure)
@@ -539,7 +547,7 @@ function finalizeQuickVerifyFailure(state: SentinelCicdState, input: {
steps: [...input.steps, ...cleanupSteps],
analysis: artifactSummary,
views: {
summary: { renderedText: renderQuickVerifySummary({ runId: input.runId, scenarioId: input.scenarioId, observerId: input.observerId, artifactSummary, steps: input.steps, publicOrigin: stringAt(state.publicExposure, "publicBaseUrl") }) },
summary: { renderedText: renderQuickVerifySummary({ runId: input.runId, scenarioId: input.scenarioId, observerId: input.observerId, artifactSummary, findings, findingCount: findings.length, steps: input.steps, publicOrigin: stringAt(state.publicExposure, "publicBaseUrl") }) },
"auth-session-switch-summary": { renderedText: renderAuthSessionSwitchQuickVerifySummary({ runId: input.runId, scenarioId: input.scenarioId, observerId: input.observerId, artifactSummary, steps: input.steps, findings, publicOrigin: stringAt(state.publicExposure, "publicBaseUrl") }) },
"turn-summary": { renderedText: typeof turnSummary.renderedText === "string" ? turnSummary.renderedText : null, ok: turnSummary.ok },
"trace-frame": { renderedText: typeof traceFrame.renderedText === "string" ? traceFrame.renderedText : null, ok: traceFrame.ok },
@@ -580,6 +588,26 @@ function quickVerifyCleanupFindings(cleanupStep: Record<string, unknown>): Recor
}];
}
function quickVerifyAnalysisVisibilityFindings(analysis: ChildCliResult, artifactSummary: Record<string, unknown>): Record<string, unknown>[] {
if (analysis.ok !== true) return [];
if (stringAtNullable(artifactSummary, "reportJsonSha256") !== null) return [];
const result = record(analysis.result);
const stdout = stringAtNullable(result, "stdoutPreview") ?? stringAtNullable(result, "stdoutTail");
const reportPathMatch = stdout === null ? null : stdout.match(/analysis\/report\.(?:json|md)/u);
return [{
id: "quick-verify-analysis-summary-unreadable",
severity: "red",
count: 1,
summary: "quick verify analyze exited successfully, but sentinel could not read analysis/report.json before recording the run.",
rootCause: "The report index would otherwise contain only control blockers such as WBC-003 and hide analyzer red/amber findings.",
rootCauseStatus: "confirmed",
rootCauseConfidence: "high",
evidenceSummary: `analyze ok=true reportJsonSha256=missing stdoutMentionsReport=${reportPathMatch === null ? "false" : "true"}`,
nextAction: "Read stateDir/analysis/report.json again or rehydrate the report from stateDir before trusting WBC-003 as the only visible finding.",
valuesRedacted: true,
}];
}
function callSentinelService(state: SentinelCicdState, method: "GET" | "POST", pathWithQuery: string, body: Record<string, unknown> | null, timeoutSeconds: number): Record<string, unknown> {
const namespace = stringAt(state.runtime, "namespace");
const serviceName = stringAt(state.runtime, "serviceName");
@@ -1540,6 +1568,7 @@ function isQuickVerifyBlockingFinding(item: Record<string, unknown>): boolean {
if (id === "observer-command-failed") return observerCommandFailureBlocks(item);
return [
"quick-verify-no-business-turn",
"quick-verify-analysis-summary-unreadable",
"quick-verify-command-sequence-failed",
"quick-verify-observer-start-failed",
"quick-verify-account-secret-missing",
@@ -1798,12 +1827,17 @@ function compactCommandWithTail(result: CommandResult): CompactCommandResult & {
function renderQuickVerifySummary(input: Record<string, unknown>): string {
const artifact = record(input.artifactSummary);
const findings = Array.isArray(artifact.findings) ? artifact.findings.map(record).slice(0, 8) : [];
const findings = Array.isArray(input.findings)
? input.findings.map(record).slice(0, 8)
: Array.isArray(artifact.findings)
? artifact.findings.map(record).slice(0, 8)
: [];
const findingCount = numberAtNullable(input, "findingCount") ?? numberAtNullable(artifact, "findingCount") ?? findings.length;
return [
"Web Probe Sentinel Quick Verify",
"=======================================================",
`run=${input.runId ?? "-"} scenario=${input.scenarioId ?? "-"} observer=${input.observerId ?? "-"}`,
`report=${artifact.reportJsonSha256 ?? "-"} artifacts=${artifact.artifactCount ?? "-"} findings=${artifact.findingCount ?? findings.length}`,
`report=${artifact.reportJsonSha256 ?? "-"} artifacts=${artifact.artifactCount ?? "-"} findings=${findingCount}`,
`publicOrigin=${input.publicOrigin ?? "-"}`,
"",
"Findings",