mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-09-20 00:42:24 +02:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user