[Backend] Preserve colliding content instead of deleting it during account-deletion anonymization #412
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: LOW
Found in the August 2026 session lifecycle review (#319).
When a user deletes their account, rows that would collide with an already-anonymised "deleted user" tombstone on a uniqueness constraint are deleted outright rather than anonymised — so if a second GM with a public note on the same session later deletes their own account, that second GM's note content is destroyed (not tombstoned like everyone else's), because it collides with the first deleted GM's now-anonymised note on the same
(session_id, visibility)uniqueness pair. The same collision-driven deletion applies to votes and attendance rows, silently shrinking historical tallies.Evidence
webapp/backend/app/services/account_deletion_service.py:119-158(_repoint_anonymize) — builds acollisionsubquery checking whether re-pointing a row'suser_idto the tombstone user would collide with a row the tombstone already owns on the givenconflict_cols, and for any row that collides, deletes it (collide = delete(model).where(...), executed before the anonymising update) rather than merging or preserving its content.SessionNote(:244, conflict on(session_id, visibility)),Vote(:233), andSessionAttendance(:236) — any of these can collide once more than one account that used the deletion flow touches the same row-uniqueness key.Failure scenario
Two different GMs who have both left the group each request account deletion at different times. The first GM's public note for a shared session is anonymised onto the tombstone user. Months later the second GM, who also had a public note on that same session, deletes their account — their note collides with the tombstone's existing
(session_id, visibility='public')row, and rather than being preserved, it is deleted outright. The campaign's historical record silently loses one GM's content, in a way indistinguishable from it never having existed.Proposed fix
On a collision, merge instead of delete — for notes, concatenate the colliding content onto the tombstone's existing row (mirroring the pattern the bot's
/noteappend already uses) rather than discarding it; for votes/attendance, where merging doesn't make semantic sense (only one vote per person is meaningful), keep the existing row and drop the redundant new one only after confirming it doesn't represent unique information worth retaining as a count, or accept the tally shrink as a documented, bounded trade-off if a genuine merge isn't feasible.Acceptance criteria
SessionNoteon account deletion has its content preserved (merged into the tombstone row) rather than deleted.