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>
329 lines
16 KiB
Markdown
329 lines
16 KiB
Markdown
# Übergabe: Anwendung auf zwei Frameworks umbauen (ISO 27001 neben TISAX)
|
||
|
||
**Stand:** 2026-08-21 · **Zielgruppe:** Entwickler:innen · **Vorarbeit:** Branch `feature/iso27001-framework-mapping`, Commit `492d315`
|
||
|
||
> **Ausgangslage:** Die Inhaltsseite ist fertig. `seed/isms-vorlagenpaket-v2` trägt jetzt **zwei
|
||
> Framework-Mappings** auf **einem** Dokumentensatz — `mapping.json` (VDA ISA, 321 Anforderungen) und
|
||
> `mapping-iso.json` (ISO/IEC 27001:2022, 120 Anforderungen). Die Anwendung kennt das zweite Mapping
|
||
> noch nicht: `parsePackageFiles` liest `mapping.json` fest verdrahtet.
|
||
>
|
||
> **Auftrag:** Framework als erste Klasse im Datenmodell und in der Paketauflösung, damit ein Mandant
|
||
> ISO, TISAX oder beides führen kann. Danach die drei ISO-Artefakte, die es im Tool noch nicht gibt:
|
||
> SoA, Kennzahlen, Managementbewertung/Korrekturmaßnahmen.
|
||
|
||
Fachlicher Hintergrund und Begründung der Entscheidung: `docs/FRAMEWORK-MAPPING-ISO27001.md`.
|
||
Gesamtarchitektur und die übrigen Lanes: `docs/KONZEPT-framework-iso27001.md`.
|
||
|
||
---
|
||
|
||
## 0. Was **nicht** angefasst werden muss
|
||
|
||
Die Paketinhalte sind generiert. Wer ISO-Texte oder Zuordnungen ändern will, ändert
|
||
`_iso_crosswalk.json` bzw. `_iso_sections.json` und lässt `python3 _generate_iso.py` laufen —
|
||
**nie direkt die Markdown-Dateien**, der Generator überschreibt sentinel-begrenzte Blöcke.
|
||
|
||
Danach müssen beide Prüfskripte grün sein:
|
||
|
||
```bash
|
||
cd seed/isms-vorlagenpaket-v2
|
||
python3 _generate_iso.py # idempotent, zweiter Lauf ändert nichts
|
||
python3 _verify.py # TISAX-Sicht
|
||
python3 _verify_iso.py # ISO-Sicht
|
||
python3 _render_diff.py HEAD # TISAX-Regression gegen den aktuellen Stand
|
||
```
|
||
|
||
Die Sichtbarkeit im Dokument steuern zwei Variablen aus `variables.schema.json`:
|
||
`FLAG_FW_TISAX` (Default `true`) und `FLAG_FW_ISO27001` (Default `false`).
|
||
|
||
### Parallelbetrieb ist vorgesehen
|
||
|
||
Sind **beide** Flags gesetzt, rendert das Dokument beide Anforderungssichten untereinander — über
|
||
**einem** gemeinsamen Umsetzungstext. Genau dafür ist die Bibliothek gebaut:
|
||
|
||
```
|
||
3.2 Sichere Anmeldung
|
||
*Anforderungsbezug:* VDA ISA 4.1.2 · ISO/IEC 27001 A.8.5
|
||
|
||
**Anforderung**
|
||
*Anforderungen nach VDA ISA 2027:*
|
||
- **[MUSS]** Verfahren zur Benutzerauthentifizierung nach dem Stand der Technik werden angewandt.
|
||
- **[SOLL]** Für privilegierte Benutzerkonten werden höherwertige Verfahren genutzt …
|
||
*Anforderungen nach ISO/IEC 27001:*
|
||
- **[ISO A.8.5]** Sichere Authentisierungstechnologien und -verfahren sind einzusetzen.
|
||
|
||
**Umsetzung bei {{ORG_NAME}}**
|
||
Die Authentifizierungsverfahren sind risikobasiert ausgewählt … Passwortvorgaben nach BL-IAM-01 …
|
||
```
|
||
|
||
Der Anforderungsbezug steht in einer eigenen Zeile unter der Überschrift und nennt je nach
|
||
Betriebsart eine oder beide Normen. Die beiden Zwischenüberschriften erscheinen **nur**, wenn
|
||
tatsächlich beide Frameworks aktiv sind.
|
||
Die 19 ISO-only-Abschnitte kommen bei einem Doppel-Mandanten additiv hinzu.
|
||
|
||
Auf der Datenseite ist der Parallelbetrieb erst nach AP1 möglich: `PolicyRequirement` trägt dann
|
||
beide ID-Namensräume (`4.1.2-M1` und `A.5.15-1`) nebeneinander — vorausgesetzt, Falle 1.1 ist gelöst.
|
||
|
||
---
|
||
|
||
## 1. Die vier Fallen — bitte zuerst lesen
|
||
|
||
### 1.1 `reconcilePackage` archiviert die Anforderungen des jeweils anderen Frameworks
|
||
|
||
**Das ist der kritische Punkt.** `prisma/import-policies.ts:368-371` archiviert jede
|
||
`PolicyRequirement`, deren `reqId` nicht im importierten Paket steht:
|
||
|
||
```ts
|
||
for (const ex of existingReqs) {
|
||
if (desiredReqIds.has(ex.reqId) || ex.archivedAt) continue;
|
||
report.requirements.archived++;
|
||
await prisma.policyRequirement.update({ where: { id: ex.id }, data: { archivedAt: now } });
|
||
}
|
||
```
|
||
|
||
Ein ISO-Import in einen Mandanten mit TISAX archiviert damit **alle 321 VDA-ISA-Anforderungen** —
|
||
und umgekehrt. Die ID-Namensräume kollidieren zwar nicht (`4.1.2-M1` vs. `A.5.15-1`, `@@unique([tenantId, reqId])`
|
||
bleibt heil), aber der Abgleich muss **framework-scoped** werden:
|
||
|
||
- `PolicyRequirement.framework Framework` ergänzen (Backfill `TISAX`),
|
||
- den Archivierungslauf auf `where: { tenantId, framework }` einschränken.
|
||
|
||
Für **Dokumente** gilt das nicht: Beide Mappings lesen dieselben `richtlinien/`- und `verfahren/`-Dateien,
|
||
`pkg.documents` ist identisch. Ebenso Variablen, Baseline-Parameter und Nachweisregister — die sind geteilt
|
||
und dürfen genau einmal je Mandant abgeglichen werden.
|
||
|
||
### 1.2 Zwei Unique-Constraints brechen bei zwei Frameworks
|
||
|
||
| Modell | heute | muss werden |
|
||
|---|---|---|
|
||
| `PolicyTemplateVersion` (`prisma/schema.prisma:1639`) | `version String @unique` | `@@unique([framework, version])` — sonst kollidieren ISO 2.1 und TISAX 2.1 |
|
||
| `PolicyPackageState` (`prisma/schema.prisma:1614`) | `tenantId String @unique` | `@@unique([tenantId, framework])` — sonst merkt sich ein Mandant nur eine Paketversion |
|
||
|
||
### 1.3 TISAX darf sich nicht verändern
|
||
|
||
Bestandsmandanten müssen bitgenau dasselbe sehen wie heute. Der Renderdiff über alle 17 Richtlinien
|
||
(TISAX-Kontext vorher/nachher) war bei der Paketumstellung **0 Abweichungen** — dieser Wert ist die
|
||
Messlatte. `src/lib/policy-render.ts` belegt fehlende Framework-Flags bereits vor
|
||
(`FLAG_FW_TISAX = true`, `FLAG_FW_ISO27001 = false`), damit ein Bestandsmandant vor dem Paket-Re-Import
|
||
keine leeren Anforderungsblöcke sieht. **Diesen Fail-Safe nicht entfernen**, auch nicht wenn die Flags
|
||
später über `TenantFramework` gesetzt werden.
|
||
|
||
Nebenbei: `applyProtection` (`src/lib/policy-render.ts:54`) setzt `FLAG_HIGH_PROTECTION` bedingungslos auf `true`
|
||
mit der Begründung „im TISAX-Modell stets aktiv". Für ISO-Mandanten ist das derzeit folgenlos (die
|
||
ISO-Blöcke nutzen die Schutzbedarf-Flags nicht), sollte aber beim Bau der `IsoStrategy` bewusst
|
||
entschieden werden.
|
||
|
||
### 1.4 Migrations- und Build-Konventionen
|
||
|
||
- Migrationsflow Prisma 7 wie in `docs/HANDOVER-DEV.md:100`: `migrate diff --from-config-datasource … --to-schema … --script`,
|
||
danach den **RLS-DO-Block manuell** an die `migration.sql` anhängen, dann `migrate deploy`.
|
||
- Jedes neue mandantengebundene Modell gehört in **`TENANT_MODELS`** (`src/server/db.ts:81`) **und**
|
||
braucht eine RLS-Policy in seiner Migration.
|
||
- `scripts/check-module-guards.ts` läuft als `prebuild`-Gate: **jede neue Datei** unter
|
||
`src/server/actions/` muss dort eingetragen sein (Modul-Key oder `EXEMPT`), sonst schlägt der Build fehl.
|
||
- Bei paralleler Lane-Entwicklung teilen sich die Worktrees dieselbe lokale Postgres-DB. Beim Erzeugen
|
||
einer Migration nur die **eigenen** DDL-Blöcke übernehmen und Fremd-Drops von Hand entfernen.
|
||
- `npm run lint` und `npm run build` müssen vor jedem Commit grün sein (`AGENTS.md:25`).
|
||
|
||
---
|
||
|
||
## 2. Arbeitspakete
|
||
|
||
### AP1 — Framework-Dimension *(Fundament, blockiert alles Weitere)*
|
||
|
||
**Schema**
|
||
|
||
```prisma
|
||
enum Framework { ISO_27001 TISAX }
|
||
|
||
model TenantFramework {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
framework Framework
|
||
isPrimary Boolean @default(false) @map("is_primary")
|
||
config Json? // z. B. { tisaxLevel: "AL3" } bzw. { certScope, certBodyTarget }
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
@@unique([tenantId, framework])
|
||
@@index([tenantId])
|
||
@@map("tenant_frameworks")
|
||
}
|
||
```
|
||
|
||
Zusätzlich: `PolicyRequirement.framework`, `PolicyTemplateVersion.framework`,
|
||
`PolicyPackageState.framework` (siehe Fallen 1.1 und 1.2). Alles additiv, Backfill der Bestandsdaten auf
|
||
`TISAX`.
|
||
|
||
**Paketauflösung**
|
||
|
||
| Datei | Änderung |
|
||
|---|---|
|
||
| `prisma/import-policies.ts` | `parsePackageFiles(seedDir, mappingFile = "mapping.json")`; Requirements framework-scoped reconcilen |
|
||
| `prisma/template-store.ts:147` | `resolvePackageForTenant(prisma, tenantId, seedDir, framework)`; `loadPublishedPackage(prisma, locale, framework)`; `getAvailableVersion` ebenso |
|
||
| `scripts/sync-policy-templates.ts:23` | über **Frameworks × Sprachen** iterieren statt nur über Sprachen |
|
||
|
||
Die vier Aufrufer von `resolvePackageForTenant` bekommen den Framework-Parameter durchgereicht:
|
||
`src/server/provision.ts:139`, `src/server/actions/policy-package.ts:28`, `src/server/actions/admin.ts`,
|
||
`src/app/(app)/policies/updates/page.tsx:44`.
|
||
|
||
**Das Seed-Verzeichnis bleibt für beide Frameworks dasselbe** — die fünf `SEED_DIR`-Konstanten ändern
|
||
sich nicht, nur der Mapping-Dateiname. Das ist der Vorteil von Variante A.
|
||
|
||
**DoD:** Bestandsmandanten laufen unverändert als TISAX; ein Mandant kann mit
|
||
`frameworks: ["ISO_27001"]`, `["TISAX"]` oder beiden provisioniert werden; bei Doppel-Framework
|
||
koexistieren 321 + 120 Anforderungen und **keine** ist fälschlich archiviert.
|
||
|
||
---
|
||
|
||
### AP2 — Provisionierung und Flags *(klein, direkt nach AP1)*
|
||
|
||
`ProvisionOpts` (`src/server/provision.ts:37`) um `frameworks: Framework[]` erweitern.
|
||
`provisionTenant` schreibt die `TenantFramework`-Zeilen, importiert **je Framework** das passende
|
||
Mapping und setzt die Sichtbarkeits-Flags als `PolicyVariable`:
|
||
|
||
| Mandant führt | `FLAG_FW_TISAX` | `FLAG_FW_ISO27001` |
|
||
|---|:--:|:--:|
|
||
| nur TISAX | `true` | `false` |
|
||
| nur ISO | `false` | `true` |
|
||
| beides | `true` | `true` |
|
||
|
||
**Achtung Reihenfolge:** `reconcilePackage` erhält nutzergepflegte Variablenwerte und überschreibt sie
|
||
nicht. Die Flags müssen also **nach** dem Import gesetzt werden, sonst bleibt der Schema-Default stehen
|
||
und ein ISO-Mandant sieht die VDA-ISA-Sicht.
|
||
|
||
`tisaxLevel` bleibt vorerst auf `TenantSettings`, wird aber als TISAX-scoped dokumentiert (Entscheidung D2).
|
||
Die AL-Flags dürfen bei einem reinen ISO-Mandanten nicht gesetzt werden.
|
||
|
||
**DoD:** Ein frisch provisionierter ISO-Mandant öffnet `/policies` und sieht in jedem Dokument die
|
||
ISO-Anforderungssicht plus die 19 ISO-only-Abschnitte; keine „ISA"-Klammern in den Überschriften.
|
||
|
||
---
|
||
|
||
### AP3 — SoA-Modul *(das fehlende ISO-Kernartefakt)*
|
||
|
||
Heute ist der Modul-Key `soa` (`src/lib/modules.ts:19`) mit `href: "/soa"` registriert, **die Route
|
||
existiert aber nicht** — die Logik liegt als Wizard-Schritt 7 in `src/server/actions/soa.ts` und ist ein
|
||
VDA-ISA-Reifegrad-Assessment (0–3), nicht die ISO-Anwendbarkeitserklärung.
|
||
|
||
```prisma
|
||
model SoaEntry {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
framework Framework
|
||
control String // "A.5.15"
|
||
applicable Boolean @default(true)
|
||
justification String // Begründung Einbeziehung ODER Ausschluss
|
||
source String? // Risiko-ID / gesetzliche / vertragliche Anforderung
|
||
implementationStatus String @default("geplant") // umgesetzt | teilweise | geplant
|
||
ownerId String? @map("owner_id")
|
||
policyCode String? @map("policy_code")
|
||
evidenceId String? @map("evidence_id")
|
||
@@unique([tenantId, framework, control])
|
||
@@index([tenantId])
|
||
@@map("soa_entries")
|
||
}
|
||
```
|
||
|
||
Die vier Felder `applicable`, `justification`, `implementationStatus` und die Ausschlussbegründung sind
|
||
**normative Pflichtangaben** (ISO/IEC 27001:2022, 6.1.3 d) — ohne sie ist die SoA im Zertifizierungsaudit
|
||
angreifbar.
|
||
|
||
Vorbefüllung aus `mapping-iso.json`: 93 Controls; das Feld `condition` (z. B. `FLAG_DEV_INHOUSE`) steuert
|
||
die Default-Anwendbarkeit. Als fachliche Vorlage für Aufbau und Spalten dient
|
||
`seed/isms-vorlagenpaket-v2/Statement-of-Applicability-ISO.md`.
|
||
|
||
**DoD:** Ein ISO-Mandant kann die SoA vollständig pflegen und als PDF/XLSX exportieren; ein Control ohne
|
||
Begründung wird als unvollständig markiert.
|
||
|
||
---
|
||
|
||
### AP4 — Managementklauseln: Kennzahlen, Bewertung, Korrekturmaßnahmen
|
||
|
||
Drei Modelle fehlen; alle drei sind ISO-Pflichtthemen und heute nicht abbildbar:
|
||
|
||
| Klausel | Modell | Inhalt |
|
||
|---|---|---|
|
||
| 9.1 | `Kpi` / `KpiValue` | Kennzahl, Datenquelle, Zielwert, Turnus, Verantwortlicher, Messwerte je Periode |
|
||
| 9.3 | `ManagementReview` | Datum, Eingaben nach 9.3.2, Ergebnisse nach 9.3.3, Beschlüsse mit Verantwortlichem und Termin |
|
||
| 10.2 | `Nonconformity` + `CorrectiveAction` | Herkunft, Sofortkorrektur, Ursachenanalyse, Maßnahme, Wirksamkeitsbewertung |
|
||
|
||
Vorhandene Bausteine, auf denen das aufsetzen kann: `Task.recurrence` (RRULE), `Task.remindAt`,
|
||
`Task.effectiveUntil` (Wirksamkeitsintervall, gekoppelt an `Evidence.validUntil`), `TaskParticipant` (RACI)
|
||
und `AuditLog`. Die Datenquellen für die Kennzahlen liegen bereits im Tool: Aufgabenfristen und
|
||
Überfälligkeit, Incident-SLA und Meldefristen (`src/lib/incident-deadlines.ts`), Reifegrade je Control,
|
||
Maßnahmenstatus.
|
||
|
||
Fachlicher Inhalt der Abschnitte steht in R03 der Bibliothek (`ISO-MS-MESSUNG`, `ISO-MS-MGMTREVIEW`,
|
||
`ISO-MS-CAPA`) — die Modelle sollten die dort beschriebenen Felder tragen.
|
||
|
||
**DoD:** Kennzahlenblatt mit Zielwerten pflegbar und über zwei Perioden auswertbar; Management-Review
|
||
entlang der 9.3.2-Agenda protokollierbar; ein Maßnahmenfall inklusive dokumentierter Wirksamkeitsprüfung
|
||
abschließbar.
|
||
|
||
---
|
||
|
||
### AP5 — Dokumentenlenkung *(klein, hohe Auditwirkung)*
|
||
|
||
- `PolicyDocument.reviewCycle` und `nextReviewAt` — heute führt nur `ManagedRegister` einen
|
||
`reviewCycle`; A.5.1 verlangt die Überprüfung „in geplanten Abständen". Behelfsweise über
|
||
`Task.recurrence` möglich, sauberer am Dokument.
|
||
- `PolicyAcknowledgement { tenantId, policyDocumentId, version, userId, acknowledgedAt }` — die
|
||
Lesebestätigung ist in `SPEC.md` §4.6 vorgesehen und fehlt. Sie ist zugleich der einfachste Nachweis
|
||
für Klausel 7.3 und Control A.6.3.
|
||
- Änderungshistorie je Dokumentversion — im Entwicklungsstand als offen geführt. Kann aus `AuditLog`
|
||
(Vorher/Nachher) abgeleitet oder als eigene Tabelle geführt werden.
|
||
|
||
**DoD:** Übersicht „Prüfung fällig" im Tool; Auswertung der Lesebestätigungen je Richtlinienversion;
|
||
Historie eines Dokuments über mindestens zwei Versionen sichtbar.
|
||
|
||
---
|
||
|
||
## 3. Reihenfolge und Aufwand
|
||
|
||
```
|
||
AP1 Framework-Dimension ██████ 4–6 PT ← blockiert alles
|
||
├─ AP2 Provisionierung ██ 1–2 PT
|
||
├─ AP3 SoA-Modul ██████ 5–8 PT
|
||
├─ AP4 Managementkl. ██████ 5–8 PT
|
||
└─ AP5 Dok.-Lenkung ███ 2–3 PT
|
||
```
|
||
|
||
AP3, AP4 und AP5 sind nach AP1 parallelisierbar. Die Schätzung entspricht den Lanes 1, 4 und 5 aus
|
||
`KONZEPT-framework-iso27001.md`; der inhaltliche Teil von Lane 2 (ISO-Mapping und -Texte) ist erledigt und
|
||
entfällt.
|
||
|
||
**Feature-Flag:** ISO bleibt laut Entscheidung D7 hinter einem Plattform-Schalter, bis AP3 abgenommen ist.
|
||
|
||
---
|
||
|
||
## 4. Abnahme
|
||
|
||
| Prüfung | Erwartung |
|
||
|---|---|
|
||
| `python3 _verify.py` und `_verify_iso.py` | beide `OK` |
|
||
| `python3 _render_diff.py <rev>` (TISAX-Sicht) | 0 Abweichungen — Skript liegt im Paket bei. **Vergleichsstand ist der Kopf dieses Branches, nicht `a9649b3`**: der Anforderungsbezug ist dort bewusst aus der Überschrift in eine eigene Zeile gewandert (14 von 17 Richtlinien betroffen, ausschließlich diese Zeile — der Anforderungs- und Umsetzungstext ist unverändert). |
|
||
| `python3 _render_diff.py <rev> --framework BEIDE` | Parallelbetrieb prüfbar |
|
||
| `npx tsx scripts/test-framework-dryrun.ts` | alle Prüfungen bestanden — Trockenlauf ohne Schreibzugriff über Paketebene und alle Mandanten der lokalen DB |
|
||
| Bestandsmandant (TISAX) nach Deploy | Readiness und Exporte identisch zum Snapshot vor dem Umbau |
|
||
| Import ISO in Mandant mit TISAX | 120 neue Anforderungen, **0 archivierte** ISA-Anforderungen |
|
||
| Import ISO, Umsetzungstexte | 120 von 120 gefüllt (0 leer) |
|
||
| Dokumente bei Doppel-Framework | 39 Dokumente, **nicht** doppelt |
|
||
| `npm run lint`, `npx tsc --noEmit`, `npm run build` | grün |
|
||
|
||
Der Importer lässt sich ohne Datenbank gegen beide Mappings prüfen — `parsePackageFiles` ist reine
|
||
Dateiarbeit und liefert `documents`, `requirements`, `variables`, `baseline`, `evidence` als
|
||
`ParsedPackage`. Ein Trockenlauf des Mandanten-Imports geht über `reconcilePackage(..., { dryRun: true })`;
|
||
er erzeugt den Änderungsreport ohne Schreibzugriff und eignet sich als Freigabebedingung.
|
||
|
||
---
|
||
|
||
## 5. Offene Punkte auf der Paketseite (nicht Entwicklung)
|
||
|
||
Diese Punkte gehören dem ISB bzw. der Redaktion, nicht dem Entwicklungsteam — hier nur zur Abgrenzung:
|
||
|
||
- Nachweisregister um Zeilen für Kennzahlenblatt, Management-Review-Protokoll und Maßnahmenregister ergänzen.
|
||
- VA-15 trennen (internes Audit vs. Managementbewertung) und ein Verfahren für Korrekturmaßnahmen
|
||
ergänzen — **Nummer ab VA-21**, VA-20 ist belegt.
|
||
- `REVIEW_CYCLE` wird an 33 Bestandsstellen für fünf verschiedene Zyklen verwendet; die getrennten
|
||
Variablen (`POLICY_REVIEW_CYCLE`, `MGMT_REVIEW_CYCLE`, `RISK_REVIEW_CYCLE`) greifen bisher nur in den
|
||
neuen ISO-Abschnitten.
|
||
- ISB-Freigabe der 19 neuen Abschnittstexte und Review des Crosswalks.
|