91 lines
6.8 KiB
TypeScript
91 lines
6.8 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.3.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: '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: 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: 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: 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: 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]));
|
|
|
|
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;
|
|
return PROCESSING_VERSION_MANIFESTS.find(item => item.current && item.profileId === profileId);
|
|
}
|
|
|
|
export function resolvedCompatibilityKey(profileId: string | undefined, processedWith: ProcessingVersion): string | undefined {
|
|
return processedWith.compatibilityKey ?? processingManifest(profileId ?? processedWith.profileId, processedWith.profileVersion)?.compatibilityKey;
|
|
}
|
|
|
|
export function processingReadiness(profileId: string | undefined, processedWith: ProcessingVersion): ProcessingReadinessStatus {
|
|
const manifest = processingManifest(profileId ?? processedWith.profileId, processedWith.profileVersion);
|
|
const current = currentProcessingManifest(profileId ?? processedWith.profileId);
|
|
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';
|
|
}
|