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,328 @@
|
||||
# Ü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.
|
||||
Reference in New Issue
Block a user