fix: attribute a reconciliation failure to exploitation only

- Stop marking a class's vulnerability-analysis agent failed when that agent
  succeeded and only reconciliation failed; the status tree now renders the
  analysis row completed and the exploitation row failed
- Consume the worker's failedReconciliations signal in the CLI, which the
  mirrored PipelineState already declared but never read
- Correct the class_reconciliation_failed message, which claimed the class's
  analysis results were still in the report when the class is excluded from it
This commit is contained in:
ajmallesh
2026-08-30 18:51:23 -07:00
parent ddfd026f93
commit e9cf782081
3 changed files with 23 additions and 7 deletions
+21 -5
View File
@@ -66,8 +66,25 @@ export function isTerminal(status: string): boolean {
return status !== 'RUNNING' && status !== 'UNSPECIFIED';
}
/**
* Whether the class-level failure recorded for this agent's class applies to this agent.
*
* A class failure is recorded against the class as a whole, so it matches both of that class's
* agents. A reconciliation failure, though, happens only after the analysis agent has already
* succeeded, so it belongs to the exploitation lane: attributing it to the analysis row as well
* would report an agent that completed as failed.
*/
function classFailureApplies(name: string, state: PipelineState | null): boolean {
if (!state) return false;
const vulnClass = agentClass(name);
if (!state.failedPipelines.some((f) => f.vulnType === vulnClass)) return false;
const reconciliationFailed = (state.failedReconciliations ?? []).some((r) => r.vulnerabilityClass === vulnClass);
const isAnalysisAgent = name.endsWith('-vuln');
return !(reconciliationFailed && isAnalysisAgent);
}
function isFailedAgent(name: string, state: PipelineState | null): boolean {
return !!state && (state.failedAgent === name || state.failedPipelines.some((f) => f.vulnType === agentClass(name)));
return !!state && (state.failedAgent === name || classFailureApplies(name, state));
}
/** An agent has entered play once it is running, has metrics, or has failed. */
@@ -91,14 +108,13 @@ function agentState(name: string, state: PipelineState | null, running: Set<stri
}
/**
* Only `failed`'s presence is used here, never its `.error` text: that string is the
* worker's raw error for the failed class, not vetted for display, so it is reduced to
* Only the presence of a class failure is used here, never its `.error` text: that string is
* the worker's raw error for the failed class, not vetted for display, so it is reduced to
* a boolean before reaching safeFailureDetail's fixed sentence.
*/
function agentError(name: string, state: PipelineState | null, byAgent: Map<string, RunningAgent>): string | undefined {
const failed = state?.failedPipelines.find((f) => f.vulnType === agentClass(name));
const hasFailure =
failed !== undefined || byAgent.get(name)?.lastFailure !== undefined || state?.failedAgent === name;
classFailureApplies(name, state) || byAgent.get(name)?.lastFailure !== undefined || state?.failedAgent === name;
return safeFailureDetail(hasFailure);
}
+1 -1
View File
@@ -141,7 +141,7 @@ function reasonMessage(reason: PartialReasonView): string | undefined {
case 'class_reconciliation_failed':
return className === undefined
? undefined
: `${className} findings could not be grouped into test cases, so that class was not exploited. Its analysis results are still in the report.`;
: `${className} findings could not be grouped into test cases, so that class was not exploited and its findings are not in the report.`;
case 'report_renumber_failed':
return className === undefined
? undefined
+1 -1
View File
@@ -228,7 +228,7 @@ export const PARTIAL_REASON_SAFE_MESSAGES: Readonly<Record<PartialReasonCode, st
class_pipeline_failed:
'{Class} could not be fully assessed. The other classes completed. Re-running this workspace retries only the part that failed.',
class_reconciliation_failed:
'{Class} findings could not be grouped into test cases, so that class was not exploited. Its analysis results are still in the report.',
'{Class} findings could not be grouped into test cases, so that class was not exploited and its findings are not in the report.',
report_renumber_failed:
'{Class} findings kept their working reference numbers, so numbering in the report may have gaps. The findings themselves are complete.',
report_compaction_failed: