-- 0075_duplicate_resolve.up.sql — duplicates resolve themselves (Scribe -- milestone #498: #5435, #5436, #5437). -- -- The sweep (0059) proposes groups and, until now, every proposal waited for the -- operator. The resolver classifies each pending group and acts on the classes -- that are safe. What makes deletion safe is Lidarr's view of the file: Lidarr -- maps one file to each track of the release it monitors, so deleting a mapped -- file opens a hole it fills by downloading again. Only a copy Lidarr does not -- map may go. -- What kind of duplicate a group is. NULL until the resolver has classified it. -- same_release every copy is on one album -- cross_release the same song on different releases (a single and its album) -- mismatch identical audio under different titles on different albums: -- a file imported as the wrong song -- review anything else: the operator decides -- Rule 36: a new value swaps this constraint in the same migration. ALTER TABLE duplicate_groups ADD COLUMN class text; ALTER TABLE duplicate_groups ADD CONSTRAINT duplicate_groups_class_check CHECK (class IN ('same_release', 'cross_release', 'mismatch', 'review')); -- Why the resolver left a group for the operator, in words the report shows -- ("Lidarr tracks every copy as its own track"). NULL when it had nothing to say. ALTER TABLE duplicate_groups ADD COLUMN resolve_note text; -- True when the resolver merged the group itself, so the report can tell its -- merges from the operator's. ALTER TABLE duplicate_groups ADD COLUMN resolved_automatically boolean NOT NULL DEFAULT false; -- Lidarr's view of each copy, as of the resolver's last pass: -- tracked Lidarr holds the file and it is not among its unmapped files -- unmapped Lidarr holds the file but maps it to no track: removing it opens -- no hole -- NULL when Lidarr was not consulted (disabled, or unreachable that pass). ALTER TABLE duplicate_group_members ADD COLUMN lidarr_state text; ALTER TABLE duplicate_group_members ADD CONSTRAINT duplicate_group_members_lidarr_state_check CHECK (lidarr_state IN ('tracked', 'unmapped')); -- Rule 25: the operator can turn the resolver off. On by default: the operator -- asked for duplicates to resolve themselves (2026-10-09). ALTER TABLE fingerprint_settings ADD COLUMN auto_resolve boolean NOT NULL DEFAULT true; -- The resolver tells admins what it did (rule 36: the kind CHECKs of 0073 are -- swapped to carry the new value). Postgres named both from their columns. ALTER TABLE user_notifications DROP CONSTRAINT user_notifications_kind_check; ALTER TABLE user_notifications ADD CONSTRAINT user_notifications_kind_check CHECK (kind IN ('request_approved', 'request_rejected', 'request_completed', 'request_pending', 'quarantine_flagged', 'scan_failed', 'tracks_missing', 'duplicates_found', 'duplicates_resolved', 'playback_errors')); ALTER TABLE user_notification_prefs DROP CONSTRAINT user_notification_prefs_kind_check; ALTER TABLE user_notification_prefs ADD CONSTRAINT user_notification_prefs_kind_check CHECK (kind IN ('request_approved', 'request_rejected', 'request_completed', 'request_pending', 'quarantine_flagged', 'scan_failed', 'tracks_missing', 'duplicates_found', 'duplicates_resolved', 'playback_errors'));