Reports reach Lemmy's moderators, from an anonymous reporter

Lemmy takes a report only from a person or a service, about one post or comment, addressed to its community, and it
answered PrivaPub's Flag (the instance actor's, an Application, with no `to` and the account and posts as its object)
400. A report of a post or comment in a community on a server whose NodeInfo names Lemmy now leaves from
`privapub_reports`, a Service with its own key that names nobody: one Flag per post, `to` the community (its own
audience, else its thread's), with the persona's words, or the category, in `summary` and `content`, sent to the
community's inbox. This is the second exception to "a server's software is for display" (owner decision 2026-10-06,
`ReportService.ServiceReportTakers`). Every other server keeps the instance actor's report. An account alone is not
reported to Lemmy, which takes no such report, and `forwarded` now says whether anything left.

The reporter is read unsigned in SecureMode and answers WebFinger like the instance actor. Nobody follows or mentions
it, the Mastodon API has no account for it, and a migration reserves its name. Checked live: Lemmy 1.0 and 0.19 keep the
reports of a thread and of a comment, with alice's words, from "Reports from privapub.test", and none names her (69
checks). A sweep of every scenario with this and the next commit: 876 checks pass; Ghost's Network feed listed alice's
post too late once, and Ghost passes alone.

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-06 19:23:17 +02:00
1 parent 10ff4b3d2a
commit f66c280b0b
33 files changed
+561 -46

No files matched your search

+1
View File
@@ -66,6 +66,7 @@ namespace PrivaPub.Federation.Inbox
LocalActorKind.Person => "person",
LocalActorKind.Group when !local.IsCircle => "group",
LocalActorKind.Application => "application",
LocalActorKind.Reporter => "reporter",
_ => default
};
}
+24
View File
@@ -0,0 +1,24 @@
using MongoDB.Entities;
using PrivaPub.StaticServices;
using PostEntity = PrivaPub.Models.Post.Post;
namespace PrivaPub.Federation.Inbox
{
public static class Communities
{
// the remote community a post was made in: its own audience, or the first one its thread names on the way up (a
// comment fetched for its thread, or delivered to a persona, may name none)
public static async Task<string> Of(PostEntity post, DbEntities dbEntities, CancellationToken token)
{
for (var depth = 0; post != default && depth < RemotePosts.MaxDepth; depth++)
{
if (!string.IsNullOrEmpty(post.AudienceURI))
return post.AudienceURI;
post = string.IsNullOrEmpty(post.AnsweringToPostId) ? default : await dbEntities.Posts.MatchID(post.AnsweringToPostId).ExecuteFirstAsync(token);
}
return default;
}
}
}
@@ -285,13 +285,7 @@ namespace PrivaPub.Federation.Inbox.Handlers
var post = target == default
? default
: await _dbEntities.Posts.Match(p => p.ObjectURI == target && !p.IsFederatedCopy && !p.DeletedAt.HasValue).ExecuteFirstAsync(token);
for (var depth = 0; post != default && depth < RemotePosts.MaxDepth; depth++)
{
if (post.AudienceURI == group.ActorURI)
return true;
post = string.IsNullOrEmpty(post.AnsweringToPostId) ? default : await _dbEntities.Posts.MatchID(post.AnsweringToPostId).ExecuteFirstAsync(token);
}
return false;
return post != default && await Communities.Of(post, _dbEntities, token) == group.ActorURI;
}
// A community's moderators lock one of its posts, or unlock it: no more replies, ours included. The community vouches
@@ -44,7 +44,7 @@ namespace PrivaPub.Federation.Inbox.Handlers
var follower = actor;
var target = await _localActors.FindByAddress(Id(follow["object"]), token);
if (target is not { IsFederated: true } || target.Kind == LocalActorKind.Application)
if (target is not { IsFederated: true } || target.IsServerActor)
{
Arrival.Drop("unknown-recipient");
return;
+1 -1
View File
@@ -254,7 +254,7 @@ namespace PrivaPub.Federation.Inbox
case "Follow":
// (by its actor's id, or the profile page Forte names instead)
var target = await _localActors.FindByAddress(Id(inner), token);
if (target is not { IsFederated: true } || target.Kind == LocalActorKind.Application)
if (target is not { IsFederated: true } || target.IsServerActor)
return new(StatusCodes.Status404NotFound, "no such local actor", Reason: "unknown-recipient");
break;
case "Undo" when inner is JsonObject && Id(inner["actor"]) != actorUri: