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:
@@ -120,8 +120,8 @@ func requireJSONForCookieWrites(next http.Handler) http.Handler {
|
||||
// by the SPA handler, which knows the inline-script hashes.
|
||||
//
|
||||
// HSTS goes out only when the request reached us over HTTPS as the trusted
|
||||
// proxy reports it (rule 94): sending it over plain HTTP is ignored by
|
||||
// browsers at best, and the app never forces HTTPS.
|
||||
// proxy reports it: sent over plain HTTP it is ignored by browsers at best,
|
||||
// and the app never forces HTTPS.
|
||||
func securityHeaders(hops func() int) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestSecurityHeaders(t *testing.T) {
|
||||
t.Errorf("Permissions-Policy = %q", base.Get("Permissions-Policy"))
|
||||
}
|
||||
|
||||
// Rule 94's three cases for HSTS.
|
||||
// HSTS with no trusted proxy, and through one saying https or http.
|
||||
if got := serve(0, "https").Get("Strict-Transport-Security"); got != "" {
|
||||
t.Errorf("hops 0 + forwarded https: HSTS = %q, want none", got)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// The publish gate (rule 177, M462 #4984), pinned. release.yml holds every
|
||||
// The publish gate, pinned. release.yml holds every
|
||||
// verifying lane and every publishing job in one graph so that a publish can
|
||||
// depend on the lanes; these tests keep that dependency from eroding.
|
||||
//
|
||||
|
||||
@@ -10,17 +10,10 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Guards the version derivation that stamps every build.
|
||||
//
|
||||
// These assertions used to be impossible to run. The derivation lived inline
|
||||
// in release.yml, which triggers only on `main` and on tags — so a mistake in
|
||||
// it could not surface until a release was already under way, and its failure
|
||||
// mode is silence: a version nobody can compare looks exactly like being up to
|
||||
// date, and nobody reports an update they were never offered.
|
||||
//
|
||||
// Moving it to ci/version.sh made it executable, so this runs on every push
|
||||
// that touches the release machinery. That is the whole point of the file; the
|
||||
// specific assertions below matter less than the fact that they run at all.
|
||||
// Guards the version derivation that stamps every build, in ci/version.sh.
|
||||
// Its failure mode is silence: a version nobody can compare looks exactly
|
||||
// like being up to date, and nobody reports an update they were never
|
||||
// offered. Keeping it in a script lets these tests run it on every push.
|
||||
//
|
||||
// The tests EXECUTE the script rather than asserting on its text, so they
|
||||
// break when the behaviour changes rather than when the wording does.
|
||||
@@ -188,8 +181,7 @@ var jobBoundary = regexp.MustCompile(`\n\n [^ \n]`)
|
||||
//
|
||||
// The naive cut — "up to the next `- name:`" — silently returns an EMPTY body
|
||||
// for the last step in a job, and every assertion over it then passes
|
||||
// vacuously. That is the failure rule 167 names: a check that reads as
|
||||
// coverage while asserting on nothing. Cutting at a blank line followed by
|
||||
// vacuously: a check that reads as coverage while asserting on nothing. Cutting at a blank line followed by
|
||||
// job-level indentation handles the last-step case, which is exactly where
|
||||
// `Build and push` sits.
|
||||
func stepBody(t *testing.T, yaml, name string) string {
|
||||
@@ -274,7 +266,7 @@ func TestDevChannel_PublishesDevAloneAndNeverLatest(t *testing.T) {
|
||||
if strings.Contains(arm, ":latest") {
|
||||
t.Errorf("dev arm moves :latest — that ships dev code to every stable operator\n%s", arm)
|
||||
}
|
||||
// Rule 145: a rolling channel gets no commit-addressable tag.
|
||||
// A rolling channel gets no commit-addressable tag.
|
||||
if strings.Contains(arm, "GITHUB_SHA") {
|
||||
t.Errorf("dev arm publishes a per-commit tag; a rolling channel should not\n%s", arm)
|
||||
}
|
||||
@@ -367,21 +359,19 @@ func TestMainChannel_PublishesLatestAndTheRollbackUnit(t *testing.T) {
|
||||
if !strings.Contains(arm, "${IMAGE}:${GITHUB_SHA}") {
|
||||
t.Errorf("main arm publishes no commit-addressable image; there is no rollback target for production commits\n%s", arm)
|
||||
}
|
||||
// Rule 147: :latest tracks main's tip, and a second name for the same
|
||||
// :latest tracks main's tip, and a second name for the same
|
||||
// image sends readers looking for a distinction that does not exist.
|
||||
if strings.Contains(arm, "${IMAGE}:main") {
|
||||
t.Errorf("main arm publishes :main — rule 147 says that tag should not exist\n%s", arm)
|
||||
}
|
||||
}
|
||||
|
||||
// A release refreshes the CHANNEL and mints nothing else (rules 145 + 146).
|
||||
// A release refreshes the CHANNEL and mints nothing else.
|
||||
//
|
||||
// The specific regression: re-adding :<sha> here. The tag build rebuilds the
|
||||
// SAME SOURCE as main's build minutes earlier, differing only in which APK is
|
||||
// baked in — so a :<sha> minted here would overwrite main's immutable rollback
|
||||
// target with different contents, under the same name. That is the exact thing
|
||||
// rule 145's immutability clause exists to prevent, and it is the half with
|
||||
// the incidents behind it.
|
||||
// The regression to guard: a :<sha> tag here. The tag build rebuilds the SAME
|
||||
// SOURCE as main's build minutes earlier, differing only in which APK is baked
|
||||
// in — so a :<sha> minted here would overwrite main's immutable rollback
|
||||
// target with different contents, under the same name.
|
||||
func TestReleaseBuild_RefreshesTheChannelAndMintsNothingElse(t *testing.T) {
|
||||
arm, _, _ := imageTagArms(t, releaseYAML(t))
|
||||
|
||||
@@ -393,8 +383,8 @@ func TestReleaseBuild_RefreshesTheChannelAndMintsNothingElse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// No version-numbered image tags anywhere, on any arm (rule 145, and the
|
||||
// operator's 2026-09-10 decision to drop them across every project).
|
||||
// No version-numbered image tags anywhere, on any arm: the build's version is
|
||||
// self-reported, and the tag map is :latest, :<sha> and :dev.
|
||||
//
|
||||
// Asserted across the whole step rather than per-arm because the mistake this
|
||||
// catches is re-adding one ANYWHERE, and the tag arm is only the likeliest
|
||||
@@ -441,9 +431,9 @@ func TestVerifyJob_ChecksTheRollbackImageNotAVersionTag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The server's self-reported version is now the ONLY thing that identifies a
|
||||
// The server's self-reported version is the ONLY thing that identifies a
|
||||
// build, so a lane that stamps a channel word instead of a version silently
|
||||
// removes that ability. It used to stamp the literal "main"/"dev".
|
||||
// removes that ability.
|
||||
func TestImageBuild_StampsADerivedVersionAndAChannel(t *testing.T) {
|
||||
yaml := releaseYAML(t)
|
||||
|
||||
|
||||
@@ -19,18 +19,16 @@ import (
|
||||
// hits it every 5s and would otherwise drown real signals (~17k lines/day).
|
||||
//
|
||||
// Severity is keyed off the response status so 4xx/5xx surface even when
|
||||
// the operator's logger level is set above Info.
|
||||
// the logger level is set above Info.
|
||||
//
|
||||
// The `remote` attribute holds the address resolved through the operator's
|
||||
// configured reverse-proxy depth, NOT the raw socket peer (#2453). Behind a
|
||||
// proxy — the normal deployment for anything public — the socket peer is the
|
||||
// proxy, so every line would have carried the same useless address, and the
|
||||
// access log would have disagreed with the Active-sessions surface about who
|
||||
// connected. The attribute key is unchanged so existing log greps keep
|
||||
// working; only its accuracy improved.
|
||||
// The `remote` attribute holds the address resolved through the configured
|
||||
// reverse-proxy depth, NOT the raw socket peer. Behind a proxy — the normal
|
||||
// deployment for anything public — the socket peer is the proxy, so every
|
||||
// line would carry the same useless address, and the access log would
|
||||
// disagree with the Active-sessions list about who connected.
|
||||
//
|
||||
// trustedHops is a func because this middleware is constructed at boot while
|
||||
// the value is operator-editable at runtime, and — since Router() registers
|
||||
// the value is editable at runtime, and — since Router() registers
|
||||
// this before it builds the settings service — because it lets the accessor
|
||||
// be wired before the thing it reads exists. auth.ClientIP tolerates a depth
|
||||
// of 0, which is what a nil service reports.
|
||||
|
||||
@@ -109,8 +109,7 @@ func TestRequestLog_AttributesPresent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The point of routing #2453 through the access log: behind a proxy, `remote`
|
||||
// must be the client rather than the proxy, and must agree with what the
|
||||
// Behind a proxy, the access log's `remote` must be the client rather than the proxy, and must agree with what the
|
||||
// Active-sessions surface records for the same request. Logs and UI
|
||||
// disagreeing about who connected is worse than either being wrong alone.
|
||||
func TestRequestLog_RemoteHonoursTrustedProxyDepth(t *testing.T) {
|
||||
@@ -136,8 +135,8 @@ func TestRequestLog_RemoteHonoursTrustedProxyDepth(t *testing.T) {
|
||||
want: "203.0.113.200",
|
||||
},
|
||||
{
|
||||
// The case that motivated the change: proxy on a PUBLIC address,
|
||||
// which the pre-#2453 heuristic logged as the proxy forever.
|
||||
// A proxy on a PUBLIC address: a "trust XFF only from private
|
||||
// peers" heuristic would log the proxy forever.
|
||||
name: "depth 1 through a public-addressed proxy logs the client",
|
||||
hops: func() int { return 1 },
|
||||
remoteAddr: "203.0.113.200:40000",
|
||||
|
||||
+16
-18
@@ -65,34 +65,34 @@ type Server struct {
|
||||
// When nil, Router() constructs a local fallback (test contexts).
|
||||
Bus *eventbus.Bus
|
||||
// PlaylistScheduler fires per-user daily system-playlist builds at
|
||||
// 03:00 in each user's stored timezone (#392 Half B). Constructed
|
||||
// 03:00 in each user's stored timezone. Constructed
|
||||
// in cmd/minstrel/main.go and threaded into the API handlers so
|
||||
// PUT /api/me/timezone and POST /api/auth/register can call
|
||||
// Refresh synchronously.
|
||||
PlaylistScheduler *playlists.Scheduler
|
||||
// RecSettings is the DB-backed recommendation tuning lab (#1250):
|
||||
// RecSettings is the DB-backed recommendation tuning:
|
||||
// scoring-weight profiles + taste-build knobs. Constructed in
|
||||
// cmd/minstrel/main.go (it pushes daily-mix weights into package
|
||||
// playlists at boot); the API layer reads radio weights per request
|
||||
// and serves the admin tuning endpoints from it. Router() constructs
|
||||
// a fallback when nil (tests).
|
||||
RecSettings *recsettings.Service
|
||||
// FingerprintSettings is the DB-backed fingerprinting policy (M400 #3913).
|
||||
// FingerprintSettings is the DB-backed fingerprinting policy.
|
||||
// Constructed in cmd/minstrel/main.go and shared with the scanner and the
|
||||
// fingerprint workers, so a save from the admin card reaches them without a
|
||||
// restart. Router() constructs a fallback when nil (tests).
|
||||
FingerprintSettings *library.FingerprintSettingsService
|
||||
// LoudnessSettings is the DB-backed loudness analysis policy (M464 #4995),
|
||||
// LoudnessSettings is the DB-backed loudness analysis policy,
|
||||
// shared with the loudness backfill. Nil makes the router load its own.
|
||||
LoudnessSettings *library.LoudnessSettingsService
|
||||
// AcoustIDLookup is the AcoustID lookup worker (M401), whose settings,
|
||||
// AcoustIDLookup is the AcoustID lookup worker, whose settings,
|
||||
// status and "look up now" the admin card drives. Nil in test contexts;
|
||||
// one is built (and never run) so the admin routes still answer.
|
||||
AcoustIDLookup *library.AcoustIDLookupWorker
|
||||
// ReacqSettings is the DB-backed missing-file re-acquisition policy
|
||||
// (milestone #290) — the same instance the sweeper in cmd/minstrel/main.go
|
||||
// reads, so a save from the admin card reaches it without a restart
|
||||
// (#3936). Router() constructs a fallback when nil (tests).
|
||||
// ReacqSettings is the DB-backed missing-file re-acquisition policy — the
|
||||
// same instance the sweeper in cmd/minstrel/main.go reads, so a save from
|
||||
// the admin card reaches it without a restart. Router() constructs a
|
||||
// fallback when nil (tests).
|
||||
ReacqSettings *reacquisition.SettingsService
|
||||
// StreamSecret is the HMAC key used by /api/cast/stream-token to
|
||||
// mint signed UPnP / Sonos stream URLs and by /api/tracks/{id}/stream
|
||||
@@ -154,13 +154,11 @@ func (s *Server) Router() http.Handler {
|
||||
tracksSvc := tracks.NewService(s.Pool, s.Logger, s.DataDir)
|
||||
playlistsSvc := playlists.NewService(s.Pool, s.Logger, s.DataDir)
|
||||
smtpSender := mailer.NewSMTPSender(s.Pool, s.Logger.With("component", "mailer"))
|
||||
// Live-event bus for SSE subscribers (#392). Constructed per-process;
|
||||
// producers in playevents / requests / scanner publish into
|
||||
// the same instance. Background workers (request reconciler, scan
|
||||
// scheduler) live in cmd/minstrel/main.go where they're constructed
|
||||
// before Router() runs; they read s.Bus directly so they share this
|
||||
// process's bus. When Router() runs before main set the bus (tests,
|
||||
// or future contexts) we fall back to a fresh local instance.
|
||||
// Live-event bus for SSE subscribers, one per process: producers in
|
||||
// playevents / requests / scanner publish into the same instance.
|
||||
// Background workers are built in cmd/minstrel/main.go before Router()
|
||||
// runs and share s.Bus. With no bus set (tests), a fresh local one is
|
||||
// used.
|
||||
bus := s.Bus
|
||||
if bus == nil {
|
||||
bus = eventbus.New()
|
||||
@@ -240,8 +238,8 @@ func (s *Server) handleHealthz(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
// handleAdminScan runs a scan synchronously and returns the resulting stats.
|
||||
// Kept synchronous for v1: libraries are small and the operator can tell when
|
||||
// it's done. Async jobs with status polling are a later-milestone concern.
|
||||
// Synchronous, so the caller can tell when it's done; the scan's progress is
|
||||
// also published as scan-run events.
|
||||
func (s *Server) handleAdminScan(w http.ResponseWriter, r *http.Request) {
|
||||
stats, err := s.Scanner.Scan(r.Context(), nil)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
@@ -244,13 +244,11 @@ func TestRouter_AdminSubtreeNotShadowed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRouter_ReacquisitionSettingsSavedThroughTheAPIReachTheSweeper is a
|
||||
// regression test for #3936. Router() used to construct a
|
||||
// reacquisition.SettingsService of its own, so a save from the admin card
|
||||
// refreshed THAT instance's cache while the sweeper in cmd/minstrel/main.go
|
||||
// kept serving what it had loaded at boot. The card showed the new policy, the
|
||||
// feature kept running the old one, and only a restart reconciled them — the
|
||||
// exact thing rule 25 says a setting must not need.
|
||||
// TestRouter_ReacquisitionSettingsSavedThroughTheAPIReachTheSweeper: a
|
||||
// Router() that built a reacquisition.SettingsService of its own would refresh
|
||||
// THAT instance's cache on a save, while the sweeper in cmd/minstrel/main.go
|
||||
// kept serving what it loaded at boot. The card would show the new policy and
|
||||
// the feature run the old one until a restart.
|
||||
//
|
||||
// The assertion is made against the instance main.go hands the sweeper: save
|
||||
// through the router, then read that instance. A second service leaves it stale.
|
||||
|
||||
@@ -6,4 +6,4 @@ package server
|
||||
const MinClientVersion = "0.1.0"
|
||||
|
||||
// The build's version and channel live in internal/buildinfo, shared with
|
||||
// the Subsonic envelope and the admin Overview (M504).
|
||||
// the Subsonic envelope and the admin Overview.
|
||||
|
||||
Reference in New Issue
Block a user