fix(journal): retract federated activities only on public→non-public

A received Delete is recorded by Mastodon as a permanent tombstone —
later Creates for the same URI are silently refused forever. The old
code sent Delete on every non-public save 'just in case' (the comment
even called over-sending harmless); on the 2026-06-07 soak this
tombstoned an unlisted activity's URI before its first real publish,
making its later flip to public invisible on the remote with no error
anywhere.

- visibilityTransitionAction(previous, next): Create on any transition
  to public (re-publish doubles as back-delivery; remotes dedupe by
  id), Delete only when leaving public, nothing for
  non-public→non-public.
- updateActivityVisibility reads the previous visibility and acts on
  the transition.
- design.md documents the tombstone permanence + the user-facing
  consequence (un-publish then re-publish won't resurrect the post on
  remotes that processed the retraction — same as Mastodon's own
  delete-and-redraft).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-06-07 08:52:23 +02:00
parent b0bfed4d4a
commit 20be961177
4 changed files with 88 additions and 15 deletions

View file

@ -135,6 +135,15 @@ Inbound signature verification uses the actor's public key from their actor obje
(task 5.1): Fedify builds collection-level responses (outbox
OrderedCollection) from counter/cursors without consulting the page
dispatcher, so the dispatcher's `null` → 404 contract doesn't cover them.
- **Retractions are transition-aware; tombstones are forever** (learned on
the 2026-06-07 soak). Mastodon records a received `Delete` as a permanent
tombstone for that URI — later `Create`s for the same URI are silently
refused. Push delivery therefore sends `Delete` only on an actual
public→non-public transition (`visibilityTransitionAction`), never "just
in case". Consequence worth surfacing user-facing eventually: if a user
un-publishes a federated activity and re-publishes it later, remotes that
processed the retraction will not show it again — same semantics as
Mastodon's own delete-and-redraft.
## Open Questions