Lieferantenmodul neu: Asset-basiert + Anforderungs-Engine (Phase 1)

Referenz: docs/ISMS-Lieferantenmanagement-GEFIM.html

Architektur: Lieferanten (und IT-Services) sind jetzt Assets.
- AssetType += IT_SERVICE; SupplierProfile/ITServiceProfile als 1:1-
  Erweiterung eines Asset; alle Kind-Entitäten (Contract, Nda,
  SupplierEvidence, SupplierAssessment, ManagementDecision, Subcontractor,
  ServiceControlResponsibility, MaturityAssessment) hängen am Asset
  (keine parallele Datenhaltung). Migration + RLS; Demo neu geseedet.
- Lieferanten erscheinen im Asset-Inventar (Filter „Lieferant"/„IT-Service"),
  tragen C/I/A, nutzbar in Graph/BIA/Risiko.

Anforderungs-Engine (Herzstück, src/lib/supplier.ts):
- Schutzbedarf aus max C/I/A der verknüpften Assets → Stufe normal/hoch/
  sehr hoch; schaltet Anforderungen gestuft scharf (must/should/high/veryhigh)
- Flache Anforderungsliste (nach Themen gruppiert) mit Status-Ableitung aus
  den hinterlegten Objekten, Referenz + Herkunfts-Control (6.1.1–6.1.3)
- Gate-Logik: sehr hoch ohne Audit/Label → Kompensation (Management-
  entscheidung UND verknüpftes Risiko); „Risiko anlegen" erzeugt echten Risk
- Konformität (Konform/Teilweise/Lücken/Handlung nötig) + berechneter
  Reifegrad; ISB-Freigabe (Abweichung nur mit Begründung → Audit-Log)
- Cockpit-Detail mit Schutzbedarf-Banner + Live-Simulation der Stufe

Verifiziert: Register mit abgeleiteter Stufe/Reifegrad/Konformität,
Cockpit-Simulation (Tiers werden inaktiv), Gate bei sehr hoch ohne Audit,
„Risiko anlegen" erzeugt R-005 im Risikomodul.

Offen (Phase 2): Fragebogen-Builder, Self-Service-Portal, IT-Service-
Detail mit RACI-Matrix, Klick im Asset-Inventar öffnet Cockpit,
Scheduler-Automatisierung.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-03 11:57:17 +02:00
co-authored by Claude Opus 4.8
parent 32ab91efbf
commit dc884062b2
17 changed files with 1605 additions and 972 deletions
+144 -150
View File
@@ -122,6 +122,7 @@ enum AssetType {
APPLICATION
LOCATION
SUPPLIER
IT_SERVICE
PERSON
DATA
}
@@ -166,7 +167,19 @@ model Asset {
relationsTo AssetRelation[] @relation("relationTo")
processAssets ProcessAsset[]
riskAssets RiskAsset[]
supplierLinks SupplierAsset[]
// 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])
@@ -397,9 +410,12 @@ model Vulnerability {
@@map("vulnerabilities")
}
// ── Lieferantenmanagement (SPEC §4.14, VDA-ISA 2027 Kap. 6, NIS2 Art. 21(2)(d)) ──
// ── 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 SupplierStatus {
enum SupplierLifecycle {
ACTIVE
ONBOARDING
UNDER_REVIEW
@@ -412,13 +428,6 @@ enum AssessmentType {
AUDIT
}
enum AssessmentStatus {
SENT
RECEIVED
EVALUATED
OVERDUE
}
enum EvidenceKind {
CERTIFICATE
TISAX_LABEL
@@ -427,99 +436,132 @@ enum EvidenceKind {
SELF_ASSESSMENT
}
enum ResponsibleParty {
CLIENT
SUPPLIER
enum RaciResponsibility {
PROVIDER
US
SHARED
}
model Supplier {
model SupplierProfile {
id String @id @default(cuid())
tenantId String @map("tenant_id")
refNo Int @map("ref_no") // Anzeige "L-001"
assetId String @unique @map("asset_id")
refNo Int @map("ref_no")
name String
sector String?
services String? // erbrachte Leistung / IT-Services
criticality Int @default(1) // 14
dataCategories String[] @default([]) @map("data_categories")
// Schutzbedarf der verarbeiteten Informationen (C/I/A 14)
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)
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?
nextReview DateTime? @map("next_review")
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")
assessments SupplierAssessment[]
contracts Contract[]
ndas Nda[]
evidence SupplierEvidence[]
responsibilities ServiceResponsibility[]
subcontractors Subcontractor[]
decisions ManagementDecision[]
controlMaturity SupplierControlMaturity[]
assetLinks SupplierAsset[]
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
@@unique([tenantId, refNo])
@@index([tenantId])
@@map("suppliers")
@@map("supplier_profiles")
}
// 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")
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?
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
createdBy String? @map("created_by")
@@unique([supplierId, assetId])
asset Asset @relation("serviceAsset", fields: [assetId], references: [id], onDelete: Cascade)
provider Asset? @relation("serviceProvider", fields: [providerAssetId], references: [id])
@@unique([tenantId, refNo])
@@index([tenantId])
@@map("supplier_assets")
@@map("it_service_profiles")
}
// Bewertung & Nachweise (6.1.1)
model SupplierAssessment {
id String @id @default(cuid())
tenantId String @map("tenant_id")
supplierId String @map("supplier_id")
id String @id @default(cuid())
tenantId String @map("tenant_id")
assetId String @map("asset_id")
type AssessmentType
status AssessmentStatus @default(SENT)
score Int? // 0100 Scoring
score Int?
selfScore Int? @map("self_score")
verifiedScore Int? @map("verified_score")
date DateTime?
nextReview DateTime? @map("next_review")
nextReview DateTime? @map("next_review")
result String?
createdAt DateTime @default(now()) @map("created_at")
createdAt DateTime @default(now()) @map("created_at")
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
asset Asset @relation(fields: [assetId], 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")
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")
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
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")
@@ -528,120 +570,72 @@ model Contract {
model Nda {
id String @id @default(cuid())
tenantId String @map("tenant_id")
supplierId String @map("supplier_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?
extensionStatus String? @map("extension_status") // z. B. offen | verlängert | ausgelaufen
beyondTerm Boolean @default(false) @map("beyond_term")
extensionStatus String? @map("extension_status")
createdAt DateTime @default(now()) @map("created_at")
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
asset Asset @relation(fields: [assetId], 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")
assetId String @map("asset_id")
name String
flowdownObligation Boolean @default(false) @map("flowdown_obligation")
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
asset Asset @relation(fields: [assetId], 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")
// 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")
supplier Supplier @relation(fields: [supplierId], references: [id], onDelete: Cascade)
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
@@index([tenantId])
@@map("management_decisions")
@@map("service_control_responsibilities")
}
// 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
// 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")
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 …
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
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) // 05
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")
@@map("maturity_assessments")
}
model AuditLog {