Files
buzz/crates
Luke Tornquist da651ad672 buzz-db: purge retention-free deletions in the app and index mentions in the event transaction (#8128)
## Summary

Part 1a of removing database objects that exist only in migrations. This
PR moves into the app the work two migration-only triggers do today. It
doesn't drop anything.

On migration-built databases (production), `purge_soft_deleted_nip_rs`
(0009/0011) and `purge_soft_deleted_buzz_mesh_status` (0019) turn a
NIP-09 soft delete of a NIP-RS read-state event (kind 30078) or a
mesh-status heartbeat (kind 30003) into a hard delete, and delete its
mentions. `schema/schema.sql` has neither trigger, so on desired-state
databases (CI) those rows remain as soft-deleted. The two schemas behave
differently today.

Changes:
- `soft_delete_by_coordinate` and
`soft_delete_event_and_update_thread_in_tx` now hard-delete matching
NIP-RS and mesh-status rows and their `event_mentions` rows in the same
transaction, before the generic soft delete. The classification
predicate is the same one the 0011 and 0019 triggers use.
- `insert_event_with_serving_write_guard` now indexes mentions inside
the event transaction, under a savepoint, instead of after commit. A
mention failure still only logs a warning. A concurrent deletion can no
longer leave a mention row behind, and `guard_event_mention_live` will
no longer be needed. The other post-commit `insert_mentions` callers
write kinds 48101 and 13534 and a test helper, never 30078.
- Both soft-delete `UPDATE`s exclude the same predicate (`AND NOT
COALESCE(..., false)`). A retention-free row is either purged or left
live, never soft-deleted, even when a replacement races the deletion
under READ COMMITTED. The purge runs only for kinds 30078 and 30003.
- While the 0011 hard-delete fence is still installed, the purge sets
the transaction-local `buzz.nip_rs_hard_delete` opt-in. 1b removes it,
along with the fence.

**Rollout:** the triggers stay in place on purpose. Pods still running
N-1 during a deploy depend on them. A follow-up (1b) will add a
migration that drops the 5 functions and 5 triggers, remove the
`buzz.nip_rs_hard_delete` opt-in code, and add the catalog-parity test.
1b should merge only after this PR is deployed everywhere.

**What got simpler:** once 1b lands, NIP-RS and mesh deletion will
follow one rule owned by the app, the same on both schemas, and the
hidden trigger work will be gone. This PR adds about 130 lines to
`event.rs`. A known gap for 1b: the replace path's Rust mesh classifier
is stricter than the SQL predicate, and 1b has to make one of them
authoritative before the triggers are dropped. 1b will then delete 5
triggers and 5 functions, plus the opt-in plumbing in `replaceable.rs`
and `deletion.rs`.

### Related issue

BUZZ-175, BUZZ-177 (Linear).

### Testing

Blox, head `a2dac90646ca64fb3f961ffda292e3da976deffc`:
- `cargo fmt --check`, `clippy -D warnings` (`buzz-db`, `buzz-relay`),
and test discovery: all pass.
- New tests: 4 of 4 pass.
- `nip09_deletion_purges_retention_free_rows_and_mentions`,
desired-state and `migration_schema_` variants. On the migration schema,
a 13-shape table checks that the app and the triggers classify each
shape the same way. A separate case covers a mesh row with a NULL
`d_tag`.
-
`coordinate_deletion_racing_nip_rs_replacement_never_soft_deletes_read_state`,
a deterministic lock interleaving.
- `serving_write_guard_insert_indexes_mentions_in_event_transaction`:
mentions are indexed in the event's own transaction (checked via
`xmin`), and a mention failure leaves the event live.
- Falsification: the race test fails against the pre-fix `event.rs`. The
mention test fails with the merge-base post-commit `runtime/mod.rs`.
Both NULL-`d_tag` variants fail with `COALESCE` removed.
- `buzz-db` PostgreSQL lane (`scripts/postgres-test-run.sh -p buzz-db
--lib --tests`): **409 of 409 passed** in an unscoped run. One box hangs
on `writer_pool_rejects_non_read_committed_database_default` (an
environment issue; it also hangs there at merge-base), and there 408 of
408 others passed.
- E2E against a real relay on both schemas: 18 of 18 signed deletion
cases, the serving-write mention path (success, pre-commit invisibility,
isolated failure, recovery), and browser mention, send, reload and
delete.
- CI at `a2dac906`: all checks pass. The `PostgreSQL Tests` lane needed
one rerun: the first attempt hit a known flaky test,
`replica_fence::sample_writer_waits_for_shared_floor_lock_before_sampling_time`,
which fails the same way on `main` at `001290a34`.

buzz-review-completed (agent review and agent E2E complete; human test
waived by Luke Tornquist)

---------

Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
2026-10-07 10:26:39 -04:00
..