// ISMS-Tool — Prisma-Schema // Fundament (Iteration 1): Mandanten, Nutzer, RBAC, Audit-Log. // Fachliche Entitäten (Assets, BIA, Risiken, …) folgen je Iteration — siehe docs/SPEC.md §5. // // Multi-Tenant-Regel: Jede fachliche Tabelle trägt tenantId. Zugriff nur über den // zentralen Tenant-Guard (src/server/db.ts); zusätzlich Postgres RLS (prisma/rls.sql). generator client { provider = "prisma-client-js" previewFeatures = ["postgresqlExtensions"] } datasource db { provider = "postgresql" extensions = [pgvector(map: "vector")] } enum TenantStatus { ACTIVE SUSPENDED } enum UserStatus { ACTIVE INVITED LOCKED DEACTIVATED } model Tenant { id String @id @default(cuid()) name String slug String @unique status TenantStatus @default(ACTIVE) config Json @default("{}") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") users User[] roles Role[] auditLogs AuditLog[] @@map("tenants") } model User { id String @id @default(cuid()) tenantId String @map("tenant_id") email String passwordHash String @map("password_hash") name String status UserStatus @default(ACTIVE) mfaSecret String? @map("mfa_secret") // Plattform-Admin ist mandantenübergreifend (Betreiberrolle) isPlatformAdmin Boolean @default(false) @map("is_platform_admin") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") tenant Tenant @relation(fields: [tenantId], references: [id]) userRoles UserRole[] ownedAssets Asset[] @relation("assetOwner") ownedProcesses Process[] @relation("processOwner") ownedRisks Risk[] @relation("riskOwner") ownedMeasures Measure[] @relation("measureOwner") @@unique([tenantId, email]) @@index([tenantId]) @@map("users") } model Role { id String @id @default(cuid()) tenantId String @map("tenant_id") key String // z. B. tenant-admin, isb, auditor, owner, user name String tenant Tenant @relation(fields: [tenantId], references: [id]) userRoles UserRole[] rolePermissions RolePermission[] @@unique([tenantId, key]) @@index([tenantId]) @@map("roles") } model Permission { id String @id @default(cuid()) key String @unique // z. B. asset:read, risk:write, incident:manage rolePermissions RolePermission[] @@map("permissions") } model UserRole { userId String @map("user_id") roleId String @map("role_id") user User @relation(fields: [userId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@id([userId, roleId]) @@map("user_roles") } model RolePermission { roleId String @map("role_id") permissionId String @map("permission_id") role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade) @@id([roleId, permissionId]) @@map("role_permissions") } // ── Assets & BIA (ein Modul, SPEC §4.1) ───────────────────────────────────── enum AssetType { INFORMATION SYSTEM APPLICATION LOCATION SUPPLIER PERSON DATA } enum AssetStatus { ACTIVE PLANNED RETIRED } enum ProcessAssetRole { PRIMARY SECONDARY } enum ProcessCategory { CORE // Kernprozess MANAGEMENT // Managementprozess SUPPORT // Unterstützender Prozess } model Asset { id String @id @default(cuid()) tenantId String @map("tenant_id") name String description String? type AssetType status AssetStatus @default(ACTIVE) ownerId String? @map("owner_id") location String? tags String[] @default([]) // Schutzbedarf 1–4 je Schutzziel (C/I/A) confidentiality Int @default(1) integrity Int @default(1) availability Int @default(1) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") createdBy String? @map("created_by") owner User? @relation("assetOwner", fields: [ownerId], references: [id]) relationsFrom AssetRelation[] @relation("relationFrom") relationsTo AssetRelation[] @relation("relationTo") processAssets ProcessAsset[] riskAssets RiskAsset[] supplierLinks SupplierAsset[] @@index([tenantId]) @@index([tenantId, type]) @@map("assets") } model AssetRelation { id String @id @default(cuid()) tenantId String @map("tenant_id") assetId String @map("asset_id") relatedAssetId String @map("related_asset_id") // z. B. depends_on, hosts, processes_data_of type String @default("depends_on") asset Asset @relation("relationFrom", fields: [assetId], references: [id], onDelete: Cascade) relatedAsset Asset @relation("relationTo", fields: [relatedAssetId], references: [id], onDelete: Cascade) @@unique([assetId, relatedAssetId, type]) @@index([tenantId]) @@map("asset_relations") } model Process { id String @id @default(cuid()) tenantId String @map("tenant_id") name String description String? category ProcessCategory @default(CORE) ownerId String? @map("owner_id") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") createdBy String? @map("created_by") owner User? @relation("processOwner", fields: [ownerId], references: [id]) processAssets ProcessAsset[] bia BiaEntry? risks Risk[] @@index([tenantId]) @@map("processes") } model ProcessAsset { id String @id @default(cuid()) tenantId String @map("tenant_id") processId String @map("process_id") assetId String @map("asset_id") role ProcessAssetRole @default(SECONDARY) process Process @relation(fields: [processId], references: [id], onDelete: Cascade) asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade) @@unique([processId, assetId]) @@index([tenantId]) @@map("process_assets") } model BiaEntry { id String @id @default(cuid()) tenantId String @map("tenant_id") processId String @unique @map("process_id") // Wiederanlaufparameter in Stunden (SPEC §4.1.2) rtoHours Int? @map("rto_hours") rpoHours Int? @map("rpo_hours") mtdHours Int? @map("mtd_hours") // Schadenshöhe 1–4 je Schutzziel impactC Int @default(1) @map("impact_c") impactI Int @default(1) @map("impact_i") impactA Int @default(1) @map("impact_a") // Kritikalität 1–4, abgeleitet (Max-Prinzip), redundant gespeichert für Listen/Filter criticality Int @default(1) notes String? createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") process Process @relation(fields: [processId], references: [id], onDelete: Cascade) @@index([tenantId]) @@map("bia_entries") } // ── Risikoanalyse (SPEC §4.2) ─────────────────────────────────────────────── enum RiskTreatment { AVOID // Vermeiden MITIGATE // Vermindern TRANSFER // Übertragen ACCEPT // Akzeptieren } enum RiskStatus { OPEN IN_TREATMENT ACCEPTED CLOSED } model Risk { id String @id @default(cuid()) tenantId String @map("tenant_id") // Laufende Nummer je Mandant → Anzeige als "R-001" refNo Int @map("ref_no") title String description String? threat String? // Bedrohung (Katalog folgt, SPEC §5) vulnerability String? // Schwachstelle // Brutto-Bewertung: Eintrittswahrscheinlichkeit × Auswirkung (je 1–5) likelihood Int @default(1) impact Int @default(1) score Int @default(1) // redundant für Listen/Heatmap // Rest-Risiko nach Maßnahmen (berechnet, dezimal wegen Teil-Minderungen) residualLikelihood Float? @map("residual_likelihood") residualImpact Float? @map("residual_impact") residualScore Float? @map("residual_score") treatment RiskTreatment @default(MITIGATE) status RiskStatus @default(OPEN) ownerId String? @map("owner_id") processId String? @map("process_id") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") createdBy String? @map("created_by") owner User? @relation("riskOwner", fields: [ownerId], references: [id]) process Process? @relation(fields: [processId], references: [id]) riskAssets RiskAsset[] riskMeasures RiskMeasure[] @@unique([tenantId, refNo]) @@index([tenantId]) @@index([tenantId, status]) @@map("risks") } model RiskAsset { id String @id @default(cuid()) tenantId String @map("tenant_id") riskId String @map("risk_id") assetId String @map("asset_id") risk Risk @relation(fields: [riskId], references: [id], onDelete: Cascade) asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade) @@unique([riskId, assetId]) @@index([tenantId]) @@map("risk_assets") } // ── Maßnahmen (SPEC §4.4) ─────────────────────────────────────────────────── enum MeasureStatus { OPEN IN_PROGRESS DONE } enum MeasurePriority { LOW MEDIUM HIGH } model Measure { id String @id @default(cuid()) tenantId String @map("tenant_id") refNo Int @map("ref_no") // Anzeige als "M-001" title String description String? status MeasureStatus @default(OPEN) priority MeasurePriority @default(MEDIUM) ownerId String? @map("owner_id") dueDate DateTime? @map("due_date") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") createdBy String? @map("created_by") owner User? @relation("measureOwner", fields: [ownerId], references: [id]) riskMeasures RiskMeasure[] @@unique([tenantId, refNo]) @@index([tenantId]) @@index([tenantId, status]) @@map("measures") } // Verknüpfung Risiko ↔ Maßnahme inkl. erwarteter Risikominderung: // Das Rest-Risiko wird aus likelihood/impact minus der Summe der // Minderungen aller verknüpften Maßnahmen berechnet (siehe src/server/risk-calc.ts). model RiskMeasure { id String @id @default(cuid()) tenantId String @map("tenant_id") riskId String @map("risk_id") measureId String @map("measure_id") // Minderung je Dimension (0,00–4,00) — mehrere Maßnahmen summieren sich, // bis ein voller Punkt (oder mehr) erreicht ist reductionLikelihood Float @default(0) @map("reduction_likelihood") reductionImpact Float @default(0) @map("reduction_impact") risk Risk @relation(fields: [riskId], references: [id], onDelete: Cascade) measure Measure @relation(fields: [measureId], references: [id], onDelete: Cascade) @@unique([riskId, measureId]) @@index([tenantId]) @@map("risk_measures") } // Globale Kataloge (SPEC §5: Threat/Vulnerability, mandantenübergreifend). // Risiken speichern weiterhin Freitext — der Katalog liefert Vorschläge (datalist), // eigene Formulierungen bleiben möglich. model Threat { id String @id @default(cuid()) name String @unique @@map("threats") } model Vulnerability { id String @id @default(cuid()) name String @unique @@map("vulnerabilities") } // ── Lieferantenmanagement (SPEC §4.14, VDA-ISA 2027 Kap. 6, NIS2 Art. 21(2)(d)) ── enum SupplierStatus { ACTIVE ONBOARDING UNDER_REVIEW OFFBOARDED } enum AssessmentType { QUESTIONNAIRE SELF_ASSESSMENT AUDIT } enum AssessmentStatus { SENT RECEIVED EVALUATED OVERDUE } enum EvidenceKind { CERTIFICATE TISAX_LABEL ATTESTATION AUDIT_REPORT SELF_ASSESSMENT } enum ResponsibleParty { CLIENT SUPPLIER SHARED } model Supplier { id String @id @default(cuid()) tenantId String @map("tenant_id") refNo Int @map("ref_no") // Anzeige "L-001" name String sector String? services String? // erbrachte Leistung / IT-Services criticality Int @default(1) // 1–4 dataCategories String[] @default([]) @map("data_categories") // Schutzbedarf der verarbeiteten Informationen (C/I/A 1–4) confidentiality Int @default(1) integrity Int @default(1) availability Int @default(1) nis2Relevant Boolean @default(false) @map("nis2_relevant") // Teil der Lieferkette status SupplierStatus @default(ACTIVE) contact String? nextReview DateTime? @map("next_review") notes String? createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") createdBy String? @map("created_by") assessments SupplierAssessment[] contracts Contract[] ndas Nda[] evidence SupplierEvidence[] responsibilities ServiceResponsibility[] subcontractors Subcontractor[] decisions ManagementDecision[] controlMaturity SupplierControlMaturity[] assetLinks SupplierAsset[] @@unique([tenantId, refNo]) @@index([tenantId]) @@map("suppliers") } // Lieferant ↔ Asset (welcher Dienstleister betrifft welche Assets) — speist den Graph model SupplierAsset { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") assetId String @map("asset_id") supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade) @@unique([supplierId, assetId]) @@index([tenantId]) @@map("supplier_assets") } model SupplierAssessment { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") type AssessmentType status AssessmentStatus @default(SENT) score Int? // 0–100 Scoring date DateTime? nextReview DateTime? @map("next_review") result String? createdAt DateTime @default(now()) @map("created_at") supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) @@index([tenantId]) @@map("supplier_assessments") } model Contract { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") type String @default("service") // service | av_dpa | nda | sla … avDpa Boolean @default(false) @map("av_dpa") // AV/DPA (DSGVO Art. 28) securityClauses Boolean @default(false) @map("security_clauses") flowdown Boolean @default(false) // Weitergabe an Subunternehmer customerTransparency Boolean @default(false) @map("customer_transparency") validFrom DateTime? @map("valid_from") validTo DateTime? @map("valid_to") reference String? createdAt DateTime @default(now()) @map("created_at") supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) @@index([tenantId]) @@map("contracts") } model Nda { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") parties String? infoScope String? @map("info_scope") subject String? validFrom DateTime? @map("valid_from") validTo DateTime? @map("valid_to") obligations String? extensionStatus String? @map("extension_status") // z. B. offen | verlängert | ausgelaufen createdAt DateTime @default(now()) @map("created_at") supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) @@index([tenantId]) @@map("ndas") } model SupplierEvidence { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") kind EvidenceKind name String? protectsCia String? @map("protects_cia") // z. B. "C,I,A" validTo DateTime? @map("valid_to") adequacyChecked Boolean @default(false) @map("adequacy_checked") createdAt DateTime @default(now()) @map("created_at") supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) @@index([tenantId]) @@map("supplier_evidence") } model ServiceResponsibility { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") itService String @map("it_service") requirement String responsibleParty ResponsibleParty @default(SHARED) @map("responsible_party") isaApplicability String? @map("isa_applicability") evidence String? integratedLocalControls String? @map("integrated_local_controls") createdAt DateTime @default(now()) @map("created_at") supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) @@index([tenantId]) @@map("service_responsibilities") } model Subcontractor { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") name String flowdownObligation Boolean @default(false) @map("flowdown_obligation") supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) @@index([tenantId]) @@map("subcontractors") } model ManagementDecision { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") reasonNoAudit String @map("reason_no_audit") decision String decidedBy String? @map("decided_by") date DateTime @default(now()) recordRef String? @map("record_ref") supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) @@index([tenantId]) @@map("management_decisions") } // VDA-ISA 2027 Kapitel 6 — Supplier Relationships (globaler Katalog, per Import versioniert) model SupplierControl { id String @id @default(cuid()) ref String @unique // 6.1.1 / 6.1.2 / 6.1.3 title String objective String mustReq String? @map("must_req") shouldReq String? @map("should_req") highReq String? @map("high_req") // Additional for high protection veryHighReq String? @map("very_high_req") // Additional for very high protection simplifiedGroupAssessment Boolean @default(false) @map("simplified_group_assessment") targetMaturity Int @default(3) @map("target_maturity") references String? // ISO 27001, NIST CSF, BSI … maturity SupplierControlMaturity[] @@map("supplier_controls") } model SupplierControlMaturity { id String @id @default(cuid()) tenantId String @map("tenant_id") supplierId String @map("supplier_id") controlId String @map("control_id") maturity Int @default(0) // 0–5 notes String? supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade) control SupplierControl @relation(fields: [controlId], references: [id], onDelete: Cascade) @@unique([supplierId, controlId]) @@index([tenantId]) @@map("supplier_control_maturity") } model AuditLog { id String @id @default(cuid()) tenantId String @map("tenant_id") actorId String? @map("actor_id") action String // create | update | delete | login | export | … entity String // z. B. asset, risk, user entityId String? @map("entity_id") before Json? after Json? createdAt DateTime @default(now()) @map("created_at") tenant Tenant @relation(fields: [tenantId], references: [id]) @@index([tenantId, entity, entityId]) @@index([tenantId, createdAt]) @@map("audit_logs") }