-
Notifications
You must be signed in to change notification settings - Fork 13
/
spec.bs
5305 lines (4685 loc) · 188 KB
/
spec.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<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*