Phase 11: Freigabereife
Pruefung / pruefung (pull_request) Canceled after 0s
Pruefung / oberflaeche (pull_request) Canceled after 0s

Kein Modul, sondern die drei Software-Voraussetzungen fuer 1.0.0:
Auskunft nach Art. 15 DSGVO, ein geprueftes Sicherungskonzept und eine
Pipeline.

Das Auskunftsrecht war ein Versprechen ohne Umsetzung: `person.export`
stand seit Phase 0 im Katalog und war dem Kommandanten zugewiesen,
implementiert war nichts. Ohne Auskunftsseite ist ein Loeschkonzept nicht
abnehmbar. Jetzt sammelt FLORIAN dreizehn Abschnitte ueber eine Person,
mit dem Zweck ueber jedem - Art. 15 verlangt nicht nur die Daten, sondern
auch Zwecke und Rechte, und eine blosse Liste erfuellt die Pflicht nicht.
Ein Durchgang liefert Anzeige, PDF und JSON nach Art. 20.

Die Trennlinie ist "ueber mich" gegen "von mir": was ueber die Person
gespeichert ist, steht vollstaendig drin, auch das Unangenehme. Was sie
getan hat, steht als Zahl drin - die Pruefprotokolle gehoeren zum
Nachweisbestand der Wehr, und vollstaendig ausgeliefert kaeme jeder ueber
eine Selbstauskunft an die Geraeteakten.

Die Rueckspielprobe hat sich sofort bezahlt gemacht: der erste Versuch
lief sauber durch und hatte dabei saemtliche Unveraenderlichkeitsgarantien
der Datenbank entfernt. Die Vorgabeprivilegierung aus dem Init-Skript gibt
jeder von pg_restore neu angelegten Tabelle wieder alle Rechte, und GRANT
kann nur hinzufuegen. Die Anwendungsrolle durfte danach das Audit-Log
aendern, Wiki-Fassungen loeschen, Kiosk-Vorgaenge umschreiben - und nichts
haette darauf hingewiesen. Gefunden haben es vier Datenbanktests
unmittelbar nach der Probe, also genau die Probe, wegen der 1.0.0 sie
verlangt.

Die Pipeline ist geschrieben, aber NICHT erprobt: in Gitea ist kein
act_runner registriert. Das steht so im README und im Devlog, damit sich
niemand auf eine Pruefung verlaesst, die nicht stattfindet.

601 Unit-Tests gruen; Sicherung und Wiederherstellung einmal vollstaendig
durchgefuehrt und die Tests danach erneut gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-05 17:16:08 +02:00
co-authored by Claude Opus 5
parent 91742a0dd7
commit 9156dcc144
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