syncova-backup/docs/verification.md
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
Enterprise-Backup-, Recovery-, Verification-, Security- und
Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme.

Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als
vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit
nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem
tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes
geht in keine Bewertung als "gut" ein.

Umfang (Phasen 0-23):

- Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll,
  Katalogaufbau allein aus den Manifesten — ohne Datenbank
- Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz
  Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck
- Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell)
- Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive
- Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit
- Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center,
  Ransomware-Heuristik (meldet, handelt nie)
- Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing
- Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository
- Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64

Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup
Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs.

Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die
systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine
wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md
und docs/release-candidate.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:10:54 +02:00

246 lines
11 KiB
Markdown

# Prüfung und Recovery Assurance
Phase 10. Der Teil der Anlage, der die Frage beantwortet, die das ganze Produkt
trägt: **Kann ich mich auf dieses Backup verlassen?**
Ein Backup, dessen Wiederherstellbarkeit nie geprüft wurde, ist eine Vermutung.
Die Prüfung macht daraus eine Aussage — und wo sie fehlt, sagt die Anlage das,
statt die Lücke mit einer freundlichen Zahl zu füllen.
## Die fünf Prüfarten
Sie unterscheiden sich in Aussagekraft und Kosten erheblich. Das ist kein
Nachteil, sondern der Zweck: Eine Anlage, die nur die teuerste Prüfung kennt,
wird selten geprüft.
| Art | Was sie liest | Was sie beweist |
| --- | --- | --- |
| `manifest` | nur das Manifest | Das Verzeichnis ist stimmig. Über die Daten sagt sie nichts. |
| `chunk_presence` | Verzeichniseinträge der Blöcke | Alle Blöcke sind da. Ihr Inhalt wurde nicht gelesen. |
| `chunk_integrity` | jeden Block vollständig | Alle Blöcke sind da und unbeschädigt. |
| `chain` | die Manifeste der ganzen Kette | Kein Glied zwischen Voll- und Zuwachssicherung fehlt. |
| `restore_test` | alles, und schreibt es zurück | Das Backup **ist** wiederherstellbar. |
Nur die letzte Zeile ist ein Nachweis. Die übrigen sind Indizien — gute, aber
Indizien. Genau deshalb wiegt der Wiederherstellungstest in der Bewertung am
schwersten und hängt an einem eigenen Recht.
Ohne Angabe der Art wird `chunk_integrity` gewählt: die schwächste Prüfung, die
überhaupt etwas über die Daten aussagt. `manifest` als Vorgabe wäre bequem und
wertlos.
### Die Blockprüfung braucht keinen Schlüssel
Sie vergleicht die **gespeicherte** Form gegen `ChunkReference.StoredDigest`,
nicht gegen die Chunk-Kennung. Die Kennung beschreibt bei einem verschlüsselten
Repository den Klartext; eine Prüfung gegen sie meldete **jeden** Block als
beschädigt. Diesen Fehler hatte der Integritätsscan der Phase 2 (siehe
`docs/repository.md`), und die Prüfmaschine hätte ihn wiederholt.
Der Nebeneffekt ist wichtiger als die Fehlerbehebung: Eine Integritätsprüfung
läuft dadurch auch dort, wo der Datenschlüssel nicht vorliegt.
### Der Wiederherstellungstest
Er legt ein Wegwerfziel über `os.MkdirTemp` an, schreibt das Backup dorthin
zurück, vergleicht jede Datei gegen den `ContentHash` im Manifest und räumt
anschließend restlos auf (`defer os.RemoveAll`). Er braucht den Datenschlüssel —
liegt er nicht vor, wird die Prüfung abgelehnt statt scheinbar ausgeführt. Ein
Vergleich von Geheimtext gegen Klartext-Prüfsummen schlüge fehl und meldete ein
gesundes Backup als beschädigt.
Ein auf einen Teilbaum beschränkter Test hebt die Einstufung **nicht** auf
`recoverable`: Er hat nicht das Backup geprüft, sondern einen Teil davon.
## Einstufung
Fünf Stufen, vier davon auf einer Leiter:
```text
failed → successful → verified → recoverable
corrupted (außerhalb)
```
| Stufe | Bedeutung |
| --- | --- |
| `failed` | Der Sicherungslauf ist gescheitert. |
| `corrupted` | Eine Prüfung fand Beschädigungen oder fehlende Blöcke. |
| `successful` | Der Lauf ist abgeschlossen. Ob sich die Daten zurückholen lassen, wurde nicht geprüft. |
| `verified` | Die Blöcke sind vorhanden und unbeschädigt. |
| `recoverable` | Das Backup wurde zurückgeschrieben und verglichen. Nachweislich wiederherstellbar. |
`corrupted` steht bewusst außerhalb der Leiter: Es ist keine Zwischenstufe,
sondern ein Ausschlussgrund.
Die Einstufung wird von zwei Datenbank-Constraints gestützt, nicht nur von der
Anwendungslogik:
```sql
CHECK (classification <> 'recoverable' OR last_restore_test_at IS NOT NULL)
CHECK (classification <> 'verified' OR last_verified_at IS NOT NULL)
```
Die stärkste Aussage der Anlage lässt sich damit nicht ohne ihren Nachweis
vergeben — auch nicht durch einen Fehler in einer künftigen Codeänderung.
### Rangfolge beim Fortschreiben
Eine bestandene Blockprüfung hebt auf `verified`, stuft aber **nie** von
`recoverable` herab: Der Wiederherstellungstest bleibt die stärkere Aussage.
### Ein Befund löscht die bisherigen Nachweise
Findet eine Prüfung Beschädigungen, werden `last_verified_at`,
`last_restore_test_at` und die gemessene Wiederherstellungsdauer auf NULL
gesetzt. Der Grund stammt aus dem Nachweis dieser Phase: Ohne diesen Schritt
stand das Backup nach Behebung des Schadens sofort wieder als `recoverable` da —
auf Grundlage eines Tests, der **vor** dem Schaden lief. Wer den Nachweis will,
muss ihn wiederholen.
### Ein Fehlschlag ist kein Befund
Konnte die Prüfung nicht laufen — Repository nicht erreichbar, Control-Server
abgestürzt —, wird der Auftrag als `failed` vermerkt und die Einstufung des
Backups bleibt unberührt. Dass die Prüfung nicht stattfand, sagt nichts über die
Daten. Alles andere wäre ein Fehlalarm, und ein Prüfwerkzeug, das grundlos Alarm
schlägt, wird bald nicht mehr ernst genommen.
## Bewertung (Recovery Assurance Score)
Zehn Eingangsgrößen nach `SYNCOVA_IMPLEMENTATION_PLAN.md` §12:
| Größe | Gewicht |
| --- | --- |
| Wiederherstellungstest | 25 |
| Integritätsprüfung | 20 |
| Aktualität | 15 |
| Wiederherstellungspunkt (RPO) | 10 |
| Wiederherstellungsdauer (RTO) | 10 |
| Unveränderlichkeit | 5 |
| Zweiter Standort | 5 |
| Verschlüsselung | 5 |
| Repository-Zustand | 3 |
| Auffälligkeiten | 2 |
Die Gewichte folgen einer Überzeugung: Der Wiederherstellungstest wiegt am
schwersten, weil er als einziger etwas nachweist. Verschlüsselung und
Unveränderlichkeit schützen die Daten, sagen aber nichts darüber, ob sie noch da
sind.
### Unbekannt zählt niemals als gut
Jede Größe trägt ein `is_known`. Eine nie durchgeführte Messung bekommt null
Punkte **und** wird als ungemessen geführt. Bei mehr als drei unbekannten Größen
meldet `is_trustworthy` false und die Zusammenfassung sagt ausdrücklich, dass die
Prozentzahl eine Vermutung ist. `missing_measurements` nennt die fehlenden
Messungen — das ist die eigentliche Handlungsanweisung.
### Beschädigt heißt null Prozent
Ein Backup mit Befund und ein gescheiterter Lauf bekommen 0 %, unabhängig davon,
wie gut die übrigen Größen aussehen.
Der Fall stammt aus dem Nachweis dieser Phase: Ein Backup mit **einem einzigen**
beschädigten Block kam auf 70 %, weil Aktualität, Verschlüsselung und ein
früherer Wiederherstellungstest weiterhin zählten. Diese Zahl liest sich wie
„weitgehend in Ordnung". Ein Backup, aus dem sich ein Block nicht mehr lesen
lässt, ist aber nicht zu 70 % wiederherstellbar, sondern nicht wiederherstellbar.
Ebenso zählt ein früherer Wiederherstellungstest nach einem Befund nicht mehr: Er
bezieht sich auf einen Zustand des Repositorys, den es nachweislich nicht mehr
gibt.
### Berechnet, nicht gespeichert
`GET /backups/{id}/assurance` berechnet die Bewertung bei jedem Aufruf neu. Sie
hängt am Alter der Messungen und veraltet damit von selbst — ein gespeicherter
Wert würde mit jedem Tag falscher, ohne dass sich etwas ändert. Der Wert in
`backups.assurance_score` ist nur ein Abbild für Übersichten.
## Ausführung
Die Prüfungen laufen in einer eigenen Schleife neben Sicherung und
Wiederherstellung. Sie mit den Sicherungen zu vermengen wäre falsch: Eine Prüfung
wird nie wiederholt, weil sie fehlschlug, und sie darf eine laufende Sicherung
nicht verdrängen — beide lesen denselben Datenträger. Standardmäßig läuft **eine**
Prüfung gleichzeitig.
- **Übernahme** über `FOR UPDATE SKIP LOCKED`: Mehrere Control-Server greifen
nicht nach demselben Auftrag.
- **Ein Teilindex** verhindert zwei gleichzeitige Prüfungen desselben Backups.
Sie läsen dieselben Daten doppelt, ohne mehr festzustellen. Der zweite Versuch
bekommt 409.
- **Lebendmeldung** alle 30 Sekunden; nach 30 Minuten ohne Meldung gilt die
Prüfung als verwaist und wird beim nächsten Start freigegeben. Ohne diesen
Schritt sperrte der Teilindex das Backup dauerhaft gegen jede weitere Prüfung.
- Das Repository wird **schreibgeschützt** geöffnet: Eine Prüfung darf nichts
anfassen und soll neben einer laufenden Sicherung stattfinden können.
## Endpunkte
| Methode | Pfad | Recht |
| --- | --- | --- |
| GET | `/api/v1/verification` | `verification.read` |
| POST | `/api/v1/verification` | `verification.write` |
| GET | `/api/v1/verification/{id}` | `verification.read` |
| GET | `/api/v1/verification/{id}/results` | `verification.read` |
| POST | `/api/v1/verification/{id}/cancel` | `verification.write` |
| GET | `/api/v1/backups/{id}/assurance` | `backups.read` |
Ein `restore_test` verlangt zusätzlich `verification.restore_test`. Er schreibt
zwar nur an einen Wegwerfort, liest aber das gesamte Backup und belastet damit
Datenträger und Leitung.
Der vollständige Bericht steht unter `/results` und nicht am Auftrag: Bei einem
großen Backup trägt er viele Befunde, und eine Liste von Aufträgen bliebe damit
nicht mehr überschaubar. Liegt kein Bericht vor, antwortet der Endpunkt mit 404
statt mit einem leeren Ergebnis — ein leeres Ergebnis sähe aus wie ein sauberes.
### Beispiel
```bash
# Integritätsprüfung anstoßen
curl -X POST http://127.0.0.1:8080/api/v1/verification \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"backup_id":"<uuid>","verification_type":"chunk_integrity"}'
# Ergebnis abholen
curl http://127.0.0.1:8080/api/v1/verification/<id>/results -H "Authorization: Bearer $TOKEN"
# Bewertung des Backups
curl http://127.0.0.1:8080/api/v1/backups/<uuid>/assurance -H "Authorization: Bearer $TOKEN"
```
## Nachgewiesener Lebenszyklus
Gegen den laufenden Dienst mit einem echten Repository durchgespielt (420 KB,
drei Objekte, verschlüsselt):
| Schritt | Einstufung | Bewertung |
| --- | --- | --- |
| Backup, ungeprüft | `successful` | 35 % — drei Größen ungemessen |
| nach `chunk_integrity` | `verified` | 55 % |
| nach `restore_test` | `recoverable` | 90 % |
| ein Byte in einem Block gekippt, `chunk_integrity` | `corrupted` | 0 % |
| `restore_test` auf dem beschädigten Backup | `corrupted` | 0 % |
| Block zurückgespielt, `chunk_integrity` | `verified` | 55 % |
| `restore_test` wiederholt | `recoverable` | 90 % |
Der Befund nannte die betroffene Datei (`rechnung.txt`), nicht nur die Zahl der
Blöcke. Die verbleibenden 10 % fehlen für Unveränderlichkeit und eine Kopie an
einem zweiten Ort — beides ist noch nicht umgesetzt und wird deshalb mit null
Punkten geführt, nicht wohlwollend geschätzt.
## Bekannte Grenzen
- **Keine Prüfrichtlinien.** `verification_policies` aus `SYNCOVA_DATABASE.md`
existiert noch nicht; Prüfungen werden von Hand oder über die API angestoßen,
nicht nach Zeitplan. Der Endpunkt `/verification-policies` fehlt entsprechend.
- **Kein zweiter Standort.** `HasOffsiteCopy` ist fest `false`. Die Größe hier
wohlwollend anzunehmen wäre die bequeme und falsche Entscheidung.
- **Auffälligkeiten** werden aus der Zahl übergangener Objekte des Laufs
abgeleitet, nicht aus einer Verlaufsanalyse.
- **Der Wiederherstellungstest schreibt vollständig zurück.** Bei einem 10-TB-
Backup braucht das entsprechend Platz und Zeit; `RestoreTestWorkingDirectory`
legt den Ort fest.