A sender's digest of its followers here is compared and mended

FEP-8fcf, received. When a delivery's Collection-Synchronization header digests the sender's followers on PrivaPub
otherwise than the personas following it, a job reads the list the header names (on the sender's origin, signed by the
instance actor): a follow the list leaves out ends, only when the list is the one the digest describes; a request it
lists is taken as accepted; a persona it lists that follows nothing there sends Undo{Follow}, as Mastodon does. Each
claiming delivery is compared once.

Checked live (scenarios/followsync.sh, now 15 checks): PrivaPub ends a follow Mastodon lost and undoes one only
Mastodon remembered.

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 07:46:46 +02:00
1 parent bd18de35a6
commit e0682fa868
11 files changed
+291 -12

No files matched your search

@@ -204,6 +204,9 @@ namespace PrivaPub.Federation.Inbox
// once per follow, so a retry still counts as a copy
if (!queued && dedupe != default && type == "Undo" && await FollowAgain(activity, actorUri, token) is { } follow)
queued = await _queue.Enqueue(JobKind.ProcessInbox, queuedPayload, host, $"{dedupe}|{Digest(activity)}|{follow}", token);
// FEP-8fcf: what the sender says of its followers here (FollowingSynchronization)
if (queued && forwardedBy == default && FollowersSynchronization.Claim(actorUri, request.Headers[FollowersSynchronization.Header].ToString()) is { } claim)
await _queue.Enqueue(JobKind.SynchronizeFollowing, JsonSerializer.Serialize(claim), host, FollowingSynchronization.DedupeKey(claim, activityId), token);
_logger.LogInformation("Inbox {Recipient}: {Type} from {Actor} queued", recipient?.Handle ?? "shared", type, actorUri);
return new(StatusCodes.Status202Accepted, Reason: !queued ? "duplicate" : forwardedBy != default ? "forwarded" : "queued");
}