Add a seek endpoint for collections (SWICG Seek Item)#3549
Draft
pfefferle wants to merge 14 commits into
Draft
Conversation
… liked collections
There was a problem hiding this comment.
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/seekthat 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-collectionget_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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
/seekendpoint (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 theitemparameter set, then passes the collection's 307 redirect through.seekItemproperty 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.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 thenfloor(index / per_page) + 1and the response is a 307 redirect whoseLocationis 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":
404. The spec requires this (MUST be 404 Not Foundwhen 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 same404.401. This is a property of the request, not of any item (it is returned identically regardless ofitem), so it discloses no membership while telling the client to authenticate and retry. The spec'sMUST-404 applies to the authenticated-but-not-authorized case; it is silent on unauthenticated requests, so a401is compliant and more useful than a misleading404.401; an authenticated non-owner gets the uniform404— 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:
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:
GET /wp-json/activitypub/1.0/actors/0/followers, and note theseekItemproperty in the response.itemparameter set to a known follower's actor ID:GET /wp-json/activitypub/1.0/seek?collection=<collection>&item=<actor-id>.Locationpoints at the collection page containing that follower.Changelog entry
Changelog entry is committed in
.github/changelog/add-seek-item-endpoint.Changelog Entry Details
Significance
Type
Message