Assessor_2.0/src/features/project-import/datahub-processing-readines...

89 lines
4.2 KiB
TypeScript

import type { ImportFileRecord } from '../../domain/canonical-model';
import type { GpoBackupStatusRecord } from '../../api/assessor-api-contract';
import { currentProcessingManifest, processingReadiness, type ProcessingReadinessStatus } from '../../domain/processing-compatibility';
export type DatahubClientProcessingState = 'ready' | 'processing' | 'update' | 'error' | 'no-data';
export interface DatahubClientProcessingSummary {
state: DatahubClientProcessingState;
label: string;
}
const UPDATE_STATES = new Set(['reimport-recommended']);
const ERROR_STATES = new Set(['incompatible', 'unknown']);
const ATTENTION_STATES = new Set([...UPDATE_STATES, ...ERROR_STATES]);
export interface DatahubScanProcessingSummary {
state: 'ready' | 'update' | 'error';
statuses: ProcessingReadinessStatus[];
explanation: string;
}
export function summarizeScanProcessingReadiness(files: ImportFileRecord[]): DatahubScanProcessingSummary {
const statuses = [...new Set(files.map(file => processingReadiness(
file.detectedTool.id || file.processedWith.profileId,
file.processedWith
)))];
if (!statuses.some(status => ATTENTION_STATES.has(status))) {
return { state: 'ready', statuses, explanation: 'The scan uses a current or compatible ASSESSOR processing profile.' };
}
const hasError = statuses.some(status => ERROR_STATES.has(status));
const relevantStates = hasError ? ERROR_STATES : UPDATE_STATES;
const representative = files.find(file => relevantStates.has(processingReadiness(
file.detectedTool.id || file.processedWith.profileId,
file.processedWith
))) ?? files[0];
const current = representative ? currentProcessingManifest(representative.detectedTool.id || representative.processedWith.profileId) : undefined;
if (statuses.includes('unknown')) {
return { state: 'error', statuses, explanation: 'The stored ASSESSOR processing profile is unknown and cannot be assessed safely.' };
}
if (statuses.includes('incompatible')) {
return { state: 'error', statuses, explanation: `The stored ASSESSOR processing profile is incompatible with the current ASSESSOR profile ${current?.profileVersion ?? 'unknown'}.` };
}
const contextCapture = current?.affectedDimensions.includes('environment-context')
? ' The newer ASSESSOR profile captures additional environment context for the assessment.'
: '';
return {
state: 'update',
statuses,
explanation: `ASSESSOR profile update: ${representative?.processedWith.profileVersion ?? 'unknown'} → ${current?.profileVersion ?? 'unknown'}. Reimporting applies the newer ASSESSOR processing profile; the ${representative?.detectedTool.name ?? 'scanner'} report itself has not changed.${contextCapture}`
};
}
/**
* Summarises all active scanner runs represented by the client card. Every
* timeslot must use a comparable ASSESSOR processing contract before the
* client can be considered fully ready for lifecycle comparison.
*/
export function summarizeClientProcessingReadiness(
files: ImportFileRecord[],
activeScanRunIds: readonly string[],
gpoBackups: readonly Pick<GpoBackupStatusRecord, 'state'>[] = []
): DatahubClientProcessingSummary {
if (gpoBackups.some(record => record.state === 'dead-letter' || record.state === 'recovery-required')) {
return { state: 'error', label: 'GPO processing failed or requires recovery in a client timeslot.' };
}
const runIds = new Set(activeScanRunIds);
const statuses = files
.filter(file => runIds.has(file.scanRunId))
.map(file => processingReadiness(file.detectedTool.id || file.processedWith.profileId, file.processedWith));
if (statuses.some(status => ERROR_STATES.has(status))) {
return { state: 'error', label: 'Processing error requires attention in a client timeslot.' };
}
if (statuses.some(status => UPDATE_STATES.has(status))) {
return { state: 'update', label: 'ASSESSOR processing profile update available in a client timeslot.' };
}
if (gpoBackups.some(record => record.state === 'queued' || record.state === 'running')) {
return { state: 'processing', label: 'GPO processing in progress.' };
}
return statuses.length || gpoBackups.length
? { state: 'ready', label: 'Processing ready across all client timeslots.' }
: { state: 'no-data', label: 'No processing status available.' };
}