docs: server comments say what the code does, not how it came to be (#6277)

Comment blocks in cmd/minstrel and 30 smaller internal packages, rewritten
to rule 32's standard with each citation read in context: task, milestone
and rule ids go, and so do the stories they were attached to ("used to",
"Before #N", measurements from one install, quotes, pointers to Fabled
Librarian's records and to docs/superpowers files that are not in the
repo). Where a story wrapped a real constraint, the constraint stays as a
present-tense fact.

Comments that had gone stale are corrected:
- taste.Config: the tuned values are overlaid by internal/recsettings;
  it is no longer "a later follow-up".
- StorageConfig: the data dir holds art, caches and the stream secret,
  not only playlist covers.
- the Subsonic album list and getUser comments describe what they do
  today rather than a milestone that would change it.

Checked: the non-comment token stream of every changed file matches
HEAD, and gofmt -l is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 12:15:05 -04:00
co-authored by Claude Opus 5.5
parent 9f9609591d
commit d191a28217
107 changed files with 485 additions and 597 deletions
+5 -7
View File
@@ -55,14 +55,12 @@ func runAdmin(args []string) error {
}
}
// adminResetPassword resets a user's login password (password_hash). It
// recovers a locked-out operator when the bootstrap password was missed or
// the DB volume was recreated (Fable #321) without DB surgery.
// adminResetPassword resets a user's login password (password_hash), so an
// admin who is locked out can get back in without editing the database.
//
// It deliberately leaves subsonic_password alone. That column is stored in
// plain text, and it used to receive the new login password here, so any
// account recovered this way had its login password readable in the database
// (#5026). The Subsonic password is generated separately in Settings.
// It leaves subsonic_password alone: that column is stored in plain text, so
// writing the login password there would make it readable in the database.
// The Subsonic password is generated separately in Settings.
func adminResetPassword(args []string) error {
fs := flag.NewFlagSet("admin reset-password", flag.ContinueOnError)
configPath := fs.String("config", os.Getenv("MINSTREL_CONFIG"), "path to YAML config file")
+54 -67
View File
@@ -128,7 +128,7 @@ func run() error {
}
defer pool.Close()
// Fingerprinting settings (M400 #3913): one instance, shared by the scanner,
// Fingerprinting settings: one instance, shared by the scanner,
// the fingerprint backfill, the duplicate sweep and the admin API, so a save
// reaches all of them without a restart. A load failure is logged, not fatal:
// the service falls back to the shipped defaults.
@@ -136,9 +136,9 @@ func run() error {
if fpErr != nil {
logger.Warn("fingerprint settings: using defaults", "err", fpErr)
}
// The library's folders (#6129): the ones saved in Settings, or
// The library's folders: the ones saved in Settings, or
// MINSTREL_LIBRARY_SCAN_PATHS until any are. A load failure falls back to
// env, which is what the server read before the setting existed.
// the env value.
roots, rootsSaved, rootsErr := library.LoadRoots(ctx, pool, cfg.Library.ScanPaths)
switch {
case rootsErr != nil:
@@ -149,14 +149,14 @@ func run() error {
}
scanner := library.New(pool, logger, roots, fpSettings)
// Loudness analysis settings (M464 #4995): shared by the loudness backfill
// Loudness analysis settings: shared by the loudness backfill
// and the admin API, and falls back to the defaults like the above.
loudSettings, loudErr := library.NewLoudnessSettingsService(ctx, pool)
if loudErr != nil {
logger.Warn("loudness settings: using defaults", "err", loudErr)
}
// AcoustID lookup settings (M401): shared by the lookup worker and the
// AcoustID lookup settings: shared by the lookup worker and the
// admin API. A load failure leaves the lookup off.
acoustIDSettings, acoustIDErr := library.NewAcoustIDSettingsService(ctx, pool)
if acoustIDErr != nil {
@@ -194,10 +194,9 @@ func run() error {
coverEnricher := coverart.NewEnricher(pool, logger.With("component", "coverart"), coverSettings)
coverEnricher.DataDir = cfg.Storage.DataDir
// Recommendation tuning lab (#1250): seeds shipped defaults on first
// boot and pushes the daily_mix weights + taste config into package
// playlists — must precede the scheduler so the first builds score
// with the operator's tuned values, not the pre-push literals.
// Recommendation tuning: seeds the shipped defaults on first boot and
// pushes the daily-mix weights and taste config into package playlists.
// It must precede the scheduler, so the first builds use the tuned values.
recSettings, err := recsettings.New(ctx, pool, logger.With("component", "recsettings"))
if err != nil {
logger.Error("recommendation settings service init failed", "err", err)
@@ -213,7 +212,7 @@ func run() error {
logger.With("component", "scan_run"),
library.RunScanConfig{
BackfillCap: 5000,
EnrichCap: -1, // #388: no global cap; per-provider rate limits
EnrichCap: -1, // no global cap; each provider paces itself
ArtistEnrichCap: -1,
DataDir: cfg.Storage.DataDir,
},
@@ -223,14 +222,13 @@ func run() error {
}()
} else {
// No startup file walk, but still run backfill + enrich so cover
// progress doesn't stall just because the operator disabled
// scan-on-startup.
// art keeps arriving with scan-on-startup off.
go func() {
if _, err := library.RunScan(ctx, pool, nil, coverEnricher,
logger.With("component", "scan_run"),
library.RunScanConfig{
BackfillCap: 5000,
EnrichCap: -1, // #388: no global cap; per-provider rate limits
EnrichCap: -1, // no global cap; each provider paces itself
ArtistEnrichCap: -1,
DataDir: cfg.Storage.DataDir,
},
@@ -240,46 +238,44 @@ func run() error {
}()
}
// Start the ListenBrainz scrobble worker. Per spec §M4a, runs every 30s
// and drains up to 50 pending rows per tick. Per-user gating happens
// inside the worker (rows from disabled users are skipped).
// ListenBrainz scrobble worker: every 30s, drains up to 50 pending rows.
// Rows from users with scrobbling off are skipped inside the worker.
scrobbleWorker := scrobble.NewWorker(pool, listenbrainz.NewClient(), logger.With("component", "scrobble"))
go scrobbleWorker.Run(ctx)
// Start the similarity ingest worker. Per spec §M4b, runs every 1h, drains
// up to 5 played tracks + 5 played artists per tick, weekly re-fetch cap
// per row. Public LB endpoints — no token required.
// Similarity ingest worker: hourly, fetches ListenBrainz similarity for up
// to 5 played tracks and 5 played artists, each re-fetched at most weekly.
// The endpoints are public, so no token is needed.
similarityWorker := similarity.NewWorker(pool, listenbrainz.NewClient(), logger.With("component", "similarity"))
go similarityWorker.Run(ctx)
// Start the household co-play worker (#1533). Recomputes artist–artist
// Household co-play worker: recomputes artist–artist
// co-occurrence edges (source='user_cooccurrence') from play_events every
// 6h — a collaborative candidate arm for the radio/mix pools. Pure local
// SQL, no external calls; empty on single-user servers.
go coplay.NewWorker(pool, logger.With("component", "coplay")).Run(ctx)
// Fingerprint backfill (M400 #3908): fingerprints the tracks the scan never
// will — everything imported before fingerprinting existed, and rows derived
// by an older method. A worker of its own rather than a scan stage; see
// internal/library/fingerprint_backfill.go for why.
// Fingerprint backfill: fingerprints the tracks the scan skips (unchanged
// files, and rows fingerprinted by an older method). A worker of its own
// rather than a scan stage; see internal/library/fingerprint_backfill.go.
go library.NewFingerprintBackfillWorker(pool, logger.With("component", "fingerprint_backfill"), fpSettings).Run(ctx)
// Loudness backfill (M464 #4995): measures every track's loudness for
// Loudness backfill: measures every track's loudness for
// normalization, new tracks included; the scan only drops a changed file's
// measurement. See internal/library/loudness_backfill.go.
go library.NewLoudnessBackfillWorker(pool, logger.With("component", "loudness_backfill"), loudSettings).Run(ctx)
// AcoustID lookup (M401): fills the recording MBIDs untagged tracks lack,
// so the similarity arms can see them. Idle until the operator adds a key
// and switches it on. See internal/library/acoustid_lookup.go.
// AcoustID lookup: fills the recording MBIDs untagged tracks lack, so the
// similarity arms can see them. Idle until an admin adds a key and
// switches it on. See internal/library/acoustid_lookup.go.
go acoustIDLookup.Run(ctx)
// Duplicate sweep (M400 #3910): proposes groups of tracks holding one
// Duplicate sweep: proposes groups of tracks holding one
// recording, from the fingerprints above. Sweeps only when fingerprints have
// changed since the last sweep.
go library.NewDuplicateSweepWorker(pool, logger.With("component", "duplicate_sweep"), fpSettings).Run(ctx)
// Start the tag-enrichment worker (#1490). Reconciles the compiled-in
// Tag-enrichment worker: reconciles the compiled-in
// tag providers with tag_provider_settings, bumps the sources version if
// the provider set changed (re-opening settled rows), then drains tracks
// needing folksonomy tags on a periodic tick. Standalone (not in the file
@@ -298,54 +294,50 @@ func run() error {
tagEnricher := tags.NewEnricher(pool, logger.With("component", "tags"), tagSettings)
go tags.NewWorker(tagEnricher, logger.With("component", "tags")).Run(ctx)
// Start the GC worker. Runs every 1h and sweeps lifecycle tables
// that have no writer-side close path or retention policy:
// orphan play_events, stale play_sessions, expired
// scrobble_queue failures, stuck system_playlist_runs, expired
// password_resets. Each sweep is idempotent — a row that's
// already clean is a no-op. Addresses drift audit findings
// #565 #566 #567 #574 #575 (Scribe parent #552).
// GC worker: hourly, sweeps the tables that have no writer-side close
// path or retention policy: orphan play_events, stale play_sessions,
// expired scrobble_queue failures, stuck system_playlist_runs and expired
// password_resets. Each sweep is idempotent.
gcWorker := gc.NewWorker(pool, logger.With("component", "gc"))
go gcWorker.Run(ctx)
// The request reconciler completes approved requests once the library
// holds what they asked for (M502 #5520).
// Live-event bus shared between SSE subscribers (api.Mount) and
// background workers that publish (reconciler today; scanner later).
// Constructed before any service that publishes so they all share the
// same instance.
// Live-event bus shared between SSE subscribers (api.Mount) and the
// background workers that publish. Built before any of them, so they all
// share one instance.
bus := eventbus.New()
// Scan-run lifecycle events use a package-level setter rather than
// threading the bus through RunScan + TryStartScan + the Scheduler
// + every test caller. Per-process singleton, set once at startup.
library.SetEventBus(bus)
// The notifications inbox's one writer (M489), shared by every
// The notifications inbox's one writer, shared by every
// background producer started here. The API builds its own over the
// same pool and bus.
notifier := notifications.New(pool, bus, logger.With("component", "notifications"))
// The request reconciler completes approved requests once the library
// holds what they asked for.
requestReconciler := requests.NewReconciler(pool, logger.With("component", "requests"), bus)
requestReconciler.SetNotifier(notifier)
library.SetNotifier(notifier)
go requestReconciler.Run(ctx)
// Release catalogue (M502 #5519): re-reads discographies older than a
// Release catalogue: re-reads discographies older than a
// week from MusicBrainz, a batch an hour, through the process-wide
// MusicBrainz pacer it shares with tag enrichment.
cat := catalogue.New(pool, nil, logger.With("component", "catalogue"))
go cat.Run(ctx)
// The wanted list (M502 #5520): reads followed artists' discographies
// The wanted list: reads followed artists' discographies
// daily for new releases and marks wants held once the library has them.
go wanted.New(pool, cat, logger.With("component", "wanted")).Run(ctx)
// Search and grab (M502 #5521): every ten minutes, searches the
// Search and grab: every ten minutes, searches the
// configured indexers for wants due, paced by each source's
// searches-per-hour, and hands the best release to the download client
// when auto-grab is on.
go wanted.NewSearcher(pool, cat, logger.With("component", "search")).Run(ctx)
// Import with verification (M502 #5522): every minute, finished
// Import with verification: every minute, finished
// downloads are checked against their album (AcoustID, lengths, tags)
// and filed into the library with MusicBrainz tags, or refused.
var printer importer.Printer
@@ -356,13 +348,13 @@ func run() error {
imp.DataDir = cfg.Storage.DataDir
go imp.Run(ctx)
// Download lifecycle (M502 #5523): every ten minutes, a download that
// Download lifecycle: every ten minutes, a download that
// failed or that nobody shares is retried, then given up and its album
// searched again; an imported one seeds, then is removed.
go lifecycle.New(pool, logger.With("component", "lifecycle")).Run(ctx)
// Duplicate resolver (M498, M502): classifies the sweep's groups and, with
// the operator's auto-resolve on, merges the copies that are safe to merge.
// Duplicate resolver: classifies the sweep's groups and, with auto-resolve
// on, merges the copies that are safe to merge.
// Started after SetNotifier so its summaries reach admins. It asks the
// catalogue for the track list of an album whose wrong slot it must
// decide, a few a pass. See internal/library/duplicate_resolve.go.
@@ -371,15 +363,12 @@ func run() error {
TrackLists: cat.RefreshReleaseGroup, Identify: acoustIDLookup.IdentifyRecordings,
}).Run(ctx)
// Missing-file re-acquisition (milestone #290, M502 #5520). Wants back
// albums whose files have been gone longer than the grace window; the
// search schedule looks for them. Hourly tick — the grace window is
// measured in hours.
// Missing-file re-acquisition: wants back albums whose files have been
// gone longer than the grace window, for the search schedule to find.
// Hourly, because the grace window is measured in hours.
//
// A settings-load failure is logged, not fatal: NewSettingsService always
// returns a usable service holding the defaults, and running on defaults
// is far better than dropping the feature because the database hiccuped
// during boot.
// returns a usable service holding the defaults.
reacqSettings, reacqErr := reacquisition.NewSettingsService(
ctx, pool, logger.With("component", "reacquisition"))
if reacqErr != nil {
@@ -387,18 +376,18 @@ func run() error {
}
go reacquisition.NewSweeper(pool, reacqSettings, logger.With("component", "reacquisition")).Run(ctx)
// library_changes compactor (#357 follow-up). Daily tick; deletes
// library_changes compactor: daily, deletes
// rows older than the configured retention so the change-log table
// doesn't grow unbounded. Clients that drop offline longer than
// retention hit the /api/library/sync 410 fallback and resync.
libraryChangesCompactor := syncpkg.NewCompactor(pool, logger.With("component", "library_changes_compactor"))
go libraryChangesCompactor.Run(ctx)
// Notifications inbox retention (M489). Daily: read rows go after 90
// Notifications inbox retention, daily: read rows go after 90
// days, anything at all after a year.
go notifications.NewRetention(pool, logger.With("component", "notifications_retention")).Run(ctx)
// Notifications by email (M489 #5346), grouped, never one per event: new
// Notifications by email, grouped, never one per event: new
// music as a daily summary at a local hour, everything else batched an
// hour (admin-configurable) after the first item.
go notifications.NewDigest(
@@ -407,10 +396,8 @@ func run() error {
logger.With("component", "notification_digest"),
).Run(ctx)
// Per-user system-playlist scheduler (#392 Half B). Fires each
// active user's daily build at 03:00 in their stored timezone.
// Replaces the 24h-anchored cron loop (removed in the next commit
// of this arc).
// Per-user system-playlist scheduler: runs each active user's daily
// build at 03:00 in their stored timezone.
playlistScheduler, err := playlists.NewScheduler(
pool,
logger.With("component", "playlist_scheduler"),
@@ -439,7 +426,7 @@ func run() error {
scanCfg := library.RunScanConfig{
BackfillCap: 5000,
EnrichCap: -1, // #388: no global cap; per-provider rate limits
EnrichCap: -1, // no global cap; each provider paces itself
ArtistEnrichCap: -1,
DataDir: cfg.Storage.DataDir,
}
@@ -468,7 +455,7 @@ func run() error {
srv.LoudnessSettings = loudSettings
srv.AcoustIDLookup = acoustIDLookup
// The sweeper above holds this same instance, so a save from the admin
// card changes what it does on its next tick (#3936).
// card changes what it does on its next tick.
srv.ReacqSettings = reacqSettings
srv.StreamSecret = cfg.StreamSecret
httpServer := &http.Server{