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
+61
View File
@@ -0,0 +1,61 @@
import "dotenv/config";
import { prisma } from "../src/server/db";
/**
* Backfill 5×5-Risikokriterien für Bestandsmandanten: ergänzt die 5. Eintritts-
* wahrscheinlichkeits-Stufe und die 5. Schadensstufe je Dimension, falls noch nicht
* vorhanden. Die Bewertung (Formular/Matrix/Score) war schon 5×5 — nur die Kriterien-
* daten waren 4-stufig geseedet. Idempotent (mehrfach ausführbar).
*
* npx tsx scripts/backfill-risk-5x5.ts # alle Mandanten
* TENANT_SLUG=gefim npx tsx scripts/backfill-risk-5x5.ts # nur einer
*/
// Sinnvolle Default-Texte für Stufe 5 der Standard-Schadensdimensionen (Abgleich per Name).
const LEVEL5: Record<string, string> = {
"Gesetzes-/Vertragsverstöße": "existenzbedrohende Rechtsfolgen (Lizenzentzug/Haftung)",
"Datenschutz": "besonders schützenswerte Daten in großem Umfang / existenzielle Betroffenheit",
"Persönliche Unversehrtheit": "Lebensgefahr für viele / Todesfolge",
"Aufgabenerfüllung": "vollständiger, dauerhafter Ausfall der Organisation",
"Innen-/Außenwirkung (Reputation)": "existenzbedrohender, dauerhafter Reputationsverlust",
"Störungs-/Ausfallzeit": "> 1 Monat / dauerhaft",
"Finanzielle Auswirkungen": "existenzbedrohend (> 1 Mio €)",
};
const FALLBACK5 = "(bitte definieren)";
async function main() {
const slug = process.env.TENANT_SLUG?.trim().toLowerCase();
const tenants = await prisma.tenant.findMany({ where: slug ? { slug } : {}, select: { id: true, slug: true } });
if (!tenants.length) { console.log("Keine Mandanten gefunden."); return; }
for (const t of tenants) {
let ew5 = 0, dim5 = 0;
// EW-Stufe 5 ergänzen, falls nicht vorhanden
const hasEw5 = await prisma.riskEwLevel.findFirst({ where: { tenantId: t.id, level: 5 } });
if (!hasEw5) {
// Nur ergänzen, wenn überhaupt EW-Stufen existieren (sonst provisioniert der Mandant sie neu).
const anyEw = await prisma.riskEwLevel.count({ where: { tenantId: t.id } });
if (anyEw > 0) {
await prisma.riskEwLevel.create({ data: { tenantId: t.id, level: 5, label: "Nahezu sicher", definition: "Ereignis ist praktisch dauerhaft gegeben oder tritt ständig ein." } });
ew5 = 1;
}
}
// Schadensdimensionen: je Dimension "5" ergänzen, falls fehlt
const dims = await prisma.riskDamageDimension.findMany({ where: { tenantId: t.id } });
for (const d of dims) {
const levels = (d.levels ?? {}) as Record<string, string>;
if (levels["5"] == null || levels["5"] === "") {
levels["5"] = LEVEL5[d.name] ?? FALLBACK5;
await prisma.riskDamageDimension.update({ where: { id: d.id }, data: { levels } });
dim5++;
}
}
console.log(`Mandant ${t.slug}: EW-Stufe 5 ${ew5 ? "ergänzt" : "bereits vorhanden/übersprungen"}, ${dim5} Schadensdimension(en) um Stufe 5 ergänzt.`);
}
console.log("Fertig.");
}
main().then(() => process.exit(0)).catch((e) => { console.error(e); process.exit(1); });