Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/activitypub')
-rw-r--r--doc/development/activitypub/actor.md11
-rw-r--r--doc/development/activitypub/actors/group.md205
-rw-r--r--doc/development/activitypub/actors/index.md148
-rw-r--r--doc/development/activitypub/actors/project.md640
-rw-r--r--doc/development/activitypub/actors/releases.md85
-rw-r--r--doc/development/activitypub/actors/topic.md91
-rw-r--r--doc/development/activitypub/actors/user.md47
-rw-r--r--doc/development/activitypub/index.md216
8 files changed, 1443 insertions, 0 deletions
diff --git a/doc/development/activitypub/actor.md b/doc/development/activitypub/actor.md
new file mode 100644
index 00000000000..1d10e421df7
--- /dev/null
+++ b/doc/development/activitypub/actor.md
@@ -0,0 +1,11 @@
+---
+redirect_to: 'actors/index.md'
+remove_date: '2023-12-08'
+---
+
+This document was moved to [another location](actors/index.md).
+
+<!-- This redirect file can be deleted after <2023-12-08>. -->
+<!-- Redirects that point to other docs in the same project expire in three months. -->
+<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/development/activitypub/actors/group.md b/doc/development/activitypub/actors/group.md
new file mode 100644
index 00000000000..dad02298170
--- /dev/null
+++ b/doc/development/activitypub/actors/group.md
@@ -0,0 +1,205 @@
+---
+stage: Create
+group: Source Code
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments"
+---
+
+# Activities for group actor **(EXPERIMENT)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127023) in GitLab 16.5 [with two flags](../../../administration/feature_flags.md) named `activity_pub` and `activity_pub_project`. Disabled by default. This feature is an [Experiment](../../../policy/experiment-beta-support.md).
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+an administrator can [enable the feature flags](../../../administration/feature_flags.md)
+named `activity_pub` and `activity_pub_project`.
+On GitLab.com, this feature is not available.
+The feature is not ready for production use.
+
+This feature requires two feature flags:
+
+- `activity_pub`: Enables or disables all ActivityPub-related features.
+- `activity_pub_project`: Enables and disable ActivityPub features specific to
+ projects. Requires the `activity_pub` flag to also be enabled.
+
+## Profile
+
+```javascript
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": GROUP_URL,
+ "type": "Group",
+ "name": GROUP_NAME,
+ "summary": GROUP_DESCRIPTION,
+ "url": GROUP_URL,
+ "outbox": GROUP_OUTBOX_URL,
+ "inbox": null,
+}
+```
+
+## Outbox
+
+The various activities for a group are:
+
+- [The group was created](#the-group-was-created).
+- All project activities for projects in that group, and its subgroups.
+- [A user joined the group](#a-user-joined-the-group).
+- [A user left the group](#a-user-left-the-group).
+- [The group was deleted](#the-group-was-deleted).
+- [A subgroup was created](#a-subgroup-was-created).
+- [A subgroup was deleted](#a-subgroup-was-deleted).
+
+### The group was created
+
+```javascript
+{
+ "id": GROUP_OUTBOX_URL#event_id,
+ "type": "Create",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": GROUP_URL,
+ "type": "Group",
+ "name": GROUP_NAME,
+ "url": GROUP_URL,
+ }
+}
+```
+
+### A user joined the group
+
+```javascript
+{
+ "id": GROUP_OUTBOX_URL#event_id,
+ "type": "Join",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": GROUP_URL,
+ "type": "Group",
+ "name": GROUP_NAME,
+ "url": GROUP_URL,
+ },
+}
+```
+
+### A user left the group
+
+```javascript
+{
+ "id": GROUP_OUTBOX_URL#event_id,
+ "type": "Leave",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": GROUP_URL,
+ "type": "Group",
+ "name": GROUP_NAME,
+ "url": GROUP_URL,
+ },
+}
+```
+
+### The group was deleted
+
+```javascript
+{
+ "id": GROUP_OUTBOX_URL#event_id,
+ "type": "Delete",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": GROUP_URL,
+ "type": "Group",
+ "name": GROUP_NAME,
+ "url": GROUP_URL,
+ }
+}
+```
+
+### A subgroup was created
+
+```javascript
+{
+ "id": GROUP_OUTBOX_URL#event_id,
+ "type": "Create",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": GROUP_URL,
+ "type": "Group",
+ "name": GROUP_NAME,
+ "url": GROUP_URL,
+ "context": {
+ "id": PARENT_GROUP_URL,
+ "type": "Group",
+ "name": PARENT_GROUP_NAME,
+ "url": PARENT_GROUP_URL,
+ }
+ }
+}
+```
+
+### A subgroup was deleted
+
+```javascript
+{
+ "id": GROUP_OUTBOX_URL#event_id,
+ "type": "Delete",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": GROUP_URL,
+ "type": "Group",
+ "name": GROUP_NAME,
+ "url": GROUP_URL,
+ "context": {
+ "id": PARENT_GROUP_URL,
+ "type": "Group",
+ "name": PARENT_GROUP_NAME,
+ "url": PARENT_GROUP_URL,
+ }
+ }
+}
+```
diff --git a/doc/development/activitypub/actors/index.md b/doc/development/activitypub/actors/index.md
new file mode 100644
index 00000000000..032cb26587a
--- /dev/null
+++ b/doc/development/activitypub/actors/index.md
@@ -0,0 +1,148 @@
+---
+stage: Create
+group: Source Code
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments"
+---
+
+# Implement an ActivityPub actor **(EXPERIMENT)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127023) in GitLab 16.5 [with two flags](../../../administration/feature_flags.md) named `activity_pub` and `activity_pub_project`. Disabled by default. This feature is an [Experiment](../../../policy/experiment-beta-support.md).
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+an administrator can [enable the feature flags](../../../administration/feature_flags.md)
+named `activity_pub` and `activity_pub_project`.
+On GitLab.com, this feature is not available.
+The feature is not ready for production use.
+
+This feature requires two feature flags:
+
+- `activity_pub`: Enables or disables all ActivityPub-related features.
+- `activity_pub_project`: Enables and disable ActivityPub features specific to
+ projects. Requires the `activity_pub` flag to also be enabled.
+
+ActivityPub is based on three standard documents:
+
+- [ActivityPub](https://www.w3.org/TR/activitypub/) defines the HTTP
+ requests happening to implement federation.
+- [ActivityStreams](https://www.w3.org/TR/activitystreams-core/) defines the
+ format of the JSON messages exchanged by the users of the protocol.
+- [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/)
+ defines the various messages recognized by default.
+
+The first one is typically handled by controllers, while the two others are
+related to what happen in serializers.
+
+To implement an ActivityPub actor, you must:
+
+- Implement the profile page of the resource.
+- Implement the outbox page.
+- Handle incoming requests on the inbox.
+
+All requests are made using
+`application/ld+json; profile="https://www.w3.org/ns/activitystreams"` as `Accept` HTTP header.
+
+The actors we're implementing for the social features:
+
+- [Releases](releases.md)
+- [Topics](topic.md)
+- [Projects](project.md)
+- [Groups](group.md)
+- [Users](user.md)
+
+## Profile page
+
+Querying the profile page is used to retrieve:
+
+- General information about it, like name and description.
+- URLs for the inbox and the outbox.
+
+To implement a profile page, create an ActivityStreams
+serializer in `app/serializers/activity_pub/`, making your serializer
+inherit from `ActivityStreamsSerializer`. See below in the serializers
+section about the mandatory fields.
+
+To call your serializer in your controller:
+
+```ruby
+opts = {
+ inbox: nil,
+ outbox: outbox_project_releases_url(project)
+}
+
+render json: ActivityPub::ReleasesActorSerializer.new.represent(project, opts)
+```
+
+- `outbox` is the endpoint where to find the activities feed for this
+actor.
+- `inbox` is where to POST to subscribe to the feed. Not yet implemented, so pass `nil`.
+
+## Outbox page
+
+The outbox is the list of activities for the resource. It's a feed for the
+resource, and it allows ActivityPub clients to show public activities for
+this actor without having yet subscribed to it.
+
+To implement an outbox page, create an ActivityStreams
+serializer in `app/serializers/activity_pub/`, making your serializer
+inherit from `ActivityStreamsSerializer`. See below in the serializers
+section about the mandatory fields.
+
+You call your serializer in your controller like this:
+
+```ruby
+serializer = ActivityPub::ReleasesOutboxSerializer.new.with_pagination(request, response)
+render json: serializer.represent(releases)
+```
+
+This converts the response to an `OrderedCollection`
+ActivityPub type, with all the correct fields.
+
+## Inbox
+
+Not yet implemented.
+
+The inbox is where the ActivityPub compatible third-parties makes their
+requests, to subscribe to the actor or send it messages.
+
+## ActivityStreams serializers
+
+The serializers implement half the core of ActivityPub support: they're all
+about [ActivityStreams](https://www.w3.org/TR/activitystreams-core/), the
+message format used by ActivityPub.
+
+To leverage the features doing most of the formatting for you, your
+serializer should inherit from `ActivityPub::ActivityStreamsSerializer`.
+
+To use it, call the `#represent` method. It requires you to provide
+`inbox` and `outbox` options (as mentioned above) if it
+is an actor profile page. You don't need those if your serializer
+represents an object that is just meant to be embedded as part of actors,
+like the object representing the contact information for a user.
+
+Each resource serialized (included other objects embedded in your
+actor) must provide an `id` and a `type` field.
+
+`id` is a URL. It's meant to be a unique identifier for the resource, and
+it must point to an existing page: ideally, an actor. Otherwise, you can
+just reference the closest actor and use an anchor, like this:
+
+```plaintext
+https://gitlab.com/user/project/-/releases#release-1
+```
+
+`type` should be taken from ActivityStreams core vocabulary:
+
+- [Activity types](https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)
+- [Actor types](https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)
+- [Object types](https://www.w3.org/TR/activitystreams-vocabulary/#object-types)
+
+The properties you can use are all documented in
+[the ActivityStreams vocabulary document](https://www.w3.org/TR/activitystreams-vocabulary).
+Given the type you have chosen for your resource, find the
+`properties` list, telling you all available properties, direct or
+inherited.
+
+It's worth noting that Mastodon adds one more property, `preferredName`.
+Mastodon expects it to be set on any actor, or that actor is not recognized by
+Mastodon.
diff --git a/doc/development/activitypub/actors/project.md b/doc/development/activitypub/actors/project.md
new file mode 100644
index 00000000000..4f876b9e3fa
--- /dev/null
+++ b/doc/development/activitypub/actors/project.md
@@ -0,0 +1,640 @@
+---
+stage: Create
+group: Source Code
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments"
+---
+
+# Activities for project actor **(EXPERIMENT)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127023) in GitLab 16.5 [with two flags](../../../administration/feature_flags.md) named `activity_pub` and `activity_pub_project`. Disabled by default. This feature is an [Experiment](../../../policy/experiment-beta-support.md).
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+an administrator can [enable the feature flags](../../../administration/feature_flags.md)
+named `activity_pub` and `activity_pub_project`.
+On GitLab.com, this feature is not available.
+The feature is not ready for production use.
+
+This feature requires two feature flags:
+
+- `activity_pub`: Enables or disables all ActivityPub-related features.
+- `activity_pub_project`: Enables and disable ActivityPub features specific to
+ projects. Requires the `activity_pub` flag to also be enabled.
+
+## Profile
+
+```javascript
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ "outbox": PROJECT_OUTBOX_URL,
+ "inbox": null,
+}
+```
+
+## Outbox
+
+For a project, we can map the events happening on the project activity
+timeline on GitLab, when a user:
+
+- [Creates the repository](#user-creates-the-repository).
+- [Pushes commits](#user-pushes-commits).
+- [Pushes a tag](#user-pushes-a-tag).
+- [Opens a merge request](#user-opens-a-merge-request).
+- [Accepts a merge request](#user-accepts-a-merge-request).
+- [Closes a merge request](#user-closes-a-merge-request).
+- [Opens an issue](#user-opens-an-issue).
+- [Closes an issue](#user-closes-an-issue).
+- [Reopens an issue](#user-reopens-an-issue).
+- [Comments on a merge request](#user-comments-on-a-merge-request).
+- [Comments on an issue](#user-comments-on-an-issue).
+- [Creates a wiki page](#user-creates-a-wiki-page).
+- [Updates a wiki page](#user-updates-a-wiki-page).
+- [Destroys a wiki page](#user-destroys-a-wiki-page).
+- [Joins the project](#user-joins-the-project).
+- [Leaves the project](#user-leaves-the-project).
+- [Deletes the repository](#user-deletes-the-repository).
+
+There's also a Design tab in the project activities, but it's just empty in
+all projects I follow and I don't see anything related to it in my projects
+sidebar. Maybe it's a premium feature? If so, it's of no concern to us for
+public following through ActivityPub.
+
+### User creates the repository
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Create",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ }
+}
+```
+
+### User pushes commits
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Update",
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ "result": COMMITS_DIFF_URL,
+}
+```
+
+### User pushes a tag
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Update",
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ "name": TAG_NAME,
+ "result": COMMIT_URL,
+}
+```
+
+### User opens a merge request
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Add",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": MERGE_REQUEST_URL,
+ "type": "Application",
+ "name": MERGE_REQUEST_TITLE,
+ "url": MERGE_REQUEST_URL,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ },
+ "target": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+}
+```
+
+### User accepts a merge request
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Accept",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": MERGE_REQUEST_URL,
+ "type": "Application",
+ "name": MERGE_REQUEST_TITLE,
+ "url": MERGE_REQUEST_URL,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ },
+ "target": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+}
+```
+
+### User closes a merge request
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Remove",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": MERGE_REQUEST_URL,
+ "type": "Application",
+ "name": MERGE_REQUEST_TITLE,
+ "url": MERGE_REQUEST_URL,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ },
+ "origin": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+}
+```
+
+### User opens an issue
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Add",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": ISSUE_URL,
+ "type": "Page",
+ "name": ISSUE_TITLE,
+ "url": ISSUE_URL,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ }
+ },
+ "target": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ }
+}
+```
+
+Why to add the project both as `object.context` and `target`? For multiple
+consistency reasons:
+
+- The **Add** activity is more commonly used with a `target`.
+- The **Remove** activity used to close the issue is more
+ commonly used with an `origin`.
+- The **Update** activity used to reopen an issue specifies that
+ `target` and `origin` have no specific meaning, making `context` better
+ suited for that.
+- We could use `context` only with **Update**, but merge requests
+ must be taken into consideration.
+
+Merge requests are very similar to issues, so we want their activities to
+be similar. While the best type for issues is `page`, the type chosen for
+merge request is `application`, both to distinguish it from issues and because
+they contain code.
+
+To distinguish merge requests from projects (which are also `application`),
+merge requests are an `application` with another `application` (the project)
+as context. Given the merge request will have a `context` even with the **Add**
+and **Remove** activities, the same is done with issues for consistency.
+
+An alternative that was considered, but dismissed: instead of **Add** for issues,
+use **Create**. That would have allowed us to always use `context`, but
+it creates more problems that it solves. **Accept** and **Reject** could work quite
+well for closing merge requests, but what would we use to close issues?
+**Delete** is incorrect, as the issue is not deleted, just closed.
+Reopening the issue later would require an **Update** after a
+**Delete**.
+
+Using **Create** for opening issues and **Remove** for closing
+issues would be asymmetrical:
+
+- **Create** is mirrored by **Delete**.
+- **Add** is mirrored by **Remove**.
+
+To minimize pain for those who will build on top of those resources, it's best
+to duplicate the project information as `context` and `target` / `origin`.
+
+### User closes an issue
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Remove",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": ISSUE_URL,
+ "type": "Page",
+ "name": ISSUE_TITLE,
+ "url": ISSUE_URL,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ },
+ "origin": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+}
+```
+
+### User reopens an issue
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Update",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": ISSUE_URL,
+ "type": "Page",
+ "name": ISSUE_TITLE,
+ "url": ISSUE_URL,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ },
+}
+```
+
+### User comments on a merge request
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Add",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": NOTE_URL,
+ "type": "Note",
+ "content": NOTE_NOTE,
+ },
+ "target": {
+ "id": MERGE_REQUEST_URL,
+ "type": "Application",
+ "name": MERGE_REQUEST_TITLE,
+ "url": MERGE_REQUEST_URL,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ },
+}
+```
+
+### User comments on an issue
+
+```javascript
+{
+ "id": PROJECT_URL#event_id,
+ "type": "Add",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": NOTE_URL,
+ "type": "Note",
+ "content": NOTE_NOTE,
+ },
+ "target": {
+ "id": ISSUE_URL,
+ "type": "Page",
+ "name": ISSUE_TITLE,
+ "url": ISSUE_URL,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ },
+}
+```
+
+### User creates a wiki page
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Create",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": WIKI_PAGE_URL,
+ "type": "Page",
+ "name": WIKI_PAGE_HUMAN_TITLE,
+ "url": WIKI_PAGE_URL,
+ }
+}
+```
+
+### User updates a wiki page
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Update",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": WIKI_PAGE_URL,
+ "type": "Page",
+ "name": WIKI_PAGE_HUMAN_TITLE,
+ "url": WIKI_PAGE_URL,
+ }
+}
+```
+
+### User destroys a wiki page
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Delete",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": WIKI_PAGE_URL,
+ "type": "Page",
+ "name": WIKI_PAGE_HUMAN_TITLE,
+ "url": WIKI_PAGE_URL,
+ }
+}
+```
+
+### User joins the project
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Add",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "target": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+}
+```
+
+The GitLab project timeline does not mention who added a member to the
+project, so this does the same. However, the **Add** activity requires an Actor.
+For that reason, we use the same person as actor and object.
+
+In the **Members** page of a project contains a `source` attribute.
+While there is sometimes mention of who added the user, this is used mainly
+to distinguish if the user is a member attached to the project directly, or
+through a group. It would not be a good "actor" (that would rather be an
+`origin` for the membership).
+
+### User leaves the project
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Remove",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "target": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+}
+```
+
+See [User joined the project](#user-joins-the-project).
+
+### User deletes the repository
+
+```javascript
+{
+ "id": PROJECT_OUTBOX_URL#event_id,
+ "type": "Delete",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ }
+}
+```
diff --git a/doc/development/activitypub/actors/releases.md b/doc/development/activitypub/actors/releases.md
new file mode 100644
index 00000000000..009b98b6adf
--- /dev/null
+++ b/doc/development/activitypub/actors/releases.md
@@ -0,0 +1,85 @@
+---
+stage: Create
+group: Source Code
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments"
+---
+
+# Activities for following releases actor **(EXPERIMENT)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127023) in GitLab 16.5 [with two flags](../../../administration/feature_flags.md) named `activity_pub` and `activity_pub_project`. Disabled by default. This feature is an [Experiment](../../../policy/experiment-beta-support.md).
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+an administrator can [enable the feature flags](../../../administration/feature_flags.md)
+named `activity_pub` and `activity_pub_project`.
+On GitLab.com, this feature is not available.
+The feature is not ready for production use.
+
+This feature requires two feature flags:
+
+- `activity_pub`: Enables or disables all ActivityPub-related features.
+- `activity_pub_project`: Enables and disable ActivityPub features specific to
+ projects. Requires the `activity_pub` flag to also be enabled.
+
+## Profile
+
+The profile is this actor is a bit different from other actors. We don't want to
+show activities for a given release, but instead the releases for a given project.
+
+The profile endpoint is handled by `Projects::ReleasesController#index`
+on the list of releases, and should reply with something like this:
+
+```javascript
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": PROJECT_RELEASES_URL,
+ "type": "Application",
+ "name": PROJECT_NAME + " releases",
+ "url": PROJECT_RELEASES_URL,
+ "content": PROJECT_DESCRIPTION,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ "outbox": PROJECT_RELEASES_OUTBOX_URL,
+ "inbox": null,
+}
+```
+
+## Outbox
+
+The release actor is relatively simple: the only activity happening is the
+**Create release** event.
+
+```javascript
+{
+ "id": PROJECT_RELEASES_OUTBOX_URL#release_id,
+ "type": "Create",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ },
+ "object": {
+ "id": RELEASE_URL,
+ "type": "Application",
+ "name": RELEASE_TITLE,
+ "url": RELEASE_URL,
+ "content": RELEASE_DESCRIPTION,
+ "context": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "summary": PROJECT_DESCRIPTION,
+ "url": PROJECT_URL,
+ },
+ },
+}
+```
diff --git a/doc/development/activitypub/actors/topic.md b/doc/development/activitypub/actors/topic.md
new file mode 100644
index 00000000000..f99a6e0569a
--- /dev/null
+++ b/doc/development/activitypub/actors/topic.md
@@ -0,0 +1,91 @@
+---
+stage: Create
+group: Source Code
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments"
+---
+
+# Activities for topic actor **(EXPERIMENT)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127023) in GitLab 16.5 [with two flags](../../../administration/feature_flags.md) named `activity_pub` and `activity_pub_project`. Disabled by default. This feature is an [Experiment](../../../policy/experiment-beta-support.md).
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+an administrator can [enable the feature flags](../../../administration/feature_flags.md)
+named `activity_pub` and `activity_pub_project`.
+On GitLab.com, this feature is not available.
+The feature is not ready for production use.
+
+This feature requires two feature flags:
+
+- `activity_pub`: Enables or disables all ActivityPub-related features.
+- `activity_pub_project`: Enables and disable ActivityPub features specific to
+ projects. Requires the `activity_pub` flag to also be enabled.
+
+## Profile
+
+```javascript
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": TOPIC_URL,
+ "type": "Group",
+ "name": TOPIC_NAME,
+ "url": TOPIC_URL,
+ "summary": TOPIC_DESCRIPTION,
+ "outbox": TOPIC_OUTBOX_URL,
+ "inbox": null,
+}
+```
+
+## Outbox
+
+Like the release actor, the topic specification is not complex. It generates an
+activity only when a new project has been added to the given topic.
+
+```javascript
+{
+ "id": TOPIC_OUTBOX_URL#event_id,
+ "type": "Add",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "actor": {
+ "id": PROJECT_URL,
+ "type": "Application",
+ "name": PROJECT_NAME,
+ "url": PROJECT_URL,
+ },
+ "object": {
+ "id": TOPIC_URL,
+ "type": "Group",
+ "name": TOPIC_NAME,
+ "url": TOPIC_URL,
+ },
+ },
+}
+```
+
+## Possible difficulties
+
+There is hidden complexity here.
+
+The simpler way to build this endpoint is to take the projects associated
+to a topic, and sort them by descending creation date. However,
+if we do that, discrepancies will occur when implementing the
+activity push part of the standard.
+
+Adding the project to a topic is not made at project creation time. It's
+made when a project's topics are _edited_. That action can happen a very long time
+after the project creation date. In that case, a push activity is
+created and sent to federated instances when adding the topic to the
+project. However, the list in the outbox endpoint that sorts projects by descending
+creation date doesn't show the project, because it was created long ago.
+
+No special logic happens when a topic is added to a project, except:
+
+- Cleaning up the topic list.
+- Creating the topic in database, if it doesn't exist yet.
+
+No event is generated. We should add such an event so the activity
+push create an event, ideally in `Projects::UpdateService`. Then, the outbox endpoint
+can list those events to be sure to match what was sent. When doing that, we should
+verify that it doesn't affect other pages or endpoints dealing with events.
diff --git a/doc/development/activitypub/actors/user.md b/doc/development/activitypub/actors/user.md
new file mode 100644
index 00000000000..9fe4f8ec88e
--- /dev/null
+++ b/doc/development/activitypub/actors/user.md
@@ -0,0 +1,47 @@
+---
+stage: Create
+group: Source Code
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments"
+---
+
+# Activities for following user actor **(EXPERIMENT)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127023) in GitLab 16.5 [with two flags](../../../administration/feature_flags.md) named `activity_pub` and `activity_pub_project`. Disabled by default. This feature is an [Experiment](../../../policy/experiment-beta-support.md).
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+an administrator can [enable the feature flags](../../../administration/feature_flags.md)
+named `activity_pub` and `activity_pub_project`.
+On GitLab.com, this feature is not available.
+The feature is not ready for production use.
+
+This feature requires two feature flags:
+
+- `activity_pub`: Enables or disables all ActivityPub-related features.
+- `activity_pub_project`: Enables and disable ActivityPub features specific to
+ projects. Requires the `activity_pub` flag to also be enabled.
+
+## Profile
+
+This activity is the first resource ActivityPub has in mind:
+
+```javascript
+{
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": USER_PROFILE_URL,
+ "type": "Person",
+ "name": USER_NAME,
+ "url": USER_PROFILE_URL,
+ "outbox": USER_OUTBOX_URL,
+ "inbox": null,
+}
+```
+
+## Outbox
+
+The user actor is special because it can be linked to all events happening on the platform.
+It's a join of events on other resources:
+
+- All release activities.
+- All project activities.
+- All group activities.
diff --git a/doc/development/activitypub/index.md b/doc/development/activitypub/index.md
new file mode 100644
index 00000000000..d89f18080f0
--- /dev/null
+++ b/doc/development/activitypub/index.md
@@ -0,0 +1,216 @@
+---
+stage: Create
+group: Source Code
+info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments"
+---
+
+# ActivityPub **(EXPERIMENT)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127023) in GitLab 16.5 [with two flags](../../administration/feature_flags.md) named `activity_pub` and `activity_pub_project`. Disabled by default. This feature is an [Experiment](../../policy/experiment-beta-support.md).
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+an administrator can [enable the feature flags](../../administration/feature_flags.md)
+named `activity_pub` and `activity_pub_project`.
+On GitLab.com, this feature is not available.
+The feature is not ready for production use.
+
+Usage of ActivityPub in GitLab is governed by the
+[GitLab Testing Agreement](https://about.gitlab.com/handbook/legal/testing-agreement/).
+
+The goal of those documents is to provide an implementation path for adding
+Fediverse capabilities to GitLab.
+
+This page describes the conceptual and high level point of view, while
+sub-pages discuss implementation in more technical depth (as in, how to
+implement this in the actual rails codebase of GitLab).
+
+This feature requires two feature flags:
+
+- `activity_pub`: Enables or disables all ActivityPub-related features.
+- `activity_pub_project`: Enables and disable ActivityPub features specific to
+ projects. Requires the `activity_pub` flag to also be enabled.
+
+## What
+
+Feel free to jump to [the Why section](#why) if you already know what
+ActivityPub and the Fediverse are.
+
+Among the push for [decentralization of the web](https://en.wikipedia.org/wiki/Decentralized_web),
+several projects tried different protocols with different ideals behind their reasoning.
+Some examples:
+
+- [Secure Scuttlebutt](https://en.wikipedia.org/wiki/Secure_Scuttlebutt) (or SSB for short)
+- [Dat](https://en.wikipedia.org/wiki/Dat_%28software%29)
+- [IPFS](https://en.wikipedia.org/wiki/InterPlanetary_File_System),
+- [Solid](https://en.wikipedia.org/wiki/Solid_%28web_decentralization_project%29)
+
+One gained traction recently: [ActivityPub](https://en.wikipedia.org/wiki/ActivityPub),
+better known for the colloquial [Fediverse](https://en.wikipedia.org/wiki/Fediverse) built
+on top of it, through applications like
+[Mastodon](https://en.wikipedia.org/wiki/Mastodon_%28social_network%29)
+(which could be described as some sort of decentralized Facebook) or
+[Lemmy](https://en.wikipedia.org/wiki/Lemmy_%28software%29) (which could be
+described as some sort of decentralized Reddit).
+
+ActivityPub has several advantages that makes it attractive
+to implementers and could explain its current success:
+
+- **It's built on top of HTTP**. You don't need to install new software or
+ to tinker with TCP/UDP to implement ActivityPub, if you have a webserver
+ or an application that provides an HTTP API (like a rails application),
+ you already have everything you need.
+- **It's built on top of JSON**. All communications are basically JSON
+ objects, which web developers are already used to, which simplifies adoption.
+- **It's a W3C standard and already has multiple implementations**. Being
+ piloted by the W3C is a guarantee of stability and quality work. They
+ have profusely demonstrated in the past through their work on HTML, CSS
+ or other web standards that we can build on top of their work without
+ the fear of it becoming deprecated or irrelevant after a few years.
+
+### The Fediverse
+
+The core idea behind Mastodon and Lemmy is called the Fediverse. Rather
+than full decentralization, those applications rely on federation, in the
+sense that there still are servers and clients. It's not P2P like SSB,
+Dat and IPFS, but instead a galaxy of servers chatting with each other
+instead of having central servers controlled by a single entity.
+
+The user signs up to one of those servers (called **instances**), and they
+can then interact with users either on this instance, or on other ones.
+From the perspective of the user, they access a global network, and not
+only their instance. They see the articles posted on other instances, they
+can comment on them, upvote them, etc.
+
+What happens behind the scenes:
+their instance knows where the user they reply to is hosted. It
+contacts that other instance to let them know there is a message for them -
+somewhat similar to SMTP. Similarly, when a user subscribes
+to a feed, their instance informs the instance where the feed is
+hosted of this subscription. That target instance then posts back
+messages when new activities are created. This allows for a push model, rather
+than a constant poll model like RSS. Of course, what was just described is
+the happy path; there is moderation, validation and fault tolerance
+happening all the way.
+
+### ActivityPub
+
+Behind the Fediverse is the ActivityPub protocol. It's a HTTP API
+attempting to be as general a social network implementation as possible,
+while giving options to be extendable.
+
+The basic idea is that an `actor` sends and receives `activities`. Activities
+are structured JSON messages with well-defined properties, but are extensible
+to cover any need. An actor is defined by four endpoints, which are
+contacted with the
+`application/ld+json; profile="https://www.w3.org/ns/activitystreams"` HTTP Accept header:
+
+- `GET /inbox`: used by the actor to find new activities intended for them.
+- `POST /inbox`: used by instances to push new activities intended for the actor.
+- `GET /outbox`: used by anyone to read the activities created by the actor.
+- `POST /outbox`: used by the actor to publish new activities.
+
+Among those, Mastodon and Lemmy only use `POST /inbox` and `GET /outbox`, which
+are the minimum needed to implement federation:
+
+- Instances push new activities for the actor on the inbox.
+- Reading the outbox allows reading the feed of an actor.
+
+Additionally, Mastodon and Lemmy implement a `GET /` endpoint (with the
+mentioned Accept header). This endpoint responds with general information about the
+actor, like name and URL of the inbox and outbox. While not required by the
+standard, it makes discovery easier.
+
+While a person is the main use case for an actor, an actor does not
+necessarily map to a person. Anything can be an actor: a topic, a
+subreddit, a group, an event. For GitLab, anything with activities (in the sense
+of what GitLab means by "activity") can be an ActivityPub actor. This includes
+items like projects, groups, and releases. In those more abstract examples,
+an actor can be thought of as an actionable feed.
+
+ActivityPub by itself does not cover everything that is needed to implement
+the Fediverse. Most notably, these are left for the implementers to figure out:
+
+- Finding a way to deal with spam. Spam is handled by authorizing or
+ blocking ("defederating") other instances.
+- Discovering new instances.
+- Performing network-wide searches.
+
+## Why
+
+Why would a social media protocol be useful for GitLab? People want a single,
+global GitLab network to interact between various projects, without having to
+register on each of their hosts.
+
+Several very popular discussions around this have already happened:
+
+- [Share events externally via ActivityPub](https://gitlab.com/gitlab-org/gitlab/-/issues/21582)
+- [Implement cross-server (federated) merge requests](https://gitlab.com/gitlab-org/gitlab/-/issues/14116)
+- [Distributed merge requests](https://gitlab.com/groups/gitlab-org/-/epics/260).
+
+The ideal workflow would be:
+
+1. Alice registers to her favorite GitLab instance, like `gitlab.example.org`.
+1. She looks for a project on a given topic, and sees Bob's project, even though
+ Bob is on `gitlab.com`.
+1. Alice selects **Fork**, and the `gitlab.com/Bob/project.git` is
+ forked to `gitlab.example.org/Alice/project.git`.
+1. She makes her edits, and opens a merge request, which appears in Bob's
+ project on `gitlab.com`.
+1. Alice and Bob discuss the merge request, each one from their own GitLab
+ instance.
+1. Bob can send additional commits, which are picked up by Alice's instance.
+1. When Bob accepts the merge request, his instance picks up the code from
+ Alice's instance.
+
+In this process, ActivityPub would help in:
+
+- Letting Bob know a fork happened.
+- Sending the merge request to Bob.
+- Enabling Alice and Bob to discuss the merge request.
+- Letting Alice know the code was merged.
+
+It does _not_ help in these cases, which need specific implementations:
+
+- Implementing a network-wide search.
+- Implementing cross-instance forks. (Not needed, thanks to Git.)
+
+Why use ActivityPub here rather than implementing cross-instance merge requests
+in a custom way? Two reasons:
+
+1. **Building on top of a standard helps reach beyond GitLab**.
+ While the workflow presented above only mentions GitLab, building on top
+ of a W3C standard means other forges can follow GitLab
+ there, and build a massive Fediverse of code sharing.
+1. **An opportunity to make GitLab more social**. To prepare the
+ architecture for the workflow above, smaller steps can be taken, allowing
+ people to subscribe to activity feeds from their Fediverse social
+ network. Anything that has a RSS feed could become an ActivityPub feed.
+ People on Mastodon could follow their favorite developer, project, or topic
+ from GitLab and see the news in their feed on Mastodon, hopefully raising
+ engagement with GitLab.
+
+## How
+
+The idea of this implementation path is not to take the fastest route to
+the feature with the most value added (cross-instance merge requests), but
+to go on with the smallest useful step at each iteration, making sure each step
+brings something immediately.
+
+1. **Implement ActivityPub for social following**.
+ After this, the Fediverse can follow activities on GitLab instances.
+ 1. ActivityPub to subscribe to project releases.
+ 1. ActivityPub to subscribe to project creation in topics.
+ 1. ActivityPub to subscribe to project activities.
+ 1. ActivityPub to subscribe to group activities.
+ 1. ActivityPub to subscribe to user activities.
+1. **Implement cross-instance search** to enable discovering projects on other instances.
+1. **Implement cross-instance forks** to enable forking a project from an other instance.
+1. **Implement ActivityPub for cross-instance discussions** to enable discussing
+ issues and merge requests from another instance:
+ 1. In issues.
+ 1. In merge requests.
+1. **Implement ActivityPub to submit cross-instance merge requests** to enable
+ submitting merge requests to other instances.
+
+For now, see [how to implement an ActivityPub actor](actors/index.md).