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
@@ -143,6 +143,20 @@ namespace PrivaPub.Federation.Controllers
|
||||
return Activity(ActivityPubRenderer.OrderedCollection(local.Followers, (int)count, default));
|
||||
}
|
||||
|
||||
// FEP-8fcf: the persona's followers on the server that signs the request, and nobody else's (FollowersSynchronization)
|
||||
[HttpGet, Route("{actor}/groupies/roll-call")]
|
||||
public async Task<IActionResult> RollCall(string actor, CancellationToken token)
|
||||
{
|
||||
var local = await _localActors.FindByUserName(actor, token);
|
||||
if (local is not { IsFederated: true } || !FollowersSynchronization.Synchronizes(local))
|
||||
return NotFound();
|
||||
var requester = await _fetches.Requester(Request, token);
|
||||
if (requester == default)
|
||||
return StatusCode(StatusCodes.Status401Unauthorized);
|
||||
var there = await FollowersSynchronization.On(local, FollowersSynchronization.Origin(requester.ActorURI), token);
|
||||
return Activity(ActivityPubRenderer.OrderedCollection(FollowersSynchronization.RollCall(local), there.Count, there.Select(f => (JsonNode)f)));
|
||||
}
|
||||
|
||||
[HttpGet, Route("{actor}/stalking")]
|
||||
public async Task<IActionResult> Following(string actor, CancellationToken token)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user