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
+106
View File
@@ -0,0 +1,106 @@
// AP2 — Provisionierung & Sichtbarkeits-Flags (docs/UEBERGABE-framework-iso27001.md §2).
//
// Provisioniert je einen Mandanten als reines TISAX, reines ISO und Doppel-Framework
// und prüft:
// - TenantFramework-Zeilen (Primär zuerst),
// - Import je Framework (Anforderungszahlen, keine Cross-Archivierung, Dokumente
// nicht dupliziert),
// - FLAG_FW_TISAX / FLAG_FW_ISO27001 NACH dem Import korrekt gesetzt,
// - AL-/Schutzbedarf-Flags nur bei TISAX (reiner ISO-Mandant: nicht gesetzt).
// Räumt die Test-Mandanten (inkl. Identitäten) vor und nach dem Lauf ab.
//
// Lauf: npx tsx scripts/test-framework-provision.ts
import "dotenv/config";
import { join } from "node:path";
import { prisma } from "../src/server/db";
import { provisionTenant } from "../src/server/provision";
const SEED_DIR = join(process.cwd(), "seed", "isms-vorlagenpaket-v2");
let failures = 0;
const ok = (c: boolean, m: string) => { console.log(`${c ? "✓" : "✗ FEHLER"} ${m}`); if (!c) failures++; };
const T = {
tisax: { slug: "ap2-test-tisax", email: "admin@ap2-test-tisax.example" },
iso: { slug: "ap2-test-iso", email: "admin@ap2-test-iso.example" },
dual: { slug: "ap2-test-dual", email: "admin@ap2-test-dual.example" },
};
const SLUGS = Object.values(T).map((t) => t.slug);
const EMAILS = Object.values(T).map((t) => t.email);
async function cleanup() {
const tenants = await prisma.tenant.findMany({ where: { slug: { in: SLUGS } }, select: { id: true } });
const ids = tenants.map((t) => t.id);
if (ids.length) {
// FK-sichere Reihenfolge: Verknüpfungen → Kinder → Stamm.
await prisma.userRole.deleteMany({ where: { user: { tenantId: { in: ids } } } });
await prisma.rolePermission.deleteMany({ where: { role: { tenantId: { in: ids } } } });
await prisma.user.deleteMany({ where: { tenantId: { in: ids } } });
await prisma.role.deleteMany({ where: { tenantId: { in: ids } } });
for (const model of [
"policyRequirement", "policyVariable", "policyDocument", "policyBaselineParam",
"policyEvidence", "policyPackageState", "tenantFramework", "tenantModule",
"managedRegister", "auditLog",
] as const) {
// @ts-expect-error dynamischer Modellzugriff (alle tragen tenantId)
await prisma[model].deleteMany({ where: { tenantId: { in: ids } } });
}
await prisma.tenantSettings.deleteMany({ where: { tenantId: { in: ids } } });
await prisma.tenant.deleteMany({ where: { id: { in: ids } } });
}
await prisma.identity.deleteMany({ where: { email: { in: EMAILS } } });
}
const flag = async (tenantId: string, key: string) =>
(await prisma.policyVariable.findFirst({ where: { tenantId, key }, select: { value: true } }))?.value ?? null;
const reqCount = (tenantId: string, framework: "TISAX" | "ISO_27001") =>
prisma.policyRequirement.count({ where: { tenantId, framework, archivedAt: null } });
async function provision(slug: string, email: string, frameworks: ("TISAX" | "ISO_27001")[]) {
return provisionTenant(prisma, {
name: slug, slug, admin: { email, name: "AP2 Test", password: "Str0ng-Passw0rt!" },
frameworks, seedPoliciesDir: SEED_DIR, actorId: null,
});
}
async function main() {
await cleanup();
// ── Reines TISAX ────────────────────────────────────────────────────────────
const tisax = await provision(T.tisax.slug, T.tisax.email, ["TISAX"]);
const tisaxFw = await prisma.tenantFramework.findMany({ where: { tenantId: tisax.id }, select: { framework: true, isPrimary: true } });
ok(tisaxFw.length === 1 && tisaxFw[0].framework === "TISAX" && tisaxFw[0].isPrimary, "TISAX-Mandant: genau eine TenantFramework-Zeile (TISAX, primär)");
ok((await reqCount(tisax.id, "TISAX")) === 321 && (await reqCount(tisax.id, "ISO_27001")) === 0, "TISAX-Mandant: 321 TISAX-, 0 ISO-Anforderungen");
ok((await flag(tisax.id, "FLAG_FW_TISAX")) === "true" && (await flag(tisax.id, "FLAG_FW_ISO27001")) === "false", "TISAX-Mandant: FLAG_FW_TISAX=true, FLAG_FW_ISO27001=false");
ok((await flag(tisax.id, "FLAG_HIGH_PROTECTION")) === "true", "TISAX-Mandant: AL-Flag FLAG_HIGH_PROTECTION gesetzt");
// ── Reines ISO ──────────────────────────────────────────────────────────────
const iso = await provision(T.iso.slug, T.iso.email, ["ISO_27001"]);
const isoFw = await prisma.tenantFramework.findMany({ where: { tenantId: iso.id }, select: { framework: true, isPrimary: true } });
ok(isoFw.length === 1 && isoFw[0].framework === "ISO_27001" && isoFw[0].isPrimary, "ISO-Mandant: genau eine TenantFramework-Zeile (ISO, primär)");
ok((await reqCount(iso.id, "ISO_27001")) === 120 && (await reqCount(iso.id, "TISAX")) === 0, "ISO-Mandant: 120 ISO-, 0 TISAX-Anforderungen");
ok((await flag(iso.id, "FLAG_FW_ISO27001")) === "true" && (await flag(iso.id, "FLAG_FW_TISAX")) === "false", "ISO-Mandant: FLAG_FW_ISO27001=true, FLAG_FW_TISAX=false");
ok((await flag(iso.id, "FLAG_HIGH_PROTECTION")) === "false", "ISO-Mandant: KEIN AL-Flag gesetzt (Default false, Übergabe §1.3)");
const isoDocs = await prisma.policyDocument.count({ where: { tenantId: iso.id } });
const isoDocCodes = (await prisma.policyDocument.findMany({ where: { tenantId: iso.id }, select: { code: true } })).map((d) => d.code);
ok(isoDocs === new Set(isoDocCodes).size, "ISO-Mandant: Dokumente nicht dupliziert (eindeutige Codes)");
// ── Doppel-Framework ─────────────────────────────────────────────────────────
const dual = await provision(T.dual.slug, T.dual.email, ["TISAX", "ISO_27001"]);
const dualFw = await prisma.tenantFramework.findMany({ where: { tenantId: dual.id }, orderBy: { isPrimary: "desc" }, select: { framework: true, isPrimary: true } });
ok(dualFw.length === 2 && dualFw[0].framework === "TISAX" && dualFw[0].isPrimary && !dualFw[1].isPrimary, "Doppel-Mandant: zwei Zeilen, TISAX primär");
const dTisax = await reqCount(dual.id, "TISAX");
const dIso = await reqCount(dual.id, "ISO_27001");
ok(dTisax === 321 && dIso === 120, `Doppel-Mandant: 321 TISAX + 120 ISO koexistieren (${dTisax}+${dIso})`);
ok((await flag(dual.id, "FLAG_FW_TISAX")) === "true" && (await flag(dual.id, "FLAG_FW_ISO27001")) === "true", "Doppel-Mandant: beide FLAG_FW_* = true");
const dualDocs = await prisma.policyDocument.count({ where: { tenantId: dual.id } });
const dualCodes = (await prisma.policyDocument.findMany({ where: { tenantId: dual.id }, select: { code: true } })).map((d) => d.code);
ok(dualDocs === new Set(dualCodes).size, `Doppel-Mandant: Dokumente NICHT dupliziert (${dualDocs} eindeutige Codes)`);
await cleanup();
console.log("\n✓ aufgeräumt (Test-Mandanten + Identitäten entfernt)");
}
main()
.then(() => { console.log(failures === 0 ? "\nAP2-Provisionierung grün." : `\n${failures} Prüfung(en) fehlgeschlagen.`); process.exit(failures === 0 ? 0 : 1); })
.catch(async (e) => { console.error(e); await cleanup().catch(() => {}); process.exit(1); });