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>
64 lines
2.6 KiB
TypeScript
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;
|
|
}
|