Neue AssetType-Werte SOFTWARE und PROJECT plus 1:1-Profile: - SoftwareProfile (Anbieter-Verknüpfung, Version/Patch-Stand, Freigabestatus BEANTRAGT/FREIGEGEBEN/GESPERRT, Freigeber, Kritikalität, Review) - ProjectProfile (IS-Klassifizierung, ISB-Einbindung, Projektstatus) Migration inkl. RLS-Policies; TENANT_MODELS und Dependency-Graph (Node-Typ + Icons Package/FolderKanban) um beide Typen erweitert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1141 lines
38 KiB
Plaintext
1141 lines
38 KiB
Plaintext
// 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
|
||
ARCHIVED
|
||
}
|
||
|
||
enum UserStatus {
|
||
ACTIVE
|
||
INVITED
|
||
LOCKED
|
||
DEACTIVATED
|
||
}
|
||
|
||
model Tenant {
|
||
id String @id @default(cuid())
|
||
name String
|
||
slug String @unique
|
||
short String?
|
||
sector String?
|
||
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[]
|
||
settings TenantSettings?
|
||
modules TenantModule[]
|
||
|
||
@@map("tenants")
|
||
}
|
||
|
||
// Mandanten-Einstellungen: Quelle der ISMS-Template-Variablen (§4.1) + Branding/Policy
|
||
model TenantSettings {
|
||
id String @id @default(cuid())
|
||
tenantId String @unique @map("tenant_id")
|
||
orgName String @map("org_name")
|
||
orgShort String? @map("org_short")
|
||
address String?
|
||
sector String?
|
||
duns String?
|
||
ismsScope String? @map("isms_scope")
|
||
ismsScopeDescription String? @map("isms_scope_description")
|
||
roleManagement String? @map("role_management")
|
||
roleIsb String? @map("role_isb")
|
||
roleItLead String? @map("role_it_lead")
|
||
roleDpo String? @map("role_dpo")
|
||
logoKey String? @map("logo_key")
|
||
accent String?
|
||
locale String @default("de")
|
||
timezone String @default("Europe/Berlin")
|
||
tisaxLevel String @default("AL2") @map("tisax_level") // AL2 | AL3
|
||
securityPolicy Json @default("{}") @map("security_policy") // pw/mfa/session
|
||
smtp Json @default("{}")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("tenant_settings")
|
||
}
|
||
|
||
// Modul-Freischaltung je Mandant (Feature-Toggle) — gated Navigation, API und Daten
|
||
model TenantModule {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
moduleKey String @map("module_key")
|
||
enabled Boolean @default(true)
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([tenantId, moduleKey])
|
||
@@index([tenantId])
|
||
@@map("tenant_modules")
|
||
}
|
||
|
||
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)
|
||
// Erzwungener Passwortwechsel beim nächsten Login (Initial-/Reset-Passwort).
|
||
mustChangePassword Boolean @default(false) @map("must_change_password")
|
||
// Optionale TOTP-MFA je Nutzer (Paket C). recoveryCodes = SHA-256-Hashes.
|
||
mfaSecret String? @map("mfa_secret")
|
||
mfaEnrolledAt DateTime? @map("mfa_enrolled_at")
|
||
recoveryCodes Json @default("[]") @map("recovery_codes")
|
||
// 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")
|
||
}
|
||
|
||
// Plattform-Administratoren (Betreiber). Getrennter Store OHNE tenant_id, eigener
|
||
// Login (/platform/login), TOTP-MFA-Pflicht + Recovery-Codes. Kein Zugriff auf
|
||
// Mandanten-Fachdaten (nur Betrieb: Mandanten, Module, Lebenszyklus). §Phase-1-Härtung.
|
||
model PlatformAdmin {
|
||
id String @id @default(cuid())
|
||
email String @unique
|
||
passwordHash String @map("password_hash")
|
||
name String
|
||
status String @default("ACTIVE") // ACTIVE | LOCKED | DISABLED
|
||
mfaSecret String? @map("mfa_secret")
|
||
mfaEnrolledAt DateTime? @map("mfa_enrolled_at")
|
||
recoveryCodes Json @default("[]") @map("recovery_codes") // SHA-256-Hashes der Recovery-Codes
|
||
failedLogins Int @default(0) @map("failed_logins")
|
||
lockedUntil DateTime? @map("locked_until")
|
||
lastLoginAt DateTime? @map("last_login_at")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("platform_admins")
|
||
}
|
||
|
||
// Plattformweite Betriebseinstellungen (Singleton). Steuert u. a., ob MFA für
|
||
// Plattform-Admins verpflichtend ist (Default: aus → MFA optional, Paket C).
|
||
model PlatformSetting {
|
||
id String @id @default("singleton")
|
||
mfaRequired Boolean @default(false) @map("mfa_required")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("platform_settings")
|
||
}
|
||
|
||
// ── Assets & BIA (ein Modul, SPEC §4.1) ─────────────────────────────────────
|
||
|
||
enum AssetType {
|
||
INFORMATION
|
||
SYSTEM
|
||
APPLICATION
|
||
LOCATION
|
||
SUPPLIER
|
||
IT_SERVICE
|
||
SOFTWARE
|
||
PROJECT
|
||
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[]
|
||
|
||
// Lieferanten-/IT-Service-Erweiterungen (1:1) + Kind-Entitäten
|
||
supplierProfile SupplierProfile?
|
||
serviceProfile ITServiceProfile? @relation("serviceAsset")
|
||
serviceProvided ITServiceProfile[] @relation("serviceProvider")
|
||
softwareProfile SoftwareProfile? @relation("softwareAsset")
|
||
softwareProvided SoftwareProfile[] @relation("softwareProvider")
|
||
projectProfile ProjectProfile?
|
||
assessments SupplierAssessment[]
|
||
evidence SupplierEvidence[]
|
||
decisions ManagementDecision[]
|
||
contracts Contract[]
|
||
ndas Nda[]
|
||
subcontractors Subcontractor[]
|
||
raci ServiceControlResponsibility[]
|
||
maturity MaturityAssessment?
|
||
|
||
@@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")
|
||
}
|
||
|
||
// ── Lieferanten- & IT-Service-Management (VDA-ISA 2027 Kap. 6, NIS2 Art. 21(2)(d)) ──
|
||
// Grundsatz: Lieferanten UND IT-Services sind Assets (type SUPPLIER | IT_SERVICE).
|
||
// SupplierProfile/ITServiceProfile sind 1:1-Erweiterungen eines Asset; alle
|
||
// Kind-Entitäten hängen am Asset (subject) — keine parallele Datenhaltung.
|
||
|
||
enum SupplierLifecycle {
|
||
ACTIVE
|
||
ONBOARDING
|
||
UNDER_REVIEW
|
||
OFFBOARDED
|
||
}
|
||
|
||
enum AssessmentType {
|
||
QUESTIONNAIRE
|
||
SELF_ASSESSMENT
|
||
AUDIT
|
||
}
|
||
|
||
enum EvidenceKind {
|
||
CERTIFICATE
|
||
TISAX_LABEL
|
||
ATTESTATION
|
||
AUDIT_REPORT
|
||
SELF_ASSESSMENT
|
||
}
|
||
|
||
enum RaciResponsibility {
|
||
PROVIDER
|
||
US
|
||
SHARED
|
||
}
|
||
|
||
model SupplierProfile {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @unique @map("asset_id")
|
||
refNo Int @map("ref_no")
|
||
|
||
sector String?
|
||
serviceDesc String? @map("service_desc")
|
||
criticality Int @default(1)
|
||
dataCategories String[] @default([]) @map("data_categories")
|
||
nis2Relevant Boolean @default(false) @map("nis2_relevant")
|
||
lifecycle SupplierLifecycle @default(ACTIVE)
|
||
contact String?
|
||
tisaxLabel String? @map("tisax_label")
|
||
tisaxValidTo DateTime? @map("tisax_valid_to")
|
||
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")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([tenantId, refNo])
|
||
@@index([tenantId])
|
||
@@map("supplier_profiles")
|
||
}
|
||
|
||
model ITServiceProfile {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @unique @map("asset_id")
|
||
refNo Int @map("ref_no")
|
||
providerAssetId String? @map("provider_asset_id") // Asset(type SUPPLIER)
|
||
criticality Int @default(1)
|
||
internal Boolean @default(false)
|
||
notes String?
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
createdBy String? @map("created_by")
|
||
|
||
asset Asset @relation("serviceAsset", fields: [assetId], references: [id], onDelete: Cascade)
|
||
provider Asset? @relation("serviceProvider", fields: [providerAssetId], references: [id])
|
||
|
||
@@unique([tenantId, refNo])
|
||
@@index([tenantId])
|
||
@@map("it_service_profiles")
|
||
}
|
||
|
||
// Software-Whitelist als Asset (analog IT-Service, R11/VA-16): eine freigegebene
|
||
// Software ist ein Asset(type SOFTWARE) mit optionalem Anbieter (Asset type SUPPLIER).
|
||
model SoftwareProfile {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @unique @map("asset_id")
|
||
refNo Int @map("ref_no")
|
||
providerAssetId String? @map("provider_asset_id") // Asset(type SUPPLIER)
|
||
version String? // Version/Patch-Stand
|
||
approvalStatus SoftwareApprovalStatus @default(BEANTRAGT) @map("approval_status")
|
||
approvedBy String? @map("approved_by")
|
||
criticality Int @default(1)
|
||
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")
|
||
|
||
asset Asset @relation("softwareAsset", fields: [assetId], references: [id], onDelete: Cascade)
|
||
provider Asset? @relation("softwareProvider", fields: [providerAssetId], references: [id])
|
||
|
||
@@unique([tenantId, refNo])
|
||
@@index([tenantId])
|
||
@@map("software_profiles")
|
||
}
|
||
|
||
enum SoftwareApprovalStatus {
|
||
BEANTRAGT // Freigabe beantragt
|
||
FREIGEGEBEN // freigegeben (Whitelist)
|
||
GESPERRT // gesperrt/untersagt
|
||
}
|
||
|
||
// Informationssicherheit in Projekten (R01/VA-19): ein Projekt ist ein Asset(type PROJECT)
|
||
// mit Kritikalität (C/I/A am Asset) und Risiko-Verknüpfung (RiskAsset).
|
||
model ProjectProfile {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @unique @map("asset_id")
|
||
refNo Int @map("ref_no")
|
||
classification String? // IS-Klassifizierung
|
||
isbInvolved Boolean @default(false) @map("isb_involved")
|
||
status ProjectStatus @default(GEPLANT)
|
||
notes String?
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
createdBy String? @map("created_by")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([tenantId, refNo])
|
||
@@index([tenantId])
|
||
@@map("project_profiles")
|
||
}
|
||
|
||
enum ProjectStatus {
|
||
GEPLANT
|
||
LAUFEND
|
||
ABGESCHLOSSEN
|
||
ABGEBROCHEN
|
||
}
|
||
|
||
// Bewertung & Nachweise (6.1.1)
|
||
model SupplierAssessment {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @map("asset_id")
|
||
type AssessmentType
|
||
score Int?
|
||
selfScore Int? @map("self_score")
|
||
verifiedScore Int? @map("verified_score")
|
||
date DateTime?
|
||
nextReview DateTime? @map("next_review")
|
||
result String?
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("supplier_assessments")
|
||
}
|
||
|
||
model SupplierEvidence {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @map("asset_id") // Subjekt (Lieferant ODER Service)
|
||
kind EvidenceKind
|
||
name String?
|
||
protectsCia String? @map("protects_cia")
|
||
validTo DateTime? @map("valid_to")
|
||
adequacyChecked Boolean @default(false) @map("adequacy_checked")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("supplier_evidence")
|
||
}
|
||
|
||
model ManagementDecision {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @map("asset_id")
|
||
reasonNoAudit String @map("reason_no_audit")
|
||
decision String
|
||
decidedBy String? @map("decided_by")
|
||
date DateTime @default(now())
|
||
recordRef String? @map("record_ref")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("management_decisions")
|
||
}
|
||
|
||
// Vertrag & Geheimhaltung (6.1.1 / 6.1.2)
|
||
model Contract {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @map("asset_id")
|
||
type String @default("service")
|
||
avDpa Boolean @default(false) @map("av_dpa")
|
||
securityClauses Boolean @default(false) @map("security_clauses")
|
||
flowdown Boolean @default(false)
|
||
customerRequirementsPassed Boolean @default(false) @map("customer_requirements_passed")
|
||
validFrom DateTime? @map("valid_from")
|
||
validTo DateTime? @map("valid_to")
|
||
reference String?
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("contracts")
|
||
}
|
||
|
||
model Nda {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @map("asset_id")
|
||
parties String?
|
||
infoScope String? @map("info_scope")
|
||
subject String?
|
||
validFrom DateTime? @map("valid_from")
|
||
validTo DateTime? @map("valid_to")
|
||
obligations String?
|
||
beyondTerm Boolean @default(false) @map("beyond_term")
|
||
extensionStatus String? @map("extension_status")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("ndas")
|
||
}
|
||
|
||
model Subcontractor {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @map("asset_id")
|
||
name String
|
||
flowdownObligation Boolean @default(false) @map("flowdown_obligation")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("subcontractors")
|
||
}
|
||
|
||
// Verantwortung je IT-Service (6.1.3) — RACI über den ISA-Control-Katalog
|
||
model ServiceControlResponsibility {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @map("asset_id") // Asset(type IT_SERVICE)
|
||
controlRef String @map("control_ref")
|
||
title String?
|
||
applicable Boolean @default(true)
|
||
responsibility RaciResponsibility @default(SHARED)
|
||
evidenceRef String? @map("evidence_ref")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("service_control_responsibilities")
|
||
}
|
||
|
||
// Reifegrad je Subjekt — Fragebogen-Selbstauskunft, durch Nachweise verifiziert, ISB-Freigabe
|
||
model MaturityAssessment {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @unique @map("asset_id")
|
||
selfValue Float? @map("self_value")
|
||
verifiedValue Float? @map("verified_value")
|
||
computedValue Float? @map("computed_value")
|
||
isbValue Float? @map("isb_value")
|
||
isbJustification String? @map("isb_justification")
|
||
approvedBy String? @map("approved_by")
|
||
approvedAt DateTime? @map("approved_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("maturity_assessments")
|
||
}
|
||
|
||
model AuditLog {
|
||
id String @id @default(cuid())
|
||
// Nullable: Plattform-Ereignisse (scope=platform, z. B. Superadmin-Login) haben keinen Mandantenbezug.
|
||
tenantId String? @map("tenant_id")
|
||
scope String @default("tenant") // tenant | platform
|
||
actorId String? @map("actor_id")
|
||
action String // create | update | delete | login | denied | 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")
|
||
}
|
||
|
||
// Generisches Aufgaben-/Freigabe-Modell (erweiterbar). Erster Typ: policy_approval
|
||
// (Richtlinien-Freigabe an eine konkrete Person). Kommentare/Statuswechsel historisiert.
|
||
model Task {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
type String // policy_approval | …
|
||
title String
|
||
status String @default("OPEN") // OPEN | DONE | REJECTED | CANCELLED
|
||
entityType String? @map("entity_type") // z. B. policy_document
|
||
entityId String? @map("entity_id")
|
||
entityRef String? @map("entity_ref") // Deep-Link-Referenz, z. B. Policy-Code
|
||
assigneeId String? @map("assignee_id") // wer bearbeiten/freigeben soll
|
||
createdById String? @map("created_by_id")
|
||
dueDate DateTime? @map("due_date")
|
||
resolvedById String? @map("resolved_by_id")
|
||
resolvedAt DateTime? @map("resolved_at")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
comments TaskComment[]
|
||
|
||
@@index([tenantId, status])
|
||
@@index([tenantId, assigneeId, status])
|
||
@@map("tasks")
|
||
}
|
||
|
||
model TaskComment {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
taskId String @map("task_id")
|
||
authorId String? @map("author_id")
|
||
kind String @default("comment") // comment | approve | reject | submit
|
||
body String
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId, taskId])
|
||
@@map("task_comments")
|
||
}
|
||
|
||
// ── Richtlinien & Verfahren (VDA-ISA 2027 Vorlagenpaket) ──────────────────────
|
||
// Dokumente werden aus ihrer echten Markdown-Vorlage gerendert; Variablen,
|
||
// Feature-Flags und Baseline-Parameter sind die eine Pflegestelle (§7).
|
||
|
||
enum PolicyDocType {
|
||
LEITLINIE // L00
|
||
RICHTLINIE // R01–R14
|
||
VERFAHREN // VA-01–VA-13
|
||
REGISTER // verwaltete Tabellen (Baseline, Nachweisregister, …)
|
||
HANDBUCH // Anwender-Handbuch
|
||
EIGENES // Word-Upload
|
||
}
|
||
|
||
enum PolicyStatus {
|
||
ENTWURF
|
||
IN_FREIGABE
|
||
FREIGEGEBEN
|
||
ARCHIVIERT
|
||
}
|
||
|
||
model PolicyDocument {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
code String // L00 | R01..R14 | VA-01..VA-13 | BASELINE | NACHWEIS | ISA_MAPPING
|
||
type PolicyDocType
|
||
title String
|
||
version String @default("1.0")
|
||
status PolicyStatus @default(FREIGEGEBEN)
|
||
owner String?
|
||
policyCode String? @map("policy_code") // bei VA: operationalisierte Richtlinie (R..)
|
||
fulfills String[] @default([]) // bei VA: erfüllte Anforderungs-IDs
|
||
rawMarkdown String @map("raw_markdown") // Vorlagen-Markdown mit Platzhaltern
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
// Freigabe-Workflow (Vier-Augen): wer eingereicht/genehmigt hat
|
||
submittedBy String? @map("submitted_by")
|
||
approvedBy String? @map("approved_by")
|
||
approvedAt DateTime? @map("approved_at")
|
||
// Schutzbedarf-/TISAX-Level-Override je Richtlinie: null = global, "AL2" | "AL3"
|
||
protectionOverride String? @map("protection_override")
|
||
// Lifecycle (nicht-destruktiver Re-Import): gesetzt, wenn das Dokument nicht mehr
|
||
// im Vorlagenpaket enthalten ist. null = aktiv. Orthogonal zum Freigabe-`status`.
|
||
archivedAt DateTime? @map("archived_at")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@unique([tenantId, code])
|
||
@@index([tenantId])
|
||
@@map("policy_documents")
|
||
}
|
||
|
||
model PolicyRequirement {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
reqId String @map("req_id") // z. B. 4.1.2-M1
|
||
policyCode String @map("policy_code") // R08
|
||
control String // 4.1.2
|
||
obligation String // MUSS | SOLL
|
||
condition String? // FLAG_… oder null
|
||
requirement String
|
||
implementation String
|
||
vaCodes String[] @default([]) @map("va_codes") // operationalisierende Verfahren
|
||
nachweisLink String? @map("nachweis_link")
|
||
// Lifecycle (nicht-destruktiver Re-Import): gesetzt, wenn die Anforderung nicht mehr
|
||
// im Vorlagenpaket enthalten ist. null = aktiv.
|
||
archivedAt DateTime? @map("archived_at")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@unique([tenantId, reqId])
|
||
@@index([tenantId])
|
||
@@index([tenantId, control])
|
||
@@map("policy_requirements")
|
||
}
|
||
|
||
// Wizard-Variablen und Feature-Flags (variables.schema.json) — eine Pflegestelle
|
||
model PolicyVariable {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
key String // UPPER_SNAKE, z. B. PW_MIN_LENGTH / FLAG_CLOUD_USED
|
||
title String
|
||
kind String // string | boolean
|
||
groupName String? @map("group_name")
|
||
value String // aktueller Wert (Default aus Schema); Flags als "true"/"false"
|
||
required Boolean @default(false)
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@unique([tenantId, key])
|
||
@@index([tenantId])
|
||
@@map("policy_variables")
|
||
}
|
||
|
||
model PolicyBaselineParam {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
blId String @map("bl_id") // BL-IAM-01
|
||
section String // "1. Identitäts- und Zugriffsmanagement"
|
||
name String
|
||
vorgabe String // Vorlagentext (kann {{VARIABLE}} enthalten)
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@unique([tenantId, blId])
|
||
@@index([tenantId])
|
||
@@map("policy_baseline_params")
|
||
}
|
||
|
||
model PolicyEvidence {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
nr Int
|
||
policyCode String @map("policy_code")
|
||
nachweis String
|
||
quelle String
|
||
verantwortlich String
|
||
turnus String
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@index([tenantId])
|
||
@@map("policy_evidence")
|
||
}
|
||
|
||
// ── Verwaltete Register-Tabellen (§7b) ───────────────────────────────────────
|
||
|
||
// Verschlüsselungsmechanismen-Register (VA-07 Kryptokonzept & Schlüsselverwaltung)
|
||
model CryptoEntry {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
dienst String
|
||
schluessel String
|
||
algorithmus String?
|
||
ablaufdatum DateTime? @map("ablauf_datum")
|
||
verantwortlich String
|
||
speicherort String?
|
||
baselineRef String? @map("baseline_ref") // z. B. BL-CRY-02
|
||
notes String?
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@index([tenantId])
|
||
@@map("crypto_entries")
|
||
}
|
||
|
||
// Klassifizierungs-Handhabungsmatrix (R02 / VA-08) — Schutzklassen × Aspekte
|
||
model ClassificationClass {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
name String
|
||
description String?
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
rules HandlingRule[]
|
||
|
||
@@index([tenantId])
|
||
@@map("classification_classes")
|
||
}
|
||
|
||
model HandlingAspect {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
name String
|
||
category String?
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
rules HandlingRule[]
|
||
|
||
@@index([tenantId])
|
||
@@map("handling_aspects")
|
||
}
|
||
|
||
model HandlingRule {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
classId String @map("class_id")
|
||
aspectId String @map("aspect_id")
|
||
text String
|
||
|
||
class ClassificationClass @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||
aspect HandlingAspect @relation(fields: [aspectId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([classId, aspectId])
|
||
@@index([tenantId])
|
||
@@map("handling_rules")
|
||
}
|
||
|
||
// Risiko-Bewertungsmatrix (R03 / VA-09) — zentrale Pflegestelle (Defaults FB-80-04)
|
||
model RiskMatrixClass {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
name String // Niedrig | Mittel | Hoch | Kritisch
|
||
maxScore Int @map("max_score") // obere Schwelle des Risikowerts
|
||
acceptance String // Akzeptanzinstanz
|
||
tone String // ok | warn | orange | risk
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@index([tenantId])
|
||
@@map("risk_matrix_classes")
|
||
}
|
||
|
||
model RiskEwLevel {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
level Int
|
||
label String
|
||
definition String
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@index([tenantId])
|
||
@@map("risk_ew_levels")
|
||
}
|
||
|
||
model RiskDamageDimension {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
name String
|
||
levels Json // { "1": "…", "2": "…", "3": "…", "4": "…" }
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@index([tenantId])
|
||
@@map("risk_damage_dimensions")
|
||
}
|
||
|
||
// Anwender-Handbuch (§9.7) — kuratierte Themen mit Deep-Links; Werte via Templating
|
||
model HandbookTopic {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
category String
|
||
title String
|
||
bodyMd String @map("body_md") // Markdown mit {{VARIABLE}} (bleibt via Baseline synchron)
|
||
sourceRefs String[] @default([]) @map("source_refs") // Deep-Links, z. B. R08#4.1.2
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@index([tenantId])
|
||
@@map("handbook_topics")
|
||
}
|
||
|
||
// Generisches verwaltetes Register (GAP-Report WP3.0): benannte Register mit
|
||
// Register-ID (REG-…), definierten Pflichtspalten, Review-Turnus und optionalen
|
||
// Cross-Links an Lieferanten (R13/VA-10) und Asset-Inventar (R02/VA-08).
|
||
model ManagedRegister {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
code String // REG-PROJECTS, REG-SW-WHITELIST, …
|
||
title String
|
||
description String?
|
||
columns Json @default("[]") // [{ key, label }] Pflichtspalten
|
||
reviewCycle String? @map("review_cycle")
|
||
responsible String?
|
||
supplierLink Boolean @default(false) @map("supplier_link") // Cross-Link Lieferant (Asset)
|
||
assetLink Boolean @default(false) @map("asset_link") // Cross-Link Asset
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
rows RegisterRow[]
|
||
|
||
@@unique([tenantId, code])
|
||
@@index([tenantId])
|
||
@@map("managed_registers")
|
||
}
|
||
|
||
model RegisterRow {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
registerId String @map("register_id")
|
||
values Json @default("{}") // { spaltenKey: wert }
|
||
supplierRef String? @map("supplier_ref") // Asset-ID (Lieferant)
|
||
assetRef String? @map("asset_ref") // Asset-ID
|
||
orderIdx Int @default(0) @map("order_idx")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
register ManagedRegister @relation(fields: [registerId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId, registerId])
|
||
@@map("register_rows")
|
||
}
|