feat(config)!: replace vuln_classes with agentic_sast

Wire Agentic SAST and reconciliation into the main pipeline, persist their durable state, and add the Miscellaneous finding and exploitation lane.

Make scan completion, cancellation, partial outcomes, resume identity, and report recovery use the integrated final workflow contract. Introduce the atomic finalization, ordering, renumbering, compaction, and output services that workflow calls. Keep completed Miscellaneous work and report drafts idempotent across resume, preserve public main's default-on exploit SARIF behavior, and describe stage-fallback candidates without claiming they were exported.

BREAKING CHANGE: `vuln_classes` has been removed. Configs containing it now fail validation, and all five core pentest classes run on every scan.

Workspaces created by Shannon 2.x cannot be resumed. Finish or discard in-flight scans before upgrading, then start a new workspace name.
This commit is contained in:
ajmallesh
2026-08-26 19:55:03 -07:00
parent c33132b0ab
commit 98c66e051d
57 changed files with 7628 additions and 1201 deletions
+30 -4
View File
@@ -9,7 +9,7 @@
import { setTimeout as sleep } from 'node:timers/promises';
import { Client, Connection, WorkflowFailedError, WorkflowNotFoundError } from '@temporalio/client';
import { ACTIVITY_TO_AGENT, type PipelineState } from './scan/pipeline.js';
import { ACTIVITY_TO_PROGRESS, type PipelineState } from './scan/pipeline.js';
const ADDRESS = '127.0.0.1:7233';
const NAMESPACE = 'default';
@@ -20,11 +20,30 @@ const TERMINAL_STATUSES: ReadonlySet<string> = new Set(['COMPLETED', 'FAILED', '
export interface RunningAgent {
readonly agent: string;
readonly label: string;
/** 'agent' rows join the static pipeline tree; 'operation' rows feed the background-work phase. */
readonly kind: 'agent' | 'operation';
/** Set when a persisted parent stage owns this row; the label then reads as that stage's step. */
readonly parentKey?: string;
readonly attempt: number;
readonly startedAt?: number;
readonly lastFailure?: string;
}
/**
* The CLI's activity mirror does not know an activity type the running scan is using, so the
* progress tree cannot be rendered completely. Distinct from a Temporal connection failure.
*/
export class ActivityMirrorError extends Error {
override name = 'ActivityMirrorError' as const;
constructor(activityType: string) {
super(
`This version of the Shannon command line does not recognise part of the running scan\n(${activityType}). Update Shannon, or watch the scan with: shannon logs <workspace>`,
);
}
}
/** Convert a proto ITimestamp (seconds is a Long) to epoch millis. */
function timestampMs(
ts: { seconds?: { toString(): string } | number | null; nanos?: number | null } | null,
@@ -66,12 +85,19 @@ export async function describeScan(workflowId: string): Promise<ScanDescription
const runningAgents: RunningAgent[] = [];
for (const pending of desc.raw.pendingActivities ?? []) {
const agent = ACTIVITY_TO_AGENT[pending.activityType?.name ?? ''];
if (!agent) continue;
const activityType = pending.activityType?.name ?? '';
const progress = ACTIVITY_TO_PROGRESS[activityType];
// Fail closed: skipping an unknown activity would render a quietly incomplete tree.
if (!progress) {
throw new ActivityMirrorError(activityType || 'unknown activity');
}
const lastFailure = pending.lastFailure?.message;
const startedAt = timestampMs(pending.scheduledTime ?? pending.lastStartedTime ?? null);
runningAgents.push({
agent,
agent: progress.key,
label: progress.label,
kind: progress.kind,
...(progress.parentKey !== undefined ? { parentKey: progress.parentKey } : {}),
attempt: pending.attempt ?? 1,
...(startedAt !== undefined ? { startedAt } : {}),
...(lastFailure ? { lastFailure } : {}),