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).
This commit is contained in:
Sven
2026-05-13 23:18:05 +02:00
parent ba760047ce
commit e191d4715b
@@ -2,13 +2,39 @@ import { access, readFile, writeFile } from "node:fs/promises";
// activitypub index.js and federation-setup.js unlisted guards are now // activitypub index.js and federation-setup.js unlisted guards are now
// built into the fork — only endpoint-syndicate (separate package) needs patching. // 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 = [ const endpointSyndicateCandidates = [
"node_modules/@indiekit/endpoint-syndicate/lib/utils.js", "node_modules/@indiekit/endpoint-syndicate/lib/utils.js",
"node_modules/@indiekit/indiekit/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 = [ 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", name: "endpoint-syndicate-source-url-unlisted-guard",
candidates: endpointSyndicateCandidates, candidates: endpointSyndicateCandidates,