import type { ToolProfileDefinition } from '../../domain/tool-profile-definition'; import { commonSeverityMappings, commonStatusMappings } from '../common-normalization'; import { genericFieldRules } from '../generic/generic-field-rules'; export const maesterDefinition: ToolProfileDefinition = { schemaVersion: 1, id: 'maester', securityScoring: { version: '1.0.0', native: { kind: 'unavailable', reason: 'Supported Maester reports expose result counts, without a native numeric overall score.' }, targets: ['entra.tenant_id'], resultPopulation: 'complete' }, priorityBasis: { version: '1.0.0', // Native severity per test. `Result` beside `Status` stays evidence text and // never becomes a rank. basis: { kind: 'severity', properties: ['test.severity'] }, excludedProperties: [ { property: 'test.result', reason: 'Descriptive result text, not a criticality.' } ] }, timeslotComparison: { version: '1.0.0', kind: 'available', granularity: 'scope', scope: { kind: 'entra-tenant', properties: ['entra.tenant_id'] }, observationTimeProperties: ['report.generated_at'], resultEvidence: 'explicit-results', negativeEvidence: 'unsupported', diagnosticPolicy: 'fail-closed-by-impact' }, name: 'Maester', version: '1.1.0', processing: { 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'] }, sourceSignals: [ { scope: 'file-name', pattern: /maester|test[\s_.-]*results?/i, contribution: 0.25, message: 'Der Dateiname entspricht einem Maester-Testexport.' }, { scope: 'raw-text', pattern: /maester\s+security\s+tests/i, contribution: 0.55, message: 'Der Dateiinhalt enthält „Maester Security Tests“.' }, { scope: 'raw-text', pattern: /maester\.dev/i, contribution: 0.35, message: 'Der Dateiinhalt enthält eine Maester-Referenz.' }, { scope: 'header', pattern: /^testscriptfile$/i, contribution: 0.24, message: 'Die Kopfzeile „TestScriptFile“ wurde gefunden.' }, { scope: 'header', pattern: /^resultdetail$/i, contribution: 0.16, message: 'Die Kopfzeile „ResultDetail“ wurde gefunden.' }, { scope: 'header', pattern: /^testskipped$/i, contribution: 0.13, message: 'Die Kopfzeile „TestSkipped“ wurde gefunden.' }, { scope: 'cell-value', pattern: /^MT\.\d+/i, contribution: 0.3, message: 'Ein Maester-Testbezeichner wurde gefunden.' } ], tableRules: { preferredNames: [{ pattern: /test|result|finding/i, contribution: 0.7, message: 'Maester-Testresultate.' }], discouragedNames: [] }, fieldRules: [ ...genericFieldRules.filter(rule => rule.role !== 'status'), { role: 'status', headers: [{ pattern: /^(?:status|result|outcome|evaluation|zustand)$/i, contribution: 0.96, message: 'Native Maester status field.' }], whenHeadersAbsent: [/^status$/i] }, { role: 'status', headers: [{ pattern: /^(?:status|outcome|evaluation|zustand)$/i, contribution: 0.96, message: 'Separate Maester status column.' }], whenHeadersPresent: [/^status$/i] }, { role: 'evidence', headers: [{ pattern: /^result$/i, contribution: 1, message: 'Maester result text next to a separate Status column.' }], whenHeadersPresent: [/^status$/i] } ], normalization: { severity: commonSeverityMappings, status: commonStatusMappings }, presentation: { titleHeaders: [/^title$/i], severityHeaders: [/^severity$/i], sortHeaders: [/^severity$/i], sortDirection: 'desc', // Der Maester-Excel-Export legt die Testergebnismeldung in eine Spalte // "Result", waehrend der Status separat in "Status" steht. Ohne diese // Angabe wuerde der Reader "Result" als reines Status-Badge einstufen // (Purple-Knight-Konvention) und den einzigen Beleg fuer das Finding // vollstaendig ausblenden. resultMessageHeaders: [/^resultdetail$/i, /^result$/i], statusHeaders: [/^status$/i, /^result$/i], // Standardansicht zeigt Abweichungen, nicht bestandene Pruefungen. // `deviation` statt `failed`, weil Maester neben Failed auch `Investigate` // meldet: eine Pruefung, die nicht automatisch entscheidbar war und fachlich // beurteilt werden muss. Mit `failed` als Vorgabe fielen diese Funde aus der // Standardansicht heraus. Die Filter `failed` und `passed` bleiben unveraendert // erhalten und trennen Pass und Fail weiterhin exakt. defaultStatusFilter: 'deviation', statusFilters: [ { value: 'all', label: 'All Results', patterns: [] }, { value: 'deviation', label: 'Deviation', patterns: [/^fail(?:ed)?$/i, /^investigate$/i] }, { value: 'failed', label: 'Failed', patterns: [/^fail(?:ed)?$/i] }, { value: 'investigate', label: 'Investigate', patterns: [/^investigate$/i] }, { value: 'passed', label: 'Passed', patterns: [/^pass(?:ed)?$/i] }, { value: 'skipped', label: 'Skipped', patterns: [/^skipped$/i, /^notrun$/i] } ] }, comparisonKeys: [['rule-id', 'asset'], ['rule-id']] };