M3: what each handler did with an activity
Arrival carries a verdict that handlers set with one line before an existing return (Arrival.Drop, Reject, Accept, About), so no handler signature changes. InboxProcessor records it as an 'in' event, with the time taken, the wait since the inbox accepted it, the attempt, the audience (from the post's visibility, or else the activity's addressing), the local actor kind, the object's age for updates, deletes and reactions, and the FEP features of a delivered object. It also records types nothing handles (unknown-type), actors that cannot be loaded (deferred) and handler failures. Drop reasons: fetch-failed, unparseable, misattributed, duplicate, deleted, not-addressed, not-followed, not-public, not-visible, not-deleted, unknown-object, unknown-recipient, cross-origin, unsupported. Accepted sub-reasons: stored, poll-vote, edit, refresh, actor-refresh, actor-delete, removed, tombstone-only, auto-accepted, pending, follow-answer, quote-answer, quote-granted, undone, reaction, reported. Rejected: blocked, ignored, quote-refused. A circle's traffic is private and kindless, and a stranger posting into one is just not-addressed, so the event store cannot reveal a circle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2
This commit is contained in:
1 parent
4fa53f63bd
commit
ac7cbd136b
20 files changed
+544
-26
No files matched your search
@@ -68,26 +68,42 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
{
|
||||
using var fetched = await _remoteActors.FetchObject(Id(node), token);
|
||||
node = fetched == default ? default : JsonNode.Parse(fetched.Root.GetRawText());
|
||||
if (node == default)
|
||||
Arrival.Drop("fetch-failed");
|
||||
}
|
||||
var note = NoteParser.Parse(node);
|
||||
if (note == default || note.AttributedTo != author.ActorURI)
|
||||
{
|
||||
Arrival.Drop(note == default ? "unparseable" : "misattributed");
|
||||
return;
|
||||
}
|
||||
Arrival.About(note.Type);
|
||||
if (note.Title != default && note.InReplyTo != default && ObjectShapes.Text(note.ContentHtml, 1) == default)
|
||||
{
|
||||
var question = await _dbEntities.Posts.Match(p => p.ObjectURI == note.InReplyTo && p.Poll != null).ExecuteFirstAsync(token);
|
||||
if (question != default)
|
||||
{
|
||||
await _polls.Receive(question, author, note.Title, Id(activity), token);
|
||||
Arrival.Accept("poll-vote");
|
||||
Arrival.About(visibility: question.Visibility);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await _dbEntities.Posts.Match(p => p.ObjectURI == note.Id).ExecuteAnyAsync(token)
|
||||
|| await DB.Default.Find<DeletedObject>().Match(d => d.ObjectURI == note.Id).ExecuteAnyAsync(token))
|
||||
if (await _dbEntities.Posts.Match(p => p.ObjectURI == note.Id).ExecuteAnyAsync(token))
|
||||
{
|
||||
Arrival.Drop("duplicate");
|
||||
return;
|
||||
}
|
||||
if (await DB.Default.Find<DeletedObject>().Match(d => d.ObjectURI == note.Id).ExecuteAnyAsync(token))
|
||||
{
|
||||
Arrival.Drop("deleted");
|
||||
return;
|
||||
}
|
||||
|
||||
var to = note.To.Concat(Strings(activity["to"])).Distinct(StringComparer.Ordinal).ToList();
|
||||
var cc = note.Cc.Concat(Strings(activity["cc"])).Distinct(StringComparer.Ordinal).ToList();
|
||||
var visibility = Addressing.Classify(to, cc, author.FollowersURL);
|
||||
Arrival.About(visibility: visibility);
|
||||
|
||||
var addressed = to.Concat(cc)
|
||||
.Concat(Addresses(activity))
|
||||
@@ -111,9 +127,13 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
var circle = localTargets.FirstOrDefault(t => t is { Kind: LocalActorKind.Group, IsCircle: true });
|
||||
if (circle != default)
|
||||
{
|
||||
if (!await IsCircleMember(circle, author.ActorURI, token))
|
||||
return;
|
||||
visibility = PostVisibility.Circle;
|
||||
Arrival.About(visibility: visibility);
|
||||
if (!await IsCircleMember(circle, author.ActorURI, token))
|
||||
{
|
||||
Arrival.Drop("not-addressed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
var group = circle ?? (visibility is PostVisibility.Public or PostVisibility.Unlisted
|
||||
? localTargets.FirstOrDefault(t => t is { Kind: LocalActorKind.Group, IsCircle: false })
|
||||
@@ -126,7 +146,10 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
.Match(f => f.TargetActorURI == author.ActorURI && f.State == FollowState.Accepted)
|
||||
.ExecuteAnyAsync(token);
|
||||
if (visibility == PostVisibility.Direct ? persons.Count == 0 : group == default && persons.Count == 0 && !repliesToLocal && !followed)
|
||||
{
|
||||
Arrival.Drop("not-addressed");
|
||||
return;
|
||||
}
|
||||
if (parent == default && visibility != PostVisibility.Direct)
|
||||
parent = await _remotePosts.Parent(note.InReplyTo, 1, token);
|
||||
|
||||
@@ -150,8 +173,11 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
}
|
||||
catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey)
|
||||
{
|
||||
Arrival.Drop("duplicate");
|
||||
return;
|
||||
}
|
||||
Arrival.Accept("stored");
|
||||
Arrival.About(local: group ?? persons.FirstOrDefault());
|
||||
await _records.Record(note, post, ObjectPath.Delivered, refetched, token);
|
||||
await _previews.Wanted(post, token);
|
||||
await _quotes.Resolve(post, note, token);
|
||||
|
||||
Reference in new issue
Block a user