From e191d4715b4a23438929ffd5185db22a6a3f49fd Mon Sep 17 00:00:00 2001 From: Sven Date: Wed, 13 May 2026 23:18:05 +0200 Subject: [PATCH] fix(backfill-timeline): exclude unlisted posts from ap_timeline on startup backfillTimeline() runs on every Indiekit restart and backfills ap_timeline from the posts collection. The query had no visibility filter, so unlisted posts (e.g. Swarm checkins) were inserted into ap_timeline and became visible in Mastodon API timelines (Phanpy/Elk). Root cause traced via ObjectId timestamp: bc625 ap_timeline entry was created at restart time (20:56 UTC), not via the syndicator (which already had an unlisted guard). The backfill ran on startup and inserted the post. Fix: add 'properties.visibility': { $ne: 'unlisted' } to the posts.find() query in backfill-timeline.js. Also deleted 18 existing unlisted posts from ap_timeline (cleanup applied directly on server). --- scripts/patch-federation-unlisted-guards.mjs | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/scripts/patch-federation-unlisted-guards.mjs b/scripts/patch-federation-unlisted-guards.mjs index 47e6d1e2..39c8b05e 100644 --- a/scripts/patch-federation-unlisted-guards.mjs +++ b/scripts/patch-federation-unlisted-guards.mjs @@ -2,13 +2,39 @@ import { access, readFile, writeFile } from "node:fs/promises"; // activitypub index.js and federation-setup.js unlisted guards are now // built into the fork — only endpoint-syndicate (separate package) needs patching. +// backfill-timeline.js also needs patching — it runs on every startup and +// backfills ap_timeline from posts without filtering unlisted posts. const endpointSyndicateCandidates = [ "node_modules/@indiekit/endpoint-syndicate/lib/utils.js", "node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-syndicate/lib/utils.js", ]; +const backfillTimelineCandidates = [ + "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/mastodon/backfill-timeline.js", +]; + const patchSpecs = [ + { + name: "backfill-timeline-unlisted-guard", + candidates: backfillTimelineCandidates, + oldSnippet: ` const allPosts = await posts + .find({ + "properties.post-status": { $ne: "draft" }, + "properties.deleted": { $exists: false }, + "properties.url": { $exists: true }, + }) + .toArray();`, + newSnippet: ` const allPosts = await posts + .find({ + "properties.post-status": { $ne: "draft" }, + "properties.deleted": { $exists: false }, + "properties.url": { $exists: true }, + // Exclude unlisted posts — they must not appear in ap_timeline (Mastodon API). + "properties.visibility": { $ne: "unlisted" }, + }) + .toArray();`, + }, { name: "endpoint-syndicate-source-url-unlisted-guard", candidates: endpointSyndicateCandidates,