Problem
Discogs returns release_year: 0 as a sentinel for "year unknown" on releases without a verified release date. Backend-Service's metadata pipeline passes this 0 through unchanged in two places:
-
Runtime path — extractAlbumMetadata at apps/backend/services/metadata/metadata.service.ts:114: releaseYear: artwork.release_year ?? undefined — ?? only fires on null/undefined, so the 0 leaks straight through to the wire response served to the iOS playcut detail view and is persisted to flowsheet.release_year via enrichment.service.ts:112 as a literal 0.
-
Historical backfill path — jobs/flowsheet-metadata-backfill/enrich.ts:152: same ?? null shape on the direct DB write.
Discovered while investigating a 2026-05-22 user-reported iOS bug: an Autechre/ilanders playcut detail view rendered Release year: 0 along with no label and a band-image fallback. Looking at the row, release_year: 0 was written by the runtime enrichment from a Discogs cold-cache lookup that matched release 2316777 (Oversteps, which has Discogs year 0 because the source release lacks a verified date).
Blast radius
SELECT count(*) FROM wxyc_schema.flowsheet WHERE release_year = 0;
-- 11824
11,824 existing prod flowsheet rows carry release_year=0 today and render literal 0 on the iOS playcut detail view. Every cold-cache lookup against a year-less Discogs release adds one. The Post-launch hardening project (#32) is exactly the bucket for this kind of quality leak in recently-launched live services.
Proposed approach
One-character fix at each of the two call sites, plus a one-shot data backfill:
-
metadata.service.ts:114 — change releaseYear: artwork.release_year ?? undefined → releaseYear: artwork.release_year || undefined. || coerces 0 (and any other falsy) to undefined. Year-0 has no real-world meaning for music releases, so the broader-than-?? coercion is safe.
-
jobs/flowsheet-metadata-backfill/enrich.ts:152 — change release_year: artwork.release_year ?? null → release_year: artwork.release_year || null. Same shape.
-
One-shot UPDATE to clean up the existing 11,824 rows:
UPDATE wxyc_schema.flowsheet SET release_year = NULL WHERE release_year = 0;
Single-statement, fast, idempotent. Run after the code fix lands so any concurrent enrichment writes can't reintroduce 0. Doesn't need a migration file because the schema doesn't change — just a one-time SQL ticked off in the PR description.
Acceptance criteria
Constraints
- The fix must not erase any legitimate year-zero entries — but year 0 AD has no music releases, so there's no real-world data being thrown away. (1 BC = year 0 in ISO 8601 but Discogs doesn't tag releases that way.)
- Must run after the code fix lands so concurrent writes don't reintroduce
0 immediately.
- No schema change required.
Related
- Original investigation: 2026-05-22 iOS playcut-detail report on Autechre/ilanders. The duplicate-row half of that incident is filed at WXYC/tubafrenzy#555; this issue covers the orthogonal metadata-quality half.
- Fits under Post-launch service hardening (epic G — observability + epic D —
album_metadata) — adjacent to the broader metadata-quality work but a small, self-contained piece.
- Shares the "inline-copy parity" pattern documented around
filterSpacerGif (metadata.service.ts:101 + scripts/check-spacer-gif-callsites.sh) — same two-call-site coercion shape.
Problem
Discogs returns
release_year: 0as a sentinel for "year unknown" on releases without a verified release date. Backend-Service's metadata pipeline passes this0through unchanged in two places:Runtime path —
extractAlbumMetadataat apps/backend/services/metadata/metadata.service.ts:114:releaseYear: artwork.release_year ?? undefined—??only fires onnull/undefined, so the0leaks straight through to the wire response served to the iOS playcut detail view and is persisted toflowsheet.release_yearvia enrichment.service.ts:112 as a literal0.Historical backfill path — jobs/flowsheet-metadata-backfill/enrich.ts:152: same
?? nullshape on the direct DB write.Discovered while investigating a 2026-05-22 user-reported iOS bug: an Autechre/ilanders playcut detail view rendered
Release year: 0along with no label and a band-image fallback. Looking at the row,release_year: 0was written by the runtime enrichment from a Discogs cold-cache lookup that matched release 2316777 (Oversteps, which has Discogs year0because the source release lacks a verified date).Blast radius
11,824 existing prod flowsheet rows carry
release_year=0today and render literal0on the iOS playcut detail view. Every cold-cache lookup against a year-less Discogs release adds one. The Post-launch hardening project (#32) is exactly the bucket for this kind of quality leak in recently-launched live services.Proposed approach
One-character fix at each of the two call sites, plus a one-shot data backfill:
metadata.service.ts:114— changereleaseYear: artwork.release_year ?? undefined→releaseYear: artwork.release_year || undefined.||coerces0(and any other falsy) toundefined. Year-0 has no real-world meaning for music releases, so the broader-than-??coercion is safe.jobs/flowsheet-metadata-backfill/enrich.ts:152— changerelease_year: artwork.release_year ?? null→release_year: artwork.release_year || null. Same shape.One-shot UPDATE to clean up the existing 11,824 rows:
Single-statement, fast, idempotent. Run after the code fix lands so any concurrent enrichment writes can't reintroduce
0. Doesn't need a migration file because the schema doesn't change — just a one-time SQL ticked off in the PR description.Acceptance criteria
metadata.service.ts: when LML returnsrelease_year: 0, the resultingAlbumMetadataResult.releaseYearisundefined(covered by a new unit test intests/unit/services/metadata.service.test.ts)enrich.ts: when LML returnsrelease_year: 0, the row update writesNULL(covered by anenrich.test.tstest if one exists alongside, or added)release_year = 0rows toNULLis documented in the PR description and ticked-off after mergeartwork.release_year = nullis needed (existing behavior — already null-coerced); existing tests covering2001-style years stay greenConstraints
0immediately.Related
album_metadata) — adjacent to the broader metadata-quality work but a small, self-contained piece.filterSpacerGif(metadata.service.ts:101 + scripts/check-spacer-gif-callsites.sh) — same two-call-site coercion shape.