Folder audit: ingest real track paths + read-only Unknown-Album detection report (#252) - #253
Merged
Merged
Conversation
) Navidrome's getAlbum song children carry a library-relative path; /proxy is a transparent passthrough so peers already send it — parse it into instance_tracks.path (nullable, NULL when absent) for both local and peer sync. Idempotent column migration follows the #199 sampling_rate pattern. No federation contract change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
…252) runFolderAudit groups instance_tracks by (instance_id, dirname(path)), flags folders spanning >=2 albums, and proposes a compilation regroup only when >=2 are sentinel-named ([Unknown Album]/empty). Exposed as GET /api/admin/hub/data-quality/folder-report (owner-only, Hub namespace). Dry run by design: zero catalog changes, no merge.ts involvement — regrouping would churn deterministic ids (pitfalls #242). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
Live preview run caught a design-breaking default: Navidrome reports tag-derived VIRTUAL paths (Artist/Album/Title.ext) unless Subsonic.DefaultReportRealPath is on — zero folder signal, so the folder audit could never fire. Worse, the default is pinned into the per-player record (keyed user+client+UA) at creation, so flipping the env var never reaches the existing sync player. Fix: set ND_SUBSONIC_DEFAULTREPORTREALPATH=true in compose AND bump the sync client name to poutine-sync-rp to deterministically mint a fresh player that inherits it. Virtual-path peers degrade to no-signal (never false proposals). Gotchas documented in pitfalls + internals. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
#252) The /proxy route overwrites the caller's c= with its own client name and drops the caller's User-Agent, so the player record peers' syncs hit on a hub's Navidrome is keyed by the proxy identity — the poutine-sync-rp bump never reaches it. Without this, a peer enabling ND_SUBSONIC_DEFAULTREPORTREALPATH keeps serving virtual paths to the whole federation through its pre-existing pinned player record. Rename poutine-proxy -> poutine-proxy-rp so upgraded peers mint a fresh player that inherits the flag. Verified /proxy/rest/* now returns real paths on the preview stack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
#252) Replace the ND_SUBSONIC_DEFAULTREPORTREALPATH env flag + client-name bumps with runtime provisioning: ensureRealPathPlayers() (services/navidrome-native.ts) logs into the local Navidrome's native API and flips reportRealPath=true on the poutine-sync and poutine-proxy player records — at boot (isMain entrypoint) and before each local sync (ping-then-flip closes the local window entirely). - Client names revert to poutine-sync / poutine-proxy: provisioning edits the pinned records directly, so no fresh player records are needed and operator per-player transcoding config is preserved. - Rollout collapses to a hub image update: no compose edit, no Navidrome container recreate on peers. - Best-effort by design: the native API is unversioned/internal, so every failure path warns (once per process) and degrades to virtual paths — observable as folder-report coverage %. Never fatal, never blocks sync. - Federation suite now asserts post-sync local instance_tracks.path are all real /music/ paths (verified green: 7/7 in the Docker cluster, no env flag). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
…efs #252) Rewrite the pitfalls #252 row and hub-internals path-quality bullet: per-player pinning is now solved by services/navidrome-native.ts at runtime; the env flag and -rp client bumps are superseded and must not be reintroduced. Documents the accepted one-sync window for freshly-reset peer Navidromes and the coverage-% canary for native-API breakage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #252.
What
Bad tags on any federated Navidrome split untagged compilations into per-artist
[Unknown Album]entries sharing one cover image (diagnosed live on music-west). This PR gives the hub the folder-identity signal to detect that damage and a read-only preview endpoint to review what inference would propose — deliberately without changing the catalog.instance_tracks.path(6dce531): parse the path Navidrome already sends through the transparent/proxypassthrough — no federation contract change, noFEDERATION_API_VERSIONbump. Nullable; older peers degrade to NULL.runFolderAuditgroups tracks by(instance_id, dirname(path)), flags folders spanning ≥2 albums, and proposesgroup-into-compilationonly when ≥2 are sentinel-named. Exposed as owner-onlyGET /api/admin/hub/data-quality/folder-report(Hub namespace). Dry run by design — zero writes, nomerge.tsinvolvement (regrouping would churn deterministic ids, pitfalls fix(merge): protect user data across re-merges (orphan audit + ID remap) and move merge off the event loop #242).Artist/Album/Title.ext) — zero folder signal — andreportRealPathis pinned per player record at creation. Instead of an env-flag + client-name bumps (the first approach on this branch), the hub now provisions the setting directly:ensureRealPathPlayers()(services/navidrome-native.ts) logs into its own Navidrome's native API and flipsreportRealPath: trueon thepoutine-syncandpoutine-proxyrecords — at boot, and before each local sync with a ping-then-flip ordering that closes the local window completely. Client names stay unbumped (operator per-player transcoding config preserved);ND_SUBSONIC_DEFAULTREPORTREALPATHis removed from compose./auth/login+/api/playertouched, local-only, never across federation). Any failure warns once and degrades to virtual paths — visible as the folder report's per-instance coverage %. Known window: a peer's first sync against a freshly-reset Navidrome may see virtual paths once; the next provisioning pass self-corrects.Validation against real synced data
Isolated stack (
poutine-preview, branch build, real Navidrome scan):af1b0534…,cd120274…), courtesy of deterministic ID generation.Full transcript on the issue: #252 (comments).
Tests
pnpm verifygreen: 760+ hub + 121 frontend tests (incl. newnavidrome-native.test.ts— 12 cases covering flip/no-op/degradation — andsync-local.test.tsordering: ping → PUT → first album read), boundary lint clean.pnpm test:federationgreen (3-hub Docker suite) including a new end-to-end assertion: post-sync, hub-a's localinstance_tracks.pathare 7/7 real/music/…paths with no env flag anywhere in the stack.Out of scope (follow-ups)
🤖 Generated with Claude Code
https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u