Assessor_2.0/src/profiles/maester/maester-definition.ts

86 lines
5.0 KiB
TypeScript

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']]
};