Integrating ActivityPub within Solid specs

Hello everyone,

Following last week’s Solid Practitioners meeting where I’ve presented ActivityPods and seen (perhaps for the first time) a lot of enthusiasm regarding the idea of integrating ActivityPub as a communication protocol within Solid, and at the request of several attendees, I’ve started to compile a list of what would be needed to add an ActivityPub layer to Solid Pods.

But first a little intro to give some context…

What is ActivityPub ?

ActivityPub is a simple communication protocol that allows any actor on the web to communicate with any other actor, no matter what software or server they are using. Actors can be persons, groups, organizations or softwares (bots).

Every actor can publish activities through its own outbox. The ActivityPub server then forwards these activities to the recipients’ inboxes (using HTTP signature for authentication). Inboxes and outboxes can also be fetched to see the activities that have been posted (if permissions allow).

In a way, ActivityPub is like email, but thanks to the use of RDF, it can describe any kind of social activity. Many commonly-used social activities are described in the ActivityStreams (AS) ontology, but it can be extended as much as needed with other ontologies.

Why ActivityPub is needed by Solid ?

The other day, @nikoNG from NextGraph shared with us the three types of communication protocols that can be abstracted (and that he also implemented in NextGraph): PubSub (Streaming), RPC (Remote Procedure Call) and Inbox. Here is the slide I showed during last week’s meeting:

As we can see in the violet area, Solid already use all three types of communication protocols, but in our opinion the Linked Data Notification (LDN) is too lightweight to fully address the related needs (ActivityPub was built upon LDN, which is why it uses the same predicate for the inbox)

The real need is that every WebID user has a clear way to be reached, and that it is also able to respond if needed. Once WebIDs can communicate with each others, many other things become possible and, maybe, we can rely less on the RPC-type of communication that is common in Solid specs.

Regarding Solid Notifications, it is a good protocol, but it requires the client to subscribe first, otherwise they don’t get any notifications. So it is good to watch resources (and we use it in ActivityPods to watch for inbox/outbox activities) but it is too limited for open communication between actors, since an actor cannot listen to the all other actors on the web.

What MUST be implemented ?

Here’s what must be implemented to have a very basic ActivityPub communication between actors:

  • Generate a private/public keypair on the Pod and attach the public key to the WebID
  • Attach a as:outbox endpoint to the WebID (ref.)
    • POST allow the actor (WebID owner) to post activities, which are then forwarded to recipients’ inboxes with a HTTP signature authentication
    • GET return an OrderedCollection with a dereferenced list of activities that the authenticated user has the right to see
  • Attach a as:inbox endpoint to the WebID (ref.)
    • POST allow other servers to send activities to the actor, with a HTTP signature authentication.
    • GET return an OrderedCollection with a dereferenced list of activities that the authenticated user has the right to see
  • Handle side effects
    • Persist activities sent through the outbox somewhere where they can be fetched (reachable URI)
    • Give acl:Read permission to the recipients (if the as:Public addressing is used, give anonymous read permission)

What SHOULD be implemented ?

The following is not required by the specs, but if it is not implemented, users probably won’t be able to communicate with Mastodon or most other ActivityPub softwares.

  • Handle the Webfinger endpoint at the root of the WebID server (/.well-known/webfinger)
  • Attach a as:Person (or as:Organization or as:Group) type to WebID
  • Attach AS-specific predicates such as as:name or as:preferredUsername to WebID
  • Attach followers/following AS collections to the WebID
  • Handle side effects
    • When receiving or emitting Follow activities, add the corresponding items to the followers/following AS collections. (ref.)
    • Persist objects that are created with a as:Create activity (ref.)
    • Update objects that are updated with a as:Update activity (ref.)
    • Replace objects that are deleted with a as:Delete with a as:Tombstone (ref.)
  • All JSON-LD objects should use the https://www.w3.org/ns/activitystreams context or they may not be understood by other Mastodon servers (see below for more details)
  • ActivityStreams Content-Type headers should be application/ld+json; profile="https://www.w3.org/ns/activitystreams" or application/activity+json. At the moment, Mastodon servers don’t work without these headers (see below for more details)

What MAY be implemented ?

If we want to implement the whole ActivityPub spec, here are other things to consider

  • Attach liked AS collections to the WebID
  • Attach likes/shared/replies AS collections to resources (for ActivityPods, we create them only when related activities are sent)
  • Handle side effects
    • When receiving or emitting Like activities, add the corresponding items to the liked/likes AS collections.
    • When receiving objects with a as:inReplyTo predicate, add the object to the resplies AS collection attached to the object.

How could this be implemented ?

I believe ActivityPub could be added to Solid in several ways:

  • As an extension of CSS, that is activated by default
  • As an extension of CSS, that can be activated by the server owner
  • As an external agent, similar to SAI’s Authorization Agent

The advantage of the last option is that it could be installed upon any Solid server, and so the user would not depend on the choice of the server owner. In fact, if some Solid users have ActivityPub inboxes, and others don’t, it will certainly be difficult to have working apps.

But I see several difficulties that are highlighted below (there may be others).

Difficulty #1: Inbox and outbox endpoints

In the scenario of an ActivityPub agent, the inbox and outbox endpoints would necessarily need to be on the server of this agent, because (as we’ve seen above) the POST endpoint is very specific and the GET must return an AS OrderedCollection, which is IMO impossible to handle as a regular Solid resource (more about this below).

This may not be a problem. But the inbox and outbox are important aspects of ActivityPub so they must be accessible by any user and also applications registered through SAI. It must be possible to fetch or listen to them, if we have the authorizations. So I wonder how well two Solid agents (SAI Authorization Agent + the ActivityPub agent) could work together, especially on endpoints which are outside of the user’s Pod ? This would require more discussions and thinking.

Another thing to consider is that it would mean the content of the inbox and outbox (ie. the URIs of related activities) would be stored outside of the Pod, in the ActivityPub agent server. I’m not sure if that’s such a good practice. If another ActivityPub agent is created, and the Pod owner wants to switch to this agent, they will lose their inbox and outbox content.

Difficulty #2: Webfinger

The /.well-known/webfinger endpoint must be at the root of the identity provider, so it cannot be handled by an ActivityPub agent. I know that @thhck developed a CSS extension for that, so it would require to activate it by default (it’s a very lightweight spec).

Difficulty #3: ActivityStreams collections

AS collections are a big subject so I will only touch upon it.

Some people suggested that LDP containers could be displayed as AS collection if the Accept header was different. However, from a semantic perspective, a ldp:Container with resources linked through ldp:includes is not the same as a as:Collection with resources linked through as:items. Overall it’s not a good practice to change the content of an RDF resource based on the Accept header.

Another solution would be to use LDP Direct Containers to double ldp:includes links with as:items. But Solid only allow LDP Basic Containers.

Anyway, all that would not solve the following problems:

  • Collections handle pagination in a very different way from LDP containers (where pagination is optional, passed through a header). If collections were simple resources, we would need to create possibly one resource per page.
  • Collections can be ordered, and in this case we are supposed to persist this order. The LDP paging spec has something about ordering, but it is done “on the fly”. Order persistance is overall difficult to handle with RDF data (we are having a long discussions on this issue about this subject)
  • Some AS collections dereference the contained items, and other don’t. The specs are not very clear about this. Inboxes and outboxes are supposed to dereference activities they contain. But they must only dereference activities that the authenticated agent has the right to see. Nothing like this exists in LDP containers.

As a conclusion, with the current Solid implementations, we can display non-ordered non-paginated collections as simple LDP resources. But as soon as we want to paginate, sort or dereference them, it becomes more difficult. Luckily only the inbox and outbox really need to be paginated and ordered, so it can be handled on an external endpoint.

Difficulty #4: JSON-LD context

Most ActivityPub-compatible servers expect JSON-LD resources with a https://www.w3.org/ns/activitystreams context. This context transforms @type to type, @id to id and removes the as: prefix. Currently there is nothing in Solid that defines how contexts should be used, since they are not saved.

For POSTing data between outboxes and inboxes, we can prepare a JSON-LD based on this context without problem. But most ActivityPub servers expect to GET objects and activities also with this context.

In ActivityPods, we defined a new JsonLdContext header to force JSON-LD data to be formatted with a given context, but this is not standard.

Difficulty #5: Content-Type headers

Since a recent version, Mastodon servers expect that the actors, activities and objects it GETs return a Content-Type header application/ld+json; profile="https://www.w3.org/ns/activitystreams" or application/activity+json.

I don’t know why they made this change. Maybe with some discussions, they could remove this requirement, which is not part of the ActivityPub specs.

Where to go next ?

This is just a brief overview of the various challenges that would be involved in making Solid fully compatible with ActivityPub.

I know an attempt was made during the “Solid for Social Networks” hackathon last year, but I can’t remember exactly how it turned out, and the blog post about it now redirects to another blog post. Can someone help about this? (Ping @hzbarcea)

If creating an ActivityPub agent proves to be too difficult, we should probably consider developing a CSS extension instead. I know that @thhck might be interested in working on this, if there was funding available.

Personally I won’t have time to develop either an ActivityPub agent or a CSS extension, but I can spend some time sharing my understanding of ActivityPub (I’ve been building ActivityPub servers since the spec came out in 2018, so I’m pretty fluent in that “language”…). I can also help writing a specification for the “glue” needed to make Solid Pods compatible with ActivityPub. This would be very useful for ActivityPods too!

So if we want to go further into this, we should probably consider forming a Community Group on the subject. Who would be interested in taking an active part to such a group ? If there is enough interest, we could have a first meeting in January?

Let me know ! :slight_smile:
Sébastien

7 Likes

amazing write-up! clear, to the point, full of detailed rich from your expertise! congrats

2 Likes

This is awesome! :star_struck: Very informative, and exactly what I was hoping for.

I’d be potentially interested both in implementing external agent, and participating in the group. I’ll certainly have another, more detailed look at this post.

I think the external inbox/outbox endpoint could write to, store data, and read from the Solid pod under the hood in some suitable format, which would fix one of the challenges.

Great stuff, thank you!

2 Likes

Excellent write-up!

On difficulty no. 4 - is that JSON-LD framing required by the AT spec? If so is there an appropriate place / spec repo to raise a conversation about this?

In the ActivityPub specs, it is a SHOULD (“Implementers SHOULD include the ActivityPub context in their object definitions.”). I raised this issue because, in reality, many ActivityPub softwares don’t do any framing (they treat JSON-LD as a simple JSON) and so they will not understand objects or activities that don’t use this context.

1 Like