Basis: Certvia dev@a48c5fb als Fundament für Craftvia
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s

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>
This commit is contained in:
2026-09-14 11:05:39 +02:00
co-authored by Claude Opus 5
commit c8e6f30a27
720 changed files with 140143 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
// 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<string>();
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`);