import type { ImportFileRecord, ProcessingVersion } from './canonical-model'; import type { ProcessingChangeClass, ToolProcessingContract } from './tool-profile-definition'; export type ProcessingReadinessStatus = | 'current' | 'compatible-older' | 'reimport-recommended' | 'incompatible' | 'unknown'; /** Globaler, in der Oberflaeche sichtbarer Stand des ASSESSOR-Verarbeitungsvertrags. */ export const CURRENT_ASSESSOR_PROCESSING_VERSION = '1.4.0'; export interface ProcessingVersionManifest extends ToolProcessingContract { profileId: string; profileVersion: string; current: boolean; } /** * Historische und aktuelle Verarbeitungsvertraege. Diese Liste ist bewusst * append-only: bereits persistierte Provenienz darf niemals still umgedeutet * oder geloescht werden. */ export const PROCESSING_VERSION_MANIFESTS: readonly ProcessingVersionManifest[] = [ { profileId: 'generic', profileVersion: '1.0.0', mappingVersion: '1.0.0', normalizationVersion: '1.0.0', presentationVersion: '1.0.0', compatibilityKey: 'generic-canonical-v1', changeClass: 'compatible', reimportRequired: false, affectedDimensions: [], current: true }, { profileId: 'defender-for-iot-vulnerabilities', profileVersion: '1.0.0', mappingVersion: '1.0.0', normalizationVersion: '1.0.0', presentationVersion: '1.0.0', compatibilityKey: 'defender-for-iot-vulnerabilities-v1', changeClass: 'compatible', reimportRequired: false, affectedDimensions: [], current: false }, { profileId: 'defender-for-iot-vulnerabilities', profileVersion: '1.1.0', mappingVersion: '1.0.0', normalizationVersion: '1.1.0', presentationVersion: '1.0.0', compatibilityKey: 'defender-for-iot-vulnerabilities-v2', changeClass: 'semantic-breaking', reimportRequired: true, affectedDimensions: ['outcome', 'resultType', 'deviation'], current: true }, { profileId: 'purple-knight', profileVersion: '1.0.0', mappingVersion: '1.0.0', normalizationVersion: '1.0.0', presentationVersion: '1.0.0', compatibilityKey: 'purple-knight-canonical-v1', changeClass: 'semantic-breaking', reimportRequired: true, affectedDimensions: ['outcome', 'isIoe'], current: false }, { profileId: 'purple-knight', profileVersion: '1.1.0', mappingVersion: '1.1.0', normalizationVersion: '1.1.0', presentationVersion: '1.1.0', compatibilityKey: 'purple-knight-canonical-v1.1', changeClass: 'semantic-breaking', reimportRequired: true, affectedDimensions: ['outcome', 'isIoe'], current: false }, { profileId: 'purple-knight', profileVersion: '1.2.0', mappingVersion: '1.1.0', normalizationVersion: '1.1.0', presentationVersion: '1.1.0', compatibilityKey: 'purple-knight-canonical-v1.1', changeClass: 'compatible', reimportRequired: true, affectedDimensions: ['environment-context'], current: false }, { profileId: 'purple-knight', profileVersion: '1.3.0', mappingVersion: '1.1.0', normalizationVersion: '1.1.0', presentationVersion: '1.1.0', compatibilityKey: 'purple-knight-canonical-v1.1', changeClass: 'compatible', reimportRequired: true, affectedDimensions: ['environment-context', 'evidence', 'import-diagnostics'], current: true }, { profileId: 'pingcastle', profileVersion: '1.0.0', mappingVersion: '1.0.0', normalizationVersion: '1.0.0', presentationVersion: '1.0.0', compatibilityKey: 'pingcastle-canonical-v1', changeClass: 'semantic-breaking', reimportRequired: true, affectedDimensions: ['outcome', 'isIoe'], current: false }, { profileId: 'pingcastle', profileVersion: '1.1.0', mappingVersion: '1.1.0', normalizationVersion: '1.1.0', presentationVersion: '1.1.0', compatibilityKey: 'pingcastle-canonical-v1.1', changeClass: 'semantic-breaking', reimportRequired: false, affectedDimensions: ['outcome', 'isIoe'], current: false }, { profileId: 'pingcastle', profileVersion: '1.2.0', mappingVersion: '1.1.0', normalizationVersion: '1.1.0', presentationVersion: '1.2.0', compatibilityKey: 'pingcastle-canonical-v1.1', changeClass: 'presentation-only', reimportRequired: true, affectedDimensions: ['evidence', 'presentation'], current: false }, { profileId: 'pingcastle', profileVersion: '1.3.0', mappingVersion: '1.1.0', normalizationVersion: '1.1.0', presentationVersion: '1.2.0', compatibilityKey: 'pingcastle-canonical-v1.1', changeClass: 'compatible', reimportRequired: true, affectedDimensions: ['environment-context', 'evidence', 'presentation'], current: false }, { profileId: 'pingcastle', profileVersion: '1.4.0', mappingVersion: '1.2.0', normalizationVersion: '1.1.0', presentationVersion: '1.2.0', compatibilityKey: 'pingcastle-canonical-v1.1', changeClass: 'compatible', reimportRequired: true, affectedDimensions: ['environment-context'], current: false }, { profileId: 'pingcastle', profileVersion: '1.5.0', mappingVersion: '1.2.0', normalizationVersion: '1.1.0', presentationVersion: '1.2.0', compatibilityKey: 'pingcastle-canonical-v1.1', changeClass: 'compatible', reimportRequired: true, affectedDimensions: ['environment-context', 'evidence', 'import-diagnostics'], current: true }, { profileId: 'cis-cat', profileVersion: '1.2.0', mappingVersion: '1.2.0', normalizationVersion: '1.2.0', presentationVersion: '1.2.0', compatibilityKey: 'cis-cat-canonical-v1', changeClass: 'compatible', reimportRequired: true, affectedDimensions: ['evidence'], current: false }, { profileId: 'cis-cat', profileVersion: '1.3.0', mappingVersion: '1.3.0', normalizationVersion: '1.3.0', presentationVersion: '1.3.0', compatibilityKey: 'cis-cat-canonical-v1', changeClass: 'compatible', reimportRequired: false, affectedDimensions: ['evidence', 'presentation'], current: false }, { profileId: 'cis-cat', profileVersion: '1.4.0', mappingVersion: '1.3.0', normalizationVersion: '1.3.0', presentationVersion: '1.3.0', compatibilityKey: 'cis-cat-canonical-v1', changeClass: 'compatible', reimportRequired: true, affectedDimensions: ['environment-context'], current: true }, { profileId: 'maester', profileVersion: '1.0.0', mappingVersion: '1.0.0', normalizationVersion: '1.0.0', presentationVersion: '1.0.0', compatibilityKey: 'maester-canonical-v1', changeClass: 'compatible', reimportRequired: false, affectedDimensions: [], current: false }, { profileId: 'maester', profileVersion: '1.1.0', mappingVersion: '1.1.0', normalizationVersion: '1.0.0', presentationVersion: '1.1.0', compatibilityKey: 'maester-canonical-v1', changeClass: 'compatible', reimportRequired: true, affectedDimensions: ['evidence', 'environment-context', 'presentation'], current: true } ] as const; const manifestKey = (profileId: string, profileVersion: string) => `${profileId.trim().toLocaleLowerCase('en-US')}@${profileVersion.trim()}`; const manifestLookup = new Map(PROCESSING_VERSION_MANIFESTS.map(item => [manifestKey(item.profileId, item.profileVersion), item])); function recordedProfileId(profileId: string | undefined, processedWith: ProcessingVersion): string | undefined { return processedWith.profileId?.trim() || profileId?.trim(); } export function processingManifest(profileId: string | undefined, profileVersion: string | undefined): ProcessingVersionManifest | undefined { if (!profileId || !profileVersion) return undefined; return manifestLookup.get(manifestKey(profileId, profileVersion)); } export function currentProcessingManifest(profileId: string | undefined): ProcessingVersionManifest | undefined { if (!profileId) return undefined; const normalized = profileId.trim().toLocaleLowerCase('en-US'); return PROCESSING_VERSION_MANIFESTS.find(item => item.current && item.profileId.toLocaleLowerCase('en-US') === normalized); } export function resolvedCompatibilityKey(profileId: string | undefined, processedWith: ProcessingVersion): string | undefined { return processedWith.compatibilityKey ?? processingManifest(recordedProfileId(profileId, processedWith), processedWith.profileVersion)?.compatibilityKey; } export function processingReadiness(profileId: string | undefined, processedWith: ProcessingVersion): ProcessingReadinessStatus { const effectiveProfileId = recordedProfileId(profileId, processedWith); const manifest = processingManifest(effectiveProfileId, processedWith.profileVersion); const current = currentProcessingManifest(effectiveProfileId); if (!manifest || !current) return 'unknown'; const recordedKey = processedWith.compatibilityKey ?? manifest.compatibilityKey; if (recordedKey !== current.compatibilityKey) return 'incompatible'; if (manifest.current) return 'current'; if (manifest.reimportRequired || current.reimportRequired) return 'reimport-recommended'; return 'compatible-older'; } export interface ProcessingScopeIssue { toolId: string; code: 'unknown-processing-contract' | 'incompatible-processing-contracts'; compatibilityKeys: string[]; } export function processingScopeIssues(files: ImportFileRecord[]): ProcessingScopeIssue[] { const byTool = new Map(); files.forEach(file => { const toolId = file.detectedTool.id || file.processedWith.profileId; byTool.set(toolId, [...(byTool.get(toolId) ?? []), file]); }); return [...byTool.entries()].flatMap(([toolId, entries]): ProcessingScopeIssue[] => { const keys = entries.map(file => resolvedCompatibilityKey(toolId, file.processedWith)); if (keys.some(key => !key)) return [{ toolId, code: 'unknown-processing-contract', compatibilityKeys: [...new Set(keys.filter((key): key is string => Boolean(key)))] }]; const unique = [...new Set(keys as string[])]; return unique.length > 1 ? [{ toolId, code: 'incompatible-processing-contracts', compatibilityKeys: unique }] : []; }); } export function changeClassLabel(value: ProcessingChangeClass | undefined): string { if (value === 'presentation-only') return 'Presentation only'; if (value === 'semantic-breaking') return 'Semantic change'; return value === 'compatible' ? 'Compatible' : 'Unknown'; }