Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
218 lines
11 KiB
TypeScript
218 lines
11 KiB
TypeScript
import c5 from "../../seed/scoping/c5-controls.json";
|
||
|
||
/**
|
||
* Reifegrad-Engine (Story A7-2) — regelbasierte, nachvollziehbare Herleitung eines
|
||
* Reifegrad-Vorschlags 0–3 je Control aus dem Belegstatus (C5 §2), der Zielreifegrad
|
||
* (C5 §3) und die „offenen Punkte" (C5 §4). Reine, seiteneffektfreie Logik; die
|
||
* Belegstände werden vom Aufrufer aus Richtlinien-/Verfahrensdokumenten, Inventar und
|
||
* Risikoregister abgeleitet. Der Vorschlag ist nicht bindend — die Bestätigung/
|
||
* Überschreibung durch den Bearbeiter ist Pflicht (persistiert als ControlAssessment).
|
||
*
|
||
* Grundlage: VDA-ISA-Reifegradmodell 0–3, Fachcontent C5. Belegtabelle je Control:
|
||
* `seed/scoping/c5-controls.json` (Generierung: scripts/build-c5-controls.ts).
|
||
*/
|
||
|
||
/**
|
||
* Neutrale, framework-UNABHÄNGIGE Control-Spezifikation (Belegbasis, Feindesign §1/§2):
|
||
* welche Richtlinie/Verfahren zuständig sind und ob Asset-/Risiko-Verknüpfung gefordert
|
||
* ist. Eine Tatsache über die Organisation, keine Frage der Norm — daher von TISAX (C5)
|
||
* UND ISO (mapping-iso) befüllt und von der GETEILTEN Belegauflösung (loadEvidenceResolver)
|
||
* konsumiert. `loadEvidenceResolver` darf `ControlSpec` NICHT um ein Framework erweitern.
|
||
*/
|
||
export interface ControlSpec {
|
||
control: string;
|
||
title: string;
|
||
/** Zuständige Richtlinie(n): L00 | R01..R14 (TISAX) bzw. R.. (ISO). Leer = keine dedizierte. */
|
||
policy: string[];
|
||
/** Geforderte Verfahren: VA-xx. Leer = operative Steuerung in der Richtlinie verankert. */
|
||
verfahren: string[];
|
||
/** Control setzt auf Inventar auf (Asset-Verknüpfung gefordert). */
|
||
needsAsset: boolean;
|
||
/** Control setzt auf Risikoregister auf (Risiko-Verknüpfung gefordert). */
|
||
needsRisk: boolean;
|
||
}
|
||
|
||
/** TISAX-Belegtabelle (C5 §1) = neutrale ControlSpec + Standard-Zielreifegrad (VDA-ISA-Zusatz). */
|
||
export interface C5ControlSpec extends ControlSpec {
|
||
/** Standard-Zielreifegrad laut Tabelle (informativ; effektiv aus Scope, s. targetMaturity). */
|
||
target: number;
|
||
}
|
||
|
||
/** Vollständige C5-Belegtabelle (45 IS-Controls). */
|
||
export const C5_CONTROLS = c5 as C5ControlSpec[];
|
||
const C5_BY_ID = new Map(C5_CONTROLS.map((c) => [c.control, c]));
|
||
|
||
/** Generischer Fallback-Spec für Controls ohne §5-Zeile (Proto 8.x / Datenschutz 9.x, C5 §6). */
|
||
export function specForControl(control: string): C5ControlSpec {
|
||
return (
|
||
C5_BY_ID.get(control) ?? {
|
||
control,
|
||
title: control,
|
||
policy: [],
|
||
verfahren: [],
|
||
needsAsset: false,
|
||
needsRisk: false,
|
||
target: 2, // §6-Gruppen: Standard-Zielreifegrad 2 (Ausnahmen HOCH → 3, s. targetMaturity).
|
||
}
|
||
);
|
||
}
|
||
|
||
/** Validierungsstatus eines Belegs (C5 §1): fehlt → verknüpft (unvalidiert) → validiert. */
|
||
export type EvidenceStatus = "fehlt" | "verknuepft" | "validiert";
|
||
|
||
/** Aus den Fachmodulen abgeleiteter Ist-Belegstand eines Controls. */
|
||
export interface ControlEvidence {
|
||
/** Status der (aggregierten) zuständigen Richtlinie(n). */
|
||
policy: EvidenceStatus;
|
||
/** Status der geforderten Verfahren (aggregiert, „schwächster" gewinnt). */
|
||
verfahren: EvidenceStatus;
|
||
/** Asset-Verknüpfung vorhanden (nur relevant, wenn spec.needsAsset). */
|
||
assetLinked: boolean;
|
||
/** Risiko-Verknüpfung vorhanden (nur relevant, wenn spec.needsRisk). */
|
||
riskLinked: boolean;
|
||
/**
|
||
* Nur für Controls OHNE dediziertes Verfahren (C5 §2, Sonderfall): validierte,
|
||
* dokumentierte Umsetzungsregelung/Konfiguration (N-basisch), die für Grad 2 an die
|
||
* Stelle von (V) tritt. Für Controls mit Verfahren irrelevant.
|
||
*/
|
||
implementationRule?: boolean;
|
||
/**
|
||
* Operativer Wirksamkeitsnachweis (N) validiert UND aktuell (Aktualitätsregel ≤12 M.,
|
||
* C5 §1) — wiederkehrender Review/Audit/Test. Grad-3-Trigger. Frühe Onboarding-Phase
|
||
* i. d. R. false → Deckelung auf Grad 2.
|
||
*/
|
||
operationalProof: boolean;
|
||
/** Nachweis widerspricht Richtlinie / offene Findings → Deckelung auf max. 1 (C5 §2). */
|
||
contradiction?: boolean;
|
||
/** Control enthält nur SOLL (MUSS definitionsgemäß erfüllt) → Bewertung startet bei 1 (C5 §2). */
|
||
onlySoll?: boolean;
|
||
}
|
||
|
||
export type MaturityRule = "R0" | "R1a" | "R1b" | "R1c" | "R2" | "R3" | "CAP";
|
||
|
||
export interface MaturitySuggestion {
|
||
value: 0 | 1 | 2 | 3;
|
||
rule: MaturityRule;
|
||
/** Kurzbegründung (nachvollziehbar, an Belege gekoppelt). */
|
||
reason: string;
|
||
}
|
||
|
||
/**
|
||
* Reifegrad-Vorschlag nach C5 §2 (generische Belegkonstellations-Tabelle) inkl.
|
||
* Sonderfälle: Controls ohne Verfahren (V-Slot durch validierte Umsetzungsregelung
|
||
* ersetzt), nur-SOLL-Controls (Start bei 1) und Deckelung bei Widerspruch/Findings.
|
||
*/
|
||
export function suggestMaturity(spec: C5ControlSpec, ev: ControlEvidence): MaturitySuggestion {
|
||
// Deckelung: Nachweis widerspricht Richtlinie / offene Findings → max. Grad 1 (C5 §2).
|
||
if (ev.contradiction) {
|
||
return { value: 1, rule: "CAP", reason: "Nachweis widerspricht Richtlinie bzw. offene Findings — Vorschlag gedeckelt (max. 1)." };
|
||
}
|
||
|
||
const requiresProcedure = spec.verfahren.length > 0;
|
||
// Bei Controls ohne dediziertes Verfahren tritt für Grad 2 eine validierte, dokumentierte
|
||
// Umsetzungsregelung (implementationRule) an die Stelle von (V); der wiederkehrende
|
||
// Wirksamkeitsnachweis (operationalProof) bleibt eigenständiger Grad-3-Trigger.
|
||
const procedureOk = requiresProcedure ? ev.verfahren === "validiert" : ev.implementationRule === true;
|
||
const assetOk = !spec.needsAsset || ev.assetLinked;
|
||
const riskOk = !spec.needsRisk || ev.riskLinked;
|
||
const policyValidated = ev.policy === "validiert";
|
||
const policyLinked = ev.policy !== "fehlt";
|
||
|
||
// R3: R2 erfüllt UND mindestens ein validierter, aktueller operativer Wirksamkeitsnachweis.
|
||
if (policyValidated && procedureOk && assetOk && riskOk && ev.operationalProof) {
|
||
return { value: 3, rule: "R3", reason: "Richtlinie und Verfahren validiert, Verknüpfungen vorhanden und aktueller Wirksamkeitsnachweis." };
|
||
}
|
||
// R2: Richtlinie validiert UND alle Verfahren validiert UND (falls einschlägig) Asset-/Risiko-Verknüpfung.
|
||
if (policyValidated && procedureOk && assetOk && riskOk) {
|
||
return { value: 2, rule: "R2", reason: "Vorgehen dokumentiert und gesteuert; Richtlinie und Verfahren validiert." };
|
||
}
|
||
// R1b: Richtlinie validiert, aber ein gefordertes Verfahren fehlt/unvalidiert (oder Verknüpfung fehlt).
|
||
if (policyValidated) {
|
||
return { value: 1, rule: "R1b", reason: "Richtlinie validiert, aber gefordertes Verfahren bzw. Verknüpfung fehlt oder ist unvalidiert." };
|
||
}
|
||
// R1a: zuständige Richtlinie verknüpft, aber nicht validiert.
|
||
if (policyLinked) {
|
||
return { value: 1, rule: "R1a", reason: "Regelungsabsicht erkennbar (Richtlinie verknüpft), aber nicht validiert/gelebt." };
|
||
}
|
||
// R1c: nur operativer Nachweis/Umsetzung ohne validierte Richtlinie.
|
||
if (ev.operationalProof || ev.implementationRule || ev.verfahren !== "fehlt") {
|
||
return { value: 1, rule: "R1c", reason: "Tätigkeit wird durchgeführt, aber nicht dokumentiert gesteuert (keine validierte Richtlinie)." };
|
||
}
|
||
// nur-SOLL: MUSS definitionsgemäß erfüllt → Bewertung startet bei 1.
|
||
if (ev.onlySoll) {
|
||
return { value: 1, rule: "R1c", reason: "Nur-SOLL-Control: MUSS-Ebene definitionsgemäß erfüllt (Start bei Grad 1)." };
|
||
}
|
||
// R0: keine Belege bzw. nur unvalidierte Fragmente ohne zuständige Richtlinie.
|
||
return { value: 0, rule: "R0", reason: "Kein belastbarer Nachweis einer geregelten Vorgehensweise." };
|
||
}
|
||
|
||
export interface ScopeForTarget {
|
||
/** Assessment-Level aus TenantSettings (A2-1): AL2 oder AL3. */
|
||
level: "AL2" | "AL3";
|
||
flags: Record<string, boolean>;
|
||
}
|
||
|
||
/**
|
||
* Zielreifegrad je Control (C5 §3): Standard 3; Absenkung auf 2 nur bei reinem MUSS-/AL2-Scope
|
||
* (kein SOLL). HOCH/SEHR-HOCH-Schutzbedarf erzwingt Grad 3 (keine Absenkung).
|
||
*/
|
||
export function targetMaturity(scope: ScopeForTarget): 2 | 3 {
|
||
const high = scope.flags.FLAG_HIGH_PROTECTION || scope.flags.FLAG_VERY_HIGH_PROTECTION;
|
||
if (scope.level === "AL3" || high || scope.flags.FLAG_INCLUDE_SHOULD) return 3;
|
||
// reiner AL2-/MUSS-Scope (kein SOLL, kein erhöhter Schutzbedarf): Absenkung auf Grad 2.
|
||
return 2;
|
||
}
|
||
|
||
/** Ein offener Punkt (Gap) nach C5 §4 mit fehlender Belegklasse und empfohlener Maßnahme. */
|
||
export interface OpenPoint {
|
||
control: string;
|
||
kind: "reifegradluecke" | "richtlinie" | "verfahren" | "nachweis" | "verknuepfung" | "widerspruch";
|
||
actual: number;
|
||
target: number;
|
||
/** Fehlende Belegklasse / Kurzbeschreibung. */
|
||
missing: string;
|
||
/** Empfohlene Maßnahme (Aufgabentitel). */
|
||
action: string;
|
||
}
|
||
|
||
/**
|
||
* Leitet die offenen Punkte (Gap → Aufgabe) nach C5 §4 ab: Reifegradlücke, fehlender
|
||
* Pflichtbeleg (Richtlinie/Verfahren), fehlender/veralteter Nachweis bei Zielgrad 3,
|
||
* fehlende Asset-/Risiko-Verknüpfung und Widerspruch/Findings.
|
||
*/
|
||
export function openPoints(spec: C5ControlSpec, ev: ControlEvidence, suggestion: MaturitySuggestion, target: number): OpenPoint[] {
|
||
const out: OpenPoint[] = [];
|
||
const base = { control: spec.control, actual: suggestion.value, target };
|
||
|
||
if (ev.contradiction) {
|
||
out.push({ ...base, kind: "widerspruch", missing: "Nachweis mit Findings/Widerspruch", action: `Abweichung in Control ${spec.control} beheben` });
|
||
}
|
||
if (ev.policy !== "validiert" && spec.policy.length > 0) {
|
||
out.push({ ...base, kind: "richtlinie", missing: `Richtlinie ${spec.policy.join("/")}`, action: `Richtlinie ${spec.policy.join("/")} erstellen/validieren (Control ${spec.control})` });
|
||
}
|
||
if (spec.verfahren.length > 0 && ev.verfahren !== "validiert") {
|
||
out.push({ ...base, kind: "verfahren", missing: `Verfahren ${spec.verfahren.join("/")}`, action: `Verfahren ${spec.verfahren.join("/")} erstellen/validieren (Control ${spec.control})` });
|
||
}
|
||
if (spec.needsAsset && !ev.assetLinked) {
|
||
out.push({ ...base, kind: "verknuepfung", missing: "Asset-Verknüpfung", action: `Asset-/Inventarverknüpfung für Control ${spec.control} herstellen` });
|
||
}
|
||
if (spec.needsRisk && !ev.riskLinked) {
|
||
out.push({ ...base, kind: "verknuepfung", missing: "Risiko-Verknüpfung", action: `Risikoverknüpfung für Control ${spec.control} herstellen` });
|
||
}
|
||
if (target >= 3 && !ev.operationalProof && !ev.contradiction) {
|
||
out.push({ ...base, kind: "nachweis", missing: "operativer Wirksamkeitsnachweis (aktuell)", action: `Wirksamkeitsnachweis (Review/Audit/Test) für Control ${spec.control} führen` });
|
||
}
|
||
// Reifegradlücke zuletzt, nur wenn nicht schon durch einen konkreten Pflichtbeleg abgedeckt.
|
||
if (suggestion.value < target && out.length === 0) {
|
||
out.push({ ...base, kind: "reifegradluecke", missing: `Reifegrad ${suggestion.value} < Ziel ${target}`, action: `Belege/Umsetzung für Control ${spec.control} bis Zielgrad ${target} anheben` });
|
||
}
|
||
return out;
|
||
}
|
||
|
||
export const MATURITY_LABEL: Record<0 | 1 | 2 | 3, string> = {
|
||
0: "Grad 0 — nicht geregelt",
|
||
1: "Grad 1 — informell",
|
||
2: "Grad 2 — dokumentiert/gesteuert",
|
||
3: "Grad 3 — wirksam/integriert",
|
||
};
|