internal/rescan ist das einzige Stück Aether, das hart löscht: DELETE FROM
files, kein Papierkorb. Entfernt wird, was beim Durchgang über die Platte nicht
gesehen wurde.
Der Ernstfall ist nicht ausgedacht. LocalFS.Walk gibt für ein nicht vorhandenes
Verzeichnis nil zurück – kein Fehler, keine Einträge. Genau der Fingerabdruck
eines Bind-Mounts, der nach einem Neustart nicht hochkam, oder eines
Tippfehlers in DATA_PATH. Dann gilt jede Datei als verwaist, und ein einziger
Aufruf mit ?anwenden=true löscht den gesamten Bestand aus der Datenbank. Die
Bytes lägen noch da, aber niemand wüsste mehr, dass es sie gibt – und der GC
räumte sie 24 Stunden später ab.
Der GC hat diese Bremse seit dem 12.09. Beide gleichen Platte und Datenbank ab,
nur aus entgegengesetzter Richtung, und beide haben denselben Fehlerfall: Eine
Seite antwortet fälschlich mit „leer". Der GC löscht dann Blobs – die Datenbank
weiss hinterher noch, was fehlt. Hier fällt die Zeile selbst. Die gefährlichere
Seite war die ungesicherte.
Zwei Bremsen: Die Notbremse (nichts gefunden, Datenbank kennt Dateien) lässt
sich nicht übersteuern. Die Mengenbremse (mehr als die Hälfte) verlangt
&bestaetigt=true. Die leere Installation ist ausgenommen.
Dazu umgebaut: erst sammeln, dann entscheiden, dann löschen. Vorher wurde
mitten in der Schleife gelöscht; die Gesamtzahl kennt man dort erst, wenn die
Hälfte schon weg ist.
Der Bericht nennt den Grund und ob er bestätigbar ist – die Oberfläche soll
keinen Knopf anbieten, der nichts bewirkt. Ein abgebrochener Lauf steht rot in
der Karte und im Audit-Log; gerade der, denn er ist der Hinweis darauf, dass
mit dem Datenordner etwas nicht stimmt.
Fünf Tests, unter Mutation nachgewiesen. Der Ernstfall läuft am echten Gerät:
fünf Zeilen in der Datenbank, ein Wurzelverzeichnis ohne users/ – nach Apply
stehen alle fünf noch, auch mit Bestätigung.
audit und thumbnails bekommen bewusst keine Go-Tests; die Begründung steht in
BEFUNDE.md statt als ewig offener Punkt.
Nebenbefund behoben: kalender-teilen-selbsttest.py leckte je Lauf ein
App-Passwort des Besitzers. Nach achtzehn Läufen war maxAppPasswords = 20
erreicht, und zwei Suiten scheiterten mit 409, ohne dass am Code etwas kaputt
war.
Geprüft: go build/vet sauber, alle Go-Pakete grün, 243 Prüfungen der sieben
Selbsttest-Suiten grün, 48 Vitest-Tests, tsc sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nicht als eigene Tabelle, wie im Plan vorgesehen: acl_entries beantwortet
fuer Ordner und Dateien laengst dieselbe Frage -- wer darf was, direkt oder
ueber eine Gruppe. Ein zweites Rechtesystem daneben waere eine zweite
Antwort auf dieselbe Frage, mit eigener Gruppenaufloesung, eigenem
Widerruf, eigenem Pruefpfad; die Luecke dazwischen faellt erst auf, wenn
jemand sieht, was er nicht sehen soll. Migration 25 haengt deshalb nur eine
dritte Ressourcenspalte an.
Der Gewinn: Freigeben an eine **Gruppe** kostete keine Zeile extra.
Drei Entscheidungen, die nicht offensichtlich waren:
* Ein fremder Kalender steht immer unter seiner UUID, nie unter dem
Pfadsegment des Eigentuemers -- das hat der sich ausgesucht, und zwei
Leute duerfen denselben Namen waehlen. Sonst laegen im Kalenderbestand
des Empfaengers zwei Kalender auf derselben Adresse.
* Alle Wege laufen durch RequireCalendar. CalDAV und Weboberflaeche sind
zwei getrennte Zugaenge zu denselben Daten; ein Recht, das nur einer von
beiden prueft, ist kein Recht. Im CalDAV-Teil genuegte, resolveCalendar
umzustellen -- dort kommt jede Operation vorbei.
* Wer ein Recht geliehen hat, darf es nicht weiterverleihen: Umbenennen,
Loeschen und Freigeben bleiben auch mit Schreibrecht beim Eigentuemer.
Oberflaeche: fremde Kalender tragen Eigentuemer und "(nur lesen)", das
Menue zeigt nur, was auch geht, und ein Termin laesst sich gar nicht erst
in einen Kalender ziehen, den der Server danach ablehnen wuerde.
scripts/kalender-teilen-selbsttest.py, 41 Pruefungen, jede ueber beide
Zugaenge. Er beginnt damit, dass der Empfaenger vorher nichts sieht --
ohne diesen Anfang beweist der Rest nichts.
Dabei hatte der Test selbst eine Falle: Mit festem Testdatum lief die
Terminliste aus ihrem Standardzeitraum, und die Pruefung "beim Eigentuemer
ist nichts angekommen" bestand **leer**.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>