Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
14 KiB
Implementierungsfaden — TISAX-Onboarding-Wizard Neustruktur
Status: Entwurf · Grundlage: Konzept v2 (4 Ebenen, nach TISAX-Fachprüfung) + IST-Analyse des Task-Moduls (Branch
dev-tasks-kanban,ce1e847). Ziel: Aus dem linearen 9-Schritt-Monolithen/onboardingwerden vier Ebenen — Fundament → Strukturanalyse → Cockpit → Audit — informationszentriert, prozessgeführt erhoben, bereichsbasiert umgesetzt.
0. Ausgangslage & Auswirkung der Kanban-Anpassung
Das Task-Modul wurde zwischenzeitlich auf ein Kanban-Board umgestellt (src/components/kanban-board.tsx, src/app/(app)/tasks/page.tsx, Action updateTaskStatus, Status IN_PROGRESS). Das ist die Basis für Ebene 3 — das Bereichs-Board wird additiv darauf gebaut, nicht neu.
Bereits vorhanden (nutzbar): generisches Task-Modell, Kanban mit DnD-Statuswechsel, assigneeId + createdById + Vier-Augen-Freigabe, PROPOSED→OPEN/DISCARDED-Vorschlagsmechanik, Wizard-Trigger (task-triggers.ts, proposeTasksFromTriggers), polymorphe links-Json + harte entityType/entityId-Kopplung, Teil-Sync Task→Objekt bei Policy-Freigabe (approveTask).
Fehlt (dieser Plan liefert es): domain/Bereich, RACI/Mitwirkende, orderIdx, Recurrence/Wiedervorlage, Auto-Completion-Deckel, Bereichs-/Personen-Filter, harte Control-/Evidence-Verknüpfung, Information als Kernobjekt.
Altlasten (Vorarbeit M0):
TASK_STATUSESinsrc/lib/tasks.tsenthältIN_PROGRESSnicht, obwohl Board/Actions ihn nutzen → Inkonsistenz beheben.src/app/(app)/tasks/page.tsx:166referenziertopen— im File nicht definiert (nurmyOpen/active/proposals/terminal). Verifizieren & fixen.
1. Datenmodell (Prisma-Migrationen)
Konventionen wie im Bestand: cuid()-IDs, tenantId, @@map(snake_case), @@unique([tenantId, …]). Enums additiv; freie String-Statusfelder brauchen keine Migration.
1.1 Werte-Modell: Information IST ein (primärer) Asset — kein Extra-Objekt
Korrektur ggü. erstem Entwurf. Ein Informationswert ist kein neues Objekt neben dem Asset, sondern ein primärer Asset (ISO 27005 / TISAX: primäre Werte = Informationen + Prozesse; sekundäre = Träger). Euer Schema bildet das bereits ab:
AssetType.INFORMATION/DATA= primäre Werte;SYSTEM/APPLICATION/LOCATION/SUPPLIER/IT_SERVICE/SOFTWARE/PERSON= sekundär/Träger.ProcessAsset.role = PRIMARY | SECONDARYtrennt primär/sekundär je Prozess-Beziehung.AssetRelationbildet Träger-Abhängigkeiten ab;confidentiality/integrity/availabilityliegen schon amAsset.
→ Kein InformationValue/ProcessInformation/InformationAsset. Stattdessen kleine Ergänzungen am Bestand:
enum InfoLabel { NONE INFO_HIGH INFO_VERY_HIGH PROTOTYPE PERSONAL_DATA }
model Asset {
// … Bestand (type, C/I/A, ownerId, status, tags) …
normalizedName String? // Dedup-Schlüssel (s. 2.1)
label InfoLabel @default(NONE) // Info hoch/sehr hoch, Prototyp, personenbezogen → steuert Scope/Bereiche
@@unique([tenantId, normalizedName]) // verhindert Exakt-Duplikate (v.a. type INFORMATION/DATA)
}
Primär/sekundär bleibt über ProcessAsset.role. Optional als intrinsische Klassifikation Asset.tier (PRIMARY|SUPPORTING), ableitbar aus type — nur einführen, falls die Rolle je Prozess nicht ausreicht (s. offene Entscheidung 4.1).
Schutzbedarf: bleibt am Asset. Primäre Informations-Assets tragen den echten C/I/A-Wert; sekundäre erben per Maximum der von ihnen getragenen primären Assets.
„Erhebung über den Prozess": Im Prozess-Schritt werden primäre Informations-Assets erfasst (Dedup/Autocomplete gegen bestehende Assets, 2.1), dann Träger-Assets via ProcessAsset(SECONDARY) + AssetRelation verknüpft. Anker = das primäre Informations-Asset. Keine Datenmigration nötig — nur additive Spalten.
1.2 Standard-Prozess-Katalog (Ebene 2)
model ProcessCatalogEntry { // global, kein tenantId
id String @id @default(cuid())
code String @unique
name String
category ProcessCategory // CORE | MANAGEMENT | SUPPORT (Neben→SUPPORT)
suggestedAssetTypes AssetType[]
suggestedRiskCodes String[] // → RiskCatalogEntry.code
suggestedInfoLabels InfoLabel[]
@@map("process_catalog")
}
1.3 Team / Funktionszuordnung (Ebene 1)
Heute sind ISMS-Rollen read-only Variablen. Neu: echte Zuordnung Funktion→User(n) inkl. „unbesetzt".
model ProjectFunctionAssignment {
id String @id @default(cuid())
tenantId String
functionKey String // ISB, PM, HR_LEAD, IT_LEAD, BCM, AUDITOR_INT, DPO …
userId String? // null = unbesetzt → erzeugt Task „Funktion besetzen"
domain Domain? // Default-Bereich dieser Funktion (Sichtbarkeit)
invitedEmail String? // Einladung, falls Account noch nicht existiert
createdAt DateTime @default(now())
@@index([tenantId, functionKey])
@@map("project_function_assignments")
}
1.4 Bereich (Domain) & Control→RACI-Mapping (Ebene 3)
enum Domain { GOVERNANCE HR PHYSICAL BCM IT PROCUREMENT COMPLIANCE DATA_PROTECTION PROTOTYPE }
enum RaciKind { RESPONSIBLE ACCOUNTABLE CONSULTED INFORMED }
model ControlDomainMap { // global default; tenant-Override via tenantId
id String @id @default(cuid())
tenantId String? // null = globaler Default
control String // "3.1.4" oder Kapitel-Präfix "3"
domain Domain
raci RaciKind @default(RESPONSIBLE)
functionKey String? // Default-Verantwortliche Funktion
@@map("control_domain_map")
}
Seed-Defaults (Kapitel → Bereich), fachlich korrigiert:
| Kapitel | Domain | Anmerkung |
|---|---|---|
| 1 | GOVERNANCE | ISB + Management |
| 2 | HR | |
| 3 (phys.) | PHYSICAL | |
| 3 (BCM) | BCM | aus K3 gelöst — eigener Bereich |
| 4 IAM | IT + HR (CONSULTED) | Joiner-Mover-Leaver |
| 5 | IT | |
| 6 | PROCUREMENT + ISB/Recht (CONSULTED) | |
| 7 | COMPLIANCE + DPO/IT (CONSULTED) | |
| Prototyp | PROTOTYPE | nur bei Label |
| Datenschutz | DATA_PROTECTION | nur bei Label |
1.5 Task-Erweiterungen (Ebene 3)
model Task {
// … Bestand …
domain Domain? // Bereich (Default aus ControlDomainMap)
orderIdx Int @default(0) // manuelle Sortierung je Bereich
recurrence String? // ISO-8601-Dauer/RRULE, z.B. "P1Y"
remindAt DateTime?
effectiveUntil DateTime? // Wirksamkeitsintervall (Wiedervorlage)
participants TaskParticipant[]
@@index([tenantId, domain, status])
}
model TaskParticipant { // RACI zusätzlich zu assigneeId (=primär Responsible)
id String @id @default(cuid())
tenantId String
taskId String
userId String
raci RaciKind
@@unique([tenantId, taskId, userId, raci])
@@map("task_participants")
}
1.6 Nachweis-/Evidence-Register (Ebene 3+4)
model Evidence {
id String @id @default(cuid())
tenantId String
title String
kind String // record | protocol | screenshot | export
fileRef String?
taskId String?
control String?
validFrom DateTime?
validUntil DateTime? // koppelt an Task.effectiveUntil
createdAt DateTime @default(now())
@@index([tenantId, control])
@@map("evidence")
}
2. Querschnitts-Bausteine
2.1 Dedup-Erkennung & Autovervollständigung für Informationswerte
Kernanforderung: Menschen erfassen über den Prozess, sollen aber denselben Wert nicht doppelt anlegen (auch nicht bei abweichender Groß-/Kleinschreibung).
- Normalisierung →
normalizedName:trim→ Mehrfach-Whitespace kollabieren → Unicode-NFC →toLowerCase(locale-awarede) → Umlaut-Faltung (ä→ae, ö→oe, ü→ue, ß→ss) → Satzzeichen entfernen. Ergebnis ist der@@unique-Schlüssel.- Beispiel: „Kundendaten", „kundendaten", „ Kundendaten " → alle
kundendaten.
- Beispiel: „Kundendaten", „kundendaten", „ Kundendaten " → alle
- Exakt-Duplikate: durch
@@unique([tenantId, normalizedName])unmöglich; bei Kollision wird das bestehende Asset verknüpft (ProcessAsset) statt neu angelegt. - Autovervollständigung: As-you-type-Suche im Server (
searchAssets(query), gefiltert auftype INFORMATION/DATA) gegen (a) das Tenant-Asset-Register und (b) globale Katalog-Vorschläge. Treffer zeigen „bereits erfasst in Prozess X" → ein Klick verknüpft. - Fuzzy-Nah-Duplikate (weiche Warnung): Trigramm-/Levenshtein-Ähnlichkeit auf
normalizedName; ab Schwelle „Meintest du Kundendaten?" vor dem Anlegen. Bei Postgres optionalpg_trgm(similarity()), sonst in-app Levenshtein auf der Kandidatenliste. - UI: Combobox (bestehendes
components.json/shadcn-Setup) mit Vorschlagsliste, Badge „neu" vs. „verknüpfen".
2.2 Auto-Completion-Engine — gedeckelt
Zentrale Funktion syncTaskFromObject(entityType, entityId), aufgerufen bei Statuswechsel von Policy/Risk/Control/Asset. Fachprüfungs-Regel: Existenz ≠ Wirksamkeit.
| Auslöser | Task geht auf … | Deckel |
|---|---|---|
PolicyDocument.status = FREIGEGEBEN |
DONE (dokumentiert) |
Reifegrad 3 / audit-ready nur mit verknüpftem Evidence + Vier-Augen |
| Asset C/I/A + Owner gesetzt | DONE |
— |
Risk.status = ACCEPTED/CLOSED |
DONE |
Restrisiko gesetzt |
ControlImplementation.status = erledigt |
DONE (umgesetzt) |
Reifegrad-Anhebung getrennt bestätigen |
- Kein Auto-
DONEohne mindestens dokumentierten Zustand; „audit-ready" ist ein separater, manueller Schritt mit Nachweis. - Wiedervorlage: Tasks mit
recurrenceerzeugen beiDONEautomatisch eine Folge-Task mit neuemdueDate/effectiveUntil(Serienlogik).
2.3 Bereichs-Sichtbarkeit & RACI
- Default-Sicht: eigene Bereiche (aus
ProjectFunctionAssignment.domaindes Users) + eigene/zugewiesene Tasks + Pool. - PM + ISB:
task:read_all(existiert bereits) → alle Bereiche. - Gezielte Mitwirkung: Eintrag in
TaskParticipant(CONSULTED/INFORMED) macht Task für die Person sichtbar, ohne ihr den ganzen Bereich zu öffnen. - Kanban erhält einen Bereichs-Selektor (Lane-Filter) zusätzlich zu den Status-Spalten; Sortierung je Bereich nach
orderIdx, dann Priorität.
3. Meilenstein-Faden (jeder Schritt einzeln lauffähig)
M0 · Vorarbeit & Bereinigung
TASK_STATUSESumIN_PROGRESSergänzen (src/lib/tasks.ts);open-Bug intasks/page.tsx:166verifizieren/fixen.- Enums
Domain,RaciKind,InfoLabel,InfoProcessRole+ Task-Felderdomain/orderIdx(nullable) migrieren — noch ohne UI-Wirkung. - Risiko: minimal. Kein Verhaltensbruch.
M1 · Ebene 1 „Fundament" (Wizard-Umbau Teil 1)
- Onboarding-Steps in
src/lib/onboarding/register-steps.tsneu ordnen/ergänzen:context→scope(einfrieren) →policy(Leitlinie, verlinkt Richtlinien-Modul) →roles(Team) →criteria(Risikoakzeptanz + C/I/A-Skala). - Team-Entität
ProjectFunctionAssignment+ Zuweisen/Einladen-Flow (Schrittroles). Neue Funktionen: BCM, unabhängiger interner Auditor, DPO, Asset-/Risk-Owner-Kennzeichnung. - Leitlinie als Fundament-Artefakt (nicht als Cockpit-Task).
- Betroffen:
src/app/(app)/onboarding/steps/*,src/server/roles.ts,src/server/actions/onboarding*.ts.
M2 · Ebene 2 „Strukturanalyse" (Information = primärer Asset)
- Additive
Asset-Spalten (normalizedName,label) +ProcessCatalogEntrymigrieren. Keine Datenmigration — Bestand bleibt gültig. - Wizard-Fluss prozessgeführt: Prozesse wählen (Katalog) → je Prozess primäre Informations-Assets erfassen (Combobox mit Dedup/Autocomplete, 2.1) → Träger-Assets (
ProcessAsset SECONDARY: System/Anwendung/Raum/Person/Dienstleister/Standort) → Schutzbedarf am Asset (Maximum/Kumulation/Verteilung) → Risiken aus Katalog. - Betroffen: neue Steps
processes/assets/protection/risks,RiskCatalogEntry-Matching (existiert).
M3 · Ebene 3 „Cockpit" auf bestehendem Kanban
ControlDomainMapseeden (Tabelle 1.4); Task-Erzeugung (soa.ts,task-triggers.ts,gap.ts) setztdomain+ Default-RACI.- Kanban erweitern: Bereichs-Lane/-Filter + Personen-Filter; Default „meine Bereiche";
orderIdx-Sortierung (DnD persistiert Reihenfolge). TaskParticipant(RACI) + Sichtbarkeitslogik (2.3).- Auto-Completion-Engine (2.2) + Recurrence/Wiedervorlage;
Evidence-Register. - Fragebogen (
context) auf Bereiche verteilen → Fragen erzeugen Tasks im jeweiligen Bereich. - Betroffen:
kanban-board.tsx,tasks/page.tsx,src/server/actions/tasks.ts,soa.ts,policies.ts(Sync-Hook).
M4 · Ebene 4 „Audit-Wizard" abtrennen
- Neuer, aktivierbarer Wizard aus heutigen Steps
gap+readiness; internes Audit (unabhängig, AL3-Pflicht) als eigener Schritt ≠ Readiness-Snapshot. - Reifegrad + Nachweise laufen bereits kontinuierlich (M3) → hier nur Konsolidierung + Management-Review.
- Betroffen: neue Route
/audit-readinesso. ä., Auslagern ausonboarding/register-steps.ts.
4. Offene Entscheidungen
- Primär/sekundär — intrinsisch oder relational? Reicht
ProcessAsset.role(Rolle je Prozess), oder soll ein intrinsischesAsset.tier (PRIMARY|SUPPORTING)als Wahrheit ergänzt werden (ableitbar austype)? → Vorschlag: mitrolestarten,tiernur bei Bedarf. (C/I/A-Ablageort ist entschieden: bleibt amAsset, sekundär erbt per Maximum.) - Fuzzy-Matching-Technik:
pg_trgm(DB-nah, schnell) vs. in-app Levenshtein (DB-agnostisch). → Abhängig davon, ob Postgres-Extension im Coolify-Deployment verfügbar ist. - RACI-Tiefe: volle RACI oder zunächst nur Responsible + Consulted (Sichtbarkeit)? → Vorschlag: klein starten (R + C), A = ISB/PM implizit.
- Audit-Wizard-Aktivierung: manuell vs. ab Coverage-Schwelle.
5. Reihenfolge-Logik (warum dieser Faden)
M0 legt risikolos das Datenfundament. M1–M2 bauen die sequenziellen Ebenen (Setup/Strukturanalyse), auf denen alles Weitere aufsetzt — inkl. der informationszentrierten Struktur, dem größten Modell-Hebel. M3 nutzt maximal das bereits vorhandene Kanban (additiv statt Neubau). M4 ist die saubere Abtrennung am Ende. Jeder Meilenstein ist für sich lauffähig und liefert sichtbaren Nutzen.