Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
264 commits
Select commit Hold shift + click to select a range
f13ce9f
docs(library-v2): record bounded artist rollups
Jul 22, 2026
a5d6039
fix(library-v2): fold aliases into artist list totals
Jul 22, 2026
a95d958
docs(library-v2): record alias list completion
Jul 22, 2026
fcfd880
fix(library-v2): scope artist actions across aliases
Jul 22, 2026
916e1d5
docs(library-v2): record alias action completion
Jul 22, 2026
535f179
fix(library-v2): rebuild album credits on reimport
Jul 22, 2026
e83117a
docs(library-v2): record credit rebuild completion
Jul 22, 2026
cf5e82b
perf(library-v2): poll artist queue status once
Jul 22, 2026
d91bb90
docs(library-v2): record queue polling completion
Jul 22, 2026
0a71e8a
fix(acquisition): verify staged copies by content
Jul 22, 2026
6d94316
docs(library-v2): record working-copy verification
Jul 22, 2026
cea13f6
fix(library-v2): run refresh scan as observable job
Jul 22, 2026
521b8f9
docs(library-v2): mark review findings implemented
Jul 22, 2026
18488b1
docs(library-v2): record final verification status
Jul 22, 2026
39506bc
docs: consolidate library v2 documentation into 4 distinct files
Jul 22, 2026
7033dff
docs(library-v2): restore missing invariants, detailed issue logs, an…
Jul 22, 2026
5d3737e
docs(library-v2): restore product rationale and source detail
Jul 22, 2026
16fbaf9
complete Library v2 playlist quality and history
Jul 22, 2026
6584891
refactor: remove coupled library v2 playlist quality prototype
Jul 22, 2026
634aaf3
refactor: remove playlist surface from library v2
Jul 22, 2026
f0b0794
refactor: make library overhaul depend on native foundation
Jul 22, 2026
2301f28
test: retain download source regression coverage
Jul 22, 2026
c0dc733
docs: record artist-list perf and search-link findings
Jul 25, 2026
6134af1
perf(library-v2): serve artist-list cache-bust tokens from one direct…
Jul 25, 2026
8ab71d0
perf(library-v2): drop the extra JPEG optimize pass on full-size artwork
Jul 25, 2026
74d6f27
perf(library-v2): resolve cold artwork off the request thread
Jul 25, 2026
013354b
perf(library-v2): warm artwork as soon as entities join the library
Jul 25, 2026
834f871
perf(library-v2): drop unrequested roll-ups from the artist list query
Jul 25, 2026
f9ba809
fix(search): link library results to Library V2 when only the name ma…
Jul 25, 2026
8cc6b30
style(webui): format the artwork retry test
Jul 25, 2026
1ff91fa
docs: record the perf and search-link remediation status
Jul 25, 2026
97f441a
fix(library2/artwork): rework version cache, executor lifecycle, infl…
Jul 25, 2026
7a18cc6
fix(search): verify legacy-artist link uniqueness off the read path
Jul 25, 2026
282ca31
fix(webui): stop committing a stale retry frame on artwork src change
Jul 25, 2026
c8654d9
fix(library2): make the artist size roll-up an explicit request param
Jul 25, 2026
7e0939a
docs: record the 25 Jul branch-review remediation status
Jul 25, 2026
9cd9ca1
fix: reconcile Foundation-rebase semantic gaps
Jul 25, 2026
283d673
docs: correct thin-adapter status — quality_profile_id passthrough is…
Jul 25, 2026
c093c5b
fix(library2): wire quality_profile_id passthrough to native Watchlis…
Jul 25, 2026
60523e2
docs+test: confirm Orphan-Approve root cause, scope it beyond quarantine
Jul 26, 2026
b1ee08c
feat(library2): journal previous_file_replaced in the F-10 history feed
Jul 26, 2026
10c7290
fix(library2): take the artwork walk off the manual-match request path
Jul 26, 2026
4c02554
feat(library2): reconcile index paths that drifted away from disk
Jul 26, 2026
c8d34eb
fix(library2): materialize simple downloads instead of skipping them
Jul 26, 2026
929cd20
feat(acquisition): correlate library_history so F-10 can journal huma…
Jul 26, 2026
1566d4d
feat(library2): deliver background-built artwork to the rendered page
Jul 26, 2026
2ec696f
docs: record the five 26 July corrections (status §20-§25)
Jul 26, 2026
e446e78
fix(repair): align native subject rows with the legacy SELECT width
Jul 26, 2026
7ca0464
fix(library2): scan every artist for album twins, not just merge surv…
Jul 26, 2026
7e6bbec
fix(library-v2): name the track behind a bundle-matching conflict
Jul 26, 2026
167c681
docs: record the first production-DB run (status §27, issues §15)
Jul 26, 2026
0cd7167
fix(library-v2): close the tag/cover/verification convergence gaps (T…
Jul 26, 2026
35caeef
fix(repair): make genre_cleanup and comma_artist_splitter read native…
Jul 26, 2026
c608339
fix(repair): give library_reorganize findings a lib2: identity (T-12)
Jul 26, 2026
f730386
feat(library-v2): anchor unmapped-artist reconcile on provider ids an…
Jul 26, 2026
82e811f
docs: record the §18 tool audit and its T-11/T-12 fixes (status §30, …
Jul 26, 2026
e4d91aa
docs(library-v2): document UI requirements, search fixes, and issue f…
Jul 26, 2026
39358e5
fix(library-v2): resolve iss27-01/02/03/04, verify iss27-05 already f…
Jul 27, 2026
b5063e0
fix(library-v2): fix Interactive Search 0-result queries, quarantine …
Jul 27, 2026
ffe8eaa
docs(library-v2): record §21/§33 Interactive Search fixes (iss27-09/1…
Jul 27, 2026
5ddc464
docs(library-v2): record live-test regressions in §22/§34 (iss27-12/1…
Jul 27, 2026
e3a3c92
docs(library-v2): record new-track-single-metadata-source scenario (i…
Jul 27, 2026
37fc41f
fix(library-v2): reconcile search and track identities
Jul 27, 2026
a0c0c2f
feat(library-v2): finish remaining library UX
Jul 27, 2026
38833e1
fix(library-v2): harden identity and async boundaries
Jul 27, 2026
d429e5f
feat(library-v2): refine track table and library actions
Jul 27, 2026
9d3a399
fix(library-v2): restore responsive column resizing
Jul 27, 2026
600f1b7
fix(library-v2): artwork contention and Prowlarr search visibility
Jul 28, 2026
1f2c4aa
fix(library-v2): converge catalog state after replacements and multi-…
Jul 28, 2026
702f7ec
fix(library-v2): resolve monitoring intent live and mirror it both ways
Jul 28, 2026
a064252
fix(library-v2): make client and UI async boundaries fail visibly
Jul 28, 2026
4f2e148
fix(library-v2): stop repair fixes from corrupting or silently losing…
Jul 28, 2026
d9479a0
docs(library-v2): record the §27 deep-dive resolution
Jul 28, 2026
1330aa9
fix(library-v2): narrow the replaced-file sweep to observable directo…
Jul 28, 2026
f1be2e9
docs(library-v2): specify the legacy artist/discovery parity work (ld…
Jul 28, 2026
eaff00b
feat(library-v2): give the catalogue a discovery path and a faster co…
Jul 28, 2026
4fe6007
feat(library-v2): port the legacy artist view so search never leaves …
Jul 28, 2026
b35b373
docs(library-v2): record the ldp-01..09 resolution and its decisions
Jul 28, 2026
03f7b8a
fix(library-v2): bookmarking one track must not monitor a whole album
Jul 28, 2026
623484e
fix(library-v2): stop the top-track bookmark locking the database
Jul 28, 2026
42c5a48
fix(library-v2): resolve the tracklist of a bookmarked release, and s…
Jul 28, 2026
c2a03e6
fix(library-v2): take the tracklist walk off the request thread and m…
Jul 28, 2026
9024e0e
docs(library-v2): park the legacy Re-identify UI before deleting the …
Jul 28, 2026
3a8c7bd
fix(library-v2): make the legacy -> lib2 migration resumable and self…
Jul 28, 2026
7d49ca1
feat(library-v2): show the automatic migration on the library page
Jul 28, 2026
3f46fcd
feat(library-v2): Library V2 becomes the Library
Jul 28, 2026
6034d70
refactor(library-v2): delete the legacy library frontend
Jul 28, 2026
987888a
docs(library-v2): record the Library cutover and the migration contract
Jul 28, 2026
93f5622
feat(library-v2): add a catalogue reset so the migration can be tested
Jul 28, 2026
1874c28
fix(library-v2): size the artist hero portrait by the layout, not by …
Jul 28, 2026
d9fcf3f
fix(library-v2): stop the legacy search-card CSS restyling the hero p…
Jul 28, 2026
ef4bb22
fix(library-v2): run the discography refresh as a background job
Jul 28, 2026
66f221e
feat(library-v2): drop the Import review entry from the section switcher
Jul 28, 2026
23aa5ee
feat: name the thread that holds the SQLite write lock
Jul 28, 2026
d874a79
fix: release the write lock before enriching from a provider
Jul 28, 2026
4834057
sync: merge upstream Nezreka/SoulSync:dev into library-overhaul
Jul 31, 2026
ee6ca15
docs(library-v2): record the post-sync multi-agent audit as issues §2…
Aug 1, 2026
da89b58
fix(search): open Library V2 from search again, and drop our duplicat…
Aug 1, 2026
29dd1a3
fix(library-v2): stop the migration landing in an empty library, and …
Aug 1, 2026
32648ef
fix(repair): close the paths that destroyed the user's only copy of a…
Aug 1, 2026
f7c978b
fix(library-v2 ui): stop reporting failed requests as facts about the…
Aug 1, 2026
5e604b8
docs(library-v2): record the §45 remediation as §46
Aug 1, 2026
6c7066c
fix(library-v2): close the four findings §45.5 left speculative
Aug 1, 2026
3535e3d
fix(library-v2): harden UI actions and search flows
Aug 4, 2026
56738ab
fix(library-v2): enforce metadata and quality contracts
Aug 4, 2026
f20c7b5
fix(runtime): isolate blocking client executors
Aug 4, 2026
778c19c
fix(imports): secure upgrades and terminal outcomes
Aug 4, 2026
c451395
docs(library-v2): record audit remediation handoff
Aug 4, 2026
6bbce43
test(imports): align containment fixtures
Aug 4, 2026
fa6a58d
feat(library-v2): squash library overhaul onto current dev
Aug 4, 2026
4f3f33b
fix(enhance): never makedirs an unreachable library root (#1109)
Aug 4, 2026
00087ea
merge: restore the pre-squash development history
Aug 4, 2026
d4cda89
fix: address the #1121 review
Aug 9, 2026
016580c
fix: address the code review of d4cda89e
Aug 9, 2026
9870595
fix(title-match): validate the dash rule against a real library
Aug 9, 2026
4078115
test(acoustid): make the real-fingerprint corpus earn its place
Aug 9, 2026
0b60ab8
merge: bring dev into library-overhaul
Aug 9, 2026
2325525
merge: bring upstream/dev in after the #1121 merge
Aug 10, 2026
0cd21a1
docs: record Nezreka's #1062 review as a worklist
Aug 10, 2026
57d238e
fix(library-v2): resolve Nezreka's #1062 review — migration, enrichme…
Aug 11, 2026
6f13619
fix(library-v2): two defects in the native enrichment sweep, plus han…
Aug 11, 2026
7aacec8
feat(library-v2): divergence between legacy and lib2 becomes a metric
Aug 11, 2026
2a47d6f
docs: record the divergence metric and what measuring it found
Aug 11, 2026
eee086f
test(library-v2): ratchet legacy-table usage so it can only go down
Aug 11, 2026
b323951
feat(library-v2): the metadata-update worker reads lib2, not legacy
Aug 11, 2026
6c2c11e
docs: the ratchet, its next levers, and the media-server split
Aug 11, 2026
7181c72
fix(library-v2): the artists page's watchlist filter is per profile a…
Aug 11, 2026
0c74c8f
fix(library-v2): the trigger and the resync cannot disagree on column…
Aug 11, 2026
7f4e0c0
docs: the mirror's remaining payload gap, and why it is blocked on sc…
Aug 11, 2026
eb69f18
feat(library-v2): albums and tracks get an enrichment column, and the…
Aug 11, 2026
20f43a3
feat(library-v2): per-provider enrichment bookkeeping, the stage-2 pr…
Aug 11, 2026
877a342
docs: stage-2 start — the batch-selection prerequisite nobody had named
Aug 11, 2026
1d56e06
feat(library-v2): the Last.fm worker leaves the legacy tables entirely
Aug 11, 2026
819535c
feat(library-v2): the Genius worker leaves the legacy tables too
Aug 11, 2026
2b2d191
docs: the worker-migration recipe, and the scalar-column hole in the …
Aug 11, 2026
b91202f
feat(library-v2): the shared worker helpers leave legacy, with Discog…
Aug 11, 2026
6d7ed19
feat(library-v2): the Similar Artists worker reads lib2, and the two …
Aug 11, 2026
dd20a76
fix(library-v2): a migrated service is mirrored backfill-only, not dr…
Aug 11, 2026
5cd04b3
feat(library-v2): the Amazon and JioSaavn workers leave the legacy ta…
Aug 11, 2026
14d5606
feat(library-v2): the MusicBrainz worker and its service leave the le…
Aug 11, 2026
a52a57b
docs: nine workers off legacy, and the two holes the handover had
Aug 11, 2026
e285d02
feat(library-v2): the Spotify and iTunes workers leave the legacy tables
Aug 11, 2026
0f3f95e
feat(library-v2): the Qobuz, Tidal and Deezer workers leave the legac…
Aug 11, 2026
7dd9114
docs: every provider worker is off legacy; soulid needs its own round
Aug 11, 2026
74635a5
refactor(library-v2): fold the eight native_p3 job overrides back int…
Aug 11, 2026
167d3ad
docs: the repair-jobs rollback fork is gone, and what the ratchet doe…
Aug 11, 2026
d28e518
test(library-v2): restate the §26 padding tests — the hazard they gua…
Aug 11, 2026
8e29f15
refactor(library-v2): der Repair-Worker verlässt die Legacy-Tabellen
Aug 11, 2026
9f3c08f
fix(library-v2): die letzte Rollback-Gabel und vierzehn stille rote T…
Aug 11, 2026
5326798
docs: der Repair-Worker liest die Legacy-Tabellen nicht mehr (§50.4.4.9)
Aug 11, 2026
591f262
fix(library-v2): der atomare Album-Publish erzählte es nur der Legacy…
Aug 11, 2026
32d8f5f
fix(library-v2): die #934-Heilung und die AudioDB-Sperre kamen beim F…
Aug 12, 2026
1bdd4c8
test(library-v2): zwei Patches zeigten auf Namen, die mit der Gabel g…
Aug 12, 2026
5efd0d4
refactor(library-v2): soulid zieht um, und die schon erzeugten Ids zi…
Aug 12, 2026
2357b95
refactor(library-v2): die Statistik-Seite liest den nativen Katalog
Aug 12, 2026
452c313
refactor(library-v2): „In deiner Bibliothek" fragt den nativen Katalog
Aug 12, 2026
e7a539d
refactor(library-v2): der Export-Wasserfall liest lib2
Aug 12, 2026
07aac52
refactor(library-v2): der Genre-Tauchgang fragt lib2, was schon da ist
Aug 12, 2026
9d585fd
refactor(library-v2): die personalisierten Playlists fragen lib2
Aug 12, 2026
8810c1e
refactor(library-v2): die Suche fragt nur noch den nativen Katalog
Aug 12, 2026
4f246b0
refactor(library-v2): der Chat teilt Dateien aus dem nativen Katalog
Aug 12, 2026
9dc604c
refactor(library-v2): der Watchlist-Scanner fragt den nativen Katalog
Aug 12, 2026
488184c
refactor(library-v2): die Bibliotheks-Aggregate lesen den nativen Kat…
Aug 12, 2026
f15d623
fix(library-v2): die Statistik-Seite verlinkte Artists mit Legacy-Ids
Aug 12, 2026
218f759
refactor(library-v2): die zwei Artist-Graphen lesen den nativen Katalog
Aug 12, 2026
bb03187
refactor(library-v2): die Artist-Detail-Seite und ihre Endpunkte sind…
Aug 12, 2026
589ef0a
feat(library-v2): der Medien-Server-Link, und der Hörverlauf am Katalog
Aug 12, 2026
2969939
feat(library-v2): der Medien-Server-Scan schreibt den nativen Katalog
Aug 12, 2026
70e2969
test(library-v2): die Scan-Tests prüfen den Katalog, und eine Reihenf…
Aug 12, 2026
d121bb9
docs: Stand nach der großen Runde — 189/47 und was die Brocken noch b…
Aug 12, 2026
82ef96a
refactor(library-v2): die öffentliche API und die Track-Suche lesen d…
Aug 12, 2026
e5dacde
refactor(library-v2): der SoulSync-Import und der Repair-Worker schre…
Aug 12, 2026
99c70b0
refactor(library-v2): web_server.py liest und schreibt nur noch den K…
Aug 12, 2026
b275776
refactor(library-v2): Artist-Detail, Radio und die Bearbeitungsschreiber
Aug 12, 2026
c872c9f
refactor(library-v2): die Datei-Pfad-Aufloesung, die Fremd-Id-Joins u…
Aug 12, 2026
5145c35
refactor(library-v2): der lange Schwanz
Aug 12, 2026
7595b6d
fix(library-v2): was die Migration fallen liess
Aug 13, 2026
dfbc052
fix(library-v2): harden cutover ownership and migration
Aug 14, 2026
e7f6854
fix(library-v2): reconcile renamed files during upgrade
Aug 14, 2026
6b49332
fix(library-v2): resolve post-cutover regressions
Aug 14, 2026
c04ac26
docs(library-v2): record album download pipeline findings
Aug 14, 2026
764b279
fix(library-v2): harden media server recognition
Aug 14, 2026
a0c9f5d
fix(library-v2): refine responsive table and server status
Aug 14, 2026
2ff72d1
merge: sync dev and harden Library v2 cutover
Aug 14, 2026
f7496e8
merge: resync latest dev with Library v2
Aug 14, 2026
297eb8f
fix(library-v2): simplify import and bulk actions
Aug 14, 2026
febd14a
fix(library-v2): simplify track server badges
Aug 14, 2026
f155e64
Merge remote-tracking branch 'upstream/dev' into library-overhaul
Aug 15, 2026
b55d881
fix(library-v2): ten regressions from the all-onto-lib2 rewrite
Aug 15, 2026
ee7e328
fix(library-v2): do not purge the provider ledger mid-migration
Aug 15, 2026
a43dee1
fix(library-v2): the enrichment queue forgot what the library is
Aug 15, 2026
ed7c931
merge: upstream 3.2.2, and reassign lands in Library v2
Aug 16, 2026
ad92d4b
fix(library-v2): do not offer reassign on a release you own nothing of
Aug 16, 2026
21a5fc7
refactor(library-v2): one journal for every physical delete
Aug 16, 2026
7964a19
fix(library-v2): a scanned library no longer reads "Not scanned"
Aug 16, 2026
9fdd4a7
fix(library-v2): permanent delete, missing-file scans, missing-counts…
Aug 17, 2026
6e31bff
Merge remote-tracking branch 'upstream/dev' into library-overhaul
Aug 17, 2026
bc2a6db
Merge remote-tracking branch 'upstream/dev' into library-overhaul
Aug 19, 2026
05f4597
fix(library-v2): remediate the five-agent branch audit
Aug 20, 2026
9b8cba0
fix(library-v2): Monitor All button towered over its neighbour
Aug 20, 2026
2212096
Merge remote-tracking branch 'upstream/dev' into library-overhaul
Aug 20, 2026
754cbdc
fix(library-v2): close the 2026-08-21 review, retire the legacy catal…
Aug 21, 2026
4e41912
Merge upstream/dev (3.2.3) into library-overhaul
Aug 22, 2026
a455b6f
fix(wishlist,library2): real artwork, stable identities, read-only tr…
Aug 22, 2026
6c2b24b
feat(library-v2): recording ownership, reorganize safety, and the 3.2…
Aug 23, 2026
d8525bd
fix(maintenance): a finding with no track behind it, and a path you c…
Aug 24, 2026
c228454
fix(retag): a title someone set by hand is the library's title
Aug 24, 2026
6ffcb8e
feat(retag): let the user hand one hand-set field back to the catalogue
Aug 24, 2026
28624da
refactor(reorganize): a reorganize moves files, and that is all it does
Aug 24, 2026
9183fdf
feat(retag): the job comes back, scoped, on the lib2 engine
Aug 24, 2026
035f413
feat(retag): two requests, two buttons — apply, or apply over my own …
Aug 24, 2026
9a03165
feat(retag): settle a hand-set field in the preview, one field at a time
Aug 24, 2026
66c287a
feat(retag): pull a matched provider's values into the catalogue
Aug 24, 2026
d89cdc0
Fix multi-artist library credits
Aug 24, 2026
a6c4f1e
Fix provider mapping for collaborative artists
Aug 25, 2026
ab90f3a
fix(acoustid): a name in another script is not evidence of a wrong do…
Aug 25, 2026
7e537b0
fix(acoustid): aliases may only follow an MBID the artist agrees on
Aug 25, 2026
2872eca
feat(enrichment): fill per-provider gaps, not just entries with no pr…
Aug 25, 2026
2bec44c
fix(acoustid): a scan that cannot confirm must not take verification …
Aug 25, 2026
33f0fa6
refactor(acoustid): the scan and the download are one pipeline, not two
Aug 25, 2026
0681227
fix(acoustid): resolve an artist's aliases from its MBID, not from it…
Aug 25, 2026
b721223
fix(musicbrainz): let the owned catalogue match an artist the name ca…
Aug 25, 2026
44036ad
fix(musicbrainz): the cross-script rescue is a fallback, not a preemp…
Aug 25, 2026
c668c9f
fix(musicbrainz): a fetch that never answered is not "no aliases"
Aug 25, 2026
96870b5
fix(acoustid): a scan may only write down what it actually checked
Aug 25, 2026
f8d0d5d
feat(enrichment): let MusicBrainz decide its own identity
Aug 25, 2026
f1da383
test(acoustid): pin the cross-script blind spot instead of closing it…
Aug 25, 2026
8fc072e
fix(musicbrainz): close five ways an identity could still be guessed …
Aug 25, 2026
a378b4d
test(acoustid): patch the resolver where it actually lives
Aug 25, 2026
7c14b5d
Merge upstream/dev 3.3.1 (98 commits) into library-overhaul
Aug 26, 2026
a29dc4a
Merge fix/lossy-transform-policy into library overhaul
Aug 26, 2026
bdaca6b
fix(library-v2): model retained file versions explicitly
Aug 26, 2026
9d0db3c
fix(library-v2): centralize primary election
Aug 26, 2026
59472a5
Merge lossy transform policy fixes into library overhaul
Aug 26, 2026
46a895e
test(library-v2): align full-suite fixtures
Aug 26, 2026
43ef0e4
fix(library-v2): keep each credit's own provider id in tracklists
Aug 27, 2026
851d1b8
fix(library-v2): reserve title matches before positional tracklist cl…
Aug 27, 2026
da3961e
fix(library-v2): stop featured credits from inheriting the artist ide…
Aug 27, 2026
7efe923
fix(library-v2): keep browse-only credits out of the artist list
Aug 27, 2026
9d976cd
fix(library-v2): an artist photo must depict that artist
Aug 27, 2026
3e09d36
fix(library-v2): report what the artist repair pass actually repaired
Aug 27, 2026
2263088
fix(library-v2): catch Deezer's blank avatar and drop stale borrowed …
Aug 27, 2026
a585aa9
fix(library-v2): a release is only complete when its size is known
Aug 27, 2026
40e230e
fix(library): complete interactive search source handling
Aug 28, 2026
1325cee
Merge upstream 3.3.2: adopt the second lift, port our deltas into it
Sep 3, 2026
40441f4
Port the artist-detail features the deletion kept swallowing
Sep 3, 2026
5182b11
Make the post-scan reconcile real, and finish the artist-detail gap
Sep 3, 2026
8a0c605
Separate the three MusicBrainz namespaces the review found merged
Sep 3, 2026
fb81e00
Stop the discography ingest from filing a group id as a release id
Sep 3, 2026
9fade33
Merge upstream/dev 3.3.3, and mount the concerts feature it shipped b…
Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 12 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,17 @@ jobs:
- name: Build webui
run: npm run build

# Audit §16.1: the generated route tree must be committed in sync with
# the route files. A diff here means someone edited routes without
# committing the regenerated webui/src/routeTree.gen.ts (or edited the
# generated file by hand) — routes would differ between dev and the
# checked-in tree.
- name: Route tree must not drift
run: |
git diff --exit-code -- src/routeTree.gen.ts || {
echo "::error file=webui/src/routeTree.gen.ts::routeTree.gen.ts is stale — run the webui build and commit the regenerated file."
exit 1
}

- name: Run tests
run: npm test
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ database/*.db-wal
database/*.db.backup_*
database/*.histbak
database/api_call_history.json
# Runtime caches/download state are instance data, never source assets.
database/lib2_artwork/
/downloads/

# Overlay/poster assets — generated per-instance, never commit: downloaded Kometa
# logo packs, cached clean bases + first-touch backups, rendered thumbnails, uploads
Expand All @@ -38,5 +41,21 @@ bin/
.tool-versions
Staging/
Transfer/

# Graphify-generated knowledge graph (regenerate with `graphify .`; never source)
graphify-out/
**/graphify-out/
docker-compose.local-image.yml
*-container-inspect.json
# Any hidden folders
**/.*/
# …except GitHub Actions: CI gates (route-tree drift, tests) only run if
# the workflows are actually committed.
!.github/

# Transient single-file streaming cache (basic-search "Play"). Cleared per use,
# never persistent — and a 7.3 MB copyrighted MP3 was committed from here once.
Stream/

# Config backups. `config/config.json.bak` was committed with a real LAN IP in it.
*.bak
183 changes: 183 additions & 0 deletions ACOUSTID_CROSS_SCRIPT_STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# AcoustID Cross-Script / MusicBrainz — Stand 2026-08-25

Branch `library-overhaul`, **7 Commits, nicht gepusht** (`ab90f3a78` … `b72122354`).
Ausgangspunkt: zwei Findings auf dem Produktionsserver, beide Dateien korrekt.

```
Wrong download: "Apetitan" is actually "APETITAN"
Expected "Apetitan" by Sawano Hiroyuki, but audio fingerprint matches
"APETITAN" by 澤野弘之 (fingerprint 100%, title 100%, artist 0%)
```

---

## Die Commits

| Commit | Was |
|---|---|
| `ab90f3a78` | Cross-Script-Signal generisch in `evaluate`; Alias-Write-back; Negativ-Cache abgestellt |
| `7e537b043` | Aliase dürfen nur einer MBID folgen, der der Artist zustimmt |
| `2872ecadb` | Per-Provider-Backfill (`backfill_missing_provider_ids`) + MB ohne Worker durchsuchbar |
| `2bec44c29` | Ein Scan, der nicht bestätigen kann, darf `verified` nicht wegnehmen |
| `33f0fa643` | Scan und Download sind **eine** Pipeline (`verify_audio_file`) |
| `068122754` | Aliase aus der bekannten MBID holen statt nach Namen zu raten |
| `b72122354` | Automatisches Artist-Matching über den eigenen Katalog statt über den Namen |

### 1. Ähnlichkeit über Schriftgrenzen ist keine Evidenz

`core/matching/audio_verification.py` berechnet jetzt pro Dimension
`title_comparable` / `artist_comparable` über `is_cross_script_mismatch`. Eine
unvergleichbare Dimension ist **unbekannt**, nie **durchgefallen**, und kann nie
das sein, worauf ein FAIL beruht. Der `fingerprint >= 0.95`-Boden für
Cross-Script-Titel ist damit weg — er beantwortete die falsche Frage.

Gemessen vorher/nachher (Download-Pfad, gleiche Eingabe):

| | vorher | jetzt |
|---|---|---|
| keine Aliase auflösbar | FAIL | SKIP |
| Aliase vorhanden | PASS | PASS |
| Aliase ohne die Kanji-Form | FAIL | SKIP |

### 2. Eine Pipeline

Der Scanner ruft `AcoustIDVerification.verify_audio_file` auf — den
Einstiegspunkt des Downloads. Verfügbarkeitsprüfung, Lookup, Confidence-Schwelle,
MusicBrainz-Anreicherung titelloser Recordings, Alias-Auflösung, Entscheidung:
einmal. Beim Scanner bleibt nur, was nur er beantworten kann — welche Dateien,
und was mit einem Urteil geschieht.

Der Verifier gibt über das Context-Dict zurück, was sein Lookup gesehen hat
(`_acoustid_recordings`, `_acoustid_best_score`, `_acoustid_recording_mbids`,
`_acoustid_decision`) und nimmt ein optionales `min_score`.

Abgesichert in `tests/test_acoustid_one_pipeline.py`: acht Fälle durch **beide**
Pfade mit Gleichheitsprüfung, plus ein struktureller Test, der fehlschlägt, wenn
im Scanner wieder `evaluate(`, `_resolve_expected_artist_aliases`,
`MIN_ACOUSTID_SCORE` oder `_enrich_recordings_from_musicbrainz` auftaucht.

### 3. Der `verified` → `unverified`-Downgrade

Der Scanner las den Verifizierungsstand aus dem **Datei-Tag** und schrieb in die
**Katalog-Spalte**. Fehlendes Tag ⇒ „ungetaggte Datei" ⇒ deren Regel setzte eine
DB-`verified`-Zeile auf `unverified` und stempelte das Tag gleich mit.

Vor dem Cross-Script-Fix unerreichbar, weil FAIL `verification_status` in Ruhe
lässt (nur `acoustid_status='fail'` → „Mismatch"). Deshalb las die Reihenfolge
sich, als hätte der Fix es verursacht — er hat es freigelegt.

Jetzt: Stand = Tag **oder** Spalte; ein fehlendes Tag wird **geheilt**.

**Lehre für später:** wer ändert, *was* ein Urteil ist, muss alles prüfen, was
jeden einzelnen Urteilswert konsumiert.

### 4. Die Alias-Brücke war nie deterministisch

`lookup_artist_aliases` riet per Live-Namenssuche hinter einem Trust-Gate. Der
Kommentar im Code nennt genau diesen Artist als Verlustfall: für
`Sawano Hiroyuki` führt eine Decoy-Entität auf dem kombinierten Score (0.82,
knapp unter 0.85), während der echte `澤野弘之` mb_score 100 / kombiniert 0.30
hat und zuletzt sortiert wird. Der Notausgang verlangt entscheidende
MB-Relevanzscores — und die sind nicht stabil.

Dazu drei weitere Kippschalter, alle behoben:
* Cache-Key war der **exakte** Namens-String (binär, kein `COLLATE NOCASE`)
* ein *fehlgeschlagener* Lookup wurde als „keine Aliase" gecacht, 30 Tage
(`_search_and_score_artists` liefert jetzt `None` bei Fehler; echte Antworten
werden als `{'aliases': [], 'resolved': True}` markiert, alte unmarkierte
Zeilen bekommen genau einen Retry)
* erfolgreiche Lookups landeten nur im namens-verschlüsselten Cache, nie am
Artist (`_persist_artist_identity`, abgesichert durch `provider_id_conflict`)

**Und die eigentliche Lösung:** kennt die Katalog-Zeile die MBID, wird nicht mehr
gesucht — `_artist_row_mbid` → `fetch_artist_aliases(mbid)` → persistieren. Eine
Identität statt einer Ähnlichkeit.

### 5. Damit die MBID auch ohne Handarbeit dort landet

* `match_artist` sucht jetzt non-strict nach, wenn strict leer bleibt (#586, war
nur in `lookup_artist_aliases` gefixt)
* Cross-Script-Kandidaten werden über den **eigenen Katalog** gematcht: MB-Score
≥ 90 **und** mindestens ein besessenes Album in den Release-Groups dieser
Entität. Ohne besessene Alben: kein Match, kein Raten.
* `backfill_missing_provider_ids` füllt Lücken **pro Provider** (vorher galt ein
Artist mit Spotify-ID als fertig) und zieht aus `lib2_provider_attempts` über
`worker_queue.next_pending` — derselbe Ledger wie die zwölf Worker, deshalb
gefahrlos parallel. Läuft als Phase 2 von `native_enrichment_sweep`
(`backfill_batch_size`, Default 100).
* MusicBrainz ist ohne Worker durchsuchbar (`get_musicbrainz_service()`) und
immer in `_library_v2_configured_match_services` — vorher fiel es bei
fehlgeschlagener Worker-Init **still** aus jedem Enrich-Lauf.

---

## Testlage

* volle Suite: **17150 passed**; die 12 Failures des Laufs seriell nachgefahren
→ 111 passed, 1 failed
* verbleibend nur `discovery/test_discovery_endpoints::test_start_sync_happy_path`
— Baseline, per `git stash` gegen den unveränderten Stand bestätigt
* die übrigen waren Contention (drei parallele pytest-Läufe; die
`test_torrent_share_limits`-Failures sagen wörtlich `Failed: Timeout`)
* betroffene Bereiche seriell nach dem letzten Commit: **2836 passed**
* ruff sauber

⚠️ Unter `-n 8` rotiert eine Gruppe von Tests nicht-deterministisch
(`test_candidate_store`, `test_import_staging`, `test_tidal_collection_tracks`,
`test_enrich_endpoint`, `test_confirmed_search_route`). Immer seriell
gegenprüfen, bevor man etwas für eine Regression hält.

---

## Offen — hier weitermachen

### Auf dem Server nachschauen

```sql
-- hat der Artist eine MBID? Dann greift die neue deterministische Alias-Stufe
SELECT id, name, musicbrainz_id, aliases FROM lib2_artists WHERE name LIKE '%awano%';

-- was der MB-Worker über ihn denkt; 'not_found' ⇒ 30-Tage-Sperre
SELECT service, status, last_attempted_at FROM lib2_provider_attempts
WHERE entity_type='artist' AND entity_id=<id>;

-- vergiftete Alias-Cache-Zeilen (empty + confidence 0 + keine MBID)
SELECT entity_name, musicbrainz_id, match_confidence, last_updated, metadata_json
FROM musicbrainz_cache WHERE entity_type='artist_aliases';
```

### Nicht erledigt

1. **Altlast-Heilung.** Dateien, die der Scanner vor `2bec44c29` fälschlich auf
`unverified` gesetzt hat, korrigieren sich **nicht** von selbst — der Code
kann nicht wissen, ob `unverified` von ihm oder aus einem echten Befund
stammt. Bräuchte einen gezielten Heilungsschritt über die betroffenen Zeilen.
2. **MB-Worker-Durchsatz.** 1 Request/Sekunde, Queue-Reihenfolge Phase außen /
Entity-Typ innen (alle nie angefassten Artists vor dem ersten Album, alle
Alben vor dem ersten Track). Auf einem frisch befüllten Server brauchen
Tracks dadurch Wochen. `retry_days = 30` für `not_found`, ohne UI zum
Zurücksetzen — nur das Löschen der Ledger-Zeile hilft sofort.
3. **Manueller MB-Lookup läuft in ein Timeout.** `core/musicbrainz_client.py:25-26`
hat einen **modulglobalen** Rate-Limiter (1 req/s), geteilt mit dem
Hintergrund-Worker; eine manuelle Suche stellt sich dahinter an und hat
danach 10 s eigenes HTTP-Timeout — gegen die 10 s des Frontends.
4. **`enrich_native_entity_all_services` schluckt Provider-Fehler** auf
Debug-Level. Der Backfill schreibt stattdessen `error` in
`lib2_provider_attempts` (sichtbar, treibt Retry), der Enrich-Walk nicht.
5. **Namensdreher-Dublette** `Sawano Hiroyuki` vs `Hiroyuki Sawano` als zwei
Artist-Zeilen — siehe `duplicate-artist-name-ordering`. `get_artist_aliases`
sucht exakt nach Namen, findet die Aliase der anderen Zeile also nicht.
6. **`canonical_artist_id` speist die Alias-Auflösung nicht.** Das „Link alias"
im UI (§40) und `lib2_artists.aliases` sind getrennte Mechanismen; ein
UI-Link ändert am AcoustID-Ergebnis nichts. Sinnvoller Zusatz: die
Alias-Auflösung dem `canonical_artist_id` folgen lassen.

### Zwei Dinge, die *keine* Bugs sind (nicht nochmal untersuchen)

* **„AcoustID hat es verifiziert, aber ich finde den Song manuell nicht auf
MusicBrainz."** Der Download *sucht* MB gar nicht — er fingerprintet, und
AcoustID liefert die Recording-MBID direkt. Die Namenssuche scheitert, weil
MBs kanonischer Artist die Kanji-Form ist (#586).
* **`skip` ist kein Fehler.** Es heißt „geprüft, keine Aussage". Die Datei behält
ihr `verified` aus dem Download; die Check-Spalte zeigt „Skipped" mit
Begründung. Wird zu `pass`, sobald die Aliase auflösbar sind.
Loading