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

11 KiB

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:

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:

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

# 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.