feat: upgrade Fedify to 2.1.0 + implement 5 FEPs

Fedify 2.1.0 upgrade:
- Upgrade @fedify/fedify, @fedify/redis, @fedify/debugger to ^2.1.0
- Remove as:Endpoints type-stripping workaround (fixed upstream, fedify#576)
- Wire onUnverifiedActivity handler for Delete from actors with gone keys

FEP implementations:
- FEP-5feb: Add indexable + discoverable to actor (search indexing consent)
- FEP-f1d5/0151: Enrich NodeInfo 2.1 with metadata, staff accounts, repo info
- FEP-4f05: Soft delete with Tombstone — deleted posts serve 410 + Tombstone
  JSON-LD with formerType, published, deleted timestamps. New ap_tombstones
  collection + lib/storage/tombstones.js
- FEP-3b86: Activity Intents — WebFinger links for Follow/Create/Like/Announce
  intents, authorize_interaction routes by intent parameter
- FEP-8fcf: Collection Sync outbound via Fedify syncCollection (documented
  that receiving side is not yet implemented)
This commit is contained in:
Ricardo
2026-03-26 17:33:28 +01:00
parent 47fe21c681
commit 35ab840a56
10 changed files with 291 additions and 120 deletions

View File

@@ -435,6 +435,20 @@ export default class ActivityPubEndpoint {
});
if (!post || post.properties?.deleted) {
// FEP-4f05: Serve Tombstone for deleted posts
const { getTombstone } = await import("./lib/storage/tombstones.js");
const tombstone = await getTombstone(self._collections, requestUrl);
if (tombstone) {
res.status(410).set("Content-Type", "application/activity+json").json({
"@context": "https://www.w3.org/ns/activitystreams",
type: "Tombstone",
id: requestUrl,
formerType: tombstone.formerType,
published: tombstone.published || undefined,
deleted: tombstone.deleted,
});
return;
}
return next();
}
@@ -811,6 +825,21 @@ export default class ActivityPubEndpoint {
* @param {string} url - Full URL of the deleted post
*/
async delete(url) {
// Record tombstone for FEP-4f05
try {
const { addTombstone } = await import("./lib/storage/tombstones.js");
const postsCol = this._collections.posts;
const post = postsCol ? await postsCol.findOne({ "properties.url": url }) : null;
await addTombstone(this._collections, {
url,
formerType: post?.properties?.["post-type"] === "article" ? "Article" : "Note",
published: post?.properties?.published || null,
deleted: new Date().toISOString(),
});
} catch (error) {
console.warn(`[ActivityPub] Tombstone creation failed for ${url}: ${error.message}`);
}
await this.broadcastDelete(url).catch((err) =>
console.warn(`[ActivityPub] broadcastDelete failed for ${url}: ${err.message}`)
);
@@ -927,6 +956,8 @@ export default class ActivityPubEndpoint {
Indiekit.addCollection("ap_oauth_apps");
Indiekit.addCollection("ap_oauth_tokens");
Indiekit.addCollection("ap_markers");
// Tombstones for soft-deleted posts (FEP-4f05)
Indiekit.addCollection("ap_tombstones");
// Store collection references (posts resolved lazily)
const indiekitCollections = Indiekit.collections;
@@ -964,6 +995,7 @@ export default class ActivityPubEndpoint {
ap_oauth_apps: indiekitCollections.get("ap_oauth_apps"),
ap_oauth_tokens: indiekitCollections.get("ap_oauth_tokens"),
ap_markers: indiekitCollections.get("ap_markers"),
ap_tombstones: indiekitCollections.get("ap_tombstones"),
get posts() {
return indiekitCollections.get("posts");
},