Background
FEP-ef61 defines a gateway endpoint for retrieving portable ActivityPub objects over HTTP. A gateway serves objects identified by ap: URIs through a well-known path such as:
GET /.well-known/apgateway/did:key:z6MkAlice/users/alice/notes/123
Fedify already serves dereferenceable ActivityPub objects through object dispatchers registered with setObjectDispatcher(). An object dispatcher maps a vocabulary class and a URI Template path, such as /users/{userId}/notes/{noteId}, to application code that returns the object.
For the first FEP-ef61 gateway serving implementation, Fedify should reuse that same object dispatcher path as the portable object path. In other words, if an application has this dispatcher:
federation.setObjectDispatcher(
Note,
"/users/{userId}/notes/{noteId}",
async (ctx, { userId, noteId }) => {
// ...
},
);
then this portable gateway request should route to the same dispatcher:
GET /.well-known/apgateway/did:key:z6MkAlice/users/alice/notes/123
The requested portable object ID would be reconstructed as:
ap+ef61://did:key:z6MkAlice/users/alice/notes/123
This keeps the first implementation close to Fedify's existing routing model instead of introducing a separate portable object dispatcher API.
Proposed work
Extend the existing object dispatcher machinery so Fedify can serve portable objects through the fixed FEP-ef61 gateway endpoint.
The work should include:
- handling
GET /.well-known/apgateway/{did}/{path...};
- reconstructing the requested portable ID as an
ap+ef61: URI from the DID authority and dispatcher path;
- routing the portable path portion through the existing object dispatcher router;
- using the same URI Template path registered through
setObjectDispatcher() as the portable object path;
- adding or extending context helpers so object dispatchers can construct portable object IDs, similar to
Context.getObjectUri();
- giving object dispatchers enough request context to know whether they are serving an ordinary HTTP object URL or a portable gateway request;
- validating that the returned object's ID canonically matches the requested portable ID;
- applying FEP-ef61 proof policy before serving portable actors, activities, and non-collection objects;
- returning ActivityPub JSON-LD with the required content type;
- returning
404 Not Found when no object dispatcher matches or the dispatcher returns null;
- preserving existing object dispatcher behavior for ordinary HTTP object requests.
The exact API can be decided during implementation, but a possible context helper could look like:
ctx.getPortableObjectUri(Note, {
userId: "alice",
noteId: "123",
});
The helper would use the current portable DID authority when called during a gateway request, or require an explicit DID authority when called outside a gateway request.
Design questions
The implementation should settle these API details before finalizing the code path:
- Should portable serving be enabled automatically for every object dispatcher, or should applications opt in per dispatcher?
- How should a dispatcher distinguish an ordinary HTTP request from a portable gateway request?
- Should
ctx.getPortableObjectUri() require an explicit DID authority outside a gateway request?
- Should dispatcher authorization predicates run unchanged for portable gateway requests, or should they receive additional portable-request context?
- How should private object access and signed GET behavior fit into the existing object authorization model?
Scope
This issue is only about read-only serving of local portable objects through the fixed well-known FEP-ef61 gateway endpoint using existing object dispatchers.
It does not include:
GET /.well-known/apgateway discovery metadata;
- gateway dereferencing of remote portable objects;
- inbox or outbox POST handling;
- gateway-to-gateway forwarding;
- FEP-ae97 actor registration;
- media upload, serving, or deletion;
- arbitrary gateway path support;
- automatic storage for portable objects.
Private object access and signed GET behavior should be considered during design. If it requires broader authorization API changes, it can be split into a follow-up issue.
Dependencies
This should depend on the lower-level portable URI and proof policy work under #288, especially:
- portable URI codec support;
- portable URI canonicalization and comparison;
- FEP-fe34 cryptographic origins;
- FEP-ef61 proof policy.
Tests
Add regression tests for portable gateway object serving through object dispatchers.
The tests should cover:
- routing
GET /.well-known/apgateway/did:key:.../users/alice/notes/123 to an existing setObjectDispatcher(Note, "/users/{userId}/notes/{noteId}", ...) dispatcher;
- reconstructing the expected
ap+ef61: portable ID;
- constructing a portable object ID from context inside a gateway request;
- returning
404 Not Found when no object dispatcher matches;
- returning
404 Not Found when the matched dispatcher returns null;
- returning ActivityPub JSON-LD with the expected
Content-Type;
- rejecting or failing a dispatcher result whose ID does not canonically match the requested portable ID;
- applying FEP-ef61 proof policy before serving portable actors, activities, and objects;
- preserving existing ordinary object dispatcher behavior;
- rejecting malformed DID/path requests.
This should be added as a sub-issue of #288.
Background
FEP-ef61 defines a gateway endpoint for retrieving portable ActivityPub objects over HTTP. A gateway serves objects identified by
ap:URIs through a well-known path such as:Fedify already serves dereferenceable ActivityPub objects through object dispatchers registered with
setObjectDispatcher(). An object dispatcher maps a vocabulary class and a URI Template path, such as/users/{userId}/notes/{noteId}, to application code that returns the object.For the first FEP-ef61 gateway serving implementation, Fedify should reuse that same object dispatcher path as the portable object path. In other words, if an application has this dispatcher:
then this portable gateway request should route to the same dispatcher:
The requested portable object ID would be reconstructed as:
This keeps the first implementation close to Fedify's existing routing model instead of introducing a separate portable object dispatcher API.
Proposed work
Extend the existing object dispatcher machinery so Fedify can serve portable objects through the fixed FEP-ef61 gateway endpoint.
The work should include:
GET /.well-known/apgateway/{did}/{path...};ap+ef61:URI from the DID authority and dispatcher path;setObjectDispatcher()as the portable object path;Context.getObjectUri();404 Not Foundwhen no object dispatcher matches or the dispatcher returnsnull;The exact API can be decided during implementation, but a possible context helper could look like:
The helper would use the current portable DID authority when called during a gateway request, or require an explicit DID authority when called outside a gateway request.
Design questions
The implementation should settle these API details before finalizing the code path:
ctx.getPortableObjectUri()require an explicit DID authority outside a gateway request?Scope
This issue is only about read-only serving of local portable objects through the fixed well-known FEP-ef61 gateway endpoint using existing object dispatchers.
It does not include:
GET /.well-known/apgatewaydiscovery metadata;Private object access and signed GET behavior should be considered during design. If it requires broader authorization API changes, it can be split into a follow-up issue.
Dependencies
This should depend on the lower-level portable URI and proof policy work under #288, especially:
Tests
Add regression tests for portable gateway object serving through object dispatchers.
The tests should cover:
GET /.well-known/apgateway/did:key:.../users/alice/notes/123to an existingsetObjectDispatcher(Note, "/users/{userId}/notes/{noteId}", ...)dispatcher;ap+ef61:portable ID;404 Not Foundwhen no object dispatcher matches;404 Not Foundwhen the matched dispatcher returnsnull;Content-Type;This should be added as a sub-issue of #288.