Files
certvia/prisma/schema.prisma
T
msolarczekandClaude Opus 4.8 db36c795bd Fundament Benutzer-/Rollenverwaltung: Schema, Passwort-Policy, Force-Change
Vorbereitung für die Benutzer- & Rollenverwaltung (Pakete A–C) ohne E-Mail-Flow.

- Schema: User.mustChangePassword (erzwungener Wechsel), User.mfaEnrolledAt +
  recoveryCodes (optionale Nutzer-MFA, Paket C); neues Singleton PlatformSetting
  mit mfaRequired (Default aus → MFA optional). Migration additiv + Grant/Seed.
- RBAC: neue Permission role:manage (Rollenverwaltung), dem Mandanten-Admin
  zugewiesen; für den bestehenden Demo-Mandanten nachgezogen (neue Tenants via
  provision/seed automatisch).
- Passwort-Policy: lib/password-policy.ts (client-safe Validierung/Beschreibung,
  Defaults + Ableitung aus TenantSettings.securityPolicy) und server/password.ts
  (Argon2id-Hash + policy-konformer Einmal-Passwort-Generator).
- Force-Change-Flow: /change-password (außerhalb der (app)-Shell) + Self-Service-
  Action changeOwnPassword (policy-geprüft, Audit). (app)-Layout erzwingt den
  Wechsel autoritativ aus der DB und sperrt deaktivierte Konten (Abmelden-Screen).

tsc + lint + build + Guard-Check grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 09:18:05 +02:00

991 lines
32 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
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 14 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")
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 14 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 14, 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 15)
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,004,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")
}
// 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")
}
// ── 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 // R01R14
VERFAHREN // VA-01VA-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")
}