# 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":"","verification_type":"chunk_integrity"}' # Ergebnis abholen curl http://127.0.0.1:8080/api/v1/verification//results -H "Authorization: Bearer $TOKEN" # Bewertung des Backups curl http://127.0.0.1:8080/api/v1/backups//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.