Problem
The actor dispatcher currently returns Actor | null, which makes it impossible to represent a deleted or suspended account properly. When null is returned, Fedify passes the request to the next middleware without responding—so there is no way to return an HTTP 410 Gone with a Tombstone object, which is what ActivityPub §6.2 expects for deleted actors.
Applications that need to handle this case today are forced to work around Fedify by registering a separate route that catches requests Fedify declines and manually returns the 410 response. This is workable, but it means the application has to duplicate logic and manage two separate code paths for what should be a single concern.
The same problem applies to WebFinger: when a remote server looks up a deleted account, it should receive 410 Gone there too, not a 200 with a stale resource descriptor or a 404.
Proposed solution
Extend the actor dispatcher return type from Actor | null to Actor | Tombstone | null. When the dispatcher returns a Tombstone, Fedify should:
- respond to the actor endpoint with
HTTP 410 Gone and the serialized Tombstone as the body
- respond to the corresponding WebFinger lookup with
HTTP 410 Gone
Returning null continues to mean “not handled” (pass through to the next middleware), so there is no change to existing behavior.
API design
The only change is to the dispatcher callback signature:
federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
const account = await db.getAccount(identifier);
if (account == null) return null;
if (account.deletedAt != null) {
return new Tombstone({
id: ctx.getActorUri(identifier),
deleted: account.deletedAt,
});
}
return new Person({
id: ctx.getActorUri(identifier),
// ...
});
});
No new methods or options are needed. The Tombstone type is already part of @fedify/vocab.
Response format
When the dispatcher returns a Tombstone, the actor endpoint should respond with:
HTTP/1.1 410 Gone
Content-Type: application/activity+json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/alice",
"type": "Tombstone",
"deleted": "2024-01-15T00:00:00Z"
}
The deleted field is not required by the spec, but it is widely expected by implementations like Mastodon for cache invalidation.
For WebFinger, a 410 Gone with an empty body (or no body) is appropriate.
Considerations
The change is purely additive. Existing dispatcher implementations that return Actor | null are unaffected, and Tombstone in the return type is opt-in.
Fedify already logs a warning when the returned actor's id does not match the expected actor URI, without blocking the response. The same behavior should apply to Tombstone: warn if the id is mismatched, but still respond with 410 Gone.
Problem
The actor dispatcher currently returns
Actor | null, which makes it impossible to represent a deleted or suspended account properly. Whennullis returned, Fedify passes the request to the next middleware without responding—so there is no way to return anHTTP 410 Gonewith aTombstoneobject, which is what ActivityPub §6.2 expects for deleted actors.Applications that need to handle this case today are forced to work around Fedify by registering a separate route that catches requests Fedify declines and manually returns the
410response. This is workable, but it means the application has to duplicate logic and manage two separate code paths for what should be a single concern.The same problem applies to WebFinger: when a remote server looks up a deleted account, it should receive
410 Gonethere too, not a200with a stale resource descriptor or a404.Proposed solution
Extend the actor dispatcher return type from
Actor | nulltoActor | Tombstone | null. When the dispatcher returns aTombstone, Fedify should:HTTP 410 Goneand the serializedTombstoneas the bodyHTTP 410 GoneReturning
nullcontinues to mean “not handled” (pass through to the next middleware), so there is no change to existing behavior.API design
The only change is to the dispatcher callback signature:
No new methods or options are needed. The
Tombstonetype is already part of@fedify/vocab.Response format
When the dispatcher returns a
Tombstone, the actor endpoint should respond with:{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://example.com/users/alice", "type": "Tombstone", "deleted": "2024-01-15T00:00:00Z" }The
deletedfield is not required by the spec, but it is widely expected by implementations like Mastodon for cache invalidation.For WebFinger, a
410 Gonewith an empty body (or no body) is appropriate.Considerations
The change is purely additive. Existing dispatcher implementations that return
Actor | nullare unaffected, andTombstonein the return type is opt-in.Fedify already logs a warning when the returned actor's
iddoes not match the expected actor URI, without blocking the response. The same behavior should apply toTombstone: warn if theidis mismatched, but still respond with410 Gone.