fix(ingest): authorize announced deletes by membership, scope tombstones master
An announced Delete was authorized only when the target id lived on the announcing community's own host. That is not the shape Lemmy federates: a post's ap_id lives on its AUTHOR's instance, and the Delete fans out through the community's Announce. Every delete of remote-author content was therefore dropped as "cross-authority" and the content survived on the bridge — the reported symptom was a deleted lemmy.world post still visible in Coves as a duplicate of its repost. Prod logs show the same drop for ~50 further ids, mostly comments. Authorization now follows the storage model instead of the host: a followed community may delete a record in its OWN repo (posts, by mapping DID) or a comment whose thread root is in that repo (comments commit into the AUTHOR's repo, so the root post's DID is what identifies the owning community). This is both a fix and a tightening — a community may no longer delete a co-hosted sibling community's content, which host equality used to allow. Targets with no mapping stay authorized so handleDelete's marker can still close the delete-before-create race, but the marker is now SCOPED to the announcing community: unscoped markers let any followed community pre-suppress arbitrary ap_ids belonging to other communities for the whole retention window (30d), which was a cross-community suppression primitive rather than a trust question. Changes: - authorizeDelete keyed on repo membership; comments resolved via their stored reply.root (new narrow RecordGetter dependency). - The resolved *store.Community threads from handleAnnounce through the delete paths, removing a duplicate lookup whose NotFound was wrapped as a retryable error (it would have wedged the ordering key). - ap_tombstones gains an announcer column, PK (ap_id, announcer); ExistsFor/Remove/Prune are scope-aware and every call site passes the scope it actually writes in ('' = origin-authorized/global). Bare deliveries re-check the community scope AFTER resolveDelivered, since the delivered envelope is attacker-controlled until the body is refetched. - Announced deletes refuse any target with a bridged-actor row, not just one with a profile mapping: the actor row lands before the profile commits, and the terminal Delete(Actor) scrub keys off the row. The materializer gains a content-only HandleDeleteRecord so the classification authorizeDelete already made is not re-derived from a second read. - Undo{Delete} of an unmapped id retracts the marker and stops: it used to fetch and materialize, which let a followed community mint DIDs and inject content for communities it does not own. Mapped restores use the authority-pinned fetch, must come back as a type consistent with the mapping, and (when announced) must name the announcing community. Rollback now runs on every error class, not only skips. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>