Deliveries to followers carry a digest of them, per server
Owner decision of 2026-10-06 (FEP-8fcf). A persona's delivery addressed to its followers carries a signed
Collection-Synchronization header naming its followers, its roll-call (…/groupies/roll-call) and the digest of its
accepted followers on the receiving server only. The roll-call answers a signed request with the persona's followers
on the signer's server and nobody else's.
Mastodon gives every Undo{Follow} it sends after reading a roll-call the same id (…#follows//undo), so a second one
looked like a copy: an Undo of a Follow that comes again while the follow it ends exists again is now kept once per
follow.
Checked live (scenarios/followsync.sh): Mastodon drops a follow PrivaPub lost, and undoes one it lost itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
bf37223849
commit
bd18de35a6
10 files changed
+385
-4
No files matched your search
@@ -170,6 +170,15 @@ namespace PrivaPub.Federation.Outbox
|
||||
});
|
||||
}
|
||||
|
||||
// FEP-8fcf: what a delivery to the persona's followers tells the receiving server of its followers there
|
||||
static async Task<(string, string)[]> Synchronization(LocalActor signer, string body, Uri inbox, CancellationToken token)
|
||||
{
|
||||
if (!FollowersSynchronization.Synchronizes(signer) || JsonNode.Parse(body) is not JsonObject activity || !FollowersSynchronization.ForFollowers(activity, signer))
|
||||
return [];
|
||||
var there = await FollowersSynchronization.On(signer, FollowersSynchronization.Origin(inbox.AbsoluteUri), token);
|
||||
return [(FollowersSynchronization.Header, FollowersSynchronization.HeaderValue(signer, FollowersSynchronization.Digest(there)))];
|
||||
}
|
||||
|
||||
async Task<JobOutcome> Deliver(Job job, Attempt attempt, CancellationToken token)
|
||||
{
|
||||
var payload = JsonSerializer.Deserialize<DeliveryPayload>(job.Payload);
|
||||
@@ -198,7 +207,7 @@ namespace PrivaPub.Federation.Outbox
|
||||
var body = Encoding.UTF8.GetBytes(payload.Body);
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, inbox) { Content = new ByteArrayContent(body) };
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(RemoteActorService.ActivityJson);
|
||||
HttpSignatures.Sign(request, signer, body);
|
||||
HttpSignatures.Sign(request, signer, body, await Synchronization(signer, payload.Body, inbox, token));
|
||||
|
||||
var started = Stopwatch.GetTimestamp();
|
||||
try
|
||||
|
||||
Reference in new issue
Block a user