Files
craftvia/prisma/import-risks.ts
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
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>
2026-09-14 11:05:39 +02:00

64 lines
2.6 KiB
TypeScript

// Import des Standard-Risikokatalogs (Story A6-1) aus dem Fachcontent C4 in das
// globale Modell RiskCatalogEntry. Tabellenzeile:
//
// | R-ORG-01 | Titel | Beschreibung | 1.1.1, 1.2.1 | Richtlinien, Organisation | Standardmaßnahme | E3/S4 — Begründung |
//
// Idempotent (Upsert über code). Kein tenantId (globaler Katalog).
import { readFileSync } from "node:fs";
import { join } from "node:path";
import type { PrismaClient } from "@prisma/client";
const C4_PATH = join(__dirname, "..", "docs", "wizard-uebergabe", "02_Fachcontent_C1-C9", "C4_Risikokatalog.md");
export interface ParsedRisk {
code: string; category: string; title: string; description: string;
controls: string[]; assetTypes: string[]; standardMeasure: string;
defaultLikelihood: number; defaultImpact: number; rationale: string; orderIdx: number;
}
const splitList = (s: string): string[] => s.split(",").map((x) => x.trim()).filter(Boolean);
/** Parst die C4-Tabellen zu Katalog-Objekten (ohne DB-Zugriff — testbar). */
export function parseRiskCatalog(text: string): ParsedRisk[] {
const out: ParsedRisk[] = [];
let i = 0;
for (const line of text.split("\n")) {
const m = line.match(/^\|\s*(R-[A-Z]+-\d+)\s*\|(.*)\|\s*$/);
if (!m) continue;
const cols = m[2].split("|").map((c) => c.trim());
if (cols.length < 6) continue; // title|desc|controls|assetTypes|measure|ES-rationale
const code = m[1];
const es = cols[5].match(/E\s*(\d)\s*\/\s*S\s*(\d)/i);
const [, esL, esI] = es ?? [];
out.push({
code,
category: code.split("-")[1],
title: cols[0],
description: cols[1],
controls: splitList(cols[2]),
assetTypes: splitList(cols[3]),
standardMeasure: cols[4],
defaultLikelihood: esL ? Number(esL) : 3,
defaultImpact: esI ? Number(esI) : 3,
rationale: cols[5].replace(/^E\s*\d\s*\/\s*S\s*\d\s*[—-]\s*/i, "").trim(),
orderIdx: i++,
});
}
return out;
}
/** Liest C4 und schreibt den Katalog (Upsert je code). Liefert die Anzahl. */
export async function importRiskCatalog(prisma: PrismaClient): Promise<number> {
const text = readFileSync(C4_PATH, "utf-8");
const risks = parseRiskCatalog(text);
for (const r of risks) {
await prisma.riskCatalogEntry.upsert({
where: { code: r.code },
update: { category: r.category, title: r.title, description: r.description, controls: r.controls, assetTypes: r.assetTypes, standardMeasure: r.standardMeasure, defaultLikelihood: r.defaultLikelihood, defaultImpact: r.defaultImpact, rationale: r.rationale, orderIdx: r.orderIdx },
create: r,
});
}
return risks.length;
}