Assessor_2.0/src/domain/tool-profile-definition.ts

261 lines
10 KiB
TypeScript

import type { CanonicalFieldRole, CanonicalFindingStatus, CanonicalSeverity } from './canonical-model';
export type ProfileSignalScope = 'file-name' | 'raw-text' | 'table-name' | 'header' | 'cell-value';
export interface WeightedPattern {
pattern: RegExp;
contribution: number;
message: string;
}
export interface ProfileSourceSignal extends WeightedPattern {
scope: ProfileSignalScope;
}
export interface ProfileFieldRule {
role: CanonicalFieldRole;
headers: WeightedPattern[];
/** Conditions apply only within the current source table/candidate shape. */
whenHeadersPresent?: RegExp[];
whenHeadersAbsent?: RegExp[];
}
export interface ProfileValueMapping<T extends string> {
normalizedValue: T;
patterns: RegExp[];
}
export interface ProfileStatusFilter {
value: string;
label: string;
patterns: RegExp[];
}
export interface ProfileStatusDisplayRule {
label: string;
patterns: RegExp[];
}
export type ProcessingChangeClass = 'presentation-only' | 'compatible' | 'semantic-breaking';
/**
* Versionierter Verarbeitungsvertrag eines Werkzeugprofils.
*
* Die Profilversion beschreibt das gesamte Paket. Mapping, Normalisierung und
* Darstellung entwickeln sich dagegen unabhaengig. Der compatibilityKey ist
* die fail-closed-Grenze fuer Aggregation und Vergleich kanonischer Ergebnisse.
*/
export interface ToolProcessingContract {
mappingVersion: string;
normalizationVersion: string;
presentationVersion: string;
compatibilityKey: string;
changeClass: ProcessingChangeClass;
reimportRequired: boolean;
affectedDimensions: string[];
}
/** Every scanner must explicitly assess native overall-score support before fallback. */
export interface ToolSecurityScoringDefinition {
version: string;
native: {
kind: 'mapped';
/** Exact mapped report properties, never fuzzy matches against finding fields. */
properties: [string, ...string[]];
scope: 'overall';
scale: '0-100';
direction: 'higher-is-better' | 'lower-is-better';
/** Other native scopes that make this single contribution ambiguous. */
incompatibleProperties?: string[];
} | {
kind: 'unavailable';
/** Why the supported report formats cannot currently supply a usable native score. */
reason: string;
};
targets: string[];
benchmark?: string[];
resultPopulation: 'complete' | 'findings-only' | 'unknown';
}
/**
* Every scanner must state how its findings may be ordered against each other.
* Severity is not universal: CIS-CAT delivers none at all and PingCastle carries
* only Points. A basis is declared with its exact mapped properties, or declared
* unavailable with a reason. Never infer one from an arbitrary field named
* severity, level, score or rank, and never order across sources with different
* bases — see docs/REPORT_STRATEGY.md.
*/
export interface ToolPriorityBasisDefinition {
version: string;
basis: {
kind: 'severity';
/** Exact mapped properties carrying the native criticality. */
properties: [string, ...string[]];
} | {
kind: 'numeric';
/** Exact mapped properties behind the numeric rank value. */
properties: [string, ...string[]];
scale: string;
direction: 'higher-is-worse' | 'lower-is-worse';
} | {
kind: 'unavailable';
/** Why the supported report formats cannot supply a defensible ordering. */
reason: string;
};
/**
* Fields that resemble a basis but are not one. Recorded so a later change
* cannot quietly promote them, which would manufacture a criticality the
* source never stated.
*/
excludedProperties?: Array<{ property: string; reason: string }>;
}
/**
* Versioned contract for remediation comparisons. This is deliberately
* separate from score population: a complete score denominator does not prove
* that an omitted rule or object was re-tested successfully.
*/
interface AvailableToolTimeslotComparisonBase {
version: string;
kind: 'available';
scope: {
kind: string;
properties: [string, ...string[]];
};
observationTimeProperties: [string, ...string[]];
resultEvidence: 'explicit-results' | 'findings-only' | 'unknown';
/** `unsupported` keeps missing target occurrences unresolved. */
negativeEvidence: 'unsupported' | 'complete-explicit-negative';
diagnosticPolicy: 'fail-closed-by-impact';
}
export type ToolTimeslotComparisonDefinition = (AvailableToolTimeslotComparisonBase & {
granularity: 'scope' | 'aggregate';
}) | (AvailableToolTimeslotComparisonBase & {
granularity: 'asset';
object: { kind: string; namespace: string; source: 'affected-asset' };
}) | {
version: string;
kind: 'unavailable';
reason: string;
};
export interface ToolProfileDefinition {
schemaVersion: 1;
id: string;
name: string;
/** Further scan-run names the import assigns to this profile, for example scope-specific names. */
runNames?: string[];
version: string;
processing: ToolProcessingContract;
securityScoring: ToolSecurityScoringDefinition;
priorityBasis: ToolPriorityBasisDefinition;
timeslotComparison: ToolTimeslotComparisonDefinition;
fallback?: boolean;
sourceSignals: ProfileSourceSignal[];
tableRules: {
preferredNames: WeightedPattern[];
discouragedNames: WeightedPattern[];
};
fieldRules: ProfileFieldRule[];
normalization: {
severity: Array<ProfileValueMapping<CanonicalSeverity>>;
status: Array<ProfileValueMapping<CanonicalFindingStatus>>;
};
presentation: {
titleHeaders: RegExp[];
severityHeaders: RegExp[];
sortHeaders: RegExp[];
sortDirection: 'asc' | 'desc';
defaultStatusFilter: string;
statusFilters: ProfileStatusFilter[];
/** Geordnete Header-Prioritaet fuer den sichtbaren Ergebnisstatus. */
statusHeaders?: RegExp[];
/** Optionale, rein visuelle Beschriftung roher Werkzeugstatus. */
statusDisplayRules?: ProfileStatusDisplayRule[];
/**
* Header, deren Feldwert ein beschreibender Ergebnistext ist statt eines
* kurzen Status-Badges. Manche Werkzeuge (z. B. Maester) nennen diese
* Spalte schlicht "Result", waehrend andere (z. B. Purple Knight) unter
* demselben Namen nur ein kurzes Statuswort fuehren und den Text separat
* in "Result Message" ablegen. Ohne diese Angabe bleibt der Reader beim
* Purple-Knight-Verhalten und blendet ein Feld namens "Result" aus.
*/
resultMessageHeaders?: RegExp[];
/**
* Header, deren Feldwert als eigener benannter Abschnitt in das
* Results-Fenster gehoert - der Feldname wird zum Titel des Abschnitts.
*
* Gedacht fuer Inhalte, die der Bericht selbst nur aufgeklappt zeigt und
* die deshalb zu den Belegdaten zaehlen, nicht zur Lesespalte: CIS-CAT
* versteckt hinter "Show Rule Result XML" und "More" das Rule Result XML
* und die Rahmenwerk-Zuordnungen. Anders als `resultMessageHeaders` (ein
* einzelner Ergebnistext anstelle einer Ergebnisdatei) duerfen es mehrere
* Abschnitte sein; sie erscheinen in Feldreihenfolge. Der Reader blendet
* diese Felder aus, damit dieselbe Aussage nicht doppelt steht.
*/
resultSectionHeaders?: RegExp[];
/**
* Verknuepfte Belegtabellen benoetigen einen fachlichen Abschnittstitel,
* wenn ihr Tabellenname Bedeutung traegt und nicht nur die Quelle wiederholt.
*/
showResultSheetTitles?: boolean;
/** Deklarative Umbenennung fachlicher Belegtabellentitel. */
resultSheetTitleRules?: Array<{ pattern: RegExp; replacement: string }>;
/**
* Ergebnisabschnitte, deren Markdown-Listenpunkte als Titel der jeweils
* folgenden Belegtabelle desselben Markdown-Abschnitts dienen.
*/
resultSectionListSheetTitles?: RegExp[];
/** Zeileninhalte verknuepfter Belegtabellen, die visuell hervorgehoben werden. */
resultSheetHighlightedRowPatterns?: RegExp[];
/** Zeileninhalte, welche die erwartete Sollbedingung einer Regel beschreiben. */
resultSheetCriterionRowPatterns?: RegExp[];
/** Darstellung einer vom Quellbericht als ausgewertet markierten Belegzeile. */
resultSheetEvaluatedRowTone?: 'violation';
/**
* Explicit per-row status markers written by the source itself (for example a
* check mark in a status cell). A row is colored only when its cells prove exactly
* one of both states; nothing is inferred from other values.
*/
resultSheetRowStatusCells?: { match: RegExp[]; violation: RegExp[] };
/**
* First-read layout of the Finding Reader (user decision 2026-09-25): which retained
* source fields state the problem, its impact and the next step. Only declared
* fields are used; a missing field removes its line instead of inventing text.
*/
firstRead?: {
/** Concrete result statement of the scanner for this environment. */
resultHeaders?: RegExp[];
/** Explicit count of affected results. */
affectedHeaders?: RegExp[];
/** Count linked result-table rows when no explicit count exists. */
affectedFromResults?: boolean;
/** Count rows the source itself marks as passed or failed (`resultSheetRowStatusCells`). */
statusRowCounts?: boolean;
/** Show the source's criterion and evaluated rows as expected and found. */
criterionEvidence?: boolean;
impactHeaders?: RegExp[];
nextStepHeaders?: RegExp[];
/** Retained https documentation link offered as the next step when no remediation text exists. */
nextStepLinkHeaders?: RegExp[];
/**
* Native remediation subsection inside another retained field, used when no dedicated
* remediation field exists. The section runs from the native heading to the end of the field;
* the text before the heading stays in place as background.
*/
nextStepSection?: { headers: RegExp[]; heading: RegExp };
/**
* Native reference heading the scanner appends to its impact text. The impact row ends before it;
* the retained remainder is shown with the references.
*/
impactReferenceMarker?: RegExp;
/** Explanatory text shown below the next step. */
backgroundHeaders?: RegExp[];
/** Documentation and references shown after the background. */
referenceHeaders?: RegExp[];
};
};
comparisonKeys: CanonicalFieldRole[][];
}