Basis: Certvia dev@a48c5fb als Fundament für Craftvia
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:
@@ -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", „ Kundendaten " → 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.
|
||||
Reference in New Issue
Block a user