diff --git a/internal/db/dbq/acoustid.sql.go b/internal/db/dbq/acoustid.sql.go index 7aba72b8..6315d96e 100644 --- a/internal/db/dbq/acoustid.sql.go +++ b/internal/db/dbq/acoustid.sql.go @@ -120,7 +120,7 @@ type ListTracksNeedingAcoustIDLookupRow struct { DurationMs int32 } -// AcoustID recording-id lookup (M401). Migration 0069 says what each +// AcoustID recording-id lookup. Migration 0069 says what each // lookup state means. // The lookup worker's queue: present tracks with no tag MBID and no lookup // since their file last changed. A looked-up id is included once its row is @@ -257,7 +257,7 @@ type SetTrackMbidFromAcoustIDParams struct { ID pgtype.UUID } -// Never over a tag id (D4): the guard is here, not only in the worker, so a +// Never over a tag id: the guard is here, not only in the worker, so a // tag the scan read between the worker's list and this write still wins. // updated_at is left alone: it gates the scan's mtime skip, and moving it // past a file's mtime would hide a change made just before the lookup. diff --git a/internal/db/dbq/acquisition.sql.go b/internal/db/dbq/acquisition.sql.go index 2ab1b648..d5ff1b48 100644 --- a/internal/db/dbq/acquisition.sql.go +++ b/internal/db/dbq/acquisition.sql.go @@ -147,7 +147,7 @@ type ListAcquisitionServicesRow struct { UpdatedAt pgtype.Timestamptz } -// Indexers and download clients (M502 #5518). +// Indexers and download clients. // Returns no secret, only whether one is set: this feeds the settings page. func (q *Queries) ListAcquisitionServices(ctx context.Context) ([]ListAcquisitionServicesRow, error) { rows, err := q.db.Query(ctx, listAcquisitionServices) diff --git a/internal/db/dbq/admin_status.sql.go b/internal/db/dbq/admin_status.sql.go index 4738dbdc..84b47755 100644 --- a/internal/db/dbq/admin_status.sql.go +++ b/internal/db/dbq/admin_status.sql.go @@ -109,7 +109,7 @@ type ListListeningNowRow struct { AlbumTitle string } -// The admin Overview's status sections (M504): who is listening, what is +// The admin Overview's status sections: who is listening, what is // downloading, what the library holds. Each is one cheap read. // Plays in progress: open play events still inside their track's length, // with a few minutes' grace for a pause. A client that never says a play diff --git a/internal/db/dbq/admin_summary.sql.go b/internal/db/dbq/admin_summary.sql.go index d1242e2f..91860ec5 100644 --- a/internal/db/dbq/admin_summary.sql.go +++ b/internal/db/dbq/admin_summary.sql.go @@ -16,7 +16,7 @@ SELECT (SELECT count(*) FROM requests WHERE status = 'pending')::bigint AS requests, (SELECT min(requested_at) FROM requests WHERE status = 'pending')::timestamptz AS requests_since, -- A broken track is one report however it was found: a listener flagged - -- it, or it failed to play (#5606). Counted by track, as the Reports + -- it, or it failed to play. Counted by track, as the Reports -- page lists them. (SELECT count(*) FROM ( SELECT track_id FROM quarantine @@ -53,11 +53,11 @@ type AdminSummaryRow struct { MissingSince pgtype.Timestamptz } -// What the admin rail and the Overview's verdict read (#5605): each queue +// What the admin rail and the Overview's verdict read: each queue // that waits on a person, with how many and since when. One round trip, so // the rail costs one cheap read on every admin page. The "since" is the // oldest item still waiting: how long the queue has been left, which is what -// tells the operator a count of 2 is fine today and wrong after a month. +// tells an admin a count of 2 is fine today and wrong after a month. func (q *Queries) AdminSummary(ctx context.Context) (AdminSummaryRow, error) { row := q.db.QueryRow(ctx, adminSummary) var i AdminSummaryRow diff --git a/internal/db/dbq/albums.sql.go b/internal/db/dbq/albums.sql.go index fff470c2..81dc812a 100644 --- a/internal/db/dbq/albums.sql.go +++ b/internal/db/dbq/albums.sql.go @@ -15,7 +15,7 @@ const countAlbums = `-- name: CountAlbums :one SELECT COUNT(*) FROM albums ` -// M6a: total album count for the /api/library/albums envelope. +// Total album count for the /api/library/albums envelope. func (q *Queries) CountAlbums(ctx context.Context) (int64, error) { row := q.db.QueryRow(ctx, countAlbums) var count int64 @@ -59,7 +59,7 @@ type DeleteAlbumIfEmptyRow struct { ArtistID pgtype.UUID } -// M7 #372: deletes the album row only when it has no remaining tracks. +// Deletes the album row only when it has no remaining tracks. // Used after a track delete to tidy up orphaned albums. RETURNING gives // us the artist_id so the service can chain into the artist-empty check. // Returns no rows when the album still has tracks; the service treats @@ -91,12 +91,12 @@ type GetAlbumByArtistAndTitleParams struct { } // Scanner uses this for the no-mbid dedupe path: resolve-or-create. An album -// of the same name in another release group is a different album (Weezer's -// six self-titled albums shared one row until 2026-10-20), so a row whose +// of the same name in another release group is a different album (one +// artist's self-titled albums), so a row whose // group is known and differs is not returned. A row of the file's own group // comes first, then one with no group yet, then the one holding the most // tracks from the file's folder: a file tagged with no group (a live bonus -// track beside Weezer's 1994 album) belongs with its folder, not the oldest +// track beside its album) belongs with its folder, not the oldest // row, and a few strays already misfiled do not outvote the folder. func (q *Queries) GetAlbumByArtistAndTitle(ctx context.Context, arg GetAlbumByArtistAndTitleParams) (Album, error) { row := q.db.QueryRow(ctx, getAlbumByArtistAndTitle, @@ -166,7 +166,7 @@ type GetAlbumCoverageRollupRow struct { PendingNoMbid int64 } -// M7 coverage-gauge: library-wide cover-art coverage rollup for the +// Library-wide cover-art coverage rollup for the // admin gauge. Single sequential scan with FILTER aggregates — // sub-millisecond on realistic libraries. pending_no_mbid is a SUBSET // of pending; the UI surfaces it separately so operators can see how @@ -176,12 +176,9 @@ type GetAlbumCoverageRollupRow struct { // Invariant: with_art + pending + settled = total. (pending_no_mbid is // not part of the sum — it's a subset of pending, surfaced separately.) // -// The IN list below must stay in sync with the cover_art_source CHECK -// constraint — currently relaxed by migration 0020 to include 'deezer' -// and 'lastfm', and migration 0030 further relaxed it to any non-empty -// string. If a new source value is added without updating this query, -// with_art will silently undercount. Drift #556 caught the deezer + -// lastfm omission introduced by migration 0020. +// The IN list below must name every source the enrichers write (the +// cover_art_source CHECK accepts any non-empty string since migration +// 0030). A source missing here makes with_art silently undercount. func (q *Queries) GetAlbumCoverageRollup(ctx context.Context) (GetAlbumCoverageRollupRow, error) { row := q.db.QueryRow(ctx, getAlbumCoverageRollup) var i GetAlbumCoverageRollupRow @@ -236,7 +233,7 @@ SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, cre ` // Batched lookup used by /api/library/sync to hydrate upsert payloads -// (#357). Mirror of GetArtistsByIDs. +// . Mirror of GetArtistsByIDs. func (q *Queries) GetAlbumsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Album, error) { rows, err := q.db.Query(ctx, getAlbumsByIDs, dollar_1) if err != nil { @@ -277,7 +274,7 @@ SELECT release_group_mbid::text LIMIT 1 ` -// #5241: the release group the library already knows for a release id, so a +// The release group the library already knows for a release id, so a // request carrying a release id can be repaired without asking MusicBrainz. func (q *Queries) GetReleaseGroupForReleaseMbid(ctx context.Context, mbid *string) (string, error) { row := q.db.QueryRow(ctx, getReleaseGroupForReleaseMbid, mbid) @@ -400,7 +397,7 @@ type ListAlbumsAlphaWithArtistRow struct { ArtistName string } -// M6a: alpha-sorted album list joined with artist_name. Used by +// Alpha-sorted album list joined with artist_name. Used by // /api/library/albums for the wrapping-grid page. Stable id-tiebreak. func (q *Queries) ListAlbumsAlphaWithArtist(ctx context.Context, arg ListAlbumsAlphaWithArtistParams) ([]ListAlbumsAlphaWithArtistRow, error) { rows, err := q.db.Query(ctx, listAlbumsAlphaWithArtist, arg.Limit, arg.Offset) @@ -547,12 +544,10 @@ type ListAlbumsByGenreParams struct { // Album "belongs to" a genre if any of its tracks carry that genre. // Serves Subsonic getAlbumList?type=byGenre. // -// Splits tracks.genre on [;,] as of #367. It previously compared the whole -// column verbatim, so a track tagged "Rock;Pop" was unreachable from EITHER -// "Rock" or "Pop" — a Subsonic client asking for a genre silently missed -// every multi-genre track. This also aligns the endpoint with -// recommendation.sql / discover.sql, which have always split, and with the -// genre browse index that #367 adds. +// Splits tracks.genre on [;,], so a track tagged "Rock;Pop" is reachable from +// both "Rock" and "Pop"; comparing the whole column would hide every +// multi-genre track from a Subsonic client asking for a genre. The same split +// as recommendation.sql, discover.sql and the genre browse index. // // EXISTS rather than JOIN + DISTINCT ON: the lateral split emits one row per // (track, genre-fragment), so a join would multiply rows per album and lean @@ -620,7 +615,7 @@ type ListAlbumsMissingMbidWithTrackRow struct { // one of its tracks' file_path so the worker can re-read tags. LIMIT // supplied by caller for batching/progress purposes. The track read is // one still on disk; an album with none left is skipped until a scan -// finds its files again (#5139). +// finds its files again. func (q *Queries) ListAlbumsMissingMbidWithTrack(ctx context.Context, limit int32) ([]ListAlbumsMissingMbidWithTrackRow, error) { rows, err := q.db.Query(ctx, listAlbumsMissingMbidWithTrack, limit) if err != nil { @@ -738,7 +733,7 @@ type ListRecentlyAddedAlbumsWithArtistRow struct { ArtistName string } -// M6a: recently-added albums joined with artist_name + artist_id for the +// Recently-added albums joined with artist_name + artist_id for the // home-page section. created_at is the row-insert timestamp from the // scanner — the closest proxy to "added to library". Stable secondary // ordering by id keeps pagination tie-break deterministic. @@ -838,7 +833,7 @@ type SetAlbumCoverWithVersionParams struct { CoverArtSourcesVersion int32 } -// M7 cover-sources: records a successful or settled enrichment with the +// Records a successful or settled enrichment with the // current sources_version stamp. cover_art_path is NULL when source is // 'none' (no file written). Atomic; called per album per pass. func (q *Queries) SetAlbumCoverWithVersion(ctx context.Context, arg SetAlbumCoverWithVersionParams) error { @@ -863,7 +858,7 @@ type SetAlbumMbidIfNullParams struct { Mbid *string } -// M7 #379: heal MBID on existing album rows during rescan. Idempotent — +// Heal MBID on existing album rows during rescan. Idempotent — // only updates when the existing mbid is NULL. func (q *Queries) SetAlbumMbidIfNull(ctx context.Context, arg SetAlbumMbidIfNullParams) error { _, err := q.db.Exec(ctx, setAlbumMbidIfNull, arg.ID, arg.Mbid) @@ -882,7 +877,7 @@ type SetAlbumReleaseGroupMbidIfNullParams struct { ReleaseGroupMbid *string } -// #5241: heal the release-group id on an existing album row, from a rescan's +// Heal the release-group id on an existing album row, from a rescan's // tags or from the re-acquisition sweeper's MusicBrainz lookup. Only fills a // NULL, so a tag never gets overwritten by a later guess or vice versa. func (q *Queries) SetAlbumReleaseGroupMbidIfNull(ctx context.Context, arg SetAlbumReleaseGroupMbidIfNullParams) error { @@ -902,7 +897,7 @@ type SetReleaseGroupForReleaseMbidIfNullParams struct { ReleaseMbid string } -// #5241: cache a MusicBrainz-resolved release group onto the album row that +// Cache a MusicBrainz-resolved release group onto the album row that // holds the release, filling only a NULL. func (q *Queries) SetReleaseGroupForReleaseMbidIfNull(ctx context.Context, arg SetReleaseGroupForReleaseMbidIfNullParams) error { _, err := q.db.Exec(ctx, setReleaseGroupForReleaseMbidIfNull, arg.ReleaseGroupMbid, arg.ReleaseMbid) diff --git a/internal/db/dbq/artists.sql.go b/internal/db/dbq/artists.sql.go index a96aec5e..9011384a 100644 --- a/internal/db/dbq/artists.sql.go +++ b/internal/db/dbq/artists.sql.go @@ -19,7 +19,7 @@ UPDATE artists AND artist_art_source = 'none' ` -// M7 cover-sources: parallel of ClearAlbumCoverNone. Used by the +// Parallel of ClearAlbumCoverNone. Used by the // eligibility loop to flip stale 'none' rows back to NULL. func (q *Queries) ClearArtistArtNone(ctx context.Context, id pgtype.UUID) error { _, err := q.db.Exec(ctx, clearArtistArtNone, id) @@ -56,7 +56,7 @@ WHERE a.id = $1 RETURNING a.id ` -// M7 #372: deletes the artist row only when it has no remaining albums +// Deletes the artist row only when it has no remaining albums // AND no remaining tracks (defensive — tracks can in principle exist // without an album, though the scanner doesn't write that shape today). func (q *Queries) DeleteArtistIfEmpty(ctx context.Context, id pgtype.UUID) (pgtype.UUID, error) { @@ -253,7 +253,7 @@ type ListArtistsAlphaWithCoversRow struct { AlbumCount int64 } -// M6a: alpha-sorted artist list with derived cover_album_id (most-recent +// Alpha-sorted artist list with derived cover_album_id (most-recent // album whose cover_art_path is set). Replaces ListArtistsAlpha for the // /api/artists?sort=alpha branch — the new column is appended, so the // alpha branch is purely additive on the wire. album_count is computed @@ -305,7 +305,7 @@ type ListArtistsMissingArtParams struct { Limit int32 } -// M7 cover-sources: returns artist rows the artist-art enricher should +// Returns artist rows the artist-art enricher should // attempt. Eligible: artist_art_source IS NULL OR (= 'none' AND stale // artist_art_sources_version). MBID-less artists are returned but the // enricher skips them at the row.mbid == nil guard inside the chain. @@ -432,7 +432,7 @@ type SetArtistArtWithVersionParams struct { ArtistArtSourcesVersion int32 } -// M7 cover-sources: records a successful or settled artist-art +// Records a successful or settled artist-art // enrichment with the current sources_version stamp. thumb_path / // fanart_path are NULL on a 'none' result, or independently NULL if // the provider returned only one of the two image types (e.g. @@ -460,7 +460,7 @@ type SetArtistMbidIfNullParams struct { Mbid *string } -// M7 #379: heal MBID on existing artist rows during rescan. Idempotent — +// Heal MBID on existing artist rows during rescan. Idempotent — // only updates when the existing mbid is NULL, so concurrent scans don't // overwrite an existing value with a stale one. func (q *Queries) SetArtistMbidIfNull(ctx context.Context, arg SetArtistMbidIfNullParams) error { diff --git a/internal/db/dbq/audit_log.sql.go b/internal/db/dbq/audit_log.sql.go index 6c0bf6f4..873c0c4d 100644 --- a/internal/db/dbq/audit_log.sql.go +++ b/internal/db/dbq/audit_log.sql.go @@ -84,7 +84,7 @@ type ListAuditLogByActionsParams struct { } // One page of the given actions, newest first: the duplicates report's -// "resolved automatically" activity (M498) reads the resolver's merges from +// "resolved automatically" activity reads the resolver's merges from // here. func (q *Queries) ListAuditLogByActions(ctx context.Context, arg ListAuditLogByActionsParams) ([]AuditLog, error) { rows, err := q.db.Query(ctx, listAuditLogByActions, diff --git a/internal/db/dbq/browse.sql.go b/internal/db/dbq/browse.sql.go index 0477431f..88f9502d 100644 --- a/internal/db/dbq/browse.sql.go +++ b/internal/db/dbq/browse.sql.go @@ -75,7 +75,7 @@ type ListAlbumYearsWithCountRow struct { AlbumCount int64 } -// Year browse index (#367). Only albums with a release_date appear — an +// Year browse index. Only albums with a release_date appear — an // album with no date isn't "year unknown" as a browsable bucket, it's absent // from this axis, and the UI says so rather than inventing a 0 row. // Newest first: recent releases are the likelier browse target. @@ -244,8 +244,7 @@ ORDER BY trim(g.genre) // Distinct genres carried by an album's tracks, for the album detail page's // quick-jump chips. Split and trimmed identically to ListGenresWithCount, so a -// chip always leads to a page that actually contains this album — the two -// diverging is exactly the bug #367 had to fix in ListAlbumsByGenre. +// chip always leads to a page that actually contains this album. func (q *Queries) ListGenresForAlbum(ctx context.Context, albumID pgtype.UUID) ([]string, error) { rows, err := q.db.Query(ctx, listGenresForAlbum, albumID) if err != nil { @@ -314,30 +313,24 @@ type ListGenresWithCountRow struct { TrackCount int64 } -// Every query in this file filters `tracks.missing_since IS NULL` (#2523). -// A row whose file has vanished keeps its genre forever — the scanner walks the -// filesystem, so it never revisits a path that no longer exists — which is how -// pre-#2499 welded genres survived a full re-scan and kept showing in the index. -// Browsing is a way of finding something to play, so a track that cannot play +// Every query in this file filters `tracks.missing_since IS NULL`. A row whose +// file has vanished keeps its genre forever — the scanner walks the +// filesystem, so it never revisits a path that no longer exists — and +// browsing is a way of finding something to play, so a track that cannot play // should not shape it. // -// The year axis filters too (#2702). It used to join albums only, on the -// reasoning that an album is a real release even when some of its tracks are -// gone — true, but it left the two browse axes disagreeing: the genre queries -// above filter tracks.missing_since, so an album whose every file had vanished -// was already absent from genre while still listed under its year, where -// opening it found nothing playable. +// The year axis filters too, so the two axes agree: an album whose every file +// has vanished is absent from both, rather than absent from its genre and +// still listed under its year, where opening it would find nothing playable. // -// Hiding is the answer rather than showing-and-marking because browsing is how -// you go looking for something to play, and the operator's rule for that case -// is to take it out of view; the admin missing-files surface is where absence -// is reported. It also means changing the axis that was inconsistent rather -// than the one that was already right. +// Hiding rather than showing-and-marking, because browsing is how you go +// looking for something to play; the admin missing-files surface is where +// absence is reported. // // The predicate is "has at least one playable track", so it also excludes an // album with no tracks at all. That is the same answer for the same reason: // nothing to play, nothing to browse to. -// Genre browse index (#367). +// Genre browse index. // // Genres live inline on tracks.genre as a delimited string, so this splits on // the same [;,] pattern already used by recommendation.sql and discover.sql — @@ -348,14 +341,14 @@ type ListGenresWithCountRow struct { // trim() but deliberately NO lower(): trimming repairs an artifact of OUR // splitting ("Rock; Pop" yields " Pop", and showing that as a distinct genre // would be a bug), whereas case is what the tag actually says. Raw ID3 is -// exposed as-is for v1, so "Rock" and "rock" appear as separate rows. +// exposed as-is, so "Rock" and "rock" appear as separate rows. // // COUNT(DISTINCT) because a sloppy tag like "Rock;Rock" would otherwise // inflate its own row. // // Ordered by count first: raw ID3 data has a long tail of one-off junk tags, -// so alphabetical would bury the handful of genres an operator actually has a -// library's worth of. Name breaks ties for a stable order. +// so alphabetical would bury the handful of genres a library actually has +// a lot of. Name breaks ties for a stable order. // Ordered by the expression, not the output alias: `ORDER BY genre` is // ambiguous between the alias and tracks.genre, and sqlc rejects it. func (q *Queries) ListGenresWithCount(ctx context.Context) ([]ListGenresWithCountRow, error) { diff --git a/internal/db/dbq/candidate_artist_tags.sql.go b/internal/db/dbq/candidate_artist_tags.sql.go index a733e4a2..12a9ceba 100644 --- a/internal/db/dbq/candidate_artist_tags.sql.go +++ b/internal/db/dbq/candidate_artist_tags.sql.go @@ -110,7 +110,7 @@ SELECT candidate_mbid, tag, weight WHERE candidate_mbid = ANY($1::text[]) ` -// Cached tags for a set of candidates, for slice 6's taste-overlap ranking. +// Cached tags for a set of candidates, for the taste-overlap ranking. // One row per (candidate, tag). func (q *Queries) ListCandidateArtistTagsForMbids(ctx context.Context, dollar_1 []string) ([]CandidateArtistTag, error) { rows, err := q.db.Query(ctx, listCandidateArtistTagsForMbids, dollar_1) @@ -160,7 +160,7 @@ type ListCandidateArtistsMissingTagsRow struct { TotalScore float64 } -// Folksonomy tags for out-of-library Discover candidates (#2376). Parallel to +// Folksonomy tags for out-of-library Discover candidates. Parallel to // track_tags.sql, but keyed by MBID because the artist has no local row. See // 0050_candidate_artist_tags.up.sql for why the bookkeeping is its own table. // Candidates eligible for tag enrichment: never processed (no state row) or diff --git a/internal/db/dbq/catalogue.sql.go b/internal/db/dbq/catalogue.sql.go index d18a4261..018c0727 100644 --- a/internal/db/dbq/catalogue.sql.go +++ b/internal/db/dbq/catalogue.sql.go @@ -352,7 +352,7 @@ type UpsertMBArtistParams struct { Country string } -// The release catalogue read from MusicBrainz (M502 #5519). +// The release catalogue read from MusicBrainz. func (q *Queries) UpsertMBArtist(ctx context.Context, arg UpsertMBArtistParams) error { _, err := q.db.Exec(ctx, upsertMBArtist, arg.Mbid, diff --git a/internal/db/dbq/coplay.sql.go b/internal/db/dbq/coplay.sql.go index f19fef6a..381a18cb 100644 --- a/internal/db/dbq/coplay.sql.go +++ b/internal/db/dbq/coplay.sql.go @@ -14,7 +14,7 @@ const deleteArtistCoplayEdges = `-- name: DeleteArtistCoplayEdges :exec DELETE FROM artist_similarity WHERE source = 'user_cooccurrence' ` -// Household co-play similarity (#1533, milestone #160 Opt 5). Minstrel is a +// Household co-play similarity. Minstrel is a // single shared-library, multi-user server (no per-user library ACL), so the // "household" is the whole instance's user set. These queries recompute // artist–artist co-occurrence edges from play_events and store them in diff --git a/internal/db/dbq/coverart_settings.sql.go b/internal/db/dbq/coverart_settings.sql.go index c87455b4..c6c32aad 100644 --- a/internal/db/dbq/coverart_settings.sql.go +++ b/internal/db/dbq/coverart_settings.sql.go @@ -77,7 +77,7 @@ SELECT provider_id, enabled, api_key, display_order, created_at, updated_at ORDER BY display_order, provider_id ` -// M7 cover-sources: queries for the cover_art_provider_settings table +// Queries for the cover_art_provider_settings table // and the cover_art_sources_meta singleton. The SettingsService // consumes these. // Returns all rows from cover_art_provider_settings. Used by the @@ -155,7 +155,7 @@ type UpsertProviderSettingsParams struct { // INSERT a default row for a newly-registered provider, or update an // existing row's display_order on boot reconciliation. Does NOT -// overwrite enabled / api_key on conflict — the operator's settings +// overwrite enabled / api_key on conflict — an admin's settings // persist across restarts. func (q *Queries) UpsertProviderSettings(ctx context.Context, arg UpsertProviderSettingsParams) error { _, err := q.db.Exec(ctx, upsertProviderSettings, arg.ProviderID, arg.Enabled, arg.DisplayOrder) diff --git a/internal/db/dbq/covers.sql.go b/internal/db/dbq/covers.sql.go index 4ea11961..ee99d700 100644 --- a/internal/db/dbq/covers.sql.go +++ b/internal/db/dbq/covers.sql.go @@ -31,7 +31,7 @@ UPDATE albums WHERE id = $1 AND cover_art_source = 'none' ` -// M7 #380: when MBID backfill heals an album, clear its 'none' cover_art_source +// When MBID backfill heals an album, clear its 'none' cover_art_source // back to NULL so the enricher's next batch retries the MBCAA fetch. // Idempotent — only updates rows currently stamped 'none'. func (q *Queries) ClearAlbumCoverNone(ctx context.Context, id pgtype.UUID) error { @@ -128,8 +128,8 @@ type ListAlbumsMissingCoverParams struct { Limit int32 } -// M7 #353: album cover enrichment queries. -// M7 cover-sources: returns rows the enricher should attempt. Eligible: +// Album cover enrichment queries. +// Returns rows the enricher should attempt. Eligible: // cover_art_source IS NULL (never tried), OR cover_art_source = 'none' // AND cover_art_sources_version != current (settled under an older // source set; recheck against the current chain). LIMIT supplied by diff --git a/internal/db/dbq/discover.sql.go b/internal/db/dbq/discover.sql.go index 3c87b264..87744461 100644 --- a/internal/db/dbq/discover.sql.go +++ b/internal/db/dbq/discover.sql.go @@ -15,7 +15,7 @@ const listCrossUserLikedTracksForDiscover = `-- name: ListCrossUserLikedTracksFo SELECT t.id, t.album_id, t.artist_id FROM general_likes gl JOIN tracks t ON t.id = gl.track_id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND gl.user_id != $1 AND NOT EXISTS ( SELECT 1 FROM play_events pe @@ -55,8 +55,8 @@ type ListCrossUserLikedTracksForDiscoverRow struct { // // GROUP BY (not SELECT DISTINCT) so the md5 ORDER BY expression need // not appear in the select list — Postgres rejects DISTINCT + ORDER BY -// by-expression at plan time (SQLSTATE 42P10), which previously caused -// the entire Discover build to fail silently. +// by-expression at plan time (SQLSTATE 42P10), which would fail the +// entire Discover build. func (q *Queries) ListCrossUserLikedTracksForDiscover(ctx context.Context, arg ListCrossUserLikedTracksForDiscoverParams) ([]ListCrossUserLikedTracksForDiscoverRow, error) { rows, err := q.db.Query(ctx, listCrossUserLikedTracksForDiscover, arg.UserID, arg.Column2) if err != nil { @@ -96,7 +96,7 @@ dormant_artists AS ( SELECT t.id, t.album_id, t.artist_id FROM tracks t JOIN dormant_artists da ON da.id = t.artist_id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = $1 @@ -161,7 +161,7 @@ func (q *Queries) ListDormantArtistTracksForDiscover(ctx context.Context, arg Li const listRandomUnheardTracksForDiscover = `-- name: ListRandomUnheardTracksForDiscover :many SELECT t.id, t.album_id, t.artist_id FROM tracks t - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = $1 @@ -223,7 +223,7 @@ WITH scored AS ( FROM tracks t JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g_split(g) ON true JOIN taste_profile_tags nt ON nt.user_id = $3 AND trim(g_split.g) = nt.tag - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND nt.weight > 0 AND trim(g_split.g) <> '' AND NOT EXISTS ( @@ -274,7 +274,7 @@ type ListTasteUnheardTracksForDiscoverRow struct { } // Taste-targeted novelty: unheard tracks whose genres overlap the user's -// taste-profile tags (taste_profile_tags, #796), ranked by summed tag +// taste-profile tags (taste_profile_tags), ranked by summed tag // weight — "new to you, but your vibe" rather than the crude random arm. // Genres live inline on tracks.genre as a delimited string, split the // same way the radio tag_overlap arm does (regexp_split_to_table on @@ -282,13 +282,13 @@ type ListTasteUnheardTracksForDiscoverRow struct { // when the user has no taste tags yet (cold start), so the caller // redistributes its slots to the other buckets. Stamped 'taste_unheard'. // -// The per-album and per-artist caps apply BEFORE the LIMIT (#5356). Summed -// weight rewards a track for carrying many of the user's tags, so a few -// artists whose every track is tagged with the whole profile take every row -// of a plain LIMIT; the caller's caps then left 6 of 120 on the deploy, and -// the best-performing arm handed its slots to the others. Ranking within -// album, then within artist over what the album cap kept, is the same walk -// capByAlbumAndArtist makes, so the caller's caps keep everything here. +// The per-album and per-artist caps apply BEFORE the LIMIT. Summed weight +// rewards a track for carrying many of the user's tags, so a few artists +// whose every track is tagged with the whole profile would take every row of +// a plain LIMIT, the caller's caps would then leave a handful, and the arm +// would hand its slots to the others. Ranking within album, then within +// artist over what the album cap kept, is the same walk capByAlbumAndArtist +// makes, so the caller's caps keep everything here. func (q *Queries) ListTasteUnheardTracksForDiscover(ctx context.Context, arg ListTasteUnheardTracksForDiscoverParams) ([]ListTasteUnheardTracksForDiscoverRow, error) { rows, err := q.db.Query(ctx, listTasteUnheardTracksForDiscover, arg.MaxPerArtist, diff --git a/internal/db/dbq/duplicates.sql.go b/internal/db/dbq/duplicates.sql.go index 9e7b70d2..bed92a34 100644 --- a/internal/db/dbq/duplicates.sql.go +++ b/internal/db/dbq/duplicates.sql.go @@ -55,9 +55,9 @@ SELECT count(*)::bigint END ` -// CountPendingDuplicateGroups narrowed to one view of the report (M498): +// CountPendingDuplicateGroups narrowed to one view of the report: // -// review what the operator still has to decide: every class but +// review what an admin still has to decide: every class but // cross_release, and groups not yet classified // cross_release the same song on different releases, which is kept, not merged func (q *Queries) CountPendingDuplicateGroupsInView(ctx context.Context, view string) (int64, error) { @@ -205,7 +205,7 @@ type ListDismissedDuplicateMemberSetsRow struct { TrackIds []pgtype.UUID } -// What the operator has already said are not duplicates. A new proposal whose +// What an admin has already said are not duplicates. A new proposal whose // every member sat together in one of these is not proposed again. func (q *Queries) ListDismissedDuplicateMemberSets(ctx context.Context) ([]ListDismissedDuplicateMemberSetsRow, error) { rows, err := q.db.Query(ctx, listDismissedDuplicateMemberSets) @@ -236,7 +236,7 @@ SELECT t.id, t.duration_ms, f.audio_stream_sha256, f.chromaprint AND f.chromaprint IS NOT NULL -- Only chromaprints taken at the current length: prints at two lengths are not -- comparable, and after a length change the backfill is still re-deriving the - -- rest (#3913). + -- rest. AND f.chromaprint_length_sec = $2 AND (t.duration_ms, t.id) > ($3::integer, $4::uuid) ORDER BY t.duration_ms, t.id @@ -356,19 +356,19 @@ type ListDuplicateGroupsForResolveRow struct { AcoustidRecording *string } -// Every pending group's present copies, for the resolver (M498 #5435): what it +// Every pending group's present copies, for the resolver: what it // needs to classify the group and to choose the copy to keep. One row per copy, // grouped by group. position_clash is true when another present track on the // same album claims the same disc and track number: tags that do not fit the // album, which the survivor rule weighs against a copy. source_verdict says -// whether the copy is a video rip ('suspect') or one the operator kept ('fine'). +// whether the copy is a video rip ('suspect') or one an admin kept ('fine'). // prior_class is the class the last pass gave the group, NULL for a group no // pass has seen. release_group_mbid is the album's MusicBrainz group, from its // tags or the library listing's match; ” when neither names one. // acoustid_state and acoustid_recording are what AcoustID said of the file, -// when it was looked up (#6169: which song one recording cut to several slots -// is). group_id narrows it to one group, for the operator settling it (#6184); -// NULL reads them all. +// when it was looked up: which song a recording cut to several slots is. +// group_id narrows it to one group, for an admin settling it; NULL reads them +// all. func (q *Queries) ListDuplicateGroupsForResolve(ctx context.Context, groupID pgtype.UUID) ([]ListDuplicateGroupsForResolveRow, error) { rows, err := q.db.Query(ctx, listDuplicateGroupsForResolve, groupID) if err != nil { @@ -533,7 +533,7 @@ type ListPendingDuplicateGroupMembersRow struct { // One page of proposals in one view (see CountPendingDuplicateGroupsInView), // newest first, flattened to one row per member so the handler folds them // without a query per group. What each copy carries — likes and plays from -// every user — is here because it is what the operator weighs when deciding +// every user — is here because it is what an admin weighs when deciding // which copy to keep. func (q *Queries) ListPendingDuplicateGroupMembers(ctx context.Context, arg ListPendingDuplicateGroupMembersParams) ([]ListPendingDuplicateGroupMembersRow, error) { rows, err := q.db.Query(ctx, listPendingDuplicateGroupMembers, arg.View, arg.PageOffset, arg.PageLimit) diff --git a/internal/db/dbq/events.sql.go b/internal/db/dbq/events.sql.go index c9a1e290..5b1ba57a 100644 --- a/internal/db/dbq/events.sql.go +++ b/internal/db/dbq/events.sql.go @@ -131,7 +131,7 @@ type GetSystemPickKindForTrackParams struct { // Looks a track up in the user's CURRENT snapshot of the given system // variant and returns its pick_kind. Used at play-ingestion time to // freeze provenance onto the play_event — snapshots rebuild daily, so -// attribution can't be reconstructed at read time (#1249/#1270). No +// attribution can't be reconstructed at read time. No // row = track not in today's snapshot (caller stores NULL); a row with // NULL pick_kind = the variant doesn't stamp (yet). songs_like_artist // is non-singleton (up to 3 mixes/user); a track in two of them takes @@ -166,7 +166,7 @@ type InsertPlayEventParams struct { // pick_kind is non-NULL only for system-playlist plays whose track was // found (with a stamped kind) in the user's live snapshot for that -// variant at ingestion time (#1249, generalized in #1270). +// variant at ingestion time. func (q *Queries) InsertPlayEvent(ctx context.Context, arg InsertPlayEventParams) (PlayEvent, error) { row := q.db.QueryRow(ctx, insertPlayEvent, arg.UserID, diff --git a/internal/db/dbq/fingerprints.sql.go b/internal/db/dbq/fingerprints.sql.go index f92f0cb2..581daed7 100644 --- a/internal/db/dbq/fingerprints.sql.go +++ b/internal/db/dbq/fingerprints.sql.go @@ -81,7 +81,7 @@ SELECT t.id, t.file_path LEFT JOIN track_fingerprints f ON f.track_id = t.id WHERE t.missing_since IS NULL -- A row taken at another length is as stale as one from an older method: - -- chromaprints at two lengths cannot be compared (#3913). + -- chromaprints at two lengths cannot be compared. AND (f.track_id IS NULL OR f.fingerprint_version < $1 OR f.chromaprint_length_sec <> $2) @@ -102,7 +102,7 @@ type ListTracksNeedingFingerprintRow struct { FilePath string } -// The backfill's work queue (#3908): tracks with no fingerprint, or one derived +// The backfill's work queue: tracks with no fingerprint, or one derived // by an older method. Keyset-paged on id so a pass visits each track at most // once. That cursor is load-bearing: an inconclusive attempt writes no row, so // without it a file that keeps timing out would be listed again straight away @@ -157,7 +157,7 @@ type UpsertTrackFingerprintParams struct { } // Written whenever a track's fingerprint is derived: by the scan when a file is -// new or its bytes changed, and by the backfill (#3908) for rows derived by an +// new or its bytes changed, and by the backfill for rows derived by an // older method. Replaces the row wholesale — a fingerprint of the old bytes has // no standing once the file has changed. func (q *Queries) UpsertTrackFingerprint(ctx context.Context, arg UpsertTrackFingerprintParams) error { diff --git a/internal/db/dbq/gc.sql.go b/internal/db/dbq/gc.sql.go index 957a6ef5..2cc7f360 100644 --- a/internal/db/dbq/gc.sql.go +++ b/internal/db/dbq/gc.sql.go @@ -20,10 +20,9 @@ UPDATE play_sessions ) ` -// #565: play_sessions.ended_at was added but never populated by any -// writer. Close sessions whose last_event_at is older than 6h — -// treating that as "user moved on" the same way audio_service does -// after grace periods. Sessions with NO events (track_count = 0) +// play_sessions.ended_at has no other writer. Close sessions whose +// last_event_at is older than 6h — treating that as "user moved on" the +// same way audio_service does after grace periods. Sessions with NO events (track_count = 0) // older than 1h are also closed (stale handshakes from clients that // never recorded a play). ended_at is set to last_event_at so the // session's duration reads naturally. @@ -46,12 +45,11 @@ UPDATE play_events AND started_at < now() - INTERVAL '24 hours' ` -// Background garbage-collector / lifecycle queries. All five address -// drift findings from the 2026-06-02 audit (Scribe parent #552): -// #565 #566 #567 #574 #575. Sweeper runs on a long tick (default 1h) +// Background garbage-collector / lifecycle queries: rows nothing else closes +// or removes. Sweeper runs on a long tick (default 1h) // so per-query cost is amortised; each is idempotent (re-running on // already-closed/-deleted rows is a no-op). -// #566: play_events rows opened more than 24h ago that never got a +// Play_events rows opened more than 24h ago that never got a // play_ended. Client crashed mid-track, network dropped, etc. We // synthesize ended_at = started_at + duration_played_ms when present, // otherwise leave duration_played_ms null and stamp ended_at = now() @@ -72,7 +70,7 @@ DELETE FROM password_resets OR (used_at IS NULL AND expires_at < now() - INTERVAL '1 hour') ` -// #575: password_resets accumulates expired + used rows forever. The +// Password_resets accumulates expired + used rows forever. The // validation path already rejects them; this just keeps the table // from growing unbounded. Used rows are kept for 7 days for audit; // unused expired rows go immediately. @@ -106,7 +104,7 @@ DELETE FROM scrobble_queue AND enqueued_at < now() - INTERVAL '14 days' ` -// #567: scrobble_queue rows that have been in status='failed' for +// Scrobble_queue rows that have been in status='failed' for // more than 14 days. The worker stops retrying after maxAttempts; // failed rows accumulate forever otherwise. CASCADE from play_events // already drops the row when the underlying event is deleted, so this @@ -127,7 +125,7 @@ UPDATE system_playlist_runs AND last_run_at < now() - INTERVAL '10 minutes' ` -// #574: system_playlist_runs.in_flight = true can wedge on a +// System_playlist_runs.in_flight = true can wedge on a // goroutine panic between SET in_flight=true and SET in_flight=false. // The duplicate-prevention check refuses to start a fresh regen while // in_flight, so a stuck row blocks all future regens for that user. diff --git a/internal/db/dbq/history.sql.go b/internal/db/dbq/history.sql.go index a07cc104..4edbbed0 100644 --- a/internal/db/dbq/history.sql.go +++ b/internal/db/dbq/history.sql.go @@ -45,7 +45,7 @@ type ListUserHistoryRow struct { ArtistName string } -// M7 #365: track-level listening history. Returns one row per +// Track-level listening history. Returns one row per // play_events entry, newest first, joined with tracks/albums/artists // so the SPA can render a TrackRef without follow-up fetches. Filters // out skipped events and tracks the user has quarantined (per-user diff --git a/internal/db/dbq/imports.sql.go b/internal/db/dbq/imports.sql.go index 56f78285..b7ce6aec 100644 --- a/internal/db/dbq/imports.sql.go +++ b/internal/db/dbq/imports.sql.go @@ -297,7 +297,7 @@ type ListGrabsRow struct { // The Wanted view's Activity tab: downloads waiting on a person first, then // the rest still running, then the ended ones newest first. Each carries -// what is wrong with it now, if anything (#5523). +// what is wrong with it now, if anything. func (q *Queries) ListGrabs(ctx context.Context, arg ListGrabsParams) ([]ListGrabsRow, error) { rows, err := q.db.Query(ctx, listGrabs, arg.Skip, arg.MaxRows) if err != nil { @@ -366,7 +366,7 @@ type ListImportedGrabsHoldingRow struct { // The downloads Minstrel imported into the folder holding path: the releases a // file there came from, to block when the files proved not to be the album -// (#6169). A file imported before Minstrel downloaded anything has none. +// . A file imported before Minstrel downloaded anything has none. func (q *Queries) ListImportedGrabsHolding(ctx context.Context, path string) ([]ListImportedGrabsHoldingRow, error) { rows, err := q.db.Query(ctx, listImportedGrabsHolding, path) if err != nil { @@ -441,7 +441,7 @@ type ListLiveGrabsRow struct { WantAlbumID pgtype.UUID } -// Import with verification (M502 #5522). +// Import with verification. // Every download still running or waiting on its import, oldest first. func (q *Queries) ListLiveGrabs(ctx context.Context) ([]ListLiveGrabsRow, error) { rows, err := q.db.Query(ctx, listLiveGrabs) @@ -574,7 +574,7 @@ type ListUpgradeTracksRow struct { SourceVerdict *string } -// The tracks an upgrade may replace (#5524): the present tracks of the album +// The tracks an upgrade may replace: the present tracks of the album // the want holds. func (q *Queries) ListUpgradeTracks(ctx context.Context, albumID pgtype.UUID) ([]ListUpgradeTracksRow, error) { rows, err := q.db.Query(ctx, listUpgradeTracks, albumID) @@ -694,7 +694,7 @@ UPDATE grabs import_forced = false, imported_at = CASE WHEN $1::text = 'imported' THEN now() ELSE imported_at END, -- The files were copied, so an imported download goes on sharing - -- until the lifecycle removes it (#5523). + -- until the lifecycle removes it. seeding = $1::text = 'imported', imported_files = $4, destination = $5, diff --git a/internal/db/dbq/library_changes.sql.go b/internal/db/dbq/library_changes.sql.go index 0eeda6c4..1d38222d 100644 --- a/internal/db/dbq/library_changes.sql.go +++ b/internal/db/dbq/library_changes.sql.go @@ -116,11 +116,10 @@ type InsertLibraryChangesParams struct { Op string } -// Batch form, for a mutation that touches many rows at once (#2704: the scan's -// reconcile pass can mark up to a quarter of a library missing in one go). -// Every caller before this one changed a single entity, so per-row inserts -// were the right shape; a loop here would be thousands of round-trips inside -// a scan that is already the slow path. +// Batch form, for a mutation that touches many rows at once (the scan's +// reconcile pass can mark up to a quarter of a library missing in one go). A +// loop of single inserts would be thousands of round-trips inside a scan that +// is already the slow path. func (q *Queries) InsertLibraryChanges(ctx context.Context, arg InsertLibraryChangesParams) error { _, err := q.db.Exec(ctx, insertLibraryChanges, arg.EntityType, arg.EntityIds, arg.Op) return err diff --git a/internal/db/dbq/library_settings.sql.go b/internal/db/dbq/library_settings.sql.go index 40006fd8..7287bdb4 100644 --- a/internal/db/dbq/library_settings.sql.go +++ b/internal/db/dbq/library_settings.sql.go @@ -101,7 +101,7 @@ type RerootTrackPathsParams struct { OldRoot string } -// A library folder that moved (#6129): every path under old_root is rewritten +// A library folder that moved: every path under old_root is rewritten // to the same place under new_root, so the tracks keep their ids and with them // their plays, likes and playlists. updated_at is left alone on purpose: the // scan compares it with each file's mtime, and the files have not changed. diff --git a/internal/db/dbq/lifecycle.sql.go b/internal/db/dbq/lifecycle.sql.go index 36771595..09aa6f5a 100644 --- a/internal/db/dbq/lifecycle.sql.go +++ b/internal/db/dbq/lifecycle.sql.go @@ -22,7 +22,7 @@ type CountGoneGrabsParams struct { } // How many of a release's grabs ended gone since a time: a release that keeps -// leaving its client is held back rather than taken on every pass (#6176). +// leaving its client is held back rather than taken on every pass. func (q *Queries) CountGoneGrabs(ctx context.Context, arg CountGoneGrabsParams) (int64, error) { row := q.db.QueryRow(ctx, countGoneGrabs, arg.InfoHash, arg.Since) var count int64 @@ -289,7 +289,7 @@ type RaiseInterventionParams struct { Detail string } -// The download lifecycle (M502 #5523): the intervention ledger, giving a +// The download lifecycle: the intervention ledger, giving a // download up, and seeding after import. // A condition seen, or seen again: a second pass lands on the open row, so // a download stuck for a week is one row that says how long. The name only diff --git a/internal/db/dbq/likes.sql.go b/internal/db/dbq/likes.sql.go index 311b1a52..5c0137ec 100644 --- a/internal/db/dbq/likes.sql.go +++ b/internal/db/dbq/likes.sql.go @@ -93,7 +93,7 @@ type LikeTrackParams struct { TrackID pgtype.UUID } -// A like is on the song (M498 #5438): every copy of it sharing the track's +// A like is on the song: every copy of it sharing the track's // song_key is liked. Returns the tracks newly liked, for the sync log; empty // when the song was already liked. func (q *Queries) LikeTrack(ctx context.Context, arg LikeTrackParams) ([]pgtype.UUID, error) { diff --git a/internal/db/dbq/loudness.sql.go b/internal/db/dbq/loudness.sql.go index c7d1df1d..9dfef31b 100644 --- a/internal/db/dbq/loudness.sql.go +++ b/internal/db/dbq/loudness.sql.go @@ -164,7 +164,7 @@ type GetReplayGainByTrackIDsRow struct { } // Track and album loudness for a set of tracks, for every surface that hands -// gains to a client (#4997). A measurement from an older analysis version is +// gains to a client. A measurement from an older analysis version is // still delivered: it is a better gain than none until the backfill redoes it. func (q *Queries) GetReplayGainByTrackIDs(ctx context.Context, ids []pgtype.UUID) ([]GetReplayGainByTrackIDsRow, error) { rows, err := q.db.Query(ctx, getReplayGainByTrackIDs, ids) @@ -280,7 +280,7 @@ type ListAlbumsNeedingLoudnessRow struct { Digest string } -// The album pass's work queue (#4996): albums whose present tracks or their +// The album pass's work queue: albums whose present tracks or their // measurements have changed since album loudness was last computed, or that // never had it. The digest is over every present track's id and the // measurement it holds at the current version ('-' for none), so a track @@ -484,7 +484,7 @@ type UpsertTrackLoudnessParams struct { AnalysisVersion int16 } -// Written when the backfill measures a track (#4995). Replaces the row +// Written when the backfill measures a track. Replaces the row // wholesale: a measurement of the old bytes has no standing once the file has // changed. func (q *Queries) UpsertTrackLoudness(ctx context.Context, arg UpsertTrackLoudnessParams) error { diff --git a/internal/db/dbq/merge.sql.go b/internal/db/dbq/merge.sql.go index 666795eb..eee18b55 100644 --- a/internal/db/dbq/merge.sql.go +++ b/internal/db/dbq/merge.sql.go @@ -69,7 +69,7 @@ type LockDuplicateGroupForMergeRow struct { Status string } -// Duplicate merge (Scribe #3911). Every statement here runs inside the one +// Duplicate merge. Every statement here runs inside the one // transaction library.MergeDuplicateGroup opens, after the removed copy's file // is already gone. The loser's own track row is deleted last with DeleteTrack; // what these do is move everything it carries onto the survivor first, so that diff --git a/internal/db/dbq/missing_pairs.sql.go b/internal/db/dbq/missing_pairs.sql.go index 3730c5f5..408f1e43 100644 --- a/internal/db/dbq/missing_pairs.sql.go +++ b/internal/db/dbq/missing_pairs.sql.go @@ -63,27 +63,27 @@ type ListMissingTrackPairsRow struct { PresentCandidates int32 } -// Missing-pair pass (M485). A track marked missing whose replacement is already +// Missing-pair pass. A track marked missing whose replacement is already // on disk under another row — most often a quality upgrade, mp3 → flac at a new // path — is folded into that replacement, so its history moves across and // re-acquisition stops chasing music the library already has. // -// Move adoption (#2528) cannot catch these: it links a new file to a missing row +// Move adoption cannot catch these: it links a new file to a missing row // at the moment the file is first inserted, and the replacement arrives as a // re-encode with no recording MBID, so it has nothing to match on. AcoustID may // give it an MBID later; by then both rows exist and only a fold can join them. // Every candidate (missing, present) pair, with how many candidates each side -// has. Two rules, measured on the operator's library on 2026-10-07 (#5274): +// has. Two rules: // // mbid the same recording MBID, within the album group (the album row, or -// albums sharing a release group). 142 of 359 pairs. -// title the same album row and the same title, ignoring case. 213 more. The +// albums sharing a release group). +// title the same album row and the same title, ignoring case. The // replacements are often not the original file — another edition, // re-encoded, untagged — so the metadata is what is left to go on. // -// Deliberately NOT gated on duration or track position. On the 142 pairs known -// to be one recording, 18% differed by over 2s (old mp3 durations are poor -// estimates), and the poorly tagged set is exactly where numbering is broken. +// Deliberately NOT gated on duration or track position. Pairs known to be one +// recording often differ by over 2s (old mp3 durations are poor estimates), +// and the poorly tagged set is exactly where numbering is broken. // // A pair whose MBIDs are both set and differ is not a title match: the tags say // they are different recordings. The caller folds a pair only when each side has diff --git a/internal/db/dbq/notifications.sql.go b/internal/db/dbq/notifications.sql.go index cc113e6f..4ea5343d 100644 --- a/internal/db/dbq/notifications.sql.go +++ b/internal/db/dbq/notifications.sql.go @@ -54,7 +54,7 @@ type InsertNotificationParams struct { EmailWanted bool } -// M489 notifications inbox (#726). Rows are written only through +// Notifications inbox. Rows are written only through // internal/notifications.Notifier, which decides recipients and honours each // recipient's inbox preference before it reaches these. // email_wanted is the recipient's email channel for this kind as of now. A @@ -122,7 +122,7 @@ type ListEmailPendingNotificationsRow struct { ReadAsOf pgtype.Timestamptz } -// Email digest (#5346) ------------------------------------------------------ +// Email digest ------------------------------------------------------ // Every unread, un-emailed row of a user who has an address, oldest first. // Read rows are never selected: the user has seen them, so they are not news. // read_as_of is the database's clock at the read, handed back to @@ -348,7 +348,7 @@ type MarkAllNotificationsReadParams struct { // all read" queued offline and replayed later must not mark notices that // arrived in between, which the user never saw. A coalesced row that grew // since then is marked read with it: it arrived before they asked, and its -// growth is a quiet update to a notice they had seen (M542). +// growth is a quiet update to a notice they had seen. func (q *Queries) MarkAllNotificationsRead(ctx context.Context, arg MarkAllNotificationsReadParams) (int64, error) { result, err := q.db.Exec(ctx, markAllNotificationsRead, arg.UserID, arg.UpTo) if err != nil { @@ -484,10 +484,10 @@ type UpsertCoalescedNotificationParams struct { // ten. Every coalesced kind's event is "N more happened", never a restated // total, so the row always says what is new since it was last read. // -// created_at and emailed_at are left alone (M542): an update changes what the -// notice says, not when it arrived. Moving created_at made the phone announce -// it again and clearing emailed_at sent it again, so a notice the hourly -// duplicate pass kept growing alerted every hour. +// created_at and emailed_at are left alone: an update changes what the +// notice says, not when it arrived. Moving created_at would make the phone +// announce it again and clearing emailed_at would send it again, so a notice +// that keeps growing would alert on every update. func (q *Queries) UpsertCoalescedNotification(ctx context.Context, arg UpsertCoalescedNotificationParams) (pgtype.UUID, error) { row := q.db.QueryRow(ctx, upsertCoalescedNotification, arg.UserID, diff --git a/internal/db/dbq/playlists.sql.go b/internal/db/dbq/playlists.sql.go index ce928088..78c134bd 100644 --- a/internal/db/dbq/playlists.sql.go +++ b/internal/db/dbq/playlists.sql.go @@ -38,7 +38,7 @@ type AppendPlaylistTrackParams struct { // Inserts at the next available position. Snapshot fields are copied // from the tracks/albums/artists join at insert time. tracks.duration_ms // is converted to seconds for the snapshot. pick_kind is NULL for manual -// playlists; the For You builder stamps 'taste'/'fresh' (#1249). +// playlists; the For You builder stamps 'taste'/'fresh'. func (q *Queries) AppendPlaylistTrack(ctx context.Context, arg AppendPlaylistTrackParams) (PlaylistTrack, error) { row := q.db.QueryRow(ctx, appendPlaylistTrack, arg.PlaylistID, arg.PickKind, arg.TrackID) var i PlaylistTrack @@ -182,7 +182,7 @@ SELECT id, user_id, name, description, is_public, cover_path, track_count, durat ` // Batched lookup used by /api/library/sync to hydrate upsert payloads -// (#357). Mirror of GetArtistsByIDs. +// . Mirror of GetArtistsByIDs. func (q *Queries) GetPlaylistsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Playlist, error) { rows, err := q.db.Query(ctx, getPlaylistsByIDs, dollar_1) if err != nil { @@ -295,7 +295,7 @@ type ListPlaylistTracksRow struct { // (track was removed from the library). The denormalized snapshot fields // on playlist_tracks remain authoritative for title/artist/album text. // -// Deliberately NOT filtered on missing_since (#2527), unlike every browse / +// Deliberately NOT filtered on missing_since, unlike every browse / // discover / mix query. A playlist entry is something the user put here on // purpose, so a missing file stays in the list and renders as a dead row // rather than silently vanishing; missing_since rides along so the service diff --git a/internal/db/dbq/reacquisition.sql.go b/internal/db/dbq/reacquisition.sql.go index a8078c59..f82244f6 100644 --- a/internal/db/dbq/reacquisition.sql.go +++ b/internal/db/dbq/reacquisition.sql.go @@ -35,9 +35,9 @@ const getReacquisitionSettings = `-- name: GetReacquisitionSettings :one SELECT id, enabled, grace_hours, max_per_pass FROM reacquisition_settings WHERE id = true ` -// Re-acquisition of missing files (milestone #290, reworked for M502 #5520). +// Re-acquisition of missing files. // The unit is the album: an album whose files went missing becomes a want for -// its release group, and the search schedule (#5521) looks for it. +// its release group, and the search schedule looks for it. func (q *Queries) GetReacquisitionSettings(ctx context.Context) (ReacquisitionSetting, error) { row := q.db.QueryRow(ctx, getReacquisitionSettings) var i ReacquisitionSetting @@ -140,17 +140,17 @@ type ListAlbumsDueReacquisitionRow struct { // // - it still has at least one track whose file has been missing longer than // the grace window. Measured on missing_since, which reconcile never -// re-stamps (#2523), so it is a genuine "gone since" clock; +// re-stamps, so it is a genuine "gone since" clock; // - MusicBrainz can name it: the artist has an MBID, and the album a // release MBID or a release group. The release MBID is only ever used to -// find the group, and 238 albums matched to a group carried no release -// id, so their losses were never wanted back (#6167). Albums failing this -// are counted separately (CountAlbumsMissingWithoutMbid); -// - it is on the wanted list (#5567): an album taken off the list, or +// find the group, and an album matched to a group may carry no release +// id. Albums failing this are counted separately +// (CountAlbumsMissingWithoutMbid); +// - it is on the wanted list: an album taken off the list, or // dropped into the library by hand, is not fetched back; // - no want already covers it: none open, and none held since its files // went missing. A want held after the loss means the library has a full -// copy despite the missing rows (#5245), so asking again would loop. +// copy despite the missing rows, so asking again would loop. func (q *Queries) ListAlbumsDueReacquisition(ctx context.Context, arg ListAlbumsDueReacquisitionParams) ([]ListAlbumsDueReacquisitionRow, error) { rows, err := q.db.Query(ctx, listAlbumsDueReacquisition, arg.GraceHours, arg.PageLimit) if err != nil { diff --git a/internal/db/dbq/recommendation.sql.go b/internal/db/dbq/recommendation.sql.go index c148db96..80c8aede 100644 --- a/internal/db/dbq/recommendation.sql.go +++ b/internal/db/dbq/recommendation.sql.go @@ -19,7 +19,7 @@ ORDER BY started_at DESC LIMIT 1 ` -// The device_class of the user's most recent play (#1551), used as the +// The device_class of the user's most recent play, used as the // "current device" for radio context conditioning. NULL when the latest play // predates device capture or came from a client that didn't send one. func (q *Queries) GetLatestPlayDeviceClassForUser(ctx context.Context, userID pgtype.UUID) (*string, error) { @@ -87,10 +87,10 @@ type ListArtistContextPlayCountsForUserRow struct { } // Per-artist completed-play counts split by whether each play falls in the -// CURRENT context cell, in the user's local timezone (#1531). The cell is -// daypart × weekday-type, optionally narrowed by device class (#1551): when -// $2 (the current device) is ”, the device dimension is ignored (identical to -// the time-only #1531 behaviour, used by the daily mixes which have no device); +// CURRENT context cell, in the user's local timezone. The cell is +// daypart × weekday-type, optionally narrowed by device class: when +// $2 (the current device) is ”, the device dimension is ignored (time only, +// for the daily mixes, which have no device); // when $2 is set (radio), a play only counts in the cell if its device_class // matches. Feeds the context-affinity scoring term. Skips excluded; a 365-day // window bounds cost. Daypart buckets: night [22,5) morning [5,12) @@ -154,7 +154,7 @@ type ListLastPlayedArtistsForUserRow struct { LastPlayedAt pgtype.Timestamptz } -// M6a: artists ranked by max(play_events.started_at) for the user, with +// Artists ranked by max(play_events.started_at) for the user, with // a derived cover_album_id via a representative-album lateral join (most // recent album that has cover_art_path set). album_count joined for the // ArtistRef wire shape. @@ -216,7 +216,7 @@ JOIN tracks t ON t.id = p.track_id JOIN albums ON albums.id = t.album_id JOIN artists ON artists.id = t.artist_id WHERE t.artist_id = $1 - AND t.missing_since IS NULL -- #2523: never offer a file that is gone + AND t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $2 AND q.track_id = t.id @@ -303,7 +303,7 @@ FROM plays p JOIN tracks t ON t.id = p.track_id JOIN albums ON albums.id = t.album_id JOIN artists ON artists.id = t.artist_id -WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone +WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $1 AND q.track_id = t.id @@ -323,7 +323,7 @@ type ListMostPlayedTracksForUserRow struct { ArtistName string } -// M6a: top-N tracks by completed-play count for the user. was_skipped +// Top-N tracks by completed-play count for the user. was_skipped // excludes skips so a user spamming next doesn't fabricate a top track. // Quarantined tracks (per-user soft-hide from M5b) are filtered out. // @@ -723,7 +723,7 @@ LEFT JOIN LATERAL ( WHERE user_id = $1 AND track_id = t.id ) pe ON true WHERE t.id <> $2 - AND t.missing_since IS NULL -- #2523: never offer a file that is gone + AND t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM play_events WHERE user_id = $1 AND track_id = t.id @@ -843,11 +843,10 @@ lb_similar AS ( LIMIT $5 ), similar_artists AS ( - -- Round-robin across the similar artists (#5297): each artist's first + -- Round-robin across the similar artists: each artist's first -- track, best-scoring artist first, then each one's second, and so on. - -- Ordering by artist score alone let the closest similar artist's - -- catalogue fill the whole LIMIT; on the operator's library this arm held - -- exactly one artist for all 17 seeds measured (#3879). + -- Ordering by artist score alone would let the closest similar artist's + -- catalogue fill the whole LIMIT. SELECT track_id, sim_score FROM ( SELECT t.id AS track_id, asim.score * 0.5 AS sim_score, asim.score AS artist_score, @@ -873,11 +872,10 @@ tag_overlap AS ( AND t.id <> $2 GROUP BY t.id HAVING count(DISTINCT trim(g_split.g)) > 0 - -- md5 breaks the ties (#3880). A seed with one tag gives every track of - -- that genre the same score, 7,804 tracks on the operator's library, and - -- with no tie-break the LIMIT took whichever rows Postgres reached first: - -- the same slice for every seed of the genre, and not one the order seed - -- controls. + -- md5 breaks the ties. A seed with one tag gives every track of that + -- genre the same score, and with no tie-break the LIMIT would take + -- whichever rows Postgres reached first: the same slice for every seed of + -- the genre, and not one the order seed controls. ORDER BY sim_score DESC, md5(t.id::text || $12::text) LIMIT $7 ), @@ -896,12 +894,10 @@ likes_overlap AS ( LIMIT $8 ), taste_overlap AS ( - -- Round-robin across the taste artists (#3880), as similar_artists is - -- (#5297): each artist's first track, strongest artist first, then each - -- one's second. Ordered by weight then track id, the strongest artist's - -- catalogue filled the whole arm with the same lowest-id tracks every - -- time; on the operator's library that was 20 tracks of one artist of - -- 174, whose catalogue is 197 tracks. + -- Round-robin across the taste artists, as similar_artists is: each + -- artist's first track, strongest artist first, then each one's second. + -- Ordered by weight then track id, the strongest artist's catalogue would + -- fill the whole arm with the same lowest-id tracks every time. SELECT track_id, sim_score FROM ( SELECT t.id AS track_id, 0.0::float8 AS sim_score, tpa.weight, @@ -918,7 +914,7 @@ taste_overlap AS ( LIMIT $10 ), coplay_artists AS ( - -- Household co-play (#1533): tracks by artists co-played across the + -- Household co-play: tracks by artists co-played across the -- instance with the seed's artist (source='user_cooccurrence', built by -- the coplay worker). Mirrors similar_artists but from local co-occurrence -- instead of ListenBrainz; empty on single-user servers. Same 0.5 damp as @@ -972,7 +968,7 @@ FROM ( UNION ALL SELECT track_id, sim_score FROM coplay_artists UNION ALL SELECT track_id, sim_score FROM random_fill ) u -JOIN tracks t ON t.id = u.track_id AND t.missing_since IS NULL -- #2523: never offer a file that is gone +JOIN tracks t ON t.id = u.track_id AND t.missing_since IS NULL -- never offer a file that is gone JOIN albums al ON al.id = t.album_id LEFT JOIN general_likes l ON l.user_id = $1 AND l.track_id = t.id LEFT JOIN LATERAL ( @@ -1014,26 +1010,23 @@ type LoadRadioCandidatesV2Row struct { SimilarityScore interface{} } -// M4c: similarity-driven candidate pool. 6-way UNION: +// Similarity-driven candidate pool. 6-way UNION: // // $1 user_id, $2 seed_track_id, $3 recently_played_hours, // $4 exclude (uuid[]), $5 lb_similar K, $6 similar_artists K, // $7 tag_overlap K, $8 likes_overlap K, $9 random_fill K, -// $10 taste_overlap K (#796 phase 2b — tracks by the user's top +// $10 taste_overlap K (tracks by the user's top // positively-weighted taste-profile artists, so taste-relevant tracks // enter the pool even when the similarity/random arms miss them; scored // in Go via TasteMatch, so sim_score here is 0 pool-inclusion), -// $11 coplay_artists K (#1533 — tracks by artists co-played across the +// $11 coplay_artists K (tracks by artists co-played across the // instance with the seed's artist; source='user_cooccurrence'), // $12 order_seed (text) — see below. // -// $12 REPLACES `ORDER BY random()` IN FOUR ARMS (#3889). Those arms returned -// a stable set only while their LIMIT exceeded the rows eligible for them: at -// that point they returned all of them and the order stopped mattering, -// because the caller sorts by track id before scoring. Below that threshold -// they returned a random SUBSET, and two builds on the same day drew -// different ones — so "daily determinism" held by accident, and only for -// libraries smaller than the limits. +// $12 orders four arms by md5(id || seed), not random(). An arm whose LIMIT is +// below the rows eligible for it returns a SUBSET, and with random() two +// builds on the same day would draw different ones; the caller sorts by track +// id before scoring, so only the subset matters. // // md5(id || seed) keeps the intent — an arbitrary spread that changes when // the seed does — while making it reproducible for a given seed. The CALLER @@ -1042,7 +1035,7 @@ type LoadRadioCandidatesV2Row struct { // keeps varying, which is what a radio should do. // Returns same shape as LoadRadioCandidates plus similarity_score column. // -// sim_score BANDS keep the tiers in order (rule 131, #6177). Similarity edges +// sim_score BANDS keep the eligibility tiers in order. Similarity edges // are stored relative to the seed's best match, 0 to 1, so the arms are placed // in bands rather than compared raw: // @@ -1052,9 +1045,9 @@ type LoadRadioCandidatesV2Row struct { // likes_overlap 0.3, flat // tier 3 taste_overlap, random_fill 0 // -// Without the bands, the seed's weaker LB edges fell below a shared genre tag -// and the flat likes score, and the one arm that measures the seed lost to -// the ones that guess. +// Without the bands, the seed's weaker LB edges would fall below a shared +// genre tag and the flat likes score, and the one arm that measures the seed +// would lose to the ones that guess. func (q *Queries) LoadRadioCandidatesV2(ctx context.Context, arg LoadRadioCandidatesV2Params) ([]LoadRadioCandidatesV2Row, error) { rows, err := q.db.Query(ctx, loadRadioCandidatesV2, arg.UserID, @@ -1122,9 +1115,9 @@ func (q *Queries) LoadRadioCandidatesV2(ctx context.Context, arg LoadRadioCandid const suggestArtistsForUser = `-- name: SuggestArtistsForUser :many WITH artist_plays AS ( - -- Completed plays only. The previous seed query counted every play_event, - -- so skipping an artist repeatedly INCREASED its signal and pushed more of - -- its neighbours at the user (issue #2367 mechanism 3). + -- Completed plays only: counting every play_event would make skipping an + -- artist repeatedly INCREASE its signal and push more of its neighbours + -- at the user. SELECT t.artist_id, count(*)::bigint AS play_count FROM play_events pe JOIN tracks t ON t.id = pe.track_id @@ -1181,7 +1174,7 @@ contributions AS ( AND r.artist_mbid = u.candidate_mbid AND r.status NOT IN ('rejected', 'failed') ) - -- Snoozed by this user and not yet expired (#2374). Time-boxed and + -- Snoozed by this user and not yet expired. Time-boxed and -- per-user: the candidate returns on its own once snoozed_until -- passes, and stays visible to everyone else meanwhile. Deliberately -- filtered at the candidate stage, NOT folded into the score — a @@ -1226,7 +1219,7 @@ type SuggestArtistsForUserRow struct { // Per-user artist suggestions ranked by taste signal x similarity, projected // through artist_similarity_unmatched (out-of-library candidates only). // -// Seeds are TIERED (rule #131) so the surface never empties: +// Seeds are TIERED so the surface never empties: // // tier 1 - taste_profile_artists.weight: engagement-graded, time-decayed and // SIGNED by internal/taste, so an artist the user has drifted away @@ -1234,9 +1227,9 @@ type SuggestArtistsForUserRow struct { // tier 2 - likes + completed plays, used ONLY when the profile has no rows // (new account, or before the first daily recompute). // -// The signal is log-damped: contribution is signal x similarity, and the old -// undamped sum let one heavily-played artist's neighbours take every slot -- -// entrenching harder the MORE the user listened (issue #2367 mechanism 2). +// The signal is log-damped: contribution is signal x similarity, and an +// undamped sum would let one heavily-played artist's neighbours take every +// slot -- entrenching harder the MORE the user listened. // // Candidates already in the library, already requested and not terminal, or // snoozed by this user, are excluded. $1=user_id, $2=half_life_days (tier 2 diff --git a/internal/db/dbq/recommendation_metrics.sql.go b/internal/db/dbq/recommendation_metrics.sql.go index 1fe4528b..39527937 100644 --- a/internal/db/dbq/recommendation_metrics.sql.go +++ b/internal/db/dbq/recommendation_metrics.sql.go @@ -44,12 +44,12 @@ type RecommendationSourceMetricsForUserRow struct { // $1 user_id, $2 window_days. plays/skips are counts; avg_completion is the // mean completion ratio over the completion_n plays that recorded one. -// pick_kind splits For You plays into taste/fresh/unattributed (#1249); +// pick_kind splits For You plays into taste/fresh/unattributed; // it is NULL for every other source, so those still group to one row. // // completion_sqsum carries the sum of SQUARED completion ratios so the Go // handler can compute a variance — needed for the margin of error on a -// completion delta (#2495). It is the sum rather than `stddev_samp` on purpose: +// completion delta. It is the sum rather than `stddev_samp` on purpose: // raw source rows get merged into surface families in Go, and sums of squares // add across groups exactly, whereas standard deviations cannot be combined // without them. Variance = (sqsum - sum²/n) / (n-1), with sum = avg × n. @@ -112,17 +112,17 @@ type RecommendationWeeklyTrendsRow struct { TasteHits int64 } -// Recommendation observability (#796 phase 4). Per-source play outcomes so the +// Recommendation observability. Per-source play outcomes so the // operator can see whether each recommendation surface is landing and tune the // taste weights. Source is stamped on play_events when a play is launched from // a recommendation surface; NULL means the user picked the track manually — // those rows are INCLUDED here as the baseline control group the surfaces are -// judged against (milestone #127: delta-vs-baseline is what makes the numbers -// actionable). Raw source strings are bucketed into stable surface families in -// the Go handler; completion_n is carried so family merges can weight +// judged against (delta-vs-baseline is what makes the numbers actionable). +// Raw source strings are bucketed into stable surface families in the Go +// handler; completion_n is carried so family merges can weight // avg_completion correctly. // Weekly per-source outcome series for the tuning lab's trend view -// (#1251). Aggregated across ALL users: the tuning knobs are global, +// . Aggregated across ALL users: the tuning knobs are global, // so judging a knob turn needs global outcomes — rows carry rates // only, no track or user identity. NULL-source (manual) rows are // included as the baseline family. diff --git a/internal/db/dbq/recommendation_tuning.sql.go b/internal/db/dbq/recommendation_tuning.sql.go index 251eeed5..1e1f76c3 100644 --- a/internal/db/dbq/recommendation_tuning.sql.go +++ b/internal/db/dbq/recommendation_tuning.sql.go @@ -70,7 +70,7 @@ SELECT id, changed_at, scope, action, changes LIMIT $1 ` -// Newest first; consumed by the metrics trend view (#1251) to annotate +// Newest first; consumed by the metrics trend view to annotate // knob turns on the timeline. func (q *Queries) ListTuningAudit(ctx context.Context, limit int32) ([]RecommendationTuningAudit, error) { rows, err := q.db.Query(ctx, listTuningAudit, limit) @@ -279,8 +279,8 @@ type UpsertDiscoverTuningDefaultsParams struct { SnoozeDays float64 } -// Boot reconcile for the Discover scope (#2377). Never overwrites -// operator-tuned values, same contract as the other two. +// Boot reconcile for the Discover scope. Never overwrites +// admin-tuned values, same contract as the other two. func (q *Queries) UpsertDiscoverTuningDefaults(ctx context.Context, arg UpsertDiscoverTuningDefaultsParams) error { _, err := q.db.Exec(ctx, upsertDiscoverTuningDefaults, arg.TagOverlapWeight, arg.SnoozeDays) return err @@ -341,10 +341,10 @@ type UpsertWeightProfileDefaultsParams struct { ContextTimeWeight float64 } -// Recommendation tuning lab queries (#1250). Seeding happens via the +// Recommendation tuning lab queries. Seeding happens via the // recsettings boot reconcile; shipped defaults live in Go only. // Boot reconcile: insert the shipped defaults for a profile if the row -// doesn't exist yet. Never overwrites operator-tuned values. +// doesn't exist yet. Never overwrites admin-tuned values. func (q *Queries) UpsertWeightProfileDefaults(ctx context.Context, arg UpsertWeightProfileDefaultsParams) error { _, err := q.db.Exec(ctx, upsertWeightProfileDefaults, arg.Profile, diff --git a/internal/db/dbq/requests.sql.go b/internal/db/dbq/requests.sql.go index d34248aa..c2e2b6d1 100644 --- a/internal/db/dbq/requests.sql.go +++ b/internal/db/dbq/requests.sql.go @@ -520,7 +520,7 @@ type RepointRequestsAlbumMbidParams struct { OldMbid *string } -// A want moved from a release id to its group (#5241) takes the requests +// A want moved from a release id to its group takes the requests // that name it along, so the reconciler finds it. func (q *Queries) RepointRequestsAlbumMbid(ctx context.Context, arg RepointRequestsAlbumMbidParams) error { _, err := q.db.Exec(ctx, repointRequestsAlbumMbid, arg.NewMbid, arg.OldMbid) diff --git a/internal/db/dbq/scan_runs.sql.go b/internal/db/dbq/scan_runs.sql.go index 94cb53bd..45727164 100644 --- a/internal/db/dbq/scan_runs.sql.go +++ b/internal/db/dbq/scan_runs.sql.go @@ -98,7 +98,7 @@ type InsertScanRunRow struct { StartedAt pgtype.Timestamptz } -// M7 #381: scan-run lifecycle queries. +// Scan-run lifecycle queries. // Creates a new scan_runs row with started_at=now(). Stages are filled in // as each completes via UpdateScanRunStage; finished_at is set by FinishScanRun. func (q *Queries) InsertScanRun(ctx context.Context) (InsertScanRunRow, error) { @@ -119,7 +119,7 @@ type UpdateScanRunArtistArtEnrichParams struct { ArtistArtEnrich []byte } -// M7 cover-sources: persists the artist-art enrich stage tally JSON. +// Persists the artist-art enrich stage tally JSON. func (q *Queries) UpdateScanRunArtistArtEnrich(ctx context.Context, arg UpdateScanRunArtistArtEnrichParams) error { _, err := q.db.Exec(ctx, updateScanRunArtistArtEnrich, arg.ID, arg.ArtistArtEnrich) return err diff --git a/internal/db/dbq/search.sql.go b/internal/db/dbq/search.sql.go index da7089e7..3e5986e1 100644 --- a/internal/db/dbq/search.sql.go +++ b/internal/db/dbq/search.sql.go @@ -99,7 +99,7 @@ const getAcquisitionSettings = `-- name: GetAcquisitionSettings :one SELECT id, auto_grab, search_interval_hours, allowed_qualities, last_pass_at, last_pass, updated_at, import_root, seed_ratio, seed_days, stall_hours, library_listing_started_at, library_listed_at, library_unlisted, upgrade_until FROM acquisition_settings WHERE id = true ` -// Searching for wants and grabbing releases (M502 #5521). +// Searching for wants and grabbing releases. func (q *Queries) GetAcquisitionSettings(ctx context.Context) (AcquisitionSetting, error) { row := q.db.QueryRow(ctx, getAcquisitionSettings) var i AcquisitionSetting @@ -385,7 +385,7 @@ SELECT info_hash, name, reason, detail, want_id, blocked_by, blocked_at, expires ` // Every release set aside and still held back: a stalled one lifts by itself -// at expires_at (#5523). +// at expires_at. func (q *Queries) ListReleaseBlocks(ctx context.Context) ([]ReleaseBlock, error) { rows, err := q.db.Query(ctx, listReleaseBlocks) if err != nil { @@ -500,7 +500,7 @@ type ListWantsDueForSearchRow struct { // Wants with no download running that were never searched or not since // @hours ago: open wants first (never-searched, then requests, then missing // files, then followed artists' releases), then held albums wanted for an -// upgrade (#5524), which are searched at most weekly. +// upgrade, which are searched at most weekly. func (q *Queries) ListWantsDueForSearch(ctx context.Context, arg ListWantsDueForSearchParams) ([]ListWantsDueForSearchRow, error) { rows, err := q.db.Query(ctx, listWantsDueForSearch, arg.Hours, arg.MaxRows) if err != nil { diff --git a/internal/db/dbq/sessions.sql.go b/internal/db/dbq/sessions.sql.go index 307af3e7..0fcdfd83 100644 --- a/internal/db/dbq/sessions.sql.go +++ b/internal/db/dbq/sessions.sql.go @@ -57,7 +57,8 @@ type DeleteSessionForUserParams struct { UserID pgtype.UUID } -// Scoped by user_id, not just id (rule #47). Keyed on the id alone, any +// Scoped by user_id, not just id: household data is per-user. Keyed on the +// id alone, any // household member could revoke another member's session by guessing a uuid. // execrows lets the handler answer 404 rather than a false 204 when the row // isn't theirs. diff --git a/internal/db/dbq/similarity.sql.go b/internal/db/dbq/similarity.sql.go index e97dd274..d1f69b8a 100644 --- a/internal/db/dbq/similarity.sql.go +++ b/internal/db/dbq/similarity.sql.go @@ -134,8 +134,8 @@ type ListPlayedTracksNeedingSimilarityRow struct { // ListenBrainz has never answered for, or not in 30 days. Never-fetched first, // then the oldest answer. Freshness is read from track_similarity_fetches, not // from the edges, so a seed whose answer matched nothing in the library still -// counts as fetched (#5296); reading the edges is what let 25 such seeds hold -// the head of the queue forever. +// counts as fetched; reading the edges would let such seeds hold the head of +// the queue forever. func (q *Queries) ListPlayedTracksNeedingSimilarity(ctx context.Context, limit int32) ([]ListPlayedTracksNeedingSimilarityRow, error) { rows, err := q.db.Query(ctx, listPlayedTracksNeedingSimilarity, limit) if err != nil { @@ -310,9 +310,9 @@ WHERE ts.source = 'listenbrainz' // // The score is RELATIVE: each edge's LB score over the seed's best score in its // whole cached answer, so 1.0 is the seed's closest recording and the rest fall -// below it (#6177). LB's raw scores are counts (16 to 1445 on the operator's -// library) and reached the scorer, which reads similarity as 0 to 1, so one -// edge outweighed likes, skips and recency together. The cache keeps the raw +// below it. LB's raw scores are counts, into the thousands, and the scorer +// reads similarity as 0 to 1, so a raw edge would outweigh likes, skips and +// recency together. The cache keeps the raw // answer; only the edge is scaled. // // Only edges of seeds the cache owns are removed, those with a fetch row. diff --git a/internal/db/dbq/songs.sql.go b/internal/db/dbq/songs.sql.go index 1eaee010..a441f9c6 100644 --- a/internal/db/dbq/songs.sql.go +++ b/internal/db/dbq/songs.sql.go @@ -33,7 +33,7 @@ type LinkTracksAsSongRow struct { Moved int64 } -// Song links (M498 #5438): copies of one song on different releases share a +// Song links: copies of one song on different releases share a // song_key (migration 0076). // Links the tracks into one song, joining any songs they already belong to. // The song keeps the lowest key among them, so linking the same set again @@ -57,7 +57,7 @@ type ListTrackSongKeysRow struct { } // What the mix writer needs to place a track: its song, and whether it is a -// video rip held back from the mixes (#5439). +// video rip held back from the mixes. func (q *Queries) ListTrackSongKeys(ctx context.Context, ids []pgtype.UUID) ([]ListTrackSongKeysRow, error) { rows, err := q.db.Query(ctx, listTrackSongKeys, ids) if err != nil { diff --git a/internal/db/dbq/suggestion_snoozes.sql.go b/internal/db/dbq/suggestion_snoozes.sql.go index f1642a34..5da3eb77 100644 --- a/internal/db/dbq/suggestion_snoozes.sql.go +++ b/internal/db/dbq/suggestion_snoozes.sql.go @@ -85,7 +85,7 @@ type SnoozeSuggestionParams struct { Column4 float64 } -// Time-boxed "not right now" on a Discover artist suggestion (#2374). +// Time-boxed "not right now" on a Discover artist suggestion. // // See 0049_suggestion_snoozes.up.sql for why this is a snooze and not a // dismissal: it records no verdict on the music, expires on its own, and diff --git a/internal/db/dbq/system_mixes.sql.go b/internal/db/dbq/system_mixes.sql.go index da852842..32387753 100644 --- a/internal/db/dbq/system_mixes.sql.go +++ b/internal/db/dbq/system_mixes.sql.go @@ -39,7 +39,7 @@ SELECT t.id, t.album_id, t.artist_id JOIN affinity_artists aa ON aa.artist_id = t.artist_id LEFT JOIN play_counts pc ON pc.track_id = t.id LEFT JOIN skip_counts sc ON sc.track_id = t.id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND COALESCE(pc.c, 0) <= 2 AND COALESCE(sc.c, 0) < 2 AND NOT EXISTS ( @@ -62,18 +62,18 @@ type ListDeepCutsTracksRow struct { } // Candidate queries for the discovery system-playlist mixes -// (#419-423). Each returns (id, album_id, artist_id) rows; the Go +// . Each returns (id, album_id, artist_id) rows; the Go // producer caps + truncates and converts to rankedCandidate. Daily- // deterministic md5(id||dateStr) ordering (where used) keeps a mix // stable within a day while rotating across days; shuffle-on-play // gives per-play variety on top. -// #419 Deep Cuts: low-play tracks (<=2 plays) from artists the user +// Deep Cuts: low-play tracks (<=2 plays) from artists the user // has liked OR played heavily (>=5 non-skip plays across the artist). -// Tracks the user has skipped twice or more don't qualify (#1257): -// eligibility used to count only unskipped plays, so the most actively -// rejected tracks read as "barely heard" and kept being re-offered. -// Threshold 2 so a single accidental skip doesn't banish a track; -// passive-signal only (no dislike UI, rule #101). +// Tracks the user has skipped twice or more don't qualify: counting only +// unskipped plays would make the most actively rejected tracks read as +// "barely heard" and keep re-offering them. Threshold 2 so a single +// accidental skip doesn't banish a track; passive signal only (there is no +// dislike button). // $1 user_id, $2 date string. func (q *Queries) ListDeepCutsTracks(ctx context.Context, arg ListDeepCutsTracksParams) ([]ListDeepCutsTracksRow, error) { rows, err := q.db.Query(ctx, listDeepCutsTracks, arg.UserID, arg.Column2) @@ -125,7 +125,7 @@ SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier FROM tracks t JOIN albums al ON al.id = t.album_id JOIN albums_tiered alt ON alt.album_id = al.id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND alt.tier IS NOT NULL AND NOT EXISTS (SELECT 1 FROM attempted a WHERE a.track_id = t.id) AND NOT EXISTS ( @@ -153,11 +153,10 @@ type ListFirstListensTracksRow struct { Tier int32 } -// #423 First Listens: songs the user has never heard or even -// attempted. "Attempted" is track-level with a >=30s listen threshold -// (#1268, operator decision 2026-07-03) — the old version disqualified -// a whole album on ANY play_event, so a 2-second accidental brush -// banished it permanently. +// First Listens: songs the user has never heard or even +// attempted. "Attempted" is track-level with a >=30s listen threshold: +// disqualifying a whole album on ANY play_event would let a 2-second +// accidental brush banish it permanently. // // tier 1 the exact desire: albums with ZERO attempted tracks // tier 2 step back: barely-attempted albums (<=25% of tracks @@ -227,7 +226,7 @@ albums_tiered AS ( SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier FROM tracks t JOIN albums_tiered alt ON alt.album_id = t.album_id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $1 AND q.track_id = t.id @@ -244,14 +243,13 @@ type ListNewForYouTracksRow struct { Tier int32 } -// #421 New for you, tiered per project rule #131 (#1267). The old -// single hard rule (added <30d AND direct affinity) had two failure -// modes with one root: no notion of consumption — the album you -// devoured in week one crowded the mix for three more weeks, and -// after a quiet month nothing qualified at all. +// New for you, tiered so the mix degrades rather than vanishes. A +// single hard rule (added <30d AND direct affinity) has no notion of +// consumption: the album you devoured in week one would crowd the mix +// for three more weeks, and after a quiet month nothing would qualify. // // "Consumed" = any track attempted for >=30s (the First Listens -// threshold, #1268): once you've meaningfully engaged, the album is +// threshold): once you've meaningfully engaged, the album is // no longer news. Applies to every tier — played albums leave the // mix the next build. // @@ -306,7 +304,7 @@ WITH windowed AS ( SELECT t.id, t.album_id, t.artist_id FROM tracks t JOIN windowed w ON w.track_id = t.id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $1 AND q.track_id = t.id @@ -326,18 +324,18 @@ type ListOnThisDayTracksRow struct { ArtistID pgtype.UUID } -// #422 On This Day: tracks the user played around this calendar date +// On This Day: tracks the user played around this calendar date // (±10 day-of-year) in the past, excluding the very recent (>30 days // ago) so it's nostalgic, not just "last week". Weighted by how much -// they were played in those windows. Floor relaxed from 60→30 days -// and window ±7→±10 so it surfaces on a months-old library instead -// of needing a full year of history; still skips cleanly (no rows → -// no playlist) when there's no qualifying history yet. -// Day-of-year distance is circular (#1256): plain ABS made Dec 28 vs -// Jan 3 read as 359 days apart, silently gutting the window for ~3 -// weeks around every New Year — precisely when holiday nostalgia is -// the point. LEAST(d, 365-d) wraps the boundary; leap-year drift of -// ±1 is absorbed by the ±10 window. The build date ($2, already here +// they were played in those windows. The 30-day floor and ±10 window +// let it surface on a months-old library rather than needing a full +// year of history; it still skips cleanly (no rows → no playlist) +// when there's no qualifying history yet. +// Day-of-year distance is circular: plain ABS would read Dec 28 vs +// Jan 3 as 359 days apart, gutting the window for ~3 weeks around +// every New Year — precisely when holiday nostalgia is the point. +// LEAST(d, 365-d) wraps the boundary; leap-year drift of ±1 is absorbed +// by the ±10 window. The build date ($2, already here // for the md5 rotation) anchors "today" instead of now() so the // window is testable and consistent with the mix's daily determinism. // $1 user_id, $2 date string. @@ -371,18 +369,18 @@ WITH stats AS ( SELECT t.id, t.album_id, t.artist_id FROM tracks t JOIN stats s ON s.track_id = t.id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND s.c >= 3 AND s.last_at <= now() - interval '30 days' AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $1 AND q.track_id = t.id ) - -- Declined-resurfacing cooldown (#1258): a skip on a + -- Declined-resurfacing cooldown: a skip on a -- rediscover-sourced play is the user explicitly saying "I've moved -- on" to the exact invitation this mix extends — such tracks sit -- out ~90 days instead of re-qualifying the next day. Passive - -- signal only (rule #101). + -- signal only. AND NOT EXISTS ( SELECT 1 FROM play_events rp WHERE rp.user_id = $1 @@ -403,11 +401,10 @@ type ListRediscoverTracksRow struct { ArtistID pgtype.UUID } -// #420 Rediscover: tracks the user played a lot but has drifted away -// from. One blended pool (issue #1246: the old two-tier UNION was -// all-or-nothing — a single ">=6 months cold" row suppressed the whole -// ">=30 days" tier, which is how a one-song playlist shipped; the 6mo -// tier was also a strict subset of the 30d tier). Eligibility is >=3 +// Rediscover: tracks the user played a lot but has drifted away +// from. One blended pool, not a two-tier UNION: the ">=6 months cold" tier is +// a strict subset of the ">=30 days" one, and a UNION that let one row of it +// suppress the wider tier would ship a one-song playlist. Eligibility is >=3 // non-skip plays and >=30 days cold; the bar sits at 3 rather than 5 // so a weeks-old library still fields a pool — on young histories the // >=5-play tracks are precisely the ones still in rotation. Ordering diff --git a/internal/db/dbq/system_playlist_rotation.sql.go b/internal/db/dbq/system_playlist_rotation.sql.go index 6aeff689..f78c26e6 100644 --- a/internal/db/dbq/system_playlist_rotation.sql.go +++ b/internal/db/dbq/system_playlist_rotation.sql.go @@ -34,7 +34,7 @@ type AppendRotationPlayedParams struct { TrackID pgtype.UUID } -// Rotation state for system-playlist sample-history dedup (#415). +// Rotation state for system-playlist sample-history dedup. // One row per (user, playlist_kind). played_track_ids accumulates // tracks heard from that playlist in the current rotation; shuffle // prefers ids NOT in this set, then resets when the snapshot is diff --git a/internal/db/dbq/system_playlists.sql.go b/internal/db/dbq/system_playlists.sql.go index d8fa1ae4..0a892e48 100644 --- a/internal/db/dbq/system_playlists.sql.go +++ b/internal/db/dbq/system_playlists.sql.go @@ -198,14 +198,14 @@ SELECT u.id FROM users u ) ` -// Track picks here join `tracks ... AND t.missing_since IS NULL` (#2523): a +// Track picks here join `tracks ... AND t.missing_since IS NULL`: a // seed or For-You candidate has to be something that can actually play. Note // this only affects newly GENERATED playlists — already-stored system // playlists keep their rows until the next daily rebuild, which is why the // shared ListPlaylistTracks read path is deliberately left unfiltered (it // also serves user-curated playlists, where hiding a track the user added // themselves would be wrong). -// M7 #352 slice 2: system-generated playlist queries. +// System-generated playlist queries. // Active = had a play in the last 7 days. The cron iterates this list. func (q *Queries) ListActiveUsersForSystemPlaylists(ctx context.Context) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listActiveUsersForSystemPlaylists) @@ -368,10 +368,10 @@ type PickSeedArtistsRow struct { } // Top-12 most-engaged distinct artist candidates, tiered so the -// "Songs like X" mixes never silently vanish (#1255): the old hard -// 7-day window emptied the seed pool after a quiet week, and the -// daily atomic-replace build then deleted every existing mix until -// the user played something again. Same fallback shape as +// "Songs like X" mixes never silently vanish: a hard 7-day window +// would empty the seed pool after a quiet week, and the daily +// atomic-replace build would then delete every existing mix until the +// user played something again. Same fallback shape as // PickTopPlayedTracksForUser (For You's seeds): // // tier 0 engagement in the last 7 days @@ -380,12 +380,12 @@ type PickSeedArtistsRow struct { // tier 3 (only if 0-2 empty) liked artists with no play history // // All returned rows share one tier; produceSeedMixes maps it onto the -// rule-#131 pick-kind ladder and stamps the built tracks, so metrics +// tiered pick-kind ladder and stamps the built tracks, so metrics // can compare mixes seeded from fresh vs stale engagement. // Score = unskipped-play count + 5 if user has liked the artist. The // Go-side picker (pickSeedArtistsForDay) shuffles the 12 // daily-deterministically and takes songsLikeSeedCount (6) so the mix -// set both rotates day-to-day and fills the dedicated Home row (#1491). +// set both rotates day-to-day and fills the dedicated Home row. func (q *Queries) PickSeedArtists(ctx context.Context, userID pgtype.UUID) ([]PickSeedArtistsRow, error) { rows, err := q.db.Query(ctx, pickSeedArtists, userID) if err != nil { @@ -452,7 +452,7 @@ SELECT COALESCE( FROM tracks t JOIN albums a ON a.id = t.album_id WHERE t.artist_id = $2 - AND t.missing_since IS NULL -- #2701: the play branch filters; this one must too + AND t.missing_since IS NULL -- the play branch filters; this one must too ORDER BY a.release_date DESC NULLS LAST, t.disc_number NULLS LAST, t.track_number NULLS LAST, @@ -530,7 +530,7 @@ SELECT id // library with sparse history. // A likes-only read still joins tracks: a like outlives its file, and a // seed whose file is gone points For You at something the user can't hear -// (#2701). The other two tiers get the same filter from their play_events +// . The other two tiers get the same filter from their play_events // join. func (q *Queries) PickTopPlayedTracksForUser(ctx context.Context, userID pgtype.UUID) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, pickTopPlayedTracksForUser, userID) diff --git a/internal/db/dbq/tag_settings.sql.go b/internal/db/dbq/tag_settings.sql.go index 61ee4524..0e2e5c6e 100644 --- a/internal/db/dbq/tag_settings.sql.go +++ b/internal/db/dbq/tag_settings.sql.go @@ -74,7 +74,7 @@ SELECT provider_id, enabled, api_key, display_order, created_at, updated_at ORDER BY display_order, provider_id ` -// Tag-enrichment provider settings queries (milestone #160, #1490 Step 2). +// Tag-enrichment provider settings queries. // The tags.SettingsService consumes these — parallel to the cover-art // SettingsService (coverart_settings.sql). Keeping the two independent // means a new tag source is added without touching the art settings. @@ -150,7 +150,7 @@ type UpsertTagProviderSettingsParams struct { // INSERT a default row for a newly-registered provider, or refresh an // existing row's display_order on boot. Does NOT overwrite enabled / -// api_key on conflict — the operator's settings persist across restarts. +// api_key on conflict — an admin's settings persist across restarts. func (q *Queries) UpsertTagProviderSettings(ctx context.Context, arg UpsertTagProviderSettingsParams) error { _, err := q.db.Exec(ctx, upsertTagProviderSettings, arg.ProviderID, arg.Enabled, arg.DisplayOrder) return err diff --git a/internal/db/dbq/taste_profile.sql.go b/internal/db/dbq/taste_profile.sql.go index 28ac1866..c7aa5c7f 100644 --- a/internal/db/dbq/taste_profile.sql.go +++ b/internal/db/dbq/taste_profile.sql.go @@ -153,7 +153,7 @@ type ListLikedTrackTasteInputsForUserRow struct { // (track_id, artist_id, genre, release_date) for each track the user has // explicitly liked. Feeds the track-like bonus into the liked track's artist, -// tags, and era (#1530); track_id keys the enriched track_tags lookup (#1490). +// tags, and era; track_id keys the enriched track_tags lookup. func (q *Queries) ListLikedTrackTasteInputsForUser(ctx context.Context, userID pgtype.UUID) ([]ListLikedTrackTasteInputsForUserRow, error) { rows, err := q.db.Query(ctx, listLikedTrackTasteInputsForUser, userID) if err != nil { @@ -216,14 +216,14 @@ type ListPlayEngagementInputsForUserRow struct { AgeDays float64 } -// Taste profile (#796 phase 1). The daily build computes signed artist/tag +// Taste profile. The daily build computes signed artist/tag // weights in Go (graded engagement + decay + like bonus) and atomic-replaces -// them here. Read queries serve "top taste" lookups (used in phase 2). +// them here. Read queries serve the scorer's "top taste" lookups. // One row per play in the decay-relevant window. completion is the play's // completion ratio (precomputed column when present, else duration_played / // track duration, clamped to [0,1]); age_days drives the time-decay. Genre // is split into tags in Go; release_date derives the decade for the era -// facet (#1530). Quarantined tracks are excluded. +// facet. Quarantined tracks are excluded. func (q *Queries) ListPlayEngagementInputsForUser(ctx context.Context, arg ListPlayEngagementInputsForUserParams) ([]ListPlayEngagementInputsForUserRow, error) { rows, err := q.db.Query(ctx, listPlayEngagementInputsForUser, arg.UserID, arg.Column2) if err != nil { @@ -269,7 +269,7 @@ type ListTasteProfileArtistsForUserRow struct { Weight float64 } -// Top-weighted taste artists (phase-2 consumption + tests). +// Top-weighted taste artists, for the scorer and tests. func (q *Queries) ListTasteProfileArtistsForUser(ctx context.Context, arg ListTasteProfileArtistsForUserParams) ([]ListTasteProfileArtistsForUserRow, error) { rows, err := q.db.Query(ctx, listTasteProfileArtistsForUser, arg.UserID, arg.Limit) if err != nil { @@ -308,7 +308,7 @@ type ListTasteProfileErasForUserRow struct { Weight float64 } -// Top-weighted taste eras (#1530); consumed by the scorer's era term. +// Top-weighted taste eras; consumed by the scorer's era term. func (q *Queries) ListTasteProfileErasForUser(ctx context.Context, arg ListTasteProfileErasForUserParams) ([]ListTasteProfileErasForUserRow, error) { rows, err := q.db.Query(ctx, listTasteProfileErasForUser, arg.UserID, arg.Limit) if err != nil { @@ -347,7 +347,7 @@ type ListTasteProfileMoodsForUserRow struct { Weight float64 } -// Top-weighted taste moods (#1534); consumed by the scorer's mood term. +// Top-weighted taste moods; consumed by the scorer's mood term. func (q *Queries) ListTasteProfileMoodsForUser(ctx context.Context, arg ListTasteProfileMoodsForUserParams) ([]ListTasteProfileMoodsForUserRow, error) { rows, err := q.db.Query(ctx, listTasteProfileMoodsForUser, arg.UserID, arg.Limit) if err != nil { diff --git a/internal/db/dbq/track_tags.sql.go b/internal/db/dbq/track_tags.sql.go index 70ddcec4..3e00caae 100644 --- a/internal/db/dbq/track_tags.sql.go +++ b/internal/db/dbq/track_tags.sql.go @@ -121,7 +121,7 @@ type ListTrackTagsForTracksRow struct { Tag string } -// Enriched tags for a set of candidate tracks (#1534), so the scorer can +// Enriched tags for a set of candidate tracks, so the scorer can // derive each candidate's mood buckets at scoring time. One row per (track, // tag); the caller filters to mood words via the internal/mood vocabulary. func (q *Queries) ListTrackTagsForTracks(ctx context.Context, dollar_1 []pgtype.UUID) ([]ListTrackTagsForTracksRow, error) { @@ -168,7 +168,7 @@ type ListTracksMissingTagsRow struct { ArtistMbid *string } -// Track-tag enrichment queries (milestone #160, #1490). The tag enricher +// Track-tag enrichment queries. The tag enricher // drains ListTracksMissingTags, fetches from the provider chain, and // writes the merged top-K via DeleteTrackTags + InsertTrackTag, then // stamps SetTrackTagSource. Mirrors the coverart artist-enricher shape. @@ -176,7 +176,7 @@ type ListTracksMissingTagsRow struct { // or previously settled 'none' under an older provider version. Returns // the fields the provider chain needs — recording MBID (nullable) for // keyed lookups, title + artist name for name-based fallback, and the -// artist MBID (nullable) for MusicBrainz's artist-tag fallback (#1519). +// artist MBID (nullable) for MusicBrainz's artist-tag fallback. // $1 = current tag_sources_version, $2 = limit. func (q *Queries) ListTracksMissingTags(ctx context.Context, arg ListTracksMissingTagsParams) ([]ListTracksMissingTagsRow, error) { rows, err := q.db.Query(ctx, listTracksMissingTags, arg.TagSourcesVersion, arg.Limit) diff --git a/internal/db/dbq/tracks.sql.go b/internal/db/dbq/tracks.sql.go index 2bd38335..5f136d09 100644 --- a/internal/db/dbq/tracks.sql.go +++ b/internal/db/dbq/tracks.sql.go @@ -62,10 +62,10 @@ UPDATE tracks WHERE id = ANY($1::uuid[]) ` -// Forgets the recording MBID of rows whose file was another song's (M498's -// wrong-slot removal). The MBID came from that file, so it names the song kept -// elsewhere on the album, and missing-pair folding would fold the emptied slot -// into it: twelve slots went that way on 2026-10-20 and were never wanted back. +// Forgets the recording MBID of rows whose file was another song's (the +// duplicate resolver's wrong-slot removal). The MBID came from that file, so it +// names the song kept elsewhere on the album, and missing-pair folding would +// fold the emptied slot into it, so it would never be wanted back. // mbid_source goes with it (tracks_mbid_source_pair). func (q *Queries) ClearTrackMbids(ctx context.Context, ids []pgtype.UUID) error { _, err := q.db.Exec(ctx, clearTrackMbids, ids) @@ -177,7 +177,7 @@ type DeleteTrackRow struct { Mbid *string } -// M7 #372: hard delete with FK cascade. The CASCADE on track_id from +// Hard delete with FK cascade. The CASCADE on track_id from // play_events / general_likes_tracks / quarantine / // quarantine_actions handles their cleanup. RETURNING gives us // album_id + artist_id for the album-empty / artist-empty cascade @@ -209,11 +209,11 @@ type FindMissingTrackByAudioHashRow struct { FilePath string } -// Move detection fallback for files with no MBID (#2528, #3914). The audio stream +// Move detection fallback for files with no MBID. The audio stream // hash identifies the encoded audio itself, so it survives a rename, a move and a -// retag — anything short of a re-encode. It replaced (file_size, duration_ms), -// which could pair two unrelated files that happened to share a byte count and a -// duration, and missed a file retagged in place, whose size changes. +// retag — anything short of a re-encode. (file_size, duration_ms) would pair two +// unrelated files that share a byte count and a duration, and miss a file +// retagged in place, whose size changes. // // Same missing-only constraint and same LIMIT 2 rationale as the MBID variant. func (q *Queries) FindMissingTrackByAudioHash(ctx context.Context, audioStreamSha256 []byte) ([]FindMissingTrackByAudioHashRow, error) { @@ -249,7 +249,7 @@ type FindMissingTrackByMbidRow struct { FilePath string } -// Move detection, strongest signal (#2528). A file that turned up at a new path +// Move detection, strongest signal. A file that turned up at a new path // carrying a recording MBID we already have on a MISSING row is that recording, // moved — not a new track. // @@ -287,8 +287,8 @@ UPDATE tracks AND regexp_replace(file_path, '^.*/', '') ~* $1::text ` -// Holds back present tracks whose basename carries a video-rip marker (M498 -// #5439), the same pattern the report lists. A track the operator called fine +// Holds back present tracks whose basename carries a video-rip marker, the +// same pattern the report lists. A track an admin called fine // keeps that verdict. func (q *Queries) FlagSuspectSourceTracks(ctx context.Context, pattern string) (int64, error) { result, err := q.db.Exec(ctx, flagSuspectSourceTracks, pattern) @@ -377,7 +377,7 @@ SELECT id, title, album_id, artist_id, track_number, disc_number, duration_ms, f ` // Batched lookup used by /api/library/sync to hydrate upsert payloads -// (#357). Mirror of GetArtistsByIDs. +// . Mirror of GetArtistsByIDs. func (q *Queries) GetTracksByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Track, error) { rows, err := q.db.Query(ctx, getTracksByIDs, dollar_1) if err != nil { @@ -451,7 +451,7 @@ type ListArtistTracksForUserRow struct { ArtistName string } -// M6a: every track for the artist across their albums, with album_title +// Every track for the artist across their albums, with album_title // and artist_name joined. Honors per-user quarantine. Used by // /api/artists/{id}/tracks for the artist-card play affordance, which // shuffles client-side. Ordering matches album/track natural order so @@ -545,19 +545,18 @@ type ListMissingTracksRow struct { LastPlayedAt pgtype.Timestamptz } -// The admin review surface for files the scan could not find (#2527). +// The admin review surface for files the scan could not find. // // Ordered by directory, then by the file's own position within its album, -// because the unit an operator actually reasons about is a FOLDER: the case -// this was built for was three whole albums that had been reorganised, and a -// flat list ordered by timestamp presents that as forty unrelated decisions. +// because the unit an admin actually reasons about is a FOLDER: a reorganised +// album, listed flat by timestamp, reads as dozens of unrelated decisions. // Grouping happens in the handler; the ordering here is what makes a group // contiguous, so a page boundary splits a directory at worst. // // last_played_at is a correlated MAX rather than a join so a track with no // plays stays in the result with NULL. It is here because "gone six months, // never played" and "gone yesterday, played 200 times" deserve opposite -// reactions, and the operator can't tell them apart from a path. +// reactions, and an admin can't tell them apart from a path. func (q *Queries) ListMissingTracks(ctx context.Context, arg ListMissingTracksParams) ([]ListMissingTracksRow, error) { rows, err := q.db.Query(ctx, listMissingTracks, arg.PageOffset, arg.PageLimit) if err != nil { @@ -616,7 +615,7 @@ type ListRandomTracksForUserRow struct { ArtistName string } -// #427 S4: backing query for GET /api/library/shuffle — the online +// Backing query for GET /api/library/shuffle — the online // source for "Shuffle all". N random tracks across the whole // library, per-user-quarantine filtered. $1 user_id, $2 limit. func (q *Queries) ListRandomTracksForUser(ctx context.Context, arg ListRandomTracksForUserParams) ([]ListRandomTracksForUserRow, error) { @@ -682,7 +681,7 @@ type ListStillMissingTrackLabelsRow struct { } // Which of the tracks a scan marked missing still are once it has adopted -// moves and folded replacements (M542): the ones worth telling an admin +// moves and folded replacements: the ones worth telling an admin // about, by name. A quality upgrade folds into its replacement and drops out. func (q *Queries) ListStillMissingTrackLabels(ctx context.Context, ids []pgtype.UUID) ([]ListStillMissingTrackLabelsRow, error) { rows, err := q.db.Query(ctx, listStillMissingTrackLabels, ids) @@ -719,7 +718,7 @@ SELECT t.id, t.source_verdict, t.source_verdict_at, -- The album's place on the wanted list: wanted for a clean copy, and - -- when that was last searched for (#5524). + -- when that was last searched for. COALESCE(w.upgrade, '')::text AS want_upgrade, w.searched_at AS searched_at FROM tracks t @@ -760,7 +759,7 @@ type ListSuspectSourceTracksRow struct { } // The admin report of present tracks whose filename looks like a video rip -// (#5410): "(Official Video)", "[Audio]", "Visualizer" and the like. +// : "(Official Video)", "[Audio]", "Visualizer" and the like. // // pattern is built in Go (internal/api/admin_suspect_sources.go) from the // same marker list that labels each row, so the filter and the labels cannot @@ -816,7 +815,7 @@ type ListTrackPathsForReconcileRow struct { } // Every row's path + current missing mark, for the scanner's reconcile pass -// (#2523). Deliberately unfiltered and unpaged: reconcile has to compare the +// . Deliberately unfiltered and unpaged: reconcile has to compare the // WHOLE table against what the walk saw, and a filtered subset would let rows // outside it drift forever. Three narrow columns keep it cheap even on a // library of a few hundred thousand tracks. @@ -921,7 +920,7 @@ type ListTracksMissingMbidWithPathRow struct { // Track recording-MBID backfill: tracks with NULL mbid that still have // a file to re-read. $1 caps the batch (mirrors the album backfill). // Missing tracks are skipped: there is no file, and opening it anyway -// logged a warning per track on every scan (#5139). +// would log a warning per track on every scan. func (q *Queries) ListTracksMissingMbidWithPath(ctx context.Context, limit int32) ([]ListTracksMissingMbidWithPathRow, error) { rows, err := q.db.Query(ctx, listTracksMissingMbidWithPath, limit) if err != nil { @@ -1081,7 +1080,7 @@ type SetTrackSourceVerdictParams struct { ID pgtype.UUID } -// The operator's call on a flagged track: 'fine' lets it back into radio and +// An admin's call on a flagged track: 'fine' lets it back into radio and // the mixes for good; 'suspect' holds it back again. func (q *Queries) SetTrackSourceVerdict(ctx context.Context, arg SetTrackSourceVerdictParams) (int64, error) { result, err := q.db.Exec(ctx, setTrackSourceVerdict, arg.Verdict, arg.ID) @@ -1115,7 +1114,7 @@ ON CONFLICT (file_path) DO UPDATE SET WHEN tracks.mbid_source = 'acoustid' THEN 'acoustid' END, genre = EXCLUDED.genre, -- Stamped on update too, so a tag-repair pass marks rows as done and the - -- next scan can short-circuit them again (#2499). + -- next scan can short-circuit them again. tag_read_version = EXCLUDED.tag_read_version, updated_at = now() RETURNING id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version, missing_since, mbid_source, song_id, song_key, source_verdict, source_verdict_at, emptied_at @@ -1138,8 +1137,8 @@ type UpsertTrackParams struct { } // file_path is the canonical identity for library scan; mbid is secondary. -// mbid is the file's tag. A looked-up id (mbid_source 'acoustid', M401) -// survives a re-read that finds no tag id, and a tag id replaces it (D4). +// mbid is the file's tag. A looked-up id (mbid_source 'acoustid') +// survives a re-read that finds no tag id, and a tag id replaces it. func (q *Queries) UpsertTrack(ctx context.Context, arg UpsertTrackParams) (Track, error) { row := q.db.QueryRow(ctx, upsertTrack, arg.Title, diff --git a/internal/db/dbq/users.sql.go b/internal/db/dbq/users.sql.go index f021d104..d21696fa 100644 --- a/internal/db/dbq/users.sql.go +++ b/internal/db/dbq/users.sql.go @@ -584,7 +584,7 @@ type SetSubsonicPasswordParams struct { // Stores (or clears with NULL) the per-user Subsonic legacy credential used // for t/s and p auth on /rest/*. Must be plaintext; see migration 0003. Only -// ever a server-generated value, never the login password (#5026). +// ever a server-generated value, never the login password. func (q *Queries) SetSubsonicPassword(ctx context.Context, arg SetSubsonicPasswordParams) error { _, err := q.db.Exec(ctx, setSubsonicPassword, arg.ID, arg.SubsonicPassword) return err diff --git a/internal/db/dbq/wanted.sql.go b/internal/db/dbq/wanted.sql.go index 2a7ca770..efe87083 100644 --- a/internal/db/dbq/wanted.sql.go +++ b/internal/db/dbq/wanted.sql.go @@ -68,15 +68,15 @@ const countWants = `-- name: CountWants :one SELECT COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason <> 'missing')::bigint AS wanted, COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason = 'missing')::bigint AS missing, COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL)::bigint AS held, - -- Held albums wanted for a better copy (#5524). + -- Held albums wanted for a better copy. COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL AND upgrade <> '')::bigint AS upgrades, COUNT(DISTINCT artist_mbid)::bigint AS artists, - -- Library albums waiting to be matched by hand (#5579). + -- Library albums waiting to be matched by hand. (SELECT COUNT(*) FROM library_album_matches m WHERE m.outcome = 'unmatched' AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))::bigint AS unmatched, (SELECT COUNT(*) FROM follows)::bigint AS following, - -- The Wanted view's other tabs (#5522): downloads running, those + -- The Wanted view's other tabs: downloads running, those -- waiting on a person, and releases set aside. (SELECT COUNT(*) FROM grabs WHERE ended_at IS NULL)::bigint AS activity, (SELECT COUNT(*) FROM grabs @@ -324,7 +324,7 @@ type GetLibraryListingRow struct { LibraryUnlisted int32 } -// The one-time listing of the library (#5567). --------------------------- +// The one-time listing of the library. --------------------------- func (q *Queries) GetLibraryListing(ctx context.Context) (GetLibraryListingRow, error) { row := q.db.QueryRow(ctx, getLibraryListing) var i GetLibraryListingRow @@ -361,7 +361,7 @@ type GetWantAlbumRow struct { AlbumID pgtype.UUID } -// What the Wanted view's track list needs of a want (#5572). +// What the Wanted view's track list needs of a want. func (q *Queries) GetWantAlbum(ctx context.Context, id pgtype.UUID) (GetWantAlbumRow, error) { row := q.db.QueryRow(ctx, getWantAlbum, id) var i GetWantAlbumRow @@ -547,9 +547,9 @@ type ListAlbumsToMatchRow struct { // Library albums the listing has not decided, or that an older matcher // refused, or whose tracks changed since they were refused: each is matched -// to MusicBrainz (#5569). A library album is one from before the cutoff, or +// to MusicBrainz. A library album is one from before the cutoff, or // one whose tracks were: a re-tag files a track under a new album row but -// keeps the track (#5579). An album row with no tracks is not an album in +// keeps the track. An album row with no tracks is not an album in // the library; one a person left off stays off. func (q *Queries) ListAlbumsToMatch(ctx context.Context, arg ListAlbumsToMatchParams) ([]ListAlbumsToMatchRow, error) { rows, err := q.db.Query(ctx, listAlbumsToMatch, arg.Cutoff, arg.Matcher, arg.MaxRows) @@ -752,7 +752,7 @@ type ListHeldWantFormatsRow struct { Rips int32 } -// Upgrades (#5524). ------------------------------------------------------- +// Upgrades. ------------------------------------------------------- // What each held album's copy is made of, for judging whether it is worth // upgrading: its present tracks grouped by format, with their bytes and // length summed, and how many are held back as video rips. A held want whose @@ -936,7 +936,7 @@ type ListReleaseTypeProfilesRow struct { FollowCount int64 } -// The wanted list (M502 #5520): release-type profiles, follows and wants. +// The wanted list: release-type profiles, follows and wants. func (q *Queries) ListReleaseTypeProfiles(ctx context.Context) ([]ListReleaseTypeProfilesRow, error) { rows, err := q.db.Query(ctx, listReleaseTypeProfiles) if err != nil { @@ -988,7 +988,7 @@ type ListUnmatchedLibraryAlbumsRow struct { Tracks int32 } -// The Collection's Unmatched section (#5579): the albums the listing could +// The Collection's Unmatched section: the albums the listing could // not match, and below them the ones a person left off. An album whose // tracks have all moved to another row is gone, not unmatched. func (q *Queries) ListUnmatchedLibraryAlbums(ctx context.Context, maxRows int32) ([]ListUnmatchedLibraryAlbumsRow, error) { @@ -1070,7 +1070,7 @@ type ListWantArtistsRow struct { Total int64 } -// The Wanted view's main page (#5572): every artist on the list, A–Z like +// The Wanted view's main page: every artist on the list, A–Z like // the library, with how many of their albums are held of how many are on // the list. q matches the artist's name or any of their albums' titles; // artist_mbid narrows to one artist, for their page's header. @@ -1114,7 +1114,7 @@ func (q *Queries) ListWantArtists(ctx context.Context, arg ListWantArtistsParams const listWants = `-- name: ListWants :many SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reason, w.album_id, w.wanted_at, w.fulfilled_at, - -- Why a held album is wanted again, and the copy it has (#5524). + -- Why a held album is wanted again, and the copy it has. w.upgrade, w.held_quality, -- A request names an album before the catalogue has read it: the -- catalogue's type and date fill in once it has. @@ -1124,10 +1124,10 @@ SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reas u.username AS requested_by, la.id AS library_artist_id, COUNT(*) OVER () AS total, - -- The search schedule's last look (#5521), and the download running. + -- The search schedule's last look, and the download running. w.searched_at, w.search_outcome, w.search_considered, w.best_release, w.best_verdict, w.search_error, -- Its latest download, running or ended, and what the import made of - -- it (#5522). + -- it. gr.id AS grab_id, gr.name AS grab_name, gr.grabbed_at, gr.origin AS grab_origin, gr.quality AS grab_quality, gr.ended_at AS grab_ended_at, gr.end_reason AS grab_end_reason, gr.progress AS grab_progress, gr.import_state AS grab_import_state, @@ -1147,7 +1147,7 @@ SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reas ELSE w.fulfilled_at IS NULL AND w.reason <> 'missing' END AND ($2::text = '' OR w.artist_mbid = $2::text) - -- want_ids narrows to given wants: the Missing page's folders (M504). + -- want_ids narrows to given wants: the Missing page's folders. AND (COALESCE(cardinality($3::uuid[]), 0) = 0 OR w.id = ANY($3::uuid[])) AND ($4::text = '' OR strpos(lower(w.artist_name), lower($4::text)) > 0 @@ -1208,7 +1208,7 @@ type ListWantsRow struct { // The Wanted page: open wants (all but missing ones, or only missing ones), // the held ones, or all of them; artist_mbid narrows to one artist's page, -// want_ids to given wants. Ordered like the library (#5572): artists A–Z by the +// want_ids to given wants. Ordered like the library: artists A–Z by the // library's sort name when it holds the artist, each artist's albums by // release date, so the page groups them as they arrive. q narrows to an // artist or album title; total counts every match, past the page. @@ -1349,7 +1349,7 @@ ON CONFLICT (album_id) DO NOTHING ` // The albums ListLibraryAlbums takes are decided as listed by their tags, -// so the matching that follows leaves them alone (#5569). +// so the matching that follows leaves them alone. func (q *Queries) RecordListedAlbums(ctx context.Context, cutoff pgtype.Timestamptz) error { _, err := q.db.Exec(ctx, recordListedAlbums, cutoff) return err @@ -1379,7 +1379,7 @@ type RepointWantParams struct { ReleaseGroupMbid string } -// A want recorded under a release id before #5241 is moved to its group. +// A want recorded under a release id is moved to its group. func (q *Queries) RepointWant(ctx context.Context, arg RepointWantParams) error { _, err := q.db.Exec(ctx, repointWant, arg.ID, arg.ReleaseGroupMbid) return err @@ -1571,10 +1571,10 @@ SELECT a.id AS album_id, COALESCE(bool_or(t.missing_since IS NULL AND t.added_at > $1::timestamptz), false)::bool AS arrived_since, COALESCE(bool_or(t.missing_since IS NOT NULL AND t.missing_since < $1::timestamptz), false)::bool AS missing_before, -- Missing tracks no track on disk stands in for, by position or - -- title. A missing row whose song is back at a new path (#5245) is + -- title. A missing row whose song is back at a new path is -- covered; a lost bonus track is not. A track with no disc number is - -- on disc 1: Air's Love 2 stayed wanted, and was downloaded again for - -- nothing, over a missing track 6 with no disc beside a present 1/6. + -- on disc 1, so a missing track 6 with no disc beside a present 1/6 + -- does not keep the album wanted. COUNT(*) FILTER (WHERE t.missing_since IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM tracks p WHERE p.album_id = a.id AND p.missing_since IS NULL diff --git a/internal/db/dbq/you_might_like.sql.go b/internal/db/dbq/you_might_like.sql.go index 066e01f6..a389b7e2 100644 --- a/internal/db/dbq/you_might_like.sql.go +++ b/internal/db/dbq/you_might_like.sql.go @@ -26,7 +26,7 @@ type CountListeningSignalForUserRow struct { DistinctArtists int64 } -// "You might like" Home rows (#790). The daily build computes ranked +// "You might like" Home rows. The daily build computes ranked // album/artist IDs in Go (similarity roll-up + cold-start gate) and // atomic-replaces them here; /api/home reads them back hydrated. // Cold-start gate. Returns the breadth of the user's real listening: diff --git a/internal/db/queries/acoustid.sql b/internal/db/queries/acoustid.sql index 7a4aa1e1..cb677a6c 100644 --- a/internal/db/queries/acoustid.sql +++ b/internal/db/queries/acoustid.sql @@ -1,4 +1,4 @@ --- AcoustID recording-id lookup (M401). Migration 0069 says what each +-- AcoustID recording-id lookup. Migration 0069 says what each -- lookup state means. -- name: ListTracksNeedingAcoustIDLookup :many @@ -29,7 +29,7 @@ ON CONFLICT (track_id) DO UPDATE SET looked_up_at = now(); -- name: SetTrackMbidFromAcoustID :execrows --- Never over a tag id (D4): the guard is here, not only in the worker, so a +-- Never over a tag id: the guard is here, not only in the worker, so a -- tag the scan read between the worker's list and this write still wins. -- updated_at is left alone: it gates the scan's mtime skip, and moving it -- past a file's mtime would hide a change made just before the lookup. diff --git a/internal/db/queries/acquisition.sql b/internal/db/queries/acquisition.sql index 139b4db5..82e11b15 100644 --- a/internal/db/queries/acquisition.sql +++ b/internal/db/queries/acquisition.sql @@ -1,4 +1,4 @@ --- Indexers and download clients (M502 #5518). +-- Indexers and download clients. -- name: ListAcquisitionServices :many -- Returns no secret, only whether one is set: this feeds the settings page. diff --git a/internal/db/queries/admin_status.sql b/internal/db/queries/admin_status.sql index f0d73827..e36b2a37 100644 --- a/internal/db/queries/admin_status.sql +++ b/internal/db/queries/admin_status.sql @@ -1,4 +1,4 @@ --- The admin Overview's status sections (M504): who is listening, what is +-- The admin Overview's status sections: who is listening, what is -- downloading, what the library holds. Each is one cheap read. -- name: ListListeningNow :many diff --git a/internal/db/queries/admin_summary.sql b/internal/db/queries/admin_summary.sql index 5bcc1a72..9d4167f7 100644 --- a/internal/db/queries/admin_summary.sql +++ b/internal/db/queries/admin_summary.sql @@ -1,14 +1,14 @@ -- name: AdminSummary :one --- What the admin rail and the Overview's verdict read (#5605): each queue +-- What the admin rail and the Overview's verdict read: each queue -- that waits on a person, with how many and since when. One round trip, so -- the rail costs one cheap read on every admin page. The "since" is the -- oldest item still waiting: how long the queue has been left, which is what --- tells the operator a count of 2 is fine today and wrong after a month. +-- tells an admin a count of 2 is fine today and wrong after a month. SELECT (SELECT count(*) FROM requests WHERE status = 'pending')::bigint AS requests, (SELECT min(requested_at) FROM requests WHERE status = 'pending')::timestamptz AS requests_since, -- A broken track is one report however it was found: a listener flagged - -- it, or it failed to play (#5606). Counted by track, as the Reports + -- it, or it failed to play. Counted by track, as the Reports -- page lists them. (SELECT count(*) FROM ( SELECT track_id FROM quarantine diff --git a/internal/db/queries/albums.sql b/internal/db/queries/albums.sql index 5fceb24e..e6a4e6f0 100644 --- a/internal/db/queries/albums.sql +++ b/internal/db/queries/albums.sql @@ -37,12 +37,12 @@ ORDER BY release_date NULLS LAST, sort_title; -- name: GetAlbumByArtistAndTitle :one -- Scanner uses this for the no-mbid dedupe path: resolve-or-create. An album --- of the same name in another release group is a different album (Weezer's --- six self-titled albums shared one row until 2026-10-20), so a row whose +-- of the same name in another release group is a different album (one +-- artist's self-titled albums), so a row whose -- group is known and differs is not returned. A row of the file's own group -- comes first, then one with no group yet, then the one holding the most -- tracks from the file's folder: a file tagged with no group (a live bonus --- track beside Weezer's 1994 album) belongs with its folder, not the oldest +-- track beside its album) belongs with its folder, not the oldest -- row, and a few strays already misfiled do not outvote the folder. SELECT albums.* FROM albums WHERE albums.artist_id = sqlc.arg(artist_id) AND albums.title = sqlc.arg(title) @@ -79,12 +79,10 @@ SELECT * FROM albums ORDER BY random() LIMIT $1; -- Album "belongs to" a genre if any of its tracks carry that genre. -- Serves Subsonic getAlbumList?type=byGenre. -- --- Splits tracks.genre on [;,] as of #367. It previously compared the whole --- column verbatim, so a track tagged "Rock;Pop" was unreachable from EITHER --- "Rock" or "Pop" — a Subsonic client asking for a genre silently missed --- every multi-genre track. This also aligns the endpoint with --- recommendation.sql / discover.sql, which have always split, and with the --- genre browse index that #367 adds. +-- Splits tracks.genre on [;,], so a track tagged "Rock;Pop" is reachable from +-- both "Rock" and "Pop"; comparing the whole column would hide every +-- multi-genre track from a Subsonic client asking for a genre. The same split +-- as recommendation.sql, discover.sql and the genre browse index. -- -- EXISTS rather than JOIN + DISTINCT ON: the lateral split emits one row per -- (track, genre-fragment), so a join would multiply rows per album and lean @@ -111,7 +109,7 @@ LIMIT $2 OFFSET $3; SELECT COUNT(*) FROM albums WHERE title ILIKE '%' || $1::text || '%'; -- name: ListRecentlyAddedAlbumsWithArtist :many --- M6a: recently-added albums joined with artist_name + artist_id for the +-- Recently-added albums joined with artist_name + artist_id for the -- home-page section. created_at is the row-insert timestamp from the -- scanner — the closest proxy to "added to library". Stable secondary -- ordering by id keeps pagination tie-break deterministic. @@ -122,7 +120,7 @@ ORDER BY albums.created_at DESC, albums.id LIMIT $1; -- name: ListAlbumsAlphaWithArtist :many --- M6a: alpha-sorted album list joined with artist_name. Used by +-- Alpha-sorted album list joined with artist_name. Used by -- /api/library/albums for the wrapping-grid page. Stable id-tiebreak. SELECT sqlc.embed(albums), artists.name AS artist_name FROM albums @@ -131,7 +129,7 @@ ORDER BY albums.sort_title, albums.id LIMIT $1 OFFSET $2; -- name: CountAlbums :one --- M6a: total album count for the /api/library/albums envelope. +-- Total album count for the /api/library/albums envelope. SELECT COUNT(*) FROM albums; -- name: CountAlbumsByArtist :one @@ -140,7 +138,7 @@ SELECT COUNT(*) FROM albums; SELECT COUNT(*) FROM albums WHERE artist_id = $1; -- name: DeleteAlbumIfEmpty :one --- M7 #372: deletes the album row only when it has no remaining tracks. +-- Deletes the album row only when it has no remaining tracks. -- Used after a track delete to tidy up orphaned albums. RETURNING gives -- us the artist_id so the service can chain into the artist-empty check. -- Returns no rows when the album still has tracks; the service treats @@ -151,7 +149,7 @@ WHERE a.id = $1 RETURNING a.id, a.artist_id; -- name: SetAlbumMbidIfNull :exec --- M7 #379: heal MBID on existing album rows during rescan. Idempotent — +-- Heal MBID on existing album rows during rescan. Idempotent — -- only updates when the existing mbid is NULL. UPDATE albums SET mbid = $2, @@ -159,7 +157,7 @@ UPDATE albums WHERE id = $1 AND mbid IS NULL; -- name: SetAlbumReleaseGroupMbidIfNull :exec --- #5241: heal the release-group id on an existing album row, from a rescan's +-- Heal the release-group id on an existing album row, from a rescan's -- tags or from the re-acquisition sweeper's MusicBrainz lookup. Only fills a -- NULL, so a tag never gets overwritten by a later guess or vice versa. UPDATE albums @@ -168,7 +166,7 @@ UPDATE albums WHERE id = $1 AND release_group_mbid IS NULL; -- name: GetReleaseGroupForReleaseMbid :one --- #5241: the release group the library already knows for a release id, so a +-- The release group the library already knows for a release id, so a -- request carrying a release id can be repaired without asking MusicBrainz. SELECT release_group_mbid::text FROM albums @@ -176,7 +174,7 @@ SELECT release_group_mbid::text LIMIT 1; -- name: SetReleaseGroupForReleaseMbidIfNull :exec --- #5241: cache a MusicBrainz-resolved release group onto the album row that +-- Cache a MusicBrainz-resolved release group onto the album row that -- holds the release, filling only a NULL. UPDATE albums SET release_group_mbid = sqlc.arg(release_group_mbid)::text, @@ -188,7 +186,7 @@ UPDATE albums -- one of its tracks' file_path so the worker can re-read tags. LIMIT -- supplied by caller for batching/progress purposes. The track read is -- one still on disk; an album with none left is skipped until a scan --- finds its files again (#5139). +-- finds its files again. SELECT a.id AS album_id, a.artist_id AS artist_id, a.title AS title, @@ -207,7 +205,7 @@ SELECT a.id AS album_id, LIMIT $1; -- name: GetAlbumCoverageRollup :one --- M7 coverage-gauge: library-wide cover-art coverage rollup for the +-- Library-wide cover-art coverage rollup for the -- admin gauge. Single sequential scan with FILTER aggregates — -- sub-millisecond on realistic libraries. pending_no_mbid is a SUBSET -- of pending; the UI surfaces it separately so operators can see how @@ -217,12 +215,9 @@ SELECT a.id AS album_id, -- Invariant: with_art + pending + settled = total. (pending_no_mbid is -- not part of the sum — it's a subset of pending, surfaced separately.) -- --- The IN list below must stay in sync with the cover_art_source CHECK --- constraint — currently relaxed by migration 0020 to include 'deezer' --- and 'lastfm', and migration 0030 further relaxed it to any non-empty --- string. If a new source value is added without updating this query, --- with_art will silently undercount. Drift #556 caught the deezer + --- lastfm omission introduced by migration 0020. +-- The IN list below must name every source the enrichers write (the +-- cover_art_source CHECK accepts any non-empty string since migration +-- 0030). A source missing here makes with_art silently undercount. SELECT COUNT(*) AS total, COUNT(*) FILTER (WHERE cover_art_source IN @@ -233,7 +228,7 @@ SELECT FROM albums; -- name: SetAlbumCoverWithVersion :exec --- M7 cover-sources: records a successful or settled enrichment with the +-- Records a successful or settled enrichment with the -- current sources_version stamp. cover_art_path is NULL when source is -- 'none' (no file written). Atomic; called per album per pass. UPDATE albums @@ -246,5 +241,5 @@ UPDATE albums -- name: GetAlbumsByIDs :many -- Batched lookup used by /api/library/sync to hydrate upsert payloads --- (#357). Mirror of GetArtistsByIDs. +--. Mirror of GetArtistsByIDs. SELECT * FROM albums WHERE id = ANY($1::uuid[]); diff --git a/internal/db/queries/artists.sql b/internal/db/queries/artists.sql index 62535726..dbb8048f 100644 --- a/internal/db/queries/artists.sql +++ b/internal/db/queries/artists.sql @@ -46,7 +46,7 @@ SELECT COUNT(*) FROM artists WHERE name ILIKE '%' || $1::text || '%'; SELECT * FROM artists WHERE id = ANY($1::uuid[]); -- name: ListArtistsAlphaWithCovers :many --- M6a: alpha-sorted artist list with derived cover_album_id (most-recent +-- Alpha-sorted artist list with derived cover_album_id (most-recent -- album whose cover_art_path is set). Replaces ListArtistsAlpha for the -- /api/artists?sort=alpha branch — the new column is appended, so the -- alpha branch is purely additive on the wire. album_count is computed @@ -68,7 +68,7 @@ ORDER BY artists.sort_name, artists.name, artists.id LIMIT $1 OFFSET $2; -- name: DeleteArtistIfEmpty :one --- M7 #372: deletes the artist row only when it has no remaining albums +-- Deletes the artist row only when it has no remaining albums -- AND no remaining tracks (defensive — tracks can in principle exist -- without an album, though the scanner doesn't write that shape today). DELETE FROM artists a @@ -78,7 +78,7 @@ WHERE a.id = $1 RETURNING a.id; -- name: SetArtistMbidIfNull :exec --- M7 #379: heal MBID on existing artist rows during rescan. Idempotent — +-- Heal MBID on existing artist rows during rescan. Idempotent — -- only updates when the existing mbid is NULL, so concurrent scans don't -- overwrite an existing value with a stale one. UPDATE artists @@ -87,7 +87,7 @@ UPDATE artists WHERE id = $1 AND mbid IS NULL; -- name: ListArtistsMissingArt :many --- M7 cover-sources: returns artist rows the artist-art enricher should +-- Returns artist rows the artist-art enricher should -- attempt. Eligible: artist_art_source IS NULL OR (= 'none' AND stale -- artist_art_sources_version). MBID-less artists are returned but the -- enricher skips them at the row.mbid == nil guard inside the chain. @@ -99,7 +99,7 @@ SELECT a.id LIMIT $2; -- name: SetArtistArtWithVersion :exec --- M7 cover-sources: records a successful or settled artist-art +-- Records a successful or settled artist-art -- enrichment with the current sources_version stamp. thumb_path / -- fanart_path are NULL on a 'none' result, or independently NULL if -- the provider returned only one of the two image types (e.g. @@ -113,7 +113,7 @@ UPDATE artists WHERE id = $1; -- name: ClearArtistArtNone :exec --- M7 cover-sources: parallel of ClearAlbumCoverNone. Used by the +-- Parallel of ClearAlbumCoverNone. Used by the -- eligibility loop to flip stale 'none' rows back to NULL. UPDATE artists SET artist_art_source = NULL, diff --git a/internal/db/queries/audit_log.sql b/internal/db/queries/audit_log.sql index 6322ec70..ea699eb1 100644 --- a/internal/db/queries/audit_log.sql +++ b/internal/db/queries/audit_log.sql @@ -15,7 +15,7 @@ SELECT count(*)::bigint FROM audit_log -- name: ListAuditLogByActions :many -- One page of the given actions, newest first: the duplicates report's --- "resolved automatically" activity (M498) reads the resolver's merges from +-- "resolved automatically" activity reads the resolver's merges from -- here. SELECT * FROM audit_log WHERE action = ANY(sqlc.arg(actions)::text[]) diff --git a/internal/db/queries/browse.sql b/internal/db/queries/browse.sql index 4b3d9ced..4a200fc4 100644 --- a/internal/db/queries/browse.sql +++ b/internal/db/queries/browse.sql @@ -1,29 +1,23 @@ --- Every query in this file filters `tracks.missing_since IS NULL` (#2523). --- A row whose file has vanished keeps its genre forever — the scanner walks the --- filesystem, so it never revisits a path that no longer exists — which is how --- pre-#2499 welded genres survived a full re-scan and kept showing in the index. --- Browsing is a way of finding something to play, so a track that cannot play +-- Every query in this file filters `tracks.missing_since IS NULL`. A row whose +-- file has vanished keeps its genre forever — the scanner walks the +-- filesystem, so it never revisits a path that no longer exists — and +-- browsing is a way of finding something to play, so a track that cannot play -- should not shape it. -- --- The year axis filters too (#2702). It used to join albums only, on the --- reasoning that an album is a real release even when some of its tracks are --- gone — true, but it left the two browse axes disagreeing: the genre queries --- above filter tracks.missing_since, so an album whose every file had vanished --- was already absent from genre while still listed under its year, where --- opening it found nothing playable. +-- The year axis filters too, so the two axes agree: an album whose every file +-- has vanished is absent from both, rather than absent from its genre and +-- still listed under its year, where opening it would find nothing playable. -- --- Hiding is the answer rather than showing-and-marking because browsing is how --- you go looking for something to play, and the operator's rule for that case --- is to take it out of view; the admin missing-files surface is where absence --- is reported. It also means changing the axis that was inconsistent rather --- than the one that was already right. +-- Hiding rather than showing-and-marking, because browsing is how you go +-- looking for something to play; the admin missing-files surface is where +-- absence is reported. -- -- The predicate is "has at least one playable track", so it also excludes an -- album with no tracks at all. That is the same answer for the same reason: -- nothing to play, nothing to browse to. -- name: ListGenresWithCount :many --- Genre browse index (#367). +-- Genre browse index. -- -- Genres live inline on tracks.genre as a delimited string, so this splits on -- the same [;,] pattern already used by recommendation.sql and discover.sql — @@ -34,14 +28,14 @@ -- trim() but deliberately NO lower(): trimming repairs an artifact of OUR -- splitting ("Rock; Pop" yields " Pop", and showing that as a distinct genre -- would be a bug), whereas case is what the tag actually says. Raw ID3 is --- exposed as-is for v1, so "Rock" and "rock" appear as separate rows. +-- exposed as-is, so "Rock" and "rock" appear as separate rows. -- -- COUNT(DISTINCT) because a sloppy tag like "Rock;Rock" would otherwise -- inflate its own row. -- -- Ordered by count first: raw ID3 data has a long tail of one-off junk tags, --- so alphabetical would bury the handful of genres an operator actually has a --- library's worth of. Name breaks ties for a stable order. +-- so alphabetical would bury the handful of genres a library actually has +-- a lot of. Name breaks ties for a stable order. SELECT trim(g.genre) AS genre, COUNT(DISTINCT tracks.id)::bigint AS track_count FROM tracks JOIN LATERAL regexp_split_to_table(coalesce(tracks.genre, ''), '[;,]') AS g(genre) ON true @@ -87,7 +81,7 @@ WHERE EXISTS ( ); -- name: ListAlbumYearsWithCount :many --- Year browse index (#367). Only albums with a release_date appear — an +-- Year browse index. Only albums with a release_date appear — an -- album with no date isn't "year unknown" as a browsable bucket, it's absent -- from this axis, and the UI says so rather than inventing a 0 row. -- Newest first: recent releases are the likelier browse target. @@ -132,8 +126,7 @@ WHERE release_date IS NOT NULL -- name: ListGenresForAlbum :many -- Distinct genres carried by an album's tracks, for the album detail page's -- quick-jump chips. Split and trimmed identically to ListGenresWithCount, so a --- chip always leads to a page that actually contains this album — the two --- diverging is exactly the bug #367 had to fix in ListAlbumsByGenre. +-- chip always leads to a page that actually contains this album. SELECT DISTINCT trim(g.genre) AS genre FROM tracks JOIN LATERAL regexp_split_to_table(coalesce(tracks.genre, ''), '[;,]') AS g(genre) ON true diff --git a/internal/db/queries/candidate_artist_tags.sql b/internal/db/queries/candidate_artist_tags.sql index 4403689f..4bc94881 100644 --- a/internal/db/queries/candidate_artist_tags.sql +++ b/internal/db/queries/candidate_artist_tags.sql @@ -1,4 +1,4 @@ --- Folksonomy tags for out-of-library Discover candidates (#2376). Parallel to +-- Folksonomy tags for out-of-library Discover candidates. Parallel to -- track_tags.sql, but keyed by MBID because the artist has no local row. See -- 0050_candidate_artist_tags.up.sql for why the bookkeeping is its own table. @@ -63,7 +63,7 @@ ON CONFLICT (candidate_mbid) DO UPDATE updated_at = now(); -- name: ListCandidateArtistTagsForMbids :many --- Cached tags for a set of candidates, for slice 6's taste-overlap ranking. +-- Cached tags for a set of candidates, for the taste-overlap ranking. -- One row per (candidate, tag). SELECT candidate_mbid, tag, weight FROM candidate_artist_tags diff --git a/internal/db/queries/catalogue.sql b/internal/db/queries/catalogue.sql index 6518e5d3..6d8d5309 100644 --- a/internal/db/queries/catalogue.sql +++ b/internal/db/queries/catalogue.sql @@ -1,4 +1,4 @@ --- The release catalogue read from MusicBrainz (M502 #5519). +-- The release catalogue read from MusicBrainz. -- name: UpsertMBArtist :exec INSERT INTO mb_artists (mbid, name, sort_name, disambiguation, artist_type, country) diff --git a/internal/db/queries/coplay.sql b/internal/db/queries/coplay.sql index d2e07c0a..a4714fb9 100644 --- a/internal/db/queries/coplay.sql +++ b/internal/db/queries/coplay.sql @@ -1,4 +1,4 @@ --- Household co-play similarity (#1533, milestone #160 Opt 5). Minstrel is a +-- Household co-play similarity. Minstrel is a -- single shared-library, multi-user server (no per-user library ACL), so the -- "household" is the whole instance's user set. These queries recompute -- artist–artist co-occurrence edges from play_events and store them in diff --git a/internal/db/queries/coverart_settings.sql b/internal/db/queries/coverart_settings.sql index aa3b898a..3695a885 100644 --- a/internal/db/queries/coverart_settings.sql +++ b/internal/db/queries/coverart_settings.sql @@ -1,4 +1,4 @@ --- M7 cover-sources: queries for the cover_art_provider_settings table +-- Queries for the cover_art_provider_settings table -- and the cover_art_sources_meta singleton. The SettingsService -- consumes these. @@ -13,7 +13,7 @@ SELECT provider_id, enabled, api_key, display_order, created_at, updated_at -- name: UpsertProviderSettings :exec -- INSERT a default row for a newly-registered provider, or update an -- existing row's display_order on boot reconciliation. Does NOT --- overwrite enabled / api_key on conflict — the operator's settings +-- overwrite enabled / api_key on conflict — an admin's settings -- persist across restarts. INSERT INTO cover_art_provider_settings (provider_id, enabled, display_order) VALUES ($1, $2, $3) diff --git a/internal/db/queries/covers.sql b/internal/db/queries/covers.sql index bec85f77..ecad7846 100644 --- a/internal/db/queries/covers.sql +++ b/internal/db/queries/covers.sql @@ -1,7 +1,7 @@ --- M7 #353: album cover enrichment queries. +-- Album cover enrichment queries. -- name: ListAlbumsMissingCover :many --- M7 cover-sources: returns rows the enricher should attempt. Eligible: +-- Returns rows the enricher should attempt. Eligible: -- cover_art_source IS NULL (never tried), OR cover_art_source = 'none' -- AND cover_art_sources_version != current (settled under an older -- source set; recheck against the current chain). LIMIT supplied by @@ -53,7 +53,7 @@ UPDATE albums WHERE id = $1; -- name: ClearAlbumCoverNone :exec --- M7 #380: when MBID backfill heals an album, clear its 'none' cover_art_source +-- When MBID backfill heals an album, clear its 'none' cover_art_source -- back to NULL so the enricher's next batch retries the MBCAA fetch. -- Idempotent — only updates rows currently stamped 'none'. UPDATE albums diff --git a/internal/db/queries/discover.sql b/internal/db/queries/discover.sql index ac9e057f..d44049ae 100644 --- a/internal/db/queries/discover.sql +++ b/internal/db/queries/discover.sql @@ -29,7 +29,7 @@ dormant_artists AS ( SELECT t.id, t.album_id, t.artist_id FROM tracks t JOIN dormant_artists da ON da.id = t.artist_id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = $1 @@ -56,12 +56,12 @@ SELECT t.id, t.album_id, t.artist_id -- -- GROUP BY (not SELECT DISTINCT) so the md5 ORDER BY expression need -- not appear in the select list — Postgres rejects DISTINCT + ORDER BY --- by-expression at plan time (SQLSTATE 42P10), which previously caused --- the entire Discover build to fail silently. +-- by-expression at plan time (SQLSTATE 42P10), which would fail the +-- entire Discover build. SELECT t.id, t.album_id, t.artist_id FROM general_likes gl JOIN tracks t ON t.id = gl.track_id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND gl.user_id != $1 AND NOT EXISTS ( SELECT 1 FROM play_events pe @@ -88,7 +88,7 @@ SELECT t.id, t.album_id, t.artist_id -- $1 = user_id, $2 = date string for md5 ordering. SELECT t.id, t.album_id, t.artist_id FROM tracks t - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM play_events pe WHERE pe.user_id = $1 @@ -108,7 +108,7 @@ SELECT t.id, t.album_id, t.artist_id -- name: ListTasteUnheardTracksForDiscover :many -- Taste-targeted novelty: unheard tracks whose genres overlap the user's --- taste-profile tags (taste_profile_tags, #796), ranked by summed tag +-- taste-profile tags (taste_profile_tags), ranked by summed tag -- weight — "new to you, but your vibe" rather than the crude random arm. -- Genres live inline on tracks.genre as a delimited string, split the -- same way the radio tag_overlap arm does (regexp_split_to_table on @@ -116,13 +116,13 @@ SELECT t.id, t.album_id, t.artist_id -- when the user has no taste tags yet (cold start), so the caller -- redistributes its slots to the other buckets. Stamped 'taste_unheard'. -- --- The per-album and per-artist caps apply BEFORE the LIMIT (#5356). Summed --- weight rewards a track for carrying many of the user's tags, so a few --- artists whose every track is tagged with the whole profile take every row --- of a plain LIMIT; the caller's caps then left 6 of 120 on the deploy, and --- the best-performing arm handed its slots to the others. Ranking within --- album, then within artist over what the album cap kept, is the same walk --- capByAlbumAndArtist makes, so the caller's caps keep everything here. +-- The per-album and per-artist caps apply BEFORE the LIMIT. Summed weight +-- rewards a track for carrying many of the user's tags, so a few artists +-- whose every track is tagged with the whole profile would take every row of +-- a plain LIMIT, the caller's caps would then leave a handful, and the arm +-- would hand its slots to the others. Ranking within album, then within +-- artist over what the album cap kept, is the same walk capByAlbumAndArtist +-- makes, so the caller's caps keep everything here. WITH scored AS ( SELECT t.id, t.album_id, t.artist_id, SUM(nt.weight) AS weight, @@ -130,7 +130,7 @@ WITH scored AS ( FROM tracks t JOIN LATERAL regexp_split_to_table(coalesce(t.genre, ''), '[;,]') AS g_split(g) ON true JOIN taste_profile_tags nt ON nt.user_id = sqlc.arg(user_id) AND trim(g_split.g) = nt.tag - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND nt.weight > 0 AND trim(g_split.g) <> '' AND NOT EXISTS ( diff --git a/internal/db/queries/duplicates.sql b/internal/db/queries/duplicates.sql index 12cc6f14..168e6aed 100644 --- a/internal/db/queries/duplicates.sql +++ b/internal/db/queries/duplicates.sql @@ -55,14 +55,14 @@ SELECT t.id, t.duration_ms, f.audio_stream_sha256, f.chromaprint AND f.chromaprint IS NOT NULL -- Only chromaprints taken at the current length: prints at two lengths are not -- comparable, and after a length change the backfill is still re-deriving the - -- rest (#3913). + -- rest. AND f.chromaprint_length_sec = sqlc.arg(chromaprint_length_sec) AND (t.duration_ms, t.id) > (sqlc.arg(after_duration_ms)::integer, sqlc.arg(after_id)::uuid) ORDER BY t.duration_ms, t.id LIMIT sqlc.arg(page_limit); -- name: ListDismissedDuplicateMemberSets :many --- What the operator has already said are not duplicates. A new proposal whose +-- What an admin has already said are not duplicates. A new proposal whose -- every member sat together in one of these is not proposed again. SELECT g.id, array_agg(m.track_id ORDER BY m.track_id)::uuid[] AS track_ids FROM duplicate_groups g @@ -113,8 +113,8 @@ SELECT count(*)::bigint AND (SELECT count(*) FROM duplicate_group_members m WHERE m.group_id = g.id) >= 2; -- name: CountPendingDuplicateGroupsInView :one --- CountPendingDuplicateGroups narrowed to one view of the report (M498): --- review what the operator still has to decide: every class but +-- CountPendingDuplicateGroups narrowed to one view of the report: +-- review what an admin still has to decide: every class but -- cross_release, and groups not yet classified -- cross_release the same song on different releases, which is kept, not merged SELECT count(*)::bigint @@ -130,7 +130,7 @@ SELECT count(*)::bigint -- One page of proposals in one view (see CountPendingDuplicateGroupsInView), -- newest first, flattened to one row per member so the handler folds them -- without a query per group. What each copy carries — likes and plays from --- every user — is here because it is what the operator weighs when deciding +-- every user — is here because it is what an admin weighs when deciding -- which copy to keep. WITH page AS ( SELECT g.id, g.tier, g.worst_bit_error_rate, g.detected_at, g.class, g.resolve_note @@ -186,19 +186,19 @@ UPDATE duplicate_groups WHERE id = sqlc.arg(id) AND status = 'pending'; -- name: ListDuplicateGroupsForResolve :many --- Every pending group's present copies, for the resolver (M498 #5435): what it +-- Every pending group's present copies, for the resolver: what it -- needs to classify the group and to choose the copy to keep. One row per copy, -- grouped by group. position_clash is true when another present track on the -- same album claims the same disc and track number: tags that do not fit the -- album, which the survivor rule weighs against a copy. source_verdict says --- whether the copy is a video rip ('suspect') or one the operator kept ('fine'). +-- whether the copy is a video rip ('suspect') or one an admin kept ('fine'). -- prior_class is the class the last pass gave the group, NULL for a group no -- pass has seen. release_group_mbid is the album's MusicBrainz group, from its -- tags or the library listing's match; '' when neither names one. -- acoustid_state and acoustid_recording are what AcoustID said of the file, --- when it was looked up (#6169: which song one recording cut to several slots --- is). group_id narrows it to one group, for the operator settling it (#6184); --- NULL reads them all. +-- when it was looked up: which song a recording cut to several slots is. +-- group_id narrows it to one group, for an admin settling it; NULL reads them +-- all. SELECT g.id AS group_id, g.tier, g.worst_bit_error_rate, diff --git a/internal/db/queries/events.sql b/internal/db/queries/events.sql index 2be5df2f..5a245ff5 100644 --- a/internal/db/queries/events.sql +++ b/internal/db/queries/events.sql @@ -26,7 +26,7 @@ LIMIT 1; -- name: InsertPlayEvent :one -- pick_kind is non-NULL only for system-playlist plays whose track was -- found (with a stamped kind) in the user's live snapshot for that --- variant at ingestion time (#1249, generalized in #1270). +-- variant at ingestion time. INSERT INTO play_events ( user_id, track_id, session_id, started_at, client_id, source, pick_kind, device_class @@ -38,7 +38,7 @@ RETURNING *; -- Looks a track up in the user's CURRENT snapshot of the given system -- variant and returns its pick_kind. Used at play-ingestion time to -- freeze provenance onto the play_event — snapshots rebuild daily, so --- attribution can't be reconstructed at read time (#1249/#1270). No +-- attribution can't be reconstructed at read time. No -- row = track not in today's snapshot (caller stores NULL); a row with -- NULL pick_kind = the variant doesn't stamp (yet). songs_like_artist -- is non-singleton (up to 3 mixes/user); a track in two of them takes diff --git a/internal/db/queries/fingerprints.sql b/internal/db/queries/fingerprints.sql index ae68f233..38e77634 100644 --- a/internal/db/queries/fingerprints.sql +++ b/internal/db/queries/fingerprints.sql @@ -1,6 +1,6 @@ -- name: UpsertTrackFingerprint :exec -- Written whenever a track's fingerprint is derived: by the scan when a file is --- new or its bytes changed, and by the backfill (#3908) for rows derived by an +-- new or its bytes changed, and by the backfill for rows derived by an -- older method. Replaces the row wholesale — a fingerprint of the old bytes has -- no standing once the file has changed. INSERT INTO track_fingerprints ( @@ -23,7 +23,7 @@ ON CONFLICT (track_id) DO UPDATE SET DELETE FROM track_fingerprints WHERE track_id = $1; -- name: ListTracksNeedingFingerprint :many --- The backfill's work queue (#3908): tracks with no fingerprint, or one derived +-- The backfill's work queue: tracks with no fingerprint, or one derived -- by an older method. Keyset-paged on id so a pass visits each track at most -- once. That cursor is load-bearing: an inconclusive attempt writes no row, so -- without it a file that keeps timing out would be listed again straight away @@ -34,7 +34,7 @@ SELECT t.id, t.file_path LEFT JOIN track_fingerprints f ON f.track_id = t.id WHERE t.missing_since IS NULL -- A row taken at another length is as stale as one from an older method: - -- chromaprints at two lengths cannot be compared (#3913). + -- chromaprints at two lengths cannot be compared. AND (f.track_id IS NULL OR f.fingerprint_version < sqlc.arg(current_version) OR f.chromaprint_length_sec <> sqlc.arg(chromaprint_length_sec)) diff --git a/internal/db/queries/gc.sql b/internal/db/queries/gc.sql index 6934ca41..e5d50620 100644 --- a/internal/db/queries/gc.sql +++ b/internal/db/queries/gc.sql @@ -1,11 +1,10 @@ --- Background garbage-collector / lifecycle queries. All five address --- drift findings from the 2026-06-02 audit (Scribe parent #552): --- #565 #566 #567 #574 #575. Sweeper runs on a long tick (default 1h) +-- Background garbage-collector / lifecycle queries: rows nothing else closes +-- or removes. Sweeper runs on a long tick (default 1h) -- so per-query cost is amortised; each is idempotent (re-running on -- already-closed/-deleted rows is a no-op). -- name: GcCloseStalePlayEvents :execrows --- #566: play_events rows opened more than 24h ago that never got a +-- Play_events rows opened more than 24h ago that never got a -- play_ended. Client crashed mid-track, network dropped, etc. We -- synthesize ended_at = started_at + duration_played_ms when present, -- otherwise leave duration_played_ms null and stamp ended_at = now() @@ -21,10 +20,9 @@ UPDATE play_events AND started_at < now() - INTERVAL '24 hours'; -- name: GcClosePlaySessionsWithNoRecentEvents :execrows --- #565: play_sessions.ended_at was added but never populated by any --- writer. Close sessions whose last_event_at is older than 6h — --- treating that as "user moved on" the same way audio_service does --- after grace periods. Sessions with NO events (track_count = 0) +-- play_sessions.ended_at has no other writer. Close sessions whose +-- last_event_at is older than 6h — treating that as "user moved on" the +-- same way audio_service does after grace periods. Sessions with NO events (track_count = 0) -- older than 1h are also closed (stale handshakes from clients that -- never recorded a play). ended_at is set to last_event_at so the -- session's duration reads naturally. @@ -38,7 +36,7 @@ UPDATE play_sessions ); -- name: GcExpireScrobbleQueueFailedRows :execrows --- #567: scrobble_queue rows that have been in status='failed' for +-- Scrobble_queue rows that have been in status='failed' for -- more than 14 days. The worker stops retrying after maxAttempts; -- failed rows accumulate forever otherwise. CASCADE from play_events -- already drops the row when the underlying event is deleted, so this @@ -48,7 +46,7 @@ DELETE FROM scrobble_queue AND enqueued_at < now() - INTERVAL '14 days'; -- name: GcResetStuckSystemPlaylistRuns :execrows --- #574: system_playlist_runs.in_flight = true can wedge on a +-- System_playlist_runs.in_flight = true can wedge on a -- goroutine panic between SET in_flight=true and SET in_flight=false. -- The duplicate-prevention check refuses to start a fresh regen while -- in_flight, so a stuck row blocks all future regens for that user. @@ -61,7 +59,7 @@ UPDATE system_playlist_runs AND last_run_at < now() - INTERVAL '10 minutes'; -- name: GcDeleteExpiredPasswordResets :execrows --- #575: password_resets accumulates expired + used rows forever. The +-- Password_resets accumulates expired + used rows forever. The -- validation path already rejects them; this just keeps the table -- from growing unbounded. Used rows are kept for 7 days for audit; -- unused expired rows go immediately. diff --git a/internal/db/queries/history.sql b/internal/db/queries/history.sql index bd56e828..ab39bba5 100644 --- a/internal/db/queries/history.sql +++ b/internal/db/queries/history.sql @@ -1,5 +1,5 @@ -- name: ListUserHistory :many --- M7 #365: track-level listening history. Returns one row per +-- Track-level listening history. Returns one row per -- play_events entry, newest first, joined with tracks/albums/artists -- so the SPA can render a TrackRef without follow-up fetches. Filters -- out skipped events and tracks the user has quarantined (per-user diff --git a/internal/db/queries/imports.sql b/internal/db/queries/imports.sql index a44bd1ea..e373303c 100644 --- a/internal/db/queries/imports.sql +++ b/internal/db/queries/imports.sql @@ -1,4 +1,4 @@ --- Import with verification (M502 #5522). +-- Import with verification. -- name: ListLiveGrabs :many -- Every download still running or waiting on its import, oldest first. @@ -52,7 +52,7 @@ UPDATE grabs import_forced = false, imported_at = CASE WHEN sqlc.arg(import_state)::text = 'imported' THEN now() ELSE imported_at END, -- The files were copied, so an imported download goes on sharing - -- until the lifecycle removes it (#5523). + -- until the lifecycle removes it. seeding = sqlc.arg(import_state)::text = 'imported', imported_files = sqlc.arg(imported_files), destination = sqlc.arg(destination), @@ -114,7 +114,7 @@ SELECT DISTINCT COALESCE(t.disc_number, 1)::int AS disc, t.track_number::int AS -- name: ListGrabs :many -- The Wanted view's Activity tab: downloads waiting on a person first, then -- the rest still running, then the ended ones newest first. Each carries --- what is wrong with it now, if anything (#5523). +-- what is wrong with it now, if anything. SELECT g.id, g.want_id, g.name, g.indexer, g.client, g.quality, g.origin, g.forced, g.grabbed_at, g.ended_at, g.end_reason, g.end_detail, g.progress, g.import_state, g.import_reason, g.import_detail, g.import_updated_at, g.imported_at, g.imported_files, @@ -142,7 +142,7 @@ SELECT b.info_hash, b.name, b.reason, b.detail, b.blocked_at, b.expires_at, w.ti LIMIT sqlc.arg(max_rows) OFFSET sqlc.arg(skip); -- name: ListUpgradeTracks :many --- The tracks an upgrade may replace (#5524): the present tracks of the album +-- The tracks an upgrade may replace: the present tracks of the album -- the want holds. SELECT id, file_path, mbid, COALESCE(disc_number, 1)::int AS disc, track_number, file_format, file_size, duration_ms, source_verdict @@ -167,7 +167,7 @@ UPDATE tracks SET source_verdict = NULL, source_verdict_at = NULL -- name: ListImportedGrabsHolding :many -- The downloads Minstrel imported into the folder holding path: the releases a -- file there came from, to block when the files proved not to be the album --- (#6169). A file imported before Minstrel downloaded anything has none. +--. A file imported before Minstrel downloaded anything has none. SELECT id, info_hash, name, want_id FROM grabs WHERE import_state = 'imported' diff --git a/internal/db/queries/library_changes.sql b/internal/db/queries/library_changes.sql index 86a17848..0437b36a 100644 --- a/internal/db/queries/library_changes.sql +++ b/internal/db/queries/library_changes.sql @@ -3,11 +3,10 @@ INSERT INTO library_changes (entity_type, entity_id, op) VALUES ($1, $2, $3); -- name: InsertLibraryChanges :exec --- Batch form, for a mutation that touches many rows at once (#2704: the scan's --- reconcile pass can mark up to a quarter of a library missing in one go). --- Every caller before this one changed a single entity, so per-row inserts --- were the right shape; a loop here would be thousands of round-trips inside --- a scan that is already the slow path. +-- Batch form, for a mutation that touches many rows at once (the scan's +-- reconcile pass can mark up to a quarter of a library missing in one go). A +-- loop of single inserts would be thousands of round-trips inside a scan that +-- is already the slow path. INSERT INTO library_changes (entity_type, entity_id, op) SELECT sqlc.arg(entity_type)::text, unnest(sqlc.arg(entity_ids)::text[]), diff --git a/internal/db/queries/library_settings.sql b/internal/db/queries/library_settings.sql index 2d892500..99d0fd37 100644 --- a/internal/db/queries/library_settings.sql +++ b/internal/db/queries/library_settings.sql @@ -8,7 +8,7 @@ UPDATE library_settings WHERE id = true RETURNING *; --- A library folder that moved (#6129): every path under old_root is rewritten +-- A library folder that moved: every path under old_root is rewritten -- to the same place under new_root, so the tracks keep their ids and with them -- their plays, likes and playlists. updated_at is left alone on purpose: the -- scan compares it with each file's mtime, and the files have not changed. diff --git a/internal/db/queries/lifecycle.sql b/internal/db/queries/lifecycle.sql index 18f49932..8b5b21c0 100644 --- a/internal/db/queries/lifecycle.sql +++ b/internal/db/queries/lifecycle.sql @@ -1,4 +1,4 @@ --- The download lifecycle (M502 #5523): the intervention ledger, giving a +-- The download lifecycle: the intervention ledger, giving a -- download up, and seeding after import. -- name: RaiseIntervention :one @@ -79,7 +79,7 @@ RETURNING *; -- name: CountGoneGrabs :one -- How many of a release's grabs ended gone since a time: a release that keeps --- leaving its client is held back rather than taken on every pass (#6176). +-- leaving its client is held back rather than taken on every pass. SELECT count(*) FROM grabs WHERE info_hash = sqlc.arg(info_hash) AND end_reason = 'gone' AND ended_at > sqlc.arg(since); diff --git a/internal/db/queries/likes.sql b/internal/db/queries/likes.sql index 350b7f59..11289c29 100644 --- a/internal/db/queries/likes.sql +++ b/internal/db/queries/likes.sql @@ -1,5 +1,5 @@ -- name: LikeTrack :many --- A like is on the song (M498 #5438): every copy of it sharing the track's +-- A like is on the song: every copy of it sharing the track's -- song_key is liked. Returns the tracks newly liked, for the sync log; empty -- when the song was already liked. INSERT INTO general_likes (user_id, track_id) diff --git a/internal/db/queries/loudness.sql b/internal/db/queries/loudness.sql index 90a92c2d..39087d72 100644 --- a/internal/db/queries/loudness.sql +++ b/internal/db/queries/loudness.sql @@ -1,5 +1,5 @@ -- name: UpsertTrackLoudness :exec --- Written when the backfill measures a track (#4995). Replaces the row +-- Written when the backfill measures a track. Replaces the row -- wholesale: a measurement of the old bytes has no standing once the file has -- changed. INSERT INTO track_loudness ( @@ -78,7 +78,7 @@ UPDATE loudness_settings RETURNING *; -- name: ListAlbumsNeedingLoudness :many --- The album pass's work queue (#4996): albums whose present tracks or their +-- The album pass's work queue: albums whose present tracks or their -- measurements have changed since album loudness was last computed, or that -- never had it. The digest is over every present track's id and the -- measurement it holds at the current version ('-' for none), so a track @@ -154,7 +154,7 @@ SELECT integrated_lufs, true_peak_dbtp FROM album_loudness WHERE album_id = $1; -- name: GetReplayGainByTrackIDs :many -- Track and album loudness for a set of tracks, for every surface that hands --- gains to a client (#4997). A measurement from an older analysis version is +-- gains to a client. A measurement from an older analysis version is -- still delivered: it is a better gain than none until the backfill redoes it. SELECT t.id, tl.integrated_lufs AS track_lufs, diff --git a/internal/db/queries/merge.sql b/internal/db/queries/merge.sql index 914f81fd..4ea5001d 100644 --- a/internal/db/queries/merge.sql +++ b/internal/db/queries/merge.sql @@ -1,4 +1,4 @@ --- Duplicate merge (Scribe #3911). Every statement here runs inside the one +-- Duplicate merge. Every statement here runs inside the one -- transaction library.MergeDuplicateGroup opens, after the removed copy's file -- is already gone. The loser's own track row is deleted last with DeleteTrack; -- what these do is move everything it carries onto the survivor first, so that diff --git a/internal/db/queries/missing_pairs.sql b/internal/db/queries/missing_pairs.sql index 89479d22..f7eec982 100644 --- a/internal/db/queries/missing_pairs.sql +++ b/internal/db/queries/missing_pairs.sql @@ -1,26 +1,26 @@ --- Missing-pair pass (M485). A track marked missing whose replacement is already +-- Missing-pair pass. A track marked missing whose replacement is already -- on disk under another row — most often a quality upgrade, mp3 → flac at a new -- path — is folded into that replacement, so its history moves across and -- re-acquisition stops chasing music the library already has. -- --- Move adoption (#2528) cannot catch these: it links a new file to a missing row +-- Move adoption cannot catch these: it links a new file to a missing row -- at the moment the file is first inserted, and the replacement arrives as a -- re-encode with no recording MBID, so it has nothing to match on. AcoustID may -- give it an MBID later; by then both rows exist and only a fold can join them. -- name: ListMissingTrackPairs :many -- Every candidate (missing, present) pair, with how many candidates each side --- has. Two rules, measured on the operator's library on 2026-10-07 (#5274): +-- has. Two rules: -- -- mbid the same recording MBID, within the album group (the album row, or --- albums sharing a release group). 142 of 359 pairs. --- title the same album row and the same title, ignoring case. 213 more. The +-- albums sharing a release group). +-- title the same album row and the same title, ignoring case. The -- replacements are often not the original file — another edition, -- re-encoded, untagged — so the metadata is what is left to go on. -- --- Deliberately NOT gated on duration or track position. On the 142 pairs known --- to be one recording, 18% differed by over 2s (old mp3 durations are poor --- estimates), and the poorly tagged set is exactly where numbering is broken. +-- Deliberately NOT gated on duration or track position. Pairs known to be one +-- recording often differ by over 2s (old mp3 durations are poor estimates), +-- and the poorly tagged set is exactly where numbering is broken. -- -- A pair whose MBIDs are both set and differ is not a title match: the tags say -- they are different recordings. The caller folds a pair only when each side has diff --git a/internal/db/queries/notifications.sql b/internal/db/queries/notifications.sql index 26f84b8a..34e15991 100644 --- a/internal/db/queries/notifications.sql +++ b/internal/db/queries/notifications.sql @@ -1,4 +1,4 @@ --- M489 notifications inbox (#726). Rows are written only through +-- Notifications inbox. Rows are written only through -- internal/notifications.Notifier, which decides recipients and honours each -- recipient's inbox preference before it reaches these. @@ -18,10 +18,10 @@ RETURNING id; -- ten. Every coalesced kind's event is "N more happened", never a restated -- total, so the row always says what is new since it was last read. -- --- created_at and emailed_at are left alone (M542): an update changes what the --- notice says, not when it arrived. Moving created_at made the phone announce --- it again and clearing emailed_at sent it again, so a notice the hourly --- duplicate pass kept growing alerted every hour. +-- created_at and emailed_at are left alone: an update changes what the +-- notice says, not when it arrived. Moving created_at would make the phone +-- announce it again and clearing emailed_at would send it again, so a notice +-- that keeps growing would alert on every update. INSERT INTO user_notifications (user_id, kind, payload, coalesce_key, emailed_at) VALUES (sqlc.arg(user_id), sqlc.arg(kind), sqlc.arg(payload), sqlc.arg(coalesce_key), CASE WHEN sqlc.arg(email_wanted)::boolean THEN NULL ELSE now() END) @@ -66,7 +66,7 @@ UPDATE user_notifications -- all read" queued offline and replayed later must not mark notices that -- arrived in between, which the user never saw. A coalesced row that grew -- since then is marked read with it: it arrived before they asked, and its --- growth is a quiet update to a notice they had seen (M542). +-- growth is a quiet update to a notice they had seen. UPDATE user_notifications SET read_at = now() WHERE user_id = sqlc.arg(user_id) @@ -104,7 +104,7 @@ ON CONFLICT (user_id, kind) DO UPDATE SET email = EXCLUDED.email, updated_at = now(); --- Email digest (#5346) ------------------------------------------------------ +-- Email digest ------------------------------------------------------ -- name: ListEmailPendingNotifications :many -- Every unread, un-emailed row of a user who has an address, oldest first. diff --git a/internal/db/queries/playlists.sql b/internal/db/queries/playlists.sql index fa414aaa..92585184 100644 --- a/internal/db/queries/playlists.sql +++ b/internal/db/queries/playlists.sql @@ -55,7 +55,7 @@ RETURNING id, cover_path; -- (track was removed from the library). The denormalized snapshot fields -- on playlist_tracks remain authoritative for title/artist/album text. -- --- Deliberately NOT filtered on missing_since (#2527), unlike every browse / +-- Deliberately NOT filtered on missing_since, unlike every browse / -- discover / mix query. A playlist entry is something the user put here on -- purpose, so a missing file stays in the list and renders as a dead row -- rather than silently vanishing; missing_since rides along so the service @@ -76,7 +76,7 @@ ORDER BY pt.position; -- Inserts at the next available position. Snapshot fields are copied -- from the tracks/albums/artists join at insert time. tracks.duration_ms -- is converted to seconds for the snapshot. pick_kind is NULL for manual --- playlists; the For You builder stamps 'taste'/'fresh' (#1249). +-- playlists; the For You builder stamps 'taste'/'fresh'. INSERT INTO playlist_tracks (playlist_id, position, track_id, title, artist_name, album_title, duration_sec, pick_kind) SELECT sqlc.arg(playlist_id)::uuid, @@ -120,5 +120,5 @@ LIMIT $2; -- name: GetPlaylistsByIDs :many -- Batched lookup used by /api/library/sync to hydrate upsert payloads --- (#357). Mirror of GetArtistsByIDs. +--. Mirror of GetArtistsByIDs. SELECT * FROM playlists WHERE id = ANY($1::uuid[]); diff --git a/internal/db/queries/reacquisition.sql b/internal/db/queries/reacquisition.sql index 5754b908..be665a19 100644 --- a/internal/db/queries/reacquisition.sql +++ b/internal/db/queries/reacquisition.sql @@ -1,6 +1,6 @@ --- Re-acquisition of missing files (milestone #290, reworked for M502 #5520). +-- Re-acquisition of missing files. -- The unit is the album: an album whose files went missing becomes a want for --- its release group, and the search schedule (#5521) looks for it. +-- its release group, and the search schedule looks for it. -- name: GetReacquisitionSettings :one SELECT * FROM reacquisition_settings WHERE id = true; @@ -20,17 +20,17 @@ RETURNING *; -- -- * it still has at least one track whose file has been missing longer than -- the grace window. Measured on missing_since, which reconcile never --- re-stamps (#2523), so it is a genuine "gone since" clock; +-- re-stamps, so it is a genuine "gone since" clock; -- * MusicBrainz can name it: the artist has an MBID, and the album a -- release MBID or a release group. The release MBID is only ever used to --- find the group, and 238 albums matched to a group carried no release --- id, so their losses were never wanted back (#6167). Albums failing this --- are counted separately (CountAlbumsMissingWithoutMbid); --- * it is on the wanted list (#5567): an album taken off the list, or +-- find the group, and an album matched to a group may carry no release +-- id. Albums failing this are counted separately +-- (CountAlbumsMissingWithoutMbid); +-- * it is on the wanted list: an album taken off the list, or -- dropped into the library by hand, is not fetched back; -- * no want already covers it: none open, and none held since its files -- went missing. A want held after the loss means the library has a full --- copy despite the missing rows (#5245), so asking again would loop. +-- copy despite the missing rows, so asking again would loop. SELECT albums.id AS album_id, albums.title AS album_title, albums.mbid AS album_mbid, diff --git a/internal/db/queries/recommendation.sql b/internal/db/queries/recommendation.sql index 740b5dce..d4c3c805 100644 --- a/internal/db/queries/recommendation.sql +++ b/internal/db/queries/recommendation.sql @@ -24,7 +24,7 @@ LEFT JOIN LATERAL ( WHERE user_id = $1 AND track_id = t.id ) pe ON true WHERE t.id <> $2 - AND t.missing_since IS NULL -- #2523: never offer a file that is gone + AND t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM play_events WHERE user_id = $1 AND track_id = t.id @@ -36,25 +36,22 @@ WHERE t.id <> $2 ); -- name: LoadRadioCandidatesV2 :many --- M4c: similarity-driven candidate pool. 6-way UNION: +-- Similarity-driven candidate pool. 6-way UNION: -- $1 user_id, $2 seed_track_id, $3 recently_played_hours, -- $4 exclude (uuid[]), $5 lb_similar K, $6 similar_artists K, -- $7 tag_overlap K, $8 likes_overlap K, $9 random_fill K, --- $10 taste_overlap K (#796 phase 2b — tracks by the user's top +-- $10 taste_overlap K (tracks by the user's top -- positively-weighted taste-profile artists, so taste-relevant tracks -- enter the pool even when the similarity/random arms miss them; scored -- in Go via TasteMatch, so sim_score here is 0 pool-inclusion), --- $11 coplay_artists K (#1533 — tracks by artists co-played across the +-- $11 coplay_artists K (tracks by artists co-played across the -- instance with the seed's artist; source='user_cooccurrence'), -- $12 order_seed (text) — see below. -- --- $12 REPLACES `ORDER BY random()` IN FOUR ARMS (#3889). Those arms returned --- a stable set only while their LIMIT exceeded the rows eligible for them: at --- that point they returned all of them and the order stopped mattering, --- because the caller sorts by track id before scoring. Below that threshold --- they returned a random SUBSET, and two builds on the same day drew --- different ones — so "daily determinism" held by accident, and only for --- libraries smaller than the limits. +-- $12 orders four arms by md5(id || seed), not random(). An arm whose LIMIT is +-- below the rows eligible for it returns a SUBSET, and with random() two +-- builds on the same day would draw different ones; the caller sorts by track +-- id before scoring, so only the subset matters. -- -- md5(id || seed) keeps the intent — an arbitrary spread that changes when -- the seed does — while making it reproducible for a given seed. The CALLER @@ -63,7 +60,7 @@ WHERE t.id <> $2 -- keeps varying, which is what a radio should do. -- Returns same shape as LoadRadioCandidates plus similarity_score column. -- --- sim_score BANDS keep the tiers in order (rule 131, #6177). Similarity edges +-- sim_score BANDS keep the eligibility tiers in order. Similarity edges -- are stored relative to the seed's best match, 0 to 1, so the arms are placed -- in bands rather than compared raw: -- tier 1 lb_similar 0.5 + 0.5 × edge (0.5, 1.0] @@ -71,9 +68,9 @@ WHERE t.id <> $2 -- tag_overlap 0.5 × share of the seed's tags -- likes_overlap 0.3, flat -- tier 3 taste_overlap, random_fill 0 --- Without the bands, the seed's weaker LB edges fell below a shared genre tag --- and the flat likes score, and the one arm that measures the seed lost to --- the ones that guess. +-- Without the bands, the seed's weaker LB edges would fall below a shared +-- genre tag and the flat likes score, and the one arm that measures the seed +-- would lose to the ones that guess. WITH seed_artist AS ( @@ -108,11 +105,10 @@ lb_similar AS ( LIMIT $5 ), similar_artists AS ( - -- Round-robin across the similar artists (#5297): each artist's first + -- Round-robin across the similar artists: each artist's first -- track, best-scoring artist first, then each one's second, and so on. - -- Ordering by artist score alone let the closest similar artist's - -- catalogue fill the whole LIMIT; on the operator's library this arm held - -- exactly one artist for all 17 seeds measured (#3879). + -- Ordering by artist score alone would let the closest similar artist's + -- catalogue fill the whole LIMIT. SELECT track_id, sim_score FROM ( SELECT t.id AS track_id, asim.score * 0.5 AS sim_score, asim.score AS artist_score, @@ -138,11 +134,10 @@ tag_overlap AS ( AND t.id <> $2 GROUP BY t.id HAVING count(DISTINCT trim(g_split.g)) > 0 - -- md5 breaks the ties (#3880). A seed with one tag gives every track of - -- that genre the same score, 7,804 tracks on the operator's library, and - -- with no tie-break the LIMIT took whichever rows Postgres reached first: - -- the same slice for every seed of the genre, and not one the order seed - -- controls. + -- md5 breaks the ties. A seed with one tag gives every track of that + -- genre the same score, and with no tie-break the LIMIT would take + -- whichever rows Postgres reached first: the same slice for every seed of + -- the genre, and not one the order seed controls. ORDER BY sim_score DESC, md5(t.id::text || $12::text) LIMIT $7 ), @@ -161,12 +156,10 @@ likes_overlap AS ( LIMIT $8 ), taste_overlap AS ( - -- Round-robin across the taste artists (#3880), as similar_artists is - -- (#5297): each artist's first track, strongest artist first, then each - -- one's second. Ordered by weight then track id, the strongest artist's - -- catalogue filled the whole arm with the same lowest-id tracks every - -- time; on the operator's library that was 20 tracks of one artist of - -- 174, whose catalogue is 197 tracks. + -- Round-robin across the taste artists, as similar_artists is: each + -- artist's first track, strongest artist first, then each one's second. + -- Ordered by weight then track id, the strongest artist's catalogue would + -- fill the whole arm with the same lowest-id tracks every time. SELECT track_id, sim_score FROM ( SELECT t.id AS track_id, 0.0::float8 AS sim_score, tpa.weight, @@ -183,7 +176,7 @@ taste_overlap AS ( LIMIT $10 ), coplay_artists AS ( - -- Household co-play (#1533): tracks by artists co-played across the + -- Household co-play: tracks by artists co-played across the -- instance with the seed's artist (source='user_cooccurrence', built by -- the coplay worker). Mirrors similar_artists but from local co-occurrence -- instead of ListenBrainz; empty on single-user servers. Same 0.5 damp as @@ -237,7 +230,7 @@ FROM ( UNION ALL SELECT track_id, sim_score FROM coplay_artists UNION ALL SELECT track_id, sim_score FROM random_fill ) u -JOIN tracks t ON t.id = u.track_id AND t.missing_since IS NULL -- #2523: never offer a file that is gone +JOIN tracks t ON t.id = u.track_id AND t.missing_since IS NULL -- never offer a file that is gone JOIN albums al ON al.id = t.album_id LEFT JOIN general_likes l ON l.user_id = $1 AND l.track_id = t.id LEFT JOIN LATERAL ( @@ -255,10 +248,10 @@ GROUP BY t.id, t.title, t.album_id, t.artist_id, t.duration_ms, t.file_path, -- name: ListArtistContextPlayCountsForUser :many -- Per-artist completed-play counts split by whether each play falls in the --- CURRENT context cell, in the user's local timezone (#1531). The cell is --- daypart × weekday-type, optionally narrowed by device class (#1551): when --- $2 (the current device) is '', the device dimension is ignored (identical to --- the time-only #1531 behaviour, used by the daily mixes which have no device); +-- CURRENT context cell, in the user's local timezone. The cell is +-- daypart × weekday-type, optionally narrowed by device class: when +-- $2 (the current device) is '', the device dimension is ignored (time only, +-- for the daily mixes, which have no device); -- when $2 is set (radio), a play only counts in the cell if its device_class -- matches. Feeds the context-affinity scoring term. Skips excluded; a 365-day -- window bounds cost. Daypart buckets: night [22,5) morning [5,12) @@ -308,7 +301,7 @@ FROM plays p GROUP BY p.artist_id; -- name: GetLatestPlayDeviceClassForUser :one --- The device_class of the user's most recent play (#1551), used as the +-- The device_class of the user's most recent play, used as the -- "current device" for radio context conditioning. NULL when the latest play -- predates device capture or came from a client that didn't send one. SELECT device_class @@ -321,24 +314,24 @@ LIMIT 1; -- Per-user artist suggestions ranked by taste signal x similarity, projected -- through artist_similarity_unmatched (out-of-library candidates only). -- --- Seeds are TIERED (rule #131) so the surface never empties: +-- Seeds are TIERED so the surface never empties: -- tier 1 - taste_profile_artists.weight: engagement-graded, time-decayed and -- SIGNED by internal/taste, so an artist the user has drifted away -- from stops contributing instead of accumulating forever. -- tier 2 - likes + completed plays, used ONLY when the profile has no rows -- (new account, or before the first daily recompute). -- --- The signal is log-damped: contribution is signal x similarity, and the old --- undamped sum let one heavily-played artist's neighbours take every slot -- --- entrenching harder the MORE the user listened (issue #2367 mechanism 2). +-- The signal is log-damped: contribution is signal x similarity, and an +-- undamped sum would let one heavily-played artist's neighbours take every +-- slot -- entrenching harder the MORE the user listened. -- -- Candidates already in the library, already requested and not terminal, or -- snoozed by this user, are excluded. $1=user_id, $2=half_life_days (tier 2 -- decay), $3=limit. WITH artist_plays AS ( - -- Completed plays only. The previous seed query counted every play_event, - -- so skipping an artist repeatedly INCREASED its signal and pushed more of - -- its neighbours at the user (issue #2367 mechanism 3). + -- Completed plays only: counting every play_event would make skipping an + -- artist repeatedly INCREASE its signal and push more of its neighbours + -- at the user. SELECT t.artist_id, count(*)::bigint AS play_count FROM play_events pe JOIN tracks t ON t.id = pe.track_id @@ -395,7 +388,7 @@ contributions AS ( AND r.artist_mbid = u.candidate_mbid AND r.status NOT IN ('rejected', 'failed') ) - -- Snoozed by this user and not yet expired (#2374). Time-boxed and + -- Snoozed by this user and not yet expired. Time-boxed and -- per-user: the candidate returns on its own once snoozed_until -- passes, and stays visible to everyone else meanwhile. Deliberately -- filtered at the candidate stage, NOT folded into the score — a @@ -421,7 +414,7 @@ ORDER BY total_score DESC LIMIT $3; -- name: ListMostPlayedTracksForUser :many --- M6a: top-N tracks by completed-play count for the user. was_skipped +-- Top-N tracks by completed-play count for the user. was_skipped -- excludes skips so a user spamming next doesn't fabricate a top track. -- Quarantined tracks (per-user soft-hide from M5b) are filtered out. -- @@ -442,7 +435,7 @@ FROM plays p JOIN tracks t ON t.id = p.track_id JOIN albums ON albums.id = t.album_id JOIN artists ON artists.id = t.artist_id -WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone +WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $1 AND q.track_id = t.id @@ -469,7 +462,7 @@ JOIN tracks t ON t.id = p.track_id JOIN albums ON albums.id = t.album_id JOIN artists ON artists.id = t.artist_id WHERE t.artist_id = sqlc.arg(artist_id) - AND t.missing_since IS NULL -- #2523: never offer a file that is gone + AND t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = sqlc.arg(user_id) AND q.track_id = t.id @@ -478,7 +471,7 @@ ORDER BY p.cnt DESC, t.id LIMIT sqlc.arg(result_limit); -- name: ListLastPlayedArtistsForUser :many --- M6a: artists ranked by max(play_events.started_at) for the user, with +-- Artists ranked by max(play_events.started_at) for the user, with -- a derived cover_album_id via a representative-album lateral join (most -- recent album that has cover_art_path set). album_count joined for the -- ArtistRef wire shape. diff --git a/internal/db/queries/recommendation_metrics.sql b/internal/db/queries/recommendation_metrics.sql index 75c70a5f..f7bf5c59 100644 --- a/internal/db/queries/recommendation_metrics.sql +++ b/internal/db/queries/recommendation_metrics.sql @@ -1,16 +1,16 @@ --- Recommendation observability (#796 phase 4). Per-source play outcomes so the +-- Recommendation observability. Per-source play outcomes so the -- operator can see whether each recommendation surface is landing and tune the -- taste weights. Source is stamped on play_events when a play is launched from -- a recommendation surface; NULL means the user picked the track manually — -- those rows are INCLUDED here as the baseline control group the surfaces are --- judged against (milestone #127: delta-vs-baseline is what makes the numbers --- actionable). Raw source strings are bucketed into stable surface families in --- the Go handler; completion_n is carried so family merges can weight +-- judged against (delta-vs-baseline is what makes the numbers actionable). +-- Raw source strings are bucketed into stable surface families in the Go +-- handler; completion_n is carried so family merges can weight -- avg_completion correctly. -- name: RecommendationWeeklyTrends :many -- Weekly per-source outcome series for the tuning lab's trend view --- (#1251). Aggregated across ALL users: the tuning knobs are global, +--. Aggregated across ALL users: the tuning knobs are global, -- so judging a knob turn needs global outcomes — rows carry rates -- only, no track or user identity. NULL-source (manual) rows are -- included as the baseline family. @@ -42,12 +42,12 @@ ORDER BY 1, 2; -- name: RecommendationSourceMetricsForUser :many -- $1 user_id, $2 window_days. plays/skips are counts; avg_completion is the -- mean completion ratio over the completion_n plays that recorded one. --- pick_kind splits For You plays into taste/fresh/unattributed (#1249); +-- pick_kind splits For You plays into taste/fresh/unattributed; -- it is NULL for every other source, so those still group to one row. -- -- completion_sqsum carries the sum of SQUARED completion ratios so the Go -- handler can compute a variance — needed for the margin of error on a --- completion delta (#2495). It is the sum rather than `stddev_samp` on purpose: +-- completion delta. It is the sum rather than `stddev_samp` on purpose: -- raw source rows get merged into surface families in Go, and sums of squares -- add across groups exactly, whereas standard deviations cannot be combined -- without them. Variance = (sqsum - sum²/n) / (n-1), with sum = avg × n. diff --git a/internal/db/queries/recommendation_tuning.sql b/internal/db/queries/recommendation_tuning.sql index 92ae3bce..49c627ed 100644 --- a/internal/db/queries/recommendation_tuning.sql +++ b/internal/db/queries/recommendation_tuning.sql @@ -1,9 +1,9 @@ --- Recommendation tuning lab queries (#1250). Seeding happens via the +-- Recommendation tuning lab queries. Seeding happens via the -- recsettings boot reconcile; shipped defaults live in Go only. -- name: UpsertWeightProfileDefaults :exec -- Boot reconcile: insert the shipped defaults for a profile if the row --- doesn't exist yet. Never overwrites operator-tuned values. +-- doesn't exist yet. Never overwrites admin-tuned values. INSERT INTO recommendation_weight_profiles ( profile, base_weight, like_boost, recency_weight, skip_penalty, jitter_magnitude, context_weight, similarity_weight, taste_weight, @@ -54,8 +54,8 @@ UPDATE taste_tuning RETURNING *; -- name: UpsertDiscoverTuningDefaults :exec --- Boot reconcile for the Discover scope (#2377). Never overwrites --- operator-tuned values, same contract as the other two. +-- Boot reconcile for the Discover scope. Never overwrites +-- admin-tuned values, same contract as the other two. INSERT INTO discover_tuning (singleton, tag_overlap_weight, snooze_days) VALUES (true, $1, $2) ON CONFLICT (singleton) DO NOTHING; @@ -77,7 +77,7 @@ INSERT INTO recommendation_tuning_audit (scope, action, changes) VALUES ($1, $2, $3); -- name: ListTuningAudit :many --- Newest first; consumed by the metrics trend view (#1251) to annotate +-- Newest first; consumed by the metrics trend view to annotate -- knob turns on the timeline. SELECT id, changed_at, scope, action, changes FROM recommendation_tuning_audit diff --git a/internal/db/queries/requests.sql b/internal/db/queries/requests.sql index 27fa6c09..7eff0d01 100644 --- a/internal/db/queries/requests.sql +++ b/internal/db/queries/requests.sql @@ -111,7 +111,7 @@ SELECT * FROM requests LIMIT 1; -- name: RepointRequestsAlbumMbid :exec --- A want moved from a release id to its group (#5241) takes the requests +-- A want moved from a release id to its group takes the requests -- that name it along, so the reconciler finds it. UPDATE requests SET album_mbid = sqlc.arg(new_mbid), diff --git a/internal/db/queries/scan_runs.sql b/internal/db/queries/scan_runs.sql index c2a005df..03d1856d 100644 --- a/internal/db/queries/scan_runs.sql +++ b/internal/db/queries/scan_runs.sql @@ -1,4 +1,4 @@ --- M7 #381: scan-run lifecycle queries. +-- Scan-run lifecycle queries. -- name: InsertScanRun :one -- Creates a new scan_runs row with started_at=now(). Stages are filled in @@ -33,7 +33,7 @@ SELECT id, started_at, finished_at, library, mbid_backfill, cover_enrich, artist LIMIT 1; -- name: UpdateScanRunArtistArtEnrich :exec --- M7 cover-sources: persists the artist-art enrich stage tally JSON. +-- Persists the artist-art enrich stage tally JSON. UPDATE scan_runs SET artist_art_enrich = $2 WHERE id = $1; diff --git a/internal/db/queries/search.sql b/internal/db/queries/search.sql index 89579c2e..01c17fa3 100644 --- a/internal/db/queries/search.sql +++ b/internal/db/queries/search.sql @@ -1,4 +1,4 @@ --- Searching for wants and grabbing releases (M502 #5521). +-- Searching for wants and grabbing releases. -- name: GetAcquisitionSettings :one SELECT * FROM acquisition_settings WHERE id = true; @@ -24,7 +24,7 @@ UPDATE acquisition_settings SET last_pass_at = now(), last_pass = sqlc.arg(repor -- Wants with no download running that were never searched or not since -- @hours ago: open wants first (never-searched, then requests, then missing -- files, then followed artists' releases), then held albums wanted for an --- upgrade (#5524), which are searched at most weekly. +-- upgrade, which are searched at most weekly. SELECT w.id, w.release_group_mbid, w.artist_name, w.title, w.reason, w.upgrade, w.held_tier, w.held_quality, COALESCE(NULLIF(w.primary_type, ''), g.primary_type, '')::text AS primary_type, COALESCE(g.secondary_types, '{}')::text[] AS secondary_types, @@ -120,7 +120,7 @@ UPDATE grabs -- name: ListReleaseBlocks :many -- Every release set aside and still held back: a stalled one lifts by itself --- at expires_at (#5523). +-- at expires_at. SELECT * FROM release_blocks WHERE expires_at IS NULL OR expires_at > now() ORDER BY blocked_at DESC; diff --git a/internal/db/queries/sessions.sql b/internal/db/queries/sessions.sql index a17245fd..6db52936 100644 --- a/internal/db/queries/sessions.sql +++ b/internal/db/queries/sessions.sql @@ -41,7 +41,8 @@ DELETE FROM sessions WHERE id = $1; DELETE FROM sessions WHERE token_hash = $1; -- name: DeleteSessionForUser :execrows --- Scoped by user_id, not just id (rule #47). Keyed on the id alone, any +-- Scoped by user_id, not just id: household data is per-user. Keyed on the +-- id alone, any -- household member could revoke another member's session by guessing a uuid. -- execrows lets the handler answer 404 rather than a false 204 when the row -- isn't theirs. diff --git a/internal/db/queries/similarity.sql b/internal/db/queries/similarity.sql index 3027fe32..b4beef0d 100644 --- a/internal/db/queries/similarity.sql +++ b/internal/db/queries/similarity.sql @@ -3,8 +3,8 @@ -- ListenBrainz has never answered for, or not in 30 days. Never-fetched first, -- then the oldest answer. Freshness is read from track_similarity_fetches, not -- from the edges, so a seed whose answer matched nothing in the library still --- counts as fetched (#5296); reading the edges is what let 25 such seeds hold --- the head of the queue forever. +-- counts as fetched; reading the edges would let such seeds hold the head of +-- the queue forever. SELECT t.id, t.mbid FROM tracks t LEFT JOIN track_similarity_fetches f ON f.track_id = t.id @@ -70,9 +70,9 @@ ON CONFLICT (artist_id) DO UPDATE SET fetched_at = now(), returned = EXCLUDED.re -- -- The score is RELATIVE: each edge's LB score over the seed's best score in its -- whole cached answer, so 1.0 is the seed's closest recording and the rest fall --- below it (#6177). LB's raw scores are counts (16 to 1445 on the operator's --- library) and reached the scorer, which reads similarity as 0 to 1, so one --- edge outweighed likes, skips and recency together. The cache keeps the raw +-- below it. LB's raw scores are counts, into the thousands, and the scorer +-- reads similarity as 0 to 1, so a raw edge would outweigh likes, skips and +-- recency together. The cache keeps the raw -- answer; only the edge is scaled. -- -- Only edges of seeds the cache owns are removed, those with a fetch row. diff --git a/internal/db/queries/songs.sql b/internal/db/queries/songs.sql index b57185ef..5095c918 100644 --- a/internal/db/queries/songs.sql +++ b/internal/db/queries/songs.sql @@ -1,4 +1,4 @@ --- Song links (M498 #5438): copies of one song on different releases share a +-- Song links: copies of one song on different releases share a -- song_key (migration 0076). -- name: LinkTracksAsSong :one @@ -41,7 +41,7 @@ UPDATE tracks SET song_id = NULL -- name: ListTrackSongKeys :many -- What the mix writer needs to place a track: its song, and whether it is a --- video rip held back from the mixes (#5439). +-- video rip held back from the mixes. SELECT id, song_key, (source_verdict IS NOT DISTINCT FROM 'suspect')::boolean AS held_back FROM tracks WHERE id = ANY(sqlc.arg(ids)::uuid[]); diff --git a/internal/db/queries/suggestion_snoozes.sql b/internal/db/queries/suggestion_snoozes.sql index 952b1ef7..1bd47d0e 100644 --- a/internal/db/queries/suggestion_snoozes.sql +++ b/internal/db/queries/suggestion_snoozes.sql @@ -1,4 +1,4 @@ --- Time-boxed "not right now" on a Discover artist suggestion (#2374). +-- Time-boxed "not right now" on a Discover artist suggestion. -- -- See 0049_suggestion_snoozes.up.sql for why this is a snooze and not a -- dismissal: it records no verdict on the music, expires on its own, and diff --git a/internal/db/queries/system_mixes.sql b/internal/db/queries/system_mixes.sql index bd5126da..e83b2da3 100644 --- a/internal/db/queries/system_mixes.sql +++ b/internal/db/queries/system_mixes.sql @@ -1,18 +1,18 @@ -- Candidate queries for the discovery system-playlist mixes --- (#419-423). Each returns (id, album_id, artist_id) rows; the Go +--. Each returns (id, album_id, artist_id) rows; the Go -- producer caps + truncates and converts to rankedCandidate. Daily- -- deterministic md5(id||dateStr) ordering (where used) keeps a mix -- stable within a day while rotating across days; shuffle-on-play -- gives per-play variety on top. -- name: ListDeepCutsTracks :many --- #419 Deep Cuts: low-play tracks (<=2 plays) from artists the user +-- Deep Cuts: low-play tracks (<=2 plays) from artists the user -- has liked OR played heavily (>=5 non-skip plays across the artist). --- Tracks the user has skipped twice or more don't qualify (#1257): --- eligibility used to count only unskipped plays, so the most actively --- rejected tracks read as "barely heard" and kept being re-offered. --- Threshold 2 so a single accidental skip doesn't banish a track; --- passive-signal only (no dislike UI, rule #101). +-- Tracks the user has skipped twice or more don't qualify: counting only +-- unskipped plays would make the most actively rejected tracks read as +-- "barely heard" and keep re-offering them. Threshold 2 so a single +-- accidental skip doesn't banish a track; passive signal only (there is no +-- dislike button). -- $1 user_id, $2 date string. WITH affinity_artists AS ( SELECT artist_id FROM general_likes_artists WHERE user_id = $1 @@ -40,7 +40,7 @@ SELECT t.id, t.album_id, t.artist_id JOIN affinity_artists aa ON aa.artist_id = t.artist_id LEFT JOIN play_counts pc ON pc.track_id = t.id LEFT JOIN skip_counts sc ON sc.track_id = t.id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND COALESCE(pc.c, 0) <= 2 AND COALESCE(sc.c, 0) < 2 AND NOT EXISTS ( @@ -51,11 +51,10 @@ SELECT t.id, t.album_id, t.artist_id LIMIT 200; -- name: ListRediscoverTracks :many --- #420 Rediscover: tracks the user played a lot but has drifted away --- from. One blended pool (issue #1246: the old two-tier UNION was --- all-or-nothing — a single ">=6 months cold" row suppressed the whole --- ">=30 days" tier, which is how a one-song playlist shipped; the 6mo --- tier was also a strict subset of the 30d tier). Eligibility is >=3 +-- Rediscover: tracks the user played a lot but has drifted away +-- from. One blended pool, not a two-tier UNION: the ">=6 months cold" tier is +-- a strict subset of the ">=30 days" one, and a UNION that let one row of it +-- suppress the wider tier would ship a one-song playlist. Eligibility is >=3 -- non-skip plays and >=30 days cold; the bar sits at 3 rather than 5 -- so a weeks-old library still fields a pool — on young histories the -- >=5-play tracks are precisely the ones still in rotation. Ordering @@ -71,18 +70,18 @@ WITH stats AS ( SELECT t.id, t.album_id, t.artist_id FROM tracks t JOIN stats s ON s.track_id = t.id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND s.c >= 3 AND s.last_at <= now() - interval '30 days' AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $1 AND q.track_id = t.id ) - -- Declined-resurfacing cooldown (#1258): a skip on a + -- Declined-resurfacing cooldown: a skip on a -- rediscover-sourced play is the user explicitly saying "I've moved -- on" to the exact invitation this mix extends — such tracks sit -- out ~90 days instead of re-qualifying the next day. Passive - -- signal only (rule #101). + -- signal only. AND NOT EXISTS ( SELECT 1 FROM play_events rp WHERE rp.user_id = $1 @@ -97,14 +96,13 @@ SELECT t.id, t.album_id, t.artist_id LIMIT 200; -- name: ListNewForYouTracks :many --- #421 New for you, tiered per project rule #131 (#1267). The old --- single hard rule (added <30d AND direct affinity) had two failure --- modes with one root: no notion of consumption — the album you --- devoured in week one crowded the mix for three more weeks, and --- after a quiet month nothing qualified at all. +-- New for you, tiered so the mix degrades rather than vanishes. A +-- single hard rule (added <30d AND direct affinity) has no notion of +-- consumption: the album you devoured in week one would crowd the mix +-- for three more weeks, and after a quiet month nothing would qualify. -- -- "Consumed" = any track attempted for >=30s (the First Listens --- threshold, #1268): once you've meaningfully engaged, the album is +-- threshold): once you've meaningfully engaged, the album is -- no longer news. Applies to every tier — played albums leave the -- mix the next build. -- @@ -151,7 +149,7 @@ albums_tiered AS ( SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier FROM tracks t JOIN albums_tiered alt ON alt.album_id = t.album_id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $1 AND q.track_id = t.id @@ -161,18 +159,18 @@ SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier LIMIT 300; -- name: ListOnThisDayTracks :many --- #422 On This Day: tracks the user played around this calendar date +-- On This Day: tracks the user played around this calendar date -- (±10 day-of-year) in the past, excluding the very recent (>30 days -- ago) so it's nostalgic, not just "last week". Weighted by how much --- they were played in those windows. Floor relaxed from 60→30 days --- and window ±7→±10 so it surfaces on a months-old library instead --- of needing a full year of history; still skips cleanly (no rows → --- no playlist) when there's no qualifying history yet. --- Day-of-year distance is circular (#1256): plain ABS made Dec 28 vs --- Jan 3 read as 359 days apart, silently gutting the window for ~3 --- weeks around every New Year — precisely when holiday nostalgia is --- the point. LEAST(d, 365-d) wraps the boundary; leap-year drift of --- ±1 is absorbed by the ±10 window. The build date ($2, already here +-- they were played in those windows. The 30-day floor and ±10 window +-- let it surface on a months-old library rather than needing a full +-- year of history; it still skips cleanly (no rows → no playlist) +-- when there's no qualifying history yet. +-- Day-of-year distance is circular: plain ABS would read Dec 28 vs +-- Jan 3 as 359 days apart, gutting the window for ~3 weeks around +-- every New Year — precisely when holiday nostalgia is the point. +-- LEAST(d, 365-d) wraps the boundary; leap-year drift of ±1 is absorbed +-- by the ±10 window. The build date ($2, already here -- for the md5 rotation) anchors "today" instead of now() so the -- window is testable and consistent with the mix's daily determinism. -- $1 user_id, $2 date string. @@ -190,7 +188,7 @@ WITH windowed AS ( SELECT t.id, t.album_id, t.artist_id FROM tracks t JOIN windowed w ON w.track_id = t.id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND NOT EXISTS ( SELECT 1 FROM quarantine q WHERE q.user_id = $1 AND q.track_id = t.id @@ -199,11 +197,10 @@ SELECT t.id, t.album_id, t.artist_id LIMIT 200; -- name: ListFirstListensTracks :many --- #423 First Listens: songs the user has never heard or even --- attempted. "Attempted" is track-level with a >=30s listen threshold --- (#1268, operator decision 2026-07-03) — the old version disqualified --- a whole album on ANY play_event, so a 2-second accidental brush --- banished it permanently. +-- First Listens: songs the user has never heard or even +-- attempted. "Attempted" is track-level with a >=30s listen threshold: +-- disqualifying a whole album on ANY play_event would let a 2-second +-- accidental brush banish it permanently. -- -- tier 1 the exact desire: albums with ZERO attempted tracks -- tier 2 step back: barely-attempted albums (<=25% of tracks @@ -244,7 +241,7 @@ SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier FROM tracks t JOIN albums al ON al.id = t.album_id JOIN albums_tiered alt ON alt.album_id = al.id - WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone + WHERE t.missing_since IS NULL -- never offer a file that is gone AND alt.tier IS NOT NULL AND NOT EXISTS (SELECT 1 FROM attempted a WHERE a.track_id = t.id) AND NOT EXISTS ( diff --git a/internal/db/queries/system_playlist_rotation.sql b/internal/db/queries/system_playlist_rotation.sql index 3f95050e..d58a792a 100644 --- a/internal/db/queries/system_playlist_rotation.sql +++ b/internal/db/queries/system_playlist_rotation.sql @@ -1,4 +1,4 @@ --- Rotation state for system-playlist sample-history dedup (#415). +-- Rotation state for system-playlist sample-history dedup. -- One row per (user, playlist_kind). played_track_ids accumulates -- tracks heard from that playlist in the current rotation; shuffle -- prefers ids NOT in this set, then resets when the snapshot is diff --git a/internal/db/queries/system_playlists.sql b/internal/db/queries/system_playlists.sql index 7c97a7a1..f5f76d0f 100644 --- a/internal/db/queries/system_playlists.sql +++ b/internal/db/queries/system_playlists.sql @@ -1,4 +1,4 @@ --- Track picks here join `tracks ... AND t.missing_since IS NULL` (#2523): a +-- Track picks here join `tracks ... AND t.missing_since IS NULL`: a -- seed or For-You candidate has to be something that can actually play. Note -- this only affects newly GENERATED playlists — already-stored system -- playlists keep their rows until the next daily rebuild, which is why the @@ -6,7 +6,7 @@ -- also serves user-curated playlists, where hiding a track the user added -- themselves would be wrong). --- M7 #352 slice 2: system-generated playlist queries. +-- System-generated playlist queries. -- name: ListActiveUsersForSystemPlaylists :many -- Active = had a play in the last 7 days. The cron iterates this list. @@ -56,22 +56,22 @@ UPDATE system_playlist_runs SET in_flight = false WHERE in_flight = true; -- name: PickSeedArtists :many -- Top-12 most-engaged distinct artist candidates, tiered so the --- "Songs like X" mixes never silently vanish (#1255): the old hard --- 7-day window emptied the seed pool after a quiet week, and the --- daily atomic-replace build then deleted every existing mix until --- the user played something again. Same fallback shape as +-- "Songs like X" mixes never silently vanish: a hard 7-day window +-- would empty the seed pool after a quiet week, and the daily +-- atomic-replace build would then delete every existing mix until the +-- user played something again. Same fallback shape as -- PickTopPlayedTracksForUser (For You's seeds): -- tier 0 engagement in the last 7 days -- tier 1 (only if tier 0 empty) last 30 days -- tier 2 (only if tiers 0+1 empty) all-time -- tier 3 (only if 0-2 empty) liked artists with no play history -- All returned rows share one tier; produceSeedMixes maps it onto the --- rule-#131 pick-kind ladder and stamps the built tracks, so metrics +-- tiered pick-kind ladder and stamps the built tracks, so metrics -- can compare mixes seeded from fresh vs stale engagement. -- Score = unskipped-play count + 5 if user has liked the artist. The -- Go-side picker (pickSeedArtistsForDay) shuffles the 12 -- daily-deterministically and takes songsLikeSeedCount (6) so the mix --- set both rotates day-to-day and fills the dedicated Home row (#1491). +-- set both rotates day-to-day and fills the dedicated Home row. WITH liked AS ( SELECT gla.artist_id FROM general_likes_artists gla WHERE gla.user_id = $1 ), @@ -163,7 +163,7 @@ alltime AS ( ), -- A likes-only read still joins tracks: a like outlives its file, and a -- seed whose file is gone points For You at something the user can't hear --- (#2701). The other two tiers get the same filter from their play_events +--. The other two tiers get the same filter from their play_events -- join. liked AS ( SELECT gl.track_id AS id, 0::bigint AS c, 2 AS tier @@ -206,7 +206,7 @@ SELECT COALESCE( FROM tracks t JOIN albums a ON a.id = t.album_id WHERE t.artist_id = $2 - AND t.missing_since IS NULL -- #2701: the play branch filters; this one must too + AND t.missing_since IS NULL -- the play branch filters; this one must too ORDER BY a.release_date DESC NULLS LAST, t.disc_number NULLS LAST, t.track_number NULLS LAST, diff --git a/internal/db/queries/tag_settings.sql b/internal/db/queries/tag_settings.sql index 03748656..f48664e3 100644 --- a/internal/db/queries/tag_settings.sql +++ b/internal/db/queries/tag_settings.sql @@ -1,4 +1,4 @@ --- Tag-enrichment provider settings queries (milestone #160, #1490 Step 2). +-- Tag-enrichment provider settings queries. -- The tags.SettingsService consumes these — parallel to the cover-art -- SettingsService (coverart_settings.sql). Keeping the two independent -- means a new tag source is added without touching the art settings. @@ -12,7 +12,7 @@ SELECT provider_id, enabled, api_key, display_order, created_at, updated_at -- name: UpsertTagProviderSettings :exec -- INSERT a default row for a newly-registered provider, or refresh an -- existing row's display_order on boot. Does NOT overwrite enabled / --- api_key on conflict — the operator's settings persist across restarts. +-- api_key on conflict — an admin's settings persist across restarts. INSERT INTO tag_provider_settings (provider_id, enabled, display_order) VALUES ($1, $2, $3) ON CONFLICT (provider_id) DO UPDATE diff --git a/internal/db/queries/taste_profile.sql b/internal/db/queries/taste_profile.sql index 77746987..d04da13f 100644 --- a/internal/db/queries/taste_profile.sql +++ b/internal/db/queries/taste_profile.sql @@ -1,13 +1,13 @@ --- Taste profile (#796 phase 1). The daily build computes signed artist/tag +-- Taste profile. The daily build computes signed artist/tag -- weights in Go (graded engagement + decay + like bonus) and atomic-replaces --- them here. Read queries serve "top taste" lookups (used in phase 2). +-- them here. Read queries serve the scorer's "top taste" lookups. -- name: ListPlayEngagementInputsForUser :many -- One row per play in the decay-relevant window. completion is the play's -- completion ratio (precomputed column when present, else duration_played / -- track duration, clamped to [0,1]); age_days drives the time-decay. Genre -- is split into tags in Go; release_date derives the decade for the era --- facet (#1530). Quarantined tracks are excluded. +-- facet. Quarantined tracks are excluded. SELECT t.id AS track_id, t.artist_id, @@ -31,7 +31,7 @@ WHERE pe.user_id = $1 -- name: ListLikedTrackTasteInputsForUser :many -- (track_id, artist_id, genre, release_date) for each track the user has -- explicitly liked. Feeds the track-like bonus into the liked track's artist, --- tags, and era (#1530); track_id keys the enriched track_tags lookup (#1490). +-- tags, and era; track_id keys the enriched track_tags lookup. SELECT t.id AS track_id, t.artist_id, t.genre, a.release_date FROM general_likes gl JOIN tracks t ON t.id = gl.track_id @@ -57,7 +57,7 @@ INSERT INTO taste_profile_tags (user_id, tag, weight) VALUES ($1, $2, $3); -- name: ListTasteProfileArtistsForUser :many --- Top-weighted taste artists (phase-2 consumption + tests). +-- Top-weighted taste artists, for the scorer and tests. SELECT artist_id, weight FROM taste_profile_artists WHERE user_id = $1 @@ -79,7 +79,7 @@ INSERT INTO taste_profile_eras (user_id, era, weight) VALUES ($1, $2, $3); -- name: ListTasteProfileErasForUser :many --- Top-weighted taste eras (#1530); consumed by the scorer's era term. +-- Top-weighted taste eras; consumed by the scorer's era term. SELECT era, weight FROM taste_profile_eras WHERE user_id = $1 @@ -94,7 +94,7 @@ INSERT INTO taste_profile_moods (user_id, mood, weight) VALUES ($1, $2, $3); -- name: ListTasteProfileMoodsForUser :many --- Top-weighted taste moods (#1534); consumed by the scorer's mood term. +-- Top-weighted taste moods; consumed by the scorer's mood term. SELECT mood, weight FROM taste_profile_moods WHERE user_id = $1 diff --git a/internal/db/queries/track_tags.sql b/internal/db/queries/track_tags.sql index 9449265a..44c003c5 100644 --- a/internal/db/queries/track_tags.sql +++ b/internal/db/queries/track_tags.sql @@ -1,4 +1,4 @@ --- Track-tag enrichment queries (milestone #160, #1490). The tag enricher +-- Track-tag enrichment queries. The tag enricher -- drains ListTracksMissingTags, fetches from the provider chain, and -- writes the merged top-K via DeleteTrackTags + InsertTrackTag, then -- stamps SetTrackTagSource. Mirrors the coverart artist-enricher shape. @@ -8,7 +8,7 @@ -- or previously settled 'none' under an older provider version. Returns -- the fields the provider chain needs — recording MBID (nullable) for -- keyed lookups, title + artist name for name-based fallback, and the --- artist MBID (nullable) for MusicBrainz's artist-tag fallback (#1519). +-- artist MBID (nullable) for MusicBrainz's artist-tag fallback. -- $1 = current tag_sources_version, $2 = limit. SELECT t.id, t.mbid, t.title, a.name AS artist_name, a.mbid AS artist_mbid FROM tracks t @@ -58,7 +58,7 @@ SELECT tt.track_id, tt.tag, tt.weight WHERE gl.user_id = $1; -- name: ListTrackTagsForTracks :many --- Enriched tags for a set of candidate tracks (#1534), so the scorer can +-- Enriched tags for a set of candidate tracks, so the scorer can -- derive each candidate's mood buckets at scoring time. One row per (track, -- tag); the caller filters to mood words via the internal/mood vocabulary. SELECT tt.track_id, tt.tag diff --git a/internal/db/queries/tracks.sql b/internal/db/queries/tracks.sql index 5dc38e4d..6be1133e 100644 --- a/internal/db/queries/tracks.sql +++ b/internal/db/queries/tracks.sql @@ -1,7 +1,7 @@ -- name: UpsertTrack :one -- file_path is the canonical identity for library scan; mbid is secondary. --- mbid is the file's tag. A looked-up id (mbid_source 'acoustid', M401) --- survives a re-read that finds no tag id, and a tag id replaces it (D4). +-- mbid is the file's tag. A looked-up id (mbid_source 'acoustid') +-- survives a re-read that finds no tag id, and a tag id replaces it. INSERT INTO tracks ( title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, @@ -25,7 +25,7 @@ ON CONFLICT (file_path) DO UPDATE SET WHEN tracks.mbid_source = 'acoustid' THEN 'acoustid' END, genre = EXCLUDED.genre, -- Stamped on update too, so a tag-repair pass marks rows as done and the - -- next scan can short-circuit them again (#2499). + -- next scan can short-circuit them again. tag_read_version = EXCLUDED.tag_read_version, updated_at = now() RETURNING *; @@ -34,7 +34,7 @@ RETURNING *; -- Track recording-MBID backfill: tracks with NULL mbid that still have -- a file to re-read. $1 caps the batch (mirrors the album backfill). -- Missing tracks are skipped: there is no file, and opening it anyway --- logged a warning per track on every scan (#5139). +-- would log a warning per track on every scan. SELECT id, file_path FROM tracks WHERE mbid IS NULL @@ -92,7 +92,7 @@ WHERE title ILIKE '%' || $1::text || '%' ); -- name: ListArtistTracksForUser :many --- M6a: every track for the artist across their albums, with album_title +-- Every track for the artist across their albums, with album_title -- and artist_name joined. Honors per-user quarantine. Used by -- /api/artists/{id}/tracks for the artist-card play affordance, which -- shuffles client-side. Ordering matches album/track natural order so @@ -112,7 +112,7 @@ ORDER BY albums.release_date NULLS LAST, albums.sort_title, t.disc_number NULLS FIRST, t.track_number NULLS FIRST, t.id; -- name: ListRandomTracksForUser :many --- #427 S4: backing query for GET /api/library/shuffle — the online +-- Backing query for GET /api/library/shuffle — the online -- source for "Shuffle all". N random tracks across the whole -- library, per-user-quarantine filtered. $1 user_id, $2 limit. SELECT sqlc.embed(t), @@ -135,7 +135,7 @@ LIMIT $2; SELECT COUNT(*) FROM tracks WHERE artist_id = $1; -- name: DeleteTrack :one --- M7 #372: hard delete with FK cascade. The CASCADE on track_id from +-- Hard delete with FK cascade. The CASCADE on track_id from -- play_events / general_likes_tracks / quarantine / -- quarantine_actions handles their cleanup. RETURNING gives us -- album_id + artist_id for the album-empty / artist-empty cascade @@ -145,11 +145,11 @@ RETURNING id, album_id, artist_id, file_path, mbid; -- name: GetTracksByIDs :many -- Batched lookup used by /api/library/sync to hydrate upsert payloads --- (#357). Mirror of GetArtistsByIDs. +--. Mirror of GetArtistsByIDs. SELECT * FROM tracks WHERE id = ANY($1::uuid[]); -- name: FindMissingTrackByMbid :many --- Move detection, strongest signal (#2528). A file that turned up at a new path +-- Move detection, strongest signal. A file that turned up at a new path -- carrying a recording MBID we already have on a MISSING row is that recording, -- moved — not a new track. -- @@ -166,11 +166,11 @@ SELECT id, file_path FROM tracks LIMIT 2; -- name: FindMissingTrackByAudioHash :many --- Move detection fallback for files with no MBID (#2528, #3914). The audio stream +-- Move detection fallback for files with no MBID. The audio stream -- hash identifies the encoded audio itself, so it survives a rename, a move and a --- retag — anything short of a re-encode. It replaced (file_size, duration_ms), --- which could pair two unrelated files that happened to share a byte count and a --- duration, and missed a file retagged in place, whose size changes. +-- retag — anything short of a re-encode. (file_size, duration_ms) would pair two +-- unrelated files that share a byte count and a duration, and miss a file +-- retagged in place, whose size changes. -- -- Same missing-only constraint and same LIMIT 2 rationale as the MBID variant. SELECT t.id, t.file_path @@ -196,7 +196,7 @@ UPDATE tracks -- name: ListTrackPathsForReconcile :many -- Every row's path + current missing mark, for the scanner's reconcile pass --- (#2523). Deliberately unfiltered and unpaged: reconcile has to compare the +--. Deliberately unfiltered and unpaged: reconcile has to compare the -- WHOLE table against what the walk saw, and a filtered subset would let rows -- outside it drift forever. Three narrow columns keep it cheap even on a -- library of a few hundred thousand tracks. @@ -217,10 +217,10 @@ UPDATE tracks AND missing_since IS NULL; -- name: ClearTrackMbids :exec --- Forgets the recording MBID of rows whose file was another song's (M498's --- wrong-slot removal). The MBID came from that file, so it names the song kept --- elsewhere on the album, and missing-pair folding would fold the emptied slot --- into it: twelve slots went that way on 2026-10-20 and were never wanted back. +-- Forgets the recording MBID of rows whose file was another song's (the +-- duplicate resolver's wrong-slot removal). The MBID came from that file, so it +-- names the song kept elsewhere on the album, and missing-pair folding would +-- fold the emptied slot into it, so it would never be wanted back. -- mbid_source goes with it (tracks_mbid_source_pair). UPDATE tracks SET mbid = NULL, mbid_source = NULL @@ -245,19 +245,18 @@ UPDATE tracks AND missing_since IS NOT NULL; -- name: ListMissingTracks :many --- The admin review surface for files the scan could not find (#2527). +-- The admin review surface for files the scan could not find. -- -- Ordered by directory, then by the file's own position within its album, --- because the unit an operator actually reasons about is a FOLDER: the case --- this was built for was three whole albums that had been reorganised, and a --- flat list ordered by timestamp presents that as forty unrelated decisions. +-- because the unit an admin actually reasons about is a FOLDER: a reorganised +-- album, listed flat by timestamp, reads as dozens of unrelated decisions. -- Grouping happens in the handler; the ordering here is what makes a group -- contiguous, so a page boundary splits a directory at worst. -- -- last_played_at is a correlated MAX rather than a join so a track with no -- plays stays in the result with NULL. It is here because "gone six months, -- never played" and "gone yesterday, played 200 times" deserve opposite --- reactions, and the operator can't tell them apart from a path. +-- reactions, and an admin can't tell them apart from a path. SELECT t.id, t.title, t.file_path, @@ -285,7 +284,7 @@ SELECT COUNT(*) FROM tracks WHERE missing_since IS NOT NULL; -- name: ListSuspectSourceTracks :many -- The admin report of present tracks whose filename looks like a video rip --- (#5410): "(Official Video)", "[Audio]", "Visualizer" and the like. +--: "(Official Video)", "[Audio]", "Visualizer" and the like. -- -- pattern is built in Go (internal/api/admin_suspect_sources.go) from the -- same marker list that labels each row, so the filter and the labels cannot @@ -308,7 +307,7 @@ SELECT t.id, t.source_verdict, t.source_verdict_at, -- The album's place on the wanted list: wanted for a clean copy, and - -- when that was last searched for (#5524). + -- when that was last searched for. COALESCE(w.upgrade, '')::text AS want_upgrade, w.searched_at AS searched_at FROM tracks t @@ -330,8 +329,8 @@ SELECT COUNT(*) FROM tracks t AND regexp_replace(t.file_path, '^.*/', '') ~* sqlc.arg(pattern)::text; -- name: FlagSuspectSourceTracks :execrows --- Holds back present tracks whose basename carries a video-rip marker (M498 --- #5439), the same pattern the report lists. A track the operator called fine +-- Holds back present tracks whose basename carries a video-rip marker, the +-- same pattern the report lists. A track an admin called fine -- keeps that verdict. UPDATE tracks SET source_verdict = 'suspect', source_verdict_at = now() @@ -348,7 +347,7 @@ UPDATE tracks AND NOT regexp_replace(file_path, '^.*/', '') ~* sqlc.arg(pattern)::text; -- name: SetTrackSourceVerdict :execrows --- The operator's call on a flagged track: 'fine' lets it back into radio and +-- An admin's call on a flagged track: 'fine' lets it back into radio and -- the mixes for good; 'suspect' holds it back again. UPDATE tracks SET source_verdict = sqlc.arg(verdict)::text, source_verdict_at = now() @@ -357,7 +356,7 @@ UPDATE tracks -- name: ListStillMissingTrackLabels :many -- Which of the tracks a scan marked missing still are once it has adopted --- moves and folded replacements (M542): the ones worth telling an admin +-- moves and folded replacements: the ones worth telling an admin -- about, by name. A quality upgrade folds into its replacement and drops out. SELECT ar.name AS artist_name, t.title FROM tracks t diff --git a/internal/db/queries/users.sql b/internal/db/queries/users.sql index e576b6b3..71e6400a 100644 --- a/internal/db/queries/users.sql +++ b/internal/db/queries/users.sql @@ -37,7 +37,7 @@ SELECT count(*) FROM users; -- name: SetSubsonicPassword :exec -- Stores (or clears with NULL) the per-user Subsonic legacy credential used -- for t/s and p auth on /rest/*. Must be plaintext; see migration 0003. Only --- ever a server-generated value, never the login password (#5026). +-- ever a server-generated value, never the login password. UPDATE users SET subsonic_password = $2 WHERE id = $1; -- name: GetUserByID :one diff --git a/internal/db/queries/wanted.sql b/internal/db/queries/wanted.sql index 16d47116..11b49a98 100644 --- a/internal/db/queries/wanted.sql +++ b/internal/db/queries/wanted.sql @@ -1,4 +1,4 @@ --- The wanted list (M502 #5520): release-type profiles, follows and wants. +-- The wanted list: release-type profiles, follows and wants. -- name: ListReleaseTypeProfiles :many SELECT p.*, @@ -145,10 +145,10 @@ SELECT a.id AS album_id, COALESCE(bool_or(t.missing_since IS NULL AND t.added_at > sqlc.arg(since)::timestamptz), false)::bool AS arrived_since, COALESCE(bool_or(t.missing_since IS NOT NULL AND t.missing_since < sqlc.arg(since)::timestamptz), false)::bool AS missing_before, -- Missing tracks no track on disk stands in for, by position or - -- title. A missing row whose song is back at a new path (#5245) is + -- title. A missing row whose song is back at a new path is -- covered; a lost bonus track is not. A track with no disc number is - -- on disc 1: Air's Love 2 stayed wanted, and was downloaded again for - -- nothing, over a missing track 6 with no disc beside a present 1/6. + -- on disc 1, so a missing track 6 with no disc beside a present 1/6 + -- does not keep the album wanted. COUNT(*) FILTER (WHERE t.missing_since IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM tracks p WHERE p.album_id = a.id AND p.missing_since IS NULL @@ -171,7 +171,7 @@ UPDATE wants WHERE id = sqlc.arg(id) AND fulfilled_at IS NULL; -- name: RepointWant :exec --- A want recorded under a release id before #5241 is moved to its group. +-- A want recorded under a release id is moved to its group. UPDATE wants SET release_group_mbid = $2, updated_at = now() WHERE id = $1; -- name: DeleteWant :execrows @@ -180,13 +180,13 @@ DELETE FROM wants WHERE id = $1; -- name: ListWants :many -- The Wanted page: open wants (all but missing ones, or only missing ones), -- the held ones, or all of them; artist_mbid narrows to one artist's page, --- want_ids to given wants. Ordered like the library (#5572): artists A–Z by the +-- want_ids to given wants. Ordered like the library: artists A–Z by the -- library's sort name when it holds the artist, each artist's albums by -- release date, so the page groups them as they arrive. q narrows to an -- artist or album title; total counts every match, past the page. SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reason, w.album_id, w.wanted_at, w.fulfilled_at, - -- Why a held album is wanted again, and the copy it has (#5524). + -- Why a held album is wanted again, and the copy it has. w.upgrade, w.held_quality, -- A request names an album before the catalogue has read it: the -- catalogue's type and date fill in once it has. @@ -196,10 +196,10 @@ SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reas u.username AS requested_by, la.id AS library_artist_id, COUNT(*) OVER () AS total, - -- The search schedule's last look (#5521), and the download running. + -- The search schedule's last look, and the download running. w.searched_at, w.search_outcome, w.search_considered, w.best_release, w.best_verdict, w.search_error, -- Its latest download, running or ended, and what the import made of - -- it (#5522). + -- it. gr.id AS grab_id, gr.name AS grab_name, gr.grabbed_at, gr.origin AS grab_origin, gr.quality AS grab_quality, gr.ended_at AS grab_ended_at, gr.end_reason AS grab_end_reason, gr.progress AS grab_progress, gr.import_state AS grab_import_state, @@ -219,7 +219,7 @@ SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reas ELSE w.fulfilled_at IS NULL AND w.reason <> 'missing' END AND (sqlc.arg(artist_mbid)::text = '' OR w.artist_mbid = sqlc.arg(artist_mbid)::text) - -- want_ids narrows to given wants: the Missing page's folders (M504). + -- want_ids narrows to given wants: the Missing page's folders. AND (COALESCE(cardinality(sqlc.arg(want_ids)::uuid[]), 0) = 0 OR w.id = ANY(sqlc.arg(want_ids)::uuid[])) AND (sqlc.arg(q)::text = '' OR strpos(lower(w.artist_name), lower(sqlc.arg(q)::text)) > 0 @@ -230,7 +230,7 @@ SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reas LIMIT sqlc.arg(max_rows) OFFSET sqlc.arg(skip); -- name: ListWantArtists :many --- The Wanted view's main page (#5572): every artist on the list, A–Z like +-- The Wanted view's main page: every artist on the list, A–Z like -- the library, with how many of their albums are held of how many are on -- the list. q matches the artist's name or any of their albums' titles; -- artist_mbid narrows to one artist, for their page's header. @@ -263,7 +263,7 @@ SELECT a.artist_mbid, a.artist_name::text AS artist_name, a.held, a.wanted, a.mi LIMIT sqlc.arg(max_rows) OFFSET sqlc.arg(skip); -- name: GetWantAlbum :one --- What the Wanted view's track list needs of a want (#5572). +-- What the Wanted view's track list needs of a want. SELECT id, release_group_mbid, album_id FROM wants WHERE id = $1; -- name: ListLibraryAlbumTracks :many @@ -278,15 +278,15 @@ SELECT title, disc_number, track_number, duration_ms, (missing_since IS NOT NULL SELECT COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason <> 'missing')::bigint AS wanted, COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason = 'missing')::bigint AS missing, COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL)::bigint AS held, - -- Held albums wanted for a better copy (#5524). + -- Held albums wanted for a better copy. COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL AND upgrade <> '')::bigint AS upgrades, COUNT(DISTINCT artist_mbid)::bigint AS artists, - -- Library albums waiting to be matched by hand (#5579). + -- Library albums waiting to be matched by hand. (SELECT COUNT(*) FROM library_album_matches m WHERE m.outcome = 'unmatched' AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))::bigint AS unmatched, (SELECT COUNT(*) FROM follows)::bigint AS following, - -- The Wanted view's other tabs (#5522): downloads running, those + -- The Wanted view's other tabs: downloads running, those -- waiting on a person, and releases set aside. (SELECT COUNT(*) FROM grabs WHERE ended_at IS NULL)::bigint AS activity, (SELECT COUNT(*) FROM grabs @@ -295,7 +295,7 @@ SELECT COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason <> 'missing')::big WHERE b.expires_at IS NULL OR b.expires_at > now())::bigint AS blocked FROM wants; --- The one-time listing of the library (#5567). --------------------------- +-- The one-time listing of the library. --------------------------- -- name: GetLibraryListing :one SELECT library_listing_started_at, library_listed_at, library_unlisted @@ -332,7 +332,7 @@ ON CONFLICT (release_group_mbid) DO NOTHING; -- name: RecordListedAlbums :exec -- The albums ListLibraryAlbums takes are decided as listed by their tags, --- so the matching that follows leaves them alone (#5569). +-- so the matching that follows leaves them alone. INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid) SELECT a.id, 'listed', 'tags', a.release_group_mbid FROM albums a @@ -346,9 +346,9 @@ ON CONFLICT (album_id) DO NOTHING; -- name: ListAlbumsToMatch :many -- Library albums the listing has not decided, or that an older matcher -- refused, or whose tracks changed since they were refused: each is matched --- to MusicBrainz (#5569). A library album is one from before the cutoff, or +-- to MusicBrainz. A library album is one from before the cutoff, or -- one whose tracks were: a re-tag files a track under a new album row but --- keeps the track (#5579). An album row with no tracks is not an album in +-- keeps the track. An album row with no tracks is not an album in -- the library; one a person left off stays off. SELECT a.id, a.title, a.mbid, a.release_group_mbid, ar.name AS artist_name, ar.mbid AS artist_mbid FROM albums a @@ -423,7 +423,7 @@ UPDATE acquisition_settings RETURNING library_listed_at, library_unlisted; -- name: ListUnmatchedLibraryAlbums :many --- The Collection's Unmatched section (#5579): the albums the listing could +-- The Collection's Unmatched section: the albums the listing could -- not match, and below them the ones a person left off. An album whose -- tracks have all moved to another row is gone, not unmatched. SELECT m.album_id, a.title, ar.name AS artist_name, m.detail, m.outcome, m.decided_at, @@ -459,7 +459,7 @@ UPDATE library_album_matches SET outcome = sqlc.arg(outcome), decided_at = now() WHERE album_id = sqlc.arg(album_id) AND outcome = sqlc.arg(was); --- Upgrades (#5524). ------------------------------------------------------- +-- Upgrades. ------------------------------------------------------- -- name: ListHeldWantFormats :many -- What each held album's copy is made of, for judging whether it is worth diff --git a/internal/db/queries/you_might_like.sql b/internal/db/queries/you_might_like.sql index 5b4697ab..62c2022d 100644 --- a/internal/db/queries/you_might_like.sql +++ b/internal/db/queries/you_might_like.sql @@ -1,4 +1,4 @@ --- "You might like" Home rows (#790). The daily build computes ranked +-- "You might like" Home rows. The daily build computes ranked -- album/artist IDs in Go (similarity roll-up + cold-start gate) and -- atomic-replaces them here; /api/home reads them back hydrated.