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>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user