fix(deletion): preserve a second leaver's note instead of deleting it (#412) #461

Merged
claude-bot merged 1 commit from fix/412-deletion-collisions into main 2026-08-31 04:53:21 +00:00
Contributor

Closes #412 (LOW).

The defect

Account deletion repoints uniqueness-constrained history onto a single "Deleted user" tombstone. A row that would collide with one the tombstone already owns cannot be repointed — the constraint forbids two rows for one user — and the collision was resolved by deleting the incoming row.

It never fires within a single deletion, because the tombstone owns nothing yet. It fires across repeated deletions, which is precisely when nobody is watching: two GMs leave the group months apart, and the second one's public note vanishes rather than being tombstoned like everyone else's. The campaign's record loses one person's writing in a way indistinguishable from it never having been written.

What ships

Notes merge. The colliding text is appended to the tombstone's existing row, separated by a rule so a reader can see two people wrote it — without naming either, since both accounts are gone. A blank note contributes nothing and leaves no dangling separator.

Votes, attendance and lore ownership still drop, and that is now a stated decision rather than an incidental one. Merging is not meaningful for them: one vote per person is the entire point of the constraint, attendance is a single yes/no, and an ownership link is not additive. With exactly one tombstone, two anonymised people genuinely cannot both be represented — a real limit of the model, not an oversight. So the loss is counted and carried into the existing user.deleted audit entry rather than happening silently. That is the second acceptance criterion.

The mutation run caught a flaw in my own code

The first version returned "collisions handled" as the count. Running the mutant — merge disabled, everything dropped — only one test failed, because that number was identical whether the text was merged or destroyed. The count was reporting something it had not checked, which in an audit entry is worse than reporting nothing.

It now reports loss specifically: a merged note is not lost, a blank one is not lost, and a dropped vote is. Against the same mutant, both merge tests now fail.

Verification

  • Mutant (never merge): test_a_second_leavers_note_is_kept_not_deleted and test_a_merged_note_is_not_counted_as_lost both fail.
  • A test covers the single-deletion path, so "the note survived" cannot pass via a merge branch that never ran — the ordinary case is a plain repoint.
  • A test asserts the drop count actually reaches the audit entry, since nobody reads a return value months later.

1,488 backend tests pass (was 1,482). Lint clean at pinned ruff 0.4.4.

🤖 Generated with Claude Code

Closes #412 (LOW). ## The defect Account deletion repoints uniqueness-constrained history onto a single "Deleted user" tombstone. A row that would collide with one the tombstone already owns cannot be repointed — the constraint forbids two rows for one user — and the collision was resolved by **deleting the incoming row**. It never fires within a single deletion, because the tombstone owns nothing yet. It fires across *repeated* deletions, which is precisely when nobody is watching: two GMs leave the group months apart, and the second one's public note vanishes rather than being tombstoned like everyone else's. The campaign's record loses one person's writing in a way indistinguishable from it never having been written. ## What ships **Notes merge.** The colliding text is appended to the tombstone's existing row, separated by a rule so a reader can see two people wrote it — without naming either, since both accounts are gone. A blank note contributes nothing and leaves no dangling separator. **Votes, attendance and lore ownership still drop**, and that is now a stated decision rather than an incidental one. Merging is not meaningful for them: one vote per person is the entire point of the constraint, attendance is a single yes/no, and an ownership link is not additive. With exactly one tombstone, two anonymised people genuinely cannot both be represented — a real limit of the model, not an oversight. So the loss is **counted and carried into the existing `user.deleted` audit entry** rather than happening silently. That is the second acceptance criterion. ## The mutation run caught a flaw in my own code The first version returned "collisions handled" as the count. Running the mutant — merge disabled, everything dropped — only **one** test failed, because that number was *identical* whether the text was merged or destroyed. The count was reporting something it had not checked, which in an audit entry is worse than reporting nothing. It now reports **loss** specifically: a merged note is not lost, a blank one is not lost, and a dropped vote is. Against the same mutant, **both** merge tests now fail. ## Verification - Mutant (never merge): `test_a_second_leavers_note_is_kept_not_deleted` and `test_a_merged_note_is_not_counted_as_lost` both fail. - A test covers the single-deletion path, so "the note survived" cannot pass via a merge branch that never ran — the ordinary case is a plain repoint. - A test asserts the drop count actually reaches the audit entry, since nobody reads a return value months later. **1,488 backend tests pass** (was 1,482). Lint clean at pinned ruff 0.4.4. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(deletion): preserve a second leaver's note instead of deleting it (#412)
All checks were successful
CI / Docker image build (pull_request) Successful in 19s
CI / Bot/backend version sync (pull_request) Successful in 1m2s
CI / Backend lint (ruff) (pull_request) Successful in 1m14s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m57s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m23s
CI / Bot tests and audit (pull_request) Successful in 2m40s
CI / Backend migration, tests, and audit (pull_request) Successful in 10m24s
094b52a85e
Account deletion repoints uniqueness-constrained history onto one tombstone
user. A row colliding with one the tombstone already owns cannot be
repointed, and the collision was resolved by deleting the incoming row. It
never fires within a single deletion — only across repeated ones, which is
exactly when nobody is watching: two GMs leave months apart and the second
one's public note vanishes rather than being tombstoned like everyone
else's.

Notes now merge: the colliding text is appended to the tombstone's row,
separated by a rule so a reader can see two people wrote it without naming
either, since both accounts are gone. Blank notes contribute nothing and
leave no dangling separator.

Votes, attendance and lore ownership still drop. Merging them is not
meaningful — one vote per person is the point of the constraint, attendance
is one yes/no, ownership is not additive — and with exactly one tombstone
two anonymised people genuinely cannot both be represented. That is a real
limit of the model, so it is now counted and carried into the existing
user.deleted audit entry rather than happening silently.

The count reports *loss*, not "collisions handled". An earlier draft
returned the latter, which reported the same number whether text was merged
or destroyed — the one distinction it exists to make. Caught by the
mutation run: only one test failed, because the count was identical either
way. Both merge tests fail against the mutant now.

1,488 backend tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-bot deleted branch fix/412-deletion-collisions 2026-08-31 04:53:22 +00:00
Sign in to join this conversation.
No description provided.