Sync-Ausbau: Löschpfad gefixt — Spec + Plan entblockt
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/<user>/ an, den _entferne_datei_wenn_verwaist() nicht abräumt. Server-Hygiene für Hermes, kein Datenverlust. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcDiyJdVRqh1TtJk5JiabX
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2a1cf9cdc2
commit
1940e2b790
@@ -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/<user>/<sid>.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/<user>/<sid>.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.
|
||||
|
||||
Reference in New Issue
Block a user