Files
scrabble-game/backend/internal/postgres/migrations/00008_retained_identities_merge_reason.sql
T
Ilia Denisov 0eefbfd6a4
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 16s
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m2s
fix(account): dedupe colliding identities on merge, journaling to the dossier
An account merge blanket-reassigned all of the secondary's identities to the primary,
so merging two accounts that each had a confirmed email (or telegram/vk) left the
survivor with two identities of one kind — which the profile and the retention dossier
both treat as singular (the profile showed an arbitrary one; a later change-email
journaled only one). The merge now keeps the primary's identity and journals the
secondary's colliding one to retained_identities (reason=merge) before dropping it, so
the survivor has one identity per kind and the absorbed credential still lands in the
legal dossier.

- migration 00008: widen retained_identities.reason CHECK to admit 'merge'
  (expand-contract — Up only widens the set, so an image rollback stays DB-safe).
- accountmerge: dedupeIdentities + retainMergedIdentity before the identity reassign.
- inttest TestAccountMergeDedupesEmail; docs ARCHITECTURE §4 + retention.go reason note.
2026-07-03 19:32:52 +02:00

23 lines
1.4 KiB
SQL

-- Admit the 'merge' reason into retained_identities. An account merge folds a secondary
-- account into a primary; when both hold an identity of the same kind (e.g. each has a
-- confirmed email), the survivor keeps its own and the secondary's is journaled to the
-- legal dossier and removed — so the survivor never ends up with two identities of one
-- kind, and the absorbed credential is still retained. That journal row carries reason
-- 'merge', alongside the existing unlink / change / delete.
--
-- Expand-contract: the Up only WIDENS the allowed reason set, so an older backend image
-- (which writes only unlink/change/delete) still satisfies the constraint — a rollback
-- stays DB-safe. The Down narrows it again and would reject pre-existing 'merge' rows, so
-- it is a dev-only convenience, not a production rollback path (image rollback runs old
-- code against this schema, not the Down migration).
-- +goose Up
ALTER TABLE backend.retained_identities DROP CONSTRAINT retained_identities_reason_chk;
ALTER TABLE backend.retained_identities ADD CONSTRAINT retained_identities_reason_chk
CHECK ((reason = ANY (ARRAY['unlink'::text, 'change'::text, 'delete'::text, 'merge'::text])));
-- +goose Down
ALTER TABLE backend.retained_identities DROP CONSTRAINT retained_identities_reason_chk;
ALTER TABLE backend.retained_identities ADD CONSTRAINT retained_identities_reason_chk
CHECK ((reason = ANY (ARRAY['unlink'::text, 'change'::text, 'delete'::text])));