Phase 11: Freigabereife (#16)
This commit was merged in pull request #16.
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user