diff options
Diffstat (limited to 'doc/development/activitypub')
-rw-r--r-- | doc/development/activitypub/actor.md | 11 | ||||
-rw-r--r-- | doc/development/activitypub/actors/group.md | 205 | ||||
-rw-r--r-- | doc/development/activitypub/actors/index.md | 148 | ||||
-rw-r--r-- | doc/development/activitypub/actors/project.md | 640 | ||||
-rw-r--r-- | doc/development/activitypub/actors/releases.md | 85 | ||||
-rw-r--r-- | doc/development/activitypub/actors/topic.md | 91 | ||||
-rw-r--r-- | doc/development/activitypub/actors/user.md | 47 | ||||
-rw-r--r-- | doc/development/activitypub/index.md | 216 |
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). |