docs: library comments say what the scanner and resolver do, not how they came to be (#6277)
release / govulncheck (push) Successful in 20s
release / web (push) Successful in 1m20s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m15s
release / android (push) Successful in 5m26s
release / Build signed APK (releases and dev) (push) Successful in 5m45s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped

Rewrites the internal/library comment blocks to rule 32. Task, milestone,
decision and rule ids are gone; the incidents behind them are restated as
the constraint they left (why a file is deleted before its row, why the
missing mark is a mark, why duplicates block on duration). Counts and
album names from one library are dropped from production comments; test
comments keep the names their fixtures use. The tagReadVersion log keeps
each version's before and after, without the measurements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 12:27:34 -04:00
co-authored by Claude Opus 5.5
parent 08a2c06b85
commit 7e6228c13f
66 changed files with 578 additions and 564 deletions
+17 -18
View File
@@ -13,30 +13,29 @@ import (
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
// Missing-pair folding (M485).
// Missing-pair folding.
//
// A track marked missing whose replacement already sits on disk under another
// row is folded into that replacement: its likes, plays, playlist entries and
// tags move across and the missing row goes. On the operator's library this was
// 355 of 451 missing tracks (#5274), nearly all quality upgrades — the mp3
// removed, a flac imported at a new path.
// tags move across and the missing row goes. The common case is a quality
// upgrade — the mp3 removed, a flac imported at a new path.
//
// Move adoption (moved.go) can't catch these. It links a new file to a missing
// row when the file is first inserted, matching on the recording MBID or the
// audio hash. A re-encode never shares the hash, and the replacements arrived
// untagged, so there was nothing to match; AcoustID filled many of the MBIDs
// weeks later, by which time both rows existed. Adoption keeps the old row and
// re-points its path, which only works while the new file has no row of its own.
// Here both do, so this folds instead, with the duplicate merge's row half.
// audio hash. A re-encode never shares the hash, and an untagged replacement
// has nothing to match until AcoustID fills its MBID, by which time both rows
// exist. Adoption keeps the old row and re-points its path, which only works
// while the new file has no row of its own. Here both do, so this folds
// instead, with the duplicate merge's row half.
//
// The rules are in ListMissingTrackPairs. Only a pair where each side has exactly
// one candidate is folded: a wrong fold can't be undone, a skipped one is only a
// row left in the missing list.
// The rules are in ListMissingTrackPairs. Only a pair where each side has
// exactly one candidate is folded: a wrong fold can't be undone, a skipped one
// is only a row left in the missing list.
//
// Unlike the duplicate merge this runs without review (operator, 2026-10-07):
// no file is touched, the missing row has nothing to play, and adoption, its
// closest relative, is automatic too. Each fold is logged with both paths so a
// wrong pair can be read back.
// Unlike the duplicate merge this runs without review: no file is touched, the
// missing row has nothing to play, and adoption, its closest relative, is
// automatic too. Each fold is logged with both paths so a wrong pair can be
// read back.
// PairResult tallies one pass.
type PairResult struct {
@@ -112,8 +111,8 @@ func PairMissingTracks(ctx context.Context, pool *pgxpool.Pool, logger *slog.Log
return res, nil
}
// foldMissingPair folds one pair in its own transaction, after locking both rows
// and checking the pair still holds.
// foldMissingPair folds one pair in its own transaction, after locking both
// rows and checking the pair still holds.
func foldMissingPair(ctx context.Context, pool *pgxpool.Pool, missingID, presentID pgtype.UUID) (foldResult, error) {
var f foldResult
err := pgx.BeginFunc(ctx, pool, func(tx pgx.Tx) error {