Phase 11: Freigabereife (#16)
Pruefung / pruefung (push) Canceled after 0s
Pruefung / oberflaeche (push) Canceled after 0s

This commit was merged in pull request #16.
This commit is contained in:
2026-09-06 20:05:49 +00:00
parent 91742a0dd7
commit 14cc4de9b8
21 changed files with 2495 additions and 8 deletions
+132
View File
@@ -34,6 +34,7 @@ Anmelden mit `SEED_ADMIN_EMAIL` und `SEED_ADMIN_PASSWORT` aus der `.env`
| Phase 8 — Lokale KI | fertig |
| Phase 9 — Kassenverwaltung | fertig |
| Phase 10 — Mandantenfähigkeit | fertig |
| Phase 11 — Freigabereife | fertig |
Vollständige Planung mit Aufwänden: [docs/ROADMAP.md](docs/ROADMAP.md) und
[docs/AUFWANDSSCHAETZUNG.md](docs/AUFWANDSSCHAETZUNG.md).
@@ -1447,9 +1448,140 @@ weitergegeben. FLORIAN verschickt keine.
---
## Phase 11 — Freigabereife
Die drei Dinge, die zwischen dem letzten Modul und dem Echtbetrieb standen:
das Auskunftsrecht, ein geprüftes Sicherungskonzept und eine Pipeline.
### Auskunft nach Art. 15 DSGVO
Jede Person darf verlangen zu erfahren, welche Daten über sie verarbeitet
werden. In einer Feuerwehr ist das mehr, als die meisten erwarten — und mit
den G26-Untersuchungen sind Gesundheitsdaten dabei, also eine besondere
Kategorie nach Art. 9.
Auf der Seite eines Mitglieds steht dafür der Knopf **Datenauskunft**
(Recht `person.export`). Ein Durchgang liefert drei Dinge:
| | Wofür |
|---|---|
| **Anzeige** | Was ausgehändigt wird, vor dem Aushändigen |
| **PDF** | Das Schreiben an die Person, für die Akte |
| **JSON** | Datenübertragbarkeit nach Art. 20 |
Bewusst nicht drei Knöpfe: wer eine Auskunft erteilt, soll sie vollständig
erteilen und nicht in Schritten, von denen zwei vergessen werden können.
**Über jedem Abschnitt steht, wozu die Daten vorliegen.** Art. 15 Abs. 1
verlangt nicht nur die Daten, sondern auch Zwecke, Empfänger, Speicherdauer
und den Hinweis auf die weiteren Rechte. Eine Liste ohne das sieht aus wie
eine Auskunft und ist keine.
Zwei Festlegungen bestimmen den Zuschnitt:
**Was über die Person gespeichert ist, steht vollständig drin** — auch das
Unangenehme: eine nicht bestandene G26, ein gemahnter Beitrag. Eine Auskunft,
die auswählt, was gut aussieht, ist keine.
**Was die Person getan hat, steht als Zahl drin.** Wer zwölf Prüfprotokolle
unterschrieben hat, bekommt die Zahl — nicht zwölf Protokolle. Die gehören zum
Nachweisbestand der Wehr; sie mitzuliefern hieße, dass jeder über eine
Selbstauskunft an die Geräteakten käme. Die Trennlinie ist nicht „viel oder
wenig", sondern „über mich" gegen „von mir".
Leere Abschnitte bleiben stehen. „Dazu liegt nichts vor" ist eine Auskunft;
ein weggelassener Abschnitt wirft die Frage auf, ob da doch etwas ist.
Der Vorgang wird protokolliert — dass eine Auskunft erteilt wurde, gehört
festgehalten.
### Sicherung
```bash
npm run sicherung # nach ./sicherungen/<Zeitstempel>/
npm run sicherung -- --ziel D:\backups
```
Gesichert wird beides, aus **demselben Moment**: die Datenbank über `pg_dump`
im Container und alle Dateien aus MinIO. Eine Datenbank von heute mit Dateien
von gestern ergibt Datensätze, die auf nichts zeigen.
Daneben entsteht ein `manifest.json` mit Zeitpunkt, Version und Umfang. Es
wird beim Zurückspielen angezeigt, damit niemand versehentlich eine Sicherung
von vor drei Monaten einspielt, weil das Verzeichnis ähnlich hieß.
### Wiederherstellung
```bash
npm run wiederherstellen -- --quelle sicherungen/2026-09-05T14-16-48
npm run wiederherstellen -- --quelle ... --wirklich
```
Ohne `--wirklich` passiert nichts — es wird nur angezeigt, was geschähe. Das
ist keine Bequemlichkeit: das Skript überschreibt den gesamten Bestand.
**Der eigentliche Zweck ist nicht der Notfall, sondern die Probe.** Eine
Sicherung, die nie zurückgespielt wurde, ist eine Vermutung: der Ordner kann
leer sein, der Dump abgeschnitten, das Passwort falsch. Man erfährt es an dem
Tag, an dem man es nicht erfahren will.
Wie berechtigt das ist, hat sich hier gezeigt. Der erste Rückspielversuch lief
sauber durch — und hatte dabei **sämtliche Unveränderlichkeitsgarantien der
Datenbank entfernt**. Der Grund liegt tiefer, als man vermutet:
`docker/postgres/10-init-app-role.sh` richtet eine Vorgabeprivilegierung ein,
damit neue Tabellen aus Migrationen automatisch nutzbar sind. `pg_restore`
legt die Tabellen aber ebenfalls neu an — die Vorgabe greift also auch dort,
und die `GRANT`-Zeilen aus dem Dump können nur hinzufügen, nie wegnehmen. Die
Anwendungsrolle durfte danach das Audit-Log ändern, Wiki-Fassungen löschen und
Kiosk-Vorgänge umschreiben. Nichts an der Anwendung hätte darauf hingewiesen.
Deshalb setzt das Skript die Sperren nach dem Zurückspielen neu und **prüft
danach nach**, ob es gewirkt hat. Die Liste dafür steht in
`scripts/sicherung-sperren.ts`, und ein Test gleicht sie gegen die migrierte
Datenbank ab: läuft sie auseinander, käme eine Sperre nach einer
Wiederherstellung nicht zurück, von der niemand mehr weiß, dass es sie gab.
Nach dem Zurückspielen bleiben drei Handgriffe, die keine Software abnehmen
kann:
1. Anmelden — kommt jemand hinein?
2. Ein Prüfprotokoll öffnen — ist das PDF da?
3. Die Mitgliederzahl mit der Erwartung vergleichen.
Erst danach gilt die Sicherung als geprüft.
### Die Pipeline
`.gitea/workflows/pruefung.yml`, zwei Aufträge:
| Auftrag | Was | Wann |
|---|---|---|
| `pruefung` | typecheck, lint, Migrationen, Seed, 599 Unit-Tests | jeder Push und Pull Request |
| `oberflaeche` | die E2E-Tests über Chromium | nur `main` und Pull Requests |
Die Trennung ist eine Zeitfrage: der erste Auftrag dauert Minuten, der zweite
eine Dreiviertelstunde.
Die Datenbank wird aus `docker/postgres` **gebaut** statt aus einem fertigen
Image genommen. FLORIAN braucht PostGIS und pgvector, und kein öffentliches
Image bringt beides mit; ein Dienst-Container mit `postgres:17` ließe die
Migrationen durchlaufen und scheiterte erst bei der ersten Umkreissuche. Der
Seed läuft mit, weil er selbst ein Test ist: fällt ein Standardwert um, merkt
man es hier und nicht, wenn eine Wehr ihn zum ersten Mal ausführt.
> **Voraussetzung: ein registrierter Runner.** Die Datei allein genügt nicht —
> Gitea braucht einen `act_runner`, der die Aufträge annimmt. Ist keiner
> eingerichtet, erscheinen die Läufe als „wartend" und laufen nie. Zum
> Zeitpunkt dieser Phase war noch keiner registriert; der Ablauf ist also
> geschrieben, aber nicht im Betrieb erprobt.
---
## Befehle
```bash
npm run sicherung # Datenbank und Dateien sichern
npm run wiederherstellen # zurueckspielen (Probelauf ohne --wirklich)
npm run dev # Entwicklungsserver auf Port 3100
npm run build # Produktionsbuild
npm run worker:dev # Hintergrundprozess mit Neuladen