# Repository Engine Dieses Dokument beschreibt den **aktuell implementierten** Stand nach Phase 2. ## Leitgedanke > Ein Repository ist selbstbeschreibend. Es enthält alles, was zur Wiederherstellung nötig ist — PostgreSQL hält davon nur eine Kopie zur schnellen Abfrage. Daraus folgt die Prüffrage für jede Designentscheidung: *Bliebe das Repository nutzbar, wenn der Control Server und seine Datenbank ersatzlos verschwinden?* Alles, was diese Frage mit Nein beantwortet, ist falsch — auch wenn es bequemer wäre. ## Aufbau ```text repository/ format/ repository.json — Descriptor: Format, Verfahren, Betriebsart manifests/ .manifest.json — die maßgebliche Beschreibung je Backup chunks/ aa/bb/ — inhaltsadressierte Datenblöcke indexes/ catalog.json — Übersicht (nur Beschleuniger, jederzeit neu erzeugbar) journals/ .journal.json — Fortschritt laufender Schreibvorgänge verification/ scan-.json — Ergebnisse der Integritätsläufe metadata/ ergänzende Angaben staging/ unfertige Daten bis zum Commit repository.lock Schreibsperre ``` ## Chunk-Ablage Die Kennung eines Chunks **ist** sein SHA-256-Inhaltshash. Daraus folgt unmittelbar: - **Deduplizierung ohne Index** — gleicher Inhalt ergibt dieselbe Kennung und damit denselben Ablageort. Ein bereits vorhandener Chunk wird schlicht nicht erneut geschrieben. - **Integritätsprüfung ohne Zusatzdaten** — beim Lesen wird neu gehasht und mit der Kennung verglichen. Eine separate Prüfsummenliste, die selbst beschädigt werden könnte, gibt es nicht. **Warum SHA-256:** kryptografisch sicher (PROMPT.md §9), Teil der Standardbibliothek, auf allen Zielplattformen hardwarebeschleunigt. Eine Fremdbibliothek brächte hier keinen Vorteil, aber eine zusätzliche Abhängigkeit im sicherheitskritischsten Pfad des Produkts. **Fanout:** `chunks/aa/bb/` — zwei Ebenen à zwei Hex-Zeichen ergeben 65 536 Verzeichnisse. Ohne diese Aufteilung lägen Millionen Dateien in einem einzigen Verzeichnis, was jede Suche unbrauchbar langsam macht. **Kennungen werden vor jeder Pfadbildung geprüft.** Sie stammen aus Manifesten, die auch aus einem fremden Repository kommen können. Ohne Prüfung liesse sich über einen Wert wie `../../etc/passwd` auf beliebige Pfade zugreifen. ## Crash-sicheres Schreiben Jede Datei entsteht in vier Schritten: 1. In eine temporäre Datei **im Zielverzeichnis** schreiben (damit `rename` keine Dateisystemgrenze überschreitet und atomar bleibt). 2. `fsync` auf die Datei — erst danach liegen die Daten wirklich auf dem Datenträger. 3. `rename` auf den Zielnamen — unter POSIX atomar. 4. `fsync` auf das Verzeichnis — erst danach überlebt auch der Namenseintrag einen Stromausfall. Ohne Schritt 2 und 4 könnte nach einem Absturz eine Datei existieren, deren Inhalt leer oder unvollständig ist — ein beschädigtes Backup, das sich als vollständig ausgibt. ## Commit-Protokoll Sieben Schritte laut SYNCOVA_ARCHITECTURE.md §10: | Schritt | Vorgang | | --- | --- | | 1 | Session öffnen, Journal anlegen | | 2 | Chunks schreiben | | 3 | Manifest erzeugen, Kennzahlen aus der Session übernehmen | | 4 | Prüfsumme bilden und sofort gegenprüfen | | 5 | Manifest atomar an seinen Platz bringen — **hier wird das Backup sichtbar** | | 6 | Katalog fortschreiben | | 7 | Erfolg melden | **Vor Schritt 5 existiert das Backup für keinen Leser.** Bricht der Vorgang vorher ab, bleiben nur Chunks liegen — die ein späterer Lauf wiederverwendet. Es entsteht **nie** ein halbes, scheinbar gültiges Backup. Schlägt Schritt 6 fehl, ist das Backup dennoch vollständig: der Katalog lässt sich jederzeit neu aufbauen. Das wird protokolliert, nicht als Fehler gemeldet. **Die Kennzahlen im Manifest stammen immer aus der Session**, nie vom Aufrufer — erfundene Statistiken wären eine Falschaussage über gesicherte Daten. ## Abschlussvermerk Ein Manifest gilt nur mit `complete: true` **und** gültigem `content_hash`. Der Hash deckt alle Felder außer diesen beiden ab. Fehlt der Vermerk oder passt der Hash nicht, liefert `ReadManifest` einen Fehler und der Katalog-Wiederaufbau überspringt das Backup. Ein unvollständiges Backup als wiederherstellbar auszuweisen wäre der schwerste denkbare Fehler dieses Produkts. ## Katalog und Wiederaufbau Der Katalog ist **ausschließlich ein Beschleuniger**. Er wird automatisch neu erzeugt, wenn er fehlt, unlesbar ist oder zu einem fremden Repository gehört (erkennbar an der Repository-Kennung). Der Wiederaufbau liest ausschließlich die Manifeste — keine Datenbank, keine externe Quelle: ```bash syncova-repo rebuild --path /backup/repository-01 ``` Das ist der Kern der Repository-Wiederherstellung (PROMPT.md §46): Repository anhängen, Katalog aufbauen, wiederherstellen. ## Integritätsprüfung ```bash syncova-repo scan --path # nur Anwesenheit prüfen (schnell) syncova-repo scan --path --deep # jeden Chunk neu hashen (vollständig) ``` Geprüft wird **von den Manifesten aus**, nicht von der Chunk-Ablage: nur so zeigt sich, ob ein Backup tatsächlich wiederherstellbar ist. Ein von mehreren Backups genutzter Chunk wird dabei nur einmal gelesen. Der Bericht benennt betroffene Backups namentlich und liefert einen Exit-Status ungleich 0, damit ein Überwachungssystem anschlägt. Die Zusammenfassung beschönigt nichts: „*n* von *m* Backups sind NICHT vollständig wiederherstellbar." Verwaiste Chunks sind eine Warnung, kein Datenverlust — sie belegen nur Speicherplatz. ## Bereinigung ```bash syncova-repo prune --path # Simulation syncova-repo prune --path --apply # tatsächlich ausführen ``` Bewusst getrennt vom Scan, da potenziell datenzerstörend. Zwei Schutzmaßnahmen: - **Ist ein Manifest unlesbar, bricht die Bereinigung ab.** Die Menge der benötigten Chunks wäre unbekannt; ein Löschen auf unvollständiger Grundlage könnte Daten vernichten. - **Sie verlangt die Schreibsperre.** Liefe sie während eines Backups, hätte ein gerade abgelegter Chunk noch kein Manifest und würde als verwaist gelöscht. ## Sperren Eine Sperrdatei (`O_CREATE|O_EXCL`, damit das Anlegen unteilbar ist) verhindert gleichzeitige Schreibzugriffe. Sie enthält PID, Hostname und Zeitpunkt, damit eine hängengebliebene Sperre einzuordnen ist. **Lesender Zugriff ist parallel möglich** (`OpenOptions{ReadOnly: true}`) — eine Wiederherstellung muss auch während eines laufenden Backups funktionieren. Nach einem Absturz bleibt die Sperre liegen. Sie wird **nicht** automatisch gelöst — das wäre gefährlich, falls der Vorgang doch noch läuft. Der Eingriff ist ausdrücklich manuell (`repository.BreakLock`). ## Abgebrochene Sessions `FindStaleSessions` findet Journale, die ein früherer Lauf offen gelassen hat, und meldet, ob trotzdem ein Manifest vorliegt (dann war der Abschluss vollzogen und nur das Aufräumen unterbrochen). `CleanupStaleSession` räumt Journal und Arbeitsverzeichnis weg — ein bereits geschriebenes Manifest bleibt unangetastet. Die Chunks eines abgebrochenen Laufs bleiben liegen und werden vom nächsten Versuch wiederverwendet. Das ist das Resume-Verhalten aus PROMPT.md §81: ein bei 70 % abgebrochenes Backup beginnt nicht bei null. ## Gehärteter Modus `--hardened` setzt `immutable: true`. Wirkung: - Jedes Backup erhält eine Aufbewahrungsfrist (Standard 30 Tage). - `DeleteBackup` scheitert, solange die Frist läuft — unabhängig davon, wer löschen möchte. - Manifeste werden schreibgeschützt abgelegt (`0400`). **Grenze, die nicht verschwiegen wird:** Der Schreibschutz ist eine zusätzliche Hürde, keine Garantie. Wer Eigentümer der Datei ist, kann die Rechte ändern. Echte Unveränderlichkeit muss die Speicherebene durchsetzen — S3 Object Lock, WORM-Medien oder ein gehärtetes Linux-System mit getrenntem Konto. PROMPT.md §15 verlangt ausdrücklich, keine Unveränderlichkeit zu behaupten, die die Speicherebene nicht hergibt. ## Statistiken `DeduplicationRatio()` liefert **zwei** Werte: das Verhältnis und ob es überhaupt existiert. Wurde jeder Block bereits vorgefunden, ist nichts abgelegt worden und eine Division nicht definiert. Ein stillschweigend geliefertes `0` würde den besten aller Fälle als den schlechtesten darstellen. Für Anzeigen ist `SavingsPercentage()` die verlässlichere Angabe — sie ist immer bestimmbar; 100 % bedeutet vollständige Deduplizierung. ## Noch nicht umgesetzt - **Kompression und Verschlüsselung der Chunks** (Phase 3/4). Die Felder `encryption_key_version` und `compression_algorithm` sind bereits Teil des Manifestformats — eine spätere Ergänzung wäre ein Formatbruch. - **Variables Chunking** (Content-Defined Chunking). Derzeit bestimmt der Aufrufer die Blockgrenzen; feste Grenzen erkennen Einfügungen mitten in einer Datei nicht als Verschiebung. - **S3-kompatibler Objektspeicher.** Die `Repository`-Schnittstelle ist dafür geschnitten, umgesetzt ist bislang nur das Dateisystem. - **Synthetic Full und Forever Forward Incremental.** Kettenverwaltung und -prüfung stehen, das Zusammenführen fehlt.