ForgeFed

Branch Snapshot,

This version:
https://forgefed.org/spec
Issue Tracking:
Codeberg
Inline In Spec
Editors:
Alice Doe (MyCoolCompany)
Bob Doe (SomeOrg)

Abstract

This document describes the ForgeFed vocabulary. It’s intended to be an extension of [ActivityStreams-Vocabulary] and provides additional vocabulary for federation of project management and version control system hosting and collaboration platforms.

This document describes the rules and guidelines for representing version control and project management related objects as linked data, using the ForgeFed vocabulary, ActivityStreams 2, and other related vocabularies.

This document provides instructions for using ActivityPub activities and properties to represent forge events, and describes the side-effects these activities should have.

(fr33) In the abstract I just pasted the 3 abstracts of the separate specs. Write something new, probably with inspiration from existing specs such as ActivityPub.

This draft is generated from branch main, commit c5475d2.

1. Introduction

Note: The spec is still under construction. Want to start implementing? Check out the ActivityPub implementation guide, and then the § 3 Server to Server Interactions section here.

Below are the 3 intro texts from the 3 specs. We probably want to replace them with a human-friendly tutorial-like example, like in the ActivityPub spec.

The ForgeFed Vocabulary describes a set of types and properties to be used by platforms that support the ForgeFed protocol. This specification describes only the new vocabulary called ForgeFed. The ForgeFed behavior specification describes how to use this vocabulary, along with standard ActivityPub vocabulary, to support the ForgeFed protocol.

The ForgeFed modeling specification is a set of rules and guidelines which describe version control repository and project management related objects and properties, and specify how to represent them as JSON-LD objects (and linked data in general) using the ForgeFed vocabulary and related vocabularies and ontologies. Using these modeling rules consistently across implementations and instances allows to have a common language spoken across networks of software forges, project management apps and more.

The ForgeFed vocabulary specification defines a dedicated vocabulary of forge-related terms, and the modeling specification uses these terms, along with terms that already exist in ActivityPub or elsewhere and can be reused for forge federation.

The ForgeFed behavior specification provides instructions for using Activities, and which Activities and properties to use, to represent forge events, and describes the side-effects these Activities should have. The objects used as inputs and outputs of behavior descriptions there are defined here in the modeling specification.

The ForgeFed behavior specification is a set of instructions for representing version control systems and project management related transactions using ActivityPub activity objects, and it describes the side effects and expected results of sending and receiving these activities. The vocabulary for these activities includes standard ActivityPub terms, new terms defined by ForgeFed, and terms borrowed from other external vocabularies.

The ForgeFed vocabulary specification defines a dedicated vocabulary of forge-related terms, and the behavior specification uses these terms, along with terms that already exist in ActivityPub or elsewhere and can be reused for forge federation.

The ForgeFed modeling specification defines rules for representing forge related objects as ActivityPub JSON-LD objects, and these objects are used in the behavior specification, included in activities, mentioned in activities, or modified as a result of activity side-effects.

2. Objects

2.1. Kinds of Objects

Objects are the core concept around which both ActivityPub and ForgeFed are built. Examples of Objects are Note, Ticket, Image, Create, Push. Some objects are resources, which are objects that contain or represent information and user made or program made content, and some objects are helpers that exist as implementation detail aren’t necessarily exposed to humans or are useful to humans. But everything is an Object, represented as compacted JSON-LD.

ForgeFed is an ActivityPub extension, and communication between ForgeFed implementations occurs using activity objects sent to actor inboxes and outboxes.

There are 4 kinds of objects in ForgeFed:

Activities

These are objects that describe actions, such as actions that happened, actions that are happening, or a request to perform an action. Their primary use is for server-to-server interaction between actors by being sent to an actor’s inbox, and client-to-server interaction between a person or program and an actor they control by being sent to the actor’s outbox. Activities can also appear or be linked inside other objects and activities and be listed in Collections.

Actors

These are static persistent objects that have an inbox and can be directly interacted with by POSTing activities to it. Their primary use is to contain or represent information and output of user actions or program actions, and to manage access to this information and modifications of it.

Child objects

These are persistent objects that, like actors, contain or represent information and output of user actions or program actions, but they do not have their own inbox and are not directly interacted with. A managed static object always has a parent object, which is an actor, and that actor’s inbox is the way to interact with the child object. The parent actor manages access and modification of the child object.

Global helper objects

These are objects that do not belong to any actor and do not need any interaction through activities. As such, they do not exactly fit into the actor model, but may be involved in implementation details and practical considerations.

Actors, children, and globals are referred to in ForgeFed as static objects, while activities are dynamic objects. The terms constant and variable are used to indicate whether an object changes during its lifetime or not.

Static objects, in addition to being an actor or child or global, also have a resource/helper distinction:

Resource

Contains or represents information and user made or program made content, usually belongs to the domain model of version control systems and project management.

Helper

Used for running things behind the scenes, not exposed directly as user content, may be transient or auto generated, usually related to implementation detail and not to concepts of version control and project management.

2.2. Object Publishing and Hosting

In ForgeFed, actors host their child objects locally, meaning the actor and the child object are hosted on the same instance. Actors may create remote objects by offering them to the relevant actor, which then may create the object on their side and assign it a URI.

The process begins with an Offer activity, in which:

Among the recipients listed in the Offer's recipient fields, exactly one recipient is the actor who is responsible for inspecting and possibly publishing the newly created object, and possibly sending back an Accept or a Reject. We’ll refer to this actor as the target actor. Specific object types described throughout this specification have a specific meaning for the target actor, which processing and inspection it is expected to do, and where it is expected to list the URI of the object once it publishes it.

The sender is essentially asking that the target actor hosts the object as a child object and assigns is a URI, allowing to observe and interact with the object. The target actor will be responsible for hosting and controlling the object, and the sender will just be mentioned as the author.

When an actor A receives the Offer activity, they can determine whether they’re the target actor as follows: If the target is A or a child object of A, then A is the target actor. Otherwise, A isn’t the target actor.

In the following example, Luke wants to open a ticket under Aviva’s Game Of Life simulation app:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.example/luke/outbox/02Ljp",    "type": "Offer",    "actor": "https://forge.example/luke",    "to": [        "https://dev.example/aviva/game-of-life",        "https://dev.example/aviva/game-of-life/team",        "https://dev.example/aviva/game-of-life/followers"    ],    "object": {        "type": "Ticket",        "attributedTo": "https://forge.example/luke",        "summary": "Test test test",        "content": "<p>Just testing</p>",        "mediaType": "text/html",        "source": {            "mediaType": "text/markdown; variant=Commonmark",            "content": "Just testing"        }    },    "target": "https://dev.example/aviva/game-of-life"}

The target actor SHOULD send an Accept or a Reject activity to the Offer’s author in response. If the target actor sends an Accept, it MUST host its own copy, assigning an id to the newly published object and adding it to the expected list specified by the Offer's target.

If the target actor sends a Reject, it MUST NOT add the object’s id to that list. However if the target actor doesn’t make any use of the object, it MAY choose not to send a Reject, e.g. to protect user privacy. The Accept or Reject may also be delayed, e.g. until review by a human user; that is implementation dependent, and implementations should not rely on an instant response.

In the Accept activity:

In the following example, Luke’s ticket is opened automatically and Aviva’s Game Of Life repository, which is an actor, automatically sends Luke an Accept activity:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/game-of-life/outbox/096al",    "type": "Accept",    "actor": "https://dev.example/aviva/game-of-life",    "to": [        "https://forge.example/luke",        "https://dev.example/aviva/game-of-life/team",        "https://dev.example/aviva/game-of-life/followers"    ],    "object": "https://forge.example/luke/outbox/02Ljp",    "result": "https://dev.example/aviva/game-of-life/issues/113"}

3. Server to Server Interactions

This section describes the whole flow of actor interactions, that allows the federated implementation of the various features of forges and related software. It provides a complete picture of interaction flows, which the actor API section can’t, because it focuses on a single actor type at a time.

3.1. Reporting Pushed Commits

The ForgeFed Push activity can be used for representing an action of pushing commits into a Repository. Two actors are involved in the process, the pusher (usually a person) and the repository, and they may be hosted on different instances.

Push activities MUST be authored and published by the Repository, not by the actor that pushed. That actor is specified in the Push’s attributedTo property.

Upon a successful push, a ForgeFed implementation that publishes a Push activity MUST provide the type, actor, attributedTo and target properties as described in § 6.8 Push.

See example in § 6.8 Push.

3.2. Opening an issue

Minimal required role: report

The first step for opening a ticket is to determine to which actor to send the ticket. We’ll refer to this actor as the ticket tracker. Given an object obj against which you’d like to open a ticket (e.g. some application’s source code repository), look at the ticketsTrackedBy property of obj.

Now that we’ve determined the ticket tracker, i.e. the actor to whom we’ll send the Ticket, the ticket may be opened using an Offer activity in which:

The target actor MAY then send back an Accept or Reject. The action that has been taken by the target actor is indicated to the ticket author as follows:

In the following example, Luke wants to open a ticket under Aviva’s Game Of Life simulation app:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.example/luke/outbox/02Ljp",    "type": "Offer",    "actor": "https://forge.example/luke",    "to": [        "https://dev.example/aviva/game-of-life",        "https://dev.example/aviva/game-of-life/team",        "https://dev.example/aviva/game-of-life/followers"    ],    "object": {        "type": "Ticket",        "attributedTo": "https://forge.example/luke",        "summary": "Test test test",        "content": "<p>Just testing</p>",        "mediaType": "text/html",        "source": {            "mediaType": "text/markdown; variant=Commonmark",            "content": "Just testing"        }    },    "target": "https://dev.example/aviva/game-of-life"}

Luke’s ticket is opened automatically and Aviva’s Game Of Life repository, which is an actor, automatically sends Luke an Accept activity:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/game-of-life/outbox/096al",    "type": "Accept",    "actor": "https://dev.example/aviva/game-of-life",    "to": [        "https://forge.example/luke",        "https://dev.example/aviva/game-of-life/team",        "https://dev.example/aviva/game-of-life/followers"    ],    "object": "https://forge.example/luke/outbox/02Ljp",    "result": "https://dev.example/aviva/game-of-life/issues/113"}

3.3. Opening a merge request

Minimal required role: report

If actor A wishes to submit a Merge Request (MR)/Pull Request (PR)/patch against a Repository R, it may do so by following these steps:

  1. Look at R's sendPatchesTo property: That is the PatchTracker to which the MR needs to be submitted; let’s call it P

  2. Verify that P consents to handling MRs for repository R by verifying that R is listed in P's tracksPatchesFor property

  3. Publish and deliver, at least to P, an Offer activity in which:

Actor P MAY send back an Accept or Reject. The action that has been taken by P is indicated to actor A as follows:

In the following example, Luke wants to open a Merge Request against a Game Of Life simulation app:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.example/luke/outbox/uCSW6urN",    "type": "Offer",    "actor": "https://forge.example/luke",    "to": [        "https://dev.example/projects/game-of-life/pr-tracker"    ],    "cc": [        "https://dev.example/projects/game-of-life",        "https://dev.example/projects/game-of-life/followers",        "https://dev.example/projects/game-of-life/repo",        "https://dev.example/projects/game-of-life/repo/followers",        "https://dev.example/projects/game-of-life/pr-tracker/followers"    ],    "object": {        "type": "Ticket",        "attributedTo": "https://forge.example/luke",        "summary": "Fix the animation bug",        "content": "<p>Please review, thanks!</p>",        "mediaType": "text/html",        "source": {            "mediaType": "text/markdown; variant=Commonmark",            "content": "Please review, thanks!"        },        "attachment": {            "type": "Offer",            "origin": {                "type": "Branch",                "context": "https://forge.example/luke/game-of-life",                "ref": "refs/heads/fix-animation-bug"            },            "target": {                "type": "Branch",                "context": "https://dev.example/projects/game-of-life/repo",                "ref": "refs/heads/main"            },            "object": {                "type": "OrderedCollection",                "totalItems": 1,                "items": [                    {                        "type": "Patch",                        "attributedTo": "https://forge.example/luke",                        "mediaType": "application/x-git-patch",                        "content": "From c9ae5f4ff4a330b6e1196ceb7db1665bd4c1..."                    }                ]            }        }    },    "target": "https://dev.example/projects/game-of-life/pr-tracker"}

Luke’s MR is opened automatically and the PatchTracker sends Luke an Accept activity:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/projects/game-of-life/pr-tracker/outbox/qQfFKwJ8",    "type": "Accept",    "actor": "https://dev.example/projects/game-of-life/pr-tracker",    "to": [        "https://forge.example/luke"    ],    "cc": [        "https://dev.example/projects/game-of-life",        "https://dev.example/projects/game-of-life/followers",        "https://dev.example/projects/game-of-life/repo",        "https://dev.example/projects/game-of-life/repo/followers",        "https://dev.example/projects/game-of-life/pr-tracker/followers"    ],    "object": "https://forge.example/luke/outbox/uCSW6urN",    "result": "https://dev.example/projects/game-of-life/pr-tracker/pulls/1219"}

3.4. Commenting

Minimal required role: report

A comment on a ForgeFed resource object (such as tickets, merge requests) MUST be published as a Create activity, in which object is a Note with fields as described in § 6.1 Comment.

In the following example, Luke replies to Aviva’s comment under a merge request he submitted earlier against her Game Of Life simulation app repository:

{    "@context": "https://www.w3.org/ns/activitystreams",    "id": "https://forge.example/luke/outbox/rLaYo",    "type": "Create",    "actor": "https://forge.example/luke",    "to": [        "https://forge.example/luke/followers",        "https://dev.example/aviva/game-of-life",        "https://dev.example/aviva/game-of-life/followers",        "https://dev.example/aviva/game-of-life/team",        "https://dev.example/aviva/game-of-life/merge-requests/19/followers",        "https://dev.example/aviva/game-of-life/merge-requests/19/team"    ],    "object": {        "id": "https://forge.example/luke/comments/rD05r",        "type": "Note",        "attributedTo": "https://forge.example/luke",        "to": [            "https://forge.example/luke/followers",            "https://dev.example/aviva/game-of-life",            "https://dev.example/aviva/game-of-life/followers",            "https://dev.example/aviva/game-of-life/team",            "https://dev.example/aviva/game-of-life/merge-requests/19/followers",            "https://dev.example/aviva/game-of-life/merge-requests/19/team"        ],        "context": "https://dev.example/aviva/game-of-life/merge-requests/19",        "inReplyTo": "https://dev.example/aviva/comments/E9AGE",        "mediaType": "text/html",        "content": "<p>Thank you for the review! I'll submit a correction ASAP</p>",        "source": {            "mediaType": "text/markdown; variant=Commonmark",            "content": "Thank you for the review! I'll submit a correction ASAP"        },        "published": "2019-11-06T20:49:05.604488Z"    }}

3.5. Granting access to shared resources

Minimal required role: admin

An actor that wishes to give other specific actors access to view or modify it (or a child object of it), SHOULD do so according to the following instructions.

3.5.1. Object capabilities

3.5.1.1. Introduction

An Object Capability (or in short OCap or OCAP) is a token providing access to certain operations on a certain resource. An actor wishing to act on a resource provides the token to the resource, alongside the Activity they wish to perform. The resource verifies the token, and if and only if it finds the token valid, and access to the requested Activity is allowed by the token, then the resource allows the Activity to be performed.

The token provided by the actor to the resource, i.e. the OCAP, is the ID URI of a previously published Grant activity.

The fundamental steps for accessing shared resources using OCAPs are:

  1. The actor managing the resource (which may be the resource itself) sends a Grant activity to the actor to whom it wishes to grant access

  2. When the actor who received the access wishes to operate on the resource, it sends the activity to the actor managing the resource, along with the ID URI of the Grant sent in step 1

  3. The actor managing the resource verifies the access provided by the Grant whose ID URI is provided, and allows the activity to be performed only if the verification passes

Providing the Grant ID URI like that when requesting to interact with a resource is called an invocation of the Grant. There is another operation possible with a Grant though: An actor can delegate a Grant it has received, i.e. pass on the access, giving it to more actors. Delegation is covered in a later section; for now let’s assume Grants are used only for invocation. We therefore get the following simplified validation process.

When an actor R receives from actor A a request to access/modify a resource r, where the request is expressed as an activity a whose capability field specifies some other activity g, then R can validate a (i.e. decide whether or not to perform the requested action) using the following steps:

  1. Resource r MUST be a resource that R manages (it may be R itself)

  2. g's type MUST be Grant

  3. g's context MUST be r

  4. g's target MUST be A

  5. Verify that g's startTime <= now < g's endTime

  6. Verify that g doesn’t specify delegates

  7. g's actor MUST be R

  8. Verify that R indeed published g and considers it an active grant (i.e. R hasn’t disabled/revoked it)

  9. checkLeaf(g):

    1. g's allows MUST be invoke

    2. Actor A SHOULD be of a type to which R allows to perform activity a on resource r, i.e. A should probably be a Person, or some automated service/bot

  10. Verify that the action being requested by activity a to perform on resource r is within what R permits for the Role specified by g's object

At this point, activity a is considered authorized, and the requested action may be performed.

3.5.1.2. Direct Granting

When an actor R, managing some resource r, wishes to allow some other actor A to interact with r, under the conditions and permissions specified by Role p, then actor R can send to actor A a Grant activity with the following properties:

3.5.1.3. Granting the delegate role

A special case of direct granting is granting permission to delegate: If role p is delegate, then the Grant actor is allowing the target to delegate Grants to the actor, i.e. to send Grants meant for delegation or Grants that are themselves delegations of other Grants (either start a chain, or extend a chain that some other actor started). More on delegation in the next sections.

When an actor A wishes to allow some other actor R to delegate Grants to actor A, then actor A can send to actor R a Grant activity with the following properties:

3.5.1.4. Starting a delegation chain

When an actor R, managing some resource r, wishes to allow or request some other actor A to delegate some access-to-r-under-role-p to certain (or any) other actors that A knows, then actor R can send to actor A a Grant activity with the following properties:

The following cases are supported in ForgeFed for starting a delegation chain. The term 'component' used below refers to a forge related service actor. This may be a service of a type defined in ForgeFed (such as Repository, TicketTracker, PatchTracker), or a service defined in some extension.

  1. actor is a component, target is a Project

    • Scenario: A component delegates access-to-a-resource-it-manages (which is often simply itself) to a project to which the component belongs

    • allows value to use: gatherAndConvey

    • Conditions for the target project:

      • It SHOULD delegate the Grant, allowing only gatherAndConvey, to its own parent projects

      • It SHOULD delegate the Grant, allowing only distribute, to teams to which it allows to access it

      • It SHOULD delegate the Grant, allowing only invoke, to people and bots to which it allows to access it

      • It SHOULD NOT make any other delegation of this Grant, and SHOULD NOT invoke it

  2. actor is a Project, target is a parent Project of it

    • Scenario: A project delegates access-to-a-resource-itself to its parent project

    • allows value to use: Same as 1

    • Conditions for the target project: Same as 1

  3. actor is a component, target is a Team

    • Scenario: A component delegates access-to-a-resource-it-manages to a team that has been approved to access the component

    • allows value to use: distribute

    • Conditions for the target team:

      • It SHOULD delegate the Grant, allowing distribute only, to its subteams

      • It SHOULD delegate the Grant, allowing invoke only, to its members

      • It SHOULD NOT make any other delegation of this Grant, and SHOULD NOT invoke it

  4. actor is a Project, target is a Team

    • Scenario: A project delegates access-to-itself to a team that has been approved to access the project

    • allows value to use: Same as 3

    • Conditions for the target project: Same as 3

3.5.1.5. Extending a delegation chain

When an actor A receives a Grant activity g where the target is A, and wishes to pass on the granted access to some other actor B (who isn’t the actor of that Grant), then actor A can do so by sending to actor B a new Grant activity h in which:

The result URI MUST be provided whenever extending a delegation chain. It MUST be a URI that actor A controls, i.e. decides what will be returned by HTTP requests to that URI. Requirements:

result MAY instead specify a JSON object in which:

In the following cases, g is a request for actor A to extend the delegation chain, and actor A SHOULD extend the chain by sending Grant activities, as described for each case.

The term 'component' used below refers to a forge related service actor. This may be a service of a type defined in ForgeFed (such as Repository, TicketTracker, PatchTracker), or a service defined in some extension.

  1. Actor A is a Project, AND g's actor is either a component of A or a subproject of A, AND g's allows is a single value gatherAndConvey

    • Scenario: Project A received some access from a component/subproject of it, and is requested to pass it on its member people, to its member teams, and to its parent projects

    • Requirements for extending the delegation chain:

      1. For each parent project P of project A, project A SHOULD publish and deliver to P a Grant activity in which:

      2. For each team T that project A considers a member team with role p, project A SHOULD publish and deliver to T a Grant activity in which:

      3. For each Person or automated service bot M (that isn’t a team) that project A considers a member with role p, project A SHOULD publish and deliver to M a Grant activity in which:

      4. Project A MUST NOT make any other delegations of g, and SHOULD NOT try to invoke it

  2. Actor A is a Team, AND g's actor is either a component/Project in which A is a member or a parent team (see subteams) of A, AND g's allows is a single value distribute

    • Scenario: Team A received some access from a component/project that considers A a member team, or from a parent team of A, and A is requested to pass it on its member people and to its subteams

    • Requirements for extending the delegation chain:

      1. For each team T that team A considers a subteam, team A SHOULD publish and deliver to T a Grant activity in which:

      2. For each Person or automated service bot M (that isn’t a team) that team A considers a member with role p, team A SHOULD publish and deliver to M a Grant activity in which:

      3. Team A MUST NOT make any other delegations of g, and SHOULD NOT try to invoke it

3.5.1.6. Revoking a Grant

At any point after an actor A publishes a § 7.1.3 Grant in which it grants some actor B access to a resource that actor A manages, actor A MAY cancel that Grant, deciding it’s no longer a valid OCAP to use via the capability property of activies that actor B sends.

If actor A cancels such a Grant, it SHOULD publish and deliver, at least to actor B, a Revoke activity notifying about the canceled Grant. In the Revoke activity, actor A MUST specify the Grants being revoked, via the object property, where each Grant is specified in one of the following ways:

  1. The Grant is specified by its id URI

  2. The whole Grant activity object is provided, and MUST contain an integrity proof

Additional requirements:

Once actor A publishes the Revoke, it MUST from now on refuse to execute requests from actor B to access resources that actor A manages, coming as activities that specify any of the canceled Grants in the capability property. If actor A receives such an activity from actor B, it SHOULD publish and send back a Reject activity, whose object specifies the activity that actor B sent.

If the Grant that actor A is revoking specifies a result, then from now on any HTTP HEAD request to the URI specified by result MUST NOT return an HTTP response status in the 200-299 range. The returned status SHOULD be 410 or 404. See Extending a delegation chain for more information.

3.5.1.7. Verifying an invocation

A previous section described direct usage of Grants, where the resource actor gives some access to a target actor, and the target actor then uses it to interact with the resource. Another way to give authorization is via delegation chains:

Access is delegated using Grant activities as well, using the delegates property to point from each Grant in the chain to the previous one. The "direct" Grant discussed earlier is simply a delegation chain of length 1.

When an actor R receives from actor A a request to access/modify a resource r, where the request is expressed as an activity a whose capability field specifies some other activity g, then R can validate a (i.e. decide whether or not to perform the requested action) using the following steps.

R begins by verifying that resource r is indeed a resource that R manages (it may be R itself). Otherwise, verification has failed.

R proceeds by collecting the delegation chain in a list, by traversing the chain backwards from the leaf all the way to the beginning of the chain. The traversal starts with the list L being empty, and R examines activity g:

  1. g's type MUST be Grant

  2. g's context MUST be r

  3. g's target MUST be A

  4. g MUST NOT already be listed in L

  5. Verify that g's startTime <= now < g's endTime

  6. Look at g's delegates:

    • If g doesn’t specify delegates:

      1. g's actor MUST be R

      2. Verify that R indeed published g and considers it an active grant (i.e. R hasn’t disabled/revoked it)

      3. Prepend g to the beginning of L, resulting with new list M

      4. We’re done with the traversal step, the output is M

    • If g's delegates is some activity h:

      1. g's actor MUST NOT be R

      2. g MUST specify exactly one result URI

      3. Verify the result:

        • If it’s an object with duration specified, and this duration of time hasn’t yet passed since the last check, proceed without checking the URI

        • Otherwise, send an HTTP HEAD request to the URI, The HTTP response status MUST be 200 or 204

      4. Prepend g to the beginning of L, resulting with new list M

      5. Continue traversal by going back to step 1, but with M being the list, and with g's actor instead of A, and now examining activity h

"Going back to step 1" refers to the top-level list item; should probably tweak the CSS to display nested lists differently.

R proceeds by traversing the resulting list L from the beginning forward, all the way to the leaf, validating and tracking attenuation in each step. R starts this by examining the first item in L, let’s call this item g:

  1. Let p be g's object

  2. Examine g's position in L:

    • If g is the last item in L:

      1. Perform checkLeaf on g (see below)

      2. Verify that the action being requested by activity a to perform on resource r is within what R permits for Role p.

      3. We’re done with the traversal!

    • Otherwise:

      1. Let h be the next item after g in L

      2. Let q be h's object

      3. The permissions that role q allows on resource r MUST be identical to or a subset of the permissios that role p allows on r

      4. Perform checkItem on (g, h) (see below)

      5. Continue traversal by going back to step 2, but with h instead of g and q instead of p

"Step 2" refers to the top-level one, need to tweak CSS for lists

The steps checkLeaf and checkItem mentioned above MAY be extended by implementations, by using custom values in the allows property. But here are the standard definitions, using the values defined in ForgeFed:

checkLeaf (g):

  1. g's allows MUST be invoke

  2. g's target (which is actor A, the sender of activity a) SHOULD be an actor of a type to which R allows to perform activity a on resource r, i.e. A should probably be a Person, or some automated service/bot

checkItem (g, h):

  1. g MUST specify exactly one value for allows

  2. That value MUST be either gatherAndConvey or distribute

At this point, activity a is considered authorized, and the requested action may be performed.

3.5.1.8. Identifying resources and their managing actors

Some shared resources are themselves actors. Some shared resources aren’t actors, but they are child objects of actors. When some actor A wishes to access a resource R and perform a certain operation, it needs to determine which actor to contact in order to request that operation. Actor A then looks at resource R, and the following MUST hold:

Therefore any object that wishes to be specified as the context of a Grant MUST either be an actor or be managedBy an actor.

3.5.1.9. Invoking a Grant

Invoking a Grant means using the Grant to authorize a request to access or modify some resource. If some actor A wishes to access or modify a resource r, using a Grant activity g for authorization, preconditions for a successful invocation include:

When actor A sends the activity a that requests to access or modify resource r, it can use g for authorization by specifying its id URI in the capability property of activity a.

To have a chance to access resource r, actor A needs to deliver activity a to the actor that manages r. See above instructions for determining who that actor is.

3.5.1.10. Time Bounds

A Grant activity MUST be considered valid for invocation (or as a valid link in a delegation chain) if and only if the current time, at the time of invocation, is within the time bounds defined by the Grant:

  1. A Grant MAY specify a startTime: The time at which the Grant becomes valid. If specified, the Grant is valid only if the time of invocation is equal or greater than the startTime

  2. A Grant SHOULD specify an endTime: The time at which the Grant expires. If specified, the Grant is valid only if the time of invocation is less than the endTime

Suggested default for picking the endTime: 6 months after publishing the Grant.

3.5.2. Granting access

3.5.2.1. Initial Grant upon resource creation

When an actor A requests to create a new shared resource R, and the resource actor approves and creates it, then the resource actor SHOULD send a Grant to actor A, which provides actor A with access to resource R.

The Role specified by the Grant's object MUST be admin, which means full access to R, including the ability to gives access-to-R to more actors (using an Invite activity, see below).

If such a Grant is sent by the resource actor upon the creation of resource R, then the Grant’s fulfills property MUST be provided and specify the ID URI of the activity (published by actor A) that requested to create resource R (typically this would be a Create activity, see Object Publishing and Hosting).

If R is a Project or a Team, additional steps occur:

  1. A, seeing R's Grant, publishes a delegate-Grant in which the target is R and capability is R's Grant

  2. From now on, whenever R wishes to extend a Grant chain to A, it uses A's delegate-Grant as the capability

3.5.2.2. Offering access using Invite activities

When an actor A wishes to offer actor B access to resource R (where the resource actor who manages R is neither A nor B), then actor A SHOULD use an Invite activity, and the following steps:

  1. Actor A publishes and delivers an Invite, at least to actor B and to the resource actor of R, with a relevant capability (see § 7.1.1 Invite for details on the properties to use)

  2. If actor B wishes to have the offered access, it publishes and delivers (at least to the resource actor of R) an Accept activity whose object specifies the Invite sent by actor A

  3. The resource actor of R receives the Invite and the Accept and:

    1. Verifies the Invite is authorized, as described above in Verifying an invocation

    2. Verifies that the Accept’s object specifies the Invite and the Accept’s actor is the Invite’s object

    3. Publishes and delivers a Grant activity (see § 7.1.3 Grant for more details on the properties) where:

  4. B is now considered a collaborator in R!

  5. If R is a Project or a Team, additional steps occur:

    1. B, seeing R's Grant, publishes a delegate-Grant in which the target is R and capability is R's Grant

    2. From now on, whenever R wishes to extend a Grant chain to B, it uses B's delegate-Grant as the capability

Actor B can now use the URI of that new Grant as the capability when it sends activities that access or manipulate resource R.

3.5.2.3. Requesting access using Join activities

When an actor A wishes to request access to resource R (where the resource actor who manages R isn’t A), then actor A SHOULD use a Join activity, and the following steps. There are two options detailed below, depending on whether actor A has been previously given a Grant authorizing it to gain access to resource R without needing someone else to approve. For example, perhaps actor A already has some access to a resource collection to which R belongs, and that access allows A to freely Join R without needing to wait for human approval.

*Option 1: Actor *A already has a Grant allowing it to gain access to R without external approval:**

  1. Actor A publishes and delivers a Join, at least to the resource actor of R, with the relevant capability it has (see § 7.1.2 Join for details on the properties to use)

  2. The resource actor of R receives the Join and:

    1. Verifies the Join is authorized, as described above in Verifying an invocation

    2. Publishes and delivers a Grant activity (see § 7.1.3 Grant for more details on the properties) where:

  3. A is now considered a collaborator in R!

  4. If R is a Project or a Team, additional steps occur:

    1. A, seeing R's Grant, publishes a delegate-Grant in which the target is R and capability is R's Grant

    2. From now on, whenever R wishes to extend a Grant chain to A, it uses A's delegate-Grant as the capability

Actor A can now use the URI of that new Grant as the capability when it sends activities that access or manipulate resource R.

*Option 2: Actor *A doesn’t have (or chooses not to use) a Grant allowing it to gain access to R without external approval:**

  1. Actor A publishes and delivers a Join, at least to the resource actor of R (see § 7.1.2 Join for details on the properties to use)

  2. If some actor B, that has previously received a Grant from the resource actor of R authorizing it to approve joins, sees the Join sent by actor A and decides to approve it, then actor B publishes and delivers (at least to the resource actor of R) an Accept activity where:

    • object specifies the Join sent by actor A

    • capability is the Grant mentioned above, authorizing to approve or deny Joins

  3. The resource actor of R receives the Join and the Accept and:

    1. Verifies the Accept is authorized, as described above in Verifying an invocation

    2. Verifies that the Accept’s object specifies the Join

    3. Publishes and delivers a Grant activity (see § 7.1.3 Grant for more details on the properties) where:

  4. A is now considered a collaborator in R!

  5. If R is a Project or a Team, additional steps occur:

    1. A, seeing R's Grant, publishes a delegate-Grant in which the target is R and capability is R's Grant

    2. From now on, whenever R wishes to extend a Grant chain to A, it uses A's delegate-Grant as the capability

Actor A can now use the URI of that new Grant as the capability when it sends activities that access or manipulate resource R.

In step 2, actor B may choose to deny the request of actor A, by sending a Reject activity (at least to the resource actor of R) where:

If the resource actor of R receives the Reject:

  1. It MUST verify the Reject is authorized, as described above in Verifying an invocation

  2. it MUST verify that the Reject’s object specifies the Join

  3. Consider this Join request canceled: If actor B, or some other actor C, tries again to Accept the Join, then:

    1. The resource actor MUST NOT send a Grant to actor A, even if the Accept is authorized

    2. The resource actor MAY publish and deliver a Reject activity, at least to the actor that sent the Accept, where object specifies the Accept

  4. It SHOULD publish and deliver a Reject activity, at least to actor A, where object specifies the Join that actor A sent

So, once a Join is rejected (using an authorized Reject), it cannot be accepted. But actor A MAY send a new Join, which could then possibly get accepted.

3.5.3. Revoking access

3.5.3.1. Taking away access using Remove activities

When an actor A wishes to cancel the membership of another actor B (who isn’t A) in a shared resource R, invalidating any active § 7.1.3 Grants that the resource actor of R has granted to actor B, then actor A SHOULD use a Remove activity, and the following steps:

  1. Actor A publishes and delivers a Remove, at least to actor B and to the resource actor of R, with a relevant capability (see § 7.2.1 Remove for details on the properties to use)

  2. The resource actor of R receives the Remove and:

    1. Verifies the Remove is authorized, as described above in Verifying an invocation

    2. Verifies that actor B indeed has active Grants for accessing resource R

    3. Marks those Grants as disabled in its internal state

    4. Publishes and delivers a § 7.2.3 Revoke activity, as described above in Revoking a Grant, where fulfills specifies the Remove

Actor B SHOULD no longer use the URI of any Grant that has been disabled as the capability when it sends activities that access or manipulate resource R.

3.5.3.2. Waiving access using Leave activities

When an actor A wishes to cancel their membership in a shared resource R (where the resource actor who manages R isn’t A), invalidating any active § 7.1.3 Grants that the resource actor of R has granted to actor A, then actor A SHOULD use a Leave activity, and the following steps:

  1. Actor A publishes and delivers a Leave, at least to the resource actor of R (see § 7.2.2 Leave for details on the properties to use)

  2. The resource actor of R receives the Leave and:

    1. Verifies that actor A indeed has active Grants for accessing resource R

    2. Marks those Grants as disabled in its internal state

    3. Publishes and delivers a § 7.2.3 Revoke activity, as described above in Revoking a Grant, where fulfills specifies the Leave

Actor A SHOULD no longer use the URI of any Grant that has been disabled as the capability when it sends activities that access or manipulate resource R.

3.5.3.3. Requesting to disable specific Grants using Undo

When an actor A wishes to deactivate a specific § 7.1.3 Grant activity (or multiple Grants), providing access to view or manipulate some resource R (where the resource actor of R isn’t A), then actor A SHOULD use an Undo activity, and the following steps. The actor B to whom access-to-resource-R was given by the Grant may be actor A itself, or some other actor, as long as actor A is authorized by the resource actor of R to deactivate that Grant.

NOTE: Upon a successful Undo, if actor B doesn’t have any active Grants left, that allow access to resource R, then the resource actor of R MAY remove actor B's membership in R, or it MAY consider actor B a member without access.

  1. Actor A publishes and delivers an Undo, at least to the resource actor of R (see § 7.2.4 Undo a Grant for details on the properties to use)

  2. The resource actor of R receives the Undo and:

    1. Verifies the Undo is authorized, as described above in Verifying an invocation

    2. Verifies that actor B indeed has all the active Grants for accessing resource R, that are listed as objects of the Undo (if more than one Grant is listed, the target of all the Grants MUST be identical)

    3. Marks all of those Grants as disabled in its internal state

    4. Publishes and delivers a § 7.2.3 Revoke activity, at least to actors A and B, as described above in Revoking a Grant, where:

      • object MUST specify all the deactivated Grants

      • fulfills MUST specify the Undo

Actor B SHOULD no longer use the URI of any Grant that has been disabled as the capability when it sends activities that access or manipulate resource R.

3.5.4. Example

Aviva creates a new Repository for her 3D Tree Growth Simulation software:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.community/users/aviva/outbox/oU6QGAqr-create-treesim",    "type": "Create",    "actor": "https://forge.community/users/aviva",    "to": [        "https://forge.community/users/aviva/followers"    ],    "object": {        "id": "https://forge.community/repos/treesim",        "type": "Repository",        "name": "Tree Growth 3D Simulation",        "summary": "A graphical simulation of trees growing"    }}

The newly created treesim Repository automatically sends back a Grant to Aviva, allowing her full access to the repo:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.community/repos/treesim/outbox/2NwyPWMX-grant-admin-to-aviva",    "type": "Grant",    "actor": "https://forge.community/repos/treesim",    "to": [        "https://forge.community/aviva",        "https://forge.community/aviva/followers"    ],    "object": "admin",    "context": "https://forge.community/repos/treesim",    "target": "https://forge.community/aviva",    "fulfills": "https://forge.community/users/aviva/outbox/oU6QGAqr-create-treesim",    "allows": "invoke",    "endTime": "2023-12-31T23:00:00-08:00"}

Aviva can now use this Grant, e.g. to update the repo’s description text:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.community/users/aviva/outbox/RmTygyuj",    "type": "Update",    "actor": "https://forge.community/users/aviva",    "to": [        "https://forge.community/users/aviva/followers",        "https://forge.community/repos/treesim",        "https://forge.community/repos/treesim/followers"    ],    "object": {        "id": "https://forge.community/repos/treesim",        "type": "Repository",        "name": "Tree Growth 3D Simulation",        "summary": "Tree growth 3D simulator for my nature exploration game"    },    "capability": "https://forge.community/repos/treesim/outbox/2NwyPWMX-grant-admin-to-aviva"}

Aviva wants to keep track of events related to the treesim repo:

{    "@context": "https://www.w3.org/ns/activitystreams",    "id": "https://forge.community/users/aviva/outbox/gqtpAhm2",    "type": "Follow",    "actor": "https://forge.community/users/aviva",    "to": "https://forge.community/repos/treesim",    "object": "https://forge.community/repos/treesim",}

Aviva can invite Luke to have access to the treesim repo:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.community/users/aviva/outbox/qfrEGqnC-invite-luke",    "type": "Invite",    "actor": "https://forge.community/users/aviva",    "to": [        "https://forge.community/aviva/followers",        "https://forge.community/repos/treesim",        "https://forge.community/repos/treesim/followers",        "https://software.site/people/luke",        "https://software.site/people/luke/followers"    ],    "instrument": "maintain",    "target": "https://forge.community/repos/treesim",    "object": "https://software.site/people/luke",    "capability": "https://forge.community/repos/treesim/outbox/2NwyPWMX-grant-admin-to-aviva"}

And it appears that Luke accepts the invitation:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://software.site/people/luke/activities/mEYYmt8u",    "type": "Accept",    "actor": "https://software.site/people/luke",    "to": [        "https://forge.community/aviva",        "https://forge.community/aviva/followers",        "https://forge.community/repos/treesim",        "https://forge.community/repos/treesim/followers",        "https://software.site/people/luke/followers"    ],    "object": "https://forge.community/users/aviva/outbox/qfrEGqnC-invite-luke"}

Seeing the Invite and the Accept, the treesim repo sends Luke a Grant giving him the access that Aviva offered, and which he accepted:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.community/repos/treesim/outbox/D5uod3pz-grant-maintainer-to-luke",    "type": "Grant",    "actor": "https://forge.community/repos/treesim",    "to": [        "https://forge.community/aviva",        "https://forge.community/aviva/followers",        "https://forge.community/repos/treesim/followers",        "https://software.site/people/luke",        "https://software.site/people/luke/followers"    ],    "object": "maintain",    "context": "https://forge.community/repos/treesim",    "target": "https://software.site/people/luke",    "fulfills": "https://forge.community/users/aviva/outbox/qfrEGqnC-invite-luke",    "allows": "invoke",    "endTime": "2023-12-31T23:00:00-08:00"}

Luke can now use this Grant, e.g. to delete some old obsolete branch of the treesim repo:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://software.site/people/luke/activities/vShj2aIe",    "type": "Delete",    "actor": "https://software.site/people/luke",    "to": [        "https://forge.community/repos/treesim",        "https://forge.community/repos/treesim/followers",        "https://software.site/people/luke/followers"    ],    "object": "https://forge.community/repos/treesim/branches/fixes-for-release-0.1.3",    "origin": "https://forge.community/repos/treesim",    "capability": "https://forge.community/repos/treesim/outbox/D5uod3pz-grant-maintainer-to-luke"}

Celine requests to have developer access to the treesim repo:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.online/@celine/sent/v5Qvd6bB-celine-join",    "type": "Join",    "actor": "https://dev.online/@celine",    "to": [        "https://forge.community/repos/treesim",        "https://forge.community/repos/treesim/followers",        "https://dev.online/@celine/followers"    ],    "object": "https://forge.community/repos/treesim",    "instrument": "write"}

Aviva sees the Join request, talks with Celine and decides to approve her request:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.community/users/aviva/outbox/PzRtDydu",    "type": "Accept",    "actor": "https://forge.community/users/aviva",    "to": [        "https://forge.community/repos/treesim",        "https://forge.community/repos/treesim/followers",        "https://dev.online/@celine",        "https://dev.online/@celine/followers"    ],    "object": "https://dev.online/@celine/sent/v5Qvd6bB-celine-join",    "capability": "https://forge.community/repos/treesim/outbox/2NwyPWMX-grant-admin-to-aviva"}

Seeing the Join and the Accept, the treesim repo sends Celine a Grant giving her the access that she requested, and which Aviva approved:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://forge.community/repos/treesim/outbox/D5uod3pz-grant-developer-to-celine",    "type": "Grant",    "actor": "https://forge.community/repos/treesim",    "to": [        "https://forge.community/aviva",        "https://forge.community/repos/treesim/followers",        "https://dev.online/@celine",        "https://dev.online/@celine/followers"    ],    "object": "write",    "context": "https://forge.community/repos/treesim",    "target": "https://dev.online/@celine",    "fulfills": "https://dev.online/@celine/sent/v5Qvd6bB-celine-join",    "allows": "invoke",    "endTime": "2023-12-31T23:00:00-08:00"}

Celine can now use this Grant to access the treesim repo.

3.6. Associating Projects and Components

Minimal required role: admin

Adding and removing a component to/from a project each have 2 versions: The "component side" version allows an actor to initiate the action using admin access to the component, while the "project side" version allows to initiate the action using admin access to the project.

Whenever authorization is mentioned below, it SHOULD be done using the invocation verification process.

3.6.1. Adding a component to a project - component side

Assuming:

Alice wants to add component C to project P. The exchange of activities SHOULD be as follows:

  1. Alice sends an Add activity in which:

    • object is C

    • target is the URI of P's components collection

    • instrument is the maximal Role that Alice would like to allow for people (and bots) when authorizing their manipulation of C by their access in P (normally it would be admin, perhaps sometimes maintain)

    • capability is the URI of a Grant that authorizes admin access to C

  2. C, seeing and authorizing the Add, publishes an Accept where object is the Add’s URI

  3. Bob, seeing the Add and the Accept, sends an Accept where:

  4. P, seeing the previous 3 activities and authorizing Bob’s Accept, publishes a delegate-Grant in which the target is C

  5. C, seeing P's Grant, sends a start-Grant where:

  6. P, seeing and authorizing C's Grant, now extends the Grant chain as relevant, to its members and parent projects

3.6.2. Removing a component from a project - component side

Assuming:

Alice wants to ask component C to remove itself from project P. The exchange of activities SHOULD be as follows:

  1. Alice sends a Remove activity where:

  2. C, seeing and authorizing the Remove, publishes a Revoke where object specifies the active start-Grant it had sent to P (and C now considers that Grant revoked and no longer considers itself as a component of P)

  3. P, seeing the Remove and the Revoke, publishes a Revoke where object specifies the active delegate-Grant it had sent to C (and P now considers that Grant revoked and no longer considers C a component of it)

3.6.3. Adding a component to a project - project side

Assuming:

Bob wants to add component C to project P. The exchange of activities SHOULD be as follows:

  1. Bob sends an Invite activity in which:

    • object is C

    • target is the URI of P's components collection

    • instrument is the maximal Role that Bob would like to allow for people (and bots) when authorizing their manipulation of C by their access in P (normally it would be admin, perhaps sometimes maintain)

    • capability is the URI of a Grant that authorizes admin access to P

  2. P, seeing and authorizing the Invite, publishes an Accept where object is the Invite’s URI

  3. Alice, seeing the Invite and the Accept, sends an Accept where:

  4. C, seeing the previous 3 activities and authorizing Alice’s Accept, sends an Accept where object is the Invite’s URI

  5. P, seeing C's Accept, publishes a delegate-Grant in which the target is C

  6. C, seeing P's Grant, sends a start-Grant where:

  7. P, seeing and authorizing C's Grant, now extends the Grant chain as relevant, to its members and parent projects

3.6.4. Removing a component from a project - project side

Assuming:

Bob wants to ask project P to remove component C from it. The exchange of activities SHOULD be as follows:

  1. Bob sends a Remove activity where:

  2. P, seeing and authorizing the Remove, publishes a Revoke where object specifies the active delegate-Grant it had sent to C (and P now considers that Grant revoked and no longer considers C a component of it)

  3. C, seeing the Remove and the Revoke, publishes a Revoke where object specifies the active start-Grant it had sent to P (and C now considers that Grant revoked and no longer considers itself as a component of P)

4. Actor Interface

This section will provide, for each actor type, a summary of the various (1) "methods" i.e. activities it can receive as requests for action; (2) "events", i.e. activities it sends out; (3) perhaps representation of resources that are specific to the actor type. Right now there’s a grey zone between methods and events, because some activities are methods for the target actor but events for any other recipient, and some events are actually sent by other actors, which cc the target actor’s followers and the target actor delivers via inbox-forwarding and not by Announceing (or custom Forwarding) those activities.

4.1. Person

4.2. Team

4.3. Project

4.4. Repository

4.5. TicketTracker

4.6. PatchTracker

5. Client to Server Interactions

This section is about how a human or bot can interact with the system by POSTing activities into the outbox of a Person (or Application/Service?) actor, and managing notifications. It’s less urgent than Server-to-Server. fr33 is using C2S in Vervis, and will be gradually working on this part.

ForgeFed uses Activities for client to server interactions, as described by ActivityPub. A client will send objects (eg. a Ticket) wrapped in a Activity (eg. Create) to an actor’s outbox, and in turn the server will take care of delivery.

5.1. Follow Activity

The Follow activity is used to subscribe to the activities of a Repository. The client MUST send a Follow activity to the Person’s outbox. The server in turn delivers the message to the destination inbox.

5.2. Push Activity

The Push activity is used to notify followers when somebody has pushed changes to a Repository. The client MUST send a Push activity to the Repository’s outbox. The server in turn delivers the message to the Repository followers.

6. Actor and Resource Representation

This section is about representation of objects. It’s possible that actor representation will move into a separate section, as well as objects specific to one actor type. And then this section will describe only objects/resources used by multiple actor types, such as comments (person, issue tracker, PR tracker) and tickets (used for both issues and PRs).

6.1. Comment

To represent a comment, e.g. a comment on a ticket or a merge request, use the ActivityPub Note type.

Properties:

type Note
attributedTo The author of the comment
context The topic of the discussion, e.g. a ticket or a merge request. It MUST be provided.
inReplyTo The entity on which this comment replies. MUST be provided. If the comment is made directly on the discussion topic, then inReplyTo MUST be identical to context. Otherwise, set inReplyTo to the comment to which this comment replies. In that case both comments MUST have an identical context.
content, mediaType, source The comment text, in rendered form and in source form
{    "@context": "https://www.w3.org/ns/activitystreams",    "id": "https://forge.example/luke/comments/rD05r",    "type": "Note",    "attributedTo": "https://forge.example/luke",    "context": "https://dev.example/aviva/game-of-life/merge-requests/19",    "inReplyTo": "https://dev.example/aviva/comments/E9AGE",    "mediaType": "text/html",    "content": "<p>Thank you for the review! I'll submit a correction ASAP</p>",    "source": {        "mediaType": "text/markdown; variant=Commonmark",        "content": "Thank you for the review! I'll submit a correction ASAP"    },    "published": "2019-11-06T20:49:05.604488Z"}

6.2. Team Membership

Properties:

type Relationship
subject A Team
relationship hasMember
object A Person who is a member of the Team
tag The role that the member has in the team
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/teams/mobilizon-dev-team/members/ThmsicTj",    "type": "Relationship",    "subject": "https://dev.example/teams/mobilizon-dev-team",    "relationship": "hasMember",    "object": "https://dev.example/people/celine",    "tag": "develop"}

6.3. Team

Properties:

type Team
name The user-given name of the team, e.g. "Gitea Development Team"
published The time the team was created on the server
summary A one-line user provided description of the project, as HTML, e.g. "We are creating a code hosting platform"
members Collection of the members of this team
subteams Subteams of this team, i.e. teams whose members (and subteams) inherit the access that this team has been granted (to projects, repositories, etc.)
context Parent Teams of this team, i.e. teams from which this team inherits access to projects, components and resources, e.g. repositories, ticket trackers (and passes them to its members and inherits them to its own subteams)
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://w3id.org/security/v2",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/teams/mobilizon-dev-team",    "type": "Team",    "name": "Mobilizon Development Team",    "summary": "We're creating a federated tool for organizing events!",    "members": {        "type": "Collection",        "totalItems": 3,        "items": [            { "type": "Relationship",              "subject": "https://dev.example/teams/mobilizon-dev-team",              "relationship": "hasMember",              "object": "https://dev.example/people/alice",              "tag": "admin"            },            { "type": "Relationship",              "subject": "https://dev.example/teams/mobilizon-dev-team",              "relationship": "hasMember",              "object": "https://dev.example/people/bob",              "tag": "maintain"            },            { "type": "Relationship",              "subject": "https://dev.example/teams/mobilizon-dev-team",              "relationship": "hasMember",              "object": "https://dev.example/people/celine",              "tag": "develop"            }        ]    },    "subteams": {        "type": "Collection",        "totalItems": 2,        "items": [            "https://dev.example/teams/mobilizon-backend-team",            "https://dev.example/teams/mobilizon-frontend-team"        ]    },    "context": "https://dev.example/teams/framasoft-developers",    "publicKey": {        "id": "https://dev.example/teams/mobilizon-dev-team#main-key",        "owner": "https://dev.example/teams/mobilizon-dev-team",        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki....."    },    "inbox": "https://dev.example/teams/mobilizon-dev-team/inbox",    "outbox": "https://dev.example/teams/mobilizon-dev-team/outbox",    "followers": "https://dev.example/teams/mobilizon-dev-team/followers"}

6.4. Project

Properties:

type Project
name The user-given name of the project, e.g. "My cool project"
published The time the project was created on the server
summary A one-line user provided description of the project, as HTML, e.g. "<p>A command-line tool that does cool things</p>"
ticketsTrackedBy The default ticket tracker to use when submitting a ticket to this project (this tracker MUST be listed under the project’s components)
subprojects A Collection of the subprojects of this project
context The parent Project(s) to which this project belongs
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/projects/wanderer",    "type": "Project",    "name": "Wanderer",    "summary": "3D nature exploration game",    "components": {        "type": "Collection",        "totalItems": 7,        "items": [            "https://dev.example/repos/opengl-vegetation",            "https://dev.example/repos/opengl-vegetation/patch-tracker",            "https://dev.example/repos/treesim",            "https://dev.example/repos/treesim/patch-tracker",            "https://dev.example/repos/wanderer",            "https://dev.example/repos/wanderer/patch-tracker",            "https://dev.example/issue-trackers/wanderer"        ]    },    "subprojects": {        "type": "Collection",        "totalItems": 2,        "items": [            "https://dev.example/projects/nature-3d-models",            "https://dev.example/projects/wanderer-fundraising"        ]    },    "ticketsTrackedBy": "https://dev.example/issue-trackers/wanderer",    "inbox": "https://dev.example/projects/wanderer/inbox",    "outbox": "https://dev.example/projects/wanderer/outbox",    "followers": "https://dev.example/projects/wanderer/followers"}

6.5. Commit

To represent a named set of changes committed into a repository’s history, use the ForgeFed Commit type. Such a committed change set is called e.g. a commit in Git, and a patch in Darcs.

Properties:

type Commit
context The Repository that this commit belongs to
attributedTo The commit author; if their actor URI is unknown, it MAY be their email address as a mailto URI
created A value of type dateTime (i.e. an ISO 8601 datetime value) specifying the time at which the commit was written by its author
committedBy The entity that committed the commit’s changes into their local copy of the repo, before the commit was pushed; if their actor URI is unknown, it MAY be their email address as a mailto URI
committed The time the commit was committed by its committer
hash The hash identifying the commit, e.g. the commit SHA1 hash in Git; the patch info SHA1 hash in Darcs
summary The commit’s one-line title as HTML-escaped plain text; if the commit title and description are a single commit message string, then the title is the 1st line of the commit message
description A JSON object with a mediaType field and a content field, where mediaType SHOULD be "text/plain" and content is the commit’s possibly-multi-line description; if the commit title and description are a single commit message string, then the description is everything after the 1st line of the commit message (possibly with leading whitespace stripped)
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/alice/myrepo/commits/109ec9a09c7df7fec775d2ba0b9d466e5643ec8c",    "type": "Commit",    "context": "https://example.dev/alice/myrepo",    "attributedTo": "https://example.dev/bob",    "created": "2019-07-11T12:34:56Z",    "committedBy": "https://example.dev/alice",    "committed": "2019-07-26T23:45:01Z",    "hash": "109ec9a09c7df7fec775d2ba0b9d466e5643ec8c",    "summary": "Add an installation script, fixes issue #89",    "description": {        "mediaType": "text/plain",        "content": "It's about time people can install it on their computers!"    }}

6.6. Branch

To represent a repository branch, use the ForgeFed Branch type. It can be a real built-in version control system branch (such as a Git branch) or a copy of the repo used as a branch (e.g. in Darcs, which doesn’t implement branches, and the way to have branches is to keep multiple versions of the repo).

Properties:

type Branch
context The Repository that this branch belongs to
name The user given name of the branch, e.g. "main"
ref The unique identifier of the branch within the repo, e.g. "refs/heads/main"
team If the branch has its own access/authority/visibility settings, this can be a Collection of the actors who have push/edit access to the branch
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/luke/myrepo/branches/master",    "type": "Branch",    "context": "https://example.dev/luke/myrepo",    "name": "master",    "ref": "refs/heads/master"}

6.7. Repository

To represent a version control repository, use the ForgeFed Repository type.

Properties:

type Repository
name The user given name of the repository, e.g. "My cool repo"
cloneUri The endpoint from which the content of the repository can be obtained via the native protocol (Git, Hg, etc.)
attributedTo The actor(s) in charge of the repository, e.g. a person or an organization; if their actor URI is unknown, it MAY be their email address as a mailto URI
published The time the repository was created on the server
summary A one-line user provided description of the repository, as HTML, e.g. "<p>A command-line tool that does cool things</p>"
team Collection of actors who have management/push access to the repository, or the subset of them who is available and wants to be contacted/notified/responsible on repo access related activities/requests
forks OrderedCollection of repositories that are forks of this repository
ticketsTrackedBy The ticket tracker that tracks tickets for this repository, this can be the repository itself if it manages its own tickets
sendPatchesTo The actor that tracks patches for this repository, this can be the repository itself if it manages its own patches and merge requests. For example it may be some external tracker or service, or the user or team to whom the repository belongs.
context The Project(s) to which this repository belongs
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://w3id.org/security/v1",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/treesim",    "cloneUri": "https://dev.example/aviva/treesim.git",    "type": "Repository",    "publicKey": {        "id": "https://dev.example/aviva/treesim#main-key",        "owner": "https://dev.example/aviva/treesim",        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki....."    },    "inbox": "https://dev.example/aviva/treesim/inbox",    "outbox": "https://dev.example/aviva/treesim/outbox",    "followers": "https://dev.example/aviva/treesim/followers",    "team": "https://dev.example/aviva/treesim/team",    "ticketsTrackedBy": "https://dev.example/aviva/treesim",    "sendPatchesTo": "https://dev.example/aviva/treesim",    "name": "Tree Growth 3D Simulation",    "attributedTo": "https://example.dev/bob",    "summary": "<p>Tree growth 3D simulator for my nature exploration game</p>"}

6.8. Push

To represent an event of Commits being pushed to a Repository, use a ForgeFed Push activity.

Properties:

type Push
actor The Repository to which the push was made, and that is publishing this Push activity
attributedTo The entity (person, bot, etc.) that pushed the commits
target The specific repo history tip onto which the commits were added, this is either a Branch (for VCSs that have branches) or a Repository (for VCSs that don’t have branches, only a single history line). If it’s a branch, it MUST be a branch belonging to the repository specified by actor. And if it’s a repository, it MUST be identical to the one specified by actor.
hashBefore Repo/branch/tip hash before adding the new commits
hashAfter Repo/branch/tip hash after adding the new commits
object An OrderedCollection of the Commits being pushed, in reverse chronological order. The items (or orderedItems) property of the collection MUST contain either the whole list of commits being pushed, or a prefix i.e. continuous subset from the beginning of the list (therefore the latest commits). earlyItems MAY be used for listing a suffix i.e. continuous subset from the end (therefore the earliest commits).
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/outbox/E26bE",    "type": "Push",    "actor": "https://dev.example/aviva",    "to": [        "https://dev.example/aviva/followers",        "https://dev.example/aviva/game-of-life",        "https://dev.example/aviva/game-of-life/team",        "https://dev.example/aviva/game-of-life/followers"    ],    "context": "https://dev.example/aviva/game-of-life",    "target": "https://dev.example/aviva/game-of-life/branches/master",    "hashBefore": "017cbb00bc20d1cae85f46d638684898d095f0ae",    "hashAfter": "be9f48a341c4bb5cd79ae7ab85fbf0c05d2837bb",    "object": {        "totalItems": 2,        "type": "OrderedCollection",        "orderedItems": [            {                "id": "https://dev.example/aviva/game-of-life/commits/be9f48a341c4bb5cd79ae7ab85fbf0c05d2837bb",                "type": "Commit",                "attributedTo": "https://dev.example/aviva",                "context": "https://dev.example/aviva/game-of-life",                "hash": "be9f48a341c4bb5cd79ae7ab85fbf0c05d2837bb",                "created": "2019-12-02T16:07:32Z",                "summary": "Add widget to alter simulation speed"            },            {                "id": "https://dev.example/aviva/game-of-life/commits/fa37fe100a8b1e69933889c5bf3caf95cd3ae1e6",                "type": "Commit",                "attributedTo": "https://dev.example/aviva",                "context": "https://dev.example/aviva/game-of-life",                "hash": "fa37fe100a8b1e69933889c5bf3caf95cd3ae1e6",                "created": "2019-12-02T15:51:52Z",                "summary": "Set window title correctly, fixes issue #7"            }        ]    }}

6.9. Ticket

To represent a work item in a project, use the ForgeFed Ticket type.

TODO decide on ticket categories/subtypes and update below

TODO decide on property for titles, update below

TODO properly document history or remove it from example

Properties:

type Ticket
context The TicketTracker or PatchTracker to which this ticket belongs
attributedTo The actor (person, bot, etc.) who submitted the ticket
summary The ticket’s one-line title, as HTML-escaped plain text
content, mediaType The ticket’s (possibly multi-line) detailed description text, in rendered form
source Source form of the ticket’s description
published The time the ticket submission was accepted (which may not be the same as the time the ticket was submitted)
followers Collection of the followers of the ticket, actors who want to be notified on activity related to the ticket
team Collection of project team members who have responsibility for work on this ticket and want to be notified on activities related to it
replies Collection of direct comments made on the ticket (but not comments made on other comments on the ticket)
dependants Collection of Tickets which depend on this ticket
dependencies Collection of Tickets on which this ticket depends
isResolved Whether the work on this ticket is done
resolvedBy If the work on this ticket is done, who marked the ticket as resolved, or which activity did so
resolved When the ticket has been marked as resolved

There’s an important distinction between these two kinds of tickets:

6.9.1. Task / Issue

A task is represented as a § 6.9 Ticket as described above, with the following additional requirements:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/game-of-life/issues/107",    "type": "Ticket",    "context": "https://dev.example/aviva/game-of-life",    "attributedTo": "https://forge.example/luke",    "summary": "Window title is empty",    "content": "<p>When I start the simulation, window title disappears suddenly</p>",    "mediaType": "text/html",    "source": {        "mediaType": "text/markdown; variant=Commonmark",        "content": "When I start the simulation, window title disappears suddenly",    },    "published": "2019-11-04T07:00:04.465807Z",    "followers": "https://dev.example/aviva/game-of-life/issues/107/followers",    "team": "https://dev.example/aviva/game-of-life/issues/107/team",    "replies": "https://dev.example/aviva/game-of-life/issues/107/discussion",    "history": "https://dev.example/aviva/game-of-life/issues/107/activity",    "dependants": "https://dev.example/aviva/game-of-life/issues/107/rdeps",    "dependencies": "https://dev.example/aviva/game-of-life/issues/107/deps",    "isResolved": true,    "resolvedBy": "https://code.example/martin",    "resolved": "2020-02-07T06:45:03.281314Z"}

6.9.2. Merge Request / Pull Request

A merge request is represented as a § 6.9 Ticket as described above, with the following additional requirements:

In that special attachment of type Offer:

{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/game-of-life/pulls/825",    "type": "Ticket",    "context": "https://dev.example/aviva/game-of-life",    "attributedTo": "https://forge.example/luke",    "summary": "Fix the empty window title bug",    "content": "<p>This fixes the bug making the title disappear</p>",    "mediaType": "text/html",    "source": {        "mediaType": "text/markdown; variant=Commonmark",        "content": "This fixes the bug making the title disappear",    },    "published": "2022-09-15T14:52:00.125987Z",    "followers": "https://dev.example/aviva/game-of-life/pulls/825/followers",    "replies": "https://dev.example/aviva/game-of-life/pulls/825/discussion",    "isResolved": false,    "attachment": {        "type": "Offer",        "origin": {            "type": "Branch",            "context": "https://forge.example/luke/game-of-life",            "ref": "refs/heads/fix-title-bug"        },        "target": {            "type": "Branch",            "context": "https://dev.example/aviva/game-of-life",            "ref": "refs/heads/main"        },        "object": {            "id": "https://dev.example/aviva/game-of-life/pulls/825/versions/1",            "type": "OrderedCollection",            "totalItems": 1,            "items": [                {                    "type": "Patch",                    "attributedTo": "https://forge.example/luke",                    "context": "https://dev.example/aviva/game-of-life/pulls/825/versions/1",                    "mediaType": "application/x-git-patch",                    "content": "From c9ae5f4ff4a330b6e1196ceb7db1665bd4c1..."                }            ],            "context": "https://dev.example/aviva/game-of-life/pulls/825"        }    }}

6.10. Patch

type Patch
attributedTo The Person who has written the patch
context An OrderedCollection representing a sequence of patches, being submitted together as a proposed change to a certain Repository, and this patch is an item in that collection
content A description of the changes that this patch proposes, encoded in the format specified by mediaType
mediaType A native patch format used by the Version Control System of the Repository to which the patch is proposed, and in which the content of this patch is encoded
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/game-of-life/pulls/825/versions/1/patches/1",    "type": "Patch",    "attributedTo": "https://forge.example/luke",    "context": "https://dev.example/aviva/game-of-life/pulls/825/versions/1",    "mediaType": "application/x-git-patch",    "content": "From c9ae5f4ff4a330b6e1196ceb7db1665bd4c1..."}

7. Access Control

7.1. Giving Access

7.1.1. Invite

To offer some actor access to a shared resource (such as a repository or a ticket tracker), use an ActivityPub Invite activity.

Properties:

type Invite
actor The entity (person, bot, etc.) that is offering access
instrument A Role specifying which operations on the resource are being allowed
target The resource, access to which is being given (for example, a repository)
object The actor who is being gives access to the resource
capability A previously published Grant, giving the actor permission to invite more actors to access the resource
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/outbox/B47d3",    "type": "Invite",    "actor": "https://dev.example/aviva",    "to": [        "https://dev.example/aviva/followers",        "https://coding.community/repos/game-of-life",        "https://coding.community/repos/game-of-life/followers",        "https://software.site/bob",        "https://software.site/bob/followers"    ],    "instrument": "maintain",    "target": "https://coding.community/repos/game-of-life",    "object": "https://software.site/bob",    "capability": "https://coding.community/repos/game-of-life/outbox/2c53A"}

7.1.2. Join

To request access to a shared resource, use an ActivityPub Join activity.

Properties:

type Join
actor The entity (person, bot, etc.) that is requesting access
instrument A Role specifying which operations on the resource are being requested
object The resource, access to which is being given (for example, a repository)
capability (optional) A previously published Grant, giving the actor permission to gain access to the resource without the approval of another actor. If capability isn’t provided, the resource won’t grant access before someone with adequate access approves the Join request.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://software.site/bob/outbox/c97E3",    "type": "Join",    "actor": "https://software.site/bob",    "to": [        "https://coding.community/repos/game-of-life",        "https://coding.community/repos/game-of-life/followers",        "https://software.site/bob/followers"    ],    "instrument": "maintain",    "object": "https://coding.community/repos/game-of-life",    "capability": "https://coding.community/repos/game-of-life/outbox/d38Fa"}

7.1.3. Grant

To give some actor access to a shared resource, use a ForgeFed Grant activity.

Properties:

type Grant
actor The entity (person, bot, etc.) that is giving access
object A Role specifying which operations on the resource are being allowed
context The resource, access to which is being given (for example, a repository)
target The actor who is being gives access to the resource
fulfills The activity that triggered the sending of the Grant, such as a related Invite (another example: if Alice Creates a new repository, the repository may automatically send back a Grant giving Alice admin access, and this Grant’s fulfills refers to the Create that Alice sent)
result A URI that can be used later for verifying that the given access is still approved, thus allowing the actor granting the access to revoke it. Alternatively, a JSON object where id is the URI and duration MAY be specified to allow to skip the revocation check if the duration time hasn’t yet passed since the last check. If duration is specified, it MUST be positive, and specify only an integral number of seconds that is less than 2^63, and no other component.
allows Modes of invocation and/or delegation that this Grant is meant to be used for
delegates If this Grant is a delegation, i.e. it is passing on some access that it has received, delegates specifies the parent Grant that it has received and now passing on
startTime (optional) The time at which the Grant becomes valid
endTime (recommended) The time at which the Grant expires
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://coding.community/repos/game-of-life/outbox/9fA8c",    "type": "Grant",    "actor": "https://coding.community/repos/game-of-life",    "to": [        "https://dev.example/aviva",        "https://dev.example/aviva/followers",        "https://coding.community/repos/game-of-life/followers",        "https://software.site/bob",        "https://software.site/bob/followers"    ],    "object": "maintain",    "context": "https://coding.community/repos/game-of-life",    "target": "https://software.site/bob",    "fulfills": "https://dev.example/aviva/outbox/B47d3",    "allows": "invoke",    "endTime": "2023-12-31T23:00:00-08:00"}

7.2. Canceling Access

7.2.1. Remove

To disable an actor’s membership in a shared resource, invalidating their access to it, use an ActivityPub Remove activity.

Properties:

type Remove
actor The actor (person, bot, etc.) that is disabling access disabled
object The actor whose access to the resource is being taken away
origin The resource, access to which is being taken away (for example, a repository)
capability A previously published Grant, giving the actor permission to disable the object actor’s access to the resource
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/outbox/F941b",    "type": "Remove",    "actor": "https://dev.example/aviva",    "to": [        "https://dev.example/aviva/followers",        "https://coding.community/repos/game-of-life",        "https://coding.community/repos/game-of-life/followers",        "https://software.site/bob",        "https://software.site/bob/followers"    ],    "origin": "https://coding.community/repos/game-of-life",    "object": "https://software.site/bob",    "capability": "https://coding.community/repos/game-of-life/outbox/2c53A"}

7.2.2. Leave

To withdraw your consent for membership in a shared resource, invalidating your access to it, use an ActivityPub Leave activity.

Properties:

type Leave
actor The actor (person, bot, etc.) that is requesting to disable their own access
object The resource, access to which is being disabled (for example, a repository)
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://software.site/bob/outbox/d08F4",    "type": "Leave",    "actor": "https://software.site/bob",    "to": [        "https://coding.community/repos/game-of-life",        "https://coding.community/repos/game-of-life/followers",        "https://software.site/bob/followers"    ],    "object": "https://coding.community/repos/game-of-life"}

7.2.3. Revoke

Another activity that can be used for disabling access is Revoke. While § 7.2.1 Remove and § 7.2.2 Leave are meant for undoing the effects of § 7.1.1 Invite and § 7.1.2 Join, Revoke is provided as an opposite of § 7.1.3 Grant. See the Behavior specification for more information about the usage of these different activity types in revocation of access to shared resources.

Properties:

type Revoke
actor The actor (person, bot, etc.) that is revoking access
fulfills An activity that triggered the sending of the Revoke, such as a related Remove or Leave
object specific § 7.1.3 Grant activities being undone, i.e. the access that they granted is now disabled and it cannot be used anymore as the capability of activities. Each Grant may either be mentioned by its id URI, or be included as a full object that includes an integrity proof.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://coding.community/repos/game-of-life/outbox/1C0e2",    "type": "Revoke",    "actor": "https://coding.community/repos/game-of-life",    "to": [        "https://coding.community/repos/game-of-life/followers",        "https://software.site/bob",        "https://software.site/bob/followers"    ],    "object": "https://coding.community/repos/game-of-life/outbox/9fA8c"}

7.2.4. Undo a Grant

The Behavior spec describes flows in which the § 7.2.3 Revoke activity is used by resources (more accurately, by the actors managing them) to announce that they’re disabling § 7.1.3 Grants that they previously sent. To allow for a clear distinction, another activity is provided here, for other actors to request the revocation of specific § 7.1.3 Grants: The ActivityPub Undo activity.

It’s likely that Grants would exist behind-the-scenes in applications, and human actors would then use activities such as Remove and Leave for disabling access. But the ability to disable specific Grants may be required for ensuring and maintaining system security, therefore Undo is provided here as well.

Properties:

type Undo
actor The actor (person, bot, etc.) that is revoking access
object specific § 7.1.3 Grant activities being undone, i.e. the access that they granted is now disabled and it cannot be used anymore as the capability of activities
capability A previously published Grant, giving the actor permission to disable the object actor’s access to the resource

8. Vocabulary

8.1. Types

The base URI of all ForgeFed terms is https://forgefed.org/ns#. The ForgeFed vocabulary has a JSON-LD context whose URI is https://forgefed.org/ns. Implementers MUST either include the ActivityPub and ForgeFed contexts in their object definitions, or other contexts that would result with the ActivityPub and ForgeFed terms being assigned they correct full URIs. Implementers MAY include additional contexts and terms as appropriate.

A typical @context of a ForgeFed object may look like this:

"@context": [    "https://www.w3.org/ns/activitystreams",    "https://forgefed.org/ns"]

8.1.1. Activities

Name Grant
URI https://forgefed.org/ns#Grant
Extends Activity
Description Indicates that target is being given (by the actor) access to a resource specified by context under the role/permission specified by object.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/aviva/outbox/reBGo",    "type": "Grant",    "actor": "https://example.dev/aviva",    "to": [        "https://example.dev/aviva/followers",        "https://example.dev/aviva/myproject",        "https://example.dev/aviva/myproject/followers",        "https://example.dev/bob",        "https://example.dev/bob/followers"    ],    "object": "write",    "context": "https://example.dev/aviva/myproject",    "target": "https://example.dev/bob"}
Name Revoke
URI https://forgefed.org/ns#Revoke
Extends Activity
Description Indicates that the actor is canceling target's access to a resource specified by context under the role specified by instrument, making the Grant activities specified by object unusable anymore in other activities' capability field.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/myproject/outbox/nlTxb",    "type": "Revoke",    "actor": "https://example.dev/myproject",    "to": [        "https://example.dev/myproject/followers",        "https://example.dev/users/aviva"    ],    "object": "https://example.dev/myproject/outbox/reBGo",    "instrument": "https://example.dev/roles/developer",    "context": "https://example.dev/myproject",    "target": "https://example.dev/users/aviva"}
Name Push
URI https://forgefed.org/ns#Push
Extends Activity
Description Indicates that new content has been pushed to the Repository.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/aviva/myproject/outbox/reBGo",    "type": "Push",    "actor": "https://example.dev/aviva/myproject",    "attributedTo": "https://example.dev/aviva",    "to": [        "https://example.dev/aviva",        "https://example.dev/aviva/followers",        "https://example.dev/aviva/myproject/followers"    ],    "summary": "<p>Aviva pushed a commit to myproject</p>",    "object": {        "type": "OrderedCollection",        "totalItems": 1,        "items": [            {                "id": "https://example.dev/aviva/myproject/commits/d96596230322716bd6f87a232a648ca9822a1c20",                "type": "Commit",                "attributedTo": "https://example.dev/aviva",                "context": "https://example.dev/aviva/myproject",                "hash": "d96596230322716bd6f87a232a648ca9822a1c20",                "created": "2019-11-03T13:43:59Z",                "summary": "Provide hints in sign-up form fields",            }        ]    },    "target": "https://example.dev/aviva/myproject/branches/master"}

8.1.2. Actors

8.1.2.1. Software development components
Name Repository
URI https://forgefed.org/ns#Repository
Extends Object
Description Represents a version control system repository.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://w3id.org/security/v1",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/treesim",    "type": "Repository",    "publicKey": {        "id": "https://dev.example/aviva/treesim#main-key",        "owner": "https://dev.example/aviva/treesim",        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki....."    },    "inbox": "https://dev.example/aviva/treesim/inbox",    "outbox": "https://dev.example/aviva/treesim/outbox",    "followers": "https://dev.example/aviva/treesim/followers",    "team": "https://dev.example/aviva/treesim/team",    "name": "Tree Growth 3D Simulation",    "summary": "<p>Tree growth 3D simulator for my nature exploration game</p>"}
Name TicketTracker
URI https://forgefed.org/ns#TicketTracker
Extends Object
Description Represents a ticket tracker, i.e. a project managing a list of work items.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://w3id.org/security/v2",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/treesim",    "type": ["Repository", "TicketTracker"],    "publicKey": {        "id": "https://dev.example/aviva/treesim#main-key",        "owner": "https://dev.example/aviva/treesim",        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki....."    },    "inbox": "https://dev.example/aviva/treesim/inbox",    "outbox": "https://dev.example/aviva/treesim/outbox",    "followers": "https://dev.example/aviva/treesim/followers",    "name": "Tree Growth 3D Simulation",    "summary": "<p>Tree growth 3D simulator for my nature exploration game</p>"}
Name PatchTracker
URI https://forgefed.org/ns#PatchTracker
Extends Object
Description Represents a tracker of merge requests, i.e. a project managing a list of patches or branches submitted as proposed changes to a given Repository.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://w3id.org/security/v2",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/treesim",    "type": ["Repository", "TicketTracker", "PatchTracker"],    "publicKey": {        "id": "https://dev.example/aviva/treesim#main-key",        "owner": "https://dev.example/aviva/treesim",        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki....."    },    "inbox": "https://dev.example/aviva/treesim/inbox",    "outbox": "https://dev.example/aviva/treesim/outbox",    "followers": "https://dev.example/aviva/treesim/followers",    "name": "Tree Growth 3D Simulation",    "summary": "<p>Tree growth 3D simulator for my nature exploration game</p>"}
8.1.2.2. Organizational structure tools
Name Project
URI https://forgefed.org/ns#Project
Extends Object
Description Represents a project, a planned endeavor that involves usage of tools related to the software development lifecycle. It may be a software project, but may also be totally unrelated to software development. For example, it may be a book that is being written using Markdown files kept in a Git repository. A Project object is a way to collect forge related components together under one title.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://w3id.org/security/v2",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/projects/wanderer",    "type": "Project",    "name": "Wanderer",    "summary": "3D nature exploration game",    "components": {        "type": "Collection",        "totalItems": 7,        "items": [            "https://dev.example/repos/opengl-vegetation",            "https://dev.example/repos/opengl-vegetation/patch-tracker",            "https://dev.example/repos/treesim",            "https://dev.example/repos/treesim/patch-tracker",            "https://dev.example/repos/wanderer",            "https://dev.example/repos/wanderer/patch-tracker",            "https://dev.example/issue-trackers/wanderer"        ]    },    "publicKey": {        "id": "https://dev.example/projects/wanderer#main-key",        "owner": "https://dev.example/projects/wanderer",        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki....."    },    "inbox": "https://dev.example/projects/wanderer/inbox",    "outbox": "https://dev.example/projects/wanderer/outbox",    "followers": "https://dev.example/projects/wanderer/followers"}
Name Team
URI https://forgefed.org/ns#Team
Extends Object
Description Represents a group of people working together, collaborating on shared resources. Each member Person in the team has a defined role, affecting the level of access they have to the team’s shared resources and to managing the team itself.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://w3id.org/security/v2",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/teams/mobilizon-dev-team",    "type": "Team",    "name": "Mobilizon Development Team",    "summary": "We're creating a federated tool for organizing events!",    "members": {        "type": "Collection",        "totalItems": 3,        "items": [            { "type": "Relationship",              "subject": "https://dev.example/teams/mobilizon-dev-team",              "relationship": "hasMember",              "object": "https://dev.example/people/alice",              "tag": "https://roles.example/admin"            },            { "type": "Relationship",              "subject": "https://dev.example/teams/mobilizon-dev-team",              "relationship": "hasMember",              "object": "https://dev.example/people/bob",              "tag": "maintain"            },            { "type": "Relationship",              "subject": "https://dev.example/teams/mobilizon-dev-team",              "relationship": "hasMember",              "object": "https://dev.example/people/celine",              "tag": "develop"            }        ]    },    "subteams": {        "type": "Collection",        "totalItems": 2,        "items": [            "https://dev.example/teams/mobilizon-backend-team",            "https://dev.example/teams/mobilizon-frontend-team"        ]    },    "context": "https://dev.example/teams/framasoft-developers",    "publicKey": {        "id": "https://dev.example/teams/mobilizon-dev-team#main-key",        "owner": "https://dev.example/teams/mobilizon-dev-team",        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki....."    },    "inbox": "https://dev.example/teams/mobilizon-dev-team/inbox",    "outbox": "https://dev.example/teams/mobilizon-dev-team/outbox",    "followers": "https://dev.example/teams/mobilizon-dev-team/followers"}

8.1.3. Objects

Name CapabilityUsage
URI https://forgefed.org/ns#CapabilityUsage
Extends Object
Values This specification defines 3: gatherAndConvey, distribute and [=invoke]
Description Represents a mode of using a Grant as an Object Capability (OCAP). There are two conceptual operations for Grants: Invocation (acting on the resource under the specified role) and Delegation (passing on the access to more actors, possibly with reduced privileges). A value of this type refers to one or both of these operations, and possibly to more specific conditions and restrictions on applying them.
TODO
Name Role
URI https://forgefed.org/ns#Role
Extends Object
Values visit, report, triage, write, maintain, admin, delegate
Description Represents a role that an actor has within a Team, or a role defining the level of access an actor has to a resource.
TODO
Name Branch
URI https://forgefed.org/ns#Branch
Extends Object
Description Represents a named variable reference to a version of the Repository, typically used for committing changes in parallel to other development, and usually eventually merging the changes into the main history line.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/luke/myrepo/branches/master",    "type": "Branch",    "name": "master",    "context": "https://example.dev/luke/myrepo",    "ref": "refs/heads/master"}
Name Commit
URI https://forgefed.org/ns#Commit
Extends Object
Description Represents a named set of changes in the history of a Repository. This is called "commit" in Git, Mercurial and Monotone; "patch" in Darcs; sometimes called "change set". Note that Commit is a set of changes that already exists in a repo’s history, while a Patch is a separate proposed change set, that could be applied and pushed to a repo, resulting with a Commit.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/alice/myrepo/commits/109ec9a09c7df7fec775d2ba0b9d466e5643ec8c",    "type": "Commit",    "context": "https://example.dev/alice/myrepo",    "attributedTo": "https://example.dev/bob",    "committedBy": "https://example.dev/alice",    "hash": "109ec9a09c7df7fec775d2ba0b9d466e5643ec8c",    "summary": "Add an installation script, fixes issue #89",    "description": {        "mediaType": "text/plain",        "content": "It's about time people can install on their computers!"    },    "created": "2019-07-11T12:34:56Z",    "committed": "2019-07-26T23:45:01Z"}
Name Patch
URI https://forgefed.org/ns#Patch
Extends Object
Description Represents a named set of changes that are being proposed for applying to a specific Repository.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/game-of-life/pulls/825/versions/1/patches/1",    "type": "Patch",    "attributedTo": "https://forge.example/luke",    "context": "https://dev.example/aviva/game-of-life/pulls/825/versions/1",    "mediaType": "application/x-git-patch",    "content": "From c9ae5f4ff4a330b6e1196ceb7db1665bd4c1..."}
Name TicketDependency
URI https://forgefed.org/ns#TicketDependency
Extends Relationship
Description Represents a relationship between 2 Tickets, in which the resolution of one ticket requires the other ticket to be resolved too. It MUST specify the subject, object and relationship properties, and the relationship property MUST be dependsOn.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "type": ["Relationship", "TicketDependency"],    "id": "https://example.dev/ticket-deps/2342593",    "attributedTo": "https://example.dev/alice",    "summary": "Alice's ticket depends on Bob's ticket",    "published": "2019-07-11T12:34:56Z",    "subject": "https://example.dev/alice/myproj/issues/42",    "relationship": "dependsOn",    "object": "https://dev.community/bob/coolproj/issues/85"}
Name Ticket
URI https://forgefed.org/ns#Ticket
Extends Object
Description Represents an item that requires work or attention. Tickets exist in the context of a project (which may or may not be a version-control repository), and are used to track ideas, proposals, tasks, bugs and more.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "type": "Ticket",    "id": "https://example.dev/alice/myrepo/issues/42",    "context": "https://example.dev/alice/myrepo",    "attributedTo": "https://dev.community/bob",    "summary": "Nothing works!",    "content": "<p>Please fix. <i>Everything</i> is broken!</p>",    "mediaType": "text/html",    "source": {        "content": "Please fix. *Everything* is broken!",        "mediaType": "text/markdown; variant=CommonMark"    },    "assignedTo": "https://example.dev/alice",    "isResolved": false}

8.2. Properties

8.2.1. General-purpose

Name earlyItems
URI https://forgefed.org/ns#earlyItems
Domain OrderedCollection
Range Ordered list of [ Object | Link ]
Functional No
Inverse of None
Description In an ordered collection (or an ordered collection page) in which items (or orderedItems) contains a continuous subset of the collection’s items from one end, earlyItems identifiers a continuous subset from the other end. For example, if items lists the chronologically latest items, earlyItems would list the chrologically earliest items. The ordering rule for items in earlyItems MUST be the same as in items. For examle, if items lists items in reverse chronogical order, then so does earlyItems.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/outbox",    "type": "OrderedCollection",    "totalItems": 712,    "orderedItems": [       "https://dev.example/aviva/outbox/712",       "https://dev.example/aviva/outbox/711",       "https://dev.example/aviva/outbox/710"    ],    "earlyItems": [       "https://dev.example/aviva/outbox/3",       "https://dev.example/aviva/outbox/2",       "https://dev.example/aviva/outbox/1"    ]}
Name previousVersions
URI https://forgefed.org/ns#previousVersions
Domain Object
Range rdf:List of objects of the same @type as the subject
Functional Yes
Inverse of None, but see currentVersion
Description Specifies the previous versions of the subject, as an ordered list in reverse chronological order.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/notes/107",    "type": "Note",    "attributedTo": "https://dev.example/aviva",    "content": "I agree!!!!! (edit: fixed a typo)",    "previousVersions": [       "https://dev.example/aviva/notes/107_old_version",       "https://dev.example/aviva/notes/107_very_old_version",       "https://dev.example/aviva/notes/107_ancient_version"    ]}
Name currentVersion
URI https://forgefed.org/ns#currentVersion
Domain Object
Range Object, of the same @type as the subject
Functional Yes
Inverse of None, but see previousVersions
Description Specifies the latest. current, up-to-date version of the subject. Once the subject specifies the currentVersion property, it SHOULD NOT get any changes to any other properties. The exception is currentVersion itself, which MUST be updated whenever needed, to always point to the latest version.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/notes/107_old_version",    "type": "Note",    "attributedTo": "https://dev.example/aviva",    "content": "I agree!!111",    "currentVersion": "https://dev.example/aviva/notes/107"}

8.2.2. Projects

Name components
URI https://forgefed.org/ns#components
Domain Project
Range Collection
Functional Yes
Inverse of None, but see the usage of context in the Modeling specification, e.g. in § 6.7 Repository
Description Identifies a Collection listing actors whose services and resources are considered to be components of this project.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/projects/wanderer",    "type": "Project",    "name": "Wanderer",    "summary": "3D nature exploration game",    "components": {        "type": "Collection",        "totalItems": 7,        "items": [            "https://dev.example/repos/opengl-vegetation",            "https://dev.example/repos/opengl-vegetation/patch-tracker",            "https://dev.example/repos/treesim",            "https://dev.example/repos/treesim/patch-tracker",            "https://dev.example/repos/wanderer",            "https://dev.example/repos/wanderer/patch-tracker",            "https://dev.example/issue-trackers/wanderer"        ]    },    "inbox": "https://dev.example/projects/wanderer/inbox",    "outbox": "https://dev.example/projects/wanderer/outbox",    "followers": "https://dev.example/projects/wanderer/followers"}
Name subprojects
URI https://forgefed.org/ns#subprojects
Domain Project
Range Collection of Projects
Functional Yes
Inverse of None, but see the usage of context in § 6.4 Project
Description Identifies a Collection listing the subprojects of this Project.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/projects/wanderer",    "type": "Project",    "name": "Wanderer",    "summary": "3D nature exploration game",    "subprojects": {        "type": "Collection",        "totalItems": 2,        "items": [            "https://dev.example/projects/nature-3d-models",            "https://dev.example/projects/wanderer-fundraising"        ]    },    "inbox": "https://dev.example/projects/wanderer/inbox",    "outbox": "https://dev.example/projects/wanderer/outbox",    "followers": "https://dev.example/projects/wanderer/followers"}

8.2.3. Team membership and nesting

Name hasMember
URI https://forgefed.org/ns#hasMember
Domain Team
Range Person
Functional No
Inverse of None
Description Identifier a Person who is a member of this Team.
TODO
Name members
URI https://forgefed.org/ns#members
Domain Team
Range Collection of Relationships whose relationship is hasMember and whose subject is this Team.
Functional Yes
Inverse of None
Description Identifies a collection of the members of this Team, represented as hasMember Relationships.
TODO
Name subteams
URI https://forgefed.org/ns#subteams
Domain Team
Range Collection of Teams.
Functional Yes
Inverse of None
Description Identifies a collection of the subteams of this Team, i.e. teams whose members inherit the access that this team’s members have to projects and to project components (such as Repositorys).
TODO

8.2.4. Ticket assignment and resolution

Name assignedTo
URI https://forgefed.org/ns#assignedTo
Domain Ticket
Range Person
Functional Yes
Inverse of None
Description Identifies the Person assigned to work on this Ticket.
TODO
Name isResolved
URI https://forgefed.org/ns#isResolved
Domain Ticket
Range `xsd:boolean`
Functional Yes
Inverse of None
Description Specifies whether the Ticket is closed, i.e. the work on it is done and it doesn’t need to attract attention anymore.
TODO
Name resolvedBy
URI https://forgefed.org/ns#resolvedBy
Domain Ticket
Range Object than is an actor, or Activity
Functional Yes
Inverse of None
Description Identifies the Actor who has resolved the Ticket, or the activity that has resolved the Ticket.
TODO
Name resolved
URI https://forgefed.org/ns#resolved
Domain Ticket
Range `xsd:dateTime`
Functional Yes
Inverse of None
Description For a resolved Ticket, specifies the time the Ticket has been resolved.
TODO

8.2.5. Ticket dependencies

Name dependsOn
URI https://forgefed.org/ns#dependsOn
Domain Ticket
Range Ticket
Functional No
Inverse of dependedBy
Description Identifies one or more tickets on which this Ticket depends, i.e. it can’t be resolved without those tickets being resolved too.
TODO
Name dependedBy
URI https://forgefed.org/ns#dependedBy
Domain Ticket
Range Ticket
Functional No
Inverse of dependsOn
Description Identifies one or more tickets which depend on this Ticket, i.e. they can’t be resolved without this tickets being resolved too.
TODO
Name dependencies
URI https://forgefed.org/ns#dependencies
Domain Ticket
Range Collection of items of type TicketDependency
Functional Yes
Inverse of None
Description Identifies a Collection of TicketDependency which specify tickets that this Ticket depends on, i.e. this ticket is the subject of the dependsOn relationship.
TODO
Name dependants
URI https://forgefed.org/ns#dependants
Domain Ticket
Range Collection of items of type TicketDependency
Functional Yes
Inverse of None
Description Identifies a Collection of TicketDependency which specify tickets that depends on this Ticket, i.e. this ticket is the object of the dependsOn relationship. Often called "reverse dependencies".
TODO

8.2.6. Repository cloning

Name cloneUri
URI https://forgefed.org/ns#cloneUri
Domain Repository
Range Object
Functional No
Inverse of None
Description An endpoint that can be used with a VCS protocol such as Git or Mercurial to access a given repository.
TODO

8.2.7. Repository pushing

Name hashBefore
URI https://forgefed.org/ns#hashBefore
Domain Push
Range xsd:string of hexadecimal digit ASCII characters
Functional Yes
Inverse of None
Description Specifies the hash of the commit that the pushed branch/repo was pointing to right before the push happpened.
TODO
Name hashAfter
URI https://forgefed.org/ns#hashAfter
Domain Push
Range xsd:string of hexadecimal digit ASCII characters
Functional Yes
Inverse of None
Description Specifies the hash of the commit that the pushed branch/repo was pointing to right after the push happpened.
TODO

8.2.8. Commits and branches

Name repository (DEPRECATED)
URI https://forgefed.org/ns#repository
Domain Commit
Range Repository
Functional Yes
Inverse of None
Description
Identifies the repository to which a commit belongs. DEPRECATED Use the standard ActivityPub context property instead.
TODO
Name description
URI https://forgefed.org/ns#description
Domain Commit
Range Object, specifying content and mediaType. The mediaType SHOULD be "text/plain".
Functional Yes
Inverse of None
Description Specifies the description text of a Commit, which is an optional possibly multi-line text provided in addition to the one-line commit title. The range of the description property works the same way the range of the ActivityPub source property works.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/alice/myrepo/commits/109ec9a09c7df7fec775d2ba0b9d466e5643ec8c",    "type": "Commit",    "context": "https://example.dev/alice/myrepo",    "attributedTo": "https://example.dev/bob",    "hash": "109ec9a09c7df7fec775d2ba0b9d466e5643ec8c",    "created": "2019-07-11T12:34:56Z",    "summary": "Add an installation script, fixes issue #89",    "description": {        "mediaType": "text/plain",        "content": "It's about time people can install on their computers!"    },}
Name committedBy
URI https://forgefed.org/ns#committedBy
Domain Commit
Range Object
Functional Yes
Inverse of None
Description Identifies the actor (usually a person, but could be something else, e.g. a bot) that added a set of changes to the version-control Repository. Sometimes the author of the changes and the committer of those changes aren’t the same actor, in which case the committedBy property can be used to specify who added the changes to the repository. For example, when applying a patch to a repository, e.g. a Git repository, the author would be the person who made the patch, and the committer would be the person who applied the patch to their copy of the repository.
TODO
Name hash
URI https://forgefed.org/ns#hash
Domain Commit
Range xsd:string of hexadecimal digit ASCII characters
Functional Yes
Inverse of None
Description Specifies the hash associated with a Commit, which is a unique identifier of the commit within the Repository, usually generated as a cryptographic hash function of some (or all) of the commit’s data or metadata. For example, in Git it would be the SHA1 hash of the commit; in Darcs it would be the SHA1 hash of the patch info.
TODO
Name committed
URI https://forgefed.org/ns#committed
Domain Commit
Range `xsd:dateTime`
Functional Yes
Inverse of None
Description Specifies the time that a set of changes was committed into the Repository and became a Commit in it. This can be different from the time the set of changes was produced, e.g. if one person creates a patch and sends to another, and the other person then applies the patch to their copy of the repository. We call the former event "created" and the latter event "committed", and this latter event is specified by the committed property.
TODO
Name filesAdded
URI https://forgefed.org/ns#filesAdded
Domain Commit
Range `xsd:string`
Functional No
Inverse of None
Description Specifies a filename, as a relative path, relative to the top of the tree of files in the Repository, of a file that got added in this Commit, and didn’t exist in the previous version of the tree.
TODO
Name filesModified
URI https://forgefed.org/ns#filesModified
Domain Commit
Range `xsd:string`
Functional No
Inverse of None
Description Specifies a filename, as a relative path, relative to the top of the tree of files in the Repository, of a file that existed in the previous version of the tree, and its contents got modified in this Commit.
TODO
Name filesRemoved
URI https://forgefed.org/ns#filesRemoved
Domain Commit
Range `xsd:string`
Functional No
Inverse of None
Description Specifies a filename, as a relative path, relative to the top of the tree of files in the Repository, of a file that existed in the previous version of the tree, and got removed from the tree in this Commit.
TODO
Name ref
URI https://forgefed.org/ns#ref
Domain Branch
Range `xsd:string`
Functional Yes
Inverse of None
Description Specifies an identifier for a Branch, that is used in the Repository to uniquely refer to it. For example, in Git, "refs/heads/master" would be the ref of the master branch.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/luke/myrepo/branches/master",    "type": "Branch",    "name": "master",    "context": "https://example.dev/luke/myrepo",    "ref": "refs/heads/master"}

8.2.9. Activity addressing

Name team
URI https://forgefed.org/ns#team
Domain Object
Range Collection of actors
Functional Yes
Inverse of None
Description Specifies a Collection of actors who are working on the object, or responsible for it, or managing or administrating it, or having edit access to it. For example, for a Repository, it could be the people who have push/edit access, the "collaborators" of the repository.
A repository https://dev.example/aviva/treesim:
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://w3id.org/security/v1",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/treesim",    "type": "Repository",    "publicKey": {        "id": "https://dev.example/aviva/treesim#main-key",        "owner": "https://dev.example/aviva/treesim",        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki....."    },    "inbox": "https://dev.example/aviva/treesim/inbox",    "outbox": "https://dev.example/aviva/treesim/outbox",    "followers": "https://dev.example/aviva/treesim/followers",    "name": "Tree Growth 3D Simulation",    "summary": "<p>Tree growth 3D simulator for my nature exploration game</p>",    "team": "https://dev.example/aviva/treesim/team"}

The repository’s team https://dev.example/aviva/treesim/team:

{    "@context": "https://www.w3.org/ns/activitystreams",    "id": "https://dev.example/aviva/treesim/team",    "type": "Collection",    "totalItems": 3,    "items": [        "https://dev.example/aviva",        "https://dev.example/luke",        "https://code.community/users/lorax"    ]}

8.2.10. Tracker linking

Name ticketsTrackedBy
URI https://forgefed.org/ns#ticketsTrackedBy
Domain Object
Range Object that is an actor
Functional Yes
Inverse of tracksTicketsFor
Description Identifies the actor which tracks tickets related to the given object. This is the actor to whom you send tickets you’d like to open against the object.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/aviva/treesim",    "type": "Repository",    "name": "Tree Growth 3D Simulation",    "summary": "<p>Tree growth 3D simulator for my nature exploration game</p>",    "ticketsTrackedBy": "https://bugs.example/projects/treesim"}
Name tracksTicketsFor
URI https://forgefed.org/ns#tracksTicketsFor
Domain Object that is an actor
Range Object
Functional No
Inverse of ticketsTrackedBy
Description Identifies objects for which which this ticket tracker tracks tickets. When you’d like to open a ticket against those objects, you can send them to this tracker.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://bugs.example/treesim",    "type": "Project",    "tracksTicketsFor": [        "https://dev.example/aviva/liblsystem",        "https://dev.example/aviva/3d-tree-models",        "https://dev.example/aviva/treesim"    ]}
Name sendPatchesTo
URI https://forgefed.org/ns#sendPatchesTo
Domain Repository
Range PatchTracker
Functional Yes
Inverse of tracksPatchesFor
Description Identifies the PatchTracker which tracks patches and merge requests related to the given repository. This is the actor to whom you send patches and merge requests you’d like to open against the repository.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://dev.example/repos/treesim",    "type": "Repository",    "name": "Tree Growth 3D Simulation",    "summary": "<p>Tree growth 3D simulator for my nature exploration game</p>",    "sendPatchesTo": "https://bugs.example/pr-trackers/treesim"}
Name tracksPatchesFor
URI https://forgefed.org/ns#tracksPatchesFor
Domain PatchTracker
Range Repository
Functional No
Inverse of sendPatchesTo
Description Identifies a repository for which which this patch and merge request tracker tracks patches and merge requests. When you’d like to open patches or merge requests against that repository, you can send them to this tracker.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://project.example/treesim",    "type": "PatchTracker",    "tracksPatchesFor": [        "https://dev.example/aviva/liblsystem",        "https://dev.example/aviva/3d-tree-models",        "https://dev.example/aviva/treesim"    ]}

8.2.11. Repository forking

Name forkedFrom
URI https://forgefed.org/ns#forkedFrom
Domain Repository
Range Repository
Functional Yes
Inverse of forks
Description Identifies the Repository which this Repository was created as a fork of, i.e. by cloning it.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/alice/myfork/",    "type": "Repository",    "forkedFrom": {        "type": "Repository",        "id": "https://example.dev/luke/myrepo/"    }}
Name forks
URI https://forgefed.org/ns#forks
Domain Repository
Range OrderedCollection of items of type Repository
Functional Yes
Inverse of forkedFrom
Description Identifies an OrderedCollection of Repositorys which were created as forks of this Repository, i.e. by cloning it. The order of the collection items is by reverse chronological order of the forking events.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/luke/myrepo/",    "type": "Repository",    "forks": {        "type": "OrderedCollection",        "totalItems": 1,        "orderedItems": [            {                "id": "https://example.dev/alice/myfork/",                "type": "Repository",            }        ]    },}

8.2.12. Access control

Name fulfills
URI https://forgefed.org/ns#fulfills
Domain Activity
Range Activity
Functional No
Inverse of None
Description For an activity A that fulfills some other activity B, specifies that A has been published as part of fulfilling the action requested by B. For example, if Alice creates a new repository using Create, she may want to instantly automatically start following this new repository using Follow (to be notified when her friends push commits there). This Follow fulfills the Create; it’s an activity automatically sent as part of creating a new repository.
TODO
Name allows
URI https://forgefed.org/ns#allows
Domain Grant
Range CapabilityUsage
Functional No
Inverse of None
Description Specifies which modes of using this Grant are being allowd by it. The two conceptual operations that Grants support are invocation (acting on the resource under the specified role) and delegation (passing on the access to more actors, possibly with reduced privileges). This property specifies which of these operations are supported, and under which conditions. See CapabilityUsage for specific values to use.
TODO
Name capability
URI https://forgefed.org/ns#capability
Domain Activity
Range Grant
Functional Yes
Inverse of None
Description Specifies a previously published Grant activity providing relevant access permissions. For example, if Alice wants to resolve a Ticket under some project, she will send an activity with capability referring to the Grant activity that gave her collaborator access to that project, which happens to include permission to resolve tickets.
TODO
Name managedBy
URI https://forgefed.org/ns#managedBy
Domain Object
Range Object that is an actor
Functional Yes
Inverse of None
Description Identifies the actor that controls the given resource, and to whom activities asking to modify the resource may be submitted.
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "type": "Ticket",    "id": "https://example.dev/alice/myrepo/issues/42",    "context": "https://example.dev/alice/myrepo",    "managedBy": "https://example.dev/alice/myrepo",    "attributedTo": "https://dev.community/bob",    "summary": "Nothing works!",    "content": "<p>Please fix. <i>Everything</i> is broken!</p>",    "mediaType": "text/html",    "source": {        "content": "Please fix. *Everything* is broken!",        "mediaType": "text/markdown; variant=CommonMark"    },    "isResolved": false}
Name delegates
URI https://forgefed.org/ns#delegates
Domain Grant
Range Grant
Functional Yes
Inverse of None
Description Actors can use Grant activities to allow other actors to access their resources. They can also allow those other actors to pass on (delegate) this access to even more actors. For a Grant that delegates access provided by an earlier Grant, the former uses delegates to specify the latter. That earlier Grant is also called the "parent capability" of this Grant.
TODO

8.2.13. Repository mirroring

Name mirrors
URI https://forgefed.org/ns#mirrors
Domain Repository
Range Repository
Functional Yes
Inverse of mirroredBy
Description Identifies the Repository which this Repository copies content from (i.e. what this repository is a "pull mirror" of).
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/alice/mymirror/",    "type": "Repository",    "mirrors": {        "type": "Repository",        "id": "https://example.dev/luke/myrepo/"    }}
Name mirroredBy
URI https://forgefed.org/ns#mirroredBy
Domain Repository
Range Repository
Functional No
Inverse of mirrors
Description Identifies a Repository which copies content from this repository (i.e. "pull mirror" of this repository).
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/luke/myrepo/",    "type": "Repository",    "mirroredBy": {        "type": "Repository",        "id": "https://example.dev/alice/mymirror/"    }}
Name mirrorsTo
URI https://forgefed.org/ns#mirrorsTo
Domain Repository
Range Repository
Functional No
Inverse of mirroredFrom
Description Identifies a Repository which this repository copies content to (i.e. "push mirror" of this repository)
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/alice/myrepo/",    "type": "Repository",    "mirrorsTo": {        "type": "Repository",        "id": "https://example.dev/alice-backup/myrepo/"    }}
Name mirroredFrom
URI https://forgefed.org/ns#mirroredFrom
Domain Repository
Range Repository
Functional Yes
Inverse of mirrorsTo
Description Identifies the Repository which copies its content to this Repository (ie. what this repository is a "push mirror" of).
{    "@context": [        "https://www.w3.org/ns/activitystreams",        "https://forgefed.org/ns"    ],    "id": "https://example.dev/alice-backup/myrepo/",    "type": "Repository",    "mirroredFrom": {        "type": "Repository",        "id": "https://example.dev/alice/myrepo/"    }}

8.3. Values

8.3.1. Capability uses

Name gatherAndConvey
URI https://forgefed.org/ns#gatherAndConvey
Type CapabilityUsage
Conditions
  • The Grant’s target MUST be a Project
  • It may delegate the Grant, allowing only gatherAndConvey, to parent projects
  • It may delegate the Grant, allowing only distribute, to teams to which it allows to access it
  • It may delegate the Grant, allowing invoke only, to people to which it allows to access it
Name distribute
URI https://forgefed.org/ns#distribute
Type CapabilityUsage
Conditions
  • The Grant’s target MUST be a Team
  • It may delegate the Grant, allowing distribute only, to its subteams
  • It may delegate the Grant, allowing invoke only, to its members
Name invoke
URI https://forgefed.org/ns#invoke
Type CapabilityUsage
Conditions
  • The Grant’s target may invoke it, i.e. use it as the capability in another activity, that requests to access or modify the resource specified by the Grant’s context

8.3.2. Roles

Name visit
URI https://forgefed.org/ns#visit
Type Role
Description Authorizes the Grant recipient (i.e. target) to view the Grant resource (i.e. context), which includes retrieving objects via HTTP and pulling/cloning VCS repos
Name report
URI https://forgefed.org/ns#report
Type Role
Description Authorizes the Grant recipient (i.e. target) to do on the Grant resource (i.e. context) anything that the visit role authorizes,
and also to do basic community participation tasks Open an issue, submit a PR, create comments and discussion threads, edit public wikis, submit PR reviews.
Name triage
URI https://forgefed.org/ns#triage
Type Role
Description Authorizes the Grant recipient (i.e. target) to do on the Grant resource (i.e. context) anything that the report role authorizes, and also to edit issue/PR propeties (labels, milestones, due dates, etc.), close and reopen issues and PRs, assign and unassign people to issues and PRs, request PR reviews, hide disruptive comments (a moderation action), lock and move discussions.
Name write
URI https://forgefed.org/ns#write
Type Role
Description Authorizes the Grant recipient (i.e. target) to do on the Grant resource (i.e. context) anything that the triage role authorizes, and also to apply PR suggested changes, edit non-public wikis, create/edit/delete labels, merge a PR, push to VCS repositories, create/edit/run/cancel CI recipes, manage releases, publish packages, create web IDE coding sessions.
Name maintain
URI https://forgefed.org/ns#maintain
Type Role
Description Authorizes the Grant recipient (i.e. target) to do on the Grant resource (i.e. context) anything that the write role authorizes, and also to edit project and component descriptions and settings unrelated to access, enable/disable components, configure "Pages" publishing of static websites from repos, push to repos' protected branches.
Name admin
URI https://forgefed.org/ns#admin
Type Role
Description Authorizes the Grant recipient (i.e. target) to do on the Grant resource (i.e. context) anything that the maintain role authorizes, and also to manage access to projects, components and teams, merge PRs even without reviews, delete issues, change project/component/team visibility, edit project/component/team access-related settings, change a repo’s default branch, manage webhooks and deployment, move components and projects between projects, archive projects/components, delete components/projects/teams.
Name delegate
URI https://forgefed.org/ns#delegate
Type Role
Description Authorizes the Grant recipient (i.e. target) to send access delegations to the Grant sender (i.e. actor)

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[ACTIVITYPUB]
Christopher Webber; Jessica Tallon. ActivityPub. URL: https://w3c.github.io/activitypub/
[ActivityStreams-Vocabulary]
James Snell; Evan Prodromou. Activity Vocabulary. URL: https://w3c.github.io/activitystreams/vocabulary/
[DCTERMS]
DCMI Usage Board. DCMI Metadata Terms. 20 January 2020. DCMI Recommendation. URL: https://www.dublincore.org/specifications/dublin-core/dcmi-terms/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[XMLSCHEMA11-2]
David Peterson; et al. W3C XML Schema Definition Language (XSD) 1.1 Part 2: Datatypes. 5 April 2012. REC. URL: https://www.w3.org/TR/xmlschema11-2/

Informative References

[FEP-8B32]
FEP-8b32: Object Integrity Proofs. URL: https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-8b32.md
[WIKIPEDIA-PR]
Wikipedia: Pull requests. URL: https://en.wikipedia.org/wiki/Distributed_version_control#Pull_requests
[WIKIPEDIA-TICKET-TRACKER]
Wikipedia: Issue tracking system. URL: https://en.wikipedia.org/wiki/Issue_tracking_system

Issues Index

(fr33) In the abstract I just pasted the 3 abstracts of the separate specs. Write something new, probably with inspiration from existing specs such as ActivityPub.
Below are the 3 intro texts from the 3 specs. We probably want to replace them with a human-friendly tutorial-like example, like in the ActivityPub spec.
This section describes the whole flow of actor interactions, that allows the federated implementation of the various features of forges and related software. It provides a complete picture of interaction flows, which the actor API section can’t, because it focuses on a single actor type at a time.
"Going back to step 1" refers to the top-level list item; should probably tweak the CSS to display nested lists differently.
"Step 2" refers to the top-level one, need to tweak CSS for lists
This section will provide, for each actor type, a summary of the various (1) "methods" i.e. activities it can receive as requests for action; (2) "events", i.e. activities it sends out; (3) perhaps representation of resources that are specific to the actor type. Right now there’s a grey zone between methods and events, because some activities are methods for the target actor but events for any other recipient, and some events are actually sent by other actors, which cc the target actor’s followers and the target actor delivers via inbox-forwarding and not by Announceing (or custom Forwarding) those activities.
This section is about how a human or bot can interact with the system by POSTing activities into the outbox of a Person (or Application/Service?) actor, and managing notifications. It’s less urgent than Server-to-Server. fr33 is using C2S in Vervis, and will be gradually working on this part.
This section is about representation of objects. It’s possible that actor representation will move into a separate section, as well as objects specific to one actor type. And then this section will describe only objects/resources used by multiple actor types, such as comments (person, issue tracker, PR tracker) and tickets (used for both issues and PRs).