diff --git a/core/imports/pipeline.py b/core/imports/pipeline.py
index a2890519..d726e54a 100644
--- a/core/imports/pipeline.py
+++ b/core/imports/pipeline.py
@@ -35,7 +35,12 @@ from core.imports.context import (
from core.imports.file_integrity import check_audio_integrity, resolve_duration_tolerance
from core.imports.filename import extract_track_number_from_filename
from core.imports.guards import check_flac_bit_depth, move_to_quarantine
-from core.imports.quarantine import entry_id_from_quarantined_filename
+from core.imports.quarantine import (
+ approve_quarantine_entry,
+ entry_id_from_quarantined_filename,
+ list_quarantine_entries,
+)
+from core.imports.version_mismatch_fallback import try_accept_version_mismatch_fallback
from core.imports.side_effects import (
emit_track_downloaded,
record_download_provenance,
@@ -985,6 +990,53 @@ def post_process_matched_download(context_key, context, file_path, runtime, meta
post_process_locks.pop(context_key, None)
+def _attempt_version_mismatch_fallback(context, task_id, batch_id, runtime, metadata_runtime):
+ """Opt-in last resort once AcoustID retries are exhausted: accept the best
+ quarantined version-mismatch candidate for this track instead of failing.
+
+ Delegates the decision + safety rules to
+ ``core.imports.version_mismatch_fallback`` (version-mismatch only, all the
+ same matched version, >= min_count, AcoustID-only bypass). Returns True when
+ a candidate was accepted and re-dispatched — the caller then skips marking
+ the task failed.
+ """
+ try:
+ download_path = docker_resolve_path(
+ config_manager.get('soulseek.download_path', './downloads')
+ )
+ quarantine_dir = os.path.join(download_path, 'ss_quarantine')
+ restore_dir = os.path.join(download_path, 'Transfer')
+ expected_title = get_import_clean_title(context, default='')
+ expected_artist = get_import_clean_artist(context, default='')
+ if not expected_title or not expected_artist:
+ return False
+
+ def _reprocess(restored_path, ctx, tid, bid):
+ new_key = f"vmfallback_{tid}_{int(time.time())}"
+ threading.Thread(
+ target=lambda: post_process_matched_download_with_verification(
+ new_key, ctx, restored_path, tid, bid, runtime, metadata_runtime
+ ),
+ daemon=True,
+ ).start()
+
+ return try_accept_version_mismatch_fallback(
+ quarantine_dir=quarantine_dir,
+ restore_dir=restore_dir,
+ expected_title=expected_title,
+ expected_artist=expected_artist,
+ task_id=task_id,
+ batch_id=batch_id,
+ config_get=config_manager.get,
+ list_entries=list_quarantine_entries,
+ approve_entry=approve_quarantine_entry,
+ reprocess=_reprocess,
+ )
+ except Exception as exc:
+ pp_logger.debug("[Version-Mismatch Fallback] skipped due to error: %s", exc)
+ return False
+
+
def post_process_matched_download_with_verification(context_key, context, file_path, task_id, batch_id, runtime, metadata_runtime=None):
on_download_completed = getattr(runtime, "on_download_completed", None)
@@ -1026,6 +1078,11 @@ def post_process_matched_download_with_verification(context_key, context, file_p
f"AcoustID mismatch for task {task_id} — retrying next-best candidate: {failure_msg}"
)
return
+ # Retries exhausted. Opt-in last resort: if every quarantined
+ # candidate for this track failed the SAME version mismatch (e.g. all
+ # instrumental), accept the best one rather than leaving it missing.
+ if _attempt_version_mismatch_fallback(context, task_id, batch_id, runtime, metadata_runtime):
+ return
logger.info(f"File was quarantined by AcoustID verification (task={task_id}): {failure_msg}")
with tasks_lock:
if task_id in download_tasks:
diff --git a/core/imports/version_mismatch_fallback.py b/core/imports/version_mismatch_fallback.py
new file mode 100644
index 00000000..7eaab479
--- /dev/null
+++ b/core/imports/version_mismatch_fallback.py
@@ -0,0 +1,185 @@
+"""Last-resort acceptance of a version-mismatched download.
+
+Some tracks simply don't exist on the configured sources in the wanted cut —
+every copy is, say, the instrumental. The retry engine correctly rejects each
+one (version mismatch) and eventually gives up, leaving the track missing.
+
+This module provides an OPT-IN fallback: once a track's retries are fully
+exhausted, if every quarantined candidate for it failed the *same* way (same
+matched version, e.g. all ``instrumental``) and there are at least ``min_count``
+of them, accept the best (first-tried) one rather than failing outright.
+
+Hard safety rules:
+- Only ``Version mismatch`` quarantines qualify. Audio/artist mismatches
+ (a genuinely different recording) and integrity/duration failures
+ (truncated or wrong file) never participate.
+- All qualifying entries must share the same matched version. A mix
+ (instrumental + live) is ambiguous → no acceptance.
+- The chosen candidate is re-imported with only the AcoustID gate bypassed;
+ the integrity / duration / bit-depth gates still run, so a truncated or
+ corrupt file is never let through by this path.
+
+``select_version_mismatch_fallback`` is the pure decision core (no I/O) so it
+can be tested directly. ``try_accept_version_mismatch_fallback`` wires it to the
+quarantine store + re-import dispatch via injected callables.
+"""
+
+from __future__ import annotations
+
+import logging
+import re
+from typing import Any, Callable, Dict, List, Optional
+
+logger = logging.getLogger(__name__)
+
+# Matches the reason string written by acoustid_verification's version gate:
+# "Version mismatch: expected '
' () but file is '' ()"
+# We only need the matched () version to test cross-entry consistency.
+_VERSION_MISMATCH_RE = re.compile(
+ r"^Version mismatch:.*\bbut file is\b.*\(([^()]+)\)\s*$"
+)
+
+
+def _norm(text: Optional[str]) -> str:
+ return (text or "").strip().casefold()
+
+
+def matched_version(reason: Optional[str]) -> Optional[str]:
+ """Return the matched version token (e.g. ``'instrumental'``) for a
+ Version-mismatch reason string, or None if the reason isn't a version
+ mismatch / can't be parsed."""
+ if not reason:
+ return None
+ m = _VERSION_MISMATCH_RE.match(reason.strip())
+ if not m:
+ return None
+ return m.group(1).strip().casefold()
+
+
+def select_version_mismatch_fallback(
+ entries: List[Dict[str, Any]],
+ expected_title: str,
+ expected_artist: str,
+ min_count: int,
+) -> Optional[Dict[str, Any]]:
+ """Pick the quarantine entry to accept as a last resort, or None.
+
+ ``entries`` are dicts as produced by
+ :func:`core.imports.quarantine.list_quarantine_entries` (needs ``id``,
+ ``reason``, ``expected_track``, ``expected_artist``, ``has_full_context``).
+
+ Returns the chosen entry (the first-tried = oldest = best, by ascending
+ ``id`` whose timestamp prefix sorts chronologically) when, for this track,
+ there are at least ``min_count`` version-mismatch entries that all share the
+ same matched version and carry full context. Otherwise None.
+ """
+ title = _norm(expected_title)
+ artist = _norm(expected_artist)
+
+ candidates = []
+ for e in entries:
+ if not e.get("has_full_context"):
+ continue
+ if _norm(e.get("expected_track")) != title:
+ continue
+ if _norm(e.get("expected_artist")) != artist:
+ continue
+ version = matched_version(e.get("reason"))
+ if version is None:
+ continue
+ candidates.append((version, e))
+
+ if len(candidates) < max(1, int(min_count or 1)):
+ return None
+
+ versions = {v for v, _ in candidates}
+ if len(versions) != 1:
+ # Inconsistent wrong versions (e.g. instrumental + live) — ambiguous,
+ # don't guess which the user wants.
+ return None
+
+ # First tried = oldest = highest-confidence (the retry walks candidates
+ # best-first). The id is a "_