Commit Graph

948 Commits

Author SHA1 Message Date
Sucukdeluxe
a35ddf68b2 Ferndiagnose: Allowlist/Port/Modus im Backup mitsichern (Token bleibt pro Server)
Neuer Schalter "Ferndiagnose-Einstellungen mitsichern" (Einstellungen, Default aus,
spiegelt "Download-Liste mitsichern"). Damit reisen die wiederkehrenden, server-
unabhaengigen MCP-Einstellungen im Backup-Export/Import mit, statt sie auf jedem der
5-6 Server neu einzutippen.

Mitgesichert wird NUR was identisch ueber alle Server ist und kein Geheimnis/keine
Identitaet traegt: Allowlist, Port, Freigabemodus (lokal/Netzwerk). Bewusst NICHT
mitgesichert:
- Token: wird pro Server automatisch frisch erzeugt (nie vom Nutzer getippt → kein
  Tipp-Aufwand gespart). Der Backup-Schluessel ist eine fest verdrahtete Konstante →
  ein geleaktes .mdd duerfte sonst Lesezugriff auf ALLE Server geben. Backstop: ohne
  Token ist ein frisch importierter Server inert (checkAuth weist alles ab), bis der
  Nutzer einmal "Aktivieren" drueckt und ein Token erzeugt — keine stille Freigabe.
- Oeffentliche Adresse + Name: pro Server verschieden (sonst zeigte der erzeugte
  Verbindungscode des Zielservers auf den falschen Host).

Mechanik:
- mcpRemote als eigene Top-Level-Sektion im BackupPayload (NICHT in settings —
  normalizeSettings ist ein Whitelist-Rebuild und wuerde unbekannte Keys verwerfen).
  Form {allowlist, port, hostMode} erzwingt die Policy strukturell (kein Token-Feld).
  Kein version-Bump (Import ist lenient, additiv vor-/rueckwaertskompatibel).
- Export sammelt aus getDebugServerRuntimeStatus()+getDebugAllowlist(), nur wenn der
  Schalter an ist; liest bewusst KEIN Token, KEINE Remote-Meta.
- Import wendet die Sektion NUR im Settings-only-Zweig an (writeDebugServerConfig +
  restartDebugServer, damit der laufende Server die Allowlist sofort uebernimmt). Im
  Full-Backup-Zweig nicht — die App relauncht dort, der Boot liest die Dateien neu.
- Sicherheitsguard: Netzwerk-Modus mit LEERER Allowlist bindet local (127.0.0.1) statt
  0.0.0.0 — nie stille Freigabe aus korrupten Daten.
- backupIncludeMcp in AppSettings + defaultSettings + normalizeSettings (sonst wuerde
  der Schalter beim Speichern verworfen).

Tests (backup-mcp.test.ts, 11): Export-Gating + Policy-Guard (Sektion traegt nur
allowlist/port/hostMode), resolveMcpRemoteRestore-Matrix (hostMode-Mapping, Port-
Grenzen, Allowlist-Filter, Netzwerk-ohne-Allowlist→local, null bei fehlend), und der
load-bearing Live-Round-Trip: Export → Resolve → Apply → restartDebugServer →
getDebugAllowlist()/Status spiegeln die importierten Werte (beweist dass der Restart
feuert), Token unangetastet, kein debug_remote.json. Volle Suite 917 gruen, tsc=6.
2026-06-19 19:40:39 +02:00
Sucukdeluxe
c79a031be8 todo: MCP-Ferndiagnose Review (v1.7.223 released, P5 = Nutzer-Acceptance) 2026-06-19 17:26:16 +02:00
Sucukdeluxe
6b52678e7e Bridge-README: MCP-Client-neutral formuliert (kein Tool-spezifischer Name) 2026-06-19 17:19:35 +02:00
Sucukdeluxe
41f2925b13 Release v1.7.223 2026-06-19 17:13:58 +02:00
Sucukdeluxe
95d284f687 Sicherheit: Allowlist anhand des echten Socket-Peers statt X-Forwarded-For
Die Allowlist-Pruefung nutzte extractDebugClientIp, das X-Forwarded-For zuerst
auswertet - ein angreiferkontrollierter Header. Damit konnte ein entfernter Client
auf einem 0.0.0.0-Bind die Allowlist komplett umgehen (X-Forwarded-For: 127.0.0.1 ->
als Loopback gewertet). Die Allowlist bot so keinerlei Schutz; nur das Token blieb.

Fix: Enforcement liest jetzt ausschliesslich req.socket.remoteAddress (getPeerIp),
das vom Kernel aus der TCP-Verbindung gesetzt wird und nicht per Header faelschbar ist.
X-Forwarded-For wird nur noch fuer das Trace-Log (Beobachtbarkeit) verwendet, nie fuer
die Zugriffsentscheidung. Reine, exportierte Matcher-Funktion evaluateClientAllowed.

Tests umgebaut auf den Threat-Model statt den faelschbaren Kanal: Unit-Tests fuer
Loopback/exakt/CIDR/fail-closed + expliziter Forged-XFF-Test (socket.remoteAddress
8.8.8.8 + X-Forwarded-For 127.0.0.1 -> verweigert). Integration: Loopback verbindet,
gespooftes X-Forwarded-For wird ignoriert, Auth bleibt erzwungen, Live-Reload.
2026-06-19 17:10:11 +02:00
Sucukdeluxe
c8beaf96ed Ferndiagnose (MCP): Verbindungscode + abgesicherter Fernzugriff + stdio-Bridge
Neue Funktion, um Diagnose eines laufenden Servers aus der Ferne zu ermoeglichen:
Hilfe -> Remote-Support -> "Ferndiagnose (MCP)". Erzeugt einen Verbindungscode,
den der Assistent nutzt, um Status, Logs, Fehler und Accounts read-only zu lesen.

App-Seite:
- Live (re)startbarer Debug-Server ohne App-Neustart (restartDebugServer wartet auf
  'close' + closeAllConnections, behandelt EADDRINUSE).
- IP-Allowlist (debug_allowlist.txt, exakte IP + CIDR), erzwungen VOR der Auth.
  Fail-closed: Netzwerk-Bind (0.0.0.0) ohne Allowlist akzeptiert nur Loopback.
- One-Click Aktivieren/Aktualisieren/Deaktivieren + Token-Rotation (alter Code sofort
  ungueltig). Sichtbarkeit waehlbar: "Nur lokal" (Tunnel-Empfehlung) vs "Im Netzwerk".
- Verbindungscode rddiag:v1:base64url({v,h,p,t,n?,fp?,s?}); oeffentlicher Host frei
  waehlbar, Netzwerk-IPs als Schnellauswahl.
- Neue IPC: get/enable/disable/rotate Remote-Diagnostics; Controller-Methoden; Typen.

Bridge (tools/rd-diagnostics-mcp, standalone, KEINE App-Dependency):
- stdio MCP-Server (@modelcontextprotocol/sdk) mit 14 Tools, proxyt die bestehende
  HTTP-Debug-API. Multi-Server ueber code/server/RDDIAG_CODE/RDDIAG_SERVERS.
- TLS-Fingerprint-Pinning auf secureConnect (vor Token-Versand), falls https genutzt.
- test/harness.mjs: faehrt einen Fake-Debug-Server hoch und treibt die Bridge als
  echten stdio-Child per JSON-RPC -> voller Protokollpfad gruen.

Sicherheit (Audit): keine persistenten Secrets in den Logs der Debug-API (Passwoerter
redigiert, keine Debrid-Keys/aufgeloesten Download-URLs geloggt; settings/accounts
redigiert). Empfohlener Transport: Loopback + privater Tunnel; Direkt-Bind nur mit
Allowlist in vertrauenswuerdigen Netzen.

Tests: connection-code-Cross-Check (App-Encoder <-> Bridge-Decoder), Allowlist-Matrix
(Loopback, exakt, CIDR, fail-closed, Live-Restart). Volle Suite 905 gruen, tsc=6.
2026-06-19 17:05:28 +02:00
Sucukdeluxe
838bd2ee7a Release v1.7.222 2026-06-17 20:39:09 +02:00
Sucukdeluxe
3d82d00e54 Fix: Mega-Debrid 'Linkgenerierung lieferte kein Ergebnis' nicht mehr als 30s-Cooldown (schneller Transient)
Eine leere Mega-Debrid-API-Antwort (success, aber kein Download-Link ->
debrid.ts:1927 'Linkgenerierung lieferte kein Ergebnis') fiel in
classifyAccountFailure in den Default-Zweig -> 30s Account-Cooldown
('Mega-Debrid Cooldown, neuer Versuch in 31s'). Das ist KEIN Limit-/Quota-Signal,
sondern ein transienter API-Blip, der praktisch sofort wieder funktioniert (vom
Nutzer live beobachtet: danach ging es). Fix: eigener Zweig klassifiziert die leere
Antwort wie die anderen transienten Resolve-Fehler (cooldownMs 0, kein
Account-Cooldown) -> der Download-Manager nimmt den schnellen Transient-Retry-Pfad
(3s, eskaliert auf max 10s) statt 31s zu warten. Kein limitSignal (kein
until-restart-Park). Rot-bewiesener Test (ohne Fix cooldownMs 30000; mit Fix 0 +
Meldung trifft isMegaDebridTransientResolveFailure). Volle Suite 894 gruen, tsc=6.
2026-06-17 20:38:32 +02:00
Sucukdeluxe
50bc608419 Release v1.7.221 2026-06-17 20:24:13 +02:00
Sucukdeluxe
ba144f323b Fix: Statistik-Doppelzaehlung bei Retry + Debrid-Link Key-Cooldown bei Abbruch (Nutzer-Nachforderung Audit)
BYTE-DROP-RETRY-1 (MED): Integritaets-/Zu-klein-/Tiny-Neuversuche zaehlten die
volle Dateigroesse pro Versuch erneut in die Byte-Statistik. Ursache: die 3
rm-dann-frisch-Sites (Integrity-Fail 8979, too-small 9020, tiny 10486) riefen
dropItemContribution() auf, das den itemContributedBytes-Eintrag loescht, den die
einzige Reconciliation (9991, writeMode 'w') zum Subtrahieren braucht -> Subtraktion
tot -> Re-Download addiert erneut. Fix (Mechanismus a): dropItemContribution an
diesen 3 Sites entfernt, der Eintrag ueberlebt, 9991 subtrahiert ihn korrekt
(selbst-korrigierend nach writeMode). Zusaetzlich am selben Punkt
totalDownloadedAllTime subtrahiert (wurde nie subtrahiert -> doppelte bei JEDEM
frischen Re-Download). BEWUSST ausgeklammert: recordProviderDownloadedBytes/
providerDailyUsageBytes, da diese isProviderDailyLimited (= Verhalten) steuern und
nicht provider-keyed sind. Reine Telemetrie-Korrektur, kein Slot/Admission betroffen.

DL-1 (LOW): Ein abort-ohne-timeout (User-Cancel) setzte am DebridLink-Rotations-
Catch via classifyKeyFailure einen 15s-Key-Cooldown (und konnte ueber Keys zu einer
providerweiten Kaskade fuehren). Fix: Mega-Gate (2072) am Catch (2789) gespiegelt -
abort-ohne-timeout + elapsedMs < getMegaDebridAbortMinRunMs() -> kein Cooldown;
ran-long-enough -> 120s (Retry rotiert); throw bailt die Rotation.

DL-CONCURRENCY-PILEUP (MED): untersucht -> bereits strukturell geloest
(getSerializedValidatingLimit = nutzbare Accounts + shouldDelayStartForItem 8526;
MW-1 haelt das Limit hoch). Kein Eingriff (Nutzer-Entscheidung).

Je rot-bewiesener Test (per Temp-Revert, nicht-vakuum; BYTE-DROP beide Beine
einzeln). Volle Suite 893 gruen, tsc unveraendert (6 vorbestehende Fehler).
2026-06-17 20:23:28 +02:00
Sucukdeluxe
39c587c8b7 Release v1.7.220 2026-06-17 14:25:04 +02:00
Sucukdeluxe
fa2c7eb6be Fix: Mega-Web Selbst-Cooldown bei belegter Queue + Legacy-Config Mega-Demotion (Audit Runde 9+10)
MW-1 (HIGH): Ein gesunder Mega-Web-Account wurde 120s gesperrt, wenn der
60s-Gesamttimeout ablief, waehrend die Umwandlung noch SERIELL in der
per-Account-Single-Flight-Queue auf ihren Vorgaenger wartete - also bevor echte
Arbeit begann. Die Queue-Wartezeit zaehlte zu elapsedMs (>= 8s), sodass der
Abbruch faelschlich wie ein In-Arbeit-Abbruch eines langsamen Accounts gewertet
wurde. Genau die gemeldete 'Tool sperrt sich selbst'-Klasse (Web-Variante).
Zweiteilig: (1) MegaWebFallback.runExclusive trackt workStarted und meldet einen
Abbruch-vor-Arbeitsbeginn als Queue-Timeout statt aborted:mega-web;
(2) unrestrictViaWeb bewahrt diese Klassifikation statt sie zu aborted:debrid zu
plaetten -> die Rotation trifft die Queue-Timeout-Ausnahme (cooldownMs 0) statt
den Abbruch-Cooldown. Ein echter In-Arbeit-Abbruch sperrt weiterhin (langsame
Accounts korrekt ueberspringen). Nach dem No-Cooldown-Pfad rotiert der Retry per
In-Flight-Tiefe auf einen freien Account.

SET-MIG-01 (MED): Eine Config von vor v1.6.90 kannte die getrennten
Mega-API/Web-Enable-Flags nicht. readSettingsFile merged {...defaultSettings(),
...parsed}, also fuellten die false-Defaults die fehlenden Flags BEVOR
normalizeSettings lief -> der creds-basierte Migrationszweig war tot, Mega blieb
auf aus trotz vorhandener Creds und wurde beim ersten Settings-Save still aus der
providerOrder demotet. Fix: reine migrateLegacyMegaEnableFlags seedet die Flags
nur, wenn BEIDE im RAW-parsed fehlen und Creds vorhanden sind (absent-both als
einziger sicherer Trigger; present-false bleibt unberuehrt = bewusst-deaktiviert
nicht re-aktivieren). Greift nur bei Legacy-Configs, die seit dem Upgrade noch
nicht neu gespeichert wurden.

Je rot-bewiesener Test (per Temp-Revert verifiziert, nicht-vakuum). Volle Suite
890 gruen, tsc unveraendert (6 vorbestehende Fehler). Runde-9/10-Doku + die zwei
offenen Nutzer-Entscheidungen aktualisiert.
2026-06-17 14:23:41 +02:00
Sucukdeluxe
85c8d6bdf9 Fix: Mega-Cooldown vor Tagesreset-Park bevorzugen + Token-Truncation + Rewind-Fail-Haertung (Synthese-Pass)
Capstone-Synthese/Regressions-Pass ueber den kumulativen Audit-Diff (v1.7.212..HEAD)
hat 3 Fix-INTERAKTIONEN gefunden — der Rest komponiert sauber.

C1 (MED): Sind BEIDE Mega-Modi aktiv (API + Web), aggregiert die Provider-Kette bei
Fehlschlag beide Token in einen String: `mega_debrid_reset_park:LONG` (API bis
Tagesreset geparkt) + `mega_debrid_cooldown:30000` (Web nur kurz im Cooldown). Der
Manager pruefte reset_park VOR cooldown → das Item wurde ~24h geparkt, obwohl der
Web-Modus in ~30s wieder verfuegbar gewesen waere. Fix: den Cooldown-Zweig (kuerzerer
Delay) VOR dem reset_park-Zweig pruefen; der erholbarere Pfad gewinnt.

C2 (LOW): Das reset_park-Token konnte von compactErrorText (220-Zeichen-Cap)
abgeschnitten werden, wenn Mega nicht der Lead-Provider war und ein vorheriger
Provider eine lange Fehlermeldung lieferte → Park wurde still uebersprungen und Mega
am Tageslimit weiter gehaemmert. Fix: beide Mega-Token aus der UNGEKUERZTEN
error.message parsen statt aus dem gekuerzten Anzeige-String.

RANGE1-truncate-fail (LOW, Haertung + Doku-Korrektur): Der v1.7.219-Commit behauptete,
das Setzen von downloadedBytes ZUERST rette den Fall eines fehlgeschlagenen finalen
Rewind-truncate ueber den Re-Entry-prealloc-reconcile. Das stimmt nicht — bei
truncate-Fehler finalisiert tryFinalizeItemFromDisk die exakt-langen Garbage-Datei VOR
dem Re-Entry (net-neutral vs pre-audit, KEINE Regression, aber der Claim war falsch).
Haertung: bei fehlgeschlagenem finalem Rewind die Teil-Datei verwerfen
(rmSync + downloadedBytes=0) → Finalize lehnt ab → sauberer Re-Download. Best-effort
(faellt der rm auch, bleibt es net-neutral) — macht den 219-Claim jetzt wahr.

Rot-bewiesen: Single-Pass-processItem-Test mit Aggregat beider Mega-Token → retryAfter
< 60s (Cooldown) und fullStatus "Cooldown" statt "Tagesreset"; mit deaktiviertem
Cooldown-Zweig (alte Reihenfolge) ~24h-Park = rot. Volle Suite 886 gruen, tsc=6.
2026-06-17 12:51:29 +02:00
Sucukdeluxe
f2ae1c19e2 docs(audit): v1.7.219 released (Roll-up Runde 7+8: HIGH RANGE-1 + REWIND-TRUNCATE + VP-1) 2026-06-17 12:19:29 +02:00
Sucukdeluxe
6ae9f5d725 Release v1.7.219 2026-06-17 12:17:53 +02:00
Sucukdeluxe
eacd0c9d81 Fix: stille Mid-File-Korruption beim Resume nach Verbindungsabbruch (known-total) + Rewind-Haertung (Runde-8-Audit)
RANGE-1 (HIGH, adversarisch 3/3): Wenn ein Debrid-Server beim Socket-Abbruch
einen Garbage-Block in den Body schreibt UND das im LETZTEN inneren Versuch
passiert, wurde der Tail nicht zurueckgespult: der `attempt < maxAttempts`-Guard
verhindert das Vormerken, resumeRewindBytesNextAttempt ist funktions-lokal und
ueberlebt den downloadToFile-Re-Entry nicht, und der Outer-Handler nimmt fuer
terminated-class den generic-retry-Zweig (loescht die Teil-Datei NICHT). Beim
Fresh-Link-Resume wurden die echten Bytes NACH dem Garbage angehaengt → eine
Datei mit exakt korrekter Laenge, die die reine Laengen-Pruefung
(validateDownloadedFileCompletion) besteht; bei manifestlosen .mkv/.mp4 ohne
Pruefsumme fiel die Korruption nie auf.

Fix: am Exhaustion-Punkt vor dem Throw den verdaechtigen Tail zurueckspulen
(truncate auf written - RESUME_REWIND_BYTES, downloadedBytes ZUERST gesetzt,
damit der binary-Re-Entry-prealloc-reconcile auch bei fehlgeschlagenem truncate
greift). GEGATED auf bekannte Groesse (totalBytes != null && > 0): dann ist
rewound size < totalBytes = minBytes, sodass tryFinalizeItemFromDisk die
gekuerzte Datei ablehnt und der Re-Entry den Garbage sauber ueberschreibt.

Ehrliche Einordnung: behoben fuer Medien mit bekannter Groesse (Debrid liefert
fast immer fileSize). Der seltene Fall OHNE Groessenangabe behaelt eine separate,
schon vorher bestehende stille Korruption unter dem dokumentierten
Size-only-Validation-Blindspot — durch dieses Rueckspulen nicht loesbar (kein
Laengensignal; nur ein Hard-Reset wuerde helfen, groesserer separater Eingriff).
Bewusst NICHT angefasst, um keinen vierten Bug einzubauen.

REWIND-TRUNCATE-FAIL (MED, 3/3): Das `finally` setzte
resumeRewindBytesNextAttempt=0 unbedingt — auch wenn die Rewind-truncate warf
(transienter win32-EBUSY/AV-Lock) → Garbage-Tail blieb, Flag gecleart, nie
erneut versucht. Fix: Reset nur noch im Success-Branch, sodass ein
fehlgeschlagenes Rueckspulen im naechsten Versuch erneut probiert wird.

PREALLOC-ZEROS-ACCEPTED (MED, schwaechste) dokumentiert nicht gefixt: dominante
Medien/Archive sind immun (footprint-threshold 0); narrow non-binary-Conjunction.

Advisor vor Implementierung konsultiert (HIGH-Hot-Path); Check A (totalBytes-null
finalisiert die gekuerzte Datei) am echten Code verifiziert und der Fix
entsprechend auf known-total gegated.

Rot-bewiesen: Cross-Call-Integrationstest (final-attempt Garbage-Inject + Drop →
Exhaustion → queueRetry → 2. downloadToFile via Fresh-Link → byte-genaue
Inhaltsgleichheit). Ohne den Fix ist die Laengen-Assertion gruen und die
Inhalts-Assertion rot (exakt-laengen-Korruption) — genau die RANGE-1-Signatur.
Volle Suite 885 gruen, tsc unveraendert (6).
2026-06-17 12:16:51 +02:00
Sucukdeluxe
0f5accc756 docs(audit): Boot-Smoke-Test 1.7.218 bestaetigt (bootet) + Advisor-Leitlinie (Synthese-Pass nach Runde 8) 2026-06-17 11:38:19 +02:00
Sucukdeluxe
be15419882 Fix: deutsche Tonspur-Erkennung waehlt nicht mehr eine falsch-getaggte Spur per Titel (Datenverlust-Schutz, Runde-7-Audit)
isGermanStream wertete die Titel-Regex /\b(german|deutsch)\b/ fuer JEDEN nicht
deutsch-getaggten Audiostream aus — also auch fuer eine bereits anderssprachig
getaggte Spur (z.B. {language:"eng", title:"German Commentary"}). Der Kommentar
ueber dem Titel-Fallback sagt ausdruecklich, er sei "used when the language tag
is missing", aber genau diese Gating-Bedingung fehlte.

Folge: pickAudioTrack (der EINZIGE Caller) nimmt den ersten isGermanStream-
Treffer. Lag eine eng-Spur mit "German"/"Deutsch" im Titel VOR der korrekt mit
"ger" getaggten Spur, gewann die englische Spur. Der Remux behielt dann Englisch,
verwarf die echte deutsche Spur, ersetzte das Original atomar in-place und
strippte den .DL.-Marker — irreversibler Datenverlust mit falscher Sprache, vom
System als Erfolg ("german-tag"/"remuxed") gemeldet. Single-Trigger, kein
compound-Vorbedingung. Betrifft genau die vom Nutzer bestaetigte Tonspur-Funktion.

Fix: Titel-Fallback nur noch ausfuehren, wenn KEIN Sprach-Tag vorhanden ist
(`if (lang) return false;` vor der Titel-Pruefung) — deckt sich exakt mit der
dokumentierten Absicht des bestehenden Kommentars. Korrekt getaggte deutsche
Spuren gewinnen jetzt immer; eine anderssprachige Spur mit "German" im Titel wird
nicht mehr faelschlich gewaehlt. Untagged-Spuren nutzen weiter den Titel-Fallback
(unveraendert), und der German-Release-Mislabel-Pfad (German-first-Konvention)
greift weiterhin.

Runde-7-Audit (adversarisch verifiziert) hat ausserdem den deferred-Extraction-
Cluster abgeschlossen: #10 CRC-Kleindatei-Delete und #12 7z-Exit-1-als-Erfolg sind
BENIGN, #11 resume-empty-output not-confirmed (compound), #13 = das dokumentierte
LOW Companion-Overwrite (PP-1, compound, nur Sekundaerdateien).

Rot-bewiesen: zwei reine pickAudioTrack-Tests (eng-Titel-"German" vor echter ger →
audioRelIndex 1; eng-Titel-"Deutsch entfernt" ohne echte dt. Spur → skip) failen
ohne den Gate. Volle Suite 884 gruen, tsc unveraendert.
2026-06-17 11:30:32 +02:00
Sucukdeluxe
32cbcd9dba docs(audit): v1.7.218 released (Roll-up Runde 4+5+6, Gitea + GitHub-Mirror) 2026-06-17 11:05:39 +02:00
Sucukdeluxe
bbb935589e Release v1.7.218 2026-06-17 11:00:47 +02:00
Sucukdeluxe
d2a1b831a0 Fix: festes Retry-Limit wird jetzt hart eingehalten — kein Endlos-Shelve-Loop mehr (Runde-6-Audit)
Bei einem FESTEN retryLimit (>=5) sind alle drei Per-Klasse-Retry-Caps
(stall/unrestrict/generic) gleich dem Limit. Die beiden "15-Fehler"-Shelve-Zweige
(download-manager.ts stall-Pfad + error-Pfad) feuern aber auf der hartkodierten
Summe >=15 OBERHALB der Per-Klasse-Terminal-Fail-Pruefungen und HALBIEREN danach
alle drei Zaehler. Dadurch wurden die Per-Klasse-Caps nie gleichzeitig
ueberschritten: der Eintrag erreichte nie status="failed", schliff endlos im
90s-Takt, item.retries wuchs weit ueber das konfigurierte Limit, der
Download-Slot blieb dauerhaft belegt, und das wiederholte
providerFailures.delete besiegte immer wieder den Provider-Circuit-Breaker →
Hoster-Hammering. resetStaleRetryState konnte nicht eingreifen, weil der
<=90s-Re-Admit item.updatedAt frisch haelt (nie 10min stale).

Der Auslieferungs-Default retryLimit=0 (= unendlich) ist NICHT betroffen — dort
ist der 15-Fehler-Shelve der gewollte Dauer-Park-Backstop und es gibt kein
endliches Budget zu verletzen.

Fix: in BEIDEN Shelve-Zweigen vor dem queueRetry eine harte Obergrenze —
`if (configuredRetryLimit > 0 && item.retries >= configuredRetryLimit)` failt den
Eintrag terminal (status="failed", recordRunOutcome, retryStateByItem.delete)
statt ihn neu zu queuen. Der ∞-Modus (retryLimit<=0) ueberspringt die Grenze und
bleibt unveraendert.

Runde-6-Audit (adversarisch verifiziert) hat ausserdem den deferred-LOW-Cluster
abgeschlossen: #10 HTTP416-shared-counter, #11 fresh-retry-preempt und #13
queue-wait→elapsedMs sind BENIGN; #12 war genau dieser Bug.

Rot-bewiesen: Single-Pass-Integrationstest seedet genericErrorRetries=15 vor und
injiziert einen generischen Fehler bei retryLimit=5 → ohne Fix requeued der
Shelve (status bleibt nicht "failed"), mit Fix wird terminal gefailt. Volle Suite
882 gruen, tsc unveraendert.
2026-06-17 10:58:18 +02:00
Sucukdeluxe
e49ed1ada0 docs(audit): Release v1.7.218 aufgeschoben (buendeln Runde 4+5+6, Advisor-Sign-off abwarten) 2026-06-17 10:39:41 +02:00
Sucukdeluxe
f375fe5a2e docs(audit): Runde 5 (reconnect/scheduler/persistenz) sauber + PP-SEM-1 dokumentiert; Synthese-Pass
Runde 5 ueber drei unberuehrte Subsysteme (auto-reconnect-Resume, Scheduler-
Fairness/Slot-Accounting, Crash/Persistenz/targetPath-Lifecycle): 0 bestaetigte
Bugs. Einzige Charakterisierung PP-SEM-1 (Post-Process-Semaphore Ueber-Admission
durch Microtask-Race) ist benign (Verschwendung, kein Strand/Loss) → bewusst
nicht live gefixt. Synthese: Retry/Cooldown/Rotation/Failover/Reconnect/Scheduler
end-to-end kohaerent self-heilend.
2026-06-17 10:37:27 +02:00
Sucukdeluxe
f1e35f5f41 Fix: Mega "bis Tagesreset gesperrt" parkt das Paket bis zum Reset statt es den ganzen Tag alle 2 min neu zu versuchen
Wenn ALLE Mega-Debrid-Accounts wegen wiederholt leerer/Server-loser Antworten
bis zum Tagesreset geparkt waren (in-memory untilRestart-Park aus Runde 3),
warf unrestrictWithAccounts einen reinen Klartext-Fehler ohne Maschinen-Token.
Im Manager-Catch fiel dieser durch parseMegaDebridCooldownRetry (kein
mega_debrid_cooldown:-Praefix) und isMegaDebridTransientResolveFailure und
landete im generischen isUnrestrictFailure-Zweig — nur weil der Text
"mega-debrid" enthaelt. Folge: das Item wurde den ganzen Tag etwa alle zwei
Minuten neu versucht (120s-Cap) und fuetterte dabei recordProviderFailure den
Provider-Circuit-Breaker, statt einmal bis zum Tagesreset zu parken. Bei
Standard-Einstellungen (retryLimit=0=unendlich) heilte es sich zwar um
Mitternacht selbst (kein Stranding), war aber unnoetige Log-Flut und Churn und
unterlief genau die untilRestart-Park-Absicht aus Runde 3.

Cross-Layer-Synthese-Pass (adversarisch verifiziert) hat parallel die
Advisor-Hypothese eines Key-Mismatch (Manager cool't aufgeloesten
"megadebrid-api", Fallback-Suche liest rohen "megadebrid") WIDERLEGT:
normalizeProviderOrder speichert immer den aufgeloesten Key, also faellt
Write/Check/Clear/Read auf denselben Key — der Routing-Fix (986fbab) ist fuer
den Mega-Fall nachweislich sicher.

Fix (konservativ, spiegelt den bereits korrekten mega_debrid_cooldown-Zweig):
- debrid.ts emittiert beim untilRestart-Park jetzt den Token
  mega_debrid_reset_park:<msBisReset>: (Delay aus megaDebridDailyParkExpiry,
  deckt sich exakt mit dem in-memory Park-Ablauf).
- download-manager.ts: neue reine parseMegaDebridResetPark (kein 15min-Clamp,
  26h-Cap) + Catch-Branch VOR der Cooldown-Klassifikation, der bis zum
  Tagesreset queued OHNE recordProviderFailure (ein geplanter Park ist kein
  Provider-Fehler).
- Token enthaelt weiter "mega_debrid" → jeder Fall-through landet schlimmstenfalls
  im heutigen Verhalten (sichere Untergrenze).

Tests: 5 neue (parseMegaDebridResetPark: parst/embedded/kein-15min-Clamp/Cap+Junk,
plus cooldown-Parser ignoriert den neuen Token) + die all-parked debrid-Assertion
prueft jetzt den Token. Volle Suite 881 gruen, tsc unveraendert.
2026-06-17 10:16:16 +02:00
Sucukdeluxe
03c908bd30 Test: No-Stranding-Gate fuer Provider-Fallback-Routing + Doku-Praezisierung
findFallbackProviderNotInCooldown-Charakterisierungstest sichert den Invariant
ab, auf dem der Routing-Fix (986fbab) seine Sicherheit aufbaut: bei kein
Cooldown den ersten Provider, bei einem Cooldown den naechsten gesunden, und
NULL erst wenn ALLE Provider im Cooldown sind. Dieses null ist der Punkt, an
dem der Aufrufer queueRetry statt Reorder macht — wuerde der Helper hier je
einen abgekuehlten Provider als letzten Notnagel zurueckgeben, liefe der
Reorder am Sicherheitsnetz vorbei. Der Test friert das ein.

Zusaetzlich: entscheidungen-offen.md praezisiert, dass Option A des Failover-
Timeouts ein Drehregler ueber den Pro-Provider-Wert ist (30s/90s, 60s/180s),
kein fixer Wert.
2026-06-17 06:31:37 +02:00
Sucukdeluxe
986fbab04f Fix: Provider-Cooldown-Fallback fuehrt die Kette jetzt wirklich mit dem Ersatz-Provider an (Routing-Slice)
Wenn ein Provider in den Manager-Cooldown laeuft (>=20 Fehler in Folge) und
auto-Fallback aktiv ist, berechnete der Manager zwar einen Ersatz-Provider
(findFallbackProviderNotInCooldown), warf ihn dann aber weg: der unrestrictLink-
Aufruf bekam keinen Hint, also baute debrid.ts die Provider-Kette frisch aus
providerOrder und fuehrte erneut mit dem ausgebremsten Provider 1 an — ein meist
in den 60s-Timeout laufender, verschwendeter Versuch, bevor ueberhaupt der
gesunde Provider drankam.

Jetzt wird der Ersatz-Provider als bevorzugter Lead durchgereicht. Die Kette
wird UMSORTIERT, nicht beschnitten: der ausgebremste Provider bleibt als letzter
Notnagel in der Reihenfolge, sodass kein Link gestrandet werden kann. Sind ALLE
Provider im Cooldown, erreicht der Aufruf unrestrictLink ohnehin nicht (der
else-Zweig queued einen Retry). Greift damit nur, wenn ein Provider bereits
nachweislich degradiert ist — strikt besser, wenn aktiv, und ohne Timeout-,
Cancel- oder Budget-Vertrag anzufassen.

- debrid.ts: reine leadProviderChainWith(order, preferred) (export) + optionaler
  4. Param preferredLeadProvider an unrestrictLink; Reorder direkt nach Aufbau
  der Reihenfolge.
- download-manager.ts: Ersatz-Provider in preferredLeadProvider festhalten und
  an den unrestrictLink-Aufruf durchreichen.
- Tests: Integration (ohne Hint fuehrt realdebrid, mit Hint fuehrt debridlink) +
  drei reine Helper-Tests fuer die No-Stranding-Invariante.

Zwei verbleibende HIGH-Punkte sind bewusst NICHT autonom geaendert, sondern als
Produkt-/UI-Entscheidung in tasks/entscheidungen-offen.md vorgelegt (globaler
60s-Failover-Timeout; gespiegelter Mega API/Web-Schalter).
2026-06-17 06:23:44 +02:00
Sucukdeluxe
bc50e4285d Release v1.7.217 2026-06-17 05:58:59 +02:00
Sucukdeluxe
cbb84f29d8 docs(tasks): Runde-3-Audit dokumentiert (3 Fixes v1.7.217, 5 charakterisiert/vorgelegt) 2026-06-17 05:56:06 +02:00
Sucukdeluxe
1e04b7bdcb Fix: Bearbeiten eines Mega-Debrid-Accounts ändert nicht mehr heimlich die API/Web-Bevorzugung
Sind beide Mega-Debrid-Modi (API + Web) aktiv, entscheidet megaDebridPreferApi,
welcher Modus zuerst versucht wird. Beim Speichern des Account-Dialogs hat
applyAccountDialogToSettings diese Einstellung jedoch fest überschrieben: ein
Bearbeiten der API-Zeile erzwang megaDebridPreferApi=true, ein Bearbeiten der
Web-Zeile erzwang false. Wer also bewusst Web bevorzugt (preferApi=false) hatte
und nur einen zweiten Account hinzufügen oder das Tageslimit ändern wollte und
dafür die API-Zeile bearbeitete, dem wurde die Reihenfolge ungefragt auf
"API zuerst" umgestellt — was die Provider-Auflösung für alle künftigen
Mega-Links ändert.

Fix: Die festen megaDebridPreferApi-Werte aus beiden Dialog-Zweigen entfernt.
Der bestehende Wert wird über den ...settings-Spread durchgereicht. Erst-Anlage
bleibt korrekt, weil der Default ohnehin true ist (constants). Bei nur EINEM
aktiven Modus ist preferApi ohnehin irrelevant (die single-mode-Zweige
kurzschließen davor).

Test: bei beiden Modi aktiv + preferApi=false bleibt nach API-Edit false; bei
preferApi=true bleibt nach Web-Edit true. Mit der alten Festverdrahtung kippt der
Wert (rot bewiesen).
2026-06-17 05:54:42 +02:00
Sucukdeluxe
dfd19260f6 Fix: Download-Queue läuft nach fehlgeschlagenem Update weiter (kein Stillstand bis Neustart)
Beim Klick auf "Update" stoppt das Tool zuerst die laufende Session
(stop({parkForRestart:true}) → running=false, alle Items auf "Wartet") und
startet dann die Installation. Schlug die Installation fehl — und das ist auf
realistischen Wegen möglich (git.24-music.de-Aussetzer, Netz weg über alle
Versuche, oder ein Release mit fehlendem/falschem Digest → Integritätsprüfung
schlägt fehl) — kam KEIN App-Neustart (nur bei Erfolg wird app.quit geplant) und
auch kein In-Process-Resume. Folge: die komplette Queue stand still (alle Items
"Wartet"), bis ein Mensch "Start" klickte oder das Programm neu startete — auf
einem unbeaufsichtigten Server mit ~1 TB/h ein echter Stillstand.

Fix: Der Stop-Installieren-Resume-Ablauf liegt jetzt in
runInstallWithResume(): War vorher eine Session aktiv und schlägt die
Installation fehl, wird die Session wieder gestartet (manager.start(),
idempotent — die Items sind bereits "queued", der Scheduler nimmt sie normal
auf). Der Resume greift auf BEIDEN Fehlerpfaden: started:false UND geworfene
Ausnahme (try/catch). Bei Erfolg (started:true) wird NICHT resumed (die App
beendet sich gleich), und war keine Session aktiv, passiert nichts.

Test: started:false → resume; install wirft → resume + rethrow; started:true →
kein resume; nicht-laufend → kein stop/resume. Logik in eine reine, testbare
Funktion ausgelagert (kein schwergewichtiger AppController-Konstruktor nötig).
2026-06-17 05:50:55 +02:00
Sucukdeluxe
76b3f99476 Fix: Mega-Debrid-Account sperrt sich nicht mehr dauerhaft selbst (Self-Cooldown bis Neustart)
Ein gesunder Mega-Debrid-Account konnte sich dauerhaft "bis Neustart" sperren,
ohne sich von selbst zu erholen — genau das vom Nutzer beobachtete Verhalten
("das Tool setzt sich selbst Cooldowns"). Zwei Ursachen, beide behoben:

1) Falsche Einstufung eines HOSTER-Problems als ACCOUNT-Limit:
   classifyAccountFailure hat den hoster-spezifischen Fehler "Kein Server fuer
   diesen Hoster" (MEGA_DEBRID_NO_SERVER_RE — Mega-Debrid hat fuer EINEN Hoster,
   z.B. Rapidgator, gerade keinen Server) mit limitSignal=true markiert. Dieses
   Signal speist den "bis Neustart"-Streak (3x -> Dauer-Park). Bei einer
   Warteschlange voller Links desselben (kurzzeitig nicht bedienbaren) Hosters
   kletterte der Streak hoch und parkte einen kerngesunden Account — der sich
   dann ueber die Rotation auf ALLE Accounts ausbreitete. Fix: limitSignal aus
   dem No-Server-Zweig entfernt (der 120s-Cooldown bleibt). Hoster-Verfuegbarkeit
   ist kein Account-Gesundheitssignal. Der echte Tageslimit-Pfad (leere Antwort)
   bleibt unveraendert.

2) "Bis Neustart"-Park hatte keine Ablaufzeit (until = MAX_SAFE_INTEGER) und
   konnte daher NIE von selbst aufgehen — auch nicht, nachdem Mega-Debrid wieder
   Server hatte oder das Tageslimit um Mitternacht zuruecksetzt. Nur ein Neustart
   half. Fix: Der Park laeuft jetzt zum naechsten Tagesreset (lokale Mitternacht,
   mind. 120s) ab und heilt sich damit garantiert innerhalb von <=24h selbst —
   egal welcher Zweig ihn ausgeloest hat. Texte entsprechend von "bis Neustart
   gesperrt" auf "bis zum Tagesreset gesperrt" angepasst (Main + Renderer
   konsistent).

Tests: (a) ein geparkter Account ist nach dem Tagesreset wieder frei statt
dauerhaft gesperrt (rot ohne Fix: MAX_SAFE_INTEGER laeuft nie ab); (b) ein
"no server"-Fehler liefert kein limitSignal mehr (rot ohne Fix), waehrend die
echte leere Antwort weiterhin als Limit zaehlt. 86 Debrid-Tests gruen.
2026-06-17 05:47:52 +02:00
Sucukdeluxe
68f50eaa5e Release v1.7.216 2026-06-17 05:19:18 +02:00
Sucukdeluxe
c8ea2f6765 Fix: Startup-Dedup ersetzt keine gute kanonische Datei mehr durch ein kleineres Duplikat
Beim Start gleicht das Tool Duplikat-benannte Dateien ("Name (1).ext") gegen die
kanonische Datei ("Name.ext") ab. Im Zweig "Duplikat rangiert hoeher und das
kanonische Item ist nicht 'completed'" wurde bisher bedingungslos
fs.rmSync(kanonisch) + fs.renameSync(Duplikat → kanonisch) ausgefuehrt — ohne die
echten Dateigroessen zu vergleichen. Der Rang mischt den persistierten STATUS mit
der Disk-Existenz; nach einem Absturz kann der Status von der Realitaet abweichen.
War die kanonische Datei auf der Platte tatsaechlich vollstaendig/gut, ihr Item
aber z.B. "failed"/"queued", waehrend das Duplikat-Item ein veraltetes
"completed" ueber einer kleineren/partiellen Datei trug, wurde die gute
kanonische Datei geloescht und durch das schlechtere Duplikat ersetzt —
unwiederbringlicher Verlust der besseren Datei. Zusatzrisiko: rmSync erfolgreich,
renameSync wirft EXDEV → kanonisch weg, nichts installiert.

Fix:
- Size-Guard: Ist die kanonische Datei >= dem Duplikat, wird sie behalten und das
  (kleinere/gleich grosse) Duplikat verworfen. Eine kleinere Datei kann eine
  groessere nie ueberschreiben.
- Sichere Reihenfolge fuer den echten Austausch (kanonisch kleiner): kanonisch →
  ".dedupbak" umbenennen, dann Duplikat → kanonisch; bei Fehler (EXDEV o.ae.)
  wird die kanonische Datei aus dem Backup wiederhergestellt. Kein Loss-Fenster
  mehr. Erst nach Erfolg wird ".dedupbak" entfernt.

Test: kanonisch 1024 B (gut) + Duplikat 256 B (partiell), primaeres Item "failed"
(Duplikat rangiert hoeher) → die kanonische Datei bleibt mit 1024 B erhalten.
Ohne den Size-Guard wird sie durch die 256-B-Datei ersetzt (rot bewiesen). Volle
download-manager-Suite (168) gruen, tsc unveraendert 6.
2026-06-17 05:13:40 +02:00
Sucukdeluxe
45789918b0 Fix: Falsch benannte Hash-Manifeste löschen keine korrekten Dateien mehr
readHashManifest hat den pro Zeile korrekt erkannten Algorithmus mit dem aus der
DATEIENDUNG abgeleiteten überschrieben (.sfv→crc32, .md5→md5, .sha1→sha1).
parseHashLine erkennt den Algorithmus jedoch eindeutig an der Digest-Länge
(32-hex=MD5, 40-hex=SHA1, 8-hex=CRC32) — die Länge ist die verlässliche Quelle.
Stimmte die Endung nicht mit dem Zeileninhalt überein (z.B. eine MD5-Zeile in
einer .sfv-Datei), wurde der Algorithmus auf crc32 verbogen, hashFile berechnete
einen 8-Zeichen-CRC und verglich ihn mit dem 32-Zeichen-MD5 → garantiert
Mismatch. Folge: die intakte Datei wurde gelöscht, neu geladen, scheiterte
erneut und brach hart ab ("Integritätsprüfung fehlgeschlagen").

Fix: Den von parseHashLine erkannten Algorithmus übernehmen statt ihn per
Dateiendung zu überschreiben. Bei korrekt benannten Manifesten ist das ohnehin
ein No-op; nur die fehl-etikettierten Fälle werden repariert.

Test: .sfv-Datei mit MD5-formatierter Zeile + passende Datei → Algorithmus bleibt
MD5 und die Prüfung besteht. Mit der Endungs-Überschreibung wird crc32 erzwungen
und die gute Datei fällt durch (rot bewiesen).
2026-06-17 05:07:13 +02:00
Sucukdeluxe
2b639b7267 Fix: Teildatei wird beim Entfernen eines laufenden Downloads gelöscht (kein Datei-Waise)
Wurde ein gerade herunterladendes Item entfernt (removeItem), brach der Abbruch
zwar den Stream ab, aber die angefangene Teildatei blieb für immer auf der
Platte: removeItem löscht das Item synchron aus der Session, und der danach
laufende processItem-catch stieg in der allerersten Zeile (Session-Mitglied-
schaftsprüfung `session.items[id] !== item`) per return aus — also VOR dem
Cancel-Zweig, der die geclaimte Teildatei via fs.rmSync entfernt. Folge:
verwaiste Bytes, die kein Item mehr referenziert, plus beim erneuten Hinzufügen
desselben Links eine stille Duplikat-Benennung "Name (1)".

Fix: Im catch wird vor dem frühen return — wenn das Item nicht mehr in der
Session ist und der Abbruchgrund "cancel" war — die geclaimte Teildatei
(claimedTargetPathByItem bzw. item.targetPath) gelöscht. Das passiert im catch,
also NACH dem Schließen des Streams (kein Race mit dem noch offenen Write-
Stream). releaseTargetPath/dropItemContribution laufen wie bisher (finally bzw.
removeItem). Eng auf cancel+entferntes-Item begrenzt → keine Auswirkung auf
normale/laufende Items.

Test: held-Connection hält den Download aktiv, removeItem mitten im Stream; die
Teildatei ist danach weg. Ohne den Fix bleibt sie liegen (rot bewiesen, Timeout).
2026-06-17 05:02:46 +02:00
Sucukdeluxe
1a33fc2573 Fix: Einstellungen gehen nicht mehr durch zeitgleiches Speichern verloren (Lost Update)
Der asynchrone Settings-Writer hatte — anders als der Session-Writer — keinen
Generations-Schutz. Lief eine periodische async Settings-Speicherung gerade,
während gleichzeitig synchron saveSettings() lief (z.B. Nutzer ändert eine
Option, oder ein Settings-Backup wird wiederhergestellt), konnte der async
rename die frisch synchron geschriebene Datei wieder mit dem ALTEN Stand
überschreiben. Die gerade gespeicherte Änderung war damit auf der Platte (und im
.bak) verloren — bis zur nächsten Speicherung.

Fix: Der Settings-Pfad spiegelt jetzt exakt den bereits abgesicherten
Session-Pfad:
- Eigener Generations-Zähler syncSettingsSaveGeneration; saveSettings() (sync)
  erhöht ihn.
- writeSettingsPayload bekommt die zum Zeitpunkt des Aufrufs erfasste Generation
  und bricht vor rename UND vor dem EXDEV-Copy ab, wenn inzwischen eine
  synchrone Speicherung passiert ist (generation < aktuell).
- saveSettingsAsync/saveSettingsPayloadAsync trägt die ORIGINAL-Generation auch
  durch die Warteschlange (vorher wurde beim Abarbeiten der Queue eine frische
  Generation erfasst → hätte den Schutz ausgehebelt).
- Eigener Zähler statt Wiederverwendung von syncSaveGeneration, damit eine
  synchrone Settings-Speicherung keine laufenden async SESSION-Schreibvorgänge
  fälschlich verwirft (keine Datei-übergreifende Kopplung).

Zusätzlich (verwandt): shutdown() ruft jetzt cancelPendingAsyncSaves() vor der
finalen synchronen Speicherung auf, damit eine noch laufende/eingereihte async
Settings-Schreibung den Shutdown-Save nicht mehr überholen kann.

Test: in-flight + eingereihte async Settings-Speicherung (ALT), dann synchron
NEU — nach dem Settle steht NEU auf der Platte. Ohne den Generations-Schutz
gewinnt ALT (rot bewiesen).
2026-06-17 04:57:23 +02:00
Sucukdeluxe
56bae4a384 Fix: Nicht-Archiv-".00x"-Dateien werden beim Aufräumen nicht mehr gelöscht (Datenverlust)
findArchiveCandidates behandelt jede "*.001"-Datei (nicht .zip.001/.7z.001) als
generischen Split-Kandidaten. Hatte so eine Datei KEINE Archiv-Signatur (also
gar kein Archiv — z.B. eine eigene gesplittete Datendatei, ein Media-Chunk),
wurde sie zwar korrekt als "kein Archiv" erkannt und übersprungen — aber dabei
trotzdem zu extractedArchives gezählt. Stand daneben ein echtes Archiv (das
Ausgabe erzeugt, failed===0), nahm der Cleanup ALLE candidates als Quelle und
collectArchiveCleanupTargets löschte die komplette ".00x"-Familie (.001/.002/...)
— obwohl daraus nie etwas entpackt wurde. Echter Datenverlust.

Fix: Generische Split-Dateien ohne Archiv-Signatur landen jetzt in einem eigenen
Set skippedNonArchives (per pathSetKey normalisiert) statt in extractedArchives
und werden vor dem Cleanup explizit aus den cleanupSources gefiltert. Greift in
beiden Pfaden: frischer Lauf (no-signature-Branch) und Resume (die Vorab-
Befüllung aus dem Resume-State prüft die Signatur erneut). Die Datei bleibt für
Fortschritt/Resume als erledigt markiert (resumeCompleted), zählt aber weder als
"failed" noch als Cleanup-Ziel.

Test: end-to-end über das echte extractPackageArchives — echtes movie.zip plus
mydata.001/.002/.003 (kein Archiv) mit cleanupMode=delete; nach dem Lauf ist die
ZIP-Ausgabe da und die .00x-Familie unangetastet. Ohne den Filter wird die
Familie gelöscht (rot bewiesen).
2026-06-17 04:52:54 +02:00
Sucukdeluxe
2646cba1c7 Fix: Stille Datei-Korruption auf Windows bei vorab-allozierten Downloads (Null-Bytes am Ende)
Auf Windows wird die Zieldatei vor dem Download auf die erwartete Groesse
vorab-alloziert (zero-extend via fd.truncate(item.totalBytes)) und dann ueber
einen r+/start:0-Stream beschrieben — dieser ueberschreibt ab Offset 0, kuerzt
die Datei aber nie. item.totalBytes ist dabei die Provider-Metadaten-Groesse
(knownTotal), nicht zwingend die echte content-length.

Lieferte der Server real weniger Bytes als vorab-alloziert (content-length kleiner
als die Provider-Angabe, oder Verbindungsabbruch mit sauberem EOF), blieb der
Schwanz der Datei mit Null-Bytes gefuellt. Die anschliessende stat-Korrektur hat
`written` dann FAELSCHLICH auf die On-Disk-Groesse (= Alloc-Groesse inkl.
Null-Padding) HOCHgesetzt. Dadurch wurde sowohl die Underflow-Pruefung als auch
das nachgelagerte Pre-alloc-Truncate ausgehebelt: Das Item galt als zu 100%
fertig, obwohl ein grosser Teil der Datei Nullen waren — echte Datentraeger-
Korruption, exakt auf der Plattform des Nutzers (win32).

Fix: Neue reine Funktion reconcileFinalizedSize() entscheidet die Korrektur.
Fuer vorab-allozierte Dateien wird `written` nur noch nach UNTEN korrigiert
(echter Kurz-Write), niemals nach oben auf das Null-Padding. Damit greifen die
bestehende Underflow-Validierung und das Pre-alloc-Truncate wieder wie vorgesehen:
- Voll heruntergeladen (content-length erreicht): Null-Padding wird abgeschnitten,
  Datei ist korrekt.
- Echt zu kurz: wird korrekt als Underflow erkannt und neu versucht.

Verhaltensaenderung (bewusst): Wenn die Provider-Metadaten-Groesse die echte
Dateigroesse UEBERSCHAETZT (Provider sagt 1 GB, echte Datei ist 300 MB und
vollstaendig), galt der Download bisher still als fertig (mit Null-Padding-
Korruption). Jetzt wird er als Underflow gewertet und neu versucht. Sichtbarer
Fehlschlag ist einem still korrupten "fertigen" File vorzuziehen. Die
ALLOCATION_UNIT_SIZE-Toleranz deckt kleine Abweichungen weiterhin ab. (Falls je
ein "wiederholt denselben Link" gemeldet wird, fuehrt die Spur hierher.)

Test: reconcileFinalizedSize-Matrix (Pre-alloc+Padding bleibt echte Byte-Zahl,
Pre-alloc+Kurz-Write schrumpft, Nicht-Pre-alloc beidseitig, Invalid-stat,
No-op-Gleichstand) — der Padding-Fall ist rot ohne den Fix.
2026-06-17 04:48:11 +02:00
Sucukdeluxe
21fb09b208 Fix: 1fichier/DDownload-Fehler respektiert jetzt "Auto-Fallback aus"
Wenn ein 1fichier- oder DDownload-Link beim Entsperren scheiterte, fiel das
Tool bisher trotzdem auf die Provider-Kette (z.B. Mega-Debrid) durch — auch
wenn der Nutzer den automatischen Anbieter-Fallback in den Einstellungen
ausgeschaltet hatte. Damit wurde ein anderer Anbieter (und dessen Tageslimit/
Account) ungewollt verbraucht, obwohl der Nutzer genau das unterbinden wollte.

Jetzt prüfen die catch-Blöcke beider Hoster zusätzlich autoProviderFallback:
ist er aus, wird der Fehler direkt weitergereicht statt still auf den nächsten
Anbieter umzuschalten. Die Abbruch-Behandlung (User-Cancel/echtes aborted)
bleibt unverändert davor.

Wichtig: Die Dateinamen-Auflösung (getLinkInfos) bleibt bewusst NICHT betroffen
— deren Fehlschlag muss weiterhin nicht-fatal sein.

Test: 1fichier liefert KO + Fallback aus → Linkgenerierung wird abgelehnt und
die Mega-Debrid-getLink-Route nachweislich nicht aufgerufen.
2026-06-17 04:37:42 +02:00
Sucukdeluxe
8196263ac3 Release v1.7.215 2026-06-17 04:23:16 +02:00
Sucukdeluxe
3bd6e3b23e Härtung Download/Rotation (Audit-Batch 1): Scheduler-Freeze, Cooldown-Respekt, Daily-Reset, Kategorisierung
Aus einem adversarisch verifizierten Multi-Agent-Audit (14 confirmed/11 refuted):

#1 HIGH Scheduler-Freeze: findNextQueuedItem hatte keinen activeTasks-Guard.
Wird ein Item zurueckgesetzt/ueberschrieben, waehrend sein alter Task noch in
einem nicht-abbrechbaren await parkt (z.B. Integritaets-Check), liefert
findNextQueuedItem dasselbe Item, startItem lehnt es ab ohne activeTasks zu
verkleinern → der SYNCHRONE Admission-Loop dreht endlos → Event-Loop friert
permanent ein. Fix: `if (this.activeTasks.has(itemId)) continue;`. Repro-Test
(ohne Fix haengt sogar der vitest-Timeout — Freeze bewiesen).

#2/#3 MED mega_debrid_cooldown:<ms> wurde verworfen: kein Parser (nur das
debrid_link-Analogon) → Item lief in den generischen 5s-Exponential-Backoff
und fragte cooled Accounts im Sekundentakt erneut an. Neuer
parseMegaDebridCooldownRetry (nimmt das frueheste Cooldown-Ende ueber alle
Accounts) + Handler VOR transient/generic → Item wartet die echte Cooldown-Zeit.

#7/#8 MED Mega per-Account Tages-Usage wurde am Tageswechsel nie zurueckgesetzt
(ensureProviderDailyUsageFresh ruecksetzte nur provider/debrid-link), und weil
es den Tagesschluessel zuerst weiterstellte, lief auch der Reset in
addMegaDebridAccountDailyUsageBytes ins Leere → Accounts blieben den ganzen Tag
faelschlich "am Limit" und schrumpften das (neue) Pro-Account-Umwandlungslimit.
Fix: megaDebridAccountDailyUsageBytes im Tageswechsel mit zuruecksetzen.

#14 LOW classifyAccountFailure: rate_limit-Branch vor quota (quota matchte
"limit" in "rate limit" → Fehl-Kategorisierung).

852/852 gruen, tsc unveraendert (6). #4 (empty→until-restart) bewusst deferred.
2026-06-17 04:22:20 +02:00
Sucukdeluxe
a7d989ebd2 Release v1.7.214 2026-06-17 03:30:47 +02:00
Sucukdeluxe
73004f3864 Fix: Selbst-Lahmlegung der Mega-Debrid-API beheben (1-Token-pro-Account + falscher 60-min-Cooldown)
Live aus conversion.log belegt: bei API-first + maxParallel hammerten viele
parallele Umwandlungen DENSELBEN Account → Mega-Debrids 1-Token-pro-Account-
Regel → "Token error, please log-in" (Token gegenseitig invalidiert). Ein
einzelner generischer Null-getLink wurde dann als "Login oder Unrestrict
fehlgeschlagen" geworfen → classifyAccountFailure matchte das Wort "Login" →
category=invalid → 60-MINUTEN-Cooldown auf einen NACHWEISLICH funktionierenden
Account (39x OK davor, klappt auch auf der Webseite). Folge: alle Accounts
cooled, alle Slots haengen an Fehl-Umwandlungen (conv12/dl0), nichts laedt.

Fix A (download-manager): getSerializedValidatingLimit gilt jetzt auch fuer
megadebrid-api (nicht nur -web) = Anzahl nutzbarer Accounts ohne :api-Cooldown.
Damit laeuft hoechstens EINE API-Umwandlung pro Account gleichzeitig (Rotation
verteilt 1/Account) — respektiert die 1-Token-Regel, verhindert die Token-
Kollision und gibt Download-Slots frei.

Fix B (debrid): (1) generische Wurf-Meldung "Login oder Unrestrict
fehlgeschlagen" -> "Linkgenerierung lieferte kein Ergebnis" (kein "Login"-
Trigger mehr). (2) classifyAccountFailure: "token error"/"please log-in" ->
kurzer temporary-Cooldown (15s); invalid-Branch nur noch bei ECHTEN
Credential-Fehlern (bad login/incorrect password/invalid credentials/
unauthorized/forbidden/connectUser) statt losem login|auth. Ein transienter
Fehler sperrt einen guten Account nicht mehr 60 min, sondern hoechstens Sekunden.

Tests: API-Serialisierung 2-Acct->2-parallel + 1-Acct->1-at-a-time; kein
60-min-invalid bei Null-Ergebnis; token-error<60s. 844/844 gruen, tsc=6.
2026-06-17 03:30:04 +02:00
Sucukdeluxe
21803f316b Fix: Support-Bundle-Export friert die UI nicht mehr 1-2 Minuten ein (asynchrones IO)
buildSupportBundle lief komplett synchron auf dem Electron-Main-Thread:
existsSync/readdirSync/statSync/readFileSync (via AdmZip.addLocalFile) ueber
ALLE Log-Dateien — bei Hunderten item-logs und gleichzeitig laufenden
Downloads (Platte ausgelastet) blockierte das den Event-Loop fuer 1-2 min,
die ganze Oberflaeche stand. Live belegt: ein Bundle mit Hunderten
item-logs-Dateien.

Fix: Alle Datei-Zugriffe im Bundle-Build auf fs.promises (nicht-blockierend)
umgestellt, buildSupportBundle ist jetzt async und liefert Promise<Buffer>.
Dateiinhalte werden per readFile gelesen und mit zip.addFile(buffer)
hinzugefuegt (gleiche Zip-Struktur wie vorher). Der Event-Loop bleibt
waehrend der IO-Wartezeiten frei → UI friert nicht mehr ein. Caller
angepasst (app-controller.exportSupportBundle async, main.ts await,
debug-server via .then).

Test support-bundle.test.ts: valides Zip mit Overview + echter Datei;
ein paralleler Timer feuert waehrend des Builds (Event-Loop nicht blockiert).
840/840 gruen, tsc unveraendert (6), Build ok.
2026-06-17 03:14:01 +02:00
Sucukdeluxe
738ae2e2d5 Release v1.7.213 2026-06-17 02:54:00 +02:00
Sucukdeluxe
5309f8dd69 Diagnose: Vollstaendiges Conversion-Trace-Logging (conversion.log) fuer haengende/langsame Link-Umwandlung
Bewusst NUR Diagnose, KEINE Verhaltensaenderung — damit das naechste
Support-Bundle das echte aktuelle Verhalten zeigt und der naechste Fix
die Ursache trifft statt zu raten (live belegt: "Unrestrict Timeout nach
60s" R53/R64, API-"Token error, please log-in" kuehlt beide Accounts ab).

Neues Modul conversion-trace.ts: AsyncLocalStorage-Trace, der einen
unrestrict-Versuch ueber alle Schichten begleitet und EINEN strukturierten
Block pro Versuch nach conversion.log schreibt (Datei-Infra wie
account-rotation-log: Rotation bei 5 MB, 14 Tage Retention; im Support-
Bundle). tracePhase ist no-op ohne aktiven Trace — additiv, null Risiko.

Instrumentiert wird die Kern-Blindstelle der bisherigen Logs:
- download-manager Boundary: runWithConversionTrace + Slot-Belegung
  (conv/dl/active/max) + Caller-Timeout-Attribution (was lief, als die 60s
  feuerten).
- Provider-Kette (debrid): chain-try/ok/failed/aborted — zeigt, ob der
  Web->API-Failover ueberhaupt feuert oder vom globalen Abbruch gekappt wird.
- Mega-Rotation: mega-account mit workMs + outcome (ok/failed/fatal/aborted)
  + Cooldown je Account.
- API-Token-Lifecycle: token cached/pending-join/fresh-login + connectMs,
  getLink response_code/text — klaert die Herkunft von "Token error".
- Mega-Web runExclusive: web-queue mit queueWaitMs UND workMs getrennt —
  klaert, ob die 60s Warten in der Queue oder echte langsame Arbeit sind.

Test conversion-trace.test.ts (Formatter + ALS-Kontext-Propagation).
838/838 gruen, tsc unveraendert (6 Baseline), Build ok.
2026-06-17 02:52:44 +02:00
Sucukdeluxe
e91e66a5f3 Release v1.7.212 2026-06-17 02:18:34 +02:00
Sucukdeluxe
876483da51 Fix: R2-Web-Limit zaehlt nur Accounts ohne laufenden Web-Cooldown
getAvailableMegaDebridAccounts filtert disabled + Tageslimit, aber NICHT
Runtime-Cooldowns. Die Rotation (debrid.ts) ueberspringt einen Account
im Cooldown jedoch — ohne diesen Filter koennte der Scheduler also eine
Web-Umwandlung zu viel zulassen, die sich dann auf einen noch brauchbaren
Account stapelt und einen Download-Slot bis zum 90s-Queue-Timeout haelt.
getSerializedValidatingLimit zaehlt jetzt nur Accounts ohne aktiven
`${id}:web`-Cooldown, sodass die Admission exakt der tatsaechlichen
Rotation-Verfuegbarkeit entspricht.

Test: 2 Accounts, einer im Web-Cooldown -> nur 1 Web-Umwandlung
gleichzeitig (kein Over-Admit). afterEach setzt jetzt auch den
Mega-Runtime-State zurueck (Cooldowns/Cursor/In-Flight) als Hygiene.
2026-06-17 02:16:22 +02:00
Sucukdeluxe
6d4da02f92 Fix: Mega-Debrid-Umwandlung robust + parallel — schneller Retry, alle Accounts, sichtbarer Grund
R3 (Kern): Transiente Resolve-Fehler ("Datei beim Hoster gerade nicht
abrufbar", frz. "Fichier supprimé") werden jetzt mit kurzem Delay
(3–6–10s) neu versucht statt mit dem 5s..120s-Exponential. Ein solcher
Einzel-Fehler zählt nicht mehr zum Provider-Circuit-Breaker (der bei 20
Fehlern 30–300s Cooldown setzt und die Mega-Session invalidiert) und
löst keine Provider-Cooldown-Inflation aus — dadurch bremst er die
gesunden Links nicht mehr aus. Neuer Branch vor der generischen
Unrestrict-Retry-Logik; Detektor isMegaDebridTransientResolveFailure
matcht sowohl die rohe französische als auch die gerenderte deutsche
Meldung (am Download-Manager kommt die klassifizierte deutsche an).

R2 (parallel): getSerializedValidatingLimit für megadebrid-web ist nicht
mehr hart 1, sondern die Anzahl nutzbarer Accounts. Der harte Wert 1
stammte aus der Einzel-Account-Zeit (Commit a3e3d6f). Sicher, weil die
Web-Ebene ohnehin pro Account serialisiert (runExclusive, key=login) und
die Account-Rotation gleichzeitige Aufrufe atomar auf distinkte Accounts
verteilt (megaDebridInFlight wird synchron vor dem await erhöht). Ein
Account → Limit 1 (keine Änderung).

R4 (Sichtbarkeit): Der echte Grund steht inline im Status und als Tooltip
(item.lastError); kaputten "?"-Separator zu "(Rn)" gesäubert.

UI-Bugfixes: Account-Duplizierung beim Entfernen (React-Key-Kollision
API/Web → rowKey nach kind), Username-Spalte zeigt eingegebenen Login
statt Anbieter-Mail (Mail als Tooltip).

Tests: transientResolveRetryDelayMs, isMegaDebridTransientResolveFailure,
2 behaviorale Download-Manager-Tests (schneller Re-Try mit deutschem
Grund; 1 Web-Umwandlung pro Account parallel). 833/833 grün, tsc
unverändert.
2026-06-17 02:07:25 +02:00
Sucukdeluxe
bec119583d docs(lessons): "permanent/tot" nie ohne Transienz-Gegenprobe (supprime war transient)
Lehre aus v1.7.210->211: Mega-Debrid "Fichier supprime" als permanent annehmen
ohne zu pruefen, ob derselbe Link je danach ein OK bekam. Intersection(failed, ok)
!= leer -> transient. Plus die Account/Link-permanent/dieser-Versuch-Linse.
2026-06-17 00:51:33 +02:00