Files
shannon/apps/worker/src/temporal/summary-mapper.ts
T
ezl-keygraph 0ab7c0b41b feat(preflight): add exploit-readiness probe and --validate-auth mode, and refresh suggested models (#476)
* feat(preflight): gate scans on an exploit-workload readiness probe

* feat: add --validate-auth to run authentication validation only

* feat: refuse reusing an auth-validation workspace for a scan

* chore: refresh suggested model IDs (Grok 4.7, OpenAI gpt-6-sol, Claude 5)

* fix(preflight): make the exploit-readiness probe trip the cyber safeguard reliably

* chore(preflight): update the exploit-readiness probe prompt

* feat: add --validate-model to run the preflight model checks only

* feat(cli): name cyber-access and app-login steps in the start loader

* feat(cli): refine start loader — skip app-login step when following, annotate preflight label

* feat(status): show Preflight and Cyber access verification rows for gated providers

* chore(preflight): suggest a fallback model in cyber-access remediation hints

* chore(preflight): drop env-var syntax from cyber-access fallback hints

* fix(preflight): separate finding heading from Target line in readiness probe

* refactor(preflight): rename exploit-readiness probe to cyber access verification

* fix(preflight): re-join finding heading with Target line in readiness probe

Reverts the heading/Target split from 22d84f2, gluing each finding's
heading back onto its Target line in the cyber-access probe's user
content.

* feat(validation): show a Checks summary in the validation log

* fix(cli): say a validation run failed, not that it could not start

* docs(ai-providers): replace broken Pi subscription link with /login steps

* feat(preflight): gate the openai-codex subscription on cyber access
2026-10-05 23:58:14 +05:30

89 lines
3.9 KiB
TypeScript

// Copyright (C) 2026 Keygraph, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version 3
// as published by the Free Software Foundation.
/**
* Maps PipelineState to WorkflowSummary for audit logging.
* Pure function with no side effects.
*/
import type { WorkflowSummary } from '../audit/workflow-logger.js';
import type { PipelineState } from './shared.js';
/**
* Maps PipelineState to WorkflowSummary.
*
* This function is deterministic (no Date.now() or I/O) so it can be
* safely imported into Temporal workflows. The caller must ensure
* state.summary is set before calling (via computeSummary).
*/
export function toWorkflowSummary(
state: PipelineState,
status: 'completed' | 'failed' | 'cancelled' | 'partial',
): WorkflowSummary {
// state.summary must be computed before calling this mapper
const summary = state.summary;
if (!summary) {
throw new Error('toWorkflowSummary: state.summary must be set before calling');
}
// The failure detail is one of the child workflow's fixed safe sentences, so it carries no
// provider, prompt, repository, or path content and travels with the stable code.
const agenticSastFailure = state.agenticSast.status === 'failed' ? state.agenticSast : undefined;
const agenticSastErrorCode = agenticSastFailure?.errorCode;
const agenticSastFailureMessage = agenticSastFailure?.error;
const agenticSastFailedStage = agenticSastFailure?.failedStageLabel;
// Both terminal Capella variants carry a warnings array; a disabled or still-running run has none.
const agenticSast = state.agenticSast;
const usageAccountingWarnings =
agenticSast.status === 'succeeded' || agenticSast.status === 'failed' ? agenticSast.warnings : [];
// Carry the terminal disposition so a successful (or reduced-coverage) run is visible in the summary,
// not only a failed one. Coverage is meaningful only on success.
const agenticSastCoverage = agenticSast.status === 'succeeded' ? agenticSast.coverage : undefined;
const endedAtMs = state.startTime + summary.totalDurationMs;
return {
status,
startedAtMs: state.startTime,
endedAtMs,
totalDurationMs: summary.totalDurationMs,
totalCostUsd: summary.totalCostUsd,
completedAgents: state.completedAgents,
skippedAgents: state.skippedAgents,
agentMetrics: Object.fromEntries(
Object.entries(state.agentMetrics).map(([name, metrics]) => [
name,
{ durationMs: metrics.durationMs, costUsd: metrics.costUsd },
]),
),
operationalMetrics: Object.fromEntries(
Object.entries(state.operationalMetrics).map(([name, metrics]) => [
name,
{ ...metrics, usageComplete: metrics.usageComplete !== false },
]),
),
// The stage wall-clocks the summary reads to report each group's real elapsed time; the priced
// metrics carry cost but no faithful duration (reconciliation stages record 0).
operationalStages: Object.fromEntries(
Object.entries(state.operationalStages).map(([key, stage]) => [
key,
{
status: stage.status,
...(stage.startedAt !== undefined && { startedAt: stage.startedAt }),
...(stage.durationMs !== undefined && { durationMs: stage.durationMs }),
},
]),
),
partialReasons: state.partialReasons,
usageAccountingComplete: summary.usageAccountingComplete,
usageAccountingWarnings: [...usageAccountingWarnings],
agenticSastStatus: agenticSast.status,
...(agenticSastCoverage !== undefined && { agenticSastCoverage }),
...(agenticSastFailedStage !== undefined && { agenticSastFailedStage }),
...(agenticSastFailureMessage !== undefined && { agenticSastFailureMessage }),
...(agenticSastErrorCode !== undefined && { agenticSastErrorCode }),
...(state.errorCode !== undefined && { errorCode: state.errorCode }),
};
}