Skip to content

fix(etl): default playlist_contents add-time to block time - #554

Open
dylanjeffers wants to merge 1 commit into
mainfrom
fix/playlist-contents-time-default
Open

fix(etl): default playlist_contents add-time to block time#554
dylanjeffers wants to merge 1 commit into
mainfrom
fix/playlist-contents-time-default

Conversation

@dylanjeffers

Copy link
Copy Markdown
Contributor

Problem

Album track rows render a release date of 12/31/69. Reported again this week on https://audius.co/SIRMYKE/album/cabanne-place-vocals-single, whose single track shows 7/26/26 on its own track page.

The API returns the entry as:

{ "track_id": "NQl5AV8", "timestamp": 0, "metadata_timestamp": 1787494865 }

timestamp: 0dayjs.unix(0) → 12/31/69.

The client half of this was fixed in AudiusProject/apps#14579, which restored a || created_at fallback. This PR fixes the write path that produces the zero in the first place, so the server stops depending on every client to defend against it.

Root cause

canonicalizePlaylistEntry emitted time only when the incoming entry carried a parseable number:

if t, ok := pickJSONInt(entry, "time", "timestamp"); ok {
    out["time"] = t
}

No key is persisted when the client omits it. dbv1.PlaylistContentsItem.Time in the api is a bare float64 with no omitempty, so a missing key deserializes to the zero value and re-serializes as "timestamp": 0 — indistinguishable downstream from a genuine zero.

Its sibling a few lines away in playlist_create.go already does the right thing for the analogous field:

releaseDate := releaseDateOrDefault(params.MetadataString("release_date"), params.BlockTime)

Fix

time is now always emitted, in precedence order:

  1. the client-sent value, when positive;
  2. the time this entry already carries in the stored row;
  3. params.BlockTime.

A zero is treated as absent rather than honored — unix 0 is never a real add-time, and the paths that produce it are ones that never set the field. This also means the fix covers both possible on-disk shapes, which matters because the public API cannot distinguish them (I could not reach Metabase to confirm which one these rows actually hold).

Step 2 is what keeps the update path honest. Without it, a metadata-only edit to an affected playlist would restamp every entry with the edit's block time rather than preserving the real add-times. Step 3 is skipped when BlockTime is itself unset, where emitting nothing beats stamping year 1.

Scope

Write path only. Rows already damaged still need a backfill — this makes new and edited playlists self-healing, but does not retroactively repair cabanne-place-vocals-single. Happy to follow up with a migration; it wants its own review since it rewrites production JSONB.

Still not pinned: which client write path drops the field. Every SDK path requires timestamp (zod z.number() in both the Create and Update schemas) and all web create paths set it, so the damage is arriving from somewhere else. The floor makes that question non-urgent rather than answering it.

Test plan

  • go build ./... and go vet clean on pkg/etl
  • TestNormalizePlaylistContentsJSON extended — missing / zero / negative timestamps all floor to block time, including the exact {track_id, timestamp: 0, metadata_timestamp: N} shape from the reported album
  • New TestNormalizePlaylistContentsJSONPriorTimes — prior value carried forward, client value still wins, a prior zero is not carried forward
  • New TestNormalizePlaylistContentsJSONZeroBlockTime — no floor available, no key emitted
  • Full processors/entity_manager suite passes against a clean PG 16 (265s, all green)

🤖 Generated with Claude Code

playlist_contents entries whose client omitted the add-timestamp persisted
with no `time` key at all. The api serializes that back as `timestamp: 0`
(dbv1.PlaylistContentsItem.Time is a bare float64), and clients render unix 0
as 12/31/69 in the album track list.

canonicalizePlaylistEntry now always emits `time`: the client value when
positive, else the time the entry already carries in the stored row, else
params.BlockTime — the same floor releaseDateOrDefault already applies to
release_date a few lines away in playlist_create.go. A zero is treated as
absent rather than honored, since unix 0 is never a real add-time.

Carrying the prior stored time forward matters on the update path: without it,
any metadata-only edit to an affected playlist would restamp every entry with
the edit's block time instead of preserving the real add-time.

Fixes the write path only. Rows already damaged still need a backfill.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant