Replies to a persona's posts reach its followers; personas join remote events

Two owner decisions of 2026-10-05, recorded in the roadmap.

Replies passed on ("the fediverse is broken without"): a public or unlisted
reply from another server to a persona's public, unlisted or followers-only
post goes on to the persona's followers as its author's server sent it, as
Mastodon forwards it, never to the replier's own server, never for a
local-only or group post; its edit and deletion follow. Only an activity its
own actor delivered is passed on (Arrival.Raw), so nothing forwarded is
forwarded again. The town checks it as relay.reply cells (specs/relay-five:
882 checks pass); Mastodon takes a passed-on activity only with an LD
signature, which GoToSocial and Akkoma don't add, and the checker knows it.

Events: a persona joins another server's event with a Join and leaves it with
a Leave, both to the organiser only, through
POST /api/privapub/v1/statuses/:id/join|leave; the organiser's Accept or
Reject is routed by our join id and shows as privapub.event.participation.
Events by invitation or taken on another site are refused before anything
is sent. Mobilizon's scenario joins and leaves an event (28 checks) and keeps
one for decePubClient's e2e.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-05 14:55:20 +02:00
1 parent 2b42377b3f
commit 0646de22bd
24 files changed
+676 -26

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
using MongoDB.Entities;
using PrivaPub.Federation.Actors;
using PrivaPub.Federation.Outbox;
using PrivaPub.Infrastructure.Statistics;
using PrivaPub.Models.Federation;
using PrivaPub.Models.Post;
using PrivaPub.StaticServices;
using System.Text.Json.Nodes;
using PostEntity = PrivaPub.Models.Post.Post;
namespace PrivaPub.Federation.Inbox
{
// ActivityPub's inbox forwarding of replies (owner decision 2026-10-05): a public or unlisted reply to one of our
// personas' own posts reaches the persona's followers as its author sent it, and so do its edits and its deletion, as
// Mastodon forwards the replies to its accounts' posts. The author's server sends a reply to its own followers and to
// the persona, never to the persona's followers, who would otherwise see half of the thread. It goes as it came (an
// LD signature in it stays good), signed by the persona; never to the replier's own server, never once more when it
// reached us forwarded, and never for a followers-only reply or a DM, which were not for the persona's followers.
public static class ReplyRelay
{
public static async Task Pass(PostEntity reply, DbEntities db, ILocalActorService localActors, IDeliveryService delivery, CancellationToken token)
{
var raw = Arrival.Current?.Raw;
if (raw == default || delivery == default || localActors == default || reply is not { IsFederatedCopy: true }
|| reply.Visibility is not (PostVisibility.Public or PostVisibility.Unlisted) || string.IsNullOrEmpty(reply.AnsweringToPostId))
return;
var parent = await db.Posts.MatchID(reply.AnsweringToPostId).ExecuteFirstAsync(token);
if (parent is not { IsFederatedCopy: false, IsLocalOnly: false } || !string.IsNullOrEmpty(parent.GroupId)
|| parent.Visibility is not (PostVisibility.Public or PostVisibility.Unlisted or PostVisibility.FollowersOnly))
return;
var owner = await localActors.FindById(LocalActorKind.Person, parent.GroupUserId, token);
if (owner is not { IsFederated: true } || JsonNode.Parse(raw) is not JsonObject activity)
return;
var replier = Interactions.HostOf(reply.ActorURI);
var inboxes = (await delivery.FollowerInboxes(owner, token)).Where(i => Interactions.HostOf(i) != replier).ToList();
if (inboxes.Count > 0)
await delivery.Enqueue(owner, inboxes, activity, token);
}
}
}