107 lines
9.6 KiB
TypeScript
107 lines
9.6 KiB
TypeScript
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<string, ImportFileRecord[]>();
|
|
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';
|
|
}
|