Capped future published dates when adding posts to feeds - #2161
Conversation
Publishers control the published date of their posts: the Ghost Admin API and content imports accept arbitrary dates, and remote fediverse servers can claim any date they like. Both the main feed and the discovery feeds sort by published_at descending, so a post dated in the future pins itself to the top of every page — and sits above every pagination cursor — until real time catches up with its claimed date. The ranking timestamp is now capped at the current time when a post is inserted into feeds and discovery feeds. Capping happens at the feed layer rather than on the post itself because post.publishedAt is canonical data that we re-emit in outgoing ActivityPub objects, and rewriting it would make our copy of a post disagree with its origin server. The feed tables' published_at is already a denormalised ranking column, so "when should this sort in a timeline" is exactly the semantic it owns.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review. WalkthroughThe feed service adds Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to Future-dated original posts are now placed in regular and discovery feeds using the current time, preventing them from remaining above pagination cursors while retaining canonical publication dates elsewhere. The changed insertion paths are covered and no merge-blocking risk remains. Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 367212b34c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function capAtNow(date: Date): Date { | ||
| const now = new Date(); | ||
|
|
||
| return date > now ? now : date; |
There was a problem hiding this comment.
Preserve unique ordering when capping future timestamps
When more than one page of future-dated posts of the same type is ingested within a second, this maps their distinct publication dates to the same value, which feeds.published_at stores at whole-second precision. getFeedData() orders only by that column and paginates with published_at < cursor, so after the first 20 tied rows, every remaining row at the cursor timestamp is skipped permanently. This is particularly likely during concurrent content imports—the scenario motivating this change—so the feed needs a compound timestamp/ID cursor or another deterministic tie-breaker rather than a shared capped timestamp.
Useful? React with 👍 / 👎.
Publishers control the published date of their posts: the Ghost Admin API and content imports accept arbitrary dates, and remote fediverse servers can claim any date they like. Both the main feed and the discovery feeds sort by
published_atdescending, so a post dated in the future pins itself to the top of every page — and sits above every pagination cursor — until real time catches up with its claimed date.The ranking timestamp is now capped at the current time when a post is inserted into
feedsanddiscovery_feeds.Capping happens at the feed layer rather than on the post itself because
post.publishedAtis canonical data that we re-emit in outgoing ActivityPub objects, and rewriting it would make our copy of a post disagree with its origin server. The feed tables'published_atis already a denormalised ranking column, so "when should this sort in a timeline" is exactly the semantic it owns. This mirrors how other fediverse servers handle it — Mastodon orders timelines by receipt time precisely so remote content can't choose its own rank.Repost entries are unaffected: they already rank by the repost's own
created_at, which we generate ourselves.Note that rows inserted before this change keep their future dates, so any posts currently pinned will stay pinned until their claimed dates pass or the rows are cleaned up separately.