// Generator (Story A7-2): parst die C5-Control-Belegtabelle (Fachcontent §5, IS-Controls) // in die maschinenlesbare Reifegrad-Datengrundlage `seed/scoping/c5-controls.json`. // Lauf: npx tsx scripts/build-c5-controls.ts // // Quelle §5-Zeilen: | **Control** Titel | P / V / N (mit (**A**)/(**R**)) | R2-Bed. | R3-Bed. | Ziel | // - policy (P): L00 | R01..R14 (zuständige Richtlinie[n], „—" = keine) // - verfahren (V): VA-01..VA-19 (geforderte Verfahren, „—" = in Richtlinie verankert) // - needsAsset/needsRisk: Belegzelle enthält (**A**) bzw. (**R**) // - target: Standard-Zielreifegrad der Tabelle (informativ; effektiv wird er zur Laufzeit // aus dem Scope nach C5 §3 abgeleitet). // §6 (Proto 8.x / Datenschutz 9.x) ist gruppenbasiert und wird NICHT je Control abgebildet; // dafür greift zur Laufzeit ein generischer Fallback-Spec. import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; import { dirname, resolve } from "node:path"; const SRC = resolve("docs/wizard-uebergabe/02_Fachcontent_C1-C9/C5_Reifegradlogik.md"); const OUT = resolve("seed/scoping/c5-controls.json"); export interface C5ControlSpec { control: string; title: string; policy: string[]; verfahren: string[]; needsAsset: boolean; needsRisk: boolean; target: number; } const CONTROL_RE = /^\|\s*\*\*([0-9]+\.[0-9]+\.[0-9]+(?:-[A-Z]+)?)\*\*\s*([^|]*?)\s*\|(.+)$/; function codes(cell: string, re: RegExp): string[] { const out = new Set(); for (const m of cell.matchAll(re)) out.add(m[0]); return [...out]; } function parse(md: string): C5ControlSpec[] { const rows: C5ControlSpec[] = []; for (const line of md.split("\n")) { const m = CONTROL_RE.exec(line.trim()); if (!m) continue; const [, control, title, rest] = m; // rest = "Belege | R2 | R3 | Ziel |" const cells = rest.split("|").map((c) => c.trim()); const belege = cells[0] ?? ""; const zielCell = cells[3] ?? cells[cells.length - 2] ?? ""; const [pPart = "", vPart = ""] = belege.split(" / "); const target = Number.parseInt(zielCell.replace(/[^0-9]/g, "").charAt(0) || "3", 10); rows.push({ control, title: title.trim(), policy: codes(pPart, /L00|R[0-9]{2}/g), verfahren: codes(vPart, /VA-[0-9]{2}/g), needsAsset: /\*\*A\*\*/.test(belege), needsRisk: /\*\*R\*\*/.test(belege), target: Number.isFinite(target) ? target : 3, }); } return rows; } const specs = parse(readFileSync(SRC, "utf8")); if (specs.length < 40) throw new Error(`C5 §5 unerwartet wenige Controls geparst: ${specs.length}`); mkdirSync(dirname(OUT), { recursive: true }); writeFileSync(OUT, JSON.stringify(specs, null, 2) + "\n"); console.log(`c5-controls.json geschrieben: ${specs.length} Controls`);