mirror of
https://github.com/forgefed/forgefed
synced 2024-11-22 03:43:10 +00:00
1dac41abd4
For projects, this is fully implemented in Vervis. The quite similar (mostly reuses the same code) implementation for teams is in progress. Reviewed-on: https://codeberg.org/ForgeFed/ForgeFed/pulls/217 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Reviewed-by: Aravinth Manivannan <realaravinth@noreply.codeberg.org> Co-authored-by: Pere Lev <pere@towards.vision> Co-committed-by: Pere Lev <pere@towards.vision>
5305 lines
188 KiB
Text
5305 lines
188 KiB
Text
<pre class='metadata'>
|
|
Status: LS-BRANCH
|
|
Group: ForgeFed Working Group
|
|
Editor: Alice Doe, MyCoolCompany https://mycoolcompany.com/home, https://alicedoe.me/about
|
|
Editor: Bob Doe, SomeOrg https://some.org, https://home.net/bob
|
|
Favicon: /img/favicon.png
|
|
Logo: /img/logo.svg
|
|
Status Text: Work in progress
|
|
Issue Tracking: Codeberg https://codeberg.org/forgefed/forgefed/issues
|
|
Issue Tracker Template: https://codeberg.org/forgefed/forgefed/issues/{0}
|
|
Indent: 4
|
|
Work Status: exploring
|
|
Repository: https://codeberg.org/forgefed/forgefed forgefed
|
|
Line Numbers: yes
|
|
Markup Shorthands: markdown yes, css no, http no, idl no, markup no
|
|
Title: ForgeFed
|
|
Shortname: forgefed
|
|
URL: https://forgefed.org/spec
|
|
Revision: 1
|
|
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.
|
|
</pre>
|
|
|
|
Issue: (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 <code>[GITBRANCH]</code>, commit
|
|
<a href="https://codeberg.org/ForgeFed/ForgeFed/commit/[GITCOMMIT]">[GITSHORT]</a>.
|
|
|
|
# Introduction # {#intro}
|
|
|
|
Note: The spec is still under construction. Want to start implementing? Check
|
|
out the ActivityPub implementation guide, and then the [[#s2s]] section here.
|
|
|
|
Issue: 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.
|
|
|
|
# Objects
|
|
|
|
## 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.
|
|
|
|
## Object Publishing and Hosting ## {#publishing}
|
|
|
|
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:
|
|
|
|
- [=object=] MUST be the object being offered for publishing, and that object
|
|
MUST NOT have an [=id=]
|
|
- [=target=] MUST indicate under which list/collection/context the sender would
|
|
like the object to be published (it may also be the URI of the target actor
|
|
itself)
|
|
|
|
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:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
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:
|
|
|
|
- [=object=] MUST be the Offer activity or its [=id=]
|
|
- [=result=] MUST be specified and be the [=id=] of the new child object now
|
|
hosted by the *target actor*, which is extracted from the [=Offer=]'s
|
|
[=object=]
|
|
|
|
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:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
# Server to Server Interactions # {#s2s}
|
|
|
|
Issue: 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.
|
|
|
|
## Reporting Pushed Commits ## {#pushing}
|
|
|
|
- Role required for pushing: [=write=]
|
|
- Role required for reporting a Push: None (because the [=Repository=] itself
|
|
is publishing the Push)
|
|
|
|
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 [[#Push]].
|
|
|
|
See example in [[#Push]].
|
|
|
|
## Opening an issue ## {#opening-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*.
|
|
|
|
- If `ticketsTrackedBy` isn't specified, then *obj* does't declare a way to
|
|
open tickets via ForgeFed.
|
|
- If `ticketsTrackedBy` is specified and is set to the [=id=] of *obj*
|
|
itself, that means *obj* manages its own tickets, i.e. it is the *ticket
|
|
tracker* to which you'll send the ticket.
|
|
- If `ticketsTrackedBy` is specified and is set to some other object, look at
|
|
the [=tracksTicketsFor=] property of that other object. If the [=id=] of
|
|
*obj* is listed there under `tracksTicketsFor`, then that other object is
|
|
the *ticket tracker* to which you'll send the ticket. Implementations
|
|
SHOULD verify this bidirectional reference between the object and the
|
|
tracker, and SHOULD NOT send a ticket if the bidirectional reference isn't
|
|
found.
|
|
|
|
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:
|
|
|
|
- [=object=] is the ticket to be opened, it's a [=Ticket=] object with fields
|
|
as described in [[#Ticket]]. It MUST specify at least [=attributedTo=],
|
|
[=summary=] and [=content=], and MUST NOT specify [=id=]. If it specifies a
|
|
[=context=], then it MUST be identical the Offer's [=target=] described
|
|
below.
|
|
- [=target=] is the ticket tracker to which the actor is offering the Ticket
|
|
(e.g. a repository or project etc. under which the ticket will be opened if
|
|
accepted). It MUST be either an actor or a child object. If it's a child
|
|
object, the actor to whom the child object belongs MUST be listed as a
|
|
recipient in the Offer's [=to=] field. If it's an actor, then that actor
|
|
MUST be listed in the `to` field.
|
|
|
|
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:
|
|
|
|
- If a [=Reject=] was sent, it means the ticket hasn't been assigned an
|
|
[=id=] URI by the tracker and isn't being tracked by the tracker
|
|
- If an [=Accept=] was sent, it means the ticket is now tracked and hosted on
|
|
the target's side
|
|
|
|
In the following example, Luke wants to open a ticket under Aviva's Game Of
|
|
Life simulation app:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Luke's ticket is opened automatically and Aviva's Game Of Life repository,
|
|
which is an actor, automatically sends Luke an Accept activity:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Opening a merge request ## {#opening-mr}
|
|
|
|
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=] is *A*
|
|
- [=target=] is *P*
|
|
- [=object=] is a [=Ticket=] in which:
|
|
* [=id=] isn't specified
|
|
* [=type=] is [=Ticket=]
|
|
* [=attributedTo=] is *A*
|
|
* [=summary=] is a one-line HTML-escaped plain-text title of the MR
|
|
* [=source=] is the MR's description
|
|
* [=content=] is an HTML rendering of the MR's description
|
|
* [=context=], if specified, is *P*
|
|
* Among the [=attachment=]s there's exactly one of type [=Offer=], in
|
|
which:
|
|
+ [=type=] is [=Offer=]
|
|
+ [=origin=] is the [=Repository=] or [=Branch=] from which the
|
|
proposed changes are proposed to be merged into the target
|
|
repository/branch
|
|
+ [=target=] is the [=Repository=] or [=Branch=] into which the
|
|
changes are proposed to be merged
|
|
+ [=object=] is an [=OrderedCollection=] of [=Patch=] objects in
|
|
reverse chronological order, all of them with:
|
|
- the same [=mediaType=]
|
|
- that [=mediaType=] MUST match the Version Control System of
|
|
the target [=Repository=]
|
|
- [=attributedTo=] MUST be *A*
|
|
+ At least [=origin=] or [=object=] MUST be provided, both MAY be
|
|
provided
|
|
|
|
Actor *P* MAY send back an [=Accept=] or [=Reject=]. The action that has been
|
|
taken by *P* is indicated to actor *A* as follows:
|
|
|
|
- If a [=Reject=] was sent, it mean the MR has been rejected, and isn't being
|
|
tracked by *P*
|
|
- If an [=Accept=] was sent, it means the MR is now tracked by *P*, and its
|
|
[=id=] is indicated by the [=Accept=]'s [=result=]
|
|
|
|
In the following example, Luke wants to open a Merge Request against a Game Of
|
|
Life simulation app:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Luke's MR is opened automatically and the [=PatchTracker=]
|
|
sends Luke an Accept activity:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Commenting ## {#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 [[#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:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Granting access to shared resources ## {#managing-access}
|
|
|
|
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.
|
|
|
|
### Object capabilities
|
|
|
|
#### Introduction #### {#s2s-grant-simple}
|
|
|
|
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](#s2s-grant-flow); for now let's assume `Grant`s
|
|
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.
|
|
|
|
#### 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:
|
|
|
|
- [=actor=]: Specifies actor *R*
|
|
- [=context=]: Specifies resource *r*
|
|
- [=target=]: Specifies actor *A*
|
|
- [=object=]: Specifies role *p*
|
|
- [=startTime=]: (optional) The time at which the Grant becomes valid
|
|
- [=endTime=]: (recommended) The time at which the Grant expires
|
|
- [=allows=]: Specifies [=invoke=]
|
|
- [=delegates=]: Not used
|
|
|
|
#### Granting the delegate role #### {#grant-delegate}
|
|
|
|
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 `Grant`s to the [=actor=], i.e. to send `Grant`s meant
|
|
for delegation or `Grants` that are themselves delegations of other `Grant`s
|
|
(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 `Grant`s to
|
|
actor *A*, then actor *A* can send to actor *R* a [=Grant=] activity
|
|
with the following properties:
|
|
|
|
- [=actor=]: Specifies actor *A*
|
|
- [=context=]: Specifies actor *A*
|
|
- [=target=]: Specifies actor *R*
|
|
- [=object=]: Specifies [=delegate=]
|
|
- [=startTime=]: (optional) The time at which the Grant becomes valid
|
|
- [=endTime=]: (recommended) The time at which the Grant expires
|
|
- [=allows=]: Specifies [=invoke=]
|
|
- [=delegates=]: Not used
|
|
|
|
#### Starting a delegation chain #### {#start-grant-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:
|
|
|
|
- [=actor=]: Specifies actor *R*
|
|
- [=context=]: Specifies resource *r*
|
|
- [=target=]: Specifies actor *A*
|
|
- [=object=]: Specifies role *p*
|
|
- [=startTime=]: (optional) The time at which the Grant becomes valid
|
|
- [=endTime=]: (recommended) The time at which the Grant expires
|
|
- [=allows=]: Specifies the conditions under which actor *A* may
|
|
delegate this `Grant` (i.e. conditions under which actor *R* will consider
|
|
the delegation valid when verifying the chain), and what the recipients of
|
|
the delegtaions that *A* will send (which are themselves `Grant` activites)
|
|
are allowed to do with these `Grants` (invoke? further delegate to certain
|
|
other actors?)
|
|
- [=delegates=]: Not used
|
|
- [=capability=]: *(optional)* Specifies a
|
|
[[#grant-delegate|delegate Grant]] previously given by *A* to *R*
|
|
|
|
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
|
|
|
|
#### Extending a delegation chain #### {#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:
|
|
|
|
- [=actor=] is actor *A*
|
|
- [=context=] (i.e. the resource) is same as *g*'s [=context=]
|
|
- [=target=] is actor *B*
|
|
- [=object=] (i.e. the granted role) is either *g*'s [=object=] or a
|
|
lower-access role than *g*'s [=object=], i.e. provides a subset of the
|
|
permissions that *g*'s [=object=] provides (the latter case is called
|
|
*attenuation*)
|
|
- [=startTime=]: *(optional)* The time at which this Grant becomes valid
|
|
- [=endTime=]: *(recommended)* The time at which this Grant expires
|
|
- [=allows=]: Specifies the conditions under which actor *B* may
|
|
delegate this `Grant` (i.e. conditions under which the delegation will be
|
|
considered valid when verifying the chain), and what the recipients of
|
|
the delegtaions that *B* will send (which are themselves `Grant` activites)
|
|
are allowed to do with these `Grants` (invoke? further delegate to certain
|
|
other actors?)
|
|
- [=delegates=] is activity *g*
|
|
- [=capability=]: *(optional)* Specifies a
|
|
[delegate Grant](#grant-delegate) previously given by *B* to *A*
|
|
- [=result=]: a URI that will be used later to verify that *h* is still active
|
|
and hasn't been revoked. Alternatively, an object with [=id=] and
|
|
[=duration=] as described below.
|
|
|
|
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:
|
|
|
|
- From the moment that actor *A* publishes activity *h*, as long as actor *A*
|
|
considers *h* an active `Grant` and hasn't revoked it, any HTTP HEAD or HTTP
|
|
GET request the [=result=] URI MUST return an HTTP response status 204 or 200.
|
|
- If later activity *h* is revoked, or actor *A* is deleted, then from the
|
|
moment that actor *A* considers *h* deactivated, any HTTP HEAD or HTTP GET
|
|
request to the [=result=] URI MUST NOT return an HTTP response status in the
|
|
200-299 range. The response status SHOULD be 410 or 404.
|
|
|
|
[=result=] MAY instead specify a JSON object in which:
|
|
|
|
- [=id=] is the URI as described above
|
|
- *(optional)* [=duration=] specifies a duration that allows the recovation URI
|
|
check to be skipped, if the duration hasn't yet passed since the last check
|
|
of the URI. If [=duration=] is specified, it MUST be positive and include
|
|
only an integral number of seconds that is less than `2^63`, and no other
|
|
component. In other words, its format is: The string "PT", then the
|
|
integer, then the string "S".
|
|
|
|
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:
|
|
- [=actor=] is project *A*
|
|
- [=context=] (i.e. the resource) is same as *g*'s [=context=]
|
|
- [=target=] is project *P*
|
|
- [=object=] (i.e. the granted role) is either *g*'s [=object=] or
|
|
a lower-access role than *g*'s [=object=]
|
|
- [=allows=] is a single value [=gatherAndConvey=]
|
|
- [=delegates=] is activity *g*
|
|
- [=capability=]: *(optional)* Specifies a
|
|
[delegate Grant](#grant-delegate) previously given by *P* to *A*
|
|
- [=result=]: a URI that will be used later to verify that *h* is
|
|
still active and hasn't been revoked, or a JSON object as
|
|
describes above
|
|
|
|
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:
|
|
- [=actor=] is project *A*
|
|
- [=context=] (i.e. the resource) is same as *g*'s [=context=]
|
|
- [=target=] is team *T*
|
|
- [=object=] (i.e. the granted role) is the lower-access role
|
|
among *g*'s [=object=] and *p*
|
|
- [=allows=] is a single value [=distribute=]
|
|
- [=delegates=] is activity *g*
|
|
- [=capability=]: *(optional)* Specifies a
|
|
[delegate Grant](#grant-delegate) previously given by *T* to *A*
|
|
- [=result=]: a URI that will be used later to verify that *h* is
|
|
still active and hasn't been revoked, or a JSON object as
|
|
describes above
|
|
|
|
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:
|
|
- [=actor=] is project *A*
|
|
- [=context=] (i.e. the resource) is same as *g*'s [=context=]
|
|
- [=target=] is actor *M*
|
|
- [=object=] (i.e. the granted role) is the lower-access role
|
|
among *g*'s [=object=] and *p*
|
|
- [=allows=] is a single value [=invoke=]
|
|
- [=delegates=] is activity *g*
|
|
- [=capability=]: *(optional)* Specifies a
|
|
[delegate Grant](#grant-delegate) previously given by *M* to *A*
|
|
- [=result=]: a URI that will be used later to verify that *h* is
|
|
still active and hasn't been revoked, or a JSON object as
|
|
describes above
|
|
|
|
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:
|
|
- [=actor=] is team *A*
|
|
- [=context=] (i.e. the resource) is same as *g*'s [=context=]
|
|
- [=target=] is team *T*
|
|
- [=object=] (i.e. the granted role) is the same as
|
|
*g*'s [=object=]
|
|
- [=allows=] is a single value [=distribute=]
|
|
- [=delegates=] is activity *g*
|
|
- [=capability=]: *(optional)* Specifies a
|
|
[delegate Grant](#grant-delegate) previously given by *T* to *A*
|
|
|
|
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:
|
|
- [=actor=] is team *A*
|
|
- [=context=] (i.e. the resource) is same as *g*'s [=context=]
|
|
- [=target=] is actor *M*
|
|
- [=object=] (i.e. the granted role) is the lower-access role
|
|
among *g*'s [=object=] and *p*
|
|
- [=allows=] is a single value [=invoke=]
|
|
- [=delegates=] is activity *g*
|
|
- [=capability=]: *(optional)* Specifies a
|
|
[delegate Grant](#grant-delegate) previously given by *M* to *A*
|
|
|
|
3. Team *A* MUST NOT make any other delegations of *g*, and SHOULD NOT
|
|
try to invoke it
|
|
|
|
#### Revoking a Grant #### {#s2s-revoke}
|
|
|
|
At any point after an actor *A* publishes a [[#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
|
|
[[fep-8b32|integrity proof]]
|
|
|
|
Additional requirements:
|
|
|
|
- Implementations displaying a `Revoke` activity or an interpretation of it in
|
|
a human interface MUST examine the `Revoke`'s [=object=] property if it is
|
|
present, check if any of the `Grant`s listed are delegations, and communicate
|
|
that detail in the human interface
|
|
|
|
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 `Grant`s 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](#extending-a-delegation-chain) for
|
|
more information.
|
|
|
|
#### Verifying an invocation #### {#s2s-grant-flow}
|
|
|
|
A [previous section](#s2s-grant-simple) described *direct* usage of
|
|
[=Grant=]s, 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:
|
|
|
|
- The *resource actor* passes access to a *target actor*, allowing (or
|
|
requesting) the *target actor* to pass this access (or reduced access) on to
|
|
more actors
|
|
- If authorized by the delegation, those actors may further pass on the access
|
|
(possibly reduced)
|
|
- Eventually, an actor that received such a delegation may use it to access the
|
|
resource
|
|
|
|
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*
|
|
|
|
Issue: "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*
|
|
|
|
Issue: "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=]
|
|
- If it's [=gatherAndConvey=]:
|
|
1. *g*'s [=target=] MUST be a [=Project=]
|
|
- If it's [=distribute=]:
|
|
1. *g*'s [=target=] MUST be a [=Team=]
|
|
2. *h*'s [=allows=] MUST be either [=distribute=]
|
|
or [=invoke=]
|
|
|
|
At this point, activity *a* is considered authorized, and the requested action
|
|
may be performed.
|
|
|
|
#### Identifying resources and their managing actors #### {#manager}
|
|
|
|
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:
|
|
|
|
- Either the resource *R* isn't an actor (i.e. doesn't have an [=inbox=]) but
|
|
does specify which actor manages it via the [=managedBy=] property;
|
|
- Or the resource *R* is an actor, i.e. it has an [=inbox=] (it doesn't have to
|
|
specify [=managedBy=], but if it does, then it MUST refer to itself)
|
|
|
|
Therefore any object that wishes to be specified as the [=context=] of a
|
|
[=Grant=] MUST either be an actor or be [=managedBy=] an
|
|
actor.
|
|
|
|
#### 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:
|
|
|
|
- *g*'s [=target=] is actor *A*
|
|
- *g*'s [=context=] is either the resource *r*, or a resource in which *r* is
|
|
contained, or the actor that [=managedBy|manages=] *r*
|
|
- *g*'s [=object=] is a [=Role=] that permits the kind of operation
|
|
that actor *A* is requesting to do on resource *r*
|
|
- *g*'s [=allows=] is [=invoke=]
|
|
- *g*'s [=startTime=] <= now < *g*'s [=endTime=]
|
|
|
|
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](#manager) instructions for
|
|
determining who that actor is.
|
|
|
|
#### 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=].
|
|
|
|
### Granting access ### {#granting-access}
|
|
|
|
#### 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](#publishing)).
|
|
|
|
If *R* is a [=Project=] or a [=Team=], additional steps occur:
|
|
|
|
1. *A*, seeing *R*'s Grant, publishes a [[#grant-delegate|delegate-Grant]]
|
|
in which the [=target=] is *R* and [=capability=] is *R*'s Grant
|
|
2. From now on, whenever *R* wishes to
|
|
[[#extending-a-delegation-chain|extend a Grant chain]] to *A*, it uses
|
|
*A*'s delegate-Grant as the [=capability=]
|
|
|
|
#### 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 [[#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](#s2s-grant-flow)
|
|
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
|
|
[[#Grant]] for more details on the properties) where:
|
|
- [=object=] is the `Invite`'s [=instrument=]
|
|
- [=context=] is the `Invite`'s [=target=], which is resource *R*
|
|
- [=target=] is the `Invite`'s [=object=], which is actor *B*
|
|
- [=fulfills=] is the `Invite`
|
|
- [=allows=] is [=invoke=]
|
|
- [=delegates=] isn't specified
|
|
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 [[#grant-delegate|delegate-Grant]]
|
|
in which the [=target=] is *R* and [=capability=] is *R*'s Grant
|
|
2. From now on, whenever *R* wishes to
|
|
[[#extending-a-delegation-chain|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*.
|
|
|
|
#### 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 [[#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](#s2s-grant-flow)
|
|
2. Publishes and delivers a [=Grant=] activity (see
|
|
[[#Grant]] for more details on the
|
|
properties) where:
|
|
- [=object=] is the `Join`'s [=instrument=]
|
|
- [=context=] is the `Join`'s [=object=], which is resource *R*
|
|
- [=target=] is the `Join`'s [=actor=], which is actor *A*
|
|
- [=fulfills=] is the `Join`
|
|
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 [[#grant-delegate|delegate-Grant]]
|
|
in which the [=target=] is *R* and [=capability=] is *R*'s Grant
|
|
2. From now on, whenever *R* wishes to
|
|
[[#extending-a-delegation-chain|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 [[#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](#s2s-grant-flow)
|
|
2. Verifies that the `Accept`'s [=object=] specifies the `Join`
|
|
3. Publishes and delivers a [=Grant=] activity (see
|
|
[[#Grant]] for more details on the properties) where:
|
|
- [=object=] is the `Join`'s [=instrument=]
|
|
- [=context=] is the `Join`'s [=object=], which is resource *R*
|
|
- [=target=] is the `Join`'s [=actor=], which is actor *A*
|
|
- [=fulfills=] is the `Join`
|
|
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 [[#grant-delegate|delegate-Grant]]
|
|
in which the [=target=] is *R* and [=capability=] is *R*'s Grant
|
|
2. From now on, whenever *R* wishes to
|
|
[[#extending-a-delegation-chain|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:
|
|
|
|
- [=object=] specifies the `Join` that actor *A* sent
|
|
- [=capability=] is the `Grant` mentioned in step 2, authorizing
|
|
actor *B* to approve or deny Joins
|
|
|
|
If the *resource actor* of *R* receives the `Reject`:
|
|
|
|
1. It MUST verify the `Reject` is authorized, as described above in
|
|
[Verifying an invocation](#s2s-grant-flow)
|
|
2. it MUST verify that the `Reject`'s [=object=] specifies the `Join`
|
|
2. 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.
|
|
|
|
### Revoking access
|
|
|
|
#### 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
|
|
[[#Grant]]s 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 [[#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](#s2s-grant-flow)
|
|
2. Verifies that actor *B* indeed has active `Grant`s for accessing
|
|
resource *R*
|
|
3. Marks those Grants as disabled in its internal state
|
|
4. Publishes and delivers a [[#Revoke]] activity, as described
|
|
above in [Revoking a Grant](#s2s-revoke), 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*.
|
|
|
|
#### 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
|
|
[[#Grant]]s 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 [[#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 `Grant`s for accessing
|
|
resource *R*
|
|
2. Marks those Grants as disabled in its internal state
|
|
3. Publishes and delivers a [[#Revoke]] activity, as described
|
|
above in [Revoking a Grant](#s2s-revoke), 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*.
|
|
|
|
#### Requesting to disable specific Grants using Undo
|
|
|
|
When an actor *A* wishes to deactivate a specific [[#Grant]] activity
|
|
(or multiple `Grant`s), 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 [[#undo-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](#s2s-grant-flow)
|
|
2. Verifies that actor *B* indeed has all the active `Grant`s for accessing
|
|
resource *R*, that are listed as [=object=]s of the `Undo` (if more than
|
|
one `Grant` is listed, the [=target=] of all the `Grant`s MUST be
|
|
identical)
|
|
3. Marks all of those Grants as disabled in its internal state
|
|
4. Publishes and delivers a [[#Revoke]] activity, at least to
|
|
actors *A* and *B*, as described above in
|
|
[Revoking a Grant](#s2s-revoke), where:
|
|
- [=object=] MUST specify all the deactivated `Grant`s
|
|
- [=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*.
|
|
|
|
### Example
|
|
|
|
Aviva creates a new [=Repository=] for her 3D Tree Growth
|
|
Simulation software:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
The newly created *treesim* `Repository` automatically sends back a `Grant` to
|
|
Aviva, allowing her full access to the repo:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Aviva can now use this `Grant`, e.g. to update the repo's description text:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Aviva wants to keep track of events related to the *treesim* repo:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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",
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Aviva can invite Luke to have access to the *treesim* repo:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
And it appears that Luke accepts the invitation:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Seeing the `Invite` and the `Accept`, the *treesim* repo sends Luke a `Grant`
|
|
giving him the access that Aviva offered, and which he accepted:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Luke can now use this `Grant`, e.g. to delete some old obsolete branch of the
|
|
*treesim* repo:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Celine requests to have developer access to the *treesim* repo:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Aviva sees the `Join` request, talks with Celine and decides to approve her
|
|
request:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Seeing the `Join` and the `Accept`, the *treesim* repo sends Celine a `Grant`
|
|
giving her the access that she requested, and which Aviva approved:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
Celine can now use this `Grant` to access the *treesim* repo.
|
|
|
|
## Adding and Removing Team Members
|
|
|
|
Minimal required role: [=admin=]
|
|
|
|
This is done using the processes described in the [[#granting-access]] section.
|
|
In particular, once the new member *M* sends the team *T* the
|
|
[[#grant-delegate|delegate-Grant]], *T* SHOULD use it to send *M*
|
|
[[#extending-a-delegation-chain|extension-Grants]] of:
|
|
|
|
- Every authorized (by a valid delegate-Grant) Grant whose [=allows=] is
|
|
[=distribute=], that *T* has received from any of its parent teams
|
|
- Every authorized (by a valid delegate-Grant) Grant whose [=allows=] is
|
|
[=distribute=], that *T* has received from any of the projects it has
|
|
direct access to
|
|
- Every authorized (by a valid delegate-Grant) Grant whose [=allows=] is
|
|
[=distribute=], that *T* has received from any of the components it has
|
|
direct access to
|
|
|
|
## Adding and Removing Project Members
|
|
|
|
Minimal required role: [=admin=]
|
|
|
|
This is done using the processes described in the [[#granting-access]] section.
|
|
In particular, once the new member *M* sends the project *J* the
|
|
[[#grant-delegate|delegate-Grant]], *J* SHOULD use it to send *M*
|
|
[[#extending-a-delegation-chain|extension-Grants]] of:
|
|
|
|
- Every authorized (by a valid delegate-Grant) Grant whose [=allows=] is
|
|
[=gatherAndConvey=], that *J* has received from any of its child projects
|
|
- Every authorized (by a valid delegate-Grant) Grant whose [=allows=] is
|
|
[=gatherAndConvey=], that *J* has received from any of its components
|
|
|
|
## 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
|
|
[[#s2s-grant-flow|invocation verification process]].
|
|
|
|
### Adding a component to a project - component side
|
|
|
|
Assuming:
|
|
|
|
- A project *P*
|
|
- A component *C*
|
|
- A person with admin access to *C*, Alice
|
|
- A person with admin access to *P*, Bob
|
|
|
|
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:
|
|
- [=object=] is the Add's URI
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes admin access to
|
|
*P*
|
|
4. *P*, seeing the previous 3 activities and authorizing Bob's Accept,
|
|
publishes a [[#grant-delegate|delegate-Grant]] in which the [=target=] is
|
|
*C*
|
|
5. *C*, seeing *P*'s Grant, sends a [[#start-grant-chain|start-Grant]] where:
|
|
- [=actor=] and [=context=] specify *C*
|
|
- [=target=] specifies *P*
|
|
- [=object=] specifies the role specified in the Add's [=instrument=]
|
|
- [=allows=] is [=gatherAndConvey=]
|
|
- [=capability=] is the URI of the delegate-Grant
|
|
6. *P*, seeing and authorizing *C*'s Grant, now
|
|
[[#extending-a-delegation-chain|extends the Grant chain]] as relevant, to
|
|
its members and parent projects
|
|
|
|
### Removing a component from a project - component side
|
|
|
|
Assuming:
|
|
|
|
- A project *P* with a component *C*
|
|
- A person with admin access to *C*, Alice
|
|
|
|
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:
|
|
- [=object=] is *C*
|
|
- [=origin=] is the URI of *P*'s [=components=] collection
|
|
- [=tag=] is *C*
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes admin access to
|
|
*C*
|
|
2. *C*, seeing and authorizing the Remove, publishes a [=Revoke=] where
|
|
[=object=] specifies the active [[#start-grant-chain|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 [[#grant-delegate|delegate-Grant]] it had
|
|
sent to *C* (and *P* now considers that Grant revoked and no longer
|
|
considers *C* a component of it)
|
|
|
|
### Adding a component to a project - project side
|
|
|
|
Assuming:
|
|
|
|
- A project *P*
|
|
- A component *C*
|
|
- A person with admin access to *C*, Alice
|
|
- A person with admin access to *P*, Bob
|
|
|
|
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:
|
|
- [=object=] is the Invite's URI
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes admin access to
|
|
*C*
|
|
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 [[#grant-delegate|delegate-Grant]] in
|
|
which the [=target=] is *C*
|
|
6. *C*, seeing *P*'s Grant, sends a [[#start-grant-chain|start-Grant]] where:
|
|
- [=actor=] and [=context=] specify *C*
|
|
- [=target=] specifies *P*
|
|
- [=object=] specifies the role specified in the Invite's [=instrument=]
|
|
- [=allows=] is [=gatherAndConvey=]
|
|
- [=capability=] is the URI of the delegate-Grant
|
|
7. *P*, seeing and authorizing *C*'s Grant, now
|
|
[[#extending-a-delegation-chain|extends the Grant chain]] as relevant, to
|
|
its members and parent projects
|
|
|
|
### Removing a component from a project - project side
|
|
|
|
Assuming:
|
|
|
|
- A project *P* with a component *C*
|
|
- A person with admin access to *P*, Bob
|
|
|
|
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:
|
|
- [=object=] is *C*
|
|
- [=origin=] is the URI of *P*'s [=components=] collection
|
|
- [=tag=] is *P*
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes admin access to
|
|
*P*
|
|
3. *P*, seeing and authorizing the Remove, publishes a [=Revoke=] where
|
|
[=object=] specifies the active [[#grant-delegate|delegate-Grant]] it had
|
|
sent to *C* (and *P* now considers that Grant revoked and no longer
|
|
considers *C* a component of it)
|
|
2. *C*, seeing the Remove and the Revoke, publishes a [=Revoke=] where
|
|
[=object=] specifies the active [[#start-grant-chain|start-Grant]] it had
|
|
sent to *P* (and *C* now considers that Grant revoked and no longer
|
|
considers itself as a component of *P*)
|
|
|
|
## Adding and Removing Children and Parents to Projects and Teams
|
|
|
|
Minimal required role: [=admin=]
|
|
|
|
Like most cooperation between ForgeFed actors, the parent-child link is based
|
|
on mutual consent:
|
|
|
|
- To form the link, both projects/teams must explicitly agree to it
|
|
- To remove the link, it's enough for one of them to withdraw its consent
|
|
|
|
Parent-child links are a part of the Grant delegation system, in which the flow
|
|
is:
|
|
|
|
- Components initiate delegation, to:
|
|
- The projects they belong to
|
|
- Teams which have access to them
|
|
- Projects delegate to their:
|
|
- Direct collaborators
|
|
- Parent projects
|
|
- Teams which have access to them
|
|
- Teams delegate to their:
|
|
- Direct collaborators
|
|
- Child teams
|
|
|
|
Once a parent-child link is formed, delegations start happening through the
|
|
link. This is described in more detail below.
|
|
|
|
Each delegation within this flow therefore has a "source" side, which sends the
|
|
delegation, and a "destination" side, which receives it and possibly delegates
|
|
it further.
|
|
|
|
In a parent-child link, therefore, there is a "source" side and a "destination"
|
|
side. For projects, the child is the source and the parent is the destination
|
|
(because projects delegate to their parents, once a link is formed). For teams,
|
|
the parent is the source and the child is the destination (because teams
|
|
delegate to their children, once a link is formed).
|
|
|
|
Since either side of the link can initiate the process, and since delegation
|
|
flow occurs in one direction, there are essentially two versions of the
|
|
process:
|
|
|
|
- Process initiated by the source side (i.e. a project asking to have a parent,
|
|
or a team asking to have a child)
|
|
- Process initiated by the destination side (i.e. a project asking to have a
|
|
child, or a team asking to have a parent)
|
|
|
|
In the [=Add=] activity that initiates the process, the initiating side is
|
|
determined by the [=target=] property. The actor to whom the collection
|
|
specified by [=target=] belongs, is the initiating side. The other side of the
|
|
link is specified in the [=object=] property.
|
|
|
|
Whenever authorization is mentioned below, it SHOULD be done using the
|
|
[[#s2s-grant-flow|invocation verification process]].
|
|
|
|
### Forming a project child-parent link
|
|
|
|
#### Initiated by the parent
|
|
|
|
Assuming:
|
|
|
|
- A project *P*
|
|
- A project *C*
|
|
- A person with admin access to *C*, Celia
|
|
- A person with admin access to *P*, Philip
|
|
|
|
Philip wants *C* to become a child of *P* (which means *P* will be a parent
|
|
of *C*). The exchange of activities SHOULD be as follows:
|
|
|
|
1. Philip sends an [=Add=] activity in which:
|
|
- [=object=] is *C*
|
|
- [=target=] is the URI of *P*'s [=subprojects=] (i.e. children) collection
|
|
- [=instrument=] is the maximal [=Role=] that Philip 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 Philip's admin
|
|
access to *P*
|
|
2. *P*, seeing and authorizing the Add, publishes an [=Accept=] where
|
|
[=object=] is the Add's URI
|
|
3. Celia, seeing the Add and the Accept, sends an [=Accept=] where:
|
|
- [=object=] is the Add's URI
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes Celia's admin
|
|
access to *C*
|
|
4. *C*, seeing the Add and authorizing Celia's Accept, publishes an [=Accept=]
|
|
where [=object=] is the Add's URI
|
|
5. *P*, seeing *C*'s Accept, publishes a [[#grant-delegate|delegate-Grant]] in
|
|
which the [=target=] is *C*, and now considers *C* a child and lists *C* in
|
|
the [=subprojects=] collection
|
|
6. *C*, seeing *P*'s Grant, now considers *P* a parent and lists *P* in the
|
|
[=context=] (i.e. parents) collection
|
|
|
|
Now *C* sends delegation Grant activities, see the [[#deleg-step-j]] section
|
|
below.
|
|
|
|
#### Initiated by the child
|
|
|
|
Assuming:
|
|
|
|
- A project *P*
|
|
- A project *C*
|
|
- A person with admin access to *C*, Celia
|
|
- A person with admin access to *P*, Philip
|
|
|
|
Celia wants *C* to become a child of *P* (which means *P* will be a parent
|
|
of *C*). The exchange of activities SHOULD be as follows:
|
|
|
|
1. Celia sends an [=Add=] activity in which:
|
|
- [=object=] is *P*
|
|
- [=target=] is the URI of *C*'s [=context=] (i.e. parents) collection
|
|
- [=instrument=] is the maximal [=Role=] that Celia 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 Celia's admin
|
|
access to *C*
|
|
2. *C*, seeing and authorizing the Add, publishes an [=Accept=] where
|
|
[=object=] is the Add's URI
|
|
3. Philip, seeing the Add and the Accept, sends an [=Accept=] where:
|
|
- [=object=] is the Add's URI
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes Philip's admin
|
|
access to *P*
|
|
4. *P*, seeing the Add, seeing *C*'s Accept, and authorizing Philip's Accept,
|
|
publishes a [[#grant-delegate|delegate-Grant]] in which the [=target=] is
|
|
*C*, and now considers *C* a child and lists *C* in the [=subprojects=]
|
|
collection
|
|
5. *C*, seeing *P*'s Grant, now considers *P* a parent and lists *P* in the
|
|
[=context=] (i.e. parents) collection
|
|
|
|
Now *C* sends delegation Grant activities, see the [[#deleg-step-j]] section
|
|
below.
|
|
|
|
#### Delegation step #### {#deleg-step-j}
|
|
|
|
*C* now sends Grant activities, which *P* delegates further by sending its own
|
|
Grant activities, as described in [[#extending-a-delegation-chain]], to its
|
|
member humans, member teams, and parent projects.
|
|
|
|
1. *C* sends a [[#start-grant-chain|start-Grant]] where:
|
|
- [=actor=] and [=context=] specify *C*
|
|
- [=target=] specifies *P*
|
|
- [=object=] specifies the role specified in the Add's [=instrument=]
|
|
- [=allows=] is [=gatherAndConvey=]
|
|
- [=capability=] is the URI of the delegate-Grant from step 5 or 4 above
|
|
2. For each delegation Grant *g* that *C* has received from its components and
|
|
child projects, *C* sends an
|
|
[[#extending-a-delegation-chain|extension-Grant]] where:
|
|
- [=actor=] specifies *C*
|
|
- [=context=] is identical to *g*'s [=context=]
|
|
- [=target=] specifies *P*
|
|
- [=object=] specifies the lower role among the one specified by the
|
|
Add's [=instrument=], and the one specified by *g*'s [=object=]
|
|
- [=allows=] is [=gatherAndConvey=]
|
|
- [=capability=] is the URI of the delegate-Grant from step 5 or 4
|
|
above
|
|
- [=delegates=] specifies *g*'s URI
|
|
|
|
When *P* sees these Grants, it delegates them further, as mentioned above.
|
|
|
|
In addition, as long as the parent-child link remains active, whenever *C*
|
|
receives a delegation Grant from any of its components or children, it SHOULD
|
|
send *P* an extension-Grant as described above, and *P* SHOULD extend it
|
|
further.
|
|
|
|
### Removing a project child-parent link
|
|
|
|
#### Initiated by the child
|
|
|
|
Assuming:
|
|
|
|
- Projects *P* and *C*, where *P* is a parent of *C*
|
|
- A person with admin access to *C*, Celia
|
|
|
|
Celia wants *C* to stop being a child of *P* (which means *P* will stop
|
|
being a parent of *C*). The exchange of activities SHOULD be as follows:
|
|
|
|
1. Celia sends an [=Remove=] activity in which:
|
|
- [=object=] is *P*
|
|
- [=origin=] is the URI of *C*'s [=context=] (i.e. parents) collection
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes Celia's admin
|
|
access to *C*
|
|
2. *C*, seeing and authorizing the Remove, publishes an [=Accept=] where
|
|
[=object=] is the Add's URI
|
|
3. *P*, seeing the Remove and the Accept, publishes a [=Revoke=] activity in
|
|
which the [=object=] is the delegate-Grant it had sent to *C*, and now
|
|
considers that Grant revoked, and removes *C* from its [=subprojects=]
|
|
collection
|
|
4. *C*, seeing *P*'s Revoke, removes *P* from its [=context=] (i.e. parents)
|
|
collection
|
|
|
|
Now, for each Grant *g* that *P* has published, as a extension of a Grant it
|
|
had received from *C*:
|
|
|
|
1. *P* sends (to *g*'s [=target=]) a [=Revoke=] activity whose [=object=]
|
|
specifies *g*'s URI
|
|
2. *P* considers *g* invalidated
|
|
3. The recipient of the [=Revoke=], if it further published extensions of *g*,
|
|
now similarly publishes Revoke activities and considers those extension
|
|
Grants invalid
|
|
|
|
#### Initiated by the parent
|
|
|
|
Assuming:
|
|
|
|
- Projects *P* and *C*, where *P* is a parent of *C*
|
|
- A person with admin access to *P*, Philip
|
|
|
|
Philip wants *C* to stop being a child of *P* (which means *P* will stop
|
|
being a parent of *C*). The exchange of activities SHOULD be as follows:
|
|
|
|
1. Philip sends an [=Remove=] activity in which:
|
|
- [=object=] is *C*
|
|
- [=origin=] is the URI of *P*'s [=subprojects=] (i.e. children) collection
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes Philip's admin
|
|
access to *P*
|
|
2. *P*, seeing and authorizing the Remove, publishes a [=Revoke=] activity in
|
|
which the [=object=] is the delegate-Grant it had sent to *C*, and now
|
|
considers that Grant revoked, and removes *C* from its [=subprojects=]
|
|
collection
|
|
3. *C*, seeing *P*'s Revoke, removes *P* from its [=context=] (i.e. parents)
|
|
collection, and MAY publish an Accept (whose [=object=] is the Remove) to
|
|
notify its followers
|
|
|
|
Now, for each Grant *g* that *P* has published, as a extension of a Grant it
|
|
had received from *C*:
|
|
|
|
1. *P* sends (to *g*'s [=target=]) a [=Revoke=] activity whose [=object=]
|
|
specifies *g*'s URI
|
|
2. *P* considers *g* invalidated
|
|
3. The recipient of the [=Revoke=], if it further published extensions of *g*,
|
|
now similarly publishes Revoke activities and considers those extension
|
|
Grants invalid
|
|
|
|
### Forming a team child-parent link
|
|
|
|
#### Initiated by the parent
|
|
|
|
Assuming:
|
|
|
|
- A team *P*
|
|
- A team *C*
|
|
- A person with admin access to *C*, Celia
|
|
- A person with admin access to *P*, Philip
|
|
|
|
Philip wants *C* to become a child of *P* (which means *P* will be a parent
|
|
of *C*). The exchange of activities SHOULD be as follows:
|
|
|
|
1. Philip sends an [=Add=] activity in which:
|
|
- [=object=] is *C*
|
|
- [=target=] is the URI of *P*'s [=subteams=] (i.e. children) collection
|
|
- [=instrument=] is the maximal [=Role=] that Philip 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 Philip's admin
|
|
access to *P*
|
|
2. *P*, seeing and authorizing the Add, publishes an [=Accept=] where
|
|
[=object=] is the Add's URI
|
|
3. Celia, seeing the Add and the Accept, sends an [=Accept=] where:
|
|
- [=object=] is the Add's URI
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes Celia's admin
|
|
access to *C*
|
|
4. *C*, seeing the Add, seeing *P*'s Accept, and authorizing Celia's Accept,
|
|
publishes a [[#grant-delegate|delegate-Grant]] in which the [=target=] is
|
|
*P*, and now considers *P* a parent and lists *P* in the [=context=] (i.e.
|
|
parents) collection
|
|
5. *P*, seeing *C*'s Grant, now considers *C* a parent and lists *C* in the
|
|
[=subteams=] collection
|
|
|
|
Now *P* sends delegation Grant activities, see the [[#deleg-step-t]] section
|
|
below.
|
|
|
|
#### Initiated by the child
|
|
|
|
Assuming:
|
|
|
|
- A team *P*
|
|
- A team *C*
|
|
- A person with admin access to *C*, Celia
|
|
- A person with admin access to *P*, Philip
|
|
|
|
Celia wants *C* to become a child of *P* (which means *P* will be a parent
|
|
of *C*). The exchange of activities SHOULD be as follows:
|
|
|
|
1. Celia sends an [=Add=] activity in which:
|
|
- [=object=] is *P*
|
|
- [=target=] is the URI of *C*'s [=context=] (i.e. parents) collection
|
|
- [=instrument=] is the maximal [=Role=] that Celia 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 Celia's admin
|
|
access to *C*
|
|
2. *C*, seeing and authorizing the Add, publishes an [=Accept=] where
|
|
[=object=] is the Add's URI
|
|
3. Philip, seeing the Add and the Accept, sends an [=Accept=] where:
|
|
- [=object=] is the Add's URI
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes Philip's admin
|
|
access to *P*
|
|
4. *P*, seeing the Add, seeing *C*'s Accept, and authorizing Philip's Accept,
|
|
publishes an [=Accept=] where [=object=] is the Add's URI
|
|
5. *C*, seeing *P*'s Accept, publishes a [[#grant-delegate|delegate-Grant]] in
|
|
which the [=target=] is *P*, and now considers *P* a parent and lists *P*
|
|
in the [=context=] (i.e. parents) collection
|
|
6. *P*, seeing *C*'s Grant, now considers *C* a child and lists *C* in the
|
|
[=subteams=] collection
|
|
|
|
Now *P* sends delegation Grant activities, see the [[#deleg-step-t]] section
|
|
below.
|
|
|
|
#### Delegation step #### {#deleg-step-t}
|
|
|
|
*P* now sends Grant activities, which *C* delegates further by sending its own
|
|
Grant activities, as described in [[#extending-a-delegation-chain]], to its
|
|
member humans and child teams.
|
|
|
|
1. *P* sends a [[#start-grant-chain|start-Grant]] where:
|
|
- [=actor=] and [=context=] specify *P*
|
|
- [=target=] specifies *C*
|
|
- [=object=] specifies the role specified in the Add's [=instrument=]
|
|
- [=allows=] is [=distribute=]
|
|
- [=capability=] is the URI of the delegate-Grant from step 4 or 5 above
|
|
2. For each delegation Grant *g* that *P* has received from its projects,
|
|
components or parent teams, *P* sends an
|
|
[[#extending-a-delegation-chain|extension-Grant]] where:
|
|
- [=actor=] specifies *P*
|
|
- [=context=] is identical to *g*'s [=context=]
|
|
- [=target=] specifies *C*
|
|
- [=object=] specifies the lower role among the one specified by the
|
|
Add's [=instrument=], and the one specified by *g*'s [=object=]
|
|
- [=allows=] is [=distribute=]
|
|
- [=capability=] is the URI of the delegate-Grant from step 4 or 5 above
|
|
- [=delegates=] specifies *g*'s URI
|
|
|
|
When *C* sees these Grants, it delegates them further, as mentioned above.
|
|
|
|
In addition, as long as the parent-child link remains active, whenever *P*
|
|
receives a delegation Grant from any of its projects, components or parents, it
|
|
SHOULD send *C* an extension-Grant as described above, and *C* SHOULD extend it
|
|
further.
|
|
|
|
### Removing a team child-parent link
|
|
|
|
#### Initiated by the child
|
|
|
|
Assuming:
|
|
|
|
- Teams *P* and *C*, where *P* is a parent of *C*
|
|
- A person with admin access to *C*, Celia
|
|
|
|
Celia wants *C* to stop being a child of *P* (which means *P* will stop
|
|
being a parent of *C*). The exchange of activities SHOULD be as follows:
|
|
|
|
1. Celia sends an [=Remove=] activity in which:
|
|
- [=object=] is *P*
|
|
- [=origin=] is the URI of *C*'s [=context=] (i.e. parents) collection
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes Celia's admin
|
|
access to *C*
|
|
2. *C*, seeing and authorizing the Remove, publishes a [=Revoke=] activity in
|
|
which the [=object=] is the delegate-Grant it had sent to *P*, and now
|
|
considers that Grant revoked, and removes *P* from its [=context=] (i.e.
|
|
parents) collection
|
|
3. *P*, seeing *C*'s Revoke, removes *C* from its [=subteams=] collection,
|
|
and MAY publish an Accept (whose [=object=] is the Remove) to notify its
|
|
followers
|
|
|
|
Now, for each Grant *g* that *C* has published, as a extension of a Grant it
|
|
had received from *P*:
|
|
|
|
1. *C* sends (to *g*'s [=target=]) a [=Revoke=] activity whose [=object=]
|
|
specifies *g*'s URI
|
|
2. *C* considers *g* invalidated
|
|
3. The recipient of the [=Revoke=], if it further published extensions of *g*,
|
|
now similarly publishes Revoke activities and considers those extension
|
|
Grants invalid
|
|
|
|
#### Initiated by the parent
|
|
|
|
Assuming:
|
|
|
|
- Teams *P* and *C*, where *P* is a parent of *C*
|
|
- A person with admin access to *P*, Philip
|
|
|
|
Philip wants *C* to stop being a child of *P* (which means *P* will stop
|
|
being a parent of *C*). The exchange of activities SHOULD be as follows:
|
|
|
|
1. Philip sends an [=Remove=] activity in which:
|
|
- [=object=] is *C*
|
|
- [=origin=] is the URI of *P*'s [=subteams=] (i.e. parents) collection
|
|
- [=capability=] is the URI of a [=Grant=] that authorizes Philip's admin
|
|
access to *P*
|
|
2. *P*, seeing and authorizing the Remove, publishes an [=Accept=] where
|
|
[=object=] is the Add's URI
|
|
3. *C*, seeing the Remove and the Accept, publishes a [=Revoke=] activity in
|
|
which the [=object=] is the delegate-Grant it had sent to *P*, and now
|
|
considers that Grant revoked, and removes *P* from its [=context=] (i.e.
|
|
parents) collection
|
|
4. *P*, seeing *C*'s Revoke, removes *C* from its [=subteams=] collection
|
|
|
|
Now, for each Grant *g* that *C* has published, as a extension of a Grant it
|
|
had received from *P*:
|
|
|
|
1. *C* sends (to *g*'s [=target=]) a [=Revoke=] activity whose [=object=]
|
|
specifies *g*'s URI
|
|
2. *C* considers *g* invalidated
|
|
3. The recipient of the [=Revoke=], if it further published extensions of *g*,
|
|
now similarly publishes Revoke activities and considers those extension
|
|
Grants invalid
|
|
|
|
# Actor Interface
|
|
|
|
Issue: 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 `Announce`ing (or custom `Forward`ing) those
|
|
activities.
|
|
|
|
## Person ## {#person-iface}
|
|
|
|
## Team ## {#team-iface}
|
|
|
|
## Project ## {#project-iface}
|
|
|
|
## Repository ## {#repo-iface}
|
|
|
|
## TicketTracker ## {#tt-iface}
|
|
|
|
## PatchTracker ## {#pt-iface}
|
|
|
|
# Client to Server Interactions
|
|
|
|
Issue: 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.
|
|
|
|
## 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.
|
|
|
|
## 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.
|
|
|
|
# Actor and Resource Representation
|
|
|
|
Issue: 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).
|
|
|
|
## Comment ## {#Comment}
|
|
|
|
To represent a comment, e.g. a comment on a ticket or a merge request, use the
|
|
ActivityPub [=Note=] type.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Team Membership
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Team
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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 [=Team=]s 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=])
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Project ## {#Project}
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## 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:
|
|
|
|
<pre class=simpledef>
|
|
[=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)
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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!"
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## 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:
|
|
|
|
<pre class=simpledef>
|
|
[=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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Repository ## {#Repository}
|
|
|
|
To represent a version control repository, use the ForgeFed
|
|
[=Repository=] type.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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>"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Push ## {#Push}
|
|
|
|
To represent an event of [=Commit=]s being pushed to a
|
|
[=Repository=], use a ForgeFed [=Push=] activity.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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 [=Commit=]s 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).
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Ticket ## {#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:
|
|
|
|
<pre class=simpledef>
|
|
[=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 [=Ticket=]s which depend on this ticket
|
|
[=dependencies=]:
|
|
Collection of [=Ticket=]s 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
|
|
</pre>
|
|
|
|
There's an important distinction between these two kinds of tickets:
|
|
|
|
- Task:
|
|
A work item which tracks some task to be done, in which the task and its
|
|
results are described in text, but the work itself is done elsewhere. This
|
|
is often called "issue" in software project hosting platforms. Tasks are
|
|
general-purpose work items that aren't specific to software development or
|
|
software projects.
|
|
- Merge request:
|
|
A work item that includes a proposal or request to apply some specific
|
|
changes to a specific [=Repository=]. The work requested by the work item
|
|
is to review and (decide whether to) merge the proposed patches to the
|
|
repository. This kind of work item is often called "Pull Request" or "Merge
|
|
Request" on software project hosting platforms.
|
|
|
|
### Task / Issue
|
|
|
|
A task is represented as a [[#Ticket]] as described above, with the
|
|
following additional requirements:
|
|
|
|
- [=context=] is the [=TicketTracker=] to which this task belongs
|
|
- There is no [=attachment=] of type [=Offer=] (but there may be attachment of other types)
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Merge Request / Pull Request
|
|
|
|
A merge request is represented as a [[#Ticket]] as described above, with
|
|
the following additional requirements:
|
|
|
|
- [=context=] is the [=PatchTracker=] to which this merge request belongs
|
|
- There is no [=attachment=] of type [=Offer=] (but there may be attachment of other types)
|
|
- There is exactly one [=attachment=] of type [=Offer=], as described below
|
|
- There MAY be more [=attachment=]s, but they MUST NOT be of type [=Offer=]
|
|
|
|
In that special [=attachment=] of type [=Offer=]:
|
|
|
|
- [=type=] is [=Offer=]
|
|
- [=origin=] is the [=Repository=] or [=Branch=] from which the proposed changes are proposed to be merged into the target repository/branch
|
|
- [=target=] is the [=Repository=] or [=Branch=] into which the changes are proposed to be merged
|
|
- [=object=] is an [=OrderedCollection=] of [=Patch=]es in reverse
|
|
chronological order, in which, in addition to standard [=OrderedCollection=]
|
|
properties:
|
|
- [=context=] is (the [=id=] of) the [[#Ticket]]
|
|
- [=previousVersions=] is a list of previous versions
|
|
of the merge request's proposed changes, i.e. previous versions of this
|
|
[=OrderedCollection=]; each of those uses
|
|
[=currentVersion=] to point back to this latest
|
|
version
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Patch
|
|
|
|
<pre class=simpledef>
|
|
[=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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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..."
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
# Access Control
|
|
|
|
## Giving Access
|
|
|
|
### Invite ### {#Invite}
|
|
|
|
To offer some actor access to a shared resource (such as a repository or a
|
|
ticket tracker), use an ActivityPub [=Invite=] activity.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Join ### {#Join}
|
|
|
|
To request access to a shared resource, use an ActivityPub [=Join=] activity.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Grant ### {#Grant}
|
|
|
|
To give some actor access to a shared resource, use a ForgeFed
|
|
[=Grant=] activity.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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 [=Create=]s 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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Canceling Access
|
|
|
|
### Remove ### {#Remove}
|
|
|
|
To disable an actor's membership in a shared resource, invalidating their
|
|
access to it, use an ActivityPub [=Remove=] activity.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Leave ### {#Leave}
|
|
|
|
To withdraw your consent for membership in a shared resource, invalidating
|
|
your access to it, use an ActivityPub [=Leave=] activity.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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)
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Revoke ### {#Revoke}
|
|
|
|
Another activity that can be used for disabling access is [=Revoke=].
|
|
While [[#Remove]] and [[#Leave]] are meant for undoing the effects
|
|
of [[#Invite]] and [[#Join]], `Revoke` is provided as an opposite of
|
|
[[#Grant]]. See the Behavior specification for more information about the
|
|
usage of these different activity types in revocation of access to shared
|
|
resources.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=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 [[#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 [[fep-8b32|integrity proof]].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Undo a Grant ### {#undo-grant}
|
|
|
|
The Behavior spec describes flows in which the [[#Revoke]] activity is
|
|
used by resources (more accurately, by the actors managing them) to announce
|
|
that they're disabling [[#Grant]]s that they previously sent. To allow for
|
|
a clear distinction, another activity is provided here, for *other* actors to
|
|
*request* the revocation of specific [[#Grant]]s: The ActivityPub [=Undo=]
|
|
activity.
|
|
|
|
It's likely that `Grant`s 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 `Grant`s may be required
|
|
for ensuring and maintaining system security, therefore `Undo` is provided here
|
|
as well.
|
|
|
|
Properties:
|
|
|
|
<pre class=simpledef>
|
|
[=type=]:
|
|
[=Undo=]
|
|
[=actor=]:
|
|
The actor (person, bot, etc.) that is revoking access
|
|
[=object=]:
|
|
specific [[#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
|
|
</pre>
|
|
|
|
# Vocabulary # {#vocab}
|
|
|
|
## 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:
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld>
|
|
"@context": [
|
|
"https://www.w3.org/ns/activitystreams",
|
|
"https://forgefed.org/ns"
|
|
]
|
|
</xmp>
|
|
</div>
|
|
|
|
### Activities
|
|
|
|
#### Related to access control
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Grant</dfn>
|
|
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=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=7>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Revoke</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=7>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
#### Related to repositories
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Push</dfn>
|
|
URI: https://forgefed.org/ns#Push
|
|
Extends: [=Activity=]
|
|
Description:
|
|
Indicates that new content has been pushed to the [=Repository=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=7>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Actors
|
|
|
|
#### Software development components
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Repository</dfn>
|
|
URI: https://forgefed.org/ns#Repository
|
|
Extends: [=Object=]
|
|
Description:
|
|
Represents a version control system repository.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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>"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>TicketTracker</dfn>
|
|
URI: https://forgefed.org/ns#TicketTracker
|
|
Extends: [=Object=]
|
|
Description:
|
|
Represents a [[wikipedia-ticket-tracker|ticket tracker]], i.e. a project
|
|
managing a list of work items.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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>"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>PatchTracker</dfn>
|
|
URI: https://forgefed.org/ns#PatchTracker
|
|
Extends: [=Object=]
|
|
Description:
|
|
Represents a tracker of [[wikipedia-pr|merge requests]], i.e. a project
|
|
managing a list of patches or branches submitted as proposed changes to a
|
|
given [=Repository=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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>"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
#### Organizational structure tools
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Project</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Team</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Objects
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>CapabilityUsage</dfn>
|
|
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 `Grant`s: 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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=7>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Role</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=7>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Branch</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=7>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Commit</dfn>
|
|
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`.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=7>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Patch</dfn>
|
|
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=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=7>
|
|
{
|
|
"@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..."
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>TicketDependency</dfn>
|
|
URI: https://forgefed.org/ns#TicketDependency
|
|
Extends: [=Relationship=]
|
|
Description:
|
|
Represents a relationship between 2 [=Ticket=]s, 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=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=6>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>Ticket</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=6>
|
|
{
|
|
"@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
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Properties
|
|
|
|
### General-purpose
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>earlyItems</dfn>
|
|
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`.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=14>
|
|
{
|
|
"@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"
|
|
]
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>previousVersions</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=10>
|
|
{
|
|
"@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"
|
|
]
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>currentVersion</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=10>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Projects
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>components</dfn>
|
|
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 [[#Repository]]
|
|
Description:
|
|
Identifies a [=Collection=] listing actors whose services and resources are
|
|
considered to be components of this project.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=11>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>subprojects</dfn>
|
|
URI: https://forgefed.org/ns#subprojects
|
|
Domain: [=Project=]
|
|
Range: [=Collection=] of [=Project=]s
|
|
Functional: Yes
|
|
Inverse of:
|
|
None, but see the usage of [=context=] in [[#Project]]
|
|
Description:
|
|
Identifies a [=Collection=] listing the subprojects of this [=Project=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=11>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Team membership and nesting
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>hasMember</dfn>
|
|
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=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>members</dfn>
|
|
URI: https://forgefed.org/ns#members
|
|
Domain: [=Team=]
|
|
Range:
|
|
[=Collection=] of [=Relationship=]s 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=] [=Relationship=]s.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>subteams</dfn>
|
|
URI: https://forgefed.org/ns#subteams
|
|
Domain: [=Team=]
|
|
Range: [=Collection=] of [=Team=]s.
|
|
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 [=Repository=]s).
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
### Ticket assignment and resolution
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>assignedTo</dfn>
|
|
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=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>isResolved</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>resolvedBy</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>resolved</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
### Ticket dependencies
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>dependsOn</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>dependedBy</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>dependencies</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>dependants</dfn>
|
|
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".
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
### Repository cloning
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>cloneUri</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
### Repository pushing
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>hashBefore</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>hashAfter</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
### Commits and branches
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export lt="prop-repository">repository</dfn> (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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>description</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=14>
|
|
{
|
|
"@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!"
|
|
},
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>committedBy</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>hash</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>committed</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>filesAdded</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>filesModified</dfn>
|
|
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=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>filesRemoved</dfn>
|
|
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=].
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>ref</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=11>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Activity addressing
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export lt="prop-team">team</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
A repository *https://dev.example/aviva/treesim*:
|
|
|
|
<xmp highlight=json-ld line-highlight=20>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
|
|
The repository's team *https://dev.example/aviva/treesim/team*:
|
|
|
|
<xmp highlight=json-ld line-highlight=4>
|
|
{
|
|
"@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"
|
|
]
|
|
}
|
|
</xmp>
|
|
|
|
</div>
|
|
|
|
### Tracker linking
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>ticketsTrackedBy</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=10>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>tracksTicketsFor</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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"
|
|
]
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>sendPatchesTo</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=10>
|
|
{
|
|
"@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"
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>tracksPatchesFor</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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"
|
|
]
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Repository forking
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>forkedFrom</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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/"
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>forks</dfn>
|
|
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 [=Repository=]s 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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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",
|
|
}
|
|
]
|
|
},
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
### Access control
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>fulfills</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>allows</dfn>
|
|
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 `Grant`s 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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>capability</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>managedBy</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
{
|
|
"@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
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>delegates</dfn>
|
|
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`.
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=9>
|
|
TODO
|
|
</xmp>
|
|
</div>
|
|
|
|
### Repository mirroring
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>mirrors</dfn>
|
|
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).
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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/"
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>mirroredBy</dfn>
|
|
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).
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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/"
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>mirrorsTo</dfn>
|
|
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)
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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/"
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>mirroredFrom</dfn>
|
|
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).
|
|
</pre>
|
|
|
|
<div class=example>
|
|
<xmp highlight=json-ld line-highlight=8>
|
|
{
|
|
"@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/"
|
|
}
|
|
}
|
|
</xmp>
|
|
</div>
|
|
|
|
## Values
|
|
|
|
### Capability uses
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>gatherAndConvey</dfn>
|
|
URI: https://forgefed.org/ns#gatherAndConvey
|
|
Type: [=CapabilityUsage=]
|
|
Conditions:
|
|
<ul>
|
|
<li>
|
|
The `Grant`'s [=target=] MUST be a [=Project=]
|
|
</li>
|
|
<li>
|
|
It may delegate the `Grant`, allowing only `gatherAndConvey`, to
|
|
parent projects
|
|
</li>
|
|
<li>
|
|
It may delegate the `Grant`, allowing only `distribute`, to teams
|
|
to which it allows to access it
|
|
</li>
|
|
<li>
|
|
It may delegate the `Grant`, allowing `invoke` only, to people to
|
|
which it allows to access it
|
|
</li>
|
|
</ul>
|
|
</pre>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>distribute</dfn>
|
|
URI: https://forgefed.org/ns#distribute
|
|
Type: [=CapabilityUsage=]
|
|
Conditions:
|
|
<ul>
|
|
<li>
|
|
The `Grant`'s [=target=] MUST be a [=Team=]
|
|
</li>
|
|
<li>
|
|
It may delegate the `Grant`, allowing `distribute` only, to its
|
|
subteams
|
|
</li>
|
|
<li>
|
|
It may delegate the `Grant`, allowing `invoke` only, to its members
|
|
</li>
|
|
</ul>
|
|
</pre>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>invoke</dfn>
|
|
URI: https://forgefed.org/ns#invoke
|
|
Type: [=CapabilityUsage=]
|
|
Conditions:
|
|
<ul>
|
|
<li>
|
|
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=]
|
|
</li>
|
|
</ul>
|
|
</pre>
|
|
|
|
### Roles
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>visit</dfn>
|
|
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
|
|
</pre>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>report</dfn>
|
|
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](#opening-issue),
|
|
[submit a PR](#opening-mr),
|
|
[create comments](#commenting)
|
|
and discussion threads,
|
|
edit public wikis,
|
|
submit PR reviews.
|
|
</pre>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>triage</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>write</dfn>
|
|
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](#pushing),
|
|
create/edit/run/cancel CI recipes,
|
|
manage releases,
|
|
publish packages,
|
|
create web IDE coding sessions.
|
|
</pre>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>maintain</dfn>
|
|
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.
|
|
</pre>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>admin</dfn>
|
|
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](#managing-access),
|
|
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.
|
|
</pre>
|
|
|
|
<pre class=simpledef>
|
|
Name: <dfn dfn export>delegate</dfn>
|
|
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=])
|
|
</pre>
|
|
|
|
<pre class=biblio>
|
|
{
|
|
"wikipedia-ticket-tracker": {
|
|
"href": "https://en.wikipedia.org/wiki/Issue_tracking_system",
|
|
"title": "Wikipedia: Issue tracking system"
|
|
},
|
|
"wikipedia-pr": {
|
|
"href": "https://en.wikipedia.org/wiki/Distributed_version_control#Pull_requests",
|
|
"title": "Wikipedia: Pull requests"
|
|
},
|
|
"fep-8b32": {
|
|
"href": "https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-8b32.md",
|
|
"title": "FEP-8b32: Object Integrity Proofs"
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<pre class=anchors>
|
|
urlPrefix: https://www.w3.org/TR/xmlschema11-2/#; type: dfn; spec: xmlschema11-2
|
|
text: dateTime
|
|
urlPrefix: http://purl.org/dc/terms/; type: dfn; spec: dcterms
|
|
text: created
|
|
urlPrefix: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-; type: dfn; spec: activitystreams-vocabulary
|
|
text: Accept
|
|
text: Add
|
|
text: Create
|
|
text: Invite
|
|
text: Join
|
|
text: Leave
|
|
text: Offer
|
|
text: Reject
|
|
text: Remove
|
|
text: Undo
|
|
urlPrefix: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-; type: dfn; spec: activitystreams-vocabulary
|
|
text: Activity
|
|
text: Collection
|
|
text: Image
|
|
text: Link
|
|
text: Note
|
|
text: Object
|
|
text: OrderedCollection
|
|
text: Person
|
|
urlPrefix: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-; type: dfn; spec: activitystreams-vocabulary
|
|
text: actor
|
|
text: attachment
|
|
text: attributedTo
|
|
text: content
|
|
text: context
|
|
text: duration
|
|
text: endTime
|
|
text: id
|
|
text: inbox
|
|
text: inReplyTo
|
|
text: instrument
|
|
text: items
|
|
text: mediaType
|
|
text: name
|
|
text: object
|
|
text: orderedItems
|
|
text: origin
|
|
text: published
|
|
text: relationship
|
|
text: replies
|
|
text: result
|
|
text: source
|
|
text: startTime
|
|
text: subject
|
|
text: summary
|
|
text: tag
|
|
text: target
|
|
text: to
|
|
text: type
|
|
urlPrefix: https://www.w3.org/TR/activitypub/#; type: dfn; spec: activitypub
|
|
text: followers
|
|
</pre>
|