Phase 11: Freigabereife
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:
co-authored by
Claude Opus 5
parent
91742a0dd7
commit
9156dcc144
@@ -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