Basis: Certvia dev@a48c5fb als Fundament für Craftvia
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s

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>
This commit is contained in:
2026-09-14 11:05:39 +02:00
co-authored by Claude Opus 5
commit c8e6f30a27
720 changed files with 140143 additions and 0 deletions
+238
View File
@@ -0,0 +1,238 @@
# 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 `/onboarding` werden 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_STATUSES` in `src/lib/tasks.ts` enthält `IN_PROGRESS` nicht, obwohl Board/Actions ihn nutzen → Inkonsistenz beheben.
- `src/app/(app)/tasks/page.tsx:166` referenziert `open` — im File nicht definiert (nur `myOpen`/`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 | SECONDARY` trennt primär/sekundär je Prozess-Beziehung.
- `AssetRelation` bildet Träger-Abhängigkeiten ab; `confidentiality/integrity/availability` liegen schon am `Asset`.
→ **Kein** `InformationValue`/`ProcessInformation`/`InformationAsset`. Stattdessen kleine Ergänzungen am Bestand:
```prisma
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)
```prisma
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".
```prisma
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)
```prisma
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)
```prisma
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)
```prisma
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-aware `de`) → Umlaut-Faltung (ä→ae, ö→oe, ü→ue, ß→ss) → Satzzeichen entfernen. Ergebnis ist der `@@unique`-Schlüssel.
- Beispiel: „Kundendaten", „kundendaten", „ Kunden­daten " → alle `kundendaten`.
- **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 auf `type 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 optional `pg_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-`DONE` ohne mindestens dokumentierten Zustand; „audit-ready" ist ein **separater, manueller** Schritt mit Nachweis.
- **Wiedervorlage:** Tasks mit `recurrence` erzeugen bei `DONE` automatisch eine Folge-Task mit neuem `dueDate`/`effectiveUntil` (Serienlogik).
### 2.3 Bereichs-Sichtbarkeit & RACI
- Default-Sicht: **eigene Bereiche** (aus `ProjectFunctionAssignment.domain` des 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_STATUSES` um `IN_PROGRESS` ergänzen (`src/lib/tasks.ts`); `open`-Bug in `tasks/page.tsx:166` verifizieren/fixen.
- Enums `Domain`, `RaciKind`, `InfoLabel`, `InfoProcessRole` + Task-Felder `domain`/`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.ts` neu 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 (Schritt `roles`). 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`) + `ProcessCatalogEntry` migrieren. **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
- `ControlDomainMap` seeden (Tabelle 1.4); Task-Erzeugung (`soa.ts`, `task-triggers.ts`, `gap.ts`) setzt `domain` + 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-readiness` o. ä., Auslagern aus `onboarding/register-steps.ts`.*
---
## 4. Offene Entscheidungen
1. **Primär/sekundär — intrinsisch oder relational?** Reicht `ProcessAsset.role` (Rolle je Prozess), oder soll ein intrinsisches `Asset.tier (PRIMARY|SUPPORTING)` als Wahrheit ergänzt werden (ableitbar aus `type`)? → Vorschlag: mit `role` starten, `tier` nur bei Bedarf. *(C/I/A-Ablageort ist entschieden: bleibt am `Asset`, sekundär erbt per Maximum.)*
2. **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.
3. **RACI-Tiefe:** volle RACI oder zunächst nur Responsible + Consulted (Sichtbarkeit)? → Vorschlag: klein starten (R + C), A = ISB/PM implizit.
4. **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.