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
+1 -1
View File
@@ -171,7 +171,7 @@ const (
tagOverlapWeightMax = 10.0
// Snooze duration: at least a day (anything less isn't a snooze, it's a
// flicker), at most a year — past that it's a permanent dismissal wearing a
// snooze's clothes, which is exactly the shape rule #101 rules out.
// snooze's clothes, and suggestions have no permanent "never show" control.
snoozeDaysMin = 1.0
snoozeDaysMax = 365.0
)
+36 -44
View File
@@ -1,16 +1,15 @@
// Package recsettings is the DB-backed home of the recommendation
// tuning knobs (#1250): the two scoring-weight profiles (radio /
// daily_mix) and the taste-profile build settings (engagement
// half-life + completion curve). It follows the coverart
// tuning knobs: the scoring-weight profiles (radio, daily_mix,
// songs_like), Discover's settings, and the taste-profile build
// settings (engagement half-life + completion curve). It follows the coverart
// SettingsService pattern — boot reconcile seeds shipped defaults for
// missing rows, values are cached under a RWMutex, and every change
// takes effect live (the daily_mix profile + taste config are pushed
// into package playlists; radio reads the cache per request).
//
// Framing (decision #1247): this is the defaults-discovery lab. The
// operator turns knobs to FIND good values; found-good values get
// baked back into the Shipped* functions below as new shipped
// defaults. End users and other operators should never need the card.
// This is a lab for finding good defaults: an admin turns knobs to find
// good values, and those get baked back into the Shipped* functions below
// as new shipped defaults. A typical install never needs to touch it.
package recsettings
import (
@@ -35,24 +34,23 @@ const (
ScopeRadio = "radio"
ScopeDailyMix = "daily_mix"
ScopeTaste = "taste"
// ScopeDiscover is the Discover request surface (#2377). Its own scope
// rather than columns on taste: SnoozeDays lives here, and a snooze must
// never be read as taste signal (#2374) — filing it under taste would put
// it one careless join from the leak that design forbids.
// ScopeDiscover is the Discover request surface. Its own scope rather
// than columns on taste: SnoozeDays lives here, and a snooze must never
// be read as taste signal — filing it under taste would put it one
// careless join from that leak.
ScopeDiscover = "discover"
// ScopeSongsLike is the "Songs like {X}" surface (#3881). It shared
// daily_mix with For-You until 2026-09-10, and that sharing WAS the bug:
// the two surfaces want opposite things. For-You answers "what will they
// enjoy today" and is supposed to roam; Songs-like answers "what sounds
// like THIS" and is the tightest surface in the product. One set of
// weights cannot serve both, and the broad answer was winning.
// ScopeSongsLike is the "Songs like {X}" surface. It is separate from
// daily_mix because the two want opposite things: For-You answers "what
// will they enjoy today" and is supposed to roam; Songs-like answers
// "what sounds like THIS" and is the tightest surface in the product. One
// set of weights cannot serve both.
ScopeSongsLike = "songs_like"
)
// TasteTuning is the tunable subset of taste.Config: the engagement
// half-life, the completion→engagement curve points, the enriched-tag
// weight (how much folksonomy tags count vs raw ID3 genre, #1520), and the
// era-facet weight (how strongly a decade-play imprints, #1530).
// weight (how much folksonomy tags count vs raw ID3 genre), and the
// era-facet weight (how strongly a decade-play imprints).
type TasteTuning struct {
HalfLifeDays float64
EngagementHardSkip float64
@@ -63,12 +61,10 @@ type TasteTuning struct {
MoodScale float64
}
// ShippedRadioWeights are the shipped radio-profile defaults (moved
// here from config.RecommendationConfig — YAML is bootstrap-only,
// rule: config in UI). Radio is seed-directed (the user picked a
// direction), so taste is a lighter nudge than in the daily mixes.
// ContextTimeWeight starts uniform (1.0) across both profiles pending
// trend data (#1531); split them once the metrics view justifies it.
// ShippedRadioWeights are the shipped radio-profile defaults. Radio is
// seed-directed (the user picked a direction), so taste is a lighter nudge
// than in the daily mixes. ContextTimeWeight is 1.0 in both profiles; the
// metrics view is what would justify splitting them.
func ShippedRadioWeights() recommendation.ScoringWeights {
return recommendation.ScoringWeights{
BaseWeight: 1.0,
@@ -99,12 +95,11 @@ func ShippedDailyMixWeights() recommendation.ScoringWeights {
}
}
// ShippedSongsLikeWeights are the shipped songs_like-profile defaults
// (#3881). The whole point is that SIMILARITY DOMINATES; every other
// profile balances it against taste and engagement, and this one must not.
// ShippedSongsLikeWeights are the shipped songs_like-profile defaults. The
// whole point is that SIMILARITY DOMINATES; every other profile balances it
// against taste and engagement, and this one must not.
//
// The failure being corrected, arithmetic from the daily_mix profile that
// this surface used to share:
// Under the daily_mix weights, for example:
//
// unrelated track, liked, not played recently → 1.0 + 2.0 + 1.0 = 4.0
// PERFECT similarity match, not liked → 1.0 + 1.5 = 2.5
@@ -132,12 +127,10 @@ func ShippedDailyMixWeights() recommendation.ScoringWeights {
// stays 2.0 because a track the user skips is still unwanted no matter how
// similar it is.
//
// These are DEFAULTS, not settings (rule 25) — the operator turns them in the
// admin tuning card and good values get baked back here. They are a
// defensible starting point rather than a measured optimum: the per-arm fill
// rates and the real sim_score distribution are still unknown (#3879), and
// `likes_overlap` contributes a flat 0.3 that a high SimilarityWeight
// amplifies. Expect to move these once that lands.
// These are DEFAULTS, not settings: an admin turns them in the tuning card
// and good values get baked back here. They are a defensible starting point
// rather than a measured optimum, and `likes_overlap` contributes a flat 0.3
// that a high SimilarityWeight amplifies.
func ShippedSongsLikeWeights() recommendation.ScoringWeights {
return recommendation.ScoringWeights{
BaseWeight: 1.0, // same for all candidates; differentiates nothing
@@ -169,12 +162,12 @@ func shippedWeightsFor(scope string) (recommendation.ScoringWeights, bool) {
return recommendation.ScoringWeights{}, false
}
// DiscoverTuning is the tunable set for the Discover request surface (#2377).
// DiscoverTuning is the tunable set for the Discover request surface.
type DiscoverTuning struct {
// TagOverlapWeight scales the taste-tag term: score × (1 + w × overlap).
// 0 disables it and restores pure similarity ranking.
TagOverlapWeight float64
// SnoozeDays is the default "not right now" duration (#2374).
// SnoozeDays is the default "not right now" duration.
SnoozeDays float64
}
@@ -183,12 +176,11 @@ type DiscoverTuning struct {
// TagOverlapWeight 1.0 lets a perfect tag match at most double a candidate's
// similarity score — enough to reorder the deck meaningfully, not enough for a
// popular-tag coincidence to beat a genuinely strong similarity match. It is a
// starting point for the tuning lab, not a tuned value: the honest way to pick
// it is the metrics trend view after some real use.
// starting point, not a tuned value: the way to pick it is the metrics trend
// view after some real use.
//
// SnoozeDays 90 matches the operator's approved shape: long enough that a
// parked suggestion stops nagging, short enough that a taste shift brings it
// back on its own.
// SnoozeDays 90: long enough that a parked suggestion stops nagging, short
// enough that a taste shift brings it back on its own.
func ShippedDiscoverTuning() DiscoverTuning {
return DiscoverTuning{
TagOverlapWeight: 1.0,
@@ -337,7 +329,7 @@ func (s *Service) Taste() TasteTuning {
// Discover returns the cached Discover-tuning values. Read per request by the
// suggestions handler, so an admin change takes effect on the next refresh
// with no restart (rule #25).
// with no restart.
func (s *Service) Discover() DiscoverTuning {
s.mu.RLock()
defer s.mu.RUnlock()
+6 -9
View File
@@ -282,7 +282,7 @@ func TestUpdate_NoOpWritesNoAudit(t *testing.T) {
}
}
// --- Discover scope (#2377, milestone #268 slice 6) ---
// --- Discover scope ---
func TestNew_SeedsDiscoverDefaults(t *testing.T) {
pool := newPool(t)
@@ -310,9 +310,8 @@ func TestUpdateDiscover_PersistsAndAudits(t *testing.T) {
t.Errorf("SnoozeDays = %v, want 30", got)
}
// The audit row must land under the new scope. This is the assertion that
// would have caught a missing rule-#36 CHECK migration: without expanding
// recommendation_tuning_audit's whitelist, this INSERT fails at runtime.
// The audit row must land under the new scope: a scope missing from
// recommendation_tuning_audit's CHECK fails this INSERT at runtime.
rows := auditRows(t, pool)
if len(rows) != 1 {
t.Fatalf("audit rows = %d, want 1", len(rows))
@@ -343,8 +342,7 @@ func TestUpdateDiscover_Validation(t *testing.T) {
{"weight past the typo bound", map[string]float64{"tag_overlap_weight": 100}},
// A sub-day snooze isn't a snooze, it's a flicker.
{"snooze under a day", map[string]float64{"snooze_days": 0.5}},
// Past a year it's a permanent dismissal wearing a snooze's clothes —
// the shape rule #101 rules out.
// Past a year it's a permanent dismissal wearing a snooze's clothes.
{"snooze past a year", map[string]float64{"snooze_days": 400}},
}
for _, c := range cases {
@@ -360,9 +358,8 @@ func TestUpdateDiscover_Validation(t *testing.T) {
}
}
// Weight 0 must be accepted — it's the operator's off switch for the whole
// tag term, so a "must be positive" bound would remove their ability to
// disable the feature.
// Weight 0 must be accepted: it's the off switch for the whole tag term, so
// a "must be positive" bound would leave no way to disable it.
func TestUpdateDiscover_ZeroWeightIsAllowed(t *testing.T) {
pool := newPool(t)
s := newService(t, pool)
@@ -7,10 +7,9 @@ import (
"git.fabledsword.com/bvandeusen/minstrel/internal/recommendation"
)
// The bug, reproduced as a ranking: a track that sounds nothing like the seed
// but which the user liked and has not played lately used to OUTRANK a perfect
// similarity match. Operator, 2026-09-10: "I was getting a seeming wide variety
// of music from each one when I was hoping to stay in a certain neighborhood."
// A track that sounds nothing like the seed, but which the user liked and has
// not played lately, must not outrank a perfect similarity match: Songs-like
// should stay in the seed's neighbourhood.
//
// This is the whole point of the songs_like profile, so it is asserted as
// BEHAVIOUR — two candidates, which one wins — rather than by checking the
@@ -85,7 +84,7 @@ func TestSongsLikeWeights_SimilarityOutrangesEverySeedIndependentTerm(t *testing
}
// A scope that is seedable but not resettable is the half-wired shape this
// guards: the profile appears in the admin card, the operator turns a knob,
// guards: the profile appears in the admin card, an admin turns a knob,
// and Reset then 404s on a scope the rest of the service knows about.
func TestShippedWeightsFor_CoversEveryWeightProfile(t *testing.T) {
for _, scope := range []string{ScopeRadio, ScopeDailyMix, ScopeSongsLike} {