From 1940e2b79093f47c36902a999e810ce0a516a8cf Mon Sep 17 00:00:00 2001 From: "Hermes (Server)" Date: Thu, 27 Aug 2026 10:31:49 +0200 Subject: [PATCH] =?UTF-8?q?Sync-Ausbau:=20L=C3=B6schpfad=20gefixt=20?= =?UTF-8?q?=E2=80=94=20Spec=20+=20Plan=20entblockt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hermes hat den irreversiblen Löschpfad in melo_cloud.py behoben: Der Dedup-Zweig von upload() stellt die Datei jetzt aus den hochgeladenen Bytes wieder her, wenn registry_pfad(sid) leer ist, statt sie zu verwerfen. End-to-end verifiziert (eigene Testdatei, Fake-Nutzer, Testdaten danach restlos entfernt — Registry vor und nach dem Test bei 325 Titeln): Upload → beide Kopien da · Löschung → beide weg, Download 404 (Bug reproduziert) · erneuter Upload → beide Kopien zurück, Download bitgenau identisch mit dem Original. Damit ist die letzte offene Frage der Spec (A6) beantwortet und keine Stufe mehr blockiert. Tasks 5-7 des Plans sind entblockt; Tasks 10-12 hängen nur noch an der Produktfrage "wird Feature 2 überhaupt gebaut?" (0 Playlisten am Server). Nebenbefund aus dem Test, als Backlog-Notiz festgehalten: _link_user() legt im Dedup-Zweig einen dritten Hardlink unter users// an, den _entferne_datei_wenn_verwaist() nicht abräumt. Server-Hygiene für Hermes, kein Datenverlust. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CcDiyJdVRqh1TtJk5JiabX --- .../plans/2026-08-27-sync-ausbau.md | 55 ++++---- .../specs/2026-08-27-sync-ausbau-design.md | 129 ++++++++++++------ 2 files changed, 116 insertions(+), 68 deletions(-) diff --git a/docs/superpowers/plans/2026-08-27-sync-ausbau.md b/docs/superpowers/plans/2026-08-27-sync-ausbau.md index 9ad7e88..eeacca4 100644 --- a/docs/superpowers/plans/2026-08-27-sync-ausbau.md +++ b/docs/superpowers/plans/2026-08-27-sync-ausbau.md @@ -94,10 +94,11 @@ Tests, SharedPreferences, Provider. **Keine neue Dependency.** - Ponytail-Prinzip: minimale, robuste Lösungen. Drei Nutzer, ~325 Songs, 0 Favoriten und 0 Playlisten auf dem Server — nichts überbauen. - **Reihenfolge ist bindend.** Tasks 1–4 (Favoriten-Fix) hängen an nichts und - kommen zuerst. Tasks 5–7 sind **blockiert bis Dustin A6 beantwortet hat** - (irreversibler Löschpfad). Tasks 10–12 sind **blockiert bis der Server-Fix - `melo_cloud.py:505` steht** bzw. bis Dustin entscheidet, ob Feature 2 - überhaupt gebaut wird. + kommen zuerst. **Tasks 5–7 sind seit dem 2026-08-27 ENTBLOCKT** — der + Löschpfad ist serverseitig behoben und end-to-end verifiziert (siehe Spec, + §ERLEDIGT). Tasks 10–12 bleiben blockiert, aber nur noch durch die + Produktfrage „wird Feature 2 überhaupt gebaut?" (0 Playlisten am Server) — + Details bei Task 10. --- @@ -1207,13 +1208,13 @@ git commit -m "Herz-Tipp meldet den Favoriten sofort deterministisch an die Clou ### Task 5: `ladeAusgewaehlteHoch` + `abbrechen()` im `SyncService` -> **BLOCKIERT bis Dustin A6 beantwortet hat.** -> A6 = „Ist der irreversible Löschpfad gewollt?" (Spec, §OFFENE ENTSCHEIDUNG). -> Dieser Task gibt mehr Titeln eine cloudId und vergrößert damit genau die -> Angriffsfläche des Löschpfads. Lautet die Antwort **„nein"**, gehört der -> Server-Auftrag „Datei-Wiederherstellung im Dedup-Zweig von `upload()`" -> **davor**. Lautet sie **„ja"**, darf sofort begonnen werden. -> **Nicht ohne Antwort anfangen.** +> ✅ **ENTBLOCKT seit 2026-08-27.** Die frühere Blockade lautete: dieser Task +> gibt mehr Titeln eine cloudId und vergrößert damit die Angriffsfläche des +> irreversiblen Löschpfads. Dustins Antwort auf A6 war „nein, nicht gewollt"; +> Hermes hat den Fix am selben Tag eingebaut (Datei-Wiederherstellung im +> Dedup-Zweig von `upload()`), end-to-end verifiziert. Ein versehentlicher +> Löschvorgang ist jetzt durch erneutes Hochladen reparabel. +> **Darf gebaut werden.** **Dateien:** - Ändern: `lib/services/sync_service.dart:143-155` (Feld `_abbruchGewuenscht`) @@ -1555,8 +1556,9 @@ git commit -m "Gezielter Upload mit Abbrechen aus dem Auswahl-Modus" ### Task 6: Auswahl-Modus-Aktion „Auf den Server laden" -> **BLOCKIERT bis Dustin A6 beantwortet hat** — dieselbe Begründung wie -> Task 5: die Aktion ist die Oberfläche zu `ladeAusgewaehlteHoch`. +> ✅ **ENTBLOCKT seit 2026-08-27** — dieselbe Begründung wie Task 5 (die +> Aktion ist die Oberfläche zu `ladeAusgewaehlteHoch`), und dieselbe +> Auflösung: Löschpfad serverseitig behoben. **Darf gebaut werden.** **Dateien:** - Ändern: `lib/shared/auswahl_leiste.dart:18-66` (`AuswahlLeiste` bekommt @@ -1875,14 +1877,11 @@ git commit -m "Auswahl-Modus: Auf den Server laden (nur in Meine Musik)" ### Task 7: Einzel-Song-Offline -> **BLOCKIERT bis Dustin A6 beantwortet hat** — nur, weil die Spec Stufe 2 -> (Feature 3 **und** 4) als Ganzes hinter die A6-Antwort stellt. -> **Ehrlicher Hinweis für Dustin:** Die inhaltliche Begründung der Spec -> („vergrößert die Angriffsfläche des Löschpfads, weil mehr Titel eine cloudId -> bekommen") trifft auf **Feature 4 nicht zu** — ein Download vergibt keine -> cloudId und meldet nichts zum Server. Dieser Task könnte also gefahrlos -> vorgezogen werden. Das zu entscheiden ist Dustins Sache, nicht die des -> Umsetzenden — bis dahin bleibt der Task blockiert. +> ✅ **ENTBLOCKT seit 2026-08-27** (Löschpfad serverseitig behoben). Der Task +> war ohnehin nur mitblockiert, weil die Spec Stufe 2 als Ganzes hinter die +> A6-Antwort stellte — die inhaltliche Begründung traf auf Feature 4 nie zu: +> ein Download vergibt keine cloudId und meldet nichts zum Server. +> **Darf gebaut werden.** **Dateien:** - Ändern: `lib/services/download_service.dart:70-111` (neue Methode @@ -3989,11 +3988,15 @@ git push -u origin feature/sync-ausbau ## Offene Punkte, die vor bzw. während der Umsetzung an Dustin gehen -1. **A6 — irreversibler Löschpfad** (blockiert Tasks 5–7). - „Verschwindet eine lokale Datei, löscht der Abgleich sie am Server und aus - der Navidrome-Bibliothek; erneutes Hochladen repariert das nicht. Gewollt?" - *Nebenfrage:* Task 7 (Einzel-Song-Offline) vergibt keine cloudId und ist - von der Begründung sachlich nicht betroffen — darf er vorgezogen werden? +1. ~~**A6 — irreversibler Löschpfad**~~ ✅ **ERLEDIGT 2026-08-27.** Dustins + Antwort: nicht gewollt. Hermes hat die Datei-Wiederherstellung im + Dedup-Zweig von `upload()` eingebaut, end-to-end verifiziert (Upload → + Löschung → erneuter Upload stellt beide Kopien bitgenau wieder her). + Tasks 5–7 sind damit entblockt. + *Offen geblieben, aber kein Blocker:* Die App tombstoned weiterhin + großzügig (nicht eingehängte SD-Karte genügt) und die Lösch-Bremse greift + erst ab 109 von 325 Titeln — der Schaden ist jetzt nur reparabel, nicht + verhindert. Ob die App vorsichtiger werden soll, ist eine eigene Frage. 2. **Feature 2 (Playlist-Sicherung, Tasks 10–12).** Bauen oder streichen? Heute 0 Playlisten am Server. Empfehlung: vertagen. Falls bauen: hängt der Server-Fix `melo_cloud.py:505` wirklich davor, diff --git a/docs/superpowers/specs/2026-08-27-sync-ausbau-design.md b/docs/superpowers/specs/2026-08-27-sync-ausbau-design.md index fb3c426..bd5d462 100644 --- a/docs/superpowers/specs/2026-08-27-sync-ausbau-design.md +++ b/docs/superpowers/specs/2026-08-27-sync-ausbau-design.md @@ -1,11 +1,13 @@ # Sync-Ausbau: Favoriten-Fix, Auswahl-Upload, Einzel-Song-Offline, Playlist-Sicherung -Status: Nachgeschärft nach agent-review-panel (Urteil Phase 14, 2026-08-27: -Score 6/10, Verdikt „Spec nachschärfen dann freigeben"). Alle 17 -Aktionspunkte A1–A17 sind eingearbeitet. Bedingung des Richters: diese -Fassung wird **einmal kurz gegengelesen**, bevor der Implementierungsplan -entsteht — A1, A5 und A7 ändern, *was* gebaut wird, nicht nur wie es -beschrieben ist. +Status: **Freigegeben, umsetzungsbereit** (2026-08-27). +Nachgeschärft nach agent-review-panel (Urteil Phase 14: Score 6/10, +Verdikt „Spec nachschärfen dann freigeben"), alle 17 Aktionspunkte +A1–A17 eingearbeitet, Gegenlesen durch einen frischen Prüfer bestanden +(Bedingung des Richters erfüllt). Die einzige offene Frage (A6, +Löschpfad) ist beantwortet und serverseitig behoben — siehe §ERLEDIGT. +**Keine Stufe ist blockiert.** Implementierungsplan: +`docs/superpowers/plans/2026-08-27-sync-ausbau.md` (13 Tasks). ## Kontext @@ -56,25 +58,41 @@ der Bau-Ansatz bleiben unangetastet): - Die **Playlist-Konfliktregel** („längerer Stand gewinnt") entfällt ersatzlos, weil es ohne Rück-Merge keinen Konflikt mehr gibt (A5). -## OFFENE ENTSCHEIDUNG — Rückfrage an Dustin (noch nicht beantwortet) +## ERLEDIGT — Löschpfad ist gefixt (2026-08-27) -> **Ist der irreversible Löschpfad gewollt?** -> Verschwindet eine lokale Datei (SD-Karte nicht eingehängt, Berechtigung -> entzogen, Dateimanager), löscht der Sync sie auf dem Server — und damit -> auch aus der Navidrome-Bibliothek. Erneutes Hochladen repariert das -> nachweislich **nicht**. Die technische Kette steht vollständig belegt -> unter §Risiken → „Irreversibler Löschpfad". +> **Die Rückfrage an Dustin („Ist der irreversible Löschpfad gewollt?") +> ist beantwortet: nein.** Hermes hat den Fix am selben Tag in +> `melo_cloud.py` eingebaut und den Dienst neu gestartet. > -> - **Antwort „ja, gewollt“** → es ist eine dokumentierte Eigenschaft, -> nichts weiter zu tun. -> - **Antwort „nein“** → ein Server-Auftrag (Dedup-Zweig stellt die Datei -> wieder her, wenn `registry_pfad(sid)` leer ist) gehört **vor** -> Feature 3. +> **Was sich geändert hat:** Im Dedup-Zweig von `upload()` prüft der +> Server jetzt `registry_pfad(sid) is None` und stellt in diesem Fall die +> Datei aus den hochgeladenen Bytes wieder her (`shutil.move` nach `REG` +> plus `UPDATE registry SET path`), statt sie über `os.remove(tmp)` zu +> verwerfen. Der bestehende `verknuepfe_navidrome`-Aufruf hängt sie +> danach automatisch zurück in die Navidrome-Bibliothek. > -> Kein Reviewer kann das entscheiden — es ist eine Produktfrage. -> **Stand: unbeantwortet.** Die Reihenfolge in §Reihenfolge ist bewusst -> so gewählt, dass mit Stufe 1 begonnen werden kann, ohne dass die -> Antwort vorliegt. +> **End-to-End verifiziert** (2026-08-27, eigene Testdatei, Fake-Nutzer, +> Testdaten danach restlos entfernt — Registry vor und nach dem Test bei +> 325 Titeln): Upload → beide Kopien da, Download bitgenau · Löschung → +> beide Dateien weg, Download 404 (Bug reproduziert) · **erneuter Upload +> → beide Dateien zurück, Download wieder bitgenau identisch.** +> +> **Folge für diese Spec: keine Stufe ist mehr blockiert.** Der +> ursprüngliche Grund, Feature 3 (Auswahl-Upload) hinter die Klärung zu +> stellen — es gibt mehr Titeln eine cloudId und vergrößert damit die +> Angriffsfläche —, ist entfallen: Ein versehentlicher Löschvorgang ist +> jetzt durch erneutes Hochladen reparabel. Die Reihenfolge in +> §Reihenfolge bleibt trotzdem so bestehen, weil sie auch aus anderen +> Gründen sinnvoll ist (Stufe 1 behebt den einzigen belegten Bug und +> hängt an nichts). +> +> **Nebenbefund aus dem Test, nicht blockierend:** `_link_user()` wird +> nur im Dedup-Zweig aufgerufen (der Neu-Zweig kehrt vorher zurück) und +> legt einen dritten Hardlink unter `users//.mp3` an, den +> `_entferne_datei_wenn_verwaist()` beim Löschen nicht mit abräumt. Bisher +> folgenlos (das Verzeichnis war leer, weil im Normalbetrieb fast alles +> über den Neu-Zweig läuft), aber jeder künftige Dedup-Upload hinterlässt +> einen verwaisten Hardlink. Server-Hygiene für Hermes, kein Datenverlust. ## Ziele @@ -465,22 +483,23 @@ bei leerer lokaler Tabelle. muss:** - Stufe 1 (A1/A2/A3) **berührt den Löschpfad nicht** und hängt an keiner - offenen Frage. Deshalb kann mit der Umsetzung begonnen werden, **ohne - dass die Antwort auf die A6-Rückfrage vorliegt**. Sie behebt außerdem - als einzige einen belegten Datenverlust-Bug — sie zuerst zu liefern - ist auch inhaltlich richtig. -- Der **Auswahl-Upload (Feature 3) kommt bewusst NACH der Klärung** der - A6-Rückfrage: Er gibt mehr Titeln eine cloudId und vergrößert damit - genau die Angriffsfläche des irreversiblen Löschpfads. Lautet die - Antwort „nein, nicht gewollt“, gehört der Server-Auftrag - (Datei-Wiederherstellung im Dedup-Zweig) davor. -- Alles, was auf einen fremden Auftrag wartet (Server-Fix `:505`, - `song_upload`-Event) oder auf einen Build-Umbau - (`flutter_local_notifications`), steht hinten — das betrifft die - **Stufen 4–6**. Innerhalb dieser drei blockiert keine Stufe eine - frühere. Für Stufe 2 gilt das ausdrücklich **nicht**: sie wartet auf - die A6-Antwort (Absatz darüber), und Stufe 4 hängt zusätzlich am - Server-Fix `:505` (§Offene Abhängigkeiten, Zeile 1). + offenen Frage. Sie behebt außerdem als einzige einen belegten + Datenverlust-Bug — sie zuerst zu liefern ist auch inhaltlich richtig. +- Der **Auswahl-Upload (Feature 3)** stand ursprünglich hinter der + A6-Klärung, weil er mehr Titeln eine cloudId gibt und damit die + Angriffsfläche des Löschpfads vergrößert. **Dieser Grund ist seit dem + 2026-08-27 entfallen** (Löschpfad serverseitig entschärft, siehe + §ERLEDIGT). Die Stufe bleibt an ihrer Position, weil Stufe 1 den + einzigen belegten Bug behebt und deshalb vorne gehört — nicht mehr, + weil Stufe 2 blockiert wäre. +- Alles, was auf einen fremden Auftrag wartet (`song_upload`-Event) oder + auf einen Build-Umbau (`flutter_local_notifications`), steht hinten — + das betrifft die **Stufen 4–6**. Innerhalb dieser drei blockiert keine + Stufe eine frühere. + +**Stand 2026-08-27: keine Stufe dieser Spec ist mehr blockiert.** Die +einzige offene Fremdabhängigkeit (`song_upload`-Event) betrifft +ausschließlich die spätere SSE-Stufe, die nicht Teil dieser Spec ist. ## Fehlerfälle — zu bauende Arbeit, keine Zusagen @@ -536,7 +555,15 @@ Verhalten im Einzelnen: Bestandsfehler, von Feature 2 nicht angefasst, aber beim Testen präsent. -### Irreversibler Löschpfad (Bestand, nicht von dieser Spec verursacht — Backlog-P0) +### Löschpfad (war Backlog-P0 — am 2026-08-27 serverseitig entschärft) + +**Der folgende Abschnitt beschreibt den Stand VOR dem Fix.** Die Kette +selbst besteht unverändert — eine verschwundene lokale Datei führt +weiterhin zur Server-Löschung. Was sich geändert hat: Sie ist nicht mehr +*irreversibel*. Erneutes Hochladen derselben Datei stellt sie jetzt in +Registry und Navidrome-Bibliothek wieder her (siehe §ERLEDIGT ganz oben). +Der letzte Satz des Kastens („Erneutes Hochladen repariert es **nicht**") +gilt daher **nicht mehr**. > `markMissing` (`database.dart:237-245`) tombstoned jeden Titel, dessen > Datei der Scan nicht findet — auch unbeabsichtigt (SD-Karte nicht @@ -552,8 +579,13 @@ Verhalten im Einzelnen: > dauerhaft 404 (`:1400-1408`). Bisher nie ausgelöst (Live-DB: > `SUM(deleted)=0`). -Die zugehörige Rückfrage an Dustin ist **noch offen** — siehe §OFFENE -ENTSCHEIDUNG ganz oben. +Die zugehörige Rückfrage an Dustin ist **beantwortet und behoben** — +siehe §ERLEDIGT ganz oben. Was bleibt: Die App tombstoned weiterhin +großzügig (nicht eingehängte SD-Karte genügt), und die Lösch-Bremse +greift weiterhin erst ab 109 von 325 Titeln. Beides ist App-Seite, +bewusst unangetastet und kein Blocker — der Schaden ist jetzt reparabel. +Ob die App zusätzlich vorsichtiger tombstonen soll, bleibt eine offene +Backlog-Frage. ### Verworfene Alternativen @@ -638,7 +670,7 @@ Tests): | # | Auftrag | Art | Blockiert | |---|---|---|---| | 1 | `melo_cloud.py:505` — Owner-Prüfung in `handle_playlist_delete` | **Voraussetzung** | Feature 2 | -| 2 | Datei-Wiederherstellung im Dedup-Zweig von `upload()` | **Voraussetzung, falls die Antwort auf die offene Entscheidung „nein" lautet** | Feature 3 | +| 2 | ~~Datei-Wiederherstellung im Dedup-Zweig von `upload()`~~ | ✅ **ERLEDIGT 2026-08-27** (Hermes; end-to-end verifiziert) | nichts mehr | | 3 | `song_upload`-SSE-Event | Voraussetzung für die spätere SSE-Stufe | nichts in dieser Spec | | 4 | Playlist-Rename-Endpunkt (`PUT`/`PATCH`) | optional | nichts (Feature 2 braucht ihn nicht mehr) | | 5 | Playlist-Tombstones serverseitig | optional / Backlog | nichts | @@ -652,3 +684,16 @@ Tests): beidseitig vorhanden, ohne dass der Server geändert werden müsste. - `server_titel_screen._geladen` ist ein Einmal-Snapshot (Bestandsfehler) — fällt beim Einbau des Einzel-Song-Knopfs auf. +- **Server-Hygiene (2026-08-27 beim Löschpfad-Test gefunden):** + `_link_user()` wird nur im Dedup-Zweig von `upload()` aufgerufen (der + Neu-Zweig kehrt vorher zurück) und legt einen dritten Hardlink unter + `users//.mp3` an, den `_entferne_datei_wenn_verwaist()` beim + Löschen nicht mit abräumt. Bisher folgenlos — das Verzeichnis war leer, + weil im Normalbetrieb fast alles über den Neu-Zweig läuft. Seit dem + Wiederherstellungs-Fix hinterlässt aber jeder Dedup-Upload einen + verwaisten Hardlink. Auftrag an Hermes, kein Datenverlust, eilt nicht. +- **App-Seite, bewusst offen:** `markMissing` tombstoned großzügig (eine + nicht eingehängte SD-Karte genügt) und die Lösch-Bremse greift erst ab + 109 von 325 Titeln. Seit dem Server-Fix ist der Schaden reparabel, also + kein Blocker — ob die App trotzdem vorsichtiger tombstonen soll, ist + eine eigene Entscheidung.