Files
indiekit-endpoint-activitypub/docs/activitypub-coverage-audit.md
Ricardo 1dc42ad5e5 feat: outbound Delete, visibility addressing, CW/sensitive, polls, Flag reports (v2.10.0)
- Outbound Delete: broadcastDelete() + POST /admin/federation/delete route
- Visibility: unlisted + followers-only addressing via defaultVisibility config
- Content Warning: outbound sensitive flag + summary as CW text
- Polls: inbound Question/poll parsing with progress bar rendering
- Flag: inbound report handler with ap_reports collection + Reports tab
- Includes DM support files from v2.9.x (messages controller, storage, templates)
- Includes coverage audit and high-impact gaps implementation plan

Confab-Link: http://localhost:8080/sessions/cc343b15-8d10-43cd-a48f-ca912eb79b83
2026-03-14 08:51:44 +01:00

25 KiB
Raw Blame History

ActivityPub Coverage Audit: @rmdes/indiekit-endpoint-activitypub vs Fedify 2.0

Date: 2026-03-13 Plugin Version: 2.9.2 Fedify Version: 2.0.0 Auditor: Claude Code (Opus 4.6)


Legend

  • Implemented — fully working in production
  • Partial — some aspects implemented, gaps remain
  • Not implemented — Fedify supports it, we don't use it

1. Inbound Activity Handlers

All handlers are in lib/inbox-listeners.js. Fedify dispatches inbound activities to registered listeners via setInboxListeners().

Activity Type Fedify Support Our Implementation Status
Follow Full Auto-accept, store follower in ap_followers, create notification, log to ap_activities (lines 90149) Implemented
Accept Full Updates ap_following entry to source: "federation", clears retry fields (lines 194235) Implemented
Reject Full Marks ap_following entry as source: "rejected" (lines 236267) Implemented
Undo(Follow) Full Removes from ap_followers (lines 151170) Implemented
Undo(Like) Full Removes from ap_activities (lines 171183) Implemented
Undo(Announce) Full Removes from ap_activities (lines 184193) Implemented
Like Full Filtered to our content only (objectId.startsWith(publicationUrl)), stores notification + activity log (lines 268317) Implemented
Announce Full Dual path: boosts of our posts → notification; boosts from followed accounts → ap_timeline with quote enrichment (lines 318412) Implemented
Create Full Four paths: DMs → ap_messages; replies to us → notification; mentions → notification; followed accounts → ap_timeline with link preview + quote enrichment (lines 413639) Implemented
Delete Full Removes from ap_activities + ap_timeline (lines 640649) Implemented
Update Full Post updates → ap_timeline content refresh; profile updates → follower data refresh (lines 672735) Implemented
Move Full Updates follower actorUrl to new address, stores movedFrom (lines 650671) Implemented
Block Full Remote actor blocked us → removes from ap_followers (lines 736744) Implemented
Add / Remove Full No-op — logged only. Mastodon uses these for featured collection management (lines 745750) Partial — not used for featured collection sync
Flag Full Not handled Not implemented — no report/moderation inbox
EmojiReact Full (LitePub) Not handled Not implemented
Dislike Full Not handled Not implemented — rarely used in fediverse
Question Full Not handled specially Not implemented — polls not parsed
Arrive / Travel / Join / Leave Full Not handled Not implemented — niche activity types
Invite / Offer Full Not handled Not implemented
Read / View / Listen Full Not handled Not implemented — niche

Score: 13/21 activity types handled (62%), covering ~99% of real-world fediverse traffic


2. Outbound Activities

Outbound activities are sent via ctx.sendActivity() from syndicator (index.js), interaction controllers (lib/controllers/interactions-*.js), compose (lib/controllers/compose.js), and messages (lib/controllers/messages.js).

Activity Fedify Support Our Implementation Status
Create(Note) Full Via syndicator (jf2ToAS2Activity()) + DM compose (submitMessageController) Implemented
Create(Article) Full Via syndicator (jf2ToAS2Activity()) Implemented
Like Full Reader interaction button (interactions-like.js:14115) Implemented
Undo(Like) Full Unlike button (interactions-like.js:121229) Implemented
Announce Full Boost button (interactions-boost.js:14~100) Implemented
Undo(Announce) Full Unboost button (interactions-boost.js:~101~180) Implemented
Follow Full Reader follow + migration + batch refollow (index.js:572667) Implemented
Undo(Follow) Full Unfollow button (index.js:674~750) Implemented
Accept(Follow) Full Auto-accept on inbound Follow (inbox-listeners.js:120128) Implemented
Update(Person) Full Profile edit broadcasts to all followers (index.js:761~850) Implemented
Delete Full Not sent when posts are deleted Not implemented
Block Full Local-only mute/block, no Block activity sent to remote Not implemented
Flag Full No report sending UI Not implemented
Move Full No outbound account migration Not implemented
Reject(Follow) Full Auto-accept only, no manual approval/reject Not implemented
Create(Question) Full No poll creation Not implemented

Score: 10/16 common outbound activities (63%)


3. Federation Dispatchers & Collections

All dispatchers are registered in lib/federation-setup.js.

Dispatcher/Collection Fedify Support Our Implementation Status
Actor (Person) Full Full with 5 actor types (Person, Service, Organization, Group, Application). Instance actor for shared inbox signing. RSA + Ed25519 key pairs. mapHandle() + mapAlias(). (lines 134160) Implemented
Inbox (personal + shared) Full Both endpoints registered. setSharedKeyDispatcher() for Authorized Fetch servers. (lines 283295) Implemented
Outbox Full Paginated, converts published blog posts to Create(Note|Article) via jf2ToAS2Activity(). 20 per page. (lines 589~650) Implemented
Followers Full Paginated + one-shot mode for sendActivity("followers") batch delivery. Counter. (lines 396445) Implemented
Following Full Paginated with counter. 20 per page. (lines 447475) Implemented
Liked Full From posts collection where post-type: "like". Paginated. (lines 477518) Implemented
Featured (pinned posts) Full Admin UI + AP collection. Converts pinned posts via jf2ToAS2Activity(). (lines 520555) Implemented
Featured Tags Full Admin UI + AP collection. Hashtag objects with category page links. (lines 557587) Implemented
Object dispatcher Full Content negotiation on individual post URLs. Returns Create(Note|Article) AS2 JSON-LD for Accept: application/activity+json. Implemented
WebFinger Full With OStatus subscribe link for remote follow from WordPress AP, Misskey, etc. (lines 275282) Implemented
NodeInfo Full Version 2.1. Reports software, protocols, total posts, active users. (lines 322339) Implemented
.authorize() on actor Full Intentionally disabled — causes infinite loops with Authorized Fetch servers. See CLAUDE.md Gotcha #16. Not implemented
Custom collections Full Not used Not implemented

Score: 11/13 (85%)


4. Cryptography & Security

Key storage in ap_keys collection. Key generation and signing handled by Fedify internals.

Feature Fedify Support Our Implementation Status
RSA key pairs (HTTP Signatures) Full Generated on first use, stored as PEM in ap_keys (federation-setup.js) Implemented
Ed25519 key pairs (Object Integrity Proofs) Full Generated on first use, stored as JWK in ap_keys Implemented
HTTP Signatures (draft-cavage-12) Full Automatic via Fedify signing on all outbound requests Implemented
HTTP Message Signatures (RFC 9421) Full Automatic via Fedify double-knocking negotiation Implemented
Double-Knocking negotiation Full Automatic — Fedify caches per-server signature spec preference Implemented
Authenticated Document Loader Full Used in all inbox handlers via getAuthLoader() helper. Required for Authorized Fetch servers (hachyderm.io, etc.) Implemented
Object Integrity Proofs (FEP-8b32) Full Ed25519 keys stored; Fedify creates proofs automatically Implemented (via Fedify)
Linked Data Signatures Full Fedify handles verification on inbound; not explicitly configured Partial — verification only
Authorized Fetch on actor endpoint Full Disabled — .authorize() causes infinite key-fetch loops. Instance actor used as workaround for shared inbox signing. Not implemented
Origin-based security (FEP-fe34) Full Not configured — using Fedify defaults Not implemented
Inbox idempotency Full Not explicitly configured — using Fedify default ("per-inbox") Implemented (default)
Signature time window Full Default (1 hour) Implemented (default)
CSRF protection N/A (app concern) Token generation + validation on all POST routes (lib/csrf.js) Implemented
Content sanitization N/A (app concern) sanitize-html on all inbound content (timeline-store.js) Implemented

Score: 8/12 Fedify-specific features (67%)


5. Content & Object Types

Object creation in lib/jf2-to-as2.js. Object parsing in lib/timeline-store.js and lib/inbox-listeners.js.

Object Type Fedify Support Our Implementation Status
Note Full Create, display, syndicate. Primary post type for notes/replies/DMs. Implemented
Article Full Create, display, syndicate. Used for article post type. Implemented
Image (attachment) Full Photo posts with Image attachments in jf2ToAS2Activity() Implemented
Video (attachment) Full Video post attachments Implemented
Audio (attachment) Full Audio post attachments Implemented
Hashtag (tag) Full Tags on syndicated posts, featured tags collection, tag timeline Implemented
Mention (tag) Full Tags on replies for addressing, DM recipient mentions Implemented
PropertyValue Full Profile attachment fields (name/value pairs) Implemented
Quote posts (FEP-044f) Full Ingest via quoteUrl (3 namespaces), enrich via fetchAndStoreQuote(), render via ap-quote-embed.njk Implemented
Question (polls) Full Not parsed — poll posts render without options Not implemented
Event Full Not handled — events render as generic objects Not implemented
Page Full Passthrough via content negotiation only Partial
ChatMessage (LitePub DMs) Full Not handled — we use standard Create(Note) DM addressing Not implemented
Tombstone Full Not created when posts are deleted Not implemented
Emoji (custom) Full (toot:Emoji) Not handled — custom emoji renders as :shortcode: text Not implemented
Place (location) Full Not handled — location data ignored Not implemented
Sensitive / Content Warning Full sensitive flag displayed on inbound items but not settable on outbound Partial
Source (original markup) Full Not used on outbound activities Not implemented

Score: 10/18 (56%), but core types fully covered


6. Audience Addressing & Visibility

Addressing logic in lib/jf2-to-as2.js (lines 179194) and lib/controllers/messages.js for DMs.

Visibility Mode Fedify Support Our Implementation Status
Public (to: PUBLIC_COLLECTION, cc: followers) Full Standard addressing for all syndicated posts Implemented
Unlisted (to: followers, cc: PUBLIC_COLLECTION) Full Not available — no UI option Not implemented
Followers-only (to: followers, no PUBLIC) Full Not available — all posts are public Not implemented
Direct/DM (to: specific actors only) Full Inbound detection (isDirectMessage()) + outbound via submitMessageController Implemented

Score: 2/4 (50%) — the two missing modes are rarely needed for IndieWeb sites


7. FEP (Fediverse Enhancement Proposals)

FEP Description Our Implementation Status
FEP-8b32 Object Integrity Proofs Ed25519 keys generated and stored; Fedify creates proofs on outbound activities Implemented (via Fedify)
FEP-521a Multiple Cryptographic Keys Both RSA + Ed25519 key pairs via setKeyPairsDispatcher() Implemented
FEP-044f Quote Posts Full pipeline: ingest quoteUrl (3 namespaces), enrich via fetchAndStoreQuote(), render embedded card, strip inline RE: references Implemented
FEP-8fcf Followers Collection Synchronization Not configured — syncCollection option not passed to sendActivity() Not implemented
FEP-fe34 Origin-Based Security Not configured — using Fedify defaults (crossOrigin not set) Not implemented
FEP-ae0c Relay Protocols @fedify/relay not used — personal site doesn't need relay Not implemented
FEP-c0e0 Actor Succession successor property not set on actor Not implemented
FEP-9091 DID-Based Actor Identification DidService/Export not used Not implemented
FEP-5711 Inverse Collection Properties likesOf, sharesOf, etc. not exposed Not implemented

Score: 3/9 (33%) — the three implemented FEPs are the most impactful for interoperability


8. Infrastructure & Operations

Feature Fedify Support Our Implementation Status
Redis message queue @fedify/redis RedisMessageQueue with parallelWorkers config (default 5) Implemented
In-process queue InProcessMessageQueue Fallback when redisUrl not set Implemented
MongoDB KvStore Custom (app-provided) MongoKvStore in lib/kv-store.js with get/set/delete/list() (required in Fedify 2.0) Implemented
Debug dashboard @fedify/debugger Optional via debugDashboard: true, password-protected at /{mount}/__debug__/ Implemented
OpenTelemetry tracing Full Via @fedify/debugger FedifySpanExporter Implemented
LogTape logging Full Configured once with _logtapeConfigured flag to prevent duplicate setup Implemented
Delivery failure handling Full 404/410 permanent failures logged + stored in ap_activities (lines 344361) Implemented
Exponential backoff retry Full Using Fedify default retry policy Implemented
Activity transformers Full Not used — autoIdAssigner() and actorDehydrator() defaults only Not implemented
PostgreSQL queue @fedify/postgres Not applicable — using Redis N/A
SQLite queue @fedify/sqlite Not applicable — using Redis N/A

Score: 8/9 relevant features (89%)


9. Application-Level Features (Beyond Fedify)

These features are built on top of Fedify — Fedify provides the federation primitives, we provide the application logic.

Feature Description Status
Timeline reader Full reader UI with tabs (notes, articles, boosts, media, replies, unread) Implemented
Notifications Like, boost, follow, mention, reply, DM notification types with unread tracking Implemented
Direct messages Inbound + outbound DMs with conversation sidebar, compose form Implemented
Explore Public Mastodon timeline aggregation from configured instances Implemented
Hashtag explore Cross-instance hashtag search via Mastodon API Implemented
Tag timeline Posts from followed accounts filtered by hashtag Implemented
Post detail Full post view with replies, quote enrichment Implemented
Remote profile View remote actor profiles with follow/mute/block actions Implemented
Moderation Mute (by URL or keyword), block, with filtering across all views Implemented
Mastodon migration CSV import + WebFinger resolution + batch re-follow state machine Implemented
Featured posts Pin/unpin posts to featured collection Implemented
Featured tags Manage featured hashtags Implemented
Profile editor Name, summary, icon, image, attachments, broadcasts update to followers Implemented
Link previews Open Graph unfurling via unfurl.js for timeline items Implemented
Infinite scroll Unified Alpine.js component with configurable cursor parameters Implemented
CSRF protection Token generation/validation on all POST routes Implemented
Content sanitization sanitize-html on all inbound content Implemented
Activity log Full inbound/outbound activity logging with TTL cleanup Implemented
Timeline cleanup Retention-based pruning (timelineRetention config) Implemented
Hashtag following Follow/unfollow hashtags, items from non-followed accounts matching tags appear in timeline Implemented
Public profile page HTML fallback for actor URL when accessed from browser Implemented

10. Overall Summary

Category Score Percentage Notes
Inbound Activities 13/21 62% All high-traffic types covered
Outbound Activities 10/16 63% Missing: Delete, Block, Flag, Move, Reject
Dispatchers/Collections 11/13 85% Near complete
Crypto/Security 8/12 67% Core signing works
Object Types 10/18 56% Core types done
Addressing 2/4 50% Public + DM only
FEPs 3/9 33% Key FEPs implemented
Infrastructure 8/9 89% Excellent
Weighted Overall ~70% ~95%+ of real-world fediverse traffic covered

11. Gap Analysis: High-Impact Improvements

Ordered by impact-to-effort ratio.

Priority 1 — High Impact, Low Effort

Gap Impact Effort Details
Outbound Delete activity High Low When a post is deleted in Indiekit, remote servers are never notified. The post remains visible on all federated instances indefinitely. Hook into Indiekit's post delete lifecycle, send Delete(Tombstone) to followers.
Outbound Block activity Medium Low Our block is local-only (ap_blocked). Remote servers don't know we blocked them, so they continue delivering activities. Send Block activity on block, Undo(Block) on unblock.
Unlisted addressing mode Medium Low Add a "visibility" option to the syndicator: public (default), unlisted (to: followers, cc: PUBLIC). Useful for posts that shouldn't appear on public timelines but are still accessible via link.

Priority 2 — Medium Impact, Medium Effort

Gap Impact Effort Details
Question/Poll support (inbound) Medium Medium Poll posts from Mastodon render without options. Parse Question object's inclusiveOptions/exclusiveOptions, display vote options and results in timeline. Voting (outbound) is a separate feature.
Flag handler (inbound reports) Medium Medium Other servers can't send us abuse reports. Add Flag inbox listener, store in a ap_reports collection, add moderation UI tab.
Content Warning / Sensitive flag (outbound) Medium Low Inbound sensitive content is displayed with a warning. Add a "sensitive" / CW option to the compose form and syndicator so outbound posts can include content warnings.
Followers-only addressing Medium Medium Add a "followers-only" visibility option. Requires to: followers only, no PUBLIC. Also needs consideration for who can see the post on our own site.

Priority 3 — Low Impact

Gap Impact Effort Details
Custom Emoji Low Medium Mastodon custom emoji renders as :shortcode: text. Parse Emoji tags, fetch images, inline-replace in content.
Reject(Follow) / manual approval Low Medium Currently all follows are auto-accepted. Add a "manually approves followers" mode with pending/accept/reject UI.
Tombstone on delete Low Low Instead of just deleting from collections, create a Tombstone object for the deleted resource. Mostly a federation correctness improvement.
Activity transformers Low Low Fedify's actorDehydrator() improves Threads compatibility. Consider enabling for broader compatibility.
FEP-8fcf Followers Sync Low Low Pass syncCollection: true to sendActivity() calls. Reduces duplicate deliveries for servers that support it.
FEP-fe34 Origin-Based Security Low Low Set crossOrigin: "ignore" or "throw" on federation options. Prevents spoofed attribution attacks.
Gap Reason
EmojiReact Misskey/Pleroma-only, very niche
Arrive/Travel/Join/Leave Almost never seen in real fediverse
Invite/Offer Group-specific, very niche
Dislike Not implemented by any major fediverse software
Relay support (FEP-ae0c) Only useful at scale, not for personal sites
DID-based identity (FEP-9091) Future spec, minimal adoption
Actor succession (FEP-c0e0) Future spec, minimal adoption
ChatMessage (LitePub DMs) Our standard DM addressing works with all servers

12. Data Flow Reference

Outbound Activity Flow

Indiekit blog post (JF2)
  ↓
syndicator.syndicate()  [index.js]
  ↓
jf2ToAS2Activity()  [lib/jf2-to-as2.js — converts JF2 → Fedify vocab objects]
  ↓
ctx.sendActivity({ identifier: handle }, "followers", activity)  [Fedify]
  ↓
Redis queue  [or InProcessMessageQueue]
  ↓
HTTP POST to follower inboxes  [signed with RSA/Ed25519 by Fedify]

Inbound Activity Flow

Remote server HTTP POST to /{mount}/inbox  [HTTP Signature verified by Fedify]
  ↓
federation-bridge.js  [reconstructs body if Express consumed stream, uses req.originalUrl]
  ↓
Fedify matches activity type → calls registered listener
  ↓
inbox-listeners.js  [authenticated document loader for all remote fetches]
  ↓
MongoDB storage  [ap_followers, ap_timeline, ap_notifications, ap_messages, ap_activities]
  ↓
Admin UI renders data  [reader, notifications, messages, moderation]

Reader Timeline Pipeline

Raw items from ap_timeline
  ↓
applyTabFilter()  [notes/articles/boosts/media/replies — lib/item-processing.js]
  ↓
loadModerationData()  [load muted URLs, keywords, blocked URLs]
  ↓
postProcessItems()  [filter muted/blocked, strip quote refs, build interaction map]
  ↓
renderItemCards()  [server-side Nunjucks → HTML for AJAX responses]
  ↓
Alpine.js infinite scroll  [apInfiniteScroll component — assets/reader-infinite-scroll.js]

13. MongoDB Collections Reference

Collection Records Indexes TTL
ap_followers Accounts following us actorUrl (unique) No
ap_following Accounts we follow actorUrl (unique) No
ap_activities Activity log direction, type, actorUrl, objectUrl, receivedAt Yes (activityRetentionDays, default 90)
ap_keys Crypto key pairs type (rsa/ed25519) No
ap_kv Fedify KV store _id (key path) Yes (Fedify-managed)
ap_profile Actor profile (single doc) No
ap_featured Pinned posts postUrl No
ap_featured_tags Featured hashtags tag No
ap_timeline Reader timeline uid (unique), published, author.url, type No (manual cleanup via timelineRetention)
ap_notifications Notifications uid (unique), type, read, createdAt Yes (notificationRetentionDays, default 30)
ap_messages Direct messages uid (unique), conversationId+published, read, direction Yes (reuses notificationRetentionDays)
ap_muted Muted actors/keywords url or keyword No
ap_blocked Blocked actors url No
ap_interactions Like/boost tracking objectUrl, type No
ap_followed_tags Hashtags we follow tag No

14. Configuration Reference

{
  mountPath: "/activitypub",           // URL prefix for all routes
  actor: {
    handle: "rick",                    // Fediverse username (@rick@rmendes.net)
    name: "Ricardo Mendes",            // Display name (seeds profile on first run)
    summary: "",                       // Bio (seeds profile)
    icon: "",                          // Avatar URL (seeds profile)
  },
  checked: true,                       // Syndicator checked by default in Micropub UI
  alsoKnownAs: "",                     // Mastodon migration alias (for Move activities)
  activityRetentionDays: 90,           // TTL for ap_activities (0 = forever)
  storeRawActivities: false,           // Store full JSON of inbound activities
  redisUrl: "",                        // Redis for delivery queue (empty = in-process)
  parallelWorkers: 5,                  // Parallel delivery workers (with Redis)
  actorType: "Person",                 // Person | Service | Organization | Group | Application
  logLevel: "warning",                 // Fedify log level: debug | info | warning | error | fatal
  timelineRetention: 1000,             // Max timeline items (0 = unlimited)
  notificationRetentionDays: 30,       // Days to keep notifications (0 = forever)
  debugDashboard: false,               // Enable @fedify/debugger at {mount}/__debug__/
  debugPassword: "",                   // Password for debug dashboard
}

This audit reflects the state of the plugin at version 2.9.2. It should be updated when new features are added or when Fedify releases new capabilities.