Skip to content

Folder audit: ingest real track paths + read-only Unknown-Album detection report (#252) - #253

Merged
benders merged 6 commits into
mainfrom
feature/252-track-path-folder-audit
Jul 13, 2026
Merged

Folder audit: ingest real track paths + read-only Unknown-Album detection report (#252)#253
benders merged 6 commits into
mainfrom
feature/252-track-path-folder-audit

Conversation

@benders

@benders benders commented Jul 13, 2026

Copy link
Copy Markdown
Owner

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 /proxy passthrough — no federation contract change, no FEDERATION_API_VERSION bump. Nullable; older peers degrade to NULL.
  • Folder audit + preview report (bc53b8b): runFolderAudit groups tracks by (instance_id, dirname(path)), flags folders spanning ≥2 albums, and proposes group-into-compilation only when ≥2 are sentinel-named. Exposed as owner-only GET /api/admin/hub/data-quality/folder-report (Hub namespace). Dry run by design — zero writes, no merge.ts involvement (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).
  • Real paths via native-API player provisioning (cfd1e31, supersedes e9a2ded/bbbf32d): Navidrome defaults to tag-derived virtual paths (Artist/Album/Title.ext) — zero folder signal — and reportRealPath is 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 flips reportRealPath: true on the poutine-sync and poutine-proxy records — 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_DEFAULTREPORTREALPATH is removed from compose.
    • Rollout = hub image update only. No compose edit, no Navidrome container recreate on peers.
    • Best-effort by design: the native API is unversioned/internal (only /auth/login + /api/player touched, 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):

  • Tagged real library + imported gnarg dump (18,686 tracks, no paths): 13/13 local coverage, gnarg honestly 0%, zero false clusters.
  • Staged real untagged compilation (artist tags only, album tag stripped): one cluster, correct proposal across Tubedigga/DJ Nexus/TYPE — and the flagged unified ids reproduce the exact release-group ids from the original music-west report (af1b0534…, cd120274…), courtesy of deterministic ID generation.
  • Native-API flip verified live on a throwaway Navidrome: virtual → real path on the very next Subsonic request, same player record, no restart.

Full transcript on the issue: #252 (comments).

Tests

  • pnpm verify green: 760+ hub + 121 frontend tests (incl. new navidrome-native.test.ts — 12 cases covering flip/no-op/degradation — and sync-local.test.ts ordering: ping → PUT → first album read), boundary lint clean.
  • pnpm test:federation green (3-hub Docker suite) including a new end-to-end assertion: post-sync, hub-a's local instance_tracks.path are 7/7 real /music/… paths with no env flag anywhere in the stack.

Out of scope (follow-ups)

  • Automatic regrouping / approval-based override layer.
  • hub-admin UI for the report (endpoint-first).
  • Peers report real paths only after they run a provisioning-capable build and re-sync; until then the audit degrades to "no clusters", never false proposals.

🤖 Generated with Claude Code

https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u

claude added 4 commits July 12, 2026 18:00
)

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
claude added 2 commits July 12, 2026 22:41
#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
@benders
benders marked this pull request as ready for review July 13, 2026 13:59
@benders
benders merged commit def186c into main Jul 13, 2026
2 checks passed
@benders
benders deleted the feature/252-track-path-folder-audit branch July 13, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ingest track paths from Navidrome sync + folder-based Unknown Album detection with read-only preview report

2 participants