Skip to content

Add a seek endpoint for collections (SWICG Seek Item)#3549

Draft
pfefferle wants to merge 14 commits into
trunkfrom
add/seek-item-endpoint
Draft

Add a seek endpoint for collections (SWICG Seek Item)#3549
pfefferle wants to merge 14 commits into
trunkfrom
add/seek-item-endpoint

Conversation

@pfefferle

@pfefferle pfefferle commented Jul 16, 2026

Copy link
Copy Markdown
Member

Proposed changes:

Implements the SWICG ActivityPub API: Seek Item extension, which lets a client jump straight to the collection page that contains a given item instead of paging through from the start.

  • New /seek endpoint (Seek_Controller): GET /wp-json/activitypub/1.0/seek?collection=<url>&item=<id>. It resolves the collection URL to an internal REST route within the plugin namespace and dispatches the request to the collection itself with the item parameter set, then passes the collection's 307 redirect through.
  • Collections advertise the endpoint via the seekItem property and its JSON-LD context, carrying their filtering arguments (order, per_page, context) so a seek runs against the same ordering the client is traversing.
  • Per-collection resolution (get_item_index()) on the followers, following, outbox, inbox, and liked collections. Each computes the item's position using the collection's own query, so visibility rules never drift between "is this item here" and "what page is it on". The page is then floor(index / per_page) + 1 and the response is a 307 redirect whose Location is that page. A temporary redirect is used because the collections are ordered newest-first, so items drift across pages as new items arrive.

The seek endpoint accepts both authentication methods commonly used with ActivityPub: OAuth Bearer tokens go through the same check the Client-to-Server endpoints use, and all other requests go through HTTP Signature verification. The dispatched collection additionally applies its own permission check, and endpoints that force signatures (FEP-8fcf's /followers/sync) keep verifying.

Membership disclosure

A seek can only ever reveal what the collection itself would, and it never lets an attacker distinguish "this specific item is here but you can't see it" from "this item is not here":

  • Item not found, hidden by the collection's visibility rules, or requested by an authenticated non-owner all collapse to a single, uniform 404. The spec requires this (MUST be 404 Not Found when the authenticated user is not authorized), and it means the presence of a specific item can never be inferred. Unknown collections and foreign or out-of-namespace URLs return the same 404.
  • A request with no credentials gets 401. This is a property of the request, not of any item (it is returned identically regardless of item), so it discloses no membership while telling the client to authenticate and retry. The spec's MUST-404 applies to the authenticated-but-not-authorized case; it is silent on unauthenticated requests, so a 401 is compliant and more useful than a misleading 404.
  • The outbox seek is owner-only. The outbox is the one collection that mixes public and private activities in a single publicly-readable list, so instead of relying on the per-item visibility filter, a non-owner is refused before any item is resolved. An unauthenticated request gets 401; an authenticated non-owner gets the uniform 404 — identically for a public activity, a private one, and an unknown one — so the seek discloses nothing about the outbox regardless of an activity's visibility. The owner can still seek all of their own activities.

As a side effect, the outbox and inbox collections gained a deterministic secondary sort (date, then ID) so pagination and seek agree on the order of items sharing a timestamp.

Other information:

  • Have you written new tests for your changes, if applicable?

18 tests cover index-to-page math (boundaries, asc/desc), the uniform 404 contract (unknown item, hidden social graph, foreign and out-of-namespace URLs, non-seekable collections), the outbox seek policy (unauthenticated returns 401, an authenticated non-owner returns the uniform 404 for public/private/unknown alike, and the owner can seek both public and private), the inbox seek requiring authentication (401), the forced-signature route still verifying (401, not bypassed), and the seekItem advertisement preserving query arguments.

Testing instructions:

  • Fetch a collection, e.g. GET /wp-json/activitypub/1.0/actors/0/followers, and note the seekItem property in the response.
  • Follow that URL with an item parameter set to a known follower's actor ID: GET /wp-json/activitypub/1.0/seek?collection=<collection>&item=<actor-id>.
  • Confirm the response is a 307 redirect whose Location points at the collection page containing that follower.
  • Confirm an unknown item returns 404.

Changelog entry

Changelog entry is committed in .github/changelog/add-seek-item-endpoint.

  • Automatically create a changelog entry from the details below.
Changelog Entry Details

Significance

  • Patch
  • Minor
  • Major

Type

  • Added - for new features
  • Changed - for changes in existing functionality
  • Deprecated - for soon-to-be removed features
  • Removed - for now removed features
  • Fixed - for any bug fixes
  • Security - in case of vulnerabilities

Message

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for the SWICG “ActivityPub API: Seek Item” extension to the plugin, enabling clients to resolve the specific collection page that contains a given item (instead of paging from the start).

Changes:

  • Adds a new REST endpoint GET /wp-json/activitypub/1.0/seek that internally dispatches to the target collection and passes through its redirect response.
  • Extends ActivityPub collections to advertise seekItem (including JSON-LD context) and to support per-collection get_item_index()-based seeking (followers, following, outbox, inbox, liked).
  • Updates federation documentation and adds PHPUnit coverage for redirect math and uniform error behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/phpunit/tests/includes/rest/class-test-seek-controller.php Adds PHPUnit coverage for /seek, seekItem advertisement, and uniform 401/404 behavior.
includes/rest/trait-collection.php Adds seekItem advertisement, shared seek handling, and helper SQL-filter utilities for index resolution.
includes/rest/class-seek-controller.php Implements the /seek endpoint and internal dispatch behavior.
includes/rest/class-outbox-controller.php Adds seek support + deterministic ordering (date, then ID) and outbox-specific owner-only seek gating.
includes/rest/class-liked-controller.php Adds seek support and index lookup for liked objects.
includes/rest/class-following-controller.php Adds seek support and index computation under the collection’s query rules.
includes/rest/class-followers-controller.php Adds seek support and index computation under the collection’s query rules.
includes/rest/class-actors-inbox-controller.php Adds seek support, shared query builder, deterministic ordering (date, then ID), and index computation.
FEDERATION.md Documents Seek Item support.
activitypub.php Registers the new Seek controller routes during rest_init.
.github/changelog/add-seek-item-endpoint Adds an end-user changelog entry for the new feature.

Comment thread includes/rest/class-seek-controller.php
Comment thread includes/rest/class-seek-controller.php Outdated
Comment thread includes/rest/trait-collection.php Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants