1
0
mirror of https://codeberg.org/fediverse/fep.git synced 2026-08-08 13:35:51 +00:00
Files
fep/search/search_index.json
T
2026-03-15 18:39:44 +00:00

1 line
1.5 MiB
Plaintext

{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Fediverse Enhancement Proposals","text":"<ul> <li> Final Proposals</li> <li> Draft Proposals</li> </ul> <p>A Fediverse Enhancement Proposal (FEP) is a document that provides information to the Fediverse community. The goal of a FEP is to improve interoperability and well-being of diverse services, applications and communities that form the Fediverse.</p> <p>The FEP Process is an initiative of the SocialHub developer community, a liaison of the W3C Social Web Incubator Community Group. For ongoing and past discussion see the SocialHub FEP category.</p>"},{"location":"#submitting-a-fep","title":"Submitting a FEP","text":"<p>Do you have an idea, opinion or information that you want to share with the wider Fediverse community? You may do so with a Fediverse Enhancement Proposal (FEP).</p> <p>To create and submit a FEP:</p> <ol> <li>Fork this repository, and then clone it to your local machine. Check the Codeberg Cheat sheet on how to prepare your Pull Request.</li> <li>Think of a title for the FEP you want to submit.</li> <li>Compute the identifier of the FEP by computing the hash of the title. This can be done with following Unix command:</li> </ol> <pre><code>$ echo -n \"The title of my proposal\" | sha256sum | cut -c-4\nb3f0\n</code></pre> <ol> <li>Create a subdirectory of <code>fep/</code> using the identifier you just computed.</li> <li>Copy the FEP template (fep-xxxx-template.md) to this subdirectory and change the filename appropriately.</li> <li> <p>Use the identifer as the \"slug\" when filling out the frontmatter.</p> <ul> <li>For example, if your computed identifier was <code>abcd</code>, then your file would be located at <code>fep/abcd/fep-abcd.md</code> and your frontmatter would include <code>slug: \"abcd\"</code>.</li> </ul> </li> <li> <p>Write down your idea in the newly created file and commit it to a new branch in your repository (ex. fep-xxxx).</p> </li> <li>When you are ready to submit the FEP, change the value of <code>dateReceived</code> field in the frontmatter to the current date.</li> <li>Create a discussion topic for your FEP. You can use ActivityPub category on the SocialHub forum.</li> <li>Add <code>discussionsTo</code> field containing the URL of the discussion topic to the front matter of your FEP.</li> <li>Create a Pull Request to complete Step 1 of FEP-a4ed: The Fediverse Enhancement Proposal Process. Further process is described in FEP-a4ed.</li> </ol> <p>Alternatively to steps 3. to 6., you can run</p> <pre><code>./scripts/new_proposal.py TITLE OF YOUR PROPOSAL\n</code></pre> <p>that should create a prefilled template for you.</p>"},{"location":"#facilitators","title":"Facilitators","text":"<p>The list of FEP's is maintained by the facilitators who are listed in the FACILITATORS.md file. Facilitators are neutral custodians of the FEP process, who merge PR's and create tracking issues.</p>"},{"location":"#contributing","title":"Contributing","text":"<p>Do you have ideas to improve the FEP Process? Post your suggestions to the issue tracker, or on the SocialHub forum. The SocialHub developer community is a \"DoOcracy\" which means: \u201cpick up any task you want, and then steer it to completion\u201d. Your contributions are most welcome, so delve in and find out how you can help.</p>"},{"location":"#license","title":"License","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this document have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"draft/","title":"Draft","text":"Title Type Tracking issue Discussions Received FEP-8b32: Object Integrity Proofs Implementation 9 #29 Discussions 2022-11-12 FEP-c390: Identity Proofs Implementation 3 #34 Discussions 2022-11-23 FEP-fb2a: Actor metadata Informational #45 - 2022-12-09 FEP-c118: Content licensing support Informational #57 - 2023-01-16 FEP-7888: Demystifying the context property Informational #68 Discussions 2023-03-14 FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces Informational #83 Discussions 2023-04-10 FEP-0ea0: Payment Links Implementation #88 Discussions 2023-04-18 FEP-fffd: Proxy Objects Informational #95 - 2023-04-29 FEP-4adb: Dereferencing identifiers with webfinger Informational #100 - 2023-05-13 FEP-a070: Ordered properties for plain JSON consumers Informational #121 - 2023-06-13 FEP-c648: Blocked Collection Informational #123 - 2023-06-14 FEP-bad1: Object history collection Informational #124 - 2023-06-15 FEP-4ccd: Pending Followers Collection and Pending Following Collection Informational #129 Discussions 2023-06-21 FEP-d36d: Sharing Content Across Federated Forums Informational #125 - 2023-07-01 FEP-1970: Chat Links Informational #128 - 2023-07-04 FEP-ae97: Client-side activity signing Implementation 2 #148 Discussions 2023-08-14 FEP-0837: Federated Marketplace Implementation 1 #147 Discussions 2023-08-17 FEP-5feb: Search indexing consent for actors Informational #154 - 2023-09-06 FEP-dc88: Formatting Mathematics Informational #161 - 2023-09-12 FEP-d8c2: OAuth 2.0 Profile for the ActivityPub API Informational #165 Discussions 2023-09-17 FEP-7628: Move actor Informational #168 Discussions 2023-09-20 FEP-37f2: a policy for calls for consensus on SWICG group decisions Informational #184 - 2023-09-28 FEP-2677: Identifying the Application Actor Informational #190 - 2023-10-14 FEP-03c1: Actors without acct-URI Informational #205 - 2023-11-10 FEP-ef61: Portable Objects Implementation 5 #209 Discussions 2023-12-06 FEP-7502: Limiting visibility to authenticated actors Informational #218 - 2023-12-24 FEP-2c59: Discovery of a Webfinger address from an ActivityPub actor Informational #226 - 2024-01-04 FEP-3264: Federated Work Coordination Informational #255 - 2024-01-31 FEP-c5a1: To-do's Informational #256 - 2024-01-31 FEP-61cf: The OpenWebAuth Protocol Implementation 5 #263 - 2024-02-06 FEP-73cd: Migration User Stories Informational #265 - 2024-02-07 FEP-96ff: Explicit signalling of ActivityPub Semantics Informational #272 - 2024-02-17 FEP-9fde: Mechanism for servers to expose supported operations Informational #282 - 2024-03-14 FEP-e229: Best practices for extensibility Informational #293 - 2024-04-02 FEP-3b86: Activity Intents Informational #302 - 2024-04-19 FEP-7458: Using the replies collection Informational #307 - 2024-04-26 FEP-0391: Special collection proofs Informational #310 - 2024-04-29 FEP-db0e: Authentication mechanism for non-public groups Informational #313 - 2024-05-03 FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases Informational #319 Discussions 2024-05-31 FEP-5e53: Opt-out Preference Signals Informational #327 Discussions 2024-06-09 FEP-7952: Roadmap For Actor and Object Portability Informational #435 Discussions 2024-06-20 FEP-e3e9: Actor-Relative URLs Informational #346 - 2024-06-29 FEP-d9ad: Create Conformance Tests for Fediverse Enhancement Proposals Informational #350 - 2024-07-02 FEP-e965: Move Activity for Migrations and Announce Activity for Tombstone Events Informational #352 Discussions 2024-07-05 FEP-9091: Export Actor Service Endpoint Informational #358 Discussions 2024-07-08 FEP-6fcd: Account Export Container Format Informational #357 Discussions 2024-07-11 FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals Informational #366 - 2024-07-11 FEP-a5c5: Web Syndication Methods Informational #368 Discussions 2024-07-15 FEP-c4ad: Viewership History Informational #367 Discussions 2024-07-15 FEP-c893: DOAP Informational #372 Discussions 2024-07-15 FEP-eb48: Hashtags Informational #373 Discussions 2024-07-16 FEP-eb22: Supported ActivityStreams types with NodeInfo Informational #386 Discussions 2024-07-25 FEP-c0e0: Emoji reactions Implementation 4 #384 Discussions 2024-08-08 FEP-c16b: Formatting MFM functions Informational #383 Discussions 2024-08-10 FEP-0499: Delivering to multiple inboxes with a multibox endpoint Informational #402 Discussions 2024-09-30 FEP-76ea: Conversation Threads Informational #407 Discussions 2024-10-04 FEP-1985: Signaling how an OrderedCollection is ordered Informational #417 Discussions 2024-10-10 FEP-268d: Search consent signals for objects Informational #421 Discussions 2024-10-12 FEP-b2b8: Long-form Text Informational #441 Discussions 2024-11-07 FEP-fe34: Origin-based security model Implementation #445 Discussions 2024-11-15 FEP-171b: Conversation Containers Implementation 3 #449 Discussions 2024-11-23 FEP-6606: ActivityPub client to server collections addressing conventions Informational #453 Discussions 2024-12-04 FEP-1311: Media Attachments Informational #459 Discussions 2024-12-08 FEP-7d8c: Documentation: Automation of FEP Informational #470 Discussions 2025-01-20 FEP-9967: Polls Implementation #478 Discussions 2025-01-23 FEP-2277: ActivityPub core types Informational #485 Discussions 2025-01-31 FEP-a974: All Actor types should be followable Informational #487 Discussions 2025-02-05 FEP-efda: Followable objects Informational #492 Discussions 2025-02-13 FEP-f228: Backfilling conversations Implementation 10 #500 Discussions 2025-02-17 FEP-f06f: Object observers Implementation #503 Discussions 2025-02-18 FEP-dd4b: Quote Posts Informational #511 Discussions 2025-02-21 FEP-c180: Problem Details for ActivityPub Informational #531 Discussions 2025-03-11 FEP-2931: Representing context with a Collection Implementation 4 #534 Discussions 2025-03-22 FEP-5711: Inverse Properties for Collections Informational #539 Discussions 2025-03-24 FEP-044f: Consent-respecting quote posts Informational #547 Discussions 2025-04-03 FEP-1042: Peer to Peer Fediverse Identities Informational #546 Discussions 2025-04-03 FEP-4f05: Soft Deletion Informational #554 Discussions 2025-04-15 FEP-8a8e: A common approach to using the Event object type Informational #565 Discussions 2025-04-23 FEP-82f6: Actor statuses Informational #600 Discussions 2025-05-12 FEP-844e: Capability discovery Implementation 5 #624 Discussions 2025-06-14 FEP-b06c: ActivityPoll Informational #638 Discussions 2025-06-25 FEP-9098: Custom emojis Implementation #648 Discussions 2025-07-06 FEP-11dd: Context Ownership and Inheritance Informational #675 Discussions 2025-09-11 FEP-8967: Generating link previews for attached links Informational #681 Discussions 2025-09-16 FEP-1580: Move Actor Objects with a <code>migration</code> Collection Implementation #702 Discussions 2025-10-09 FEP-d8c8: BitTorrent <code>Torrent</code> Objects Informational #722 Discussions 2025-11-03 FEP-19b3: Specifying Properties of a Service Informational #719 Discussions 2025-11-04 FEP-22b6: Linking an ActivityPub Object to a HTML page and back Informational #730 Discussions 2025-11-12 FEP-f15d: Context Relocation and Removal Informational #751 Discussions 2026-01-12 FEP-ee3a: Exif metadata support Informational #752 Discussions 2026-01-13 FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary Informational #763 Discussions 2026-02-19 FEP-a427: Server Domain Migration Informational #767 Discussions 2026-02-25 FEP-fc48: Generic ActivityPub server Implementation #769 Discussions 2026-02-27 FEP-3ab2: ActivityPub Event Streaming API Informational - Discussions 2026-03-14"},{"location":"final/","title":"Final","text":"Title Type Tracking issue Discussions Received Finalized FEP-a4ed: The Fediverse Enhancement Proposal Process Informational #201 - 2020-10-16 2021-01-18 FEP-8fcf: Followers collection synchronization across servers Informational #6 - 2020-10-24 2022-02-07 FEP-f1d5: NodeInfo in Fediverse Software Informational #50 - 2020-12-13 2023-06-02 FEP-400e: Publicly-appendable ActivityPub collections Informational #5 - 2021-02-16 2022-02-04 FEP-e232: Object Links Informational #14 Discussions 2022-08-01 2023-12-03 FEP-1b12: Group federation Informational #22 - 2022-11-12 2023-02-09 FEP-521a: Representing actor's public keys Implementation 6 #130 Discussions 2023-07-08 2025-06-14 FEP-67ff: FEDERATION.md Informational #157 Discussions 2023-09-05 2024-09-22 FEP-d556: Server-Level Actor Discovery Using WebFinger Implementation 7 #243 - 2024-01-20 2025-03-15 FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePub Informational #424 Discussions 2024-10-19 2025-03-14 FEP-0151: NodeInfo in Fediverse Software (2025 edition) Implementation 5 #599 Discussions 2025-05-12 2026-03-07"},{"location":"withdrawn/","title":"Withdrawn","text":"Title Type Tracking issue Discussions Received Withdrawn FEP-8c3f: Web Monetization Informational #3 - 2022-01-18 2023-10-27 FEP-2100: Unbound Group and Organization Informational #36 Discussions 2022-03-31 2025-04-06 FEP-5624: Per-object reply control policies Informational #18 Discussions 2022-08-23 2025-06-24 FEP-cb76: Content Addressed Vocabulary Informational #41 Discussions 2022-11-29 2025-04-07 FEP-2e40: The FEP Vocabulary Extension Process Informational #62 - 2023-02-13 2023-09-04 FEP-d767: Extend ActivityPub with Valueflows Informational #76 - 2023-04-02 2024-02-03 FEP-5bf0: Collection sorting and filtering Informational #80 - 2023-04-10 2025-06-14 FEP-612d: Identifying ActivityPub Objects through DNS Informational #89 - 2023-04-18 2025-10-10 FEP-07d7: A Custom URL Scheme and Web-Based Protocol Handlers for Linking to ActivityPub Resources Informational #171 - 2023-09-22 2025-04-22 FEP-6481: Specifying ActivityPub extension support with NodeInfo Informational #279 - 2024-03-12 2024-10-31 FEP-c7d3: Ownership Informational #323 Discussions 2024-06-04 2025-02-21"},{"location":"fep/0151/","title":"FEP-0151: NodeInfo in Fediverse Software (2025 edition)","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>FINAL</code> implementation 2025-05-12 2026-03-07 #599 Discussions codeberg"},{"location":"fep/0151/#summary","title":"Summary","text":"<p>NodeInfo is a protocol intended to standardize upon a way to provide server-level metadata to the public. This enables tools and clients to utilize this metadata to assess server health or facilitate end-users choices about servers and software to use on the Fediverse.</p> <p>This document is a revised version of FEP-f1d5: NodeInfo in Fediverse Software, which was published in 2020.</p>"},{"location":"fep/0151/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/0151/#nodeinfo-best-practices","title":"NodeInfo best practices","text":"<p>Servers SHOULD implement NodeInfo protocol version 2.1.</p>"},{"location":"fep/0151/#privacy-and-security-considerations","title":"Privacy and security considerations","text":"<p>Some information contained in the NodeInfo document might be considered sensitive by server operators.</p> <p>The NodeInfo endpoint could be disabled to hide that information.</p>"},{"location":"fep/0151/#hiding-usage-statistics","title":"Hiding usage statistics","text":"<p>Some softwares might wish to publish NodeInfo, but without revealing usage statistics. While the NodeInfo 2.1 schema specifies <code>usage</code> and <code>usage.users</code> as required properties, it does not require any additional properties within <code>usage.users</code>. Servers MUST NOT publish intentionally skewed usage statistics; instead, servers wishing to hide their usage statistics SHOULD publish an empty object:</p> <pre><code>{\n \"usage\": {\n \"users\": {}\n }\n}\n</code></pre>"},{"location":"fep/0151/#capability-detection","title":"Capability detection","text":"<p>Servers SHOULD NOT rely on NodeInfo data for protocol negotiation and capability detection, because server operators might intentionally disable NodeInfo.</p> <p>Servers MUST NOT infer capabilities from the <code>software</code> field and MUST treat <code>software.name</code> and <code>software.version</code> values as opaque strings. Attempting to use this information is very difficult to do reliably, and often hinders inteoperability. However, software version information MAY be used to block instances with known security vulnerabilities.</p> <p>Instead of NodeInfo, protocol-specific capability discovery mechanisms could be used. The capabilities of an ActivityPub application can be advertised using a server actor, as described in FEP-844e: Capability discovery.</p>"},{"location":"fep/0151/#metadata","title":"<code>metadata</code>","text":"<p>Properties of the <code>metadata</code> object are not standardized, but some of them are widely used:</p> <ul> <li><code>nodeName</code> (<code>string</code>): The name of the server.</li> <li><code>nodeDescription</code> (<code>string</code>): The long form description of the server.</li> <li><code>staffAccounts</code> (<code>array[string]</code>): The list of staff account IDs (actor IDs for ActivityPub).</li> <li><code>federation.enabled</code> (<code>boolean</code>): Whether the server is connected to other servers.</li> </ul> <p>Example:</p> <pre><code>{\n \"metadata\": {\n \"nodeName\": \"My server\",\n \"nodeDescription\": \"My server\",\n \"staffAccounts\": [\n \"https://social.example/actors/1\"\n ],\n \"federation\": {\n \"enabled\": true\n }\n }\n}\n</code></pre>"},{"location":"fep/0151/#implementations","title":"Implementations","text":"<ul> <li>ActivityPub for WordPress</li> <li>Mitra</li> <li>squidcity</li> <li>BadgeFed</li> <li>Ktistec</li> </ul>"},{"location":"fep/0151/#references","title":"References","text":"<ul> <li>Jonne Ha\u00df, NodeInfo protocol, 2014</li> <li>CJ, FEP-f1d5: NodeInfo in Fediverse Software, 2020</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>silverpill, FEP-844e: Capability discovery, 2025</li> </ul>"},{"location":"fep/0151/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/0391/","title":"FEP-0391: Special collection proofs","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2024-04-29 #310 - codeberg"},{"location":"fep/0391/#summary","title":"Summary","text":"<p>Some properties represent special collections, such as:</p> <ul> <li><code>outbox</code> (ActivityPub)</li> <li><code>inbox</code> (ActivityPub)</li> <li><code>followers</code> (ActivityPub)</li> <li><code>following</code> (ActivityPub)</li> <li><code>liked</code> (ActivityPub)</li> <li><code>likes</code> (ActivityPub)</li> <li><code>shares</code> (ActivityPub)</li> <li><code>replies</code> (FEP-7458)</li> <li><code>context</code> (FEP-7888)</li> </ul> <p>Verifying that any given object is part of a special collection is usually only possible by resolving that collection and checking its items one-by-one until the current object is found. This can be inefficient to verify. It would be easier if there was an inverse claim for each claim made about an object being part of a special collection. This FEP aims to define some properties that can be used to make those inverse claims.</p>"},{"location":"fep/0391/#mapping-claims-and-inverse-claims","title":"Mapping claims and inverse claims","text":"<p>Given the special collections above, we can map the following claims and inverse claims. The last two inverse claims are particularly salient, since inclusion in <code>replies</code> and/or <code>context</code> carries additional semantic meaning and is a socially loaded relation used to establish conversational constructs.</p>"},{"location":"fep/0391/#outbox-claim","title":"Verifying presence in <code>outbox</code>","text":"<p>Claim : Activity <code>&lt;A&gt;</code>'s actor (<code>&lt;A&gt;.actor</code>) is actor <code>&lt;B&gt;</code> Inverse claim : Actor <code>&lt;B&gt;</code>'s outbox collection (<code>&lt;B&gt;.outbox</code>) contains activity <code>&lt;A&gt;</code></p> <p>This is not particularly useful to prove.</p>"},{"location":"fep/0391/#inbox-claim","title":"Verifying presence in <code>inbox</code>","text":"<p>Claim : Activity <code>&lt;A&gt;</code> has <code>to</code>/<code>cc</code>/<code>audience</code> including actor <code>&lt;B&gt;</code> Inverse claim : Actor <code>&lt;B&gt;</code>'s inbox collection (<code>&lt;B&gt;.inbox</code>) contains activity <code>&lt;A&gt;</code></p> <p>This is not particularly useful to prove, and at best can only be implied if <code>&lt;B&gt;</code> is addressed directly and not through some collection that triggers inbox forwarding.</p>"},{"location":"fep/0391/#followers-claim","title":"Verifying presence in <code>followers</code>","text":"<p>Claim : Actor <code>&lt;B&gt;</code>'s following collection (<code>&lt;B&gt;.following</code>) contains actor <code>&lt;A&gt;</code></p> <p>Equivalent claim : Actor <code>&lt;B&gt;</code> claims that <code>&lt;B&gt;</code> is following <code>&lt;A&gt;</code></p> <p>Another equivalent claim : Actor <code>&lt;B&gt;</code> claims that <code>&lt;A&gt;</code> is followed by <code>&lt;B&gt;</code></p> <p>Inverse claim : Actor <code>&lt;A&gt;</code>'s followers collection (<code>&lt;A&gt;.followers</code>) contains actor <code>&lt;B&gt;</code></p> <p>Equivalent inverse claim : Actor <code>&lt;A&gt;</code> claims that <code>&lt;A&gt;</code> is followed by <code>&lt;B&gt;</code></p> <p>Another equivalent inverse claim : Actor <code>&lt;A&gt;</code> claims that <code>&lt;B&gt;</code> is following <code>&lt;A&gt;</code></p> <p>This can be verified by showing one of the following:</p> <ul> <li><code>&lt;A&gt;.followers</code> includes <code>&lt;B&gt;</code> as well</li> <li>A Relationship exists where:<ul> <li><code>attributedTo</code> is <code>&lt;A&gt;</code></li> <li><code>subject</code> is <code>&lt;B&gt;</code></li> <li><code>relationship</code> is <code>IsFollowing</code></li> <li><code>object</code> is <code>&lt;A&gt;</code></li> </ul> </li> <li>A Relationship exists where:<ul> <li><code>attributedTo</code> is <code>&lt;A&gt;</code></li> <li><code>subject</code> is <code>&lt;A&gt;</code></li> <li><code>relationship</code> is <code>IsFollowedBy</code></li> <li><code>object</code> is <code>&lt;B&gt;</code></li> </ul> </li> <li>A trusted activity exists where:<ul> <li><code>actor</code> is <code>&lt;A&gt;</code></li> <li><code>type</code> is <code>Accept</code></li> <li><code>object.actor</code> is <code>&lt;B&gt;</code></li> <li><code>object.type</code> is <code>Follow</code></li> <li><code>object.object</code> is <code>&lt;A&gt;</code></li> </ul> </li> <li>A trusted activity exists where:<ul> <li><code>actor</code> is <code>&lt;A&gt;</code></li> <li><code>type</code> is <code>Add</code></li> <li><code>object</code> is <code>&lt;B&gt;</code></li> <li><code>target</code> is <code>&lt;A&gt;.followers</code></li> </ul> </li> </ul>"},{"location":"fep/0391/#following-claim","title":"Verifying presence in <code>following</code>","text":"<p>Claim : Actor <code>&lt;B&gt;</code>'s followers collection (<code>&lt;B&gt;.followers</code>) contains actor <code>&lt;A&gt;</code></p> <p>Equivalent claim : Actor <code>&lt;B&gt;</code> claims that <code>&lt;B&gt;</code> is followed by <code>&lt;A&gt;</code></p> <p>Another equivalent claim : Actor <code>&lt;B&gt;</code> claims that <code>&lt;A&gt;</code> is following <code>&lt;B&gt;</code></p> <p>Inverse claim : Actor <code>&lt;A&gt;</code>'s following collection (<code>&lt;A&gt;.following</code>) contains actor <code>&lt;B&gt;</code></p> <p>Equivalent inverse claim : Actor <code>&lt;A&gt;</code> claims that <code>&lt;A&gt;</code> is following <code>&lt;B&gt;</code></p> <p>Another equivalent inverse claim : Actor <code>&lt;A&gt;</code> claims that <code>&lt;B&gt;</code> is followed by <code>&lt;A&gt;</code></p> <p>This can be verified by showing one of the following:</p> <ul> <li><code>&lt;A&gt;.following</code> includes <code>&lt;B&gt;</code> as well</li> <li>A Relationship exists where:<ul> <li><code>attributedTo</code> is <code>&lt;A&gt;</code></li> <li><code>subject</code> is <code>&lt;A&gt;</code></li> <li><code>relationship</code> is <code>IsFollowing</code></li> <li><code>object</code> is <code>&lt;B&gt;</code></li> </ul> </li> <li>A Relationship exists where:<ul> <li><code>attributedTo</code> is <code>&lt;A&gt;</code></li> <li><code>subject</code> is <code>&lt;B&gt;</code></li> <li><code>relationship</code> is <code>IsFollowedBy</code></li> <li><code>object</code> is <code>&lt;A&gt;</code></li> </ul> </li> <li>A trusted activity exists where:<ul> <li><code>actor</code> is <code>&lt;B&gt;</code></li> <li><code>type</code> is <code>Accept</code></li> <li><code>object.actor</code> is <code>&lt;A&gt;</code></li> <li><code>object.type</code> is <code>Follow</code></li> <li><code>object.object</code> is <code>&lt;B&gt;</code></li> <li><code>object</code> is a trusted activity</li> </ul> </li> <li>A trusted activity exists where:<ul> <li><code>actor</code> is <code>&lt;A&gt;</code></li> <li><code>type</code> is <code>Add</code></li> <li><code>object</code> is <code>&lt;B&gt;</code></li> <li><code>target</code> is <code>&lt;A&gt;.following</code></li> </ul> </li> </ul>"},{"location":"fep/0391/#liked-claim","title":"Verifying presence in <code>liked</code>","text":"<p>Claim : Actor <code>&lt;A&gt;</code>'s liked collection (<code>&lt;A&gt;.liked</code>) contains object <code>&lt;O&gt;</code></p> <p>This can be verified by showing a trusted activity exists in <code>&lt;O&gt;.likes</code> where: - <code>actor</code> is <code>&lt;A&gt;</code> - <code>type</code> is <code>Like</code> - <code>object</code> is <code>&lt;O&gt;</code></p> <p>Note that there is an issue that may occur if <code>&lt;A&gt;</code> issues multiple <code>Like</code> activities for the same object <code>&lt;O&gt;</code>, and then issues any <code>Undo Like</code> activities at a later point in time. The most recent activity will have its side-effects carried out. It is possible for some of these functionally duplicate <code>Like</code> activites to remain in <code>&lt;O&gt;.likes</code> even though the object <code>&lt;O&gt;</code> is no longer in <code>&lt;A&gt;.liked</code>. See https://github.com/w3c/activitypub/issues/381 for more information.</p>"},{"location":"fep/0391/#likes-claim","title":"Verifying presence in <code>likes</code>","text":"<p>Claim : Object <code>&lt;O&gt;</code>'s likes collection (<code>&lt;O&gt;.likes</code>) contains a Like activity <code>&lt;L&gt;</code> from actor <code>&lt;A&gt;</code></p> <p>If <code>&lt;L&gt;</code> is not already trusted via some other mechanism, this can be verified by showing that <code>&lt;A&gt;.liked</code> includes <code>&lt;O&gt;</code>. Note the duplication issue from the previous section.</p> <p>The Like activity <code>&lt;L&gt;</code> MAY <code>result</code> in an <code>Add</code> activity <code>&lt;R&gt;</code> targeting <code>&lt;O&gt;.likes</code>. This <code>result</code> can be used by <code>&lt;A&gt;</code> as proof of the claim, if it can be shown to be a trusted activity.</p>"},{"location":"fep/0391/#shares-claim","title":"Verifying presence in <code>shares</code>","text":"<p>Claim : Object <code>&lt;O&gt;</code>'s shares collection (<code>&lt;O&gt;.shares</code>) contains an Announce activity <code>&lt;S&gt;</code> from actor <code>&lt;A&gt;</code></p> <p>The Announce activity <code>&lt;S&gt;</code> MAY <code>result</code> in an <code>Add</code> activity <code>&lt;R&gt;</code> targeting <code>&lt;O&gt;.shares</code>. This <code>result</code> can be used by <code>&lt;A&gt;</code> as proof of the claim, if it can be shown to be a trusted activity.</p>"},{"location":"fep/0391/#replies-claim","title":"Verifying presence in <code>replies</code>","text":"<p>Claim : Object <code>&lt;R&gt;</code> is <code>inReplyTo</code> object <code>&lt;O&gt;</code></p> <p>Inverse claim : Object <code>&lt;O&gt;</code>'s replies collection (<code>&lt;O&gt;.replies</code>) contains object <code>&lt;R&gt;</code> attributed to actor <code>&lt;A&gt;</code></p> <p>The inverse claim can be verified by showing that an activity exists where: - <code>actor</code> is <code>&lt;O&gt;.attributedTo</code> - <code>type</code> set includes <code>Add</code> - <code>object</code> is <code>&lt;R&gt;</code> - <code>target</code> is <code>&lt;O&gt;.replies</code></p>"},{"location":"fep/0391/#context-claim","title":"Verifying presence in <code>context</code>","text":"<p>Claim : Object <code>&lt;O&gt;</code> has a <code>context</code> pointing to context collection <code>&lt;C&gt;</code></p> <p>Inverse claim : Context collection <code>&lt;C&gt;</code> contains object <code>&lt;O&gt;</code></p> <p>The inverse claim can be verified by showing that an activity exists where: - <code>actor</code> is <code>&lt;O&gt;.context.attributedTo</code> - <code>type</code> set includes <code>Add</code> - <code>object</code> is <code>&lt;R&gt;</code> - <code>target</code> is <code>&lt;O&gt;.context</code></p>"},{"location":"fep/0391/#types-of-proofs","title":"Types of proofs","text":"<p>We can map proofs onto the <code>result</code> property provided that its subject is an activity and its referent is a trusted activity. We may also define some extension properties for non-activity objects.</p>"},{"location":"fep/0391/#replies-stamp","title":"Showing inclusion in <code>replies</code> collections via a reply stamp","text":"<p>Provides proof that some object was added to some <code>replies</code> collection.</p> <p>On an activity where <code>inReplyTo</code> has been set, the proof can be verified if all of the following are satisfied:</p> <ul> <li><code>result.actor</code> MUST be included in either <code>inReplyTo.actor</code> or <code>inReplyTo.attributedTo</code></li> <li><code>result.type</code> MUST include <code>Add</code></li> <li><code>result.object</code> MUST be equivalent to the current activity</li> <li><code>result.target</code> MUST be equivalent to <code>inReplyTo.replies</code></li> </ul> <p>On a <code>Create</code> activity where the <code>object</code> has <code>inReplyTo</code> set, the proof can be verified if all of the following are satisfied:</p> <ul> <li><code>result.actor</code> MUST be included in either <code>object.inReplyTo.attributedTo</code> or <code>object.inReplyTo.actor</code></li> <li><code>result.type</code> MUST include <code>Add</code></li> <li><code>result.object</code> MUST be equivalent to <code>object</code></li> <li><code>result.target</code> MUST be equivalent to <code>object.inReplyTo.replies</code></li> </ul> <p>On a non-activity Object where <code>inReplyTo</code> has been set, we define the extension property <code>inReplyToProof</code> since the use of <code>result</code> is invalid on non-Activity types. The proof is valid if all of the following are satisfied:</p> <ul> <li><code>inReplyToProof.actor</code> MUST be included in either <code>inReplyTo.attributedTo</code> or <code>inReplyTo.actor</code></li> <li><code>inReplyToProof.type</code> MUST include <code>Add</code></li> <li><code>inReplyToProof.object</code> MUST be equivalent to the current object</li> <li><code>inReplyToProof.target</code> MUST be equivalent to <code>inReplyTo.replies</code></li> </ul> <p>Example of an activity with <code>inReplyTo</code> set to some activity with a <code>replies</code> collection:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-activity\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Activity\",\n \"object\": \"https://example.com/some-object\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-other-create\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Create\",\n \"object\": \"https://example.com/some-other-object\",\n \"content\": \"I am accepting replies to this activity.\",\n \"replies\": \"https://example.com/some-other-create/replies\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": \"https://example.com/some-other-create/replies\",\n \"attributedTo\": \"https://example.com/some-activity\"\n }\n}\n</code></pre> <p>Example of a <code>Create</code> activity with <code>object.inReplyTo</code> set to some non-activity object with a <code>replies</code> collection:</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-reply\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-reply\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"content\": \"This is a reply, and I can prove it was added to the replies collection.\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"content\": \"I am accepting replies to this object.\",\n \"replies\": \"https://example.com/some-object/replies\"\n },\n \"inReplyToProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-reply\",\n \"target\": \"https://example.com/some-object/replies\",\n \"attributedTo\": \"https://example.com/create-some-reply\"\n }\n}\n</code></pre>"},{"location":"fep/0391/#context-stamp","title":"Showing inclusion in <code>context</code> collections via a context stamp","text":"<p>Provides proof that some object was added to some <code>context</code> collection.</p> <p>On an activity where the <code>context</code> has been set to a collection, the proof can be verified if all of the following are satisfied:</p> <ul> <li><code>result.actor</code> MUST be included in <code>context.attributedTo</code></li> <li><code>result.type</code> MUST include <code>Add</code></li> <li><code>result.object</code> MUST be equivalent to the current activity</li> <li><code>result.target</code> MUST be equivalent to <code>context</code></li> </ul> <p>On a <code>Create</code> activity where the <code>object</code> has <code>context</code> set, the proof can be verified if all of the following are satisfied:</p> <ul> <li><code>result.actor</code> MUST be included in <code>object.context.attributedTo</code></li> <li><code>result.type</code> MUST include <code>Add</code></li> <li><code>result.object</code> MUST be equivalent to <code>object</code></li> <li><code>result.target</code> MUST be equivalent to <code>object.context</code></li> </ul> <p>On a non-activity Object where <code>context</code> has been set, we define the extension property <code>contextProof</code> since the use of <code>result</code> is invalid on non-Activity types. The proof is valid if all of the following are satisfied:</p> <ul> <li><code>contextProof.actor</code> MUST be included in <code>context.attributedTo</code></li> <li><code>contextProof.type</code> MUST include <code>Add</code></li> <li><code>contextProof.object</code> MUST be equivalent to the current object</li> <li><code>contextProof.target</code> MUST be equivalent to <code>context</code></li> </ul> <p>Example of an activity with <code>context</code> set to some owned collection:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-activity\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Activity\",\n \"object\": \"https://example.com/some-object\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/some-activity\"\n }\n}\n</code></pre> <p>Example of a <code>Create</code> activity with <code>object.context</code> set to some owned collection:</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"contextProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-object\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\"\n }\n}\n</code></pre>"},{"location":"fep/0391/#likes-stamp","title":"Showing inclusion in <code>likes</code> collections via a like stamp","text":"<p>Provides proof that the current activity was added to the <code>object.likes</code> collection.</p> <p>On a <code>Like</code> activity where the <code>object</code> has a <code>likes</code> collection, the proof can be verified if all of the following are satisfied:</p> <ul> <li><code>result.actor</code> MUST be included in <code>object.attributedTo</code></li> <li><code>result.type</code> MUST include <code>Add</code></li> <li><code>result.object</code> MUST be equivalent to the current activity</li> <li><code>result.target</code> MUST be equivalent to <code>object.likes</code></li> </ul> <p>Example:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"A Like activity, with proof that it was added to the likes collection.\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Like\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting likes of this object.\",\n \"likes\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/actors/1\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-like\",\n \"target\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/some-like\"\n }\n}\n</code></pre>"},{"location":"fep/0391/#shares-stamp","title":"Showing inclusion in <code>shares</code> collections via a share stamp","text":"<p>Provides proof that the current activity was added to the <code>object.shares</code> collection.</p> <p>On an <code>Announce</code> activity where the <code>object</code> has a <code>shares</code> collection, the proof can be verified if all of the following are satisfied:</p> <ul> <li><code>result.actor</code> MUST be included in <code>object.attributedTo</code></li> <li><code>result.type</code> MUST include <code>Add</code></li> <li><code>result.object</code> MUST be equivalent to the current activity</li> <li><code>result.target</code> MUST be equivalent to <code>object.shares</code></li> </ul> <p>Example:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-announce\",\n \"summary\": \"An Announce activity, with proof that it was added to the shares collection.\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Announce\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting shares of this object.\",\n \"shares\": \"https://example.com/some-object/shares\",\n \"attributedTo\": \"https://example.com/actors/1\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-announce\",\n \"target\": \"https://example.com/some-object/shares\",\n \"attributedTo\": \"https://example.com/some-announce\"\n }\n}\n</code></pre>"},{"location":"fep/0391/#relationship-proofs","title":"Relationship proofs","text":"<p>Warning</p> <p>Experimental, requires further thought.</p> <p>Warning</p> <p>Currently bugged. See https://github.com/w3c/activitystreams/issues/593 for more details.</p> <p>Provides proof that the current relationship is reciprocally claimed.</p> <p>Relationships other than following or being a follower may be proved using this property, but the requirements for such a proof are out of scope of this FEP.</p> <p>We define the extension property <code>relationshipProof</code> since the use of <code>result</code> is invalid on non-Activity types. The proof is valid if all of the following are satisfied:</p> <p>Example that proves a user is following another user:</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"following\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowing\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowedBy\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-followers\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow-accepted\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Accept\",\n \"object\": {\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/2\"\n }\n }\n ]\n}\n</code></pre> <p>Example that proves a user is followed by another user:</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowedBy\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowing\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/1\"\n }\n ]\n}\n</code></pre> <p>Miscellaneous examples:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-follow\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/following\"\n },\n \"type\": \"Follow\",\n \"object\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/followers\"\n },\n \"result\": {\n \"id\": \"https://example.com/accept-follow\",\n \"actor\": \"https://example.com/actor/1\",\n \"type\": \"Accept\",\n \"object\": \"https://example.com/some-follow\",\n \"result\": [\n {\n \"id\": \"https://example.com/resulting-add-to-followers\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\",\n \"attributedTo\": \"https://example.com/accept-follow\"\n },\n {\n \"id\": \"https://example.com/resulting-add-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\",\n \"attributedTo\": \"https://example.com/accept-follow\"\n }\n ]\n }\n}\n</code></pre>"},{"location":"fep/0391/#root-trust","title":"Existing mechanisms of trust","text":"<p>Verifying stamps has an issue with bootstrapping trust. In addition to verifying stamps via logical inference, ActivityPub clients SHOULD also consider the following:</p> <ul> <li>Same-origin policy, if the root of trust is the DNS name.</li> <li>Cryptographic signatures, if the root of trust is some cryptographic key associated with the actor. See HTTP Signatures, Linked Data Signatures, FEP-8b32: Object Integrity Proofs, or similar.</li> </ul>"},{"location":"fep/0391/#flow","title":"Obtaining and revoking stamps","text":"<p>Upon receiving an activity with a side effect of adding something to a special collection, ActivityPub servers SHOULD generate and deliver an <code>Add</code> activity representing this side-effect. The ActivityPub server MAY require manual action by a user. The resulting stamp activity SHOULD be <code>attributedTo</code> the activity that caused the side-effect, delivered to its <code>actor</code>, and additionally SHOULD either be resolvable (to allow direct same-origin checking) or otherwise include a cryptographic proof. If a cryptographic proof is included, the proof SHOULD expire after some reasonable window, beyond which point a new proof should be issued. If the resulting stamp activity is resolvable, it MAY become unresolvable after some time; HTTP caching SHOULD be used to indicate a time-to-live for ActivityPub clients to re-check cached stamps. The time-to-live MAY be used as the duration of the cryptographic proof, if one exists.</p> <p>The recipient of a stamp SHOULD <code>Update</code> their object to include the appropriate property for the stamp -- <code>result</code> if it is an activity, <code>inReplyToProof</code> if it is a non-activity object that declares <code>inReplyTo</code>, <code>contextProof</code> if it is a non-activity object that declares <code>context</code>. [TODO: how to handle relationship proofs?]</p> <p>Revoking a stamp can be done by: - If it is resolvable, making it no longer resolve - If it is signed, then waiting for the signature to expire - <code>Update</code> the stamp with some property to signal that the <code>Add</code> activity has been undone. [TODO: flesh this out more -- how does this work exactly? should it use Remove? Tombstone? Undo? how does this interact with <code>outbox</code> and the activity history? i'm thinking Update -&gt; Tombstone, or extension like \"undoneBy\"]</p>"},{"location":"fep/0391/#a-simpler-example-flow-for-issuing-and-revoking-a-like-stamp","title":"A simpler example flow for issuing and revoking a like stamp","text":"<p>This flow is similar for any stamp that uses <code>result</code> on an activity -- <code>Like</code>, <code>Announce</code>, Activity with <code>inReplyTo</code>, Activity with <code>context</code>.</p> <p>Actor 2 likes a post by actor 1:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"Actor 2 liked a Note by actor 1\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"type\": \"Like\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting likes of this object.\",\n \"likes\": \"https://example.com/some-object/likes\",\n \"attributedTo\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/followers\"\n },\n \"cc\": [\n \"https://example.com/actors/1/followers\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-object/audience\"\n },\n \"to\": \"https://example.com/actors/1\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-object/audience\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-like/audience\"\n}\n</code></pre> <p>Actor 1 sends a like stamp:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-proof\",\n \"summary\": \"Actor 1 approved a like\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-like\",\n \"target\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/some-like\",\n \"to\": \"https://example.com/actors/2\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-like/audience\",\n \"as:Public\"\n ]\n}\n</code></pre> <p>Actor 2 updates their Like activity with proof:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-update\",\n \"summary\": \"Actor 2 updated their Like with proof\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"Actor 2 liked a Note by actor 1, with proof\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Like\",\n \"object\": \"https://example.com/some-object\",\n \"to\": \"https://example.com/actors/1\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-object/audience\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-like/audience\",\n \"result\": \"https://example.com/some-proof\"\n },\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-like/audience\",\n \"as:Public\"\n ]\n}\n</code></pre> <p>If actor 1 had provided cryptographic proof, actor 2 would be able to authoritatively serve the stamp until the proof expired. Actor 1 can silently revoke the stamp by making it no longer resolve, or actively revoke the stamp by issuing a revocation activity [TODO: Update with \"undoneBy\" pointing to a Remove?]</p>"},{"location":"fep/0391/#a-more-complex-flow-for-stamps-involving-embedded-objects","title":"A more complex flow for stamps involving embedded objects","text":"<p>This flow uses <code>inReplyToProof</code> or <code>contextProof</code> on the <code>object</code> of a <code>Create</code> instead of using only <code>result</code> on the activity itself.</p> <p>Some actor creates some object that is part of some context, owned by some context moderator:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": {\n \"id\": \"https://example.com/some-actor\",\n \"followers\": \"https://example.com/some-actor/followers\"\n },\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\",\n \"audience\": \"https://example.com/some-context/audience\"\n },\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\"\n },\n \"to\": [\n \"https://example.com/some-context-moderator\",\n \"https://example.com/some-context/audience\"\n ],\n \"cc\": \"as:Public\",\n \"audience\": \"https://example.com/create-some-object/audience\"\n}\n</code></pre> <p>The context moderator adds the object to the context:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-proof\",\n \"summary\": \"A new post was added to the context\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-object\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\",\n \"cc\": [\n \"https://example.com/some-context/audience\",\n \"https://example.com/some-actor\",\n \"https://example.com/create-some-object/audience\",\n \"as:Public\"\n ]\n}\n</code></pre> <p>The actor who created the object now can issue two updates: one for the Create activity to add a <code>result</code>, and one for the <code>object</code> of that activity to add <code>object.contextProof</code>:</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/update-some-object\",\n \"actor\": {\n \"id\": \"https://example.com/some-actor\",\n \"followers\": \"https://example.com/some-actor/followers\"\n },\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": \"https://example.com/some-context\",\n \"contextProof\": \"https://example.com/some-proof\",\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\"\n },\n \"to\": [\n \"https://example.com/some-actor/followers\",\n \"as:Public\"\n ]\n}\n</code></pre> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/update-create\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": \"https://example.com/some-object\",\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\",\n \"result\": \"https://example.com/some-proof\"\n }\n}\n</code></pre>"},{"location":"fep/0391/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>a, FEP-7458: Using the replies collection, 2024</li> <li>a, FEP-7888: Demystifying the context property, 2023</li> <li>silverpill, FEP-8b32: Object Integrity Proofs, 2022</li> </ul>"},{"location":"fep/0391/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/0391/contextProof/","title":"contextProof https://w3id.org/fep/0391/contextProof","text":"<p>Provides proof that some object was added to some <code>context</code> collection</p> <p>Domain : Object with context set to a Collection that has attributedTo set</p> <p>Range : Add</p>"},{"location":"fep/0391/contextProof/#example","title":"Example","text":"<pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"contextProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-comment\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\"\n }\n}\n</code></pre>"},{"location":"fep/0391/inReplyToProof/","title":"inReplyToProof https://w3id.org/fep/0391/inReplyToProof","text":"<p>Provides proof that some object was added to the replies collection of some inReplyTo object</p> <p>Domain : Object with inReplyTo set to an Object that has a replies collection</p> <p>Range : Add</p>"},{"location":"fep/0391/inReplyToProof/#example","title":"Example","text":"<pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-reply\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-reply\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"content\": \"This is a reply, and I can prove it was added to the replies collection.\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"content\": \"I am accepting replies to this object.\",\n \"replies\": \"https://example.com/some-object/replies\"\n },\n \"inReplyToProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-reply\",\n \"target\": \"https://example.com/some-object/replies\",\n \"attributedTo\": \"https://example.com/create-some-reply\"\n }\n}\n</code></pre>"},{"location":"fep/0391/relationshipProof/","title":"relationshipProof https://w3id.org/fep/0391/relationshipProof","text":"<p>Provides proof that the current relationship is reciprocally claimed.</p> <p>Domain : Relationship</p> <p>Range : Relationship | Add | Follow? | Accept Follow?</p> <p>Status : Experimental</p> <p>Example 1:</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"following\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowing\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowedBy\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-followers\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow-accepted\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Accept\",\n \"object\": {\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/2\"\n }\n }\n ]\n}\n</code></pre> <p>Example 2:</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowedBy\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowing\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/1\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/03c1/","title":"FEP-03c1: Actors without acct-URI","text":"Authors Status Date received Tracking issue Discussions Repository helge @helge@mymath.rocks <code>DRAFT</code> 2023-11-10 #205 - codeberg"},{"location":"fep/03c1/#summary","title":"Summary","text":"<p>Most current Fediverse applications use an acct-URI as unique display name for actors. Usually, this display is done by displaying <code>acct:user@domain.example</code> as <code>@user@domain.example</code>. This FEP states that if there is no acct-URI associated with an actor, the actor should be displayed as its id. So the actor with id <code>https://actor.example/path</code> will be displayed as <code>https://actor.example/path</code>.</p> <p>In addition to the example below, we wish to point out that further independence of webfinger will enable new features such as using domain names as handles.</p>"},{"location":"fep/03c1/#example-rss","title":"Example: RSS","text":"<p>Consider building an application that posts new entries of an RSS feed to the Fediverse. Let's assume we are interested in the updates of the bovine python library available through RSS at https://pypi.org/rss/project/bovine/releases.xml. Then an uri for this actor might look like</p> <pre><code>https://rss.example/actor?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\n</code></pre> <p>This would enable <code>rss.example</code> to automatically generate the respective actor and generate an actor object like</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://rss.example/actor?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"type\": \"Service\",\n \"inbox\": \"https://rss.example/inbox?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"outbox\": \"https://rss.example/outbox?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"followers\": \"https://rss.example/followers?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"name\": \"PyPI recent updates for bovine\",\n \"url\": \"https://pypi.org/project/bovine/\",\n \"summary\": \"Recent updates to the Python Package Index for bovine\"\n}\n</code></pre> <p>There is now no obvious choice for <code>preferredUsername</code> and thus acct-URI. If one wanted to create one, the best choice would probably be to hash the url of the feed. This unfortunately has the consequence of not generating a human readable id, but quite the opposite.</p>"},{"location":"fep/03c1/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/044f/","title":"FEP-044f: Consent-respecting quote posts","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com <code>DRAFT</code> 2025-04-03 #547 Discussions codeberg"},{"location":"fep/044f/#summary","title":"Summary","text":"<p>This document proposes a representation of quote posts that allows verifying consent of the quoted user, through a revocable authorization mechanism, as well as a representation of the user's choice regarding whetheir their posts can be quoted and by whom.</p> <p>The approval mechanism defined in this document is systematic and required for all quotes except self-quotes, but as with <code>Follow</code> and <code>Accept</code>, approval can be granted automatically depending on the user's choice.</p>"},{"location":"fep/044f/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].</p> <p>In the remaining of this document, \u201cquoted object\u201d refers to the object being quoted, \u201coriginal author\u201d to its author, and \u201cquote post\u201d refers to the object quoting the \u201cquoted object\u201d.</p>"},{"location":"fep/044f/#acknowledgments","title":"Acknowledgments","text":"<p>(This section is non-normative.)</p> <p>This proposal has benefitted from significant discussions on SocialHub as well as discussions with trwnh and GoToSocial developers. In fact, the <code>interactionPolicy</code> vocabulary directly comes from GoToSocial's interaction policies which have since evolved along the current proposal.</p>"},{"location":"fep/044f/#quote","title":"Representation of a quote post","text":"<p>A \u201cquote post\u201d is represented as an object with a <code>quote</code> (<code>https://w3id.org/fep/044f#quote</code>) attribute.</p>"},{"location":"fep/044f/#example","title":"Example","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post&lt;span class=\\\"quote-inline\\\"&gt;&lt;br/&gt;RE: &lt;a href=\\\"https://example.com/users/alice/statuses/1\\\"&gt;https://example.com/users/alice/statuses/1&lt;/a&gt;&lt;/span&gt;\",\n \"quote\": \"https://example.com/users/alice/statuses/1\"\n}\n</code></pre> <p>This example is non-normative, and the <code>&lt;span class=\\\"quote-inline\\\"&gt;&lt;br/&gt;RE: &lt;a href=\\\"https://example.com/users/alice/statuses/1\\\"&gt;https://example.com/users/alice/statuses/1&lt;/a&gt;&lt;/span&gt;</code> part of the content is an example of textual fallback, but does not otherwise carry meaning. In particular, it does not influence where the embedded quote should be displayed.</p>"},{"location":"fep/044f/#compatibility-with-other-quote-implementations","title":"Compatibility with other quote implementations","text":"<p>(This section is non-normative.)</p> <p>While this FEP introduces <code>https://w3id.org/fep/044f#quote</code>, there are competing definitions for the representation of quote posts: - <code>_misskey_quote</code> (<code>https://misskey-hub.net/ns/#_misskey_quote</code>) - <code>quoteUrl</code> (<code>https://www.w3.org/ns/activitystreams#quoteUrl</code>) - <code>quoteUri</code> (<code>http://fedibird.com/ns#quoteUri</code>) - FEP-e232 Object links with a <code>https://misskey-hub.net/ns/#_misskey_quote</code> <code>rel</code> value</p> <p>We believe each of those to have significant drawbacks, such as re-using a namespace that has no definition for them, implying the value is an URL or URI, or using an unusual naming scheme, and none of them are linked to a control mechanism like the one defined in this FEP, hence why we introduced <code>https://w3id.org/fep/044f#quote</code>.</p> <p>That being said, we suggest some of them as fallback for compatibility with existing fediverse software implementations.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quoteUrl\": \"as:quoteUrl\",\n \"quoteUri\": \"http://fedibird.com/ns#quoteUri\",\n \"_misskey_quote\": \"https://misskey-hub.net/ns/#_misskey_quote\",\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post&lt;span class=\\\"quote-inline\\\"&gt;&lt;br/&gt;RE: &lt;a href=\\\"https://example.com/users/alice/statuses/1\\\"&gt;https://example.com/users/alice/statuses/1&lt;/a&gt;&lt;/span&gt;\",\n \"quote\": \"https://example.com/users/alice/statuses/1\",\n \"quoteUrl\": \"https://example.com/users/alice/statuses/1\",\n \"quoteUri\": \"https://example.com/users/alice/statuses/1\",\n \"_misskey_quote\": \"https://example.com/users/alice/statuses/1\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"rel\": \"https://misskey-hub.net/ns#_misskey_quote\",\n \"href\": \"https://example.com/users/alice/statuses/1\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/044f/#advertising-a-quote-policy","title":"Advertising a quote policy","text":"<p>Users may not want their posts to be quoted, or not by everyone. To allow users to express that, we re-use GoToSocial's interaction policies and define a <code>canQuote</code> sub-policy.</p> <p>Each quotable object advertises an <code>interactionPolicy</code> (<code>https://gotosocial.org/ns#interactionPolicy</code>) with a <code>canQuote</code> (<code>https://gotosocial.org/ns#canQuote</code>) sub-policy.</p> <p>A sub-policy is defined by two attributes: - <code>automaticApproval</code> (<code>https://gotosocial.org/ns#automaticApproval</code>): an array of <code>Actor</code> and <code>Collection</code> of <code>Actor</code> objects from whom interactions are expected to be automatically approved - <code>manualApproval</code> (<code>https://gotosocial.org/ns#manualApproval</code>): an array of <code>Actor</code> and <code>Collection</code> of <code>Actor</code> objects from whom interactions are subject to manual review</p> <p>Interactions from actors that are neither in <code>automaticApproval</code> nor <code>manualApproval</code> are expected to never be approved.</p> <p>To advertise a policy of disallowing all quotes, <code>interactionPolicy.canQuote.automaticApproval</code> SHOULD contain the object author's identifier as its single value. This is because an empty array is equivalent to a missing property under JSON-LD canonicalization.</p> <p><code>automaticApproval</code> and <code>manualApproval</code> SHOULD be restricted to individual actors, the special public collection <code>https://www.w3.org/ns/activitystreams#Public</code>, the author's <code>followers</code> collection, and the author's <code>following</code> collection.</p> <p>Note that the policy is entirely advisory. It SHOULD be used to provide user interface hints such as enabling a \u201cQuote\u201d button or explaining why an object cannot be quoted, but it MUST NOT be used to verify whether a quote post is valid. See later sections for the actual verification mechanism.</p>"},{"location":"fep/044f/#example_1","title":"Example","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gts\": \"https://gotosocial.org/ns#\",\n \"interactionPolicy\": {\n \"@id\": \"gts:interactionPolicy\",\n \"@type\": \"@id\"\n },\n \"canQuote\": {\n \"@id\": \"gts:canQuote\",\n \"@type\": \"@id\"\n },\n \"automaticApproval\": {\n \"@id\": \"gts:automaticApproval\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"interactionPolicy\": {\n \"canQuote\": {\n \"automaticApproval\": \"https://example.com/users/alice/followers\"\n }\n },\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/alice/statuses/1\",\n \"attributedTo\": \"https://example.com/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"content\": \"I allow my followers to quote this post\"\n}\n</code></pre>"},{"location":"fep/044f/#QuoteAuthorization","title":"Approval stamps","text":"<p>In order to enforce a policy, we rely on approval stamps, a mechanism used to tell third-party servers that a quote is approved, regardless of the current state of the policy.</p> <p>Quote approval stamps are objects of the type <code>QuoteAuthorization</code> (<code>https://w3id.org/fep/044f#QuoteAuthorization</code>), with <code>interactingObject</code> (<code>https://gotosocial.org/ns#interactingObject</code>), <code>interactionTarget</code> (<code>https://gotosocial.org/ns#interactionTarget</code>) and <code>attributedTo</code> attributes.</p> <p>The <code>interactingObject</code> attribute MUST reference the accepted quote post, the <code>interactionTarget</code> attribute MUST reference the quoted object, and the <code>attributedTo</code> attribute MUST correspond to the author of the quoted object.</p> <p>A <code>QuoteAuthorization</code> object MUST be dereferenceable by all parties allowed to see the original post, and MAY be publicly dereferenceable. It MUST NOT embed its <code>interactingObject</code> as to avoid possible information leaks. For the same reason, it MUST NOT embed its <code>interactionTarget</code> object if the server is unable to verify that the party dereferencing the object has permission to see the quoted object.</p> <p>When a third-party attempts to dereference the <code>QuoteAuthorization</code>, the <code>interactionTarget</code> MAY be inlined if the third-party has permission to access the quoted object. This is so that the third-party does not have to perform a second request to access the quoted object.</p>"},{"location":"fep/044f/#example-of-quoteauthorization","title":"Example of <code>QuoteAuthorization</code>","text":"<p>The following stamp can be used to prove that actor <code>https://example.com/users/alice</code> has accepted <code>https://example.org/users/bob/statuses/1</code> as a quote of her post <code>https://example.com/users/alice/statuses/1</code>:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteAuthorization\": \"https://w3id.org/fep/044f#QuoteAuthorization\",\n \"gts\": \"https://gotosocial.org/ns#\",\n \"interactingObject\": {\n \"@id\": \"gts:interactingObject\",\n \"@type\": \"@id\"\n },\n \"interactionTarget\": {\n \"@id\": \"gts:interactionTarget\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"QuoteAuthorization\",\n \"id\": \"https://example.com/users/alice/stamps/1\",\n \"attributedTo\": \"https://example.com/users/alice\",\n \"interactingObject\": \"https://example.org/users/bob/statuses/1\",\n \"interactionTarget\": \"https://example.com/users/alice/statuses/1\"\n}\n</code></pre>"},{"location":"fep/044f/#verifying-a-quoteauthorization","title":"Verifying a <code>QuoteAuthorization</code>","text":"<p>To be considered valid for a particular quote post, a <code>QuoteAuthorization</code> MUST satisfy the following properties: - its <code>interactingObject</code> is the quote post under consideration - its <code>interactionTarget</code> property is the quoted object - its <code>attributedTo</code> property is the author of its <code>interactionTarget</code> - the authenticity of the <code>QuoteAuthorization</code> object can be asserted</p>"},{"location":"fep/044f/#revoking-a-quoteauthorization","title":"Revoking a <code>QuoteAuthorization</code>","text":"<p>An approval stamp can be revoked by <code>Delete</code>ing the stamp.</p>"},{"location":"fep/044f/#QuoteRequest","title":"<code>QuoteRequest</code> request activity","text":"<p>The <code>QuoteRequest</code> (<code>https://w3id.org/fep/044f#QuoteRequest</code>) activity type is introduced to request approval for a quote post.</p> <p>The <code>QuoteRequest</code> activity uses the <code>object</code> property to refer to the quoted object, and the <code>instrument</code> property to refer to the quote post.</p>"},{"location":"fep/044f/#example-quoterequest-activity","title":"Example <code>QuoteRequest</code> activity","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteRequest\": \"https://w3id.org/fep/044f#QuoteRequest\",\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": {\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post&lt;br/&gt;RE: https://example.com/users/alice/statuses/1\",\n \"quote\": \"https://example.com/users/alice/statuses/1\"\n }\n}\n</code></pre>"},{"location":"fep/044f/#receiving-accepting-or-rejecting-quote-posts","title":"Receiving, accepting or rejecting quote posts","text":"<p>When receiving a <code>QuoteRequest</code> activity, the original author decides (either manually or automatically) whether the quote is acceptable. Software that automatically accepts quotes on the author's behalf should notify the author of such quotes according to their notification settings.</p> <p>The receiving end MAY inspect the <code>instrument</code> of the <code>QuoteRequest</code> itself to decide whether it is acceptable.</p> <p>If the quote post is considered acceptable, the original author MUST reply with an <code>Accept</code> activity with the <code>QuoteRequest</code> activity as its object, and a <code>QuoteAuthorization</code> as its <code>result</code>.</p> <p>If the quote post is considered unacceptable, the authority SHOULD reply with a <code>Reject</code> activity with the <code>QuoteRequest</code> activity as its object.</p>"},{"location":"fep/044f/#example-accept","title":"Example <code>Accept</code>","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteRequest\": \"https://w3id.org/fep/044f#QuoteRequest\"\n }\n ],\n \"type\": \"Accept\",\n \"to\": \"https://example.com/users/bob\",\n \"id\": \"https://example.com/users/alice/activities/1234\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": \"https://example.org/users/bob/statuses/1\"\n },\n \"result\": \"https://example.com/users/alice/stamps/1\"\n}\n</code></pre>"},{"location":"fep/044f/#example-reject","title":"Example <code>Reject</code>","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"Quote\": \"https://w3id.org/fep/044f#QuoteRequest\"\n }\n ],\n \"type\": \"Reject\",\n \"to\": \"https://example.com/users/bob\",\n \"id\": \"https://example.com/users/alice/activities/1234\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": \"https://example.org/users/bob/statuses/1\"\n }\n}\n</code></pre>"},{"location":"fep/044f/#quoteAuthorization","title":"Requesting, obtaining and validating approval","text":"<p>In order to get approval, the quote post author MUST send a <code>QuoteRequest</code> (<code>https://w3id.org/fep/044f#QuoteRequest</code>) activity to the author of the quoted object, with the quoted object as its <code>object</code> property and the quote post as its <code>instrument</code>.</p> <p>The quote post SHOULD be inlined in the <code>instrument</code> property and, if not, it SHOULD dereferenceable by the recipient at this point, as the author of the quoted object may want to inspect it to decide whether to accept the quote.</p> <p>The quote post author MAY wait until they receive an <code>Accept</code> or <code>Reject</code> activity before sending the post's <code>Create</code> activity to its intended audience. Doing so is possible for ActivityPub servers that implement the current proposal, and avoids having to issue an <code>Update</code> soon afterwards the <code>Create</code> for the quote post. It is however not possible to implement for ActivityPub clients, which will likely need to issue a <code>Create</code> before the <code>QuoteRequest</code> activity.</p>"},{"location":"fep/044f/#rejection","title":"Rejection","text":"<p>If the author of the quote post receives a <code>Reject</code> activity from the quoted object's author to their <code>QuoteRequest</code> activity, they MUST consider the quote post to be explicitly rejected.</p> <p>If the implementation waits for the <code>Accept</code> before issuing a <code>Create</code>, this MAY translate as the inability to publish the quote post.</p> <p>Otherwise, it MAY translate as a <code>Delete</code> to outright remove the quote post, or an <code>Update</code> to remove the quote part from the post.</p>"},{"location":"fep/044f/#acceptance","title":"Acceptance","text":"<p>If the author of the quote receives an <code>Accept</code> activity, they MUST add a reference to its <code>result</code> in the <code>quoteAuthorization</code> (<code>https://w3id.org/fep/044f#quoteAuthorization</code>) property.</p> <p>Depending on whether they already sent a <code>Create</code> activity to the quote post's intended audience, they SHOULD send a <code>Create</code> activity or an <code>Update</code> activity with the updated <code>quoteAuthorization</code> property.</p>"},{"location":"fep/044f/#example-updated-note-object","title":"Example updated <code>Note</code> object","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quote\": \"https://w3id.org/fep/044f#quote\",\n \"quoteAuthorization\": {\n \"@id\": \"https://w3id.org/fep/044f#quoteAuthorization\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post&lt;br/&gt;RE: https://example.com/users/alice/statuses/1\",\n \"quote\": \"https://example.com/users/alice/statuses/1\",\n \"quoteAuthorization\": \"https://example.com/users/alice/stamps/1\"\n}\n</code></pre>"},{"location":"fep/044f/#verifying-third-party-quote-posts","title":"Verifying third-party quote posts","text":"<p>When processing a quote post from a remote actor, a recipient MUST consider them unapproved unless any of those conditions apply: - the author of the quote post and that of the original post are the same (same <code>attributedTo</code>) - <code>quoteAuthorization</code> exists, can be dereferenced and is a valid <code>QuoteAuthorization</code> activity for this object</p>"},{"location":"fep/044f/#revocation-of-a-quote-post","title":"Revocation of a quote post","text":""},{"location":"fep/044f/#revoking-a-previously-accepted-quote-post","title":"Revoking a previously-accepted quote post","text":"<p>The original author may want to perform /a posteriori/ moderation of the quote posts, or block a quote poster in particular.</p> <p>To do this, the original actor MUST <code>Delete</code> the <code>QuoteAuthorization</code>. They SHOULD send the <code>Delete</code> activity to the quote post's author and any recipient it has reasons to think has accessed the quote post.</p> <p>The original author MUST NOT embed the <code>object</code> nor the <code>target</code> of the <code>QuoteAuthorization</code>, so as to avoid potential information leakage.</p>"},{"location":"fep/044f/#handling-a-revocation","title":"Handling a revocation","text":"<p>Upon receiving a <code>Delete</code> activity for a previously-verified <code>QuoteAuthorization</code>, third-parties MUST check that the <code>Delete</code> is valid and MUST subsequently consider the quote post unapproved.</p> <p>Additionally, if the recipient owns the quote post, it MUST forward the <code>Delete</code> to the audience of the quote post.</p>"},{"location":"fep/044f/#opportunistic-re-verification-of-quote-approvals","title":"Opportunistic re-verification of quote approvals","text":"<p>Because getting revocation properly forwarded depends on the good will of the revoked post's author, it may be necessary to have other means of checking whether an approval has been revoked.</p> <p>For this reason, recipients SHOULD re-check the <code>quoteAuthorization</code> document when an already-known quote post is accessed for the first time in a given period of time.</p>"},{"location":"fep/044f/#server-behavior-considerations","title":"Server behavior considerations","text":"<p>This proposal has been made with great care to not require new server behavior, allowing ActivityPub clients to implement this proposal without requiring generic ActivityPub server software to implement additional logic.</p> <p>In particular, this is the reason the approval stamp is a separate object rather than the <code>Accept</code> itself. Indeed, nothing in the ActivityPub specification would cause a <code>Reject</code> or <code>Undo</code> activity to invalidate the <code>Accept</code> activity itself, which means it would not be suitable as an approval stamp. While ActivityPub does not technically forbid <code>Accept</code> activities to be the target of a <code>Delete</code> activity, we have found no precedent for that, and we anticipate that deleting activities might not be correctly handled across the fediverse. For this reason, we opted to use a separate object that can be directly managed by an ActivityPub client, for instance by issuing a <code>Create</code> activity ahead of sending the <code>Accept</code> activity, and that can be deleted with the usual mechanism.</p> <p>An alternative approach we considered is using a dedicated endpoint to check for approval of a quote. This would effectively allow externalizing approval verification to a separate mechanism, but while this would not require new server behavior, this would still require a new server component to be specified, which is why we opted for the mechanism described in this specification instead.</p>"},{"location":"fep/044f/#backward-compatibility-considerations","title":"Backward compatibility considerations","text":"<p>(This section is non-normative.)</p> <p>To clients that do not implement this FEP, quote posts are seen as regular posts with no explicit relation with the quoted post. While this is preferable to having the quoted post relayed without the comment, this is still lacking significant semantic context.</p> <p>Therefore, quote posts should be authored in such a way that their contents include a reference to the quoted post, e.g. by adding <code>&lt;span class=\\\"quote-inline\\\"&gt;&lt;br/&gt;RE: &lt;a href=\\\"https://example.com/users/alice/statuses/1\\\"&gt;https://example.com/users/alice/statuses/1&lt;/a&gt;&lt;/span&gt;</code>.</p> <p>Using a special class like <code>quote-inline</code> can be useful to hide redundant information information when the post is detected to have an attached quote.</p>"},{"location":"fep/044f/#security-considerations","title":"Security considerations","text":"<p>(This section is non-normative.)</p> <p>Servers that do not implementing the current FEP will still be able to quote the post without providing any dogpiling-reducing friction. There is unfortunately nothing we can do about that. However, servers which do implement the current FEP should refuse displaying those quotes, so implementing the control mechanisms of the current FEP remains worthwhile.</p> <p>The current FEP does not specify behavior around the visibility of quoted posts, but implementations should pay special attention to not inadvertently expose the contents of a quoted post to users who are not allowed to see it. In particular, implementations should ensure that any restriction based on user blocks, or follow relationships are still respected. Likewise, implementations should pay attention so that any discoverability, searchability or privacy setting, such as FEP-5feb, are still respected. To reduce risk of accidental exposure through third-party servers with insufficient visibility checks, it is recommended to not allow quoting posts with a wider allowed audience than that of the quoted post.</p> <p>Effectively revoking authorized quote posts relies on the participation of the quote poster's server to effectively reach the audience of the quote post. This means that an ill-intentioned server which obtained an authorization could deliberately refuse to forward the revocation. Still, the ability to revoke a quote post remains useful between well-intentioned servers, and opportunistic re-verification of quote approvals should also help with discovering that a quote authorization has been revoked, despite the potential lack of forwarding.</p> <p>By not adding a hash or copy of the reply in the <code>QuoteAuthorization</code> object, malicious actors could exploit this in a split horizon setting, sending different versions of the same activity to different actors. This is, however, already a concern in pretty much all contexts in ActivityPub, and enshrining that information in the <code>QuoteAuthorization</code> object would have many drawbacks: - significantly more complex implementation - inability to change the JSON-LD representation after the fact - possibly leaking private information if the <code>QuoteAuthorization</code> object is publicly dereferenceable</p>"},{"location":"fep/044f/#implementations","title":"Implementations","text":"<ul> <li>ActivityBot can always be quoted, but does not itself create quote posts</li> <li>Almost Static ActivityPub can always be quoted, but does not itself create quote posts</li> <li>BadgeFed can always be quoted, but does not itself create quote posts</li> <li>Bonfire can quote and be quoted, customize quote policies, and revoke quotes</li> <li>Daily Rucks can always be quoted, but does not itself create quote posts</li> <li>dotmakeup can always be quoted and issues verifications for quotes between local accounts</li> <li>Friendica can quote and be quoted, with a non-modifiable automatic public quote permission</li> <li>Iceshrimp.NET can quote and be quoted, with a non-modifiable automatic public quote permission</li> <li>ktistec can quote and be quoted, customize quote policies, and revoke quotes</li> <li>Loops can always be quoted, with a non-modifiable automatic public quote permission</li> <li>Mastodon 4.4 has added support for verifying and displaying remote quote posts; version 4.5 introduced support for authoring quote posts and setting quote policies (restricted to automatic approval, with either <code>as:Public</code>, the author's <code>followers</code> collections, or nobody)</li> <li>Micro.blog can always be quoted, but allows users to revoke specific quotes</li> <li>PieFed can always be quoted, with a non-modifiable automatic public quote permission</li> <li>Smithereen exposes always-allowed quote policies, supports authoring quote posts as well as issuing <code>QuoteAuthorization</code> in response to a <code>QuoteRequest</code>, it does not currently verify quotes nor request stamps</li> <li>WordPress ActivityPub can quote and be quoted, interaction policies can be customized during post authoring</li> </ul>"},{"location":"fep/044f/#references","title":"References","text":"<ul> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> </ul>"},{"location":"fep/044f/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/0499/","title":"FEP-0499: Delivering to multiple inboxes with a multibox endpoint","text":"Authors Status Date received Tracking issue Discussions Repository a <code>DRAFT</code> 2024-09-30 #402 Discussions codeberg"},{"location":"fep/0499/#summary","title":"Summary","text":"<p>This FEP introduces a server-wide endpoint for delivering activities to multiple inboxes. sharedInbox currently allows for doing this, but it requires the remote server to know how to deliver the activity based on its addressing properties. However, the remote server might not know how to deliver the activity to private recipients, or recipients within a collection. The multibox endpoint removes this knowledge requirement from the receiving server and instead makes the sending server responsible for marking inboxes to explicitly deliver to.</p>"},{"location":"fep/0499/#motivation","title":"Motivation","text":"<p>(This section is non-normative.)</p> <p>sharedInbox allows servers to reduce network traffic when delivering Public activities to multiple recipients, but it doesn't work when delivering to collections of actors, or when delivering to private audiences using bto or bcc. If an activity is addressed to a collection and that activity is delivered to a remote sharedInbox endpoint, then this introduces a requirement for the remote server to know the contents of the collection (or at least the local subset of its contents). In the common case of delivering activities addressing followers collections to a sharedInbox endpoint, then the remote server must first recognize that this collection id is specifically a followers collection, and then guess which local actors are following that activity's actor. This is prone to issues and can also have disastrous outcomes when follower state drifts out of sync.</p> <p>We can eliminate the dependency on shared follower state and allow for delivery of non-Public activities by introducing a new endpoint that likewise allows delivering to multiple inboxes, but without requiring the remote server to know anything. This enables addressing arbitrary collections as well as the use of <code>bto</code> and <code>bcc</code> more efficiently.</p>"},{"location":"fep/0499/#prior-art","title":"Prior art","text":"<p>(This section is non-normative.)</p> <p>There are various advantages described in the original multibox proposal:</p> <p>Shared Inbox provides the ability for server to server communication traffic to be reduced from R requests, where R is the number of recipients, to a single HTTP request. This is a desirable property as it reduces the amount of HTTP round trips for both the sender and receiver. Unfortunately the design of Shared Inboxes as described in the ActivityPub specification makes it very easy for a spammer to abuse the system by not requiring explicit delivery recipients. We propose an alternative to Shared Inbox called MultiBox that keeps the desirable properties of Shared Inbox while protecting against scenarios in which the sender uses Shared Inbox to \"spam\" a server.</p> <p>Like Shared Inbox, MultiBox consists of a single HTTP endpoint for multiple Actors. Unlike Shared Inbox, in a MultiBox request, each recipient is explicitly listed by Inbox, requiring both the knowledge of the Actor and a corresponding Inbox for that actor. This information is transmitted through the use of an HTTP header Audience where each Inbox is listed using comma separated values.</p> <p>This has two advantages over Shared Inbox. Used on its own, it eliminates the vulnerability mentioned previously whereby recipients to a message do not need to be listed. If this proposal is adopted alongside the Object-Capabilities Based Inbox proposal (4.5), the advantages multiply as we also gain the ability to appropriately filter incoming messages according to the criteria set out by the specific Inboxes, as well as letting us know the origin of each Inbox.</p> <p>For the sender, the additional computing resources required to send a MultiBox request are minimal, but doing so would make mass-messages expensive for senders wishing to abuse the system.</p> <p>One open question on this proposal is that if we use the HTTP header Audience to store the list of recipients, this may result in a limitation. HTTP header sizes are not explicitly capped at the protocol level but implementations often cap them at different lengths- 4Kb for the Nginx web server or 8Kb for Apache.</p> <p>This would limit the number of per message recipients, though this limitation would rarely be reached. An alternative to this proposal would be a new MultiBox object encapsulating the Audience field and the ~Activity.</p>"},{"location":"fep/0499/#proposal","title":"Proposal","text":"<p>(This section is non-normative.)</p> <p>This FEP uses the \"alternative\" approach of putting inboxes in the body of the POST request, instead of using an HTTP header. Whereas headers are limited to possibly as low as 4Kb by default, the limit on POST request bodies is usually much higher; Nginx by default uses a 1MB limit for POST requests. This is the difference between 4000 characters versus 1 million characters.</p>"},{"location":"fep/0499/#specification","title":"Specification","text":"<p>Actors MAY have a <code>multibox</code> endpoint which is available for efficient delivery of activities to multiple inboxes on the same domain.</p> <p>A server MAY reduce the number of delivery requests by identifying all recipients which share the same <code>multibox</code> who would otherwise be delivered to individually, and deliver an activity of the following form to their shared <code>multibox</code> endpoint:</p> <ul> <li>The type MUST be <code>Add</code>.</li> <li>The <code>object</code> MUST be the activity being delivered.</li> <li>The <code>target</code> MUST be all inboxes being delivered to.</li> </ul> <p>Upon receipt of such an activity, the receiving server MUST add the activity in <code>object</code> into all local inboxes specified in <code>target</code>, although it MAY filter delivery to certain inboxes according to implementation-specific rules (for example, spam filtering).</p>"},{"location":"fep/0499/#examples","title":"Examples","text":"<p>(This section is non-normative.)</p> <p>Discovering the multibox endpoint:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n</code></pre> <p>Alternatively, without a context declaration:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\n</code></pre> <p>Delivering to the multibox endpoint:</p> <pre><code>POST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n</code></pre> <p>For transient activities or for cases not requiring a fetch (e.g. no access control), you can embed the activity inline:</p> <pre><code>POST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": {\n \"@context\": \"https://context.example\"\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"InGameNotification\",\n \"content\": \"The payload is nearing the checkpoint!\",\n \"generator\": \"https://game.example\"\n },\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n</code></pre>"},{"location":"fep/0499/#terms-defined","title":"Terms defined","text":"multibox URI <code>https://w3id.org/fep/0499/multibox</code> Label multibox endpoint Comment An optional endpoint used for wide delivery of activities to multiple inboxes. POST an Add activity where the object is at least one activity to be delivered, and the target is at least one inbox to deliver to. Domain A map of <code>endpoints</code> Range The endpoint (@id) Required No Functional Yes Is defined by FEP-0499 <pre>\n<code>\n{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n</code>\n</pre> <pre>\n<code>\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\n</code>\n</pre> <pre>\n<code>\nPOST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n</code>\n</pre>"},{"location":"fep/0499/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Christine Lemmer Webber, Rebooting the Web of Trust 9: Keeping Unwanted Messages off the Fediverse &gt; Proposed Suggestions &gt; MultiBox, 2020</li> </ul>"},{"location":"fep/0499/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/0499/multibox/","title":"multibox","text":"<p>URI : <code>https://w3id.org/fep/0499/multibox</code></p> <p>Label : multibox endpoint</p> <p>Comment : An optional endpoint used for wide delivery of activities to multiple inboxes. POST an Add activity where the object is at least one activity to be delivered, and the target is at least one inbox to deliver to.</p> <p>Domain : A map of <code>endpoints</code></p> <p>Range : The endpoint (@id)</p> <p>Required : No</p> <p>Functional : Yes</p> <p>Is defined by : FEP-0499</p>"},{"location":"fep/0499/multibox/#examples","title":"Examples","text":"<p>Discovering a multibox endpoint with additional context</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n</code></pre> <p>Discovering a multibox endpoint without additional context</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\n</code></pre> <p>Delivering to a multibox endpoint</p> <pre><code>POST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n</code></pre>"},{"location":"fep/07d7/","title":"FEP-07d7: A Custom URL Scheme and Web-Based Protocol Handlers for Linking to ActivityPub Resources","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Jennifer Moore contact@jenniferplusplus.com <code>WITHDRAWN</code> 2023-09-22 2025-04-22 #171 - codeberg <ul> <li>Summary</li> <li>Motivation</li> <li>Context</li> <li>Requirements</li> <li>1. Definitions</li> <li>2. URI Scheme<ul> <li>2.1 Intents</li> <li>2.2 Example links using the scheme</li> </ul> </li> <li>3. Protocol Handlers<ul> <li>3.1 Retrieving hyperlinked objects</li> <li>3.2 Displaying hyperlinked objects</li> <li>3.3 Servers</li> <li>3.4 Clients</li> </ul> </li> <li>4. Safety and Security</li> <li>References</li> <li>Copyright</li> </ul>"},{"location":"fep/07d7/#summary","title":"Summary","text":"<p>This specification addresses sometimes difficult interactions with ActivityPub resources hosted on remote servers. It defines a custom URL scheme which can be used by custom web-based protocol handlers to route hyperlinks to those resources to the user's preferred server. It additionally advises when ActivityPub servers can include these links in HTML views they generate, and how clients and servers can implement those web-based protocol handlers.</p>"},{"location":"fep/07d7/#motivation","title":"Motivation","text":"<p>When a person follows a link to some ActivityPub powered site, the browser will navigate to the resource on that site. This is often not what the person would have prefered when they have an account on a different ActivityPub aware site. In order to interact with the linked resource from their own account, the person must separately open their home server and search for the resource. This is an awkward and sometimes confusing process, especially for people who are not already familiar with the idosyncracies of navigating a federated social network.</p> <p>Web-based protocol handling is a feature of modern web browsers. This allows a site to register with the user's browser as a handler for custom URI schemes. The browser will send links using that scheme to the registerd handler. This way, it's possible to direct links to the user's preferred server or application. The handler can retrieve the resource and provide familiar and appropriate presentation. Native ActivityPub clients can also take advantage of these URIs to provide similar handling. </p>"},{"location":"fep/07d7/#context","title":"Context","text":"<p>Similar proposals such as Fedilinks and Mastodon Issue 19679 have been made, and were even briefly implemented in Mastodon. There seems to be some concensus that custom protocol handlers have the technical capability to solve the problem of difficult interactions with cross-instance objects. There is also ample prior art for this approach. <code>mailto:</code> and <code>tel:</code> are common examples from web standards. Zoom uses a custom<code>zoommtg:</code> protocol to launch their desktop app, and Apple uses <code>itms:</code> to launch iTunes.</p> <p>It seems the main impediment to early attempts was a perception of poor UX and limited adoption. This seems to be mostly a chicken-and-egg problem. That will always be a problem, until it's not. In this case, it seems it would be helpful to let standards drive implementation, as the opposite hasn't ocurred. This proposal also recommends behavior that is compatible with gradual adoption.</p> <p>Some similar proposals have also disussed emphasizing outcome, rather than content. Encapsulating action is an understandable desire, and this proposal attempts to facilitate that. But the protocol aspect is firmly focussed on interaction with ActivityPub objects, without making any assumptions about the design or capabilities of current or future ActivityPub services. The hope is that this gives the resulting implementations better longevity. Documents can outlive software, and people should still be able to interact with them in useful ways. This also keeps the proposal scoped just to ActivityPub concerns, without imposing on other standards.</p>"},{"location":"fep/07d7/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/07d7/#1-definitions","title":"1. Definitions","text":"<p>This proposal refers to ActivityPub objects as viewed and represented in multiple ways from multiple servers. For clarity, these are the terms that will be used to describe those scenarios.</p> <p>Origin server means the server that has authority for the ActivityPub object. This is the server that hosts and controls access to the object, and will recieve requests for the URI used as the object's ID.</p> <p>Handler is any software that handles the <code>web+activitypub:</code> scheme and protocol. Typically either an ActivityPub server where the person has an account, or a client application they have installed on their device.</p> <p>ActivityPub object means the JSON-LD document representing an ActivityPub Object, as described in the ActivityPub and ActivityStreams[ActivityStreamsRef] specifications.</p> <p>HTML representation means an HTML or other document rendered to display the ActivityPub object for human use.</p> <p>A client is any software that provides a human-friendly presentation of ActivityPub objects, or can interact with an ActivityPub server. For example, this could be a server's web UI or a native mobile app. This software does not necessarily utilize the C2S profile of the ActivityPub spec.</p>"},{"location":"fep/07d7/#2-uri-scheme","title":"2. URI Scheme","text":"<p>When creating hyperlinks to ActivityPub resources, individuals and applications SHOULD include a link using the custom <code>web+activitypub:</code> scheme. This scheme can be handled by web-based or native handlers registered with browsers by end-users. Because there's no guarantee that a given browser will have any registered handler for this scheme, these links SHOULD NOT be used in place of a link that refers to the resource by ID or an alternative HTML representation of it. Links using the <code>web+activitypub:</code> scheme SHOULD be used as an addition to those more canonical links.</p> <p>The address provided using the <code>web+activitypub:</code> scheme SHOULD be the same as the referenced ActivityPub object's ID. The address MAY instead be for an alternative human-readable address, or for an HTML representation of the object, as normal for the origin server. Applications using or generating these links should be aware that not all clients will be able to dereference those alternative or human readable addresses. The most interoperable choice is to use the object's canonical ActivityPub ID.</p> <p>The linked address MUST replace the scheme used with <code>web+activitypub:</code>. That means the link MUST NOT simply prepend <code>web+activitypub:</code> onto a preexisting <code>https:</code> or other scheme, as this would violate the generic URI syntax specified by RFC-3986</p>"},{"location":"fep/07d7/#21-intents","title":"2.1 Intents","text":"<p>A <code>web+activitypub:</code> link MAY encapsulate an intent for an activity to be performed on or with the linked object. For example, a pre-assembled link could be used to directly Follow an actor or to Announce an object. An encapsulated intent MUST be represented by appending a query string to the target URI. It's possible that the target URI already includes a query string. In this case, to encapsulate an intent, the intent MUST append a new query parameter to the existing query string. A <code>web+activitypub:</code> link MUST NOT encapsulate more than one intent.</p> <p>Including an encapsulated intent is OPTIONAL. Responding to the intent is also OPTIONAL. The creator of the intent should keep in mind that the handler MAY support intents, but does not have to. Also keep in mind that the handler MUST provide a mechanism for the user to confirm or decline to perform the intended action. The use or purpose of the <code>web+activitypub:</code> link MUST NOT require that the intended activity is actually performed.</p> <p>The format of the query parameter to encapsulate an intent is <code>intent=&lt;activity&gt;</code> where <code>&lt;activity&gt;</code> is any of the following activities described by the Activity Streams vocabulary. The activity string SHOULD be all lowercase. It's permissible to use any casing. But, there are many systems in the wild which might intercept or preprocess the URIs in a <code>web+activitypub:</code> link, and not all of those will respect the original casing.</p> <ul> <li>add</li> <li>announce</li> <li>arrive</li> <li>create</li> <li>follow</li> <li>invite</li> <li>like</li> </ul> <p>The <code>web+activitypub:</code> link SHOULD NOT encapsulate an intent for any activity not listed above. In addition, the link MUST NOT encapsulate an intent for any of the following activities. Implementers should exercise discretion regarding activities added as extensions to the ActivityPub specification. You should err on the side taking no action where there is doubt about the safety or security of doing so.</p> <ul> <li>block</li> <li>delete</li> <li>dislike</li> <li>flag</li> <li>ignore</li> <li>leave</li> <li>move</li> <li>offer</li> <li>remove</li> </ul> <p>For security and privacy, <code>web+activitypub:</code> links MUST NOT include a username or password component. They also SHOULD NOT be relative URIs.</p>"},{"location":"fep/07d7/#22-example-links-using-the-scheme","title":"2.2 Example links using the scheme","text":"<p>Below are some examples of <code>web+activitypub:</code> hyperlinks that reflect some expected uses.</p> <p>One-click check in</p> <p>A link to enable one-click check in to pick up a shopping order.</p> <p><code>&lt;a hfref=\"web+activitypub:shopping.example/pickup/12345?intent=arrive\"&gt;Check in to pick up your order&lt;/a&gt;</code></p> <p>One-click follow</p> <p>A link to enable one-click following from another website. The handler must not perform the follow activity unless the user confirms it.</p> <p><code>&lt;a href=\"web+activitypub:uss-enterprise.example/user/picard?intent=follow&gt;Follow me on the fediverse&lt;/a&gt;</code></p> <p>Link with no intent</p> <p>A link to view a blog post published as an ActivityPub Article</p> <p><code>&lt;a href=\"web+activitypub:my-blog.example/article/write-your-first-fep&gt;Read the article on your home instance&lt;/a&gt;</code></p>"},{"location":"fep/07d7/#3-protocol-handlers","title":"3. Protocol Handlers","text":""},{"location":"fep/07d7/#31-retrieving-hyperlinked-objects","title":"3.1 Retrieving hyperlinked objects","text":"<p>ActivityPub applications that can retrieve remote objects and generate an HTML display MAY also implement a web-based protocol handler for the <code>web+activitypub:</code> URI scheme. Applications MAY use other available registration mechanisms, such as Android inents. The handler MUST attempt to load the specified URI as an <code>application/ld+json</code> document. If the origin does not respond with an ActivityPub object, the handler SHOULD fallback to other methods it uses to locate ActivityPub objects, such as webfinger.</p> <p>When resolving the given URI, the handler MUST ignore any provided username or password component, and it MUST exclude those components from the requests it makes to retrieve the object. If the given URI includes a query string with an intent parameter, the handler MUST exclude the intent parameter from the requests made to retrieve the object.</p> <p>The handler MUST NOT attempt to retrieve objects from a relative URI. There's likely no good reason for a <code>web+activitypub:</code> link to target a relative URI, and there is some risk it could be used to facilitate scams or phishing attempts by making it appear some third party has access to data they shouldn't.</p> <p>The handler MUST perform the same sanitization or other safe handling of untrusted URIs as it normally would. For example, there's likely no good reason for a production system to try to load resources from localhost, or using an ip address, rather than a hostname. Doing so could also facilitate phishing or scams.</p> <p>When retreiving resources identified by a <code>web+activitypub:</code> scheme, the handler SHOULD assume the origin uses HTTPS. The handler MAY attempt to use other schemes, such as <code>did:</code>. The handler MAY make this determination using any heuristic or algorithm the developers choose.</p> <p>Other than the above considerations, the handler MUST attempt to resolve the URI exactly as given.</p>"},{"location":"fep/07d7/#32-displaying-hyperlinked-objects","title":"3.2 Displaying hyperlinked objects","text":"<p>After successfully retrieving the referenced object, the handler SHOULD produce an HTML representation of the object and present it for the user. The handler MUST escape and sanitize the content of the object before displaying it, to prevent cross-site scripting attacks.</p> <p>The handler SHOULD display the object in the normal way for the application, and in the normal context. For example, if the application normally displays replies alongside an object, it SHOULD do so in this case as well.</p> <p>The handler SHOULD enable the user to interact with the object normally, such as to generate Like or Announce activities referring to it.</p> <p>When the provided <code>web+activitypub:</code> URI includes an encapsulated intent, the handler MAY prompt the user to perform that intended activity. The handler MUST NOT perform that activity without explicit confirmation from the user. The handler MUST NOT perform or offer to perform the intended activity if it is one of the ones listed below. These activities present an elevated risk of disrupting the user's social connections or enlisting them in malicious activities. The handler MAY perform other activities, but SHOULD do so with care. <code>web+activitypub:</code> links are usually created by third parties. Be mindful that there is some risk of exposing sensitive information or facilitating harmful behavior when third parties are allowed to direct other people's actions.</p> <p>The handler MUST NOT perform any of these activities in response to an ecapsulated intent:</p> <ul> <li>block</li> <li>delete</li> <li>dislike</li> <li>flag</li> <li>ignore</li> <li>leave</li> <li>move</li> <li>offer</li> <li>remove</li> </ul>"},{"location":"fep/07d7/#33-servers","title":"3.3 Servers","text":"<p>ActivityPub servers MAY offer users an option to register with their browser as a <code>web+activitypub:</code> handler. Servers SHOULD NOT do this unless the user has authenticated with the server. The server SHOULD also offer a similar option to de-register as a handler. Servers SHOULD NOT attempt to register as a handler unless initiated by the user in some way. Servers MUST NOT attempt to automatically register as a handler again after a user has declined to allow it.</p>"},{"location":"fep/07d7/#34-clients","title":"3.4 Clients","text":"<p>Clients MAY offer users an option to register as handlers for <code>web+activitypub:</code> links. They SHOULD use whatever mechanism is appropriate to the client to do so. For instance, Android apps would likely use the intent system, and Windows apps could set the appropriate registry flags. Clients SHOULD provide a mechanism to de-register as a handler, where possible. Clients MAY defer this capability to the operating system as appropriate.</p> <p>If a client will retrieve the linked object itself, the client MUST adhere to the same specifications from section 3.1 and 3.2 as any other application would.</p>"},{"location":"fep/07d7/#4-safety-and-security","title":"4. Safety and Security","text":"<p>Because there is no way to know or control which application will ultimately handle any <code>web+activitypub:</code> link, creators SHOULD NOT include private or sensitive information in the link. Creators MUST NOT include any type of authenticating token or credential in the link. And handling applications themselves MUST apply all the normal and appropriate access controls when retrieving and displaying resources linked this way.</p>"},{"location":"fep/07d7/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[Web-based Protocol Handlers] Mozilla Developer Network, Web-based Protocol Handlers</li> <li>[HTML Living Standard] WHATWG, HTML, 2023</li> <li>[Fedilinks] Fedilinks Authors, Fedilinks</li> </ul>"},{"location":"fep/07d7/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/0837/","title":"FEP-0837: Federated Marketplace","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2023-08-17 #147 Discussions codeberg"},{"location":"fep/0837/#summary","title":"Summary","text":"<p>This document describes a minimal implementation of a federated marketplace based on ActivityPub protocol and Valueflows vocabulary. In such marketplace actors can publish offers and requests, respond to offers and requests published by other actors, enter into agreements and exchange information necessary to complete these agreements.</p>"},{"location":"fep/0837/#history","title":"History","text":"<p>Extension of ActivityPub protocol with Valueflows vocabulary was initially proposed by Lynn Foster in FEP-d767.</p>"},{"location":"fep/0837/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/0837/#overview","title":"Overview","text":"<pre><code>sequenceDiagram\n actor Alice\n actor Bob\n Note right of Alice: Alice publishes a Proposal\n Bob -&gt;&gt; Alice: Bob sends Offer(Agreement) activity\n Alice -&gt;&gt; Bob: Alice sends Accept(Agreement) activity\n Note over Alice, Bob: Alice and Bob complete the transaction\n Alice -&gt;&gt; Bob: Alice sends confirmation activity</code></pre>"},{"location":"fep/0837/#proposals","title":"Proposals","text":"<p>Valueflows defines proposals as published requests or offers, sometimes with what is expected in return.</p> <p>The representation of a proposal is a JSON document with the following properties:</p> <ul> <li><code>id</code> (REQUIRED): the proposal's unique global identifier.</li> <li><code>type</code> (REQUIRED): the type of the object SHOULD be <code>Proposal</code>. If interoperability with other ActivityPub services is desirable, implementers MAY use object types from Activity Vocabulary, such as <code>Note</code>.</li> <li><code>purpose</code> (REQUIRED): the type of proposal. Possible values are <code>offer</code> and <code>request</code>.</li> <li><code>attributedTo</code> (REQUIRED): the identifier of an actor who published the proposal.</li> <li><code>name</code> (RECOMMENDED): the title of the proposal.</li> <li><code>content</code> (OPTIONAL): the description of the proposal. The type of content SHOULD be <code>text/html</code>.</li> <li><code>published</code> (RECOMMENDED): the date and time at which the proposal was published.</li> <li><code>location</code> (OPTIONAL): indicates a physical location associated with the proposal. The representation of location MUST conform to the recommendations of Activity Vocabulary document, section 5.3 Representing Places.</li> <li><code>publishes</code> (REQUIRED): the primary intent of this proposal (see below).</li> <li><code>reciprocal</code> (OPTIONAL): the reciprocal intent of this proposal (see below).</li> <li><code>unitBased</code> (OPTIONAL): indicates whether the quantities expressed in the proposal can be multiplied or not. The default is <code>false</code>.</li> <li><code>to</code> (REQUIRED): the audience of the proposal.</li> </ul> <p>Intents are proposed economic transactions. The primary intent describes what is being offered or requested, and reciprocal intent describes what is expected or offered in return. Some examples:</p> <ul> <li>A good is offered in exchange for money. Transfer of a good is a primary intent and a money transfer is a reciprocal intent.</li> <li>A good is offered as a gift. Transfer of a good is a primary intent and there's no reciprocal intent.</li> <li>Service is requested in exchange for money. Delivery of a service is a primary intent and money transfer is a reciprocal intent.</li> </ul> <p>The representation of an intent is a JSON document with the following properties:</p> <ul> <li><code>id</code> (REQUIRED): the unique global identifier of the intent. Implementations SHOULD use URI fragments to identify intents associated with a given proposal. The RECOMMENDED fragment identifiers for primary and reciprocal intents are <code>primary</code> and <code>reciprocal</code>.</li> <li><code>type</code> (REQUIRED): the type of the object MUST be <code>Intent</code>.</li> <li><code>action</code> (REQUIRED): the type of economic transaction. The value of this property SHOULD be either <code>deliverService</code> or <code>transfer</code>.</li> <li><code>resourceConformsTo</code> (RECOMMENDED): the type of an economic resource. Could be any URI.</li> <li><code>resourceQuantity</code> (REQUIRED): the amount and unit of the economic resource. This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification. The RECOMMENDED unit for countable items is <code>one</code>.</li> <li><code>hasNumericalValue</code> (OPTIONAL): the amount of the resource. If not specified, arbitrary amounts can be used when responding to the proposal.</li> <li><code>availableQuantity</code> (OPTIONAL): the quantity of the offered resource currently available.</li> <li><code>minimumQuantity</code> (OPTIONAL): the minimum possible quantity of the resource.</li> </ul> <p>Minimal example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Proposal\": \"vf:Proposal\",\n \"Intent\": \"vf:Intent\",\n \"action\": \"vf:action\",\n \"purpose\": \"vf:purpose\",\n \"unitBased\": \"vf:unitBased\",\n \"publishes\": \"vf:publishes\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Proposal\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"purpose\": \"offer\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"publishes\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"action\": \"transfer\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n</code></pre> <p>Full example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Proposal\": \"vf:Proposal\",\n \"Intent\": \"vf:Intent\",\n \"action\": \"vf:action\",\n \"purpose\": \"vf:purpose\",\n \"unitBased\": \"vf:unitBased\",\n \"publishes\": \"vf:publishes\",\n \"reciprocal\": \"vf:reciprocal\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"availableQuantity\": \"vf:availableQuantity\",\n \"minimumQuantity\": \"vf:minimumQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Proposal\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"purpose\": \"offer\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"name\": \"Offering used bike\",\n \"content\": \"Blue one-speed bike, 15 years old, some rust\",\n \"published\": \"2023-06-18T19:22:03.918737Z\",\n \"location\": {\n \"type\": \"Place\",\n \"longitude\": -71.0,\n \"latitude\": 25.0\n },\n \"publishes\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"action\": \"transfer\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q11442\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n },\n \"availableQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n },\n \"minimumQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"reciprocal\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"action\": \"transfer\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q4917\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n },\n \"unitBased\": false,\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n</code></pre>"},{"location":"fep/0837/#publishing-a-proposal","title":"Publishing a proposal","text":"<p>Proposals can be linked to actors (if actor provides a service) or to other objects (if they represent economic resources) using FEP-0ea0 payment links. Proposals can also be added to public collections, or be delivered to actor's followers using <code>Create</code> activity, or announced by group actors.</p> <p>If FEP-0ea0 payment link is used, its <code>href</code> attribute MUST contain the proposal ID and its <code>rel</code> array MUST contain the string <code>https://w3id.org/valueflows/ont/vf#Proposal</code>. The value of <code>mediaType</code> attribute SHOULD be <code>application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"</code>.</p> <p>Example of a proposal attached to an actor via payment link:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://market.example/users/alice\",\n \"inbox\": \"https://market.example/users/alice\",\n \"outbox\": \"https://market.example/users/alice\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Buy a bike\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"rel\": [\"payment\", \"https://w3id.org/valueflows/ont/vf#Proposal\"]\n }\n ]\n}\n</code></pre> <p>Consuming implementations which don't have marketplace features MAY display proposals similarly to <code>Note</code> objects.</p>"},{"location":"fep/0837/#responding-to-a-proposal","title":"Responding to a proposal","text":""},{"location":"fep/0837/#agreements","title":"Agreements","text":"<p>An interested party responds to a proposal and then parties start negotiating to reach an agreement.</p> <p>To respond to a proposal, an interested party MUST send an <code>Agreement</code> object wrapped in <code>Offer</code> activity to the actor indicated by the <code>attributedTo</code> property of the proposal. The proposing party MUST either commit to the action described in the proposal or send a rejection.</p> <p>In the first case, the proposer finalizes the agreement and sends <code>Accept(Offer)</code> activity back to the interested party.</p> <p>In the second case, the proposer sends <code>Reject(Offer)</code> activity. The interested party MAY send <code>Offer(Agreement)</code> activities many times until agreement is reached.</p> <p>The representation of an agreement is a JSON document with the following properties:</p> <ul> <li><code>id</code> (OPTIONAL): the unique global identifier of the agreement. This property is REQUIRED for finalized agreements.</li> <li><code>type</code> (REQUIRED): the type of the object MUST be <code>Agreement</code>.</li> <li><code>attributedTo</code> (OPTIONAL): the identifier of an actor who created the agreement.</li> <li><code>stipulates</code> (REQUIRED): the primary commitment associated with the agreement.</li> <li><code>stipulatesReciprocal</code> (OPTIONAL): the reciprocal commitment associated with the agreement. It is REQUIRED if the corresponding proposal has a reciprocal intent.</li> </ul> <p>Commitments are promised economic transactions. The representation of a commitment is a JSON document with the following properties:</p> <ul> <li><code>id</code> (OPTIONAL): the unique global identifier of the commitment. This property is REQUIRED for commitments in finalized agreements. Implementations SHOULD use URI fragments to identify commitments associated with a given agreement. The RECOMMENDED fragment identifiers for commitments satisfying primary and reciprocal intents of the proposal are <code>primary</code> and <code>reciprocal</code>.</li> <li><code>type</code> (REQUIRED): the type of the object MUST be <code>Commitment</code>.</li> <li><code>satisfies</code> (REQUIRED): the reference to an intent.</li> <li><code>resourceQuantity</code> (REQUIRED): the amount and unit of the economic resource.</li> </ul> <p>The first commitment MUST satisfy the primary intent of the proposal. The second commitment MUST satisfy the reciprocal intent of the proposal (if present).</p> <p>The units specified in the agreement MUST match the units specified in the proposal. If the value of <code>unitBased</code> property of the proposal is <code>false</code>, the amount of resources specified in commitments MUST be equal to amounts specified in the proposal. If the value is <code>true</code>, amounts MUST be multiples of amounts specified in the proposal. If <code>unitBased</code> property is not present on the proposal, arbitrary amounts can be used.</p> <p>Example of an <code>Offer(Agreement)</code> activity:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Agreement\": \"vf:Agreement\",\n \"stipulates\": \"vf:stipulates\",\n \"stipulatesReciprocal\": \"vf:stipulatesReciprocal\",\n \"Commitment\": \"vf:Commitment\",\n \"satisfies\": \"vf:satisfies\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Offer\",\n \"id\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"actor\": \"https://social.example/users/bob\",\n \"object\": {\n \"type\": \"Agreement\",\n \"stipulates\": {\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"stipulatesReciprocal\": {\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n }\n },\n \"to\": \"https://market.example/users/alice\"\n}\n</code></pre>"},{"location":"fep/0837/#accepting-an-agreement","title":"Accepting an agreement","text":"<p>The <code>object</code> of <code>Accept</code> activity MUST be the <code>id</code> of the <code>Offer</code> activity previously sent to the actor.</p> <p><code>Accept</code> activity MUST have the <code>result</code> property containing the <code>Agreement</code> object. The finalized agreement and corresponding commitments MUST have an <code>id</code> property. If a similar agreement between parties already exists, it MAY be updated and its <code>id</code> re-used. The finalized agreement MUST have an <code>attributedTo</code> property and its value MUST match the actor of the <code>Accept</code> activity.</p> <p>The quantities specified in the finalized agreement MUST match the quantities specified in <code>Agreement</code> object from the <code>Offer</code> activity.</p> <p>The finalized agreement MAY have <code>url</code> property containing one or more links to resources associated with the agreement. An example of such resource is a payment page (which can be represented as an FEP-0ea0 link).</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Agreement\": \"vf:Agreement\",\n \"stipulates\": \"vf:stipulates\",\n \"stipulatesReciprocal\": \"vf:stipulatesReciprocal\",\n \"Commitment\": \"vf:Commitment\",\n \"satisfies\": \"vf:satisfies\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Accept\",\n \"id\": \"https://market.example/activities/059f08fa-31b1-4136-8d76-5987d705a0ab\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"result\": {\n \"type\": \"Agreement\",\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"stipulates\": {\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2#primary\",\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"stipulatesReciprocal\": {\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2#reciprocal\",\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n },\n \"url\": {\n \"type\": \"Link\",\n \"href\": \"https://pay.example/invoices/7f1f0c81-0108-4c91-9cb1-d38ebccc3aa1\",\n \"rel\": \"payment\"\n }\n },\n \"to\": \"https://social.example/users/bob\"\n}\n</code></pre>"},{"location":"fep/0837/#rejecting-an-agreement","title":"Rejecting an agreement","text":"<p>The <code>object</code> of <code>Reject</code> activity MUST be the <code>id</code> of the <code>Offer</code> activity previously sent to the actor.</p> <p>Activity MAY contain <code>content</code> property indicating the reason for rejection.</p> <p>Example:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://market.example/activities/8c05f97f-1531-4b70-9ca8-4ee4a09f36a4\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"content\": \"Not available\",\n \"to\": \"https://social.example/users/bob\"\n}\n</code></pre>"},{"location":"fep/0837/#confirmations","title":"Confirmations","text":"<p>Economic transaction happens outside the protocol. When both parties complete their parts of the transaction, the proposing party MUST publish a confirmation.</p> <p>The type and structure of confirmation activity may vary between different marketplaces, but it MUST contain a reference to the <code>Agreement</code> object. The <code>context</code> property is RECOMMENDED for this purpose.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Create\",\n \"id\": \"https://market.example/receipts/ad2f7ee1-6567-413e-a10b-72650cbdc743/create\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": {\n \"type\": \"Document\",\n \"id\": \"https://market.example/receipts/ad2f7ee1-6567-413e-a10b-72650cbdc743\",\n \"name\": \"Receipt\",\n \"context\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2\",\n \"published\": \"2023-07-03T14:13:41.843794Z\"\n },\n \"to\": \"https://social.example/users/bob\"\n}\n</code></pre>"},{"location":"fep/0837/#implementations","title":"Implementations","text":"<ul> <li>Mitra</li> </ul>"},{"location":"fep/0837/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>Lynn Foster, elf Pavlik, Bob Haugen, Valueflows, 2023</li> <li>Lynn Foster, FEP-d767: Extend ActivityPub with Valueflows, 2023</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>Hajo Rijgersberg, Don Willems, Xin-Ying Ren, Mari Wigham, Jan Top, Ontology of units of Measure, 2017</li> <li>silverpill, FEP-0ea0: Payment Links, 2023</li> </ul>"},{"location":"fep/0837/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/0ea0/","title":"FEP-0ea0: Payment Links","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2023-04-18 #88 Discussions codeberg"},{"location":"fep/0ea0/#summary","title":"Summary","text":"<p>This FEP describes a way to attach payment information to ActivityPub actors and objects. That information might be a link to donation page, a link for buying an artwork, or anything else that can be represented with a URI.</p>"},{"location":"fep/0ea0/#history","title":"History","text":"<p>PeerTube videos may have <code>support</code> property, which contains a text explaining how to support the content creator.</p> <p>FEP-8c3f: Web Monetization was published in 2022. The ensuing discussion on SocialHub forum led to the creation of this proposal.</p>"},{"location":"fep/0ea0/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/0ea0/#payment-links","title":"Payment links","text":"<p>Payment link is an object with the following properties:</p> <ul> <li><code>type</code> (REQUIRED): the type MUST be <code>Link</code>.</li> <li><code>name</code> (RECOMMENDED): the <code>name</code> property SHOULD contain a human-readable description of the payment link.</li> <li><code>href</code> (REQUIRED): the <code>href</code> property MUST contain a payment URI. This can be a URL of a website, or any other kind of URI, such as 'payto' URI.</li> <li><code>rel</code> (REQUIRED): the <code>rel</code> property MUST contain the string <code>payment</code> or an array containing that string. The <code>payment</code> relation type is defined in Link Relations Registry.</li> </ul> <p>Payment links MUST be added to <code>attachment</code> array of an actor or an object.</p>"},{"location":"fep/0ea0/#examples","title":"Examples","text":"<p>Payment link attached to an actor:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://social.example/users/alice\",\n \"inbox\": \"https://social.example/users/alice/inbox\",\n \"outbox\": \"https://social.example/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Donate\",\n \"href\": \"payto://iban/DE75512108001245126199\",\n \"rel\": \"payment\"\n }\n ]\n}\n</code></pre> <p>Payment link attached to an object:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Image\",\n \"id\": \"https://gallery.example/photos/123\",\n \"attributedTo\": \"https://gallery.example/users/alice\",\n \"name\": \"Painting of a cat\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Buy\",\n \"href\": \"https://gallery.example/photos/123/order\",\n \"rel\": [\n \"payment\",\n \"https://gallery.example/ns#buy\"\n ]\n }\n ]\n}\n</code></pre>"},{"location":"fep/0ea0/#payment-links-as-actor-metadata","title":"Payment links as actor metadata","text":"<p>(This section is non-normative.)</p> <p>Implementers may treat payment links attached to actor object in the same way as actor metadata fields. In that case, <code>name</code> translates into field label and <code>href</code> translates into field value.</p>"},{"location":"fep/0ea0/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>Diogo Peralta Cordeiro, Phablulo Joel, FEP-8c3f: Web Monetization, 2022</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>F. Dold, C. Grothoff, The 'payto' URI Scheme for Payments, 2020</li> <li>IANA, Link Relations, 2005</li> </ul>"},{"location":"fep/0ea0/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/1042/","title":"FEP-1042: Peer to Peer Fediverse Identities","text":"Authors Status Date received Tracking issue Discussions Repository Mauve Signweaver contact@mauve.moe <code>DRAFT</code> 2025-04-03 #546 Discussions codeberg"},{"location":"fep/1042/#summary","title":"Summary","text":"<p>ActivityPub's federated model allows for flexibility in referencing data between different instances. However it requires that these instances be always online and do not allow for non-internet or locally published identities outside of the HTTPS/DNS based web.</p> <p>This document describes how implementors can extend ActivityPub to link to objects hosted on Peer to Peer protocols and how compatible clients should detect this support and load each others' content.</p>"},{"location":"fep/1042/#rationale","title":"Rationale","text":"<p>ActivityPub's federated model allows for flexibility in referencing data between different instances. However it requires that these instances be always online and do not allow for non-internet or locally published identities outside of the HTTPS/DNS based web. By linking to P2P versions of documents from HTTPS versions, we can enable interoperability with existing AP implementations while opening the way to P2P-only networks.</p>"},{"location":"fep/1042/#details","title":"Details","text":""},{"location":"fep/1042/#general-approach","title":"General Approach","text":"<p>There are existing specifications in place for linking to \"alternate\" URLs for content. These are already used by implementations to resolve ActivityPub identities from web pages, to link to web pages from ActivityPub objects like an Actor or a Note, or to discover the ActivityPub account of a user referenced with <code>@username@domain</code> syntax with Webfinger. </p> <p>Our approach builds on these methods by adding extra URLs pointing to P2P versions of resources along side the expected HTTP links.</p> <p>Compliant clients should attempt to prioritize loading P2P versions of AP data when possible / enabled by users.</p>"},{"location":"fep/1042/#link-elements-in-html","title":"Link elements in HTML","text":"<p>One of the easiest ways is to sit beside the content negotiation support with links to HTML pages. Statically published AP sites which cannot do content negotiation via Accept headers have the option to point to the ActivityStreams version of a site using a <code>link</code> header pointing to the JSON-LD file for the AP object associated with the page. P2P sites should add extra links after this to the P2P URLs of the site which also use the <code>rel=alternate</code> attribute.</p> <p>When loading a link to an HTML page, clients should attempt to detect the <code>Link</code> element with the preferred url scheme and resume from there.</p>"},{"location":"fep/1042/#webfinger","title":"Webfinger","text":"<p>Similar to the HTML links, published sites should add the P2P URLs of their AP Actors after the HTTP version within the <code>links</code> section of their JRD document. These links must use <code>\"type\": \"application/activity+json\"</code> and <code>\"rel\": \"self\"</code> along with their P2P URL under <code>href</code>. Make sure to place these P2P links after the HTTP versions to avoid bugs in implementations that check the first Link without looking at the URL scheme.</p> <p>When attempting to resolve a user from a webfinger style mention like <code>@username@domain.com</code>, clients should first resolve the Webfinger data using standard methods over HTTP. Clients should then loop through the <code>links</code> field and search for <code>\"type\": \"application/activity+json\"</code> and <code>\"rel\": \"self\"</code> objects with an <code>href</code> pointing to a P2P URL.</p>"},{"location":"fep/1042/#url-links-in-ap-objects","title":"URL links in AP Objects","text":"<p>FEP-fffd set the foundation for linking to alternate versions of content. P2P clients should expand on this by adding links to p2p versions of all AP objecs served via HTTP. Note that the P2P objects should have their <code>id</code> set to their P2P URL and Must link to the HTTP version of the data. Due to how most AP implementations resolve these links, make sure any HTTP links come first in your URL list so that they don't accidentally cause loading errors in implementations lacking support for P2P resolving.</p> <p>When loading a JSON-LD file associated with an Activitypub object (Actor, Note, Collection, etc), clients should check the <code>url</code> field for objects with <code>type: Link</code> and <code>rel: Alternate</code> with the <code>href</code> property pointing to a URL with the supported P2P protocol.</p>"},{"location":"fep/1042/#gateways","title":"Gateways","text":"<p>For accounts that don't want to set up their own HTTP server, an HTTP gateway may be used. In this case, have your P2P site loadable on a gateway on its own subdomain. For example on <code>ipfs.io</code>. From there use this domain as your root for all the URLs for HTTP versions of your data. Have your P2P objects link to the HTTP URLs and vise versa.</p>"},{"location":"fep/1042/#examples","title":"Examples","text":"<p>Actor HTTP version</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", { \"@language\": \"en- CA\" }],\n \"type\": \"Person\",\n \"id\": \"https://staticpub.mauve.moe/about.html\",\n \"url\": [{\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\"\n }],\n \"outbox\": \"https://staticpub.mauve.moe/outbox.jsonld\",\n \"inbox\": \"https://social.distributed.press/v1/@mauve@staticpub.mauve.moe/inbox\",\n \"following\": \"https://staticpub.mauve.moe/following.jsonld\",\n \"followers\": \"https://staticpub.mauve.moe/followers.jsonld\",\n \"preferredUsername\": \"mauve\",\n \"name\": \"Mauve \ud83d\udc41\ufe0f\",\n \"summary\": \"This is an example static site profile. You can find the source code at https://github.com/RangerMauve/staticpub.mauve.moe/\",\n \"icon\": [{\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"name\": \"Distributed Press logo\",\n \"url\": \"https://staticpub.mauve.moe/profile.png\"\n }]\n}\n</code></pre> <p>Actor IPNS version:</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", { \"@language\": \"en- CA\" }],\n \"type\": \"Person\",\n \"id\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\",\n \"url\": [{\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://staticpub.mauve.moe/about.jsonld\"\n }],\n \"outbox\": \"ipns://staticpub.mauve.moe/outbox-ipns.jsonld\",\n \"inbox\": \"https://social.distributed.press/v1/@mauve@staticpub.mauve.moe/inbox\",\n \"following\": \"ipns://staticpub.mauve.moe/following-ipns.jsonld\",\n \"followers\": \"ipns://staticpub.mauve.moe/followers-ipns.jsonld\",\n \"preferredUsername\": \"mauve\",\n \"name\": \"Mauve \ud83d\udc41\ufe0f\",\n \"summary\": \"This is an example static site profile. You can find the source code at https://github.com/RangerMauve/staticpub.mauve.moe/\",\n \"icon\": [{\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"name\": \"Distributed Press logo\",\n \"url\": \"ipns://staticpub.mauve.moe/profile.png\"\n }]\n}\n</code></pre> <p>Webfinger doc</p> <pre><code>{ \n \"subject\": \"acct:mauve@staticpub.mauve.moe\",\n \"aliases\": [\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://staticpub.mauve.moe/about.html\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://staticpub.mauve.moe/about.jsonld\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/1042/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Alyssa P. Hacker, An example proposal, 2020</li> <li>[staticpub.mauve.moe], example static site with P2P published ActivityPub data.</li> <li>FEP fffd - Proxy Objects, spec for linking to alternate representations</li> <li>alsoKnownAs discussion, used to link different fediverse accounts together</li> <li>Distributed Press Social Reader, our initial client implementation for this spec</li> <li>Jekyll ActivityPub Plugin, static site generator with support for p2p publishing.</li> <li>Jekyll ActivityPub Plugin, source code</li> <li>HTML Link tag discussions</li> <li>Sutty's Actor, with the Jekyll plugin enabled</li> </ul>"},{"location":"fep/1042/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/11dd/","title":"FEP-11dd: Context Ownership and Inheritance","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org <code>DRAFT</code> 2025-09-11 #675 Discussions codeberg"},{"location":"fep/11dd/#summary","title":"Summary","text":""},{"location":"fep/11dd/#what-is-a-context","title":"What is a context?","text":"<p>FEP 7888 lays out some guidelines for use of the <code>context</code> as a common property between a set of objects when they can be grouped together through reply association (e.g. a reply-tree).</p>"},{"location":"fep/11dd/#what-is-this-for","title":"What is this for?","text":"<p>This proposal aims to extend these guidelines further by codifying:</p> <ol> <li>That a context must declare an owner via <code>context.attributedTo</code>.</li> <li>Where and when a context may be inherited by new objects.</li> </ol>"},{"location":"fep/11dd/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119].</p>"},{"location":"fep/11dd/#context-ownership","title":"Context Ownership","text":"<p>A context MUST have an owner. The following are upgrades to FEP 7888 that pertain to ownership:</p> <ol> <li>A <code>context</code> MUST be resolvable.</li> <li>When resolved the <code>context</code> MUST contain:<ul> <li><code>attributedTo</code> denoting the authority/owner of that context.</li> </ul> </li> <li><code>context.attributedTo</code> MUST resolve to an actor (ActivityPub \u00a74.1 Actor Objects).</li> </ol>"},{"location":"fep/11dd/#example","title":"Example","text":"<p>A resolvable context collection (see FEP f228) declares an owner by setting <code>context.attributedTo</code> to a resolvable URI.</p> <pre><code>{\n \"id\": \"https://cats.example.org/1/context\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://cats.example.org/users/cat\",\n \"items\": [...]\n}\n</code></pre>"},{"location":"fep/11dd/#context-inheritance","title":"Context Inheritance","text":"<p>Inheritance is considered only when a new object being published has a relation (typically via <code>inReplyTo</code>) to another object.</p> <p>There are situations where a relation exists but context is explicitly not inherited (e.g. forked topic, quote post). These situations are out of scope of this FEP.</p>"},{"location":"fep/11dd/#as-a-publisher","title":"As a publisher","text":"<p>The object MUST inherit <code>context</code> from the root node, if the property is present and resolvable. Otherwise the object MUST NOT publish a context.</p> <p>Implementors SHOULD map that inherited context to a local identifier (if applicable) to support future use-cases/activities.</p> <p>When publishing an object with a <code>context</code> property outside the local domain, the context owner SHOULD be addressed (<code>to</code>, <code>cc</code>, <code>audience</code>).</p> <p>Note</p> <p>If an object is at the root/top-level, then there is nothing to inherit and a new context should be generated.</p>"},{"location":"fep/11dd/#as-a-consumer","title":"As a consumer","text":"<p>When consuming an object declaring a <code>context</code> outside the local domain, the object's membership MAY be verified by resolving the context directly.</p> <p>Note</p> <p>The remote <code>context</code>'s server may not reflect the object's membership in a timely manner due to delays in processing (i.e. network congestion, approval queues, etc.)</p>"},{"location":"fep/11dd/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>a, FEP 7888: Demystifying the context property, 2023</li> <li>silverpill, FEP f228: Backfilling conversations, 2025</li> </ul>"},{"location":"fep/11dd/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/1311/","title":"FEP-1311: Media Attachments","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocks <code>DRAFT</code> 2024-12-08 #459 Discussions codeberg"},{"location":"fep/1311/#summary","title":"Summary","text":"<p>Media Attachments are ubiquitous in the Fediverse. My quick investigation into the explore tab on <code>mastodon.social</code> yields that about half the posts contain an image attachment. The mechanism for these is poorly documented. For example, it is not mentioned in ActivityPub.</p> <p>My goal in this FEP is to document current usage, and issue recommendations on how to improve it. These recommendations are based on the support table Recommended Media Attachment Format available at FunFedi.dev.</p> <p>For developers that enjoy making their keyboards smoke, I believe that the above link combined with the content of Testing should be enough to adapt their Fediverse applications. The other parts are meant for people, who which to improve the situation related to media attachments.</p>"},{"location":"fep/1311/#basic-format","title":"Basic format","text":"<p>We will discuss our basic suggested format with the following example of an image attachment.</p> <pre><code>{\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n}\n</code></pre> <p>There is a lot to say here, first how does this relate to communication in ActivityPub which is done by activity. For this consider the activity (taken from data.funfedi.dev) given by</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/credentials/v2\",\n {\n \"size\": \"https://joinpeertube.org/ns#size\"\n }\n ],\n \"type\": \"Create\",\n \"actor\": \"http://pasture-one-actor/actor\",\n \"to\": [\n \"http://akkoma/users/witch\",\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"id\": \"http://pasture-one-actor/actor/S5Szzuugy50\",\n \"published\": \"2024-12-05T08:18:48Z\",\n \"object\": {\n \"type\": \"Note\",\n \"attributedTo\": \"http://pasture-one-actor/actor\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"http://akkoma/users/witch\"\n ],\n \"id\": \"http://pasture-one-actor/actor/qDqgbPpNQPw\",\n \"published\": \"2024-12-05T08:18:48Z\",\n \"content\": \"Recommended Image Format\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n }\n ]\n }\n}\n</code></pre> <p>We note that the media attachment is contained in the array of <code>attachment</code> of the <code>Note</code> object. Furthermore, in difference to the activity and the object, there is no <code>id</code> property nor <code>actor</code> or <code>attributedTo</code> property. This is on purpose, as those are inherited from the object the media attachment is attached to. See Content Licensing for discussion about not having an <code>attributedTo</code> property.</p> <p>In particular, one should emphasize that a media attachment not having an <code>id</code> is useful to signify that it is not useful as an object without the note, it is attached to.</p>"},{"location":"fep/1311/#specifying-basic-properties","title":"Specifying basic properties","text":"<p>To use media attachments, an object MUST have an <code>attachment</code> property, whose value is an array containing objects. Furthermore, the contained objects MUST have a type property. For it to be a media attachment the type property MUST be <code>Audio</code>, <code>Image</code>, or <code>Video</code>. However, the attachment property MAY contain other form of attachments, e.g. FEP-0ea0: Payment Links.</p> <p>This can be expressed as the json-schema:</p> <pre><code>{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"attachment\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"examples\": [\"Audio\", \"Image\", \"Video\"]\n }\n },\n \"required\": [\"type\"]\n }\n }\n },\n \"required\": [\"attachment\"]\n}\n</code></pre> <p>We now discuss the specific form a media attachment. In addition to <code>type</code>, a media attachment MUST also contain an <code>url</code> property providing the link to the media. The <code>url</code> property MAY also be an array, see Multiple Media Versions. However, this just to be future proof.</p> <p>Furthermore, media attachments SHOULD contain a <code>name</code> property providing an alternative plain text description of the media object.</p> <p>Again this can be represented as a json-schema.</p> <pre><code>{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"Audio\", \"Image\", \"Video\"]\n },\n \"name\": {\n \"type\": \"string\",\n \"examples\": [\"A beautiful cow\"]\n },\n \"url\": {\n \"type\": [\"string\", \"array\"],\n \"examples\": [\"http://you.example/image.png\"]\n }\n },\n \"required\": [\"type\", \"url\"]\n}\n</code></pre>"},{"location":"fep/1311/#current-state-of-type","title":"Current state of type","text":"<p>Many current implementations use <code>Document</code> for media attachments. This has the distinct disadvantage to using <code>Audio</code>, <code>Image</code>, or <code>Video</code> that the parser needs to inspect more content than <code>type</code> to discover what type of attachment it is.</p> <p>Let's write a bit of python to determine if something is a media attachment based on <code>mediaType</code>, discussed later.</p> <pre><code>def is_media(attachment:dict) -&gt; bool:\n media_type = attachment.get(\"mediaType\")\n if media_type is None:\n ... # handle error case\n\n main_type, _ = media_type.split(\"/\", 1)\n\n return main_type in [\"audio\", \"image\", \"video\"]\n</code></pre> <p>This already is fairly complicated, but there is more. If one allows <code>url</code> to be an array, one needs a different check, so it turns into something like:</p> <pre><code>def is_media(attachment:dict) -&gt; bool:\n url = attachment.get(\"url\")\n if isinstance(url, list):\n return is_media(url[0])\n\n media_type = attachment.get(\"mediaType\")\n if media_type is None:\n ... # handle error case\n\n main_type, _ = media_type.split(\"/\", 1)\n\n return main_type in [\"audio\", \"image\", \"video\"]\n</code></pre> <p>There are more exceptions and poor configuration to treat, e.g. url could be an empty list, or the implementation could attach <code>mediaType</code> to the full object instead of the <code>Link</code>.</p> <p>The goal of a specification needs to be to simplify the code that needs to be written, so we insist on people using the types <code>Audio</code>, <code>Image</code>, and <code>Video</code> for media attachments.</p>"},{"location":"fep/1311/#properties-of-the-linked-file","title":"Properties of the linked file","text":"<p>In this section, we discuss properties related to the linked file. The linked file is retrieved either by performing a <code>GET</code> request on the value of <code>url</code> or on <code>href</code> of the <code>Link</code> objects if <code>url</code> is an array. Just to mention it, this requirement might change, once Authentication and Authorization is dealt with.</p> <p>Let's look back at our original example of a media attachment</p> <pre><code>{\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n}\n</code></pre> <p>Here the linked file is given by a <code>GET</code> on <code>http://pasture-one-actor/assets/cow.jpg</code> and the result would be</p> <p></p> <p>The properties <code>mediaType</code>, <code>digestMultibase</code>, and <code>size</code> could be valid for any attached file, even a non media one, e.g. a text document. Let's quickly review them. mediaType is defined in the ActivityStreams Vocabulary. It describes the MIME type and tells us important information on how to render the file.</p> <p><code>digestMultibase</code> is defined here as part of Verifiable Credential Data Integrity. The encoding of a digest in multibase with multihash is somewhat different to the rest of multicodec, because one first has a byte to indicate the format, then another one to indicate the length. This means in particular that all <code>digestMultibase</code> using <code>sha-256</code> will start with <code>zQm</code>, the <code>z</code> indicating base58 encoding. Checking the digest is important to ensure integrity. As media is often hosted off site using S3, this seems important. For another usage see Content Addressed Storage.</p> <p>Finally <code>size</code> being the file size in bytes is borrowed from PeerTube. The <code>size</code> should tell us if we want to preload the media or not.</p> <p>There is something missing in the file properties: access control, see the section Authentication and Authorization in the open questions below.</p>"},{"location":"fep/1311/#specifying-file-properties","title":"Specifying file properties","text":"<p>The creator of a media attachment SHOULD include the values of <code>mediaType</code>, <code>digestMultibase</code>, and <code>size</code>. The consumer of a media attachment SHOULD ensure integrity of the downloaded attachment based on <code>digestMultibase</code>, i.e. check the digest. The consumer of a media attachment SHOuLD decide based on <code>size</code> and <code>mediaType</code> the best way to consume the attachment.</p> <p><code>size</code> and <code>mediaType</code> become more relevant when multiple versions of the media attachment are provided. For example, this could mean that in one feeds one only sees the low quality video by default.</p>"},{"location":"fep/1311/#properties-of-an-image","title":"Properties of an image","text":"<p>We have now discussed all properties of our example document except for <code>width</code> and <code>height</code>. These properties are only relevant for an image and a video, but not for audio. Similarly, audio and video can have a <code>duration</code>, which images don't. Finally, Mastodon has introduced the additional properties</p> <ul> <li><code>focalPoint</code></li> <li><code>blurHash</code></li> </ul> <p>where at least <code>focalPoint</code> is user defined. There are a lot of other properties one can consider for media, e.g.</p> <ul> <li>Where was the picture taken? e.g. <code>location</code></li> <li>What is the frame rate of the video? e.g. <code>fps</code></li> <li>Provide an album cover for audio?</li> </ul> <p>In order to standardize these things further work is needed.</p>"},{"location":"fep/1311/#multiple-media-versions","title":"Multiple Media Versions","text":"<p>As it is currently not supported in the Fediverse, I will just give the basic example how to use multiple attached Links:</p> <pre><code>{\n \"type\": \"Video\",\n \"name\": \"A beautiful cow eating\",\n \"url\": [\n {\n \"type\": \"Link\",\n \"size\": 54373,\n \"digest\": \"zQmSzK5qEe5tpjwGMhmjx9RvVoPkWhEmCwxP2s7wPMpKMoK\",\n \"width\": 256,\n \"height\": 144,\n \"href\": \"http://pasture-one-actor/assets/cow_eating.mp4\",\n \"mediaType\": \"video/mp4\"\n },\n {\n \"type\": \"Link\",\n \"size\": 2271723,\n \"digest\": \"zQme2X4rgWuRdmAtGGMSEbdoeRQ2NAL2VptcdRGTYDZbSKG\",\n \"width\": 1920,\n \"height\": 1080,\n \"href\": \"http://pasture-one-actor/assets/cow_eating_hd.mp4\",\n \"mediaType\": \"video/mp4\"\n }\n ],\n \"duration\": \"PT3S\"\n}\n</code></pre> <p>As the example shows, this is useful to attach both a low quality version (54kb) and a high quality one (2.2MB) of a video.</p> <p>We think that supporting this will open the door for richer applications.</p>"},{"location":"fep/1311/#testing","title":"Testing","text":"<p>By using json-schema, one can validate some level of correctness of generated media attachments. Relevant schemas are available at Fediverse schemas for media attachments. They can be combined into a feature test using Gherkin, see Media Format.</p> <p>If you wish to validate everything, including digest, you can use the examples provided at FunFedi.dev.</p>"},{"location":"fep/1311/#open-question","title":"Open Question","text":"<p>This section is essentially a todo list for the community on stuff that should be fixed, but isn't yet.</p>"},{"location":"fep/1311/#content-licensing","title":"Content Licensing","text":"<p>The picture in the examples was created based on this picture available for free on pixabay by photographer derekmuller. Unfortunately, the current standards to not let me attach this information to my media object.</p> <p>One could now say that this could be solved with just using the <code>attributedTo</code> property. Unfortunately, this has a lot of drawbacks. For example derekmuller is not an ActivityPub actor. Also attributing my cropped low resolution image to him, might be something he does not appreciate. Finally, just attributing this image is probably not enough, one should also inform people on how it is licensed.</p> <p>See FEP-c118 and its discussions for more on the topic.</p>"},{"location":"fep/1311/#authentication-and-authorization","title":"Authentication and Authorization","text":"<p>Currently, image links must be accessible without any form of authorization. This is due while communication between a user and their server requiring authentication and between servers requiring authentication, images are often stored on third party services, e.g. S3, thus adding authentication is hard.</p> <p>For some approaches to resolve this see this Fediverse discussion.</p> <p>One approach to achieve authentication and authorization easily with existing technologies would be Bearcaps.</p> <p>For a different approach see also Binary Fediverse transport.</p>"},{"location":"fep/1311/#content-addressed-storage","title":"Content addressed storage","text":"<p>Storing media is costly. It is thus important to avoid duplication. By having a digest for all media through the <code>digestMultibase</code> property, we can use this to index our media storage. This means that before downloading a file, we can check if we already have it.</p>"},{"location":"fep/1311/#mixed-media-content","title":"Mixed media content","text":"<p>Consider posting a song, e.g. something from the brat summer, then you might want to attach the album cover, e.g. an image featuring the color <code>#8ACE00</code>. Maybe you will also want to attach some lyrics. This means that your media content contains three parts of separate media type.</p> <p>One might want to extend the schema for media attachments to convey this information.</p>"},{"location":"fep/1311/#binary-fediverse-transport","title":"Binary Fediverse transport","text":"<p>A failure of ActivityPub is that it restricts transport to be JSON. ActivityPub thus forces people to use external means, e.g. download the file, to convey media content.</p> <p>One could solve some problems, e.g. Authentication and Authorization, by just allowing transport of binary blobs. This would require an extension of the wire format.</p> <p>Allowing for messages to contain binary blobs would also for sharing media via thick clients.</p>"},{"location":"fep/1311/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Helge, FunFedi.dev: Recommended Media Attachment Format</li> <li>Helge / funfedi.dev Interoperability Data for the Fediverse, akkoma v3.13.3, inputs 0.1.11, Recommended Media Attachment Format, example 1</li> <li>Tim Bray, FEP-c118: Content licensing support</li> <li>Pixabay user derekmuller</li> <li>silverpill, FEP-0ea0: Payment Links</li> <li>Helge, Feature: Media Attachments part of fediverse-features</li> <li>Helge, Schemas for the Fediverse</li> <li>Mastodon, Bearcaps</li> </ul>"},{"location":"fep/1311/#posts","title":"Posts","text":"<ul> <li>Social Media Post by silverpill et al. Relevant parts:</li> </ul> <p>Apparently Streams has some mechanism for protecting attachments. Media URLs in non-public posts look like this: https://{domain}/photo/{filename}.jpg?token={token}</p> <p>IIRR at least in Hubzilla that token is just part of OpenWebAuth's \"magic authentication\". Where I guess the token contains info about which instance(s?) to contact in order to verify your identity. The audience is kept in the media server(s) database and sync'ed between clones.</p>"},{"location":"fep/1311/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/1580/","title":"Index","text":"<p>hide: - navigation description: \"(This section is non-normative) Prior FEPs (FEP-7628, FEP-E965) describe an ability for an Actor to move to a new id, often hosted on a different server instance, however they do not describe a mechanism for moving objects that are owned<sup>4</sup> by that actor. This FEP describes a mechanism of migrating objects owned by a moved Actor to the target instance using two OrderedCollections created by the target instance a migration collection that contains a mapping from source object URIs to new URIs on the target instance, and a moves collection that contains the actor Move activities that prove a migration has taken place and allows verification of object signatures in the case the source Actor is no longer available. This FEP attempts to balance effectiveness, performance, security, and ease of implementation by allowing 3rd-party instances to gradually update their local copies of the affected Objects. This FEP describes a \"Push\"-style migration<sup>6</sup> initiated by a source instance followed by a \"pull\" of objects by a target instance, as well as a \"Pull\"-style migration initiated by a target instance given a prior export of actor data. The migration operation is agnostic to the type of the Objects being migrated, supporting protocol evolution to unanticipated Object types across instances with varying support for them. Collection-based object migration is orthogonal to, and compatible with content-addressed or other portable object schemes (e.g. FEP-ef61). tl;dr to migrate objects, create a mapping from the old to new objects on the target instance, and let 3rd-party instances gradually migrate their local representations using that map.\"</p>"},{"location":"fep/1580/#fep-1580-move-actor-objects-with-a-migration-collection","title":"FEP-1580: Move Actor Objects with a <code>migration</code> Collection","text":"Authors Status Type Date received Tracking issue Discussions Repository Jonny Saunders j@nny.fyi <code>DRAFT</code> implementation 2025-10-09 #702 Discussions codeberg"},{"location":"fep/1580/#summary","title":"Summary","text":"<p>(This section is non-normative)</p> <p>Prior FEPs (FEP-7628, FEP-E965) describe an ability for an Actor to move to a new <code>id</code>, often hosted on a different server instance, however they do not describe a mechanism for moving objects that are owned<sup>4</sup> by that actor.</p> <p>This FEP describes a mechanism of migrating objects owned by a moved Actor to the target instance using two <code>OrderedCollection</code>s created by the target instance: </p> <ul> <li>a <code>migration</code> collection that contains a mapping from source object URIs to new URIs on the target instance, and</li> <li>a <code>moves</code> collection that contains the actor <code>Move</code> activities that prove a migration has taken place and allows verification of object signatures in the case the source Actor is no longer available.</li> </ul> <p>This FEP attempts to balance effectiveness, performance, security, and ease of implementation by allowing 3rd-party instances to gradually update their local copies of the affected Objects. This FEP describes a \"Push\"-style migration<sup>6</sup> initiated by a source instance followed by a \"pull\" of objects by a target instance, as well as a \"Pull\"-style migration initiated by a target instance given a prior export of actor data. The migration operation is agnostic to the type of the <code>Object</code>s being migrated, supporting protocol evolution to unanticipated <code>Object</code> types across instances with varying support for them. Collection-based object migration is orthogonal to, and compatible with content-addressed or other portable object schemes (e.g. FEP-ef61).</p> <p>tl;dr: to migrate objects, create a mapping from the old to new objects on the target instance, and let 3rd-party instances gradually migrate their local representations using that map.</p>"},{"location":"fep/1580/#problem-statement","title":"Problem Statement","text":"<p>(This section is non-normative)</p> <p>Following an Actor migration, the new, \"target\" Actor is considered equivalent to and the successor of the prior \"source\" Actor. Despite being declared as equivalent<sup>2</sup> <code>as:alsoKnownAs</code>, activities and objects on a cooperative source instance continue to exist under the ownership of the now-inactive Actor, effectively becoming \"zombie objects\" that the new primary Actor has no direct control of; on an uncooperative or unavailable instance, these objects are lost even with a valid migration activity. Inability to migrate the objects owned by an Actor means that they are catastrophically lost in the case of instance shutdown, and has far-ranging impacts on the social and political structure of the fediverse by strongly dis-incentivizing movement between instances, and makes new user entry into the fediverse difficult and confusing, where a choice of instance made before having the chance to gain familiarity with the network becomes indefinite destiny.</p> <p>Moving objects is hard: - The combinatorics of every third-party instance needing to update every object after a move make object migration potentially forbiddingly costly. This is particularly true when considering ActivityPub in \"pub/sub-only\" mode where actions must be processed immediately upon receipt: a target server emitting 100,000 <code>Move</code> activities, one for each post, that all recipients need to keep up with would drown the fediverse. - The mapping from old to new URIs is not knowable in advance, as the local IDs used by one instance software need not map onto the IDs used by another, so a single batch update action is impossible. - There is no guarantee that a given <code>Object</code> on the source instance will be fully supported by the target instance, and some non-uri properties like <code>proof</code> signatures by definition must change after a migration, so 3rd-party instances must query the target instance to retrieve the updated versions of the migrated objects. - Objects are embedded within a larger graph that includes other collections like <code>replies</code> that must also be updated to reflect the migration. - Not all objects should be migrated, e.g. someone may want to make a \"fresh start\" on a new instance, keeping their followers/followed accounts, but only bringing some subset of objects (or none) along with them. - The source instance may not be cooperative or even online, so object migration may require the use of external backups which may be lossy or out of date. - Instance software that does not support object migration may be left in an inconsistent state with the rest of the fediverse that does.</p> <p>However ActivityPub has a dual push and pull operation, where activities and objects can be stored in <code>Collection</code>s for at-will consumption by 3rd-party instances. By storing a mapping between prior object URIs/IDs on the source instance and new URIs/IDs on the target instance, object migration can be done gradually, partially, safely, and selectively. This approach uses standard ActivityPub semantics and common collection enumeration operations, easing implementation.</p>"},{"location":"fep/1580/#specification","title":"Specification","text":"<p>MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS.</p> <p>Supporting instances MUST indicate their support of this FEP by including its namespace in the <code>@context</code> of affected Actor objects.</p>"},{"location":"fep/1580/#terminology","title":"Terminology","text":"<p>As used in this document:</p> <ul> <li>An \"Actor move\" or \"Actor migration\" refer to the <code>Move</code> specifications in FEP-7628 and FEP-E965 where the primary actor of a set of linked identities is changed. Specifically, a \"irreversible\" migration of the <code>movedTo</code> variety, rather than the reversible, partial <code>copiedTo</code>. The \"Actor move\" activity is addressed to <code>as:Public</code>.</li> <li>A \"source instance\" is an ActivityPub server instance from which the Actor is moving from</li> <li>A \"target instance\" is an ActivityPub server instance which the Actor is moving to. A target instance MAY be the same as the source instance.</li> <li>A \"3rd-party instance\" is an ActivityPub server instance which is neither a source or target instance, but stores <code>Object</code>s that are affected by the object migration.</li> <li>A \"proposed move\" is an <code>Offer[Move]</code> activity emitted by the source instance addressed to the target instance's shared inbox (and not <code>as:Public</code>) after the source instance has validated the source and target actors are FEP-7628 linked actors in order to solicit an <code>Accept[Move]</code> from the target instance confirming the move. It is distinct from the \"actor move\" <code>Move</code> activity that is publicly emitted following acceptance of the move proposal.</li> </ul> <p>CURIE prefixes used in this document: - <code>migration</code>: <code>https://w3id.org/fep/1580/</code> - the namespace created by this FEP (FEP-888d) - <code>as</code>: <code>https://www.w3.org/ns/activitystreams</code> - the ActivityStreams vocabulary - <code>sec</code>: <code>https://w3id.org/security#</code> - The Data Integrity vocabulary used by FEP-8b32 proofs.</p> <p>The prefixes are assumed to be declared in the <code>@context</code> of any JSON-LD examples. Prefixes are included on first mention of a non-activitystreams term, but are otherwise omitted for concision.</p>"},{"location":"fep/1580/#preconditions","title":"Preconditions","text":"<p>Supporting software MUST support the following behavior specified in other FEPs - FEP-7628 - FEP-8b32 - specifically the software must be capable of managing keypairs per actor rather than use one private key per instance.</p> <p>All behavior described in this FEP MUST follow receipt of a valid <code>Move</code> activity or its side effects as defined by FEP-7628, FEP-E965 and this FEP that inactivates the source account: i.e. the source account is either no longer available or has a <code>movedTo</code> property declared.</p>"},{"location":"fep/1580/#target-instance","title":"Target Instance","text":""},{"location":"fep/1580/#ingest","title":"Ingest","text":"<p>After an Actor <code>Move</code> activity is completed and verified, the target instance MUST perform an \"ingest routine\" that - enumerates all supported actor collections present in the actor object OR the set of objects approved during the moderation handshake, as described in Pre-Move Migration - ingests their contents, and - creates new Activities and Objects that preserve as much of their content as is supported.</p> <p>\"Supported actor collections\" is left purposefully undefined and is left as an implementation detail to supporting software, however supporting software SHOULD attempt to support all collections and objects that it has a representation for. If unknown object types are encountered, the target instance MAY store a full copy of those objects in case a future software update supports them.</p> <p>If applicable, objects that control the visibility of other objects (in particular: <code>Block</code> activities) MUST be ingested first so they may be used to control the visibility of objects within the <code>migration</code> collection. Migrated items MUST be subject to the same visibility constraints as on the source instance, including blocks, whenever possible.</p> <p>During collection enumeration, requests MUST be signed as being on behalf of the target Actor if signing a request as being on behalf of an actor is supported by the source and target instances.</p> <p>Created/updated timestamps MUST be preserved from the original object, but supporting instances MAY add and MAY display additional properties to indicate an object has been migrated: - a <code>migration:migratedAt</code> timestamp to display the time an object was migrated, and - a <code>migration:migratedFrom</code> URI to link to the prior object.</p> <p>The target instance MUST insert the newly migrated objects in relevant collections and perform any side effects that normally accompany object creation, however it SHOULD NOT emit Activities indicating the creation of these migrated objects.</p> <p>Instead, the target instance MUST create two collections on the Actor object to support gradual migration:</p>"},{"location":"fep/1580/#migration-collection","title":"<code>migration</code> Collection","text":"<p>The target instance MUST create a <code>migration</code><sup>5</sup> <code>OrderedCollection</code> that maps the object URIs from the source instance to their counterparts on the target instance. The <code>migration</code> collection SHOULD remain present on the actor object for the lifetime of the actor.</p> <p>The <code>migration</code> collection MUST be ordered in reverse chronological order by the timestamp that the migrated counterpart of the original object is created on the target instance (NOT according to the date the original object was created or updated on the source instance).</p> <p>The <code>migration</code> collection MUST contain a <code>migration:moves</code> URI reference to the accompanying <code>moves</code> collection (below).</p> <p>The items in the <code>migration</code> collection SHOULD use URI references in their <code>object</code> and <code>target</code> fields rather than inlining the materialized objects, and SHOULD paginate the collection as <code>OrderedCollectionPages</code> to reduce resources spent serializing and constructing the collection.</p> <p>Items in the <code>migration</code> collection MUST match the item visibility of the source objects, when possible. In particular: - <code>Move</code> activities for objects that are not addressed to <code>as:Public</code> MUST not be publicly displayed. - <code>Move</code> activities for non-public objects MAY be displayed when the request is authenticated as having been made by an Actor in the object's audience<sup>3</sup>. - <code>Move</code> activities MUST respect actor-level visibility controls like blocks.</p> <p>The <code>migration</code> collection MAY be created incrementally, inserting migrated objects as they are ingested, or it may only populate the collection upon completion of the ingest routine.</p> <p>Implementing software MAY provide appropriate options and interfaces to allow the migrating actor to select which, if any objects they want to migrate (see Pre-Move Moderation). If the actor elects to not migrate any objects, the <code>migration</code> collection MUST still be published and its <code>items</code> field MUST be empty<sup>9</sup>.</p> <p>Upon completion of the ingest routine, the <code>migration</code> collection's <code>migration:migrationComplete</code> property must be set to <code>true</code>, indicating for consuming instances that for the given state of the <code>moves</code> collection (below) there will be no changes to the <code>migration</code> collection. To \"reopen\" the <code>migration</code> collection for consumption by third party instances, signaling new changes from e.g. an upload of an exported collection (below), the target instance MUST emit another (potentially duplicate) <code>Move</code> activity and set the <code>migrationComplete</code> property to false until the changes are completed. The target instance MAY delay setting <code>migrationComplete</code> to <code>true</code> in order to provide an interface to prompt the migrating actor to upload a previously exported collection of objects.</p> <p>A target instance MUST NOT begin another actor move to another instance until the migration is complete in order to prevent loss of objects still in the process of being migrated from the source instance.</p>"},{"location":"fep/1580/#examples","title":"Examples","text":"<p>(This section is non-normative)</p> <p>(\"...\": \"...\" is used to indicate \"the rest of the object\" in the absence of JSON comments)</p> <p>Actor on target instance</p> <pre><code>{\n \"id\": \"https://example.com/chuckTargetson\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"migration\": \"https://example.com/chuckTargetson/migration\",\n \"moves\": \"https://example.com/chuckTargetson/moves\"\n}\n</code></pre> <p><code>migration</code> OrderedCollection</p> <pre><code>{\n \"id\": \"https://example.com/chuckTargetson/migration\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/chuckTargetson\",\n \"totalItems\": 100,\n \"first\": \"https://example.com/chuckTargetson/migration/page/0\",\n \"migrationComplete\": false,\n \"moves\": \"https://example.com/chuckTargetson/moves\"\n}\n</code></pre> <p><code>migration</code> OrderedCollectionPage</p> <pre><code>{\n \"id\": \"https://example.com/chuckTargetson/migration/page/0\",\n \"type\": \"OrderedCollectionPage\",\n \"partOf\": \"https://example.com/chuckTargetson/migration\",\n \"next\": \"https://example.com/chuckTargetson/migration/page/1\",\n \"items\": [\n {\n \"type\": \"Move\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"origin\": \"https://source.example.com/barbaraSourceworth/posts/12345\",\n \"target\": \"https://example.com/chuckTargetson/items/98765\"\n },\n {\n \"type\": \"Move\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"origin\": \"https://source.example.com/barbaraSourceworth/posts/abcde\",\n \"target\": \"https://example.com/chuckTargetson/items/zyxwp\"\n }\n {\"...\": \"...\"}\n ]\n}\n</code></pre>"},{"location":"fep/1580/#moves-collection","title":"<code>moves</code> Collection","text":"<p>The <code>moves</code> collection serves two purposes: - As a content-hashable indicator of the state of an account's migrations - As a durable source of authentication for confirming the validity of the items in the <code>migration</code> collection.</p> <p>The <code>moves</code> collection contains the deduplicated <code>Move</code> activities emitted when an Actor is migrated. <code>Move</code> activities MUST be signed by the source actor using an FEP-8b32 proof. The <code>move</code> collection MUST make a copy of the source Actor object that contains the means of validating object integrity proofs available either inlined in the items of the <code>Move</code> collection or in the <code>actors</code> collection (described below). Source instances SHOULD inline the source Actor object in actor <code>Move</code> activities.</p> <p>If the actor is not inlined in a signed <code>Move</code> activity, the target instance MUST create an inlined copy of the source Actor object in its <code>actors</code> collection. The <code>actors</code> collection is an <code>OrderedCollection</code> of inlined Actor objects sorted in lexicographic order according to their <code>id</code> (example below).</p> <p>The <code>moves</code> collection MUST itself be signed with an <code>FEP-8b32</code> proof. By including the inlined Actor objects and <code>Move</code> activities signed by the source Actor(s), and having the collection be signed by the target Actor, the <code>moves</code> collection serves as a self-contained, verifiable, bidirectional proof of the validity of the items in the <code>migration</code> collection.</p> <p>Consuming instances MAY consider a hash of the canonicalized form of the collection (e.g. RFC 8785, RDF canonicalization) as a content hash for the purposes of change detection, but the details of content addressing activitypub objects are outside the scope of this FEP.</p> <p>The <code>moves</code> collection MAY include a full chain of account movement, including moves from prior instances to the source account where the target is not the current target account. Inclusion of account move activities MAY be used by consuming instances to migrate objects across several moves, but specifying chains of migration is out of scope for this FEP.</p>"},{"location":"fep/1580/#examples_1","title":"Examples","text":"<p>(This section is non-normative)</p> <p><code>moves</code> collection with Actors inlined in Move activities</p> <pre><code>{\n \"id\": \"https://example.com/chuckTargetson/moves\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/activities/12345\",\n \"type\": \"Move\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"actor\": {\n \"id\": \"https://source.example.com/barbaraSourceworth\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"publicKey\": {\n \"id\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"owner\": \"https://source.example.com/barbaraSourceworth\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n...\"\n }\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n }\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://example.com/chuckTargetson#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n</code></pre> <p><code>moves</code> collection with Actors inlined in the <code>actors</code> collection</p> <pre><code>{\n \"id\": \"https://example.com/chuckTargetson/moves\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/activities/12345\",\n \"type\": \"Move\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n }\n ],\n \"actors\": {\n \"id\": \"https://example.com/chuckTargetson/moves#actors\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/barbaraSourceworth\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"publicKey\": {\n \"id\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"owner\": \"https://source.example.com/barbaraSourceworth\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n...\"\n }\n }\n ]\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://example.com/chuckTargetson#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n</code></pre>"},{"location":"fep/1580/#migrations-from-exported-collections","title":"Migrations from Exported Collections","text":"<p>In circumstances where the original objects are not available at the source instance, importing objects from some external backup of the relevant collections is no different than enumerating them from the source instance. If a valid <code>Move</code> activity was emitted, supporting instances SHOULD provide a means of uploading and ingesting exported collections, and if they do, they SHOULD ingest and otherwise handle objects from an export format of a collection identically as those that were retrieved from the source instance, ignoring any duplicates. </p> <p>Recall this FEP assumes that a valid, signed <code>Move</code> activity was emitted by the source instance - the case where an account <code>Move</code> activity was not emitted and the source instance is unavailable or hostile is outside the scope of this FEP. However, if the actor export contains the private key that accompanies the public key used for object proofs, and a <code>Move</code> activity can be created and validated independently of the source instance, the actor <code>Move</code> activity SHOULD be considered equivalent to one emitted by the source instance (FEP-7628 \"Pull\" mode migrations). Supporting software MAY provide interfaces and mechanisms for import and export of private actor keys and creation of <code>Move</code> activities from those exports.</p> <p>Target instances MAY treat imports from exported collections as subject to the same moderation flow they would apply to objects imported from a source instance (see Pre-Move Moderation) and choose objects to remove or modify from an import. If moderation is performed, the target instance MUST notify the target actor of removed or modified objects.</p> <p>See notes on security below re: source object forgery.</p>"},{"location":"fep/1580/#rate-limiting","title":"Rate Limiting","text":"<p>The target instance is the only instance that can know the rate at which it can service requests. Accordingly, the target instance SHOULD use standard HTTP rate limit headers and response codes to coordinate gradual migration requests by 3rd-party instances.</p> <p>The target instance SHOULD service migration-related requests as quickly as possible without otherwise degrading normal service so that time spent with third-party instances in an inconsistent state is minimized. </p>"},{"location":"fep/1580/#source-instance","title":"Source Instance","text":"<p>Comparatively little needs to be implemented on the source server.</p> <p>If authentication and signing requests as being made on behalf of an actor is supported by the source and target instances, the source server MUST consider requests made on behalf of the of the target Actor as if they were made by the source Actor for purposes of visibility and access.</p> <p>Source instances MUST make objects that control object visibility (e.g., blocks) available to target instances during migration in order to preserve Actor visibility preferences. If any such visibility control objects are not typically present in an Actor object's linked Collections, the source instance MUST include them in Collections linked from the Actor object, and they SHOULD use keys that can be predicted from the object type, e.g. <code>blocks</code> for <code>Block</code> activities, though the specific terms and Collection structure are left unspecified here<sup>7</sup>.</p> <p>Source instances SHOULD: - continue to host original copies of the migrated objects until the target instance completes its ingestion. - prompt source actors to export any objects that were rejected or changed during moderation (see Pre-Move Moderation), and attempt to store them for some reasonable period to allow an export to be made. - consume the <code>migration</code> collection from the target instance and store a mapping from the source to target objects. - present the migrated objects with a <code>FEP-7628</code> <code>movedTo</code> property containing the new target object URI while the object remains present - if the source object is removed, return a 301 redirect to the target object URI.</p> <p>However, source instances MAY elect not to preserve the original objects for one of several reasons e.g. in FEP-73cd re: moderation, legal, or other concerns that make continued hosting of the source objects intolerable to the source instance. This FEP is written to minimize the responsibility of the source instance, purposefully avoiding binding it into longterm service requirements by making a self-contained record of <code>moves</code> and <code>migration</code> on the target instance.</p>"},{"location":"fep/1580/#3rd-party-instances","title":"3rd-Party Instances","text":"<p>After observing and validating an account <code>Move</code> activity, 3rd-party instances MUST initiate a \"migration routine\" (below) that consumes the <code>migration</code> collection of the target Actor. </p> <p>If the <code>Move</code> activity was not observed by a 3rd-party instance (e.g. because the instance was offline or unreachable), a 3rd-party instance MUST initiate the \"migration routine\" upon observing either: - a <code>FEP-7628</code> <code>movedTo</code> property in the source Actor object AND reciprocal <code>alsoKnownAs</code> property in the target Actor, or - the presence of an unprocessed <code>Move</code> action in the target Actor's <code>moves</code> collection.</p> <p>If the <code>migration</code> collection is not marked as complete (via <code>migrationComplete == true</code>), 3rd-party instances MUST periodically check for new items, with some long timeout in case the target instance is malfunctioning or incorrectly implements <code>migrationComplete</code>. </p> <p>3rd-Party instances MUST validate the object integrity proof for actor <code>Move</code> activities, but are NOT REQUIRED to re-validate each of the migrated items. </p> <p>If after observing a valid <code>Move</code> activity, 3rd-party instance observes a mismatch in migration data (e.g. mapping to different URIs, differences in content) between the source and target instances, it MUST prefer the target instance's data, as the <code>Move</code> activity has designated the target Actor and target instance as the primary Actor in custody of the objects.</p>"},{"location":"fep/1580/#migration-routine","title":"Migration Routine","text":"<p>For an actor <code>Move</code> activity, 3rd-party instances MUST update all references to the source Actor in its internal representations of objects to refer to the target Actor - e.g. in \"mentions\" or <code>as:Link</code>s. They MAY preserve representations of both the source and target actor for the purposes of representing and displaying the <code>Move</code>.</p> <p>For each entry in the <code>migration</code> collection that the 3rd-party instance has a local representation of, 3rd-party instances MUST - Update the item's ID/URI from the <code>origin</code> to the <code>target</code>. - Request any updated fields in the <code>target</code> object from the target instance. - Update its internal object representations so it is indistinguishable from the case where source object was created by the target actor and instance (except for migration-specific fields like <code>migratedFrom</code>).</p> <p>3rd-party instances MUST NOT emit additional <code>Update</code> or other activities while updating migrated objects.</p> <p>3rd-party instances SHOULD NOT fetch items referred to in the <code>migration</code> mapping that they do not already have a local representation of: object migration is not backfilling. </p> <p>3rd-party instances SHOULD implement migration routine operations as optimistic: they should perform predictable operations using their local representations of objects without waiting for full data from the target instance when possible. For example, after consuming the <code>migration</code> collection, which is effectively a mapping from source to target URIs, the instance should update the object's URI without waiting for a full refresh of the object. The target instance MAY update the content of the objects during migration, but 3rd-party instances SHOULD continue to treat them as unchanged until told otherwise by the target instance. </p> <p>3rd-party instances SHOULD treat interactions (replies, likes, etc.) from other 3rd-party instances as if they have been, or will be migrated as well. 3rd-party instances SHOULD store the relevant <code>migratedFrom</code> and other information needed to reconcile activities and objects from other 3rd-party instances that do not implement object migration: e.g. if actor <code>xena</code> migrated from instance <code>a</code> to instance <code>b</code>, and instance <code>c</code> has a receives an activity from instance <code>d</code> that is <code>inReplyTo</code> object <code>zzz</code> from <code>@xena@a</code>, but instance <code>c</code> knows <code>@xena@a</code> is now <code>@xerxes@b</code> and <code>zzz</code> is now <code>yyy</code>, it should transform the <code>inReplyTo</code> field while processing that activity.</p> <p>Migration routine operations SHOULD also be patient: requests for <code>migration</code> pages and refreshes of migrated objects should be given ample inter-request delays. The target instance is responsible for communicating rate limits, but 3rd-party instances should also be polite by default and aim to complete, for example, a migration of an Actor with 10,000 objects on the order of ~days to a week.</p> <p>Migration of extended objects that are referred to by a migrated object SHOULD be performed on a \"best effort\" basis - when there is an unambiguous change to be made (e.g. updating the <code>target</code> for activities in the object's <code>Like</code> collection to refer to the new URI), it should be made, but it is the responsibility of the target instance to include any objects that should be migrated in the <code>migration</code> collection.</p> <p>The target instance MAY provide some batched or condensed collection of <code>Update</code>s that contain only the changed fields for migrated objects, but specification of such a change collection is outside the scope of this FEP.</p>"},{"location":"fep/1580/#passive-updates","title":"Passive Updates","text":"<p>If 3rd-party instances passively or periodically refreshes objects from the source or target instance and observe that they have been migrated (e.g. the source instance responds with a 301 redirect to the target object, a <code>movedTo</code> property is present on an object), the 3rd-party instance must verify the existence of a relevant actor <code>Move</code> activity, and if found, perform the migration routine on that object.</p>"},{"location":"fep/1580/#retroactive-migration","title":"Retroactive Migration","text":"<p>As this FEP is implemented and deployed by various fedi server apps, different instances will \"become aware of\" migrations at different times. This FEP supports incremental migration as each different role becomes object migration aware:</p> <ul> <li>Source instances: should require very few changes. They should periodically check in on instances to which former Actors have migrated to to check for <code>migration</code> collections, and when present they should create redirects and populated <code>movedTo</code> fields accordingly.</li> <li>Target instances: should attempt to enumerate any collections from source instances for accounts that have migrated to them, ingest whatever objects still are available there, and publish their <code>migration</code> and <code>moves</code> collections accordingly. In the initial window of implementation, there will be many previously-migrated accounts that no longer have access to a signed account <code>Move</code> activity to prove a given set of entries in the <code>migration</code> collection. However they should still publish those collections however possible, and allow 3rd-party instances to decide how to handle previously-accepted account <code>Move</code>s. This transition path will be the subject of further expansion and revision as lessons are learned during implementation. In cases where the source instance is no longer available and the moved Actor has an export copy of their prior posts, this FEP should support recovering those posts to the target instance.</li> <li>3rd-party instances: unprocessed <code>migration</code> collections encountered while refreshing Actors is a formally spec'd initiation route for migration, so no special consideration should be needed. The gradual deployment of object migration across instances should soften some of the resource burden of needing to migrate a decade of objects.</li> </ul>"},{"location":"fep/1580/#pre-move-moderation","title":"Pre-Move Moderation","text":"<p>See discussion below (Discussion: Moderation) motivating this section and its design.</p> <p>Recall that a valid actor <code>Move</code> having already been emitted and accepted is a precondition of this spec. This section<sup>8</sup> specifies behaviors before the actor <code>Move</code> activity has been emitted and accepted.</p> <p>Prior to emitting the publicly-addressed actor <code>Move</code> activity, source and target instances MUST complete the following Proposed Move handshake:</p> <ul> <li><code>verification</code>: The source instance verifies the source and target actors are Linked Actors (as per FEP-7628)</li> <li><code>subsetting</code>: OPTIONAL - The source instance allows the source actor to subset the objects they wish to migrate</li> <li><code>syn</code>: The source instance sends a <code>Offer[Move]</code> \"Proposed Move\" activity addressed to the target instance's shared inbox.</li> <li><code>moderation</code>: OPTIONAL - The target instance may perform a \"moderation routine\" to remove or update objects within the set of objects to be migrated</li> <li><code>syn-ack</code>:</li> <li>If no moderation was performed or no changes were made to the proposed collection of objects to migrate, the target instance returns an <code>Accept[Move]</code> activity addressed to the source actor indicating it accepts the Proposed Move with no changes.</li> <li>If after moderation the target instance accepts the move with some changes to or removals from the proposed collection of objects to migrate, the target instance returns a <code>TentativeAccept[Move]</code> activity addressed to the source actor with an attached collection of <code>Remove</code> and <code>Update</code> activities.</li> <li>If after moderation the target instance chooses to reject the actor migration, the target instance returns a <code>Reject[Move]</code> activity addressed to the source actor, and the source actor is notified that their request to move was rejected.</li> <li><code>review</code>: If a <code>TentativeAccept[Move]</code> response was received, the source instance provides a means of the source actor reviewing the changes to the migrated items required by moderation and indicating whether they accept them or wish to cancel the actor move operation.</li> <li><code>ack</code>:</li> <li>If an <code>Accept[Move]</code> response was received, the public Actor <code>Move</code> activity is emitted.</li> <li>If a <code>TentativeAccept[Move]</code> was received and the source Actor indicated that they accept the changes required by moderation in the <code>review</code> phase, the public Actor <code>Move</code> activity is emitted</li> <li>If a <code>TentativeAccept[Move]</code> was received and the source actor indicated that did not accept the changes from moderation and wish to cancel the actor move, a <code>Reject[TentativeAccept[Move]]</code> activity is emitted addressed to the target instance's shared inbox.</li> </ul> <p><code>subsetting</code>: The source instance MAY provide the source actor some means of selecting which objects they choose to migrate. If such a subsetting feature is provided, the resulting subset of objects to migrate MUST be included as an <code>attachment</code> to the Proposed Move. If a collection of objects to migrate is attached to a proposed move, the target instance MUST consider only these objects in the subsequent moderation stage and replace the set of objects that would otherwise be discovered by enumerating the source actor's collections in the ingest routine. An empty attachment collection MUST be interpreted as the intent to migrate only the actor with none of its objects. If an attachment of objects to migrate is absent, the target instance MUST consider the set of objects to migrate as the set of importable objects in the source actor's collections. </p> <p><code>moderation</code>: The target instance MAY provide some means of moderating the collection of objects to be migrated. The target instance MUST return a <code>TentativeAccept[Move]</code> activity with an attached collection of <code>Update[Object]</code> and <code>Reject[Object]</code> activities that represents the objects that would be changed or removed in a migration, relative to the collection of proposed objects to migrate. Following the ActivityPub specification of server-to-server <code>Update</code> actions, the complete object with modifications made must be included in <code>Update[Object]</code> responses, rather than a set of partial updates (only those fields that have changed). To avoid specious updates that are trivially implied by an Actor Move (e.g. the <code>proof</code> field changing) or would require the target instance to have already imported the object (e.g. the <code>id</code> field changing), the updated object MUST NOT contain changes to fields that can only be determined after an object is migrated. The source instance MUST NOT validate the updated objects against the <code>proof</code> field - as they will by definition fail the integrity proof.</p> <p>Collections or OrderedCollections referenced within the attachments of the Proposed <code>Move</code> and <code>TentativeAccept[Move]</code> activities MUST be treated as if the contained objects were included in the attachments directly - proposing to migrate or moderate a Collection means that one is proposing to migrate or moderating all the items in that Collection.</p> <p>If a valid Actor Move activity is received without having previously accepted a Proposed Move, e.g. from a source instance that does not support this FEP: - If a target instance chooses not to moderate migrated objects, it MUST accept the Actor move activity as otherwise described in this FEP, migrating all objects it is capable of migrating. - If a target instance chooses to moderate migrated objects, it MUST consider all objects it is capable of migrating as being subject to moderation, but MAY choose not to immediately ingest all objects, instead gradually ingest and add them to the <code>migration</code> collection as they are approved. The target instance MUST immediately notify the target actor of the pending moderation and advise them to make a backup of the objects from the source instance to preserve any objects that may be removed or modified by moderation.</p> <p>If the target instance refuses the initial Proposed <code>Move</code> request, e.g. if it does not support this FEP, the source instance MUST notify the source actor that their objects may not be migrated and allow them to approve or cancel emission of the public actor <code>Move</code> activity.</p>"},{"location":"fep/1580/#examples_2","title":"Examples","text":"<p>(This section is non-normative)</p> <p>Proposed Move with a set of proposed object to migrate <pre><code>{\n \"id\": \"https://source.example.com/activities/23456\",\n \"type\": \"Move\",\n \"to\": \"https://example.com/sharedInbox\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": [\n \"https://source.example.com/barbaraSourceworth/posts/111\",\n \"https://source.example.com/barbaraSourceworth/posts/222\",\n \"https://source.example.com/barbaraSourceworth/followers\"\n \"...\"\n ]\n } \n} \n</code></pre></p> <p>Proposed Move specifying that no objects should be migrated with an empty collection</p> <pre><code>{\n \"id\": \"https://source.example.com/activities/34567\",\n \"type\": \"Move\",\n \"to\": \"https://example.com/sharedInbox\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": []\n } \n} \n</code></pre> <p>TentativeAccept[Move] specifying a set of removals and updates from moderation</p> <p>The original proposed collection of objects is modified such that - post <code>111</code> would not be migrated - post <code>222</code> would have its attachment removed (but otherwise remain unchanged, assuming the \"...\" expands to the original object's remaining fields)</p> <pre><code>{\n \"id\": \"https://example.com/activities/45678\",\n \"to\": \"https://source.example.com/barbaraSourceworth/inbox\",\n \"type\": \"TentativeAccept\",\n \"object\": \"https://source.example.com/activities/23456\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": [\n {\n \"type\": \"Reject\",\n \"object\": \"https://source.example.com/barbaraSourceworth/posts/111\"\n },\n {\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://source.example.com/barbaraSourceworth/posts/222\",\n \"attachment\": null,\n \"content\": \"The thing I originally posted, since server-to-server Updates aren't partial updates\",\n \"...\": \"...\"\n }\n }\n ]\n } \n} \n</code></pre>"},{"location":"fep/1580/#discussion","title":"Discussion","text":"<p>(This section is non-normative)</p> <p>Feedback and edits are welcome for each of these sections to include the inevitable caveats and consequences unforeseen by the author.</p>"},{"location":"fep/1580/#out-of-scope","title":"Out of Scope","text":"<p>This FEP is focused exclusively on the circumstance where a valid <code>Move</code> activity exists or can be created, and thus does not consider</p> <ul> <li>Adversarial/uncooperative instances that refuse to create a <code>Move</code> activity or export a private key to sign one created by the target instance.</li> <li>Content-addressed/portable objects: FEP-ef61 and other non-HTTP/non-URL-based <code>id</code> schemes typically don't require object migration (indeed that is their primary purpose), and thus this FEP does not apply to them. Implementing object migration for HTTP/URL objects should not be construed as competing with or trading off with instance-independent objects or alternative <code>id</code> or resolution schemes, and is intended to resolve a need in existing systems rather than preventing new systems from being created.</li> <li>\"Splitting\" actors and partial migration: Account Redirects (e.g. <code>copiedTo</code>-style, non-final redirects) are not considered in this FEP, and it assumes that there is a single target instance and actor which becomes the primary Actor in custody of the source actor's objects. It is not possible in this formulation for the source Actor to indicate which objects might be owned by different targets or to resolve conflicts over object ownership between multiple target actors, though future amendments could allow the actor <code>Move</code> activity to contain scoping information that could accomplish that. Merging actors (multiple source actors moving to a single target actor) is explicitly supported by the <code>moves</code> collection, however.</li> <li>Chained movement: Though a linear, unbranching chain of actor movements should be resolvable, this FEP only considers pairwise source-to-target moves. </li> <li>Deep perf optimization: This FEP attempts to avoid being overprescriptive and minimize implementation burden - create two collections, enumerate them, and otherwise apply normal update operations - which trades off with a perfectly performance-optimized spec. It would, for example, probably be ideal to update objects using a compressed, batched changeset with partial updates rather than refreshing full objects - such a follow-on FEP would be welcome - but it would require several new mechanisms without wide implementation.</li> <li>Export format/private key export: which is the subject of FEP-6fcd</li> </ul>"},{"location":"fep/1580/#moderation","title":"Moderation","text":"<p>Migrating objects from one instance to another involves changing their moderation context: the source and target instances are likely to have differences in rules governing content, behavior, and connections between actors. If migrating objects is subject to different rate limits and moderation control than normal object creation, it may serve as an abuse vector, where content that is not allowed on the target instance can be created at faster rates or out of the eye of moderators<sup>1</sup>. As a result, target instances may want some means of moderating incoming content. </p> <p>The potential implementations of moderation for migrated content might vary considerably, and this FEP is not intended to overly constrain them in advance. However since the needs of the migrating actor and the needs of the instance may conflict during moderation, some guidance is provided above (Pre-Move Moderation) to balance them.</p> <p>Some examples of conflicting needs between involved parties: - The migrating actor has an interest in maintaining whatever subset of their previously-created objects they elect to migrate, but the target instance has an interest in maintaining the norms and rules that govern instance content. There is a risk of permanent loss if the target instance chooses to not ingest some objects during a migration and the source instance chooses to remove them following a migration. - Reviewing a large number of objects might create a heavy moderation burden on target instances, but a migrating actor should not be left in a prolonged state of uncertainty after their account has already been moved regarding whether their content will be allowed to migrate with them. - The migrating actor may wish to revoke their move to a target instance after seeing the results of moderation, but repeated moves create a large amount of traffic on the network, and the target instance should not have to serve as an intermediate host to content it views as against its rules in order to facilitate a second-order move. - Current implementations of <code>FEP-7628</code> account movements may not be subject to moderation, as they only take into account the actor, not any of its objects. An overly-complex moderation spec in this FEP may prolong its implementation, prolonging the broader harms from the absence of object migration.</p> <p>The above spec language reflects a strategy for balancing these conflicting needs: - Moderation should take place between the proposal of the <code>Move</code> and its acceptance - Target instances should make modifications from moderation to the collection of migrated objects transparent to the migrating actor. - Actors must have some means of changing their mind about a move if the terms of moderation are unacceptable to them after the target instance reviews any imported content. - If subsetting and moderation are both waived, or the source or target instances do not implement this FEP, the handshake can fallback to behavior equivalent to emitting a public actor move activity without the handshake. This can allow for staged implementation, handling the core migration routines first, and the frontend work needed for moderation afterwards.</p> <p>The pre-move moderation spec is intended to be very general to accommodate different strategies for managing bulk moderation. For (non-exhaustive) example:</p> <ul> <li>Accept all: e.g. when a source instance is known to share moderation practices or the source actor is otherwise trusted). This strategy may also be useful when an instance performs moderation at the time an Actor is created - e.g. if an instance supports a \"create from account on another instance\" feature that specifically ties a request for Actor creation with a request for migration from another Actor.</li> <li>Strip media: e.g. return <code>Update</code> for all objects with attached media with that attachment removed, e.g. if storage costs would be prohibitive or an instance doesn't support the content type.</li> <li>Strip DMs: e.g. either by the source actor by omitting them in the Proposed Move or moderation on the target instance by returning <code>Remove</code> for all objects not addressed to <code>as:Public</code> or the \"followers\" collection.</li> <li>Custom selection: manually reviewing objects to import and exclude by both the source actor or target instance moderation.</li> <li>Reject Migrations: The target instance indicates they refuse migrating objects altogether</li> <li>Reject Moves: The target instance indicates they refuse to migrate both actors and objects</li> </ul> <p>The above specification regarding pre-move moderation is compatible with <code>FEP-e965</code>, as it only requires that actors be publicly declared as linked in order for a <code>Move</code> to be considered valid - this spec adds additional optional behavior prior to the <code>Move</code> activity being broadcast to 3rd-party instances without modifying that requirement.</p>"},{"location":"fep/1580/#performance","title":"Performance","text":"<p>Migrating tens of thousands of objects across hundreds or thousands of instances is an intrinsically costly operation. The strategy here attempts to mitigate extreme resource requirements by allowing migrations to happen gradually and partially. Rather than emitting a flood of thousands of activities, 3rd-party instances can accomplish a majority of the important changes needed by consuming a collection of small <code>Move</code> activities that contain pairs of URIs, and then slowly backfill remaining migration changes over days or weeks. The need for object migration is so high that the resource cost is well worth it, and after the initial period after deployment where all the migrations until the present need to be processed, account migrations are relatively rare (i.e. Mastodon has a 30-day cooldown).</p>"},{"location":"fep/1580/#compatibility","title":"Compatibility","text":"<p>Instances that support object migration should remain interoperable with instances that do not, and that should be achievable by keeping a map from prior to current object IDs and transforming references to migrated objects when processing activities from non-migratory instances. Some inconsistency in network state is inevitable when implementing new behaviors, but this FEP shouldn't introduce new baffling complexities to network state: the current behavior of objects on accounts that have migrated and are no longer active is that they are inert - so additional replies can be created but they won't be received, and so on. Any change that introduces even partial object migration is a net-positive for network coherence.</p>"},{"location":"fep/1580/#security","title":"Security","text":"<p>The heavy-lifting of secure object migrations is already handled by the existing account <code>Move</code> specs, which establish a trusted means of declaring that one actor is now another actor. This FEP should not introduce new risks for account takeover that are not already inherent in the existing account <code>Move</code> specs. Account <code>Move</code> activities must always be validated by an integrity proof by the source actor, accordingly attempts at account hijacking or spurious attribution must be rejected. However, since there is no mechanism for forcing key expiration or rotation, and thus a compromised secret key may be used to initiate a hostile account migration using an actor export, the Migrations from Exported Collections section is left as an optional recommendation subject to more formal specification in the future.</p> <p>Importing an external backup introduces the possibility of object forgery. Assuming a valid account <code>Move</code> and proper signing and validation, this is actually not a problem: uploading an export where the author edits all their posts or adds new ones is effectively the same as them batch-posting a bunch of back-dated posts. This FEP does not provide for modifying objects which the target actor does not own, and the target actor may do whatever they please with their objects. 3rd-party instances should be responsible for notifying 3rd-party accounts of changes to objects that they have interacted with, as is done with updates. </p> <p>Ingesting objects from collections hosted by a hostile source instance poses a larger problem: implementations may want to consider some interface that displays changes made to objects during collection ingestion and allows the target actor to reject them. Uploading some prior export of objects should override the versions of objects served by the source instance, as those should reflect the will of the actor more directly. This is another implementation detail that will require expansion and revision from experiences during implementation.</p> <p>The author notes they are not a security expert and this FEP and all implementations should be independently audited.</p>"},{"location":"fep/1580/#privacy","title":"Privacy","text":"<p>No new access routes should need to be established, as all operations use existing <code>Collection</code> mechanisms which must support filtering for object access already. the <code>migration</code> collection should not be an information leak (e.g. by indicating that some private objects exist by referring to their <code>id</code>, which may have more information beyond mere existence, as is the case with the snowflake <code>id</code> scheme used by Mastodon) and only contain information that is already publicly enumerable via public collections on the source actor.</p>"},{"location":"fep/1580/#supplementary-information","title":"Supplementary Information","text":"<ul> <li><code>fep-1580.jsonld</code> - The JSON-LD context document to be included in the <code>@context</code> statement of actors on implementing instances.</li> <li><code>migration.yaml</code> - A linkml schema that provides a concrete form of the ActivityPub/ActivityStreams extensions described in the text. </li> <li><code>migration.owl.ttl</code> - An OWL schema generated from the LinkML schema</li> </ul>"},{"location":"fep/1580/#acknowledgements","title":"Acknowledgements","text":"<p>Thank you to those that helped provide feedback and recommendations for this FEP:</p> <p>(in alphabetical order by handle, please feel free to make a PR to add yourself if you helped!) - <code>ansuz</code> - Advice from cryptography POV - <code>apropos</code> - Clarifying questions - <code>silverpill</code> - Suggestions re: clarity and use of ActivityStreams - <code>trwnh</code> - Help with JSON-LD, critical feedback.</p>"},{"location":"fep/1580/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> </ul>"},{"location":"fep/1580/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/1580/#footnotes","title":"Footnotes","text":"<ol> <li> <p>The problem of abusive content being created on an instance is not unique to migrations, and most software implementing ActivityPub has means of creating abusive content rapidly (through API requests and automation) and outside the normal visibility of moderation (with direct messages or other scoped visibility settings). While the problem is not unique, this section attempts to address some new potentials for abuse vectors that may exist given prior implementations of actor moves that did not consider migrating objects along with the actor.\u00a0\u21a9</p> </li> <li> <p>The description of <code>alsoKnownAs</code> cautions about treatment as an equivalence relationship, however the combination of a <code>movedTo</code> from the source Actor and an inverse <code>alsoKnownAs</code> property from the target Actor does indicate an equivalence relationship where the source Actor is inactive and has become the target Actor. <code>copiedTo</code> does not necessarily indicate an equivalence, as according to the DID spec, linked Actors that are still in use can be used for different purposes. Accordingly we do not consider <code>copiedTo</code> Actor redirects in this FEP.\u00a0\u21a9</p> </li> <li> <p>Members of <code>as:audience</code>, <code>as:to</code>, <code>as:bto</code>, <code>as:cc</code>, <code>as:bcc</code>, and any other properties used for scoping visibility or delivery.\u00a0\u21a9</p> </li> <li> <p>\"Ownership\" in this document is defined according to FEP-fe34: Ownership \u21a9\u21a9</p> </li> <li> <p>the CURIE for the collection is, maybe unhelpfully, <code>migration:migration</code> to refer to <code>https://w3id.org/fep/1580/migration</code> \u21a9</p> </li> <li> <p>\"Push\" mode migration, as defined in FEP-7628 \u21a9\u21a9</p> </li> <li> <p>This is possible since the target instance must enumerate all collections linked from an actor document if a specific collection of objects to migrate is not provided by the <code>Offer[Move]</code> handshake.\u00a0\u21a9</p> </li> <li> <p>Until the next heading.\u00a0\u21a9</p> </li> <li> <p>The inclusion of an empty <code>migration</code> collection is to differentiate \"a migration that migrated no objects\" from \"an instance that doesn't implement this FEP,\" which would be ambiguous if <code>migration</code> was omitted.\u00a0\u21a9</p> </li> </ol>"},{"location":"fep/171b/","title":"FEP-171b: Conversation Containers","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2024-11-23 #449 Discussions codeberg"},{"location":"fep/171b/#summary","title":"Summary","text":"<p>This document specifies a model for managing conversations in ActivityPub network. It is based on the implementation of Conversation Containers in Streams.</p> <p>In this model conversations are represented as collections controlled by a single actor. Such conversations take place within a specific audience and may be moderated.</p>"},{"location":"fep/171b/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/171b/#containers","title":"Containers","text":"<p>A conversation container is implemented as a collection. Every item in that collection is an <code>Add</code> activity where <code>object</code> is another activity (such as <code>Create</code>, <code>Update</code>, <code>Delete</code>, <code>Like</code>, <code>Dislike</code>, <code>EmojiReact</code> or <code>Announce</code>). The conversation owner distributes <code>Add</code> activities to other participants, thus keeping their views of the conversation synchronized.</p> <pre><code>sequenceDiagram\n actor Alice\n actor Bob\n actor Charlie\n Note right of Alice: Alice starts a conversation by creating a post\n Alice -&gt;&gt; Bob: Add(Create(Note))\n Alice -&gt;&gt; Charlie: Add(Create(Note))\n Note left of Bob: Bob reacts to the post\n Bob -&gt;&gt; Alice: Like(Note)\n Note right of Alice: Alice receives activity and distributes it to other participants\n Alice -&gt;&gt; Charlie: Add(Like(Note))</code></pre>"},{"location":"fep/171b/#container-collection","title":"Container collection","text":"<ul> <li>Collection type MUST be <code>OrderedCollection</code>.</li> <li>Collection items MUST be in chronological order.</li> <li>Collection MUST have an <code>attributedTo</code> property containing the <code>id</code> of the conversation owner.</li> <li>Collection SHOULD have <code>collectionOf</code> property with value <code>Activity</code>.</li> </ul> <p>The audience of a conversation SHOULD be able to access the container collection. If a server forwards activities without storing them, it is permitted to not publish the collection. However, that would limit the ability of other servers to backfill conversations.</p> <p>Actors who are not part of the audience MUST be denied access to the collection.</p> <p>Note</p> <p>The required collection type might be changed to a more descriptive one (such as <code>ConversationContainer</code>) in a future version of this document. That will allow identification of <code>Add</code> activities belonging to a conversation container by the value of <code>Add.target.type</code>.</p> <p>Note</p> <p>In addition to the conversation container collection, implementers MAY provide collection that represents the conversation tree and contains posts.</p>"},{"location":"fep/171b/#adding-activities-to-a-conversation","title":"Adding activities to a conversation","text":"<p>Conversation owner can add any activity to the conversation. However, if a <code>context</code> property is present on the activity, its value SHOULD be identical to the ID of a conversation container.</p> <p>When activity is added to the conversation, its owner sends an <code>Add</code> activity to the conversation audience (usually defined by a top-level post).</p> <p><code>Add</code> activities published by the conversation owner MUST have a <code>target</code> property containing a partial object:</p> <ul> <li><code>type</code>: <code>OrderedCollection</code></li> <li><code>id</code>: the <code>id</code> of the conversation container.</li> <li><code>attributedTo</code>: the <code>id</code> of the collection owner.</li> </ul> <p><code>Add</code> activities published by the conversation owner MUST be added to the conversation container collection.</p> <p>Note</p> <p>The \"conversation outbox\" model where the <code>target</code> of <code>Add</code> activity is a collection of <code>Add</code> activities is not compatible with ActivityStreams definition of Add activity, according to which Add activity indicates that the actor has added the object to the target.</p>"},{"location":"fep/171b/#top-level-post","title":"Top-level post","text":"<p>The author of a top-level post it not necessarily the conversation owner. When owner is a group, conversations can be started by any of its members.</p> <p>The top-level post MUST have a <code>contextHistory</code> property that refers to the conversation container. The presence of this property indicates that publisher implements conversation containers.</p> <p>Note</p> <p>In addtion to <code>contextHistory</code> property, implementers MAY add a <code>context</code> property that refers to a collection of posts.</p>"},{"location":"fep/171b/#interactions","title":"Interactions","text":"<p>Regardless of their audience, all activities in a conversation SHOULD only be delivered to the conversation owner.</p> <p>Conversation participants SHOULD reject conversation activities that have not been added to the conversation by its owner.</p> <p>The audience of a reply MUST be copied from a conversation root.</p> <p>Reply with a different audience can be created by starting a new conversation and including a FEP-e232 link to the post instead of specifying it in <code>inReplyTo</code>.</p>"},{"location":"fep/171b/#moderation","title":"Moderation","text":"<p>When conversation owner does not want to add activity to a conversation, that activity is ignored and a corresponding <code>Add</code> activity is not published.</p> <p>To remove a previously approved post from a conversation, its owner publishes a <code>Delete</code> activity where <code>object</code> is the post that must be removed. This activity is then wrapped in <code>Add</code> activity and distributed to the conversation audience.</p> <p>Note</p> <p>Actor shouldn't be able to delete objects it didn't create. In a future version of this document <code>Delete</code> might be replaced with <code>Remove(target: Thread)</code>.</p>"},{"location":"fep/171b/#backfilling","title":"Backfilling","text":"<p>Conversation participants can retrieve missing activities by reading the conversation container collection.</p>"},{"location":"fep/171b/#authentication","title":"Authentication","text":"<p>When an ActivityPub server receives an <code>Add</code> activity in its inbox, it MUST perform the authentication procedure according to FEP-fe34:</p> <ul> <li>If <code>Add.object</code> is not embedded, fetch it. If location of the fetched activity has the same origin as <code>Add.object</code>, add it to the conversation.</li> <li>If <code>Add.object</code> is embedded, check whether <code>Add.actor</code> and <code>Add.object.actor</code> have the same origin.</li> <li>If origins are equal, add <code>Add.object</code> to the conversation.</li> <li>If origins are different and FEP-8b32 integrity proof is present, verify the proof. If the proof is valid, add <code>Add.object</code> to the conversation.</li> <li>If integrity proof is not present, fetch <code>Add.object</code> by its <code>id</code>. If location of the fetched activity has the same origin as <code>Add.object.id</code>, add fetched activity to the conversation.</li> </ul> <p>The processing of unauthenticated embedded activities is strongly discouraged. If such activities are not rejected by the consumer, a malicious conversation owner may be able to perform a cache poisoning attack and overwrite any actor or a post in consumer's local cache by sending a forged <code>Update(Actor)</code> or <code>Update(Object)</code> wrapped in an <code>Add</code> activity.</p> <p>Warning</p> <p>Sometimes activities have non-dereferenceable identifiers. That may prevent their authentication.</p>"},{"location":"fep/171b/#examples","title":"Examples","text":"<p>Example of an <code>Add</code> activity for a reply to a followers-only post:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Add\",\n \"id\": \"https://alice.example/activities/add/1\",\n \"actor\": \"https://alice.example/actors/1\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Create\",\n \"id\": \"https://bob.example/activities/create/1\",\n \"actor\": \"https://bob.example/actors/1\",\n \"context\": \"https://alice.example/contexts/1\",\n \"contextHistory\": \"https://alice.example/contexts/1/history\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Note\",\n \"id\": \"https://bob.example/posts/1\",\n \"inReplyTo\": \"https://alice.example/posts/1\",\n \"content\": \"This is a reply\",\n \"to\": [\n \"https://alice.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n },\n \"to\": [\n \"https://alice.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n },\n \"target\": {\n \"type\": \"OrderedCollection\",\n \"id\": \"https://alice.example/contexts/1/history\",\n \"attributedTo\": \"https://alice.example/actors/1\"\n },\n \"to\": [\n \"https://bob.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n}\n</code></pre> <p>Example of a container of a followers-only conversation:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/171b\"\n ],\n \"type\": \"OrderedCollection\",\n \"id\": \"https://alice.example/contexts/1/history\",\n \"attributedTo\": \"https://alice.example/actors/1\",\n \"collectionOf\": \"Activity\",\n \"orderedItems\": [\n \"https://alice.example/activities/add/1\"\n ]\n}\n</code></pre>"},{"location":"fep/171b/#comparison-with-other-proposals","title":"Comparison with other proposals","text":"<ul> <li>FEP-400e: The <code>object</code> of <code>Add</code> is an object, not an activity, and conversation collection contains added objects. <code>Reject(Create)</code> activity is generated for rejected posts. Conversation participants are expected to add a <code>target</code> property to posts.</li> <li>FEP-1b12: <code>Announce</code> activity is used instead of <code>Add</code>. Conversation and related activities are synchronized between participants, but conversation backfilling mechanism is not specified.</li> <li>GoToSocial Interaction Policy: conversation is managed separately for each post (in a conversation container the owner has authority over the entire thread). <code>Accept</code> or <code>Reject</code> activity is generated for every interaction (in a conversation container <code>Add</code> activity is generated when activity is approved, and rejected activities are ignored). Conversation is not synchronized between participants, but can be backfilled by recursively fetching <code>replies</code> collections.</li> <li>Forwarding from inbox: Activites are distributed without wrapping. Conversations are synchronized between participants, but conversation backfilling mechanism is not specified.</li> </ul>"},{"location":"fep/171b/#implementations","title":"Implementations","text":"<ul> <li>Streams &amp; Forte</li> <li>Hubzilla</li> <li>Mitra (only for private conversations)</li> </ul>"},{"location":"fep/171b/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Mike Macgirvin, Containers, 2024</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Gregory Klyushnikov, FEP-400e: Publicly-appendable ActivityPub collections, 2021</li> <li>silverpill, FEP-e232: Object Links, 2022</li> <li>silverpill, FEP-fe34: Origin-based security model, 2024</li> <li>silverpill, FEP-8b32: Object Integrity Proofs, 2022</li> </ul>"},{"location":"fep/171b/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/1970/","title":"FEP-1970: Chat Links","text":"Authors Status Date received Tracking issue Discussions Repository John Livingston git@john-livingston.fr <code>DRAFT</code> 2023-07-04 #128 - codeberg"},{"location":"fep/1970/#summary","title":"Summary","text":"<p>This FEP describes a way to attach a chat room to ActivityPub actors and objects. The chat room itself can be a web page, a XMPP room, a Matrix room, an IRC channel, ... The chat itself does not necessarily publish messages using ActivityPub.</p>"},{"location":"fep/1970/#chat-links","title":"Chat links","text":"<p>Chat link is an object with the following properties:</p> <ul> <li><code>type</code> (REQUIRED): the type MUST be <code>Link</code>.</li> <li><code>name</code> (RECOMMENDED): the <code>name</code> property SHOULD contain a human-readable description of the chat link.</li> <li><code>href</code> (REQUIRED): the <code>href</code> property MUST contain a chat URI. This can be an URL of a website, or any other kind of URI, such as a XMPP uri.</li> <li><code>rel</code> (REQUIRED): the <code>rel</code> property MUST contain the string <code>discussion</code> or an array containing that string. The <code>discussion</code> relation type is proposed as a HTML5 link type extensions.</li> </ul> <p>Chat links MUST be added to the <code>attachment</code> array of an actor or an object.</p>"},{"location":"fep/1970/#examples","title":"Examples","text":""},{"location":"fep/1970/#basic-object-attachment","title":"Basic object attachment","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/1970/#basic-actor-attachment","title":"Basic actor attachment","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.tld/users/alice\",\n \"inbox\": \"https://example.tld/users/alice/inbox\",\n \"outbox\": \"https://example.tld/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/1970/#multiple-way-to-join-the-chat","title":"Multiple way to join the chat","text":"<p>In the following example, the chat room can be joined using a web browser or a xmpp client:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n },\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"xmpp://123@room.example.tld?join\",\n \"rel\": \"discussion\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/1970/#live-video-stream","title":"Live video stream","text":"<p>For a live stream, represented by a Video object, the chat link could link to the room where viewers can interract with the streamer.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n</code></pre> <p>If the chat room is unique per streamer (and common to all the streamer videos), it can be attached to both the Video and the Person object.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.tld/users/alice\",\n \"following\": \"https://example.tld/users/alice/following\",\n \"followers\": \"https://example.tld/users/alice/followers\",\n \"name\": \"Alice\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n</code></pre> <p>If the chat room is unique to a streamer's channel, it can be linked to both the Video and the Group object.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Group\",\n \"id\": \"https://example.tld/channel/alice\",\n \"following\": \"https://example.tld/users/alice/following\",\n \"followers\": \"https://example.tld/users/alice/followers\",\n \"name\": \"Alice channel\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/1970/#document-discussion","title":"Document discussion","text":"<p>Some file sharing sofwares allows to have a discussion room attached to files. In such case, the chat links can be added as attachment to Document objects.</p>"},{"location":"fep/1970/#person-instant-messaging","title":"Person instant messaging","text":"<p>If a user has instant messaging applications, chat links could be added to its Person object, so that we can join him.</p> <p>If the user has multiple instant messaging accounts (XMPP, Matrix, ...), each on of them can be added, and differenciated by the URI scheme.</p>"},{"location":"fep/1970/#implementations","title":"Implementations","text":"<p>At the time of the writing, this FEP is been implemented in the Peertube livechat plugin, a chat plugin for Peertube.</p>"},{"location":"fep/1970/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[Microformats existing rel values] Microformats existing rel values, referenced by the HTML Living Standard.</li> </ul>"},{"location":"fep/1970/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/1985/","title":"FEP-1985: Signaling how an OrderedCollection is ordered","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2024-10-10 #417 Discussions codeberg"},{"location":"fep/1985/#summary","title":"Summary","text":"<p>OrderedCollection is defined as an ordered set in the Activity Vocabulary, but the precise ordering is not defined. The ActivityPub specification requires that instances of OrderedCollection MUST be ordered reverse chronologically by insertion order, but a later errata was proposed to relax this restriction by only applying it to properties defined as OrderedCollection within the ActivityPub specification. Consequently, this allows for some collections to be presented forward chronologically by insertion order, and some collections to be presented reverse chronologically by insertion order. This FEP introduces an <code>orderType</code> property and two vocabulary terms <code>ForwardChronological</code> and <code>ReverseChronological</code> to explicitly signal the ordering of a collection.</p>"},{"location":"fep/1985/#terms-defined","title":"Terms defined","text":"orderType URI <code>https://w3id.org/fep/1985/orderType</code> Label is ordered in Comment Indicates the type of ordering for an OrderedCollection. Domain OrderedCollection Range OrderingClass (@vocab) Required No Functional Yes See also ForwardChronological | ReverseChronological Is defined by FEP-1985 <pre>\n<code>\n{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n</code>\n</pre> <pre>\n<code>\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n</code>\n</pre> OrderingClass URI <code>https://w3id.org/fep/1985/OrderingClass</code> Label OrderingClass Comment Abstract base class for the <code>orderType</code> property's range. See also ForwardChronological | ReverseChronological Is defined by FEP-1985 ForwardChronological URI <code>https://w3id.org/fep/1985/ForwardChronological</code> Label forward chronological order Comment This OrderedCollection is ordered forward chronologically, and adding an item to this collection will add it at the end. Subclass of OrderingClass See also orderType Is defined by FEP-1985 ReverseChronological URI <code>https://w3id.org/fep/1985/ReverseChronological</code> Label reverse chronological order Comment This OrderedCollection is ordered reverse chronologically, and adding an item to this collection will add it at the start. Subclass of OrderingClass See also orderType Is defined by FEP-1985"},{"location":"fep/1985/#examples","title":"Examples","text":"<p>(This section is non-normative.)</p> <p>Example of a forward chronological OrderedCollection with additional context:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n</code></pre> <p>Example of a forward chronological OrderedCollection without additional context:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n</code></pre> <p>Example of a reverse chronological OrderedCollection with additional context:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/3\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/1\"\n ],\n \"orderType\": \"ReverseChronological\"\n}\n</code></pre> <p>Example of a reverse chronological OrderedCollection without additional context:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/3\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/1\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ReverseChronological\"\n }\n}\n</code></pre>"},{"location":"fep/1985/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>W3C Wiki contributors, ActivityPub errata, 2024</li> <li>a, Evolving OrderedCollection to be more useful, 2020</li> </ul>"},{"location":"fep/1985/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/1985/ForwardChronological/","title":"ForwardChronological","text":"<p>URI : <code>https://w3id.org/fep/1985/ForwardChronological</code></p> <p>Label : forward chronological order</p> <p>Comment : This OrderedCollection is ordered forward chronologically, and adding an item to this collection will add it at the end.</p> <p>Subclass of : OrderingClass</p> <p>See also : orderType</p> <p>Is defined by : FEP-1985</p>"},{"location":"fep/1985/OrderingClass/","title":"OrderingClass","text":"<p>URI : <code>https://w3id.org/fep/1985/OrderingClass</code></p> <p>Label : OrderingClass</p> <p>Comment : Abstract base class for the <code>orderType</code> property's range.</p> <p>See also : ForwardChronological | ReverseChronological</p> <p>Is defined by : FEP-1985</p>"},{"location":"fep/1985/ReverseChronological/","title":"ReverseChronological","text":"<p>URI : <code>https://w3id.org/fep/1985/ReverseChronological</code></p> <p>Label : reverse chronological order</p> <p>Comment : This OrderedCollection is ordered reverse chronologically, and adding an item to this collection will add it at the start.</p> <p>Subclass of : OrderingClass</p> <p>See also : orderType</p> <p>Is defined by : FEP-1985</p>"},{"location":"fep/1985/orderType/","title":"orderType","text":"<p>URI : <code>https://w3id.org/fep/1985/orderType</code></p> <p>Label : is ordered in</p> <p>Comment : Indicates the type of ordering for an OrderedCollection.</p> <p>Domain : OrderedCollection</p> <p>Range : OrderingClass (@vocab)</p> <p>Required : No</p> <p>Functional : Yes</p> <p>See also : ForwardChronological | ReverseChronological</p> <p>Is defined by : FEP-1985</p>"},{"location":"fep/1985/orderType/#examples","title":"Examples","text":"<p>Example of a forward chronological OrderedCollection with additional context</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n</code></pre> <p>Example of a forward chronological OrderedCollection without additional context</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n</code></pre>"},{"location":"fep/19b3/","title":"FEP-19b3: Specifying Properties of a Service","text":"Authors Status Date received Tracking issue Discussions Repository Helge <code>DRAFT</code> 2025-11-04 #719 Discussions codeberg"},{"location":"fep/19b3/#summary","title":"Summary","text":"<p>Actors of type <code>Service</code> are used in the Fediverse to represent automated process. In this FEP, we suggest some property values to use to convey further information about the underlying automated process and the responsible parties for the automated process.</p>"},{"location":"fep/19b3/#property-values","title":"Property Values","text":"<p>Property values can be attached to an actor to specify additional values. Using property values has two key advantage:</p> <ul> <li>It's a standardized approach</li> <li>They are visible to Fediverse users</li> </ul> <p>The official definition of <code>PropertyValue</code> can be found at PropertyValue - Schema.org Type. The section Examples of Property Value contains examples and further discussions on the usage of property value.</p>"},{"location":"fep/19b3/#suggested-property-values","title":"Suggested Property Values","text":"<p>The property values suggested here are meant to provide information to the users of the Fediverse. They are not meant to influence the behavior of Fediverse applications.</p>"},{"location":"fep/19b3/#source","title":"Source","text":"<p>This property value should provide a link to the source code, e.g.</p> <pre><code>{\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n}\n</code></pre>"},{"location":"fep/19b3/#author","title":"Author","text":"<p>The Author field would specify the author of the source linked in Source. The exact format of the value can be debated. Some possibilities </p> <ul> <li>It could be a Fediverse handle, e.g. <code>@helge@mymath.rocks</code>,</li> <li>the corresponding acct-uri, e.g. <code>acct:helge@mymath.rocks</code></li> <li>or even a webpage </li> <li>or an email address (again with a discussion between <code>user@domain.example</code> or <code>mailto:user@domain.example</code>)</li> </ul>"},{"location":"fep/19b3/#support","title":"Support","text":"<p>If the author of the service is not the one running it, one needs to use an alternative field. We suggest using Support in this case.</p>"},{"location":"fep/19b3/#frequency","title":"Frequency","text":"<p>One can run services with a periodic trigger, e.g. cron. This indicates their frequency. Example:</p> <pre><code>{\n \"type\": \"PropertyValue\",\n \"name\": \"Frequency\",\n \"value\": \"At 42 minutes past the hour\"\n}\n</code></pre>"},{"location":"fep/19b3/#servicetype","title":"ServiceType","text":"<p>Indicates the type of the service, e.g. <code>Comment Tracking System</code>, see FEP-136c. This should be primarily used for services that do more complicated processing, and link to documentation what the service does.</p> <pre><code>{\n \"type\": \"PropertyValue\",\n \"name\": \"ServiceType\",\n \"value\": \"Comment Tracking System, https://bovine.codeberg.page/comments/136c/fep-136c/\"\n}\n</code></pre> <p>We imagine that specifying Reacts To Mentions would be useful to indicate that this is a bot the answers when mentioned. Furthermore, for bots just as @release, once might specify something like On Event, as they are triggered when continuous integration jobs are run.</p>"},{"location":"fep/19b3/#examples-of-property-value","title":"Examples of property value","text":"<p>The following example is taken from the actor below</p> <pre><code>{\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n}\n</code></pre> <p>In difference to the example provided by us, mastodon uses HTML as the value of property values, e.g.</p> <pre><code>{\n \"type\": \"PropertyValue\",\n \"name\": \"\ud83e\udd16\",\n \"value\": \"&lt;a href=\\\"https://botwiki.org\\\" target=\\\"_blank\\\" rel=\\\"nofollow noopener me\\\" translate=\\\"no\\\"&gt;&lt;span class=\\\"invisible\\\"&gt;https://&lt;/span&gt;&lt;span class=\\\"\\\"&gt;botwiki.org&lt;/span&gt;&lt;span class=\\\"invisible\\\"&gt;&lt;/span&gt;&lt;/a&gt;\"\n}\n</code></pre> <p>This example also shows another behavior, we recommend against: using emojis as name. This has the disadvantage of being awkward to parse (in particular for people using screen readers).</p>"},{"location":"fep/19b3/#example-of-actor-with-property-value","title":"Example of actor with property value","text":"<p>An example of an actor with property values looks like</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"PropertyValue\": {\n \"@id\": \"https://schema.org/PropertyValue\",\n \"@context\": {\n \"value\": \"https://schema.org/value\",\n \"name\": \"https://schema.org/name\"\n }\n }\n }\n ],\n \"id\": \"https://dev.bovine.social/actor/kH3y9kw8cqRUgzso3oM3_w\",\n \"type\": \"Service\",\n \"preferredUsername\": \"release\",\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n },\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n }\n ], \n \"...\": \"...\"\n}\n</code></pre> <p>Note</p> <p>There are variance with <code>@context</code> which change which URIs property values expand if one uses JSON-LD. As JSON-LD is unused for all practical purposes, this currently does not matter. See the Appendix for details on what goes wrong.</p>"},{"location":"fep/19b3/#appendix-the-json-ld-problem","title":"Appendix: The JSON-LD problem","text":"<p>The document</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"PropertyValue\": {\n \"@id\": \"https://schema.org/PropertyValue\",\n \"@context\": {\n \"value\": \"https://schema.org/value\",\n \"name\": \"https://schema.org/name\"\n }\n }\n }\n ],\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n }\n ]\n}\n</code></pre> <p>turns to </p> <pre><code>{\n \"https://www.w3.org/ns/activitystreams#attachment\": {\n \"@type\": \"https://schema.org/PropertyValue\",\n \"https://schema.org/name\": \"Author\",\n \"https://schema.org/value\": \"acct:helge@mymath.rocks\"\n },\n \"https://www.w3.org/ns/activitystreams#name\": \"release\"\n}\n</code></pre> <p>when compacted against <code>\"@context\": {}</code>. Any variation of <code>@context</code> should behave similarly. The obvious choices</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://schema.org\"\n ],\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n }\n ]\n}\n</code></pre> <p>and with the order of <code>\"https://www.w3.org/ns/activitystreams\"</code> and <code>\"https://schema.org\"</code> reversed lead to incorrect results due to name being overloaded.</p>"},{"location":"fep/19b3/#references","title":"References","text":"<ul> <li>Helge, FEP-136c: Comment Tracking Services, in preparation</li> <li>fediverse-ideas: Botiquette</li> <li>schema.org PropertyValue - Schema.org Type</li> </ul>"},{"location":"fep/19b3/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/1b12/","title":"FEP-1b12: Group federation","text":"Authors Status Date received Date final Tracking issue Discussions Repository Felix Ableitner me@nutomic.com <code>FINAL</code> 2022-11-12 2023-02-09 #22 - codeberg"},{"location":"fep/1b12/#summary","title":"Summary","text":"<p>Internet forums are one of the oldest forms of social media. This document describes how they are implemented in existing Activitypub platforms using <code>Group</code> actors. It also introduces a new property to indicate that a given object belongs to a group.</p>"},{"location":"fep/1b12/#history","title":"History","text":"<p>Friendica released federated forums in version 2019.03.</p> <p>Lemmy published the first public beta of federated groups in v0.8.0 (October 2020).</p> <p>[FEP-400e] introduces publicly appendable collections, which can also be used to implement forums. However they are incompatible with the implementations described here.</p>"},{"location":"fep/1b12/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in [RFC-2119].</p>"},{"location":"fep/1b12/#group-actor","title":"Group actor","text":"<p>A federated forum is represented by a <code>Group</code> actor as specified in [Activity Vocabulary]. This actor is not directly controlled by a human, but can perform its main functionality in a fully automated way, essentially acting as a bot. It has the same general properties as any <code>Person</code> actor.</p>"},{"location":"fep/1b12/#following-a-group","title":"Following a Group","text":"<p>Public groups SHOULD support a standard <code>Follow</code>-<code>Accept</code> workflow. This ensures compatibility with existing implementations which support <code>Person</code> following using the same vocabulary. After receiving a valid <code>Follow</code> activity, the group SHOULD automatically respond with an <code>Accept/Follow</code>, and add the sender to its followers collection. Correspondingly actors can unfollow a group by sending an <code>Undo/Follow</code> activity.</p>"},{"location":"fep/1b12/#audience-property","title":"Audience property","text":"<p>In order to render content in a forum, it is necessary to know which particular forum the content belongs to. This way users can navigate up from a thread to the forum's main page, or view metadata like the description or moderators. With current implementations there is no easy way to determine which forum a given activity or object belongs to, if any.</p> <p>Currently there are different approaches to specify which group a given object or activity belongs to. Lemmy, Friendica and lotide put the group ID in the <code>to</code> field. Peertube uses <code>attributedTo</code>. Both properties have the problem that they are also used for different purposes by some platforms, and are represented as arrays. So to retrieve the group from a received object, an implementation needs to loop through these properties and resolve each URL, until it hits one which resolves to a <code>Group</code>. Clearly this is very inefficient.</p> <p>To simplify this process, we propose to specify the group identifier in the <code>audience</code> property. This property is part of Activitystreams, but not yet used in the wild. This way no extension is necessary, and neither will it cause problems for existing implementations. Additionally, platforms can continue to federate the group identifier in the existing format for backwards compatibility.</p>"},{"location":"fep/1b12/#threads-and-comments","title":"Threads and comments","text":"<p>Each <code>Group</code> actor represents a single forum. Forums contain many user-submitted threads, which can be represented by different objects depending on the implementation. Thread objects SHOULD have a <code>name</code> property which represents the thread title.</p> <p>Example thread: <pre><code>{\n \"type\": \"Page\",\n \"id\": \"https://sally.example.org/p/1\",\n \"attributedTo\": \"https://sally.example.org\",\n \"name\": \"Hello forum!\",\n \"audience\": \"https://example.org/my-forum\"\n}\n</code></pre></p> <p>Threads can have replies, which are usually represented as <code>Note</code>s. They MUST have a property <code>inReplyTo</code> referencing either the thread they belong to, or the parent reply in case of nested replies.</p> <p>Example reply: <pre><code>{\n \"type\": \"Note\",\n \"id\": \"https://sally.example.org/p/3\",\n \"attributedTo\": \"https://sally.example.org\",\n \"inReplyTo\": \"https://sally.example.org/p/1\",\n \"content\": \"My first comment\",\n \"audience\": \"https://example.org/my-forum\"\n}\n</code></pre></p> <p>The properties <code>inReplyTo</code> and <code>audience</code> can be used to navigate up from a reply to a thread and forum. To navigate down from a group to threads and comments, groups MAY have a <code>replies</code> collection which contains all threads. Each thread MAY again have a <code>replies</code> collection which lists all top-level comments responding to the thread.</p>"},{"location":"fep/1b12/#the-announce-activity","title":"The Announce activity","text":"<p>The main task of a group is to distribute content among its followers.</p> <p>When a group receives a activity in its inbox, it SHOULD perform some automatic validation, such as checking for domain and user blocks. Groups MAY require additional validation, such as accepting content only from followers, or even manual approval from group moderators. In case an activity fails these checks, the group MAY respond to the sender with a <code>Reject</code> activity.</p> <p>In case the incoming activity is deemed valid, the group MUST wrap it in an <code>Announce</code> activity, with the original activity as object. The wrapped activity MUST be preserved exactly as it was received, without changing or removing any properties. This ensures that forwarded activities can be verified with [Object Integrity Proofs]. Announce activities SHOULD get added to the group outbox. If the group exposes collections of threads and comments, relevant items should also be added to them.</p> <p>Example: <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.org/my-forum\",\n \"id\": \"example.org/a/5\",\n \"object\": {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Create\",\n \"id\": \"https://sally.example.org/a/2\"\n \"actor\": \"https://sally.example.org\",\n \"audience\": \"https://example.org/my-forum\",\n \"object\": {\n \"type\": \"Page\",\n \"id\": \"https://sally.example.org/p/1\",\n \"content\": \"Hello forum!\",\n \"audience\": \"https://example.org/my-forum\"\n }\n }\n}\n</code></pre></p> <p>After the group successfully verifies and wraps the received activity, it sends it to the inboxes of its followers. Followers then use the outer <code>Announce</code> activity to verify that the content was really approved by the group. After this step the <code>Announce</code> can be discarded and only the inner activity shown to users.</p> <p>This mechanism can be used to publish any possible activity type. Examples include <code>Announce/Like</code>, <code>Announce/Delete/Note</code> or <code>Announce/Undo/Like</code>. Implementations may choose not to forward some activity types which are considered private, for example <code>Follow</code> activities.</p>"},{"location":"fep/1b12/#group-moderation","title":"Group moderation","text":"<p>Group moderators are those actors who control the group, are able to change its metadata and remove malicious content. They are listed in the group\u2019s <code>attributedTo</code> collection. Moderation is an optional feature, implementations can safely ignore this entire section. At the moment it is only implemented by Lemmy.</p> <pre><code>{\n \"id\": \"https://example.org/my-forum\",\n \"type\": \"Group\",\n \"name\": \"Ten Forward\",\n \"attributedTo\": \"https://example.org/my-forum/moderators\",\n}\n</code></pre> <pre><code>{\n \"type\": \"OrderedCollection\",\n \"id\": \"https://example.org/my-forum/moderators\",\n \"orderedItems\": [\n \"https://example.org/picard\",\n \"https://example.org/riker\"\n ]\n}\n</code></pre> <p>Group moderators can be changed with <code>Add</code> and <code>Remove</code> activities:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.org/my-forum\",\n \"id\": \"example.org/a/8\",\n \"object\": {\n \"id\": \"https://example.org/a/7\",\n \"type\": \"Add\",\n \"actor\": \"https://example.org/picard\",\n \"object\": \"http://example.org/data\",\n \"audience\": \"https://example.org/my-forum\",\n \"target\": \"https://example.org/my-forum/moderators\"\n }\n}\n</code></pre> <p>The actions which can be done by moderators are called moderation activities. These are implementation specific, examples include <code>Add</code>, <code>Remove</code> (to change the moderators collection), <code>Block</code> (ban malicious users) and <code>Update/Group</code> (change group metadata).</p> <p>If an group or group follower supports moderation, it MUST validate incoming moderation activities before further processing. Such activities MUST have an actor who is listed in <code>attributedTo</code>. Group followers MUST additionally verify that the moderation activity was announced by the group.</p> <p>Implementations SHOULD also accept moderation activities which come from the same server where the community is hosted, under the assumption that these are sent by server administrators. These moderation activities also need to be wrapped in <code>Announce</code> by the group.</p>"},{"location":"fep/1b12/#implementations","title":"Implementations","text":"<p>This document is written based on existing group implementations in Lemmy, Friendica, Hubzilla, Lotide and Peertube. These already federate successfully in production.</p> <p>The <code>audience</code> field is an exception as it is not in use yet. Lemmy will add support for it in version 0.17.0.</p>"},{"location":"fep/1b12/#references","title":"References","text":"<ul> <li>[FEP-400e] Gregory Klyushnikov, FEP-400e: Publicly-appendable ActivityPub collections</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> <li>[Activity Vocabulary], James M Snell, Evan Prodromou, Activity Vocabulary</li> <li>[Object Integrity Proofs] silverpill, FEP-8b32: Object Integrity Proofs</li> </ul>"},{"location":"fep/1b12/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/2100/","title":"Index","text":"<p>hide: - navigation description: \"Historically, after the sudden death of a popular instance, one could neither target groups hosted at it anymore nor contact the whole followers collection to let them know of the new instance housing a certain group. If we always have absolute knowledge of the complete followers collection (or good enough), we can automate based on which instance has more local followers which server would become the new house. Another alternative would be to automatically archive the old group and start again from scratch. This FEP, on the other hand, discusses something very different of automatically moving an actor from one server to a different one. It is about collaboration between different group or organization actors to promote an unified experience between the participants of the linked group actors. We think this may be easier, more flexible, and promote a better UX than only notifying the actor that the house of a certain group has moved, but both solutions would probably achieve similar results in the above use case. This proposal introduces an interpretation of a Group following another Group and the gs unbound attribute. This allow two groups (or organization) to \"act as one\" (not exactly, but elaborated afterwards). This primarily aims at effectively removing a central point of authority for groups, but offers more than that. With this, @alice@undefinedhackers.net can mention a group named hackers (!hackers) or even address an activity To !hackers@instance.gnusocial.test (C2S) and let her instance's !hackers announce to other instances' !hackers. Finally, this proposal is general enough to allow a server to simultaneously have !lug@server (without links), !lug-unbound@server (with the greatest links collection it can grow), and !lug-with-some-links@server (with only some links). It doesn't require linked groups to have the same preferredUsername.\"</p>"},{"location":"fep/2100/#fep-2100-unbound-group-and-organization","title":"FEP-2100: Unbound Group and Organization","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Diogo Peralta Cordeiro mail@diogo.site <code>WITHDRAWN</code> 2022-03-31 2025-04-06 #36 Discussions codeberg <p>This FEP wasn't a result of my individual work but rather of the joint effort in this SocialHub discussion and, prior to that, the discussion in GNU social's IRC/XMPP with rozzin (Joshua Judson Rosen) and someonewithpc (Hugo Sales).</p>"},{"location":"fep/2100/#summary","title":"Summary","text":"<p>Historically, after the sudden death of a popular instance, one could neither target groups hosted at it anymore nor contact the whole followers collection to let them know of the new instance housing a certain group. If we always have absolute knowledge of the complete followers collection (or good enough), we can automate based on which instance has more local followers which server would become the new house. Another alternative would be to automatically archive the old group and start again from scratch.</p> <p>This FEP, on the other hand, discusses something very different of automatically moving an actor from one server to a different one. It is about collaboration between different group or organization actors to promote an unified experience between the participants of the linked group actors. We think this may be easier, more flexible, and promote a better UX than only notifying the actor that the house of a certain group has moved, but both solutions would probably achieve similar results in the above use case.</p> <p>This proposal introduces an interpretation of a Group following another Group and the <code>gs:unbound</code> attribute. This allow two groups (or organization) to \"act as one\" (not exactly, but elaborated afterwards).</p> <p>This primarily aims at effectively removing a central point of authority for groups, but offers more than that. With this, <code>@alice@undefinedhackers.net</code> can mention a group named hackers (!hackers) or even address an activity To <code>!hackers@instance.gnusocial.test</code> (C2S) and let her instance's !hackers announce to other instances' !hackers.</p> <p>Finally, this proposal is general enough to allow a server to simultaneously have <code>!lug@server</code> (without links), <code>!lug-unbound@server</code> (with the greatest links collection it can grow), and <code>!lug-with-some-links@server</code> (with only some links). It doesn't require linked groups to have the same <code>preferredUsername</code>.</p>"},{"location":"fep/2100/#notation-and-definitions","title":"Notation and Definitions","text":"<p>To keep things simple, sometimes you will see things formatted like <code>Activity{Object}</code>. For example, <code>Create{Note}</code> would be a <code>Create</code> activity containing a <code>Note</code> in the object field. Also, we will focus in Actor of type <code>Group</code>, but nothing should stop from using this for <code>Organization</code>.</p> <ul> <li><code>@nickname@server</code> will be used to refer Actors of type Person or Application.</li> <li><code>!nickname@server</code> will be used to refer Actors of type Group or Organization.</li> <li><code>@#!group@server#collection</code> will be used to refer collection <code>collection</code> of <code>!group@server</code>.</li> </ul> <p>The key words MAY, MUST, MUST NOT, SHOULD, and SHOULD NOT are to be interpreted as described in [RFC2119].</p>"},{"location":"fep/2100/#links-between-groups-terminology","title":"Links between Groups terminology","text":""},{"location":"fep/2100/#activitystreams-20-requirements-for-this-mechanism","title":"ActivityStreams 2.0 requirements for this mechanism","text":""},{"location":"fep/2100/#example-group-actor-in-this-fep","title":"Example Group Actor in this FEP","text":"<pre><code>{\n \"type\": \"Group\",\n \"streams\": [],\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gs\": \"https://www.gnu.org/software/social/ns#\"\n },\n {\n \"unbound\": {\n \"@id\": \"gs:unbound\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"id\": \"https://instance.gnusocial.test/group/hackers\",\n \"unbound\": true,\n \"preferredUsername\": \"hackers\",\n \"endpoints\": {\n \"sharedInbox\": \"https://instance.gnusocial.test/inbox.json\"\n },\n \"inbox\": \"https://instance.gnusocial.test/group/hackers/inbox.json\",\n \"outbox\": \"https://instance.gnusocial.test/group/hackers/outbox.json\",\n \"following\": \"https://instance.gnusocial.test/group/hackers/subscriptions\",\n \"followers\": \"https://instance.gnusocial.test/group/hackers/subscribers\",\n}\n</code></pre>"},{"location":"fep/2100/#creating-a-link-between-two-group-actors","title":"Creating a link between two group actors","text":"<p>Creating a directed link between two group actors is just a regular Follow request between any two actors.</p> <p>Assume that <code>!hackers@instance.gnusocial.test</code> sends a Follow request to <code>!lug@gnusocial.net</code>.</p> <p>If <code>gs:unbound: false</code> or not present, then if <code>!lug@gnusocial.net</code> accepts the Follow request, it will Announce{*} entering its inbox to <code>!hackers@instance.gnusocial.test</code>.</p> <p>If <code>gs:unbound: true</code>, then <code>!lug@gnusocial.net</code> will both accept the Follow request and submit a Follow request of its own to <code>!hackers@instance.gnusocial.test</code>.</p> <p>If both <code>!hackers@instance.gnusocial.test</code> and <code>!lug@gnusocial.net</code> have added each other to their linksTo, they will act as if they were the same group. If they have equivalent <code>groupLinks</code> collections, then they are essentially fully mirrored groups.</p> <p>Note that the \"Link negotiation\" happens between two Group actors (S2S).</p>"},{"location":"fep/2100/#some-scenarios","title":"Some scenarios","text":""},{"location":"fep/2100/#1-group-a-follows-group-b-which-has-gsunbound-false","title":"1. Group A follows Group B which has <code>gs:unbound = false</code>","text":"<ul> <li>A SHOULD NOT attempt to Follow B;</li> <li>If B receives a Follow from A, it SHOULD reject.</li> </ul>"},{"location":"fep/2100/#2-group-a-follows-group-b-which-has-gsunbound-true","title":"2. Group A follows Group B which has <code>gs:unbound = true</code>","text":"<ul> <li>A SHOULD send a Follow to B;</li> <li>B SHOULD Accept;</li> <li>B SHOULD Follow A, if A has <code>gs:unbound = true</code>.</li> </ul>"},{"location":"fep/2100/#3-group-a-follows-group-b-which-has-no-gsunbound-attribute","title":"3. Group A follows Group B which has no <code>gs:unbound</code> attribute","text":"<ul> <li>A SHOULD send a Follow to B;</li> <li>B MAY Accept.</li> </ul>"},{"location":"fep/2100/#4-forwarding-from-inbox","title":"4. Forwarding from Inbox","text":"<ul> <li><code>!hackers@</code>C: Announce{Note} TO <code>!hackers@</code>[B] (S2S)</li> <li>B MUST NOT forward this to other groups. If other groups expect to receive this activity, then they must follow <code>!hackers@</code>C as well.</li> </ul>"},{"location":"fep/2100/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[ActivityStreams Vocabulary] James M Snell, Evan Prodromou, ActivityStreams Vocabulary, 2017</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> </ul>"},{"location":"fep/2100/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/2277/","title":"FEP-2277: ActivityPub core types","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> informational 2025-01-31 #485 Discussions codeberg"},{"location":"fep/2277/#summary","title":"Summary","text":"<p>Classification of ActivityPub objects based on their shape.</p>"},{"location":"fep/2277/#rationale","title":"Rationale","text":"<p>ActivityPub applications often have different processing rules for actors, activities, collections and other objects. In most cases, the class of an object can be inferred from its context: object delivered to inbox is expected to be an activity, and the value of its <code>actor</code> property is expected to be an actor.</p> <p>However, the class can not always be inferred from context. In some cases, only the object ID is known, such as when it is provided by a user. Embeddings may also be ambiguous:</p> <ul> <li>The <code>object</code> of <code>Update</code> activity can be an object or an actor.</li> <li>The <code>object</code> of <code>Announce</code> activity can be an object or an activity.</li> </ul> <p>Applications may use the <code>type</code> property to determine object's class, but that hinders interoperabilty because such applications would not be able to process objects with unknown types. Therefore, a different approach would be preferable.</p>"},{"location":"fep/2277/#core-object-types","title":"Core object types","text":"<p>Activity Streams 2.0 standard defines 8 core object types:</p> <ul> <li><code>Object</code></li> <li><code>Link</code></li> <li><code>Activity</code></li> <li><code>IntransitiveActivity</code></li> <li><code>Collection</code></li> <li><code>OrderedCollection</code></li> <li><code>CollectionPage</code></li> <li><code>OrderedCollectionPage</code></li> </ul> <p>Unfortunately, definitions provided in the specification are not precise. Only <code>Object</code> and <code>Link</code> are defined as disjoint types, meaning an object could be an <code>Activity</code> and a <code>Collection</code> at the same time. \"Actors\" are described as specializations of <code>Object</code>, but there is no corresponding <code>Actor</code> core type.</p> <p>The lack of good definitions and the exclusion of the <code>Actor</code> type make standard classification unsuitable for practical purposes. Therefore, applications may need to use a different classification.</p> <p>One way to divide objects into distinct classes is to look at their properties and their connections to other objects (indicated by their properties). This approach can be used to define 7 core types:</p> <ul> <li><code>Actor</code>: an entity that publishes and receives activities.</li> <li><code>Activity</code>: an action taken by an actor.</li> <li><code>Collection</code>: a container for other objects (a collection or a collection page).</li> <li><code>VerificationMethod</code>: a verification method.</li> <li><code>PublicKey</code>: a public key (a legacy form of a verification method).</li> <li><code>Link</code>: a link.</li> <li><code>Object</code>: all other objects.</li> </ul> <p>The next section specifies an algorithm that classifies any ActivityPub object as one of these core types by analyzing the object's shape. This technique is often referred to as duck typing.</p>"},{"location":"fep/2277/#duck-typing","title":"Duck typing","text":"<p>The following algorithm can be used to determine the core type of the object:</p> <ol> <li>If object has <code>inbox</code> and <code>outbox</code> properties, return <code>Actor</code>.</li> <li>If object has <code>publicKeyMultibase</code> property, return <code>VerificationMethod</code>.</li> <li>If object has <code>publicKeyPem</code> property, return <code>PublicKey</code>.</li> <li>If object has <code>href</code> property, return <code>Link</code>.</li> <li>If object has <code>actor</code> property, return <code>Activity</code>.</li> <li>If object has <code>items</code>, <code>orderedItems</code>, <code>totalItems</code>, <code>partOf</code>, <code>first</code>, <code>last</code>, <code>next</code>, <code>prev</code> or <code>current</code> property, return <code>Collection</code>.</li> <li>Otherwise, return <code>Object</code>.</li> </ol> <p>Application of this algorithm results in non-overlapping core types. For example, an actor with <code>items</code> property is still an actor and not a collection.</p> <p>The value of <code>type</code> property is not taken into consideration.</p> <p>Warning</p> <p>ActivityPub standard requires actors to have both <code>inbox</code> and <code>outbox</code> properties, but in practice <code>outbox</code> is not always present. If compatibility with non-conformant implementations is desirable, step #1 can be changed to \"If object has <code>inbox</code> property, return <code>Actor</code>\".</p> <p>Warning</p> <p>Pleroma adds an <code>actor</code> property to objects that are not activities. To make an allowance for that, the step #5 of the algorithm can be changed to \"If object has an <code>actor</code> property, and doesn't have an <code>attributedTo</code> property, return <code>Activity</code>\".</p>"},{"location":"fep/2277/#json-ld","title":"JSON-LD","text":"<p>The output of the algorithm might be different in LD-aware and in LD-unaware applications due to a possibility of re-mapping of terms in <code>@context</code>.</p> <p>This may pose a security risk.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"foo\": \"as:inbox\",\n \"bar\": \"as:outbox\"\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://social.example/note\",\n \"foo\": \"https://social.example/inbox\",\n \"bar\": \"https://social.example/outbox\"\n}\n</code></pre>"},{"location":"fep/2277/#alternatives-considered","title":"Alternatives considered","text":""},{"location":"fep/2277/#multi-typing","title":"Multi-typing","text":"<p>The alternative to duck typing is to use multiple types. For example, this object can be unambiguously identified as an <code>Activity</code>:</p> <pre><code>{\n \"type\": [\"Bite\", \"Activity\"]\n}\n</code></pre> <p>However, existing implementations don't add a second type, and even if changing all of them were possible, duck typing would still need to be used as a fallback during the transitional period.</p>"},{"location":"fep/2277/#type-hierachies","title":"Type hierachies","text":"<p>The core type of an object can be determined via the definition of its <code>type</code> in a vocabulary, but that means all ActivityPub applications would have to support JSON-LD.</p>"},{"location":"fep/2277/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Streams 2.0, 2017</li> </ul>"},{"location":"fep/2277/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/22b6/","title":"FEP-22b6: Linking an ActivityPub Object to a HTML page and back","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocks <code>DRAFT</code> 2025-11-12 #730 Discussions codeberg"},{"location":"fep/22b6/#summary","title":"Summary","text":"<p>Links are a fundamental part of the internet. This FEP describes how to use links to link a HTML page to an ActivityPub object. The mechanisms described in this document are not new and are used to link to RSS feeds (see alternate, second example).</p>"},{"location":"fep/22b6/#linking-from-a-html-page","title":"Linking from a HTML page","text":"<p>The HTML living standard states about alternate</p> <p>The keyword creates a hyperlink referencing an alternate representation of the current document.</p> <p>In this sense, we can link from the HTML page to an ActivityPub object using </p> <pre><code>&lt;head&gt;\n ...\n &lt;link rel=\"alternate\" type=\"application/activity+json\" href=\"http://location.example/object.id\"&gt;\n ...\n&lt;/head&gt;\n</code></pre> <p>which can be used for automatic discovery. To provide a link for the user, one should use an anchor, e.g.</p> <pre><code>&lt;body&gt;\n ...\n &lt;a rel=\"alternate\" type=\"application/activity+json\" href=\"http://location.example/object.id\"&gt;\n ActivityPub Object\n &lt;/a&gt;\n ...\n&lt;/body&gt;\n</code></pre> <p>We recommend using both forms as they serve different purposes.</p> <p>Warning</p> <p>One might be tempted to use canonical instead of alternate if the HTML page just renders the content of the ActivityPub object. The author believes that sticking to the single format giving here has the highest chance of leading to widespread support.</p>"},{"location":"fep/22b6/#discussions","title":"Discussions","text":"<p>As the example below shows, there are use cases when the ActivityPub object and the page are on different servers. One can however still check the relationship, by verifying the ActivityPub object contains a link back to the page.</p> <p>Similarly, the ActivityPub object might provide less content than the webpage.</p>"},{"location":"fep/22b6/#example","title":"Example","text":"<p>The page https://bovine.codeberg.page/comments/ contains the link element</p> <pre><code>&lt;link rel=\"alternate\"\n href=\"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=\"\n type=\"application/activity+json\"&gt;\n</code></pre> <p>and the linked object resolves to</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Page\",\n \"attributedTo\": \"https://comments.bovine.social/actor/rF4xnx1QraAIU3Krg-2Qzg\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"published\": \"2025-10-20T17:45:11Z\",\n \"url\": [\n {\n \"type\": \"Link\",\n \"href\": \"https://bovine.codeberg.page/comments/\",\n \"mediaType\": \"text/html\"\n }\n ],\n \"name\": \"Comment Tracking System\",\n \"summary\": null,\n \"id\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=\",\n \"context\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/context\",\n \"replies\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/replies\",\n \"likes\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/likes\",\n \"shares\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/shares\"\n}\n</code></pre> <p>The Page object can be understood as Page in the ActivityVocabulary here, i.e. \"Represents a Web Page\".</p>"},{"location":"fep/22b6/#alternatives","title":"Alternatives","text":"<p>As already said, we recommend using this suggested approach to link from the HTML to an ActivityPub object.</p> <p>However, there are other methods that are in use to provide the link, and should be considered valid options.</p> <ul> <li>Content Negotiation</li> <li>Link Headers</li> </ul> <p>For the use case of FEP-136c: Comment Tracking Services, that motivates this work, embedding the links in the HTML is the only viable option. This is due to the HTML often being hosted on static sites that do not provide the option to use Content Negotiation or Link Headers.</p>"},{"location":"fep/22b6/#references","title":"References","text":"<ul> <li>Helge, FEP-136c: Comment Tracking Services, in preparation</li> <li>HTML Living Standard, 4.6.7.1 Link type \"alternate\"</li> <li>HTML Living Standard, 4.6.7.4 Link type \"canonical\"</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary</li> </ul>"},{"location":"fep/22b6/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/2677/","title":"FEP-2677: Identifying the Application Actor","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.com <code>DRAFT</code> 2023-10-14 #190 - codeberg"},{"location":"fep/2677/#summary","title":"Summary","text":"<p>It is a common pattern in Fediverse applications to have a special actor of type <code>Application</code>. This is for example the actor at <code>https://mastodon.example/actor</code> for Mastodon or at <code>https://pleroma.example/internal/fetch</code> for Pleroma. This application actor can be fetched with an unsigned request, so it is possible to use it to fetch public keys.</p> <p>The goal of this FEP is to provide an explicit mechanism of identifying the application actor, with the goal of making it usable for further tasks, e.g.</p> <ul> <li>Allowing for application to application communication by having application actor send activities to another application actor's inbox.</li> <li>Having an object one can attach further information to. This means, one could attach a list of implemented FEPs to the application actor.</li> </ul>"},{"location":"fep/2677/#requirements","title":"Requirements","text":""},{"location":"fep/2677/#application-actor","title":"Application Actor","text":"<p>We will first define, what we mean by an application actor. We will impose two requirements on them</p> <ol> <li>It's an [ActivityPub] Actor of type Application.</li> <li>It can be retrieved without authentication, e.g. with an unsigned HTTP request.</li> </ol> <p>As already mentioned application actors are currently used by most Fediverse applications to fetch public keys. See here for a list of examples.</p>"},{"location":"fep/2677/#identifying-the-application-actor-with-nodeinfo","title":"Identifying the Application Actor with nodeinfo","text":"<p>In [NodeInfo], see also [FEP-f1d5], the well-known path <code>/.well-known/nodeinfo</code> is defined and it is specified that a document in JRD format [RFC 7033] is served there.</p> <p>The requirement of the current FEP is that the <code>/.well-known/nodeinfo</code> contains an additional link with relation type <code>https://www.w3.org/ns/activitystreams#Application</code>, which resolves to an application actor as described in the previous section.</p> <p>We note that it is not necessary to implement the relations specified in [NodeInfo] to satisfy the current FEP.</p>"},{"location":"fep/2677/#example","title":"Example","text":"<p>We will consider the server with domain <code>node.example</code>. This means that a request to <code>https://node.example/.well-known/nodeinfo</code> will resolve to</p> <pre><code> {\n \"links\": [\n {\n \"rel\": \"http://nodeinfo.diaspora.software/ns/schema/2.0\",\n \"href\": \"https://node.example/nodeinfo/2.0\"\n },\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Application\",\n \"href\": \"https://node.example/actor\"\n }\n ]\n }\n</code></pre> <p>Next, a request to <code>https://node.example/actor</code> with accept header <code>application/activity+json</code> could return</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/v1\",\n ],\n \"id\": \"https://node.example/actor\",\n \"type\": \"Application\",\n \"inbox\": \"https://node.example/actor/inbox\",\n \"outbox\": \"https://node.example/actor/outbox\",\n \"publicKey\": {\n \"id\": \"https://node.example/actor#main-key\",\n \"owner\": \"https://node.example/actor\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n....\\n-----END PUBLIC KEY-----\\n\"\n }\n}\n</code></pre>"},{"location":"fep/2677/#discussion","title":"Discussion","text":"<p>The approach of this FEP has been chosen to have a light touch. Some alternative choices would be:</p> <ol> <li>A fixed path for the application actor</li> <li>A fixed path for application information instead of aiming to attach it to the application actor.</li> </ol> <p>Both options have the downside that it would require to introduce a new path, which forces every implementation to implement it in a similar way. Instead, we reuse an existing path, and the implementer is free to choose where to put the application actor.</p> <p>The second question is why fix the type <code>Application</code> and not <code>Service</code>. First, it is consistent with most current implementations. Second, Mastodon uses <code>Service</code> for bot accounts. So it feels like a good idea to start distinguishing from this usage. We would phrase this distinction as follows:</p> <ul> <li>An actor of type <code>Application</code> is triggered by events in the application, e.g. a signed request and then fetching the corresponding public key.</li> <li>An actor of type <code>Service</code> is triggered by either activities arriving at its inbox or external events, e.g. a timer. This means an actor of type <code>Service</code> is similar to one controlled by an user.</li> </ul> <p>These are not hard rules on when to use <code>Application</code> or <code>Service</code>. They will probably break down as more complicated Fediverse implementations are build. We hope that they can serve as a level of guidance, how to differentiate actors.</p>"},{"location":"fep/2677/#currently-implemented-application-actors","title":"Currently implemented application actors","text":"Software Application Actor URI Bovine <code>https://bovine.example/activitypub/bovine</code> Firefish <code>https://firefish.example/actor</code> Lemmy <code>https://lemmy.example/</code> Mastodon <code>https://mastodon.example/actor</code> Mitra <code>http://mitra.example/actor</code> Pleroma <code>https://pleroma.example/internal/fetch</code> Mbin <code>https://mbin.example/i/actor</code> WordPress <code>https://wordpress.example/wp-json/activitypub/1.0/application</code> Mobilizon <code>https://mobilizon.example/relay</code> Gancio <code>https://gancio.example/federation/u/&lt;instance_name&gt;</code> Friendica <code>https://friendica.example/</code> PeerTube <code>https://peertube.example/accounts/peertube</code> Pixelfed <code>https://pixelfed.example/i/actor</code> <p>Note: Feel free to add further links.</p>"},{"location":"fep/2677/#implementations","title":"Implementations","text":"Software dateImplemented dateReleased WordPress 2023-12-21 - Mobilizon 2023-12-14 - Gancio 2023-12-22 -"},{"location":"fep/2677/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[FEP-f1d5] CJ, silverpill, NodeInfo in Fediverse Software, 2023</li> <li>[NodeInfo] NodeInfo protocol 2.1</li> <li>[RFC 2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>[RFC 7033] P. Jones, G. Salgueiro, M. Jones, J. Smarr, Webfinger, 2013</li> </ul>"},{"location":"fep/2677/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/268d/","title":"FEP-268d: Search consent signals for objects","text":"Authors Status Date received Tracking issue Discussions Repository Daiki \"tesaguri\" Mizukami tesaguriguma+fep-268d@gmail.com <code>DRAFT</code> 2024-10-12 #421 Discussions codeberg"},{"location":"fep/268d/#summary","title":"Summary","text":"<p>This FEP documents an extension property for [Activity Streams 2.0] to signal the consent for an object to be searched by a given actor.</p>"},{"location":"fep/268d/#history","title":"History","text":"<p>This section is non-normative.</p> <p>Fedibird introduced the <code>searchableBy</code> property in August 2022.</p> <p>kmyblue implemented the <code>searchableBy</code> property in April 2023.</p> <p>Mastodon began implementing the <code>indexable</code> property [FEP-5feb] in February 2023 and released it in September 2023.</p>"},{"location":"fep/268d/#background","title":"Background","text":"<p>This section is non-normative.</p> <p>[FEP-5feb] introduced the <code>indexable</code> property, which allows an Activity Streams actor to signal their preference for whether objects attributed to them should be made publicly available for search.</p> <p>The <code>indexable</code> property is an actor-level property which takes a boolean value, allowing the actor to make all of their objects available for search by either anyone or no one at all. However, sometimes it is desirable to make a certain set of objects searchable/unsearchable or make an object searchable by a limited audience. The object-level <code>searchableBy</code> property documented in this FEP complements the <code>indexable</code> property with a more fine-grained audience targeting ability.</p>"},{"location":"fep/268d/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119].</p>"},{"location":"fep/268d/#terms-defined","title":"Terms defined","text":"searchableBy URI <code>http://fedibird.com/ns#searchableBy</code> Label searchable by Comment Identifies one or more entities that are allowed to search for the subject. Domain <code>Object</code> Range <code>Object</code> | <code>Link</code> Required No Is defined by FEP-268d A non-normative example of a publicly searchable post: <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/1\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://example.com/users/1/followers\",\n \"type\": \"Note\",\n \"content\": \"Hello, world!\",\n \"searchableBy\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n</code></pre> A post only searchable by the mentioned users and the author's followers: <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/42\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://alice.example/actor\",\n \"cc\": [\"https://example.com/users/1/followers\", \"https://www.w3.org/ns/activitystreams#Public\"],\n \"type\": \"Note\",\n \"content\": \"@Alice Happy birthday!\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://alice.example/actor\",\n \"name\": \"@Alice\"\n },\n \"searchableBy\": [\"https://alice.example/actor\", \"https://example.com/users/1/followers\"]\n}\n</code></pre> (where `https://example.com/users/1/followers` is the `followers` collection of `https://example.com/users/1`.) A post only searchable by the author themselves: <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/123\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://example.com/users/1/followers\",\n \"cc\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"type\": \"Note\",\n \"summary\": \"Note to self\",\n \"searchableBy\": \"https://example.com/users/1\"\n}\n</code></pre> An actor with a default searchability of `as:Public`: <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/users/1\",\n \"type\": \"Person\",\n \"searchableBy\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n</code></pre> ## Searchability of objects This section describes how the searchability of an object for a given actor should be determined. If the object has at least one `searchableBy` property value and the searching actor isn't included in the property values, the object MUST NOT be made available for search by that actor. Even in this case, the object SHOULD be made available for search if the object is attributed to the searching actor themselves, and MAY be made available for search if the searching actor has previously interacted with the object (e.g. by `Like`-ing the object). If the `searchableBy` property values of the object include the searching actor, the object SHOULD be made available for search by the actor, unless the searching actor is otherwise forbidden to see the object by access controls such as the Activity Streams audience targeting (`to`, `bto`, `cc`, `bcc` and `audience` properties) of the object and `Block`s by the object's attributed actors. Objects with a `searchableBy` value of `https://www.w3.org/ns/activitystreams#Public` SHOULD be made available for search by any users. If the object does not have any `searchableBy` values but is attributed to an actor with at least one `searchableBy` value, the object SHALL be treated as inheriting the `searchableBy` value of the attributed actor. Note that, in JSON-LD, an empty array value (`[]`) is semantically equivalent to `null` or \"undefined\". In consequence, if a publisher wishes to make an object searchable by no one (instead of falling back on the attributed actor's `searchableBy` value), a placeholder object needs to be used as the `searchableBy` value of that object. The RECOMMENDED placeholder in that case is the object's `attributedTo` value, if any. If the object does not fall under any of the conditions described in this section, the searchability of the object MAY be determined by an implementation-defined strategy. Note that the searchability signaled by the `searchableBy` property is only applicable to Activity Streams representation of objects. Searchability of non-Activity Streams representations (most notably, HTML) of objects with non-public `searchableBy` property value SHOULD be signaled by other means. Non-normative examples of such means include the Robots Exclusion Protocol [RFC-9309] [ROBOTSTXT], the `` HTML tag [ROBOTSTXT-META] and the `X-Robots-Tag` HTTP header [GOOGLE-ROBOTS]. ## Interaction with FEP-5feb (`toot:indexable`) *This section is only applicable to conformant [FEP-5feb] implementations.* If an object does not have any `searchableBy` values and is attributed to an actor with no `searchableBy` values but with an `http://joinmastodon.org/ns#indexable` value, the searchability of the object SHOULD be determined according to [FEP-5feb]. If an object has at least one `searchableBy` value, the property SHALL take precedence over the `indexable` property. In particular, if an object is not searchable according to the `searchableBy` property, the object must not be made available for search, even if the object is targeted to the `as:Public` collection and the object is attributed to an actor with `\"indexable\": true`. ## Security considerations *This section is non-normative.* As mentioned in the normative section, consuming implementations should take care to ignore JSON entries with an empty array value (`\"searchableBy\": []`). Failure to do so would have a security implication if the implementation is verifying RDF-based signatures of objects, because the empty array value does not affect the RDF dataset (and hence the resulting signature value), so that an attacker can remove/insert a `\"searchableBy\": []` entry from/to a signed object without making the signature verification fail, allowing them to tamper with the object's searchability if the empty array value were handled differently from \"undefined\". ## Implementations *This section is non-normative.* - [Fedibird](https://github.com/fedibird/mastodon/tree/fedibird) - [kmyblue](https://github.com/kmycode/mastodon) ## References - [Activity Streams 2.0] James M Snell, Evan Prodromou, [Activity Streams 2.0](https://www.w3.org/TR/activitystreams-core/), 2017 - [FEP-5feb] Claire, [FEP-5feb: Search indexing consent for actors](https://w3id.org/fep/5feb), 2023 - [RFC-2119] S. Bradner, [Key words for use in RFCs to Indicate Requirement Levels](https://doi.org/10.17487/RFC2119), 1997 - [RFC-9309] M. Koster, [Robots Exclusion Protocol](https://doi.org/10.17487/RFC9309), 2022 - [ROBOTSTXT] Martijn Koster, [A Standard for Robot Exclusion](https://www.robotstxt.org/orig.html), 1994 - [ROBOTSTXT-META] Martijn Koster, [About the Robots \\ tag](https://www.robotstxt.org/meta.html), 2007 - [GOOGLE-ROBOTS] Google, [Robots Meta Tags Specifications](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) ## Copyright CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work."},{"location":"fep/2931/","title":"Index","text":"<p>hide: - navigation description: \"FEP-7888 attempts to lay out clarifications for the use of the context property based on rationale and history, in which context is used primarily to logically group objects related by their \"context\", or in other words, stating that some object \"was created in relation to\" another object, where the latter object denotes some purpose for the first object. In response to FEP-7888, and motivated by the desire to backfill entire conversations, various softwares wishing to federate have chosen to directly represent context as a Collection of objects acknowledged to be \"within\" some canonical context collection. This FEP describes this approach, its usages, and some drawbacks.\"</p>"},{"location":"fep/2931/#fep-2931-representing-context-with-a-collection","title":"FEP-2931: Representing context with a Collection","text":"Authors Status Type Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> implementation 2025-03-22 #534 Discussions codeberg"},{"location":"fep/2931/#summary","title":"Summary","text":"<p>FEP-7888 attempts to lay out clarifications for the use of the <code>context</code> property based on rationale and history, in which <code>context</code> is used primarily to logically group objects related by their \"context\", or in other words, stating that some object \"was created in relation to\" another object, where the latter object denotes some purpose for the first object. </p> <p>In response to FEP-7888, and motivated by the desire to backfill entire conversations, various softwares wishing to federate have chosen to directly represent <code>context</code> as a Collection of objects acknowledged to be \"within\" some canonical context collection. This FEP describes this approach, its usages, and some drawbacks.</p>"},{"location":"fep/2931/#the-concept-of-a-canonical-context-collection","title":"The concept of a canonical context collection","text":"<p>FEP-7888 describes the concept of \"Canonical collections of objects within an authoritative context\" as a case where, if a <code>context</code> dereferences to some object, then the <code>type</code> of that resolved object can indicate a certain relation as representing the \"canonical collection\". In the case of this FEP, the \"canonical collection\" is the Collection itself. This gives us the following normative description:</p> <ul> <li>When a <code>context</code> property is dereferenceable to an object that is a <code>Collection</code> or <code>OrderedCollection</code>, the canonical context collection is <code>id</code>.</li> </ul>"},{"location":"fep/2931/#example-representation-of-a-context-collection","title":"Example representation of a context Collection","text":"<p>(This section is non-normative.)</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://posts.example/1\",\n \"type\": \"Note\",\n \"context\": {\n \"id\": \"https://conversation.example\",\n \"type\": \"OrderedCollection\",\n \"items\": [\n \"https://posts.example/1\",\n \"https://posts.example/2\",\n \"https://posts.example/5\"\n ]\n }\n}\n</code></pre>"},{"location":"fep/2931/#consuming-a-context-collection","title":"Consuming a context Collection","text":"<p>(This section is non-normative.)</p> <p>The context represented by a Collection can be processed (for example, for backfill purposes) by iteratively processing its <code>items</code> (or <code>orderedItems</code> if expressed using that JSON-LD term) and considering them to be logically grouped together.</p>"},{"location":"fep/2931/#producing-and-maintaining-a-context-collection","title":"Producing and maintaining a context Collection","text":"<p>(This section is non-normative.)</p> <p>The protocol for becoming aware of or discovering objects claiming to be included within your context are out of scope for this FEP. However, if you encounter an object claiming your context, then it can be Added directly to the Collection.</p>"},{"location":"fep/2931/#drawbacks","title":"Drawbacks","text":"<p>(This section is non-normative.)</p>"},{"location":"fep/2931/#collections-cannot-be-cleanly-followed-via-c2s-outbox-delivery","title":"Collections cannot be cleanly followed via C2S outbox delivery","text":"<p>Using a Collection type forcibly opts that object into certain spec behaviors defined by ActivityPub. Namely, if you are using a context collection, and you wish to make your Collection followable, the naive approach is to make it into an ActivityPub actor by giving it an <code>inbox</code> that can at least respond to Follow activities with an Accept Follow. However, there is an issue with the outbox delivery algorithm defined in the ActivityPub client-to-server API, wherein the inbox discovery algorithm necessarily always attempts to iterate over every single item in the Collection, not just the Collection itself. This behavior is triggered whenever an ActivityPub outbox receives a payload with an addressing property such as <code>to</code>, <code>cc</code>, <code>bto</code>, <code>bcc</code>, or <code>audience</code>. For a context Collection that is also an AP actor, addressing this Collection can be unintentionally costly to the server performing delivery, as this server is expected to attempt iteration over potentially unbounded numerous items from the Collection.</p> <p>For example, given the following activity:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://activity.example\",\n \"type\": \"Announce\",\n \"object\": \"https://posts.example/7\",\n \"to\": \"https://conversation.example\"\n}\n</code></pre> <p>The intention might be to only POST to the <code>inbox</code> of <code>https://conversation.example</code> (and thus notify the context Collection of <code>https://posts.example/7</code> existing), but the outbox delivery algorithm as currently defined at the time of writing this FEP will instead attempt delivery to all of the following <code>inbox</code> properties it can find across the following objects:</p> <ul> <li><code>https://conversation.example</code></li> <li><code>https://posts.example/1</code></li> <li><code>https://posts.example/2</code></li> <li><code>https://posts.example/5</code></li> </ul> <p>There is no way to opt out of this \"Collection expansion\" behavior. Consequently, following a context Collection cannot be done via naive ActivityPub. See FEP-efda: Followable objects for a potential workaround, where the <code>Follow</code> activity is addressed to <code>attributedTo</code> and sent to <code>attributedTo.inbox</code> instead of being addressed directly to <code>id</code> and delivered to <code>id.inbox</code>.</p> <p>This problem does not occur if the ActivityPub \"POST to outbox\" is not used, and instead the <code>Follow</code> activity is delivered directly with Linked Data Notifications \"POST to inbox\".</p>"},{"location":"fep/2931/#semantic-confusion-between-objects-and-collections-of-objects","title":"Semantic confusion between objects and collections of objects","text":"<p>When modeling explicit concepts such as a \"conversation\", there is a difference between saying that \"a conversation is a collection of posts\", versus saying that \"a conversation has a collection of posts\". The former relation (\"is a\") may be imprecise or may prevent declaring other relations in a way that makes sense. The latter relation (\"has a\") allows for declaring properties of the Conversation that are not properties of the collection of posts instead.</p>"},{"location":"fep/2931/#implementations","title":"Implementations","text":"<ul> <li>NodeBB</li> <li>Discourse</li> <li>Wordpress</li> <li>Frequency</li> </ul>"},{"location":"fep/2931/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>a, FEP-7888: Demystifying the context property, 2023</li> <li>trwnh, w3c/activitypub#486: Delivering to Collections can be ambiguous, 2025</li> </ul>"},{"location":"fep/2931/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/2c59/","title":"FEP-2c59: Discovery of a Webfinger address from an ActivityPub actor","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.name <code>DRAFT</code> 2024-01-04 #226 - codeberg"},{"location":"fep/2c59/#summary","title":"Summary","text":"<p>[Webfinger][Webfinger] is used on the fediverse to abstract out variations in [ActivityPub][ActivityPub] actor URL formats, giving a uniform way of addressing an actor. With a Webfinger address, a client can discover the actor's ActivityPub actor URL. This specification defines an explicit way to reverse the process, and discover a preferred Webfinger address from an ActivityPub actor URL.</p>"},{"location":"fep/2c59/#motivation","title":"Motivation","text":"<p>[ActivityPub][ActivityPub] is a protocol for federated social networking. It uses HTTPS URLs to identify actors, that is, the people, organizations, applications and groups that use the protocol.</p> <p>The URL format is not standardized, and there are many variations. For example, a user with the username <code>evan</code> on the <code>activitypub.example</code> domain could have an actor URL of:</p> <ul> <li>A path on the domain, like <code>https://activitypub.example/users/evan</code></li> <li>Including an ID number, like <code>https://activitypub.example/users/1234</code></li> <li>A specific subdomain for social data, like <code>https://social.activitypub.example/evan</code></li> <li>One subdomain per user, like <code>https://evan.activitypub.example</code></li> </ul> <p>[Webfinger][Webfinger] is a discovery protocol for people on the Internet. A Webfinger address is an email-address-like identity in the form <code>user@activitypub.example</code>. Following the Webfinger protocol, a client can discover information about the person at that address, including their ActivityPub actor URL.</p> <p>This discovery is unidirectional and can cross domain boundaries. This allows potentially misleading or harmful use of WebFinger to identify actors on the network without their consent. For example, the owner of <code>idiot.example</code> could create a Webfinger address <code>pompous@idiot.example</code> that points to <code>https://activitypub.example/users/evan</code>.</p> <p>In addition, ActivityPub servers that display actors' Webfinger addresses in their UIs need a reliable way to know which Webfinger is preferred for a given actor ID.</p> <p>To solve this problem, [Mastodon][Mastodon] constructs a canonical Webfinger address for an actor by extracting the <code>preferredUsername</code> from the actor's profile, and prepending it to the domain name of the actor's identity URL. It then does a Webfinger lookup on that address, and if the actor URL returned matches the identity URL, it uses that Webfinger address.</p> <p>This technique can cause issues for certain actor URLs. First, <code>preferredUsername</code> is not a required property for an ActivityPub actor.</p> <p>Second, it becomes inconvenient to use subdomains or alternate domains for actor URLs. If the original WebFinger is <code>evan@organization.example</code>, and the ActivityPub URL is <code>https://social.organization.example/evan</code>, the Webfinger will be canonicalized to the longer <code>evan@social.organization.example</code>. A similar problem arises if the actor URL uses one subdomain per user, like <code>https://evan.organization.example/</code>.</p> <p>This specification defines a way to discover a Webfinger address from an ActivityPub actor without constructing it from properties and URL parsing. It gives domain owners the option to use a registered domain for their Webfinger addresses, and host their ActivityPub server on a subdomain or a different domain. The Mastodon method can be used as a fallback.</p>"},{"location":"fep/2c59/#specification","title":"Specification","text":"<ul> <li>An ActivityPub actor SHOULD include a <code>webfinger</code> property, which is a canonical Webfinger address for the actor.</li> <li>The <code>webfinger</code> property is functional, that is, there can be at most one <code>webfinger</code> property per actor.</li> <li>If provided, the JRD link data for the Webfinger address in the <code>webfinger</code> property MUST link directly to the actor URL verbatim, without redirects or aliases.</li> <li>The identity in the <code>webfinger</code> property SHOULD be a plain Webfinger address, like <code>user@domain.example</code>.</li> <li>The identity in the <code>webfinger</code> property MAY be an <code>acct:</code> URL, like <code>acct:user@domain.example</code>.</li> </ul>"},{"location":"fep/2c59/#context","title":"Context","text":"<p>The context document for this specification is <code>https://purl.archive.org/socialweb/webfinger</code>. Its contents are as follows:</p> <pre><code>{\n \"@context\": {\n \"wf\": \"https://purl.archive.org/socialweb/webfinger#\",\n \"xsd\": \"http://www.w3.org/2001/XMLSchema#\",\n \"webfinger\": {\n \"@id\": \"wf:webfinger\",\n \"@type\": \"xsd:string\"\n }\n }\n}\n</code></pre>"},{"location":"fep/2c59/#examples","title":"Examples","text":"<p>A publisher can include the <code>webfinger</code> property for an actor.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://social.example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://social.example.com/evanp/inbox\",\n \"outbox\": \"https://social.example.com/evanp/outbox\",\n \"following\": \"https://social.example.com/evanp/following\",\n \"followers\": \"https://social.example.com/evanp/followers\",\n \"liked\": \"https://social.example.com/evanp/liked\",\n \"webfinger\": \"evanp@example.com\"\n}\n</code></pre> <p>A publisher can include the <code>webfinger</code> property for a <code>Person</code> object referenced in the <code>actor</code> property of an activity.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://social.example.com/evanp/activity/775\",\n \"to\": [\"Public\"],\n \"type\": \"Like\",\n \"summary\": \"Evan liked a note\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"id\": \"https://social.example.com/evanp\",\n \"webfinger\": \"evanp@example.com\"\n },\n \"object\": {\n \"type\": \"Note\",\n \"id\": \"https://social.example.com/other/note/221\",\n \"to\": [\"Public\"]\n }\n}\n</code></pre> <p>The publisher can include the <code>webfinger</code> property for an actor with an <code>acct:</code> prefix.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://prefix.example/urlfan\",\n \"type\": \"Person\",\n \"name\": \"URL Fan\",\n \"inbox\": \"https://prefix.example/urlfan/inbox\",\n \"outbox\": \"https://prefix.example/urlfan/outbox\",\n \"following\": \"https://prefix.example/urlfan/following\",\n \"followers\": \"https://prefix.example/urlfan/followers\",\n \"liked\": \"https://prefix.example/urlfan/liked\",\n \"webfinger\": \"acct:urlfan@prefix.example\"\n}\n</code></pre>"},{"location":"fep/2c59/#security-considerations","title":"Security Considerations","text":"<p>The source of the <code>webfinger</code> property is important. A third party may spoof, intentionally or unintentionally, the <code>webfinger</code> property of another actor. The property should only be treated as the preferred Webfinger address for the actor if it comes from the actor <code>id</code> URL, or if it is delivered with the actor's [HTTP Signature][HTTP Signature] via the ActivityPub protocol.</p>"},{"location":"fep/2c59/#references","title":"References","text":"<ul> <li>[Webfinger] Brad Fitzpatrick, WebFinger, 2013</li> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[Mastodon] Eugen Rochko, Mastodon, 2016</li> <li>[Mastodon Webfinger], Mastodon contributors, Webfinger - Mastodon documentation, 2018</li> <li>[HTTP Signature], Cavage, M., HTTP Signatures, 2017</li> </ul>"},{"location":"fep/2c59/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/2e40/","title":"FEP-2e40: The FEP Vocabulary Extension Process","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Helge Krueger helge.krueger@gmail.com <code>WITHDRAWN</code> 2023-02-13 2023-09-04 #62 - codeberg"},{"location":"fep/2e40/#summary","title":"Summary","text":"<p>Current usage of ActivityPub relies on the ActivityStreams namespace [AS-NS] combined with custom extensions [Mastodon NS]. As far as I can tell, no best practices exist or a formal process to add new namespaces.</p> <p>This FEP will</p> <ul> <li>Create a FEP Vocabulary based on identified best practices</li> <li>Define a process to add new entries to this FEP Vocabulary without a risk of Term collision</li> <li>Define a process to elevate Terms to be common</li> <li>Define a process to create specialized Vocabularies</li> <li>Using [FEP-61CE] as an example how this process can be used.</li> </ul> <p>Note: Withdrawn as no longer compatible with the FEP repository structure, see this issue.</p>"},{"location":"fep/2e40/#background-and-terminology","title":"Background and Terminology","text":"<p>The JSON-LD context is introduced in 3.1 The Context of [JSON-LD]. The context of an object is specified by its <code>@context</code> property.</p> <p>One can think of the context as defining certain strings to be equivalent. For example <code>Note</code>, <code>as:Note</code>, and <code>https://www.w3.org/ns/activitystreams#Note</code> all represent the same thing. More details can be found in 3.2. IRIs. Following [JSON-LD], we will refer to all three strings mentioned above as a Term. The focus here is on having a format that is machine-readable. Having a standardized format allows various algorithms to be applicable see [Canon] and [LD-API].</p> <p>The second useful aspect of this is that one can define the used terms through the provided URL: https://www.w3.org/ns/activitystreams#Note. Clicking on it will let you easily find the definiton of the Note Type. This format is human-readable.</p> <p>We will refer to the combination of Context and easily accessible documentations for the terms a Vocabulary.</p>"},{"location":"fep/2e40/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].</p>"},{"location":"fep/2e40/#the-fep-vocabulary","title":"The FEP Vocabulary","text":"<p>We now formalize the process of creating an adding to the FEP Vocabulary. This section formalizes the location of the two files containing the machine readable json-ld context and human readable markdown description. How to add new terms will be discussed in the next section.</p> <p>This FEP creates the file <code>/feps/assets/fep-2e40/namespace.json</code> with content</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"fep\": \"https://w3id.org/fep#\"\n }\n ]\n}\n</code></pre> <p>A description of this context is contained in <code>/feps/assets/fep-2e40/namespace.md</code>. The goal of inclusion of the ActivityStreams context is to increase the chances of most objects having the simple form</p> <pre><code>{\n \"@context\": \"https://w3id.org/fep\",\n ...\n}\n</code></pre> <p>which is as close as one can get to pure JSON with JSON-LD. These two files form the FEP Vocabulary. Through a pull-request to [W3-ID], these two files will be made available at <code>https://w3id.org/fep</code>.</p>"},{"location":"fep/2e40/#process-to-add-a-new-term-to-the-fep-vocabulary","title":"Process to add a new Term to the FEP Vocabulary","text":"<p>The authors of a new FEP-xxxx following [FEP-a3ed] MAY include a section or sections titled \"Create FEP Term: $TERM\". The $TERM MUST follow the following rules inspired by points 2-5 of 3. The Registration Process in [DID-Reg].</p> <ol> <li>The $TERM MUST be indicative of its function. Avoid generic terms such as \"myProperty\" or \"foo\".</li> <li>If there are copyright, trademark, or any intellectual property rights concerns, the addition and use MUST be authorized in writing by the intellectual property rights holder under a F/RAND license. Examples include terms that use trademarked brand names, property names that utilize the titles of copyrighted works, and patented technology that would cause the use of the extension to require licensing a patent.</li> <li>Any addition MUST NOT create unreasonable legal, security, moral, or privacy issues that will result in direct harm to others. Examples of unacceptable additions include any containing racist language, technologies used to persecute minority populations, and unconsented pervasive tracking.</li> </ol> <p>Furthermore, the author MAY perform the following action</p> <ol> <li>Add a new term of the form <code>fep-xxxx-$TERM</code> to <code>/feps/assets/fep-2e40/namespace.json</code>.</li> <li>Add a description for this term to <code>/feps/assets/fep-2e40/namespace.md</code>.</li> </ol> <p>The changes to the two files MUST be described in the FEP, and they MUST only add new content or change content added by the FEP. An example is provided in the next section. While these actions are optional at the DRAFT stage of a FEP, they MUST be performed before a FEP can reach its FINAL stage. We will refer to a term introduced by this process as a FEP term.</p> <p>Instead of having many sections with similar names, the author of a FEP MAY combine them.</p> <p>Instead of defining a new FEP term, the author of a FEP MAY decide to include terms from another namespace as a FEP Term. For example, we decide to introduce <code>conversation</code> as follows</p> <pre><code> \"fep-xxxx-conversation\": {\n \"@id\": \"http://ostatus.org#conversation\",\n \"@type\": \"@id\"\n },\n</code></pre> <p>This has the advantage that terms, which are currently used but not properly documented, can be given a proper definition. Furthermore, this will help keep the <code>@context</code> block small. When doing so, the author MUST either provide a reference to the current definition of the object OR write a new definition of the object. If a reference is provided, it should be considered best pratice to quote the reference.</p>"},{"location":"fep/2e40/#example-create-fep-term-eventsource","title":"Example \"Create FEP term: eventSource\"","text":"<p>In [FEP-61ce], I plan to introduce the term <code>eventSource</code>. As this will most likely be the first FEP using the herein defined procces, this will lead to <code>/feps/assets/fep-2e40/namespace.json</code> being changed to</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"fep\": \"https://www.w3id.org/fep#\",\n \"fep-61ce-eventSource\": {\n \"@id\": \"fep:fep-61ce-eventSource\",\n \"@type\": \"@id\"\n }\n }\n ]\n}\n</code></pre> <p>with usage information</p> <p>The term <code>eventSource</code> is to be as part of the <code>endpoints</code> of an [ActivityPub] Actor. It specifies an endpoint, where the Client can receive push notifications using the Server Side Events protocol of activities being added to collections on the server. By default the inbox collection of the Actor is used. By specifying the <code>X-ActivityPub-Collection</code> header a different collection can be specified to retrieve push notifications from.</p> <p>Example usage:</p> <p><pre><code>{\n \"@context\": [\"https://www.w3id.org/fep\",\n {\"eventSource\": \"fep-61ce-eventSource\"}\n ],\n \"type\": \"Person\",\n \"id\": \"https://example.com/client_actor\",\n \"inbox\": \"https://example.com/client_actor/inbox\",\n \"outbox\": \"https://example.com/client_actor/outbox\",\n \"preferredUsername\": \"actor\",\n \"endpoints\": {\n \"proxyUrl\": \"https://example.com/client_actor/proxyUrl\",\n \"eventSource\": \"https://example.com/client_actor/eventSource\"\n }\n}\n</code></pre> </p>"},{"location":"fep/2e40/#promotion-to-common-terms","title":"Promotion to common terms","text":"<p>While the above example is already nice looking, it still has the uglyness of needing to define <code>eventSource</code> as <code>fep-61ce/eventSource</code>. We will call the process of adding the line</p> <pre><code>\"eventSource\": \"fep-61ce-eventSource\"\n</code></pre> <p>to <code>/feps/assets/fep-2e40/namespace.json</code> a promotion to common term.</p> <p>In order to be elligible for promotion, a term MUST NOT conflict with any term currently in the vocabulary. For example, if the term <code>visualization</code> was already introduced in a previous FEP, it is not acceptable to introduce <code>visualisation</code> and expect promotion of this term.</p> <p>Any term elligible for promotion CAN be promoted to a common term by the author of the FEP after the FEP has been declared FINAL. If an author does not seek to promotion of a term, it SHOULD be noted in the section introducing this term.</p>"},{"location":"fep/2e40/#secondary-fep-vocabularies","title":"Secondary FEP Vocabularies","text":"<p>We have now introduced a process for the common or primary vocabulary with context in <code>/feps/assets/fep-2e40/namespace.json</code> and documentation in <code>/feps/assets/fep-2e40/namespace.json</code>. We will now introduce secondary vocabularies. These are meant to be an option to introduce many domain specific terms without having to add them to the primary vocabulary.</p> <p>An author of a FEP MAY include a section titled \"Introducing Secondary FEP Vocabulary fep-DOMAIN\". Here DOMAIN is a short name representing the domain. Then files <code>/feps/fep-xxxx/namespace.json</code> and <code>/feps/fep-xxxx/namespace.md</code> can be created. An example of a file of the <code>@context</code> file is given below. By including the original <code>fep</code> context, one can keep duplication to a minimum. Similarly, the documentation file should only explain the namespace and which additional terms are promoted or overridden. For the documentation of the fep terms, one should refer back to the original documentation.</p> <p>Furthermore, the author MAY adjust the configuration of [W3-ID] to reflect his changes. All obligations that are MAY during the draft stage will turn into MUST before the FEP can be finalized.</p>"},{"location":"fep/2e40/#example-the-barber-vocabulary","title":"Example: The barber vocabulary","text":"<p>The barber community in the FediVerse has decided that, they need a marker if people shave themself. So they have introduce FEP-ba1b and defined the term <code>shavesHimself</code>. It was decided not to promote this term to the primary vocabulary. Instead the barber community has decided to introduce FEP-ba2b, in which they introduce the secondary FEP vocabulary <code>fep-barber</code>. The context of this vocabulary then takes the form:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3id.org/fep\",\n {\n \"shavesHimself\": \"fep-ba1b-shavesHimself\"\n }\n ]\n}\n</code></pre> <p>By including the primary FEP vocabulary, it is ensured that changes to the primary vocabulary carry over to the secondary <code>fep-barber</code> vocabulary. Furthermore, by the promotion of <code>shavesHimself</code> taking place after the inclusion of the primary vocabulary, it is ensure that the primary vocabulary cannot override it.</p>"},{"location":"fep/2e40/#acknowledgements","title":"Acknowledgements","text":"<p>The comments by aschrijver and trwnh on SocialHub have helped me greatly in clarifying and formulating the concepts discussed in this FEP.</p>"},{"location":"fep/2e40/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[AS-NS] Amy Guy ActivityStreams 2.0 Terms, 2018</li> <li>[AS-Git] M. Sporny Add alsoKnownAs property to AS vocabulary #511, 2020</li> <li>[Canon] A. Rundgren, B. Jordan, S. Erdtman RFC 8785 JSON Canonicalization Scheme (JCS), 2020</li> <li>[DID Reg] Orie Steele, Manu Sporny DID Specification Registries, 2023</li> <li>[FEP-a4ed] pukkamustard FEP-a4ed: The Fediverse Enhancement Proposal Process</li> <li>[FEP-61ce] Helge Krueger [FEP-61CE: Server Sent Events for Actor], in preparation</li> <li>[JSON-LD] Gregg Kellogg, Pierre-Antoine Champin, Dave Longley JSON-LD, 2020</li> <li>[LD-API] Gregg Kellogg, Pierre-Antoine Champin, Dave Longley JSON-LD-API, 2020</li> <li>[Mastodon NS] Eugen Rochko et al JSON-LD Namespacing</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> <li>[W3-ID] Contibutors Permanent Identifiers for the Web</li> </ul>"},{"location":"fep/2e40/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/2e40/namespace/","title":"The FEP Vocabulary","text":"<p>This file contains the description of Terms of the FEP Vocabulary. The FEP Vocabulary is defined according to [FEP-2e40]. The FEP Vocabulary is an extension of Activity Streams 2.0, which is described in [AS-NS].</p>"},{"location":"fep/2e40/namespace/#promoted-terms","title":"Promoted terms","text":"<p>The following terms have been elevated to be usable without their fep prefix.</p>"},{"location":"fep/2e40/namespace/#definitions-of-terms","title":"Definitions of Terms","text":"<p>This section contains the definition of terms separated by FEP.</p>"},{"location":"fep/2e40/namespace/#references","title":"References","text":"<ul> <li>[AS-NS] Amy Guy ActivityStreams 2.0 Terms, 2018</li> <li>[FEP-2e40] Helge Krueger FEP-2e40: The FEP Vocabulary Extension Process, 2023</li> </ul>"},{"location":"fep/3264/","title":"FEP-3264: Federated Work Coordination","text":"Authors Status Date received Tracking issue Discussions Repository Lynn Foster lynn@mikorizal.org <code>DRAFT</code> 2024-01-31 #255 - codeberg"},{"location":"fep/3264/#summary","title":"Summary","text":"<p>This document describes an implementation of project planning and work coordination based on ActivityPub protocol and Valueflows vocabulary. It includes planning what people want to do, and (optionally) recording what is done.</p>"},{"location":"fep/3264/#history","title":"History","text":"<p>This is a parallel submission to FEP-c5a1 To-do's, which supports much simpler planning. Both planning submissions continue the idea of Valueflows extensions to ActivityPub/ActivityStreams based on use case, started by silverpill with FEP-0837 Federated Marketplace.</p>"},{"location":"fep/3264/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/3264/#planning-a-project-or-some-work","title":"Planning a project or some work","text":"<p>This FEP will support work plans or projects of any size, using a Plan with one or more Processes, each with one or more input and/or output Commitments.</p> <p>Consuming implementations which don't have planning features MAY display plan-related objects similarly to <code>Note</code> objects.</p> <p>In all cases, the objects can also be discussed using <code>Note</code> objects and <code>replyTo</code>.</p> <p>Valueflows defines a <code>Plan</code> as a logical collection of processes that constitute a body of scheduled work with defined deliverable(s).</p> <p>The representation of a plan is a JSON document with the following properties:</p> <ul> <li><code>id</code> (REQUIRED): the plan's unique global identifier.</li> <li><code>type</code> (REQUIRED): the type of the object SHOULD be <code>Plan</code>. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as <code>Note</code>.</li> <li><code>attributedTo</code> (REQUIRED): the actor who published the plan.</li> <li><code>name</code> (REQUIRED): the title of the plan.</li> <li><code>content</code> (OPTIONAL): the description of the plan. The type of content SHOULD be <code>text/html</code>.</li> <li><code>published</code> (RECOMMENDED): the date and time at which the plan was published.</li> <li><code>to</code> (REQUIRED): the audience of the plan.</li> <li><code>planIncludes</code> (REQUIRED): the processes that are part of this plan (see below).</li> </ul> <p>A <code>Process</code> is defined as An activity that changes inputs into outputs, by transforming or transporting economic resource(s). Processes that are part of a plan can be part of a series (graph) of processes that produce the final output(s) at the end; and/or they can be unrelated to each other, and produce separate final output(s) for the plan.</p> <p>The representation of a process is a JSON document with the following properties:</p> <ul> <li><code>id</code> (REQUIRED): the process's unique global identifier.</li> <li><code>type</code> (REQUIRED): the type of the object MUST be <code>Process</code>.</li> <li><code>name</code> (REQUIRED): an informal or formal textual identifier for a process.</li> <li><code>hasBeginning</code> (OPTIONAL): the date and time the process is planned to begin.</li> <li><code>hasEnd</code> (OPTIONAL): the date and time the process is planned to end.</li> <li><code>content</code> (OPTIONAL): the description of what is to be done in the process. The type of content SHOULD be <code>text/html</code>.</li> <li><code>hasInput</code> (OPTIONAL): the input commitment(s) of the process (see below).</li> <li><code>hasOutput</code> (OPTIONAL): the output commitment(s) of the process (see below).</li> </ul> <p>A <code>Commitment</code> is defined as A planned economic flow that has been promised by an agent to another agent.</p> <ul> <li><code>id</code> (REQUIRED): the commitment's unique global identifier.</li> <li><code>type</code> (REQUIRED): the type of the object MUST be <code>Commitment</code>.</li> <li><code>action</code> (REQUIRED): the type of economic transaction. If the commitment is an input, the value of this property SHOULD be one of: <code>work</code>, <code>use</code>, <code>consume</code>, <code>cite</code>, <code>pickup</code>, <code>accept</code>. It the commitment is an output, the value of this property SHOULD be one of: <code>produce</code>, <code>deliverService</code>, <code>dropoff</code>, <code>modify</code>. See the action definitions and behaviors for more information.</li> <li><code>resourceConformsTo</code> (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the <code>work</code> action). Could be any URI.</li> <li><code>resourceQuantity</code> (OPTIONAL): the amount and unit of the non-effort-based economic resource. Either <code>resourceQuantity</code> or <code>effortQuantity</code> is RECOMMENDED, and both can be included in case of <code>use</code>. This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification.</li> <li><code>hasNumericalValue</code> (REQUIRED): amount of the resource.</li> <li><code>effortQuantity</code> (OPTIONAL): the amount and unit of the work or use effort-based action. This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification.</li> <li><code>hasNumericalValue</code> (REQUIRED): amount of the effort.</li> <li><code>provider</code> (RECOMMENDED): the actor who commits to providing the resource. If not included, it is assumed to be the <code>attributedTo</code> actor.</li> <li><code>receiver</code> (RECOMMENDED): the actor who commits to receiving the resource. If not included, it is assumed to be the <code>to</code> actor.</li> <li><code>content</code> (OPTIONAL): the description of what is to be done for this commitment. The type of content SHOULD be <code>text/html</code>.</li> </ul> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Plan\": \"vf:Plan\",\n \"Process\": \"vf:Process\",\n \"Commitment\": \"vf:Commitment\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"planIncludes\": \"vf:planIncludes\",\n \"hasBeginning\": \"vf:hasBeginning\",\n \"hasEnd\": \"vf:hasEnd\",\n \"hasInput\": \"vf:hasInput\",\n \"hasOutput\": \"vf:hasOutput\"\n }\n ],\n \"type\": \"Plan\",\n \"id\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"name\": \"Making Apple Cider\",\n \"content\": \"Annual weekend of community apple cider making\",\n \"published\": \"2024-09-18T19:22:03.918737Z\",\n \"to\": \"https://project.example/actors/gleaning-collective\",\n \"planIncludes\": [\n {\n \"type\": \"Process\",\n \"id\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick\",\n \"name\": \"Pick Apples\",\n \"hasBeginning\": \"2024-10-18T09:00:00.000000Z\",\n \"hasEnd\": \"2024-10-18T18:00:00.000000Z\",\n \"content\": \"Meet at the orchard, bring your own basket and a lunch, and be ready for some outdoor fall work and fun.\",\n \"hasInput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick1\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick2\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/chiam\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick3\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/alice\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ],\n \"hasOutput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick4\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q89\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"500\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ]\n },\n {\n \"type\": \"Process\",\n \"id\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"name\": \"Make Cider\",\n \"hasBeginning\": \"2024-10-19T09:00:00.000000Z\",\n \"hasEnd\": \"2024-10-19T16:00:00.000000Z\",\n \"content\": \"Meet at the community building, be prepared to get messy. We will press the apples, then clean the press.\",\n \"hasInput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider1\",\n \"action\": \"consume\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q89\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"500\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"content\": \"The apples picked the day before.\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider2\",\n \"action\": \"use\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q674484\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"7\"\n },\n \"provider\": \"https://project.example/actors/diego\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"content\": \"Diego will drop his cider press off early in the morning, and pick it up later, preferably nice and clean.\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider3\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"4\"\n },\n \"provider\": \"https://project.example/actors/chiam\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ],\n \"hasOutput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider5\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q5977438\",\n \"resourceQuantity\": {\n \"hasUnit\": \"gallon-US\",\n \"hasNumericalValue\": \"15\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/food-pantry\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider6\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q2836947\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"100\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/singing-trees-farm\",\n \"content\": \"The apple remains will be great pig food, leave it in the big bin, and they will pick it up.\"\n }\n ]\n }\n ]\n}\n</code></pre>"},{"location":"fep/3264/#accepting-a-commitment","title":"Accepting a Commitment","text":"<p>The <code>object</code> of <code>Accept</code> activity MUST be the <code>id</code> of the <code>Commitment</code> in the <code>Plan</code> object previously sent to the actor.</p> <p>Activity MAY contain <code>content</code> property for further coordination.</p> <p>Example:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Accept\",\n \"id\": \"https://project.example/activities/059f08fa-31b1-4136-8d76-5987d705a0ac\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"content\": \"I'll come early to help unload the cider press.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n</code></pre>"},{"location":"fep/3264/#rejecting-a-commitment","title":"Rejecting a Commitment","text":"<p>The <code>object</code> of <code>Reject</code> activity MUST be the <code>id</code> of the <code>Commitment</code> in the <code>Plan</code> object previously sent to the actor.</p> <p>Activity MAY contain <code>content</code> property indicating the reason for rejection.</p> <p>Example:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://project.example/activities/8c05f97f-1531-4b70-9ca8-4ee4a09f36a4\",\n \"actor\": \"https://project.example/actors/chiam\",\n \"object\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider3\",\n \"content\": \"Sorry, already working elsewhere that day, will check with Eleanore.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n</code></pre>"},{"location":"fep/3264/#adding-a-commitment-to-the-plan","title":"Adding a Commitment to the Plan","text":"<p>In some cases, an actor might want to add a new commitment to the plan.</p> <p>The representation of a commitment is a JSON document with the following properties:</p> <ul> <li><code>id</code> (REQUIRED): the commitment's unique global identifier.</li> <li><code>type</code> (REQUIRED): the type of the object SHOULD be <code>Commitment</code>. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as <code>Note</code>.</li> <li><code>attributedTo</code> (REQUIRED): the actor who published the commitment.</li> <li><code>content</code> (OPTIONAL): the description of the commitment or communication about the commitment. The type of content SHOULD be <code>text/html</code>.</li> <li><code>published</code> (RECOMMENDED): the date and time at which the commitment was published.</li> <li><code>to</code> (REQUIRED): the audience of the commitment.</li> <li><code>action</code> (REQUIRED): the type of economic transaction. If the commitment is an input, the value of this property SHOULD be one of: <code>work</code>, <code>use</code>, <code>consume</code>, <code>cite</code>, <code>pickup</code>, <code>accept</code>. It the commitment is an output, the value of this property SHOULD be one of: <code>produce</code>, <code>deliverService</code>, <code>dropoff</code>, <code>modify</code>. See the action definitions and behaviors for more information.</li> <li><code>inputOf</code>: the process for which this commitment is an input. Either <code>inputOf</code> or <code>outputOf</code> is REQUIRED.</li> <li><code>outputOf</code>: the process for which this commitment is an output. Either <code>inputOf</code> or <code>outputOf</code> is REQUIRED.</li> <li><code>context</code> (OPTIONAL): the plan the commitment is part of.</li> <li><code>resourceConformsTo</code> (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the <code>work</code> action). Could be any URI.</li> <li><code>resourceQuantity</code>: the amount and unit of the non-effort-based economic resource. Either <code>resourceQuantity</code> or <code>effortQuantity</code> is RECOMMENDED, and both can be included in case of <code>use</code>. This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification.</li> <li><code>hasNumericalValue</code> (REQUIRED): amount of the resource.</li> <li><code>effortQuantity</code>: the amount and unit of the work or use effort-based action. Either <code>resourceQuantity</code> or <code>effortQuantity</code> is RECOMMENDED, and both can be included in case of <code>use</code>. This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification.</li> <li><code>hasNumericalValue</code> (REQUIRED): amount of the resource.</li> <li><code>provider</code> (RECOMMENDED): the actor who commits to providing the resource. If not included, it is assumed to be the <code>attributedTo</code> actor.</li> <li><code>receiver</code> (RECOMMENDED): the actor who commits to receiving the resource. If not included, it is assumed to be the <code>to</code> actor.</li> </ul> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"inputOf\": \"vf:inputOf\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc743/create\",\n \"actor\": \"https://project.example/actors/eleanore\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc743\",\n \"attributedTo\": \"https://project.example/actors/eleanore\",\n \"context\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"published\": \"2024-09-21T14:13:41.843794Z\",\n \"inputOf\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"5\"\n },\n \"provider\": \"https://project.example/actors/eleanore\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n</code></pre>"},{"location":"fep/3264/#recording-what-actually-was-done","title":"Recording what actually was done","text":"<p>Actual economic activity is represented with EconomicEvents in Valueflows. Economic events can be recorded as fulfilling a planned commitment, or by themselves if they were not explicitly planned. In either case, in the scope of this FEP, they are also inputs or outputs of a process.</p> <p>This FEP does not include creating and updating of actual resource inventory, which is also effected through economic events.</p> <p>The representation of an economic event is a JSON document with the following properties:</p> <ul> <li><code>id</code> (REQUIRED): the economic event's unique global identifier.</li> <li><code>type</code> (REQUIRED): the type of the object SHOULD be <code>EconomicEvent</code>. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as <code>Note</code>.</li> <li><code>attributedTo</code> (REQUIRED): the actor who published the economic event.</li> <li><code>content</code> (OPTIONAL): the description of the economic event or communication about the economic event. The type of content SHOULD be <code>text/html</code>.</li> <li><code>published</code> (RECOMMENDED): the date and time at which the economic event was published.</li> <li><code>to</code> (REQUIRED): the audience of the economic event.</li> <li><code>action</code> (REQUIRED): the type of economic transaction. If the economic event is an input, the value of this property SHOULD be one of: <code>work</code>, <code>use</code>, <code>consume</code>, <code>cite</code>, <code>pickup</code>, <code>accept</code>. It the economic event is an output, the value of this property SHOULD be one of: <code>produce</code>, <code>deliverService</code>, <code>dropoff</code>, <code>modify</code>. See the action definitions and behaviors for more information.</li> <li><code>fulfills</code>: the commitment the economic event is completely or partially fulfilling, REQUIRED if there is sucn a commitment, otherwise N/A.</li> <li><code>inputOf</code>: the process for which this economic event is an input. Either <code>inputOf</code> or <code>outputOf</code> is REQUIRED.</li> <li><code>outputOf</code>: the process for which this commitment is an output. Either <code>inputOf</code> or <code>outputOf</code> is REQUIRED.</li> <li><code>context</code> (OPTIONAL): the plan the economic event is part of.</li> <li><code>resourceConformsTo</code> (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the <code>work</code> action). Could be any URI.</li> <li><code>resourceQuantity</code>: the amount and unit of the non-effort-based economic resource. Either <code>resourceQuantity</code> or <code>effortQuantity</code> is RECOMMENDED, and both can be included in case of <code>use</code>. This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification.</li> <li><code>hasNumericalValue</code> (REQUIRED): amount of the resource.</li> <li><code>effortQuantity</code>: the amount and unit of the work or use effort-based action. Either <code>resourceQuantity</code> or <code>effortQuantity</code> is RECOMMENDED, and both can be included in case of <code>use</code>. This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification.</li> <li><code>hasNumericalValue</code> (REQUIRED): amount of the resource.</li> <li><code>provider</code> (RECOMMENDED): the actor who provided the resource. If not included, it is assumed to be the <code>attributedTo</code> actor.</li> <li><code>receiver</code> (RECOMMENDED): the actor who received the resource. If not included, it is assumed to be the <code>to</code> actor.</li> <li><code>finished</code> (OPTIONAL): set to true if this economic event completes the commitment. (Note more than one economic event can be recorded against the same commitment.)</li> </ul> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"EconomicEvent\": \"vf:EconomicEvent\",\n \"inputOf\": \"vf:inputOf\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/create\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": {\n \"type\": \"EconomicEvent\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc932\",\n \"attributedTo\": \"https://project.example/actors/bob\",\n \"context\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"published\": \"2024-10-21T14:16:41.843794Z\",\n \"fulfills\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"inputOf\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6.5\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n</code></pre>"},{"location":"fep/3264/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[Valueflows] Lynn Foster, elf Pavlik, Bob Haugen Valueflows, 2024</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>[Activity Vocabulary] James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>[Ontology of units of Measure] Hajo Rijgersberg, Don Willems, Xin-Ying Ren, Mari Wigham, Jan Top, Ontology of units of Measure, 2017</li> </ul>"},{"location":"fep/3264/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/34c1/","title":"FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary","text":"Authors Status Date received Tracking issue Discussions Repository Fred Hauschel @naturzukunft2026@mastodon.social <code>DRAFT</code> 2026-02-19 #763 Discussions codeberg"},{"location":"fep/34c1/#summary","title":"Summary","text":"<p>This FEP proposes using the TREE Hypermedia Vocabulary for client-initiated filter requests on ActivityPub Collections (especially Inbox). Clients can send a filter as a JSON-LD object via HTTP POST to retrieve a filtered subset of the collection.</p> <p>This enables use cases such as: - Home Timeline: Content lifecycle activities (<code>Create</code>, <code>Update</code>, <code>Delete</code>, <code>Announce</code>) from followed actors, visible to public or followers - Mentions: Activities addressed to the actor via <code>as:to</code> or <code>as:cc</code> - Private Messages: Activities not addressed to <code>as:Public</code> (DMs, followers-only, group messages) - Media Filter: Only activities with images or videos</p>"},{"location":"fep/34c1/#context","title":"Context","text":"<p>The context document for this ActivityPub extension is at <code>https://w3id.org/fep/34c1/context</code>. Its contents are as follows:</p> <pre><code>{\n \"@context\": {\n \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n \"FilterRequest\": \"fep34c1:FilterRequest\",\n \"pageSize\": {\n \"@id\": \"fep34c1:pageSize\",\n \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\"\n },\n \"myFollowees\": { \"@id\": \"fep34c1:myFollowees\", \"@type\": \"@id\" },\n \"myAudience\": { \"@id\": \"fep34c1:myAudience\", \"@type\": \"@id\" }\n }\n}\n</code></pre>"},{"location":"fep/34c1/#defined-terms","title":"Defined Terms","text":""},{"location":"fep/34c1/#fep34c1filterrequest","title":"<code>fep34c1:FilterRequest</code>","text":"URI <code>https://w3id.org/fep/34c1#FilterRequest</code> Notes A container object for a filter query. It holds one or more <code>tree:relation</code> entries that define the filter criteria, and an optional <code>fep34c1:pageSize</code> to control pagination. Domain (none) Range (none)"},{"location":"fep/34c1/#fep34c1pagesize","title":"<code>fep34c1:pageSize</code>","text":"URI <code>https://w3id.org/fep/34c1#pageSize</code> Notes The maximum number of items per page in the filtered response. Domain <code>fep34c1:FilterRequest</code> Range <code>xsd:nonNegativeInteger</code> Functional Yes"},{"location":"fep/34c1/#fep34c1myfollowees","title":"<code>fep34c1:myFollowees</code>","text":"URI <code>https://w3id.org/fep/34c1#myFollowees</code> Notes Server-resolved placeholder. When used as <code>tree:value</code>, the server MUST replace it with the set of actor IRIs from the <code>following</code> collection of the authenticated (requesting) actor."},{"location":"fep/34c1/#fep34c1myaudience","title":"<code>fep34c1:myAudience</code>","text":"URI <code>https://w3id.org/fep/34c1#myAudience</code> Notes Server-resolved placeholder. When used as <code>tree:value</code>, the server MUST resolve it to all collection IRIs in which the authenticated actor is known to be a member. This includes at minimum the <code>followers</code> collections of followed actors, and MAY include group memberships, lists, or other collections as the server discovers them."},{"location":"fep/34c1/#motivation","title":"Motivation","text":""},{"location":"fep/34c1/#problem","title":"Problem","text":"<p>ActivityPub defines Collections (Inbox, Outbox, Followers, etc.) but no mechanism for clients to request filtered views. Clients must:</p> <ol> <li>Fetch all items with pagination</li> <li>Filter and sort locally</li> <li>Transfer unnecessarily large amounts of data</li> </ol> <p>For a \"Home Timeline\" (activities from followees), this is particularly inefficient since the inbox also contains follow requests, likes, and other irrelevant activities.</p>"},{"location":"fep/34c1/#existing-approaches","title":"Existing Approaches","text":"Approach Problem FEP-5bf0 WITHDRAWN (June 2025); focused on server-side views, not client queries Hydra GET-oriented (URL templates), not for POST queries SPARQL Too complex for simple filters, security risks Proprietary Mastodon's <code>/api/v1/timelines/home</code> is not standardized"},{"location":"fep/34c1/#why-tree","title":"Why TREE?","text":"<p>The TREE Hypermedia Vocabulary offers:</p> <ul> <li>Established vocabulary (W3C Community Group, EU Government Adoption)</li> <li>Clear semantics for filter operations (<code>tree:path</code>, <code>tree:value</code>, <code>tree:Relation</code>)</li> <li>SPARQL-compatible comparison operators</li> <li>JSON-LD compatible</li> <li>Extensible for additional relation types</li> </ul>"},{"location":"fep/34c1/#specification","title":"Specification","text":""},{"location":"fep/34c1/#1-filter-endpoint","title":"1. Filter Endpoint","text":"<p>Servers MAY provide a filter endpoint for collections:</p> <pre><code>POST /ap/actors/{username}/inbox/filter\nContent-Type: application/ld+json\n</code></pre> <p>The endpoint MUST be announced in the Collection object via the <code>tree:search</code> property:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/tree\"\n ],\n \"type\": \"OrderedCollection\",\n \"id\": \"https://example.com/ap/actors/alice/inbox\",\n \"tree:search\": {\n \"@type\": \"fep34c1:FilterEndpoint\",\n \"tree:template\": \"https://example.com/ap/actors/alice/inbox/filter\"\n }\n}\n</code></pre>"},{"location":"fep/34c1/#2-filter-request-format","title":"2. Filter Request Format","text":"<p>A filter request is a JSON-LD object of type <code>fep34c1:FilterRequest</code>:</p> <pre><code>{\n \"@context\": {\n \"tree\": \"https://w3id.org/tree#\",\n \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\",\n \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n },\n \"@type\": \"fep34c1:FilterRequest\",\n \"tree:relation\": [\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": { \"@id\": \"rdf:type\" },\n \"tree:value\": [\n { \"@id\": \"as:Create\" },\n { \"@id\": \"as:Update\" },\n { \"@id\": \"as:Delete\" },\n { \"@id\": \"as:Announce\" }\n ]\n },\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": { \"@id\": \"as:actor\" },\n \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n },\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n \"tree:value\": [\n { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" },\n { \"@id\": \"fep34c1:myAudience\" }\n ]\n }\n ],\n \"fep34c1:pageSize\": 20\n}\n</code></pre>"},{"location":"fep/34c1/#3-relation-types","title":"3. Relation Types","text":""},{"location":"fep/34c1/#31-must-be-supported-core","title":"3.1 MUST be Supported (Core)","text":"Relation Semantics SPARQL Equivalent <code>tree:EqualToRelation</code> Value is equal to <code>?x = ?value</code> <code>tree:NotEqualToRelation</code> Value is not equal to <code>?x != ?value</code> <code>tree:GreaterThanRelation</code> Value is greater than <code>?x &gt; ?value</code> <code>tree:LessThanRelation</code> Value is less than <code>?x &lt; ?value</code> <code>tree:GreaterThanOrEqualToRelation</code> Value is greater than or equal to <code>?x &gt;= ?value</code> <code>tree:LessThanOrEqualToRelation</code> Value is less than or equal to <code>?x &lt;= ?value</code>"},{"location":"fep/34c1/#32-should-be-supported-recommended","title":"3.2 SHOULD be Supported (Recommended)","text":"Relation Semantics SPARQL Equivalent <code>tree:PrefixRelation</code> Value starts with <code>STRSTARTS(?x, ?value)</code> <code>tree:SubstringRelation</code> Value contains <code>CONTAINS(?x, ?value)</code> <code>tree:SuffixRelation</code> Value ends with <code>STRENDS(?x, ?value)</code>"},{"location":"fep/34c1/#4-special-values","title":"4. Special Values","text":""},{"location":"fep/34c1/#41-fep34c1myfollowees-dynamic-followee-list","title":"4.1 <code>fep34c1:myFollowees</code> - Dynamic Followee List","text":"<p>The value <code>fep34c1:myFollowees</code> is a server-resolved placeholder. When used as <code>tree:value</code>, the server MUST replace it with the set of actor IRIs from the <code>following</code> collection of the authenticated (requesting) actor.</p> <p>This means a relation like:</p> <pre><code>{\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": { \"@id\": \"as:actor\" },\n \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n}\n</code></pre> <p>is equivalent to: \"Return only activities where the <code>as:actor</code> property matches one of the actor IRIs in the requesting actor's <code>following</code> collection.\" In other words, only activities authored by actors that the requesting user follows will be included in the result.</p>"},{"location":"fep/34c1/#42-fep34c1myaudience-dynamic-audience-collections","title":"4.2 <code>fep34c1:myAudience</code> - Dynamic Audience Collections","text":"<p>The value <code>fep34c1:myAudience</code> is a server-resolved placeholder. When used as <code>tree:value</code>, the server MUST resolve it to all collection IRIs in which the authenticated actor is known to be a member. This includes at minimum the <code>followers</code> collections of followed actors, and MAY include group memberships, lists, or other collections.</p> <p>This is useful for audience filtering: Fediverse servers typically address followers-only posts with the actor's followers collection IRI in <code>as:to</code> or <code>as:cc</code>.</p> <p>Note: These are explicit FEP-34c1 placeholder properties rather than reused ActivityStreams terms. An earlier version of this spec used <code>as:following</code> and <code>as:followers</code> as placeholders, but this was semantically incorrect \u2014 <code>as:followers</code> denotes the collection itself, not its items (see discussion). Using dedicated FEP-34c1 properties makes the server-resolved semantics explicit and avoids overloading AS2 vocabulary. An alternative approach using SHACL Property Paths was considered but rejected due to the complexity of expressing pagination traversal in SHACL path expressions.</p>"},{"location":"fep/34c1/#43-multiple-values-or-conjunction","title":"4.3 Multiple Values (OR Conjunction)","text":"<p>If <code>tree:value</code> is an array, the values are combined with OR:</p> <pre><code>{\n \"tree:value\": [\n { \"@id\": \"as:Create\" },\n { \"@id\": \"as:Update\" },\n { \"@id\": \"as:Delete\" },\n { \"@id\": \"as:Announce\" }\n ]\n}\n</code></pre> <p>Semantics: <code>rdf:type = as:Create OR rdf:type = as:Update OR rdf:type = as:Delete OR rdf:type = as:Announce</code></p>"},{"location":"fep/34c1/#5-multiple-paths-union","title":"5. Multiple Paths (UNION)","text":"<p>If <code>tree:path</code> is an array, the filter matches if any of the specified paths contains a matching value. This is equivalent to a UNION in SPARQL.</p> <pre><code>{\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n \"tree:value\": { \"@id\": \"https://example.com/ap/actors/alice\" }\n}\n</code></pre> <p>Semantics: \"Match if the value appears in <code>as:to</code> OR in <code>as:cc</code>.\"</p> <p>This is particularly useful for audience filtering, since ActivityPub activities use both <code>as:to</code> and <code>as:cc</code> for addressing.</p>"},{"location":"fep/34c1/#6-combination-of-relations-and-conjunction","title":"6. Combination of Relations (AND Conjunction)","text":"<p>Multiple <code>tree:relation</code> entries are combined with AND:</p> <pre><code>{\n \"tree:relation\": [\n { \"@type\": \"tree:EqualToRelation\", \"tree:path\": {\"@id\": \"rdf:type\"}, \"tree:value\": {\"@id\": \"as:Create\"} },\n { \"@type\": \"tree:EqualToRelation\", \"tree:path\": {\"@id\": \"as:actor\"}, \"tree:value\": {\"@id\": \"fep34c1:myFollowees\"} },\n { \"@type\": \"tree:EqualToRelation\", \"tree:path\": [{\"@id\": \"as:to\"}, {\"@id\": \"as:cc\"}], \"tree:value\": {\"@id\": \"https://www.w3.org/ns/activitystreams#Public\"} }\n ]\n}\n</code></pre> <p>Semantics: <code>rdf:type = as:Create AND as:actor IN myFollowees AND (as:to = as:Public OR as:cc = as:Public)</code></p>"},{"location":"fep/34c1/#7-response-format","title":"7. Response Format","text":"<p>The response is an <code>OrderedCollectionPage</code> with the filtered items. Pagination MUST use <code>as:next</code> and <code>as:prev</code> (standard ActivityStreams pagination). Servers MAY additionally include <code>tree:relation</code> entries to provide boundary values for TREE-aware clients.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/tree\",\n { \"fep34c1\": \"https://w3id.org/fep/34c1#\" }\n ],\n \"@type\": \"OrderedCollectionPage\",\n \"partOf\": \"https://example.com/ap/actors/alice/inbox\",\n \"next\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=abc123\",\n \"prev\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=def456\",\n \"orderedItems\": [\n { /* Activity 1 */ },\n { /* Activity 2 */ }\n ],\n \"tree:relation\": [\n {\n \"@type\": \"tree:GreaterThanRelation\",\n \"tree:path\": { \"@id\": \"as:published\" },\n \"tree:value\": \"2026-01-28T10:00:00Z\",\n \"tree:node\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=abc123\"\n }\n ],\n \"totalItems\": 42\n}\n</code></pre>"},{"location":"fep/34c1/#8-pagination","title":"8. Pagination","text":"<p>Servers MUST provide <code>as:next</code> and <code>as:prev</code> links on <code>OrderedCollectionPage</code> responses for standard ActivityStreams pagination. This ensures compatibility with existing ActivityPub clients.</p> <p>Servers MAY additionally include <code>tree:relation</code> entries to provide boundary values for TREE-aware clients. Each pagination relation uses three properties from the TREE vocabulary:</p> <ul> <li><code>tree:value</code> \u2014 the boundary value (e.g., a timestamp) that delimits the current page</li> <li><code>tree:node</code> \u2014 the URL to fetch to retrieve the next page of results beyond that boundary</li> <li><code>tree:path</code> \u2014 the property used for ordering (typically <code>as:published</code>)</li> </ul> <p>For example, a <code>tree:GreaterThanRelation</code> with <code>tree:value</code> of <code>\"2026-01-28T10:00:00Z\"</code> and a <code>tree:node</code> URL means: \"Fetching <code>tree:node</code> will return items with <code>as:published</code> greater than <code>2026-01-28T10:00:00Z</code>.\"</p> <p>Pagination directions:</p> <ul> <li><code>tree:GreaterThanRelation</code> on <code>as:published</code> \u2192 next page contains older items (published after the boundary, in reverse chronological order)</li> <li><code>tree:LessThanRelation</code> on <code>as:published</code> \u2192 next page contains newer items (published before the boundary)</li> </ul>"},{"location":"fep/34c1/#9-authentication","title":"9. Authentication","text":"<p>The filter endpoint MUST require the same authentication as the collection itself:</p> <ul> <li>Inbox: Only the owner (JWT/OAuth2)</li> <li>Outbox: Public or owner-only (server-dependent)</li> </ul>"},{"location":"fep/34c1/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/34c1/#query-injection","title":"Query Injection","text":"<p>Servers MUST validate and sanitize filter requests. In particular:</p> <ul> <li><code>tree:path</code> MUST be restricted to allowed properties</li> <li><code>tree:value</code> MUST be restricted to allowed values/types</li> <li>Complex queries (e.g., deep property paths) MAY be rejected</li> </ul> <p>Recommended allowed <code>tree:path</code> values:</p> <ul> <li><code>rdf:type</code></li> <li><code>as:actor</code></li> <li><code>as:object</code></li> <li><code>as:to</code></li> <li><code>as:cc</code></li> <li><code>as:published</code></li> <li><code>as:tag</code></li> <li><code>as:inReplyTo</code></li> <li><code>as:attributedTo</code></li> </ul>"},{"location":"fep/34c1/#rate-limiting","title":"Rate Limiting","text":"<p>Filter requests are potentially more expensive than regular GET requests. Servers SHOULD implement rate limiting.</p>"},{"location":"fep/34c1/#no-arbitrary-sparql-queries","title":"No Arbitrary SPARQL Queries","text":"<p>This FEP explicitly defines no SPARQL interface. The supported operations are limited to the defined <code>tree:Relation</code> types.</p>"},{"location":"fep/34c1/#examples","title":"Examples","text":""},{"location":"fep/34c1/#example-1-home-timeline","title":"Example 1: Home Timeline","text":"<p>Shows content lifecycle activities from followed actors, visible to public or followers.</p> <pre><code>{\n \"@context\": {\n \"tree\": \"https://w3id.org/tree#\",\n \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\",\n \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n },\n \"@type\": \"fep34c1:FilterRequest\",\n \"tree:relation\": [\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": { \"@id\": \"rdf:type\" },\n \"tree:value\": [\n { \"@id\": \"as:Create\" },\n { \"@id\": \"as:Update\" },\n { \"@id\": \"as:Delete\" },\n { \"@id\": \"as:Announce\" }\n ]\n },\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": { \"@id\": \"as:actor\" },\n \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n },\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n \"tree:value\": [\n { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" },\n { \"@id\": \"fep34c1:myAudience\" }\n ]\n }\n ],\n \"fep34c1:pageSize\": 20\n}\n</code></pre>"},{"location":"fep/34c1/#example-2-mentions","title":"Example 2: Mentions","text":"<p>Shows activities where the actor is directly addressed via <code>as:to</code> or <code>as:cc</code> (public mentions only, excluding DMs).</p> <pre><code>{\n \"@context\": {\n \"tree\": \"https://w3id.org/tree#\",\n \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\",\n \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n },\n \"@type\": \"fep34c1:FilterRequest\",\n \"tree:relation\": [\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": { \"@id\": \"rdf:type\" },\n \"tree:value\": [{ \"@id\": \"as:Create\" }]\n },\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n \"tree:value\": { \"@id\": \"https://example.com/ap/actors/alice\" }\n },\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n }\n ]\n}\n</code></pre>"},{"location":"fep/34c1/#example-3-private-messages","title":"Example 3: Private Messages","text":"<p>Shows activities not addressed to <code>as:Public</code> (neither in <code>as:to</code> nor <code>as:cc</code>). This includes direct messages, followers-only posts, and group messages.</p> <pre><code>{\n \"@context\": {\n \"tree\": \"https://w3id.org/tree#\",\n \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\",\n \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n },\n \"@type\": \"fep34c1:FilterRequest\",\n \"tree:relation\": [\n {\n \"@type\": \"tree:EqualToRelation\",\n \"tree:path\": { \"@id\": \"rdf:type\" },\n \"tree:value\": [{ \"@id\": \"as:Create\" }]\n },\n {\n \"@type\": \"tree:NotEqualToRelation\",\n \"tree:path\": { \"@id\": \"as:to\" },\n \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n },\n {\n \"@type\": \"tree:NotEqualToRelation\",\n \"tree:path\": { \"@id\": \"as:cc\" },\n \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n }\n ]\n}\n</code></pre>"},{"location":"fep/34c1/#example-4-activities-since-timestamp","title":"Example 4: Activities Since Timestamp","text":"<pre><code>{\n \"@context\": {\n \"tree\": \"https://w3id.org/tree#\",\n \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\",\n \"xsd\": \"http://www.w3.org/2001/XMLSchema#\"\n },\n \"@type\": \"fep34c1:FilterRequest\",\n \"tree:relation\": [\n {\n \"@type\": \"tree:GreaterThanRelation\",\n \"tree:path\": { \"@id\": \"as:published\" },\n \"tree:value\": {\n \"@value\": \"2026-01-27T00:00:00Z\",\n \"@type\": \"xsd:dateTime\"\n }\n }\n ]\n}\n</code></pre>"},{"location":"fep/34c1/#example-5-posts-from-a-specific-instance","title":"Example 5: Posts from a Specific Instance","text":"<pre><code>{\n \"@context\": {\n \"tree\": \"https://w3id.org/tree#\",\n \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\"\n },\n \"@type\": \"fep34c1:FilterRequest\",\n \"tree:relation\": [\n {\n \"@type\": \"tree:PrefixRelation\",\n \"tree:path\": { \"@id\": \"as:actor\" },\n \"tree:value\": \"https://mastodon.social/\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/34c1/#implementation-notes","title":"Implementation Notes","text":""},{"location":"fep/34c1/#for-servers","title":"For Servers","text":"<ol> <li> <p>Special placeholder values (<code>fep34c1:myFollowees</code>, <code>fep34c1:myAudience</code>) MUST be resolved at query time before evaluating the filter (see Section 4).</p> </li> <li> <p>Allowed <code>tree:path</code> values SHOULD be restricted to a known set of ActivityStreams properties (see Security Considerations).</p> </li> <li> <p>For multi-path with <code>EqualToRelation</code>, the semantics are UNION (match if any path matches). For <code>NotEqualToRelation</code>, the semantics are ALL (none of the paths may match).</p> </li> </ol>"},{"location":"fep/34c1/#for-clients","title":"For Clients","text":"<ol> <li>Clients SHOULD check if <code>tree:search</code> is present in the collection</li> <li>If not present: fall back to client-side filtering</li> <li>Filter requests SHOULD be cached (ETag/If-None-Match)</li> <li>Clients SHOULD expect <code>405 Method Not Allowed</code> if the server does not support filters</li> </ol>"},{"location":"fep/34c1/#backwards-compatibility","title":"Backwards Compatibility","text":"<p>This FEP is fully backwards compatible:</p> <ul> <li>Servers without filter support return <code>405 Method Not Allowed</code> on POST requests</li> <li>Clients can fall back to client-side filtering</li> <li>Existing GET semantics for collections remain unchanged</li> <li>The <code>tree:search</code> property signals filter support</li> </ul>"},{"location":"fep/34c1/#implementations","title":"Implementations","text":"<ul> <li>ChangingGraph is a federated social platform built with Spring Boot and RDF4J. It implements collection filtering on the inbox using TREE relations, translating filter requests to SPARQL queries. It uses multi-path filtering for audience-based views (Timeline, Mentions, Private Messages).</li> </ul>"},{"location":"fep/34c1/#references","title":"References","text":"<ul> <li>Pieter Colpaert et al., TREE Hypermedia Specification, W3C Community Group, 2023</li> <li>Christine Lemmer-Webber et al., ActivityPub, W3C Recommendation, 2018</li> <li>James Snell, Evan Prodromou, Activity Streams 2.0, W3C Recommendation, 2017</li> <li>mpuckett, FEP-5bf0: Collection sorting and filtering, 2023 (WITHDRAWN 2025)</li> </ul>"},{"location":"fep/34c1/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/37f2/","title":"Index","text":"<p>hide: - navigation description: \"A FEP proposing that W3C Social Web Incubator Community Group harmonize its process with other W3C Groups as well as the Fediverse Enhancement Process on socialhub.activitypub.rocks by * posting Calls for Consensus on the SWICG mailing list public-swicg@w3.org * engaging other SWICG fora like socialhub.activitypub.rocks (linked to as \"Forum\" from the SWICG Webpage) * having a shared response period\"</p>"},{"location":"fep/37f2/#fep-37f2-a-policy-for-calls-for-consensus-on-swicg-group-decisions","title":"FEP-37f2: a policy for calls for consensus on SWICG group decisions","text":"Authors Status Date received Tracking issue Discussions Repository bengo https://mastodon.social/@bengo <code>DRAFT</code> 2023-09-28 #184 - codeberg"},{"location":"fep/37f2/#summary","title":"Summary","text":"<p>A FEP proposing that W3C Social Web Incubator Community Group harmonize its process with other W3C Groups as well as the Fediverse Enhancement Process on socialhub.activitypub.rocks by: * posting Calls for Consensus on the SWICG mailing list public-swicg@w3.org * engaging other SWICG fora like socialhub.activitypub.rocks (linked to as \"Forum\" from the SWICG Webpage) * having a shared response period</p>"},{"location":"fep/37f2/#introduction","title":"Introduction","text":"<p>The Social Web Incubation Community Group is missing an explicit decision-making policy, which essentially all other W3C community groups have to ensure asynchronous and healthy consensus mechanisms across timezones and participatory modes.</p>"},{"location":"fep/37f2/#proposal","title":"Proposal","text":"<p>W3C SWICG will seek to make decisions through consensus and due process, per the W3C Process Document, \u00a75.2.1 Consensus.</p> <p>To afford asynchronous decisions and organizational deliberation, any resolution (including publication decisions) taken in a face-to-face meeting or teleconference will be considered provisional.</p> <p>A call for consensus (CFC) will be issued for all resolutions via email to public-swicg@w3.org (archives). The presence of formal resolutions will be indicated by a \"CFC\" prefix in the subject line of the email. Additional outreach to community venues for more affirmative consent is strongly encouraged. There will be a response period of 14 days. If no sustained objections are raised by the end of the response period, the resolution will be considered to have consensus as a resolution of the Community Group, i.e. a group decision.</p> <p>All decisions made by the group should be considered resolved unless and until new information becomes available or unless reopened at the discretion of the Chairs or the Director.</p> <p>This policy is an operational agreement per the W3C Community and Business Group Process.</p>"},{"location":"fep/37f2/#context","title":"Context","text":""},{"location":"fep/37f2/#w3c-groups-with-similar-decision-policies","title":"W3C Groups with Similar Decision Policies","text":"<p>These community groups and working groups have similar decision policies with tentative meeting resolutions and confirmation of calls for consensus via email:</p> <ul> <li>WebAssembly Community Group Charter</li> <li>Credentials Community Group Charter (see section \"Transparency\")</li> <li>Web Extensions Community Group Charter</li> <li>Web of Things Interest Group Charter</li> <li>HTML Working Group Charter</li> <li>Web Platform Working Group Charter</li> <li>Web Applications Working Group Charter</li> <li>Media Working Group Charter</li> <li>Web Performance Working Group Charter</li> <li>Service Workers Working Group Charter</li> <li>Verifiable Credentials Working Group Charter</li> <li>JSON-LD Working Group Charter</li> <li>WebAssembly Working Group Charter</li> <li>Web Authentication Working Group Charter</li> <li>Immersive Web Working Group Charter</li> <li>Web Payments Working Group Charter</li> <li>Devices and Sensors Working Group Charter</li> <li>Distributed Tracing Working Group Charter</li> <li>Web Editing Working Group Charter</li> <li>Internationalization Working Group Charter</li> <li>Publishing Maintenance Working Group Charter</li> <li>Solid Community Group Charter (see section \"Decision Policy\")</li> <li>Decentralized Identifier Working Group Charter</li> </ul> <p>Proposal processes on SWICG Forum with identical response period:</p> <ul> <li>FEP-a4ed: The Fediverse Enhancement Proposal Process</li> </ul>"},{"location":"fep/37f2/#w3c-community-group-process","title":"W3C Community Group Process","text":"<p>W3C SWICG is a W3C Community Group (CG).</p> <p>CGs are described in their process document as follows (excerpted for concision):</p> <p>This document defines W3C Community Groups, where anyone may develop Specifications, hold discussions, develop tests, and so on, with no participation fee. \u2026</p> <p>Community Groups that develop specifications do so under policies designed to strike a balance between ease of participation and safety for implementers and patent holders \u2026</p> <p>A Community Group may adopt operational agreements\u2026 that establish the group\u2019s scope of work, decision-making processes, communications preferences, and other operations. \u2026</p> <p>The following rules govern Community Group operational agreements: * They must be publicly documented. * They must be fair and must not unreasonably favor or discriminate against any group participant or their employer. * They must not conflict with or modify this Community and Business Group Process, the Community Contributor License Agreement (CLA), or the Final Specification Agreement. \u2026</p> <p>the Chair determines the means by which the group adopts and modifies operational agreements. The Chair must give actual notice to the participants of any material changes to the agreements. Participants may resign from the group if they do not wish to participate under the new agreements. \u2026</p> <p>Note: W3C encourages groups adopt decision-making policies that promote consensus. \u2026</p> <p>Each Community Group must have at least one Chair who is responsible for ensuring the group fulfills the requirements of this document as well as the group\u2019s operational agreements.</p>"},{"location":"fep/37f2/#related-reading","title":"Related Reading","text":"<ul> <li>IETF RFC7282 On Consensus and Humming in the IETF</li> <li>Doty, Nick, and Deirdre K. Mulligan. 2013. \"Internet Multistakeholder Processes and Techno-Policy Standards: Initial Reflections on Privacy at the World Wide Web Consortium\" Journal on Telecommunications and High Technology Law 11.</li> <li>Harmonization (standards), en.wikipedia.org</li> </ul>"},{"location":"fep/37f2/#editorial-notes","title":"Editorial Notes","text":"<p>The title of this proposal was generated in line with norms established by Content addressed vocabulary for extensions and FEP-a4ed: The Fediverse Enhancement Proposal Process.</p> <pre><code>\u26a1 P='a policy for calls for consensus on SWICG group decisions'\n\u26a1 echo \"SWIP-$(echo -n \"$P\" | sha256sum | cut -c-4): $P\"\nSWIP-37f2: a policy for calls for consensus on group decisions\n</code></pre> <p>The 'SW' in 'SWIP' stands for 'Social Web'.</p> <p>This proposal was initially published at: * https://socialweb.coop/SWIP/37f2/a-policy-for-calls-for-consensus-on-swicg-group-decisions/</p>"},{"location":"fep/37f2/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/3ab2/","title":"FEP-3ab2: ActivityPub Event Streaming API","text":"Authors Status Date received Discussions Repository Steve Bate svc-fep@stevebate.net <code>DRAFT</code> 2026-03-14 Discussions codeberg"},{"location":"fep/3ab2/#motivation","title":"Motivation","text":"<p>The ActivityPub specification defines a server-to-server federation protocol but does not specify how a client application (e.g. a Web front-end or mobile app) receives real-time notifications from an ActivityPub server. This FEP proposes a straightforward, standards-aligned approach using HTTP/1.1 Server-Sent Events.</p> <p>Goals: - Reuse existing HTTP authentication. - Efficiently use SSE streaming resources - Provide a discoverable, RESTful control API for managing SSE sessions.</p>"},{"location":"fep/3ab2/#summary","title":"Summary","text":"<p>This FEP specifies a lightweight, cookie-authenticated Server-Sent Events (SSE) streaming API that ActivityPub server implementations can expose to their authenticated clients. The API provides:</p> <ol> <li>A session-control sub-API for issuing and revoking short-lived, singoe-use streaming tickets, and for managing per-user topic subscriptions.</li> <li>A stream endpoint that delivers a multiplexed, real-time event feed for all topics the authenticated to which the user is subscribed.</li> </ol> <p>The design intentionally separates authentication (handled by the server's existing mechanism, e.g. OAuth 2.0, session cookies, or HTTP Basic) from streaming authorization (a short-lived ticket stored in an <code>HttpOnly</code> cookie), so that the SSE connection never carries user credentials.</p> <p>This proposal addresses two issues related to SSE event streaming in an ActivityPub context.</p> <ul> <li>Credential leaks: Since the SSE browser <code>EventSource</code> API does not allow sending HTTP headers when opening an SSE event stream, some proposals recommend sending credentials in the streaming URL. However, this is not desirable because of the risk that the credentials may be exposed in logs of servers (including proxy server outside the control of the the AP server operator).</li> <li>SSE Concurrent Connection Limits: Browsers enforce a small per\u2011origin limit on concurrent HTTP connections (often around 6), and each SSE stream occupies one of those connections for as long as it stays open, which can block other requests to the same origin. This proposal multiplexes multiple subscriptions on a single SSE connection rather than creating a stream per subscription.</li> </ul>"},{"location":"fep/3ab2/#terminology","title":"Terminology","text":"Term Definition Actor A standard ActivityPub actor Principal The authenticated identity (URI of the Actor) making a request. Ticket A short-lived, randomly-generated opaque token that authorizes one SSE stream connection. Topic An opaque string naming a channel of events (e.g. <code>notifications</code>, <code>inbox</code>, <code>timeline:home</code>). SSE Session The lifecycle from ticket issuance through stream connection to ticket revocation or expiry. <p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC 2119.</p>"},{"location":"fep/3ab2/#endpoint-discovery","title":"Endpoint Discovery","text":"<p>The streaming control endpoint is exposed using the ActivityPub Actor <code>endpoints</code> property using the <code>streamingControl</code> endpoint property.</p> <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/3ab2\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Person\",\n \"id\": \"https://server.example/actor/1\",\n ...\n \"endpoints\": [\n \"streamingControl\": \"https://server.example/streaming/control\"\n ]\n}\n</code></pre>"},{"location":"fep/3ab2/#topics","title":"Topics","text":"<p>Topic represented as multiple text segments separated by the \"/\" character.</p>"},{"location":"fep/3ab2/#topic-wildcards","title":"Topic Wildcards","text":"<p>For subscriptions, topics MAY contain wildcards if the server supports them. The standard MQTT wildcards are used for this purpose. A server can advertise wildcard support in the <code>Create Session</code> response via the <code>wildcard_support</code> field.</p> Wildcard Description Example + Matches a single topic segment <code>foo/+/baz</code> # Matches zero or more segments at the end of the pattern <code>foo/#</code> <p>When publishing, the topic MUST use URL escaping for any wildcard characters in the topic. Subscribers MUST also escape non-wildcard '+' and '#' characters in topic subscriptions.</p>"},{"location":"fep/3ab2/#activitypub-topics","title":"ActivityPub topics","text":"<p>For ActivityPub entities, topics will correspond to ActivityPub HTTP/HTTPS URIs. To construct a topic from a URI, the following algorithm is used.</p> <ol> <li>Remove the URL scheme.</li> <li>The URL authority (domain and optional port) is the first segment of the topic.</li> <li>The URL path becomes \"/\"-separated path segments </li> <li>If a URL fragment is present this becomes the final segment.</li> </ol> <p>Topic wildcard characters in topic segments MUST be URL-escaped.</p> <p>Examples</p> URL Topic <code>https://server.example/note/1</code> <code>server.example/note/1</code> <code>https://server.example:1000/actor#xyz</code> <code>server.example:1000/actor/xyz</code> <code>https://server.example:1000/actor+123#xyz</code> <code>server.example:1000/actor%2B123/xyz</code>"},{"location":"fep/3ab2/#session-management","title":"Session Management","text":""},{"location":"fep/3ab2/#create-session-post-streamingcontrol","title":"Create Session \u2014 <code>POST {streamingControl}</code>","text":"<p>Requires: authenticated Principal.</p> <p>The server:</p> <ol> <li>Generates a cryptographically random ticket.</li> <li>Records SSE session state (ticket, Principal, origin, etc.)</li> <li>Sets a <code>Set-Cookie</code> response header carrying the ticket value with the configured path, <code>SameSite</code>, <code>Secure</code>, and <code>HttpOnly</code> attributes.</li> <li>Returns <code>201 Created</code> with a JSON body.</li> </ol> <p>Server Response body:</p> Field Type Description <code>subscriptions_url</code> string Required. Absolute URL of the subscriptions endpoint. Clients MUST use this value for all subscription management requests in this session. <code>stream_url</code> string Required. Absolute URL of the SSE stream endpoint. Clients MUST use this value to open the event stream. <code>expires_at</code> string ISO 8601 UTC timestamp of ticket expiry <code>wildcard_support</code> bool Indicates whether the server supports topic wildcards or not. Defaults is <code>true</code> if not provided. <p>Servers MUST include <code>subscriptions_url</code> and <code>stream_url</code> in every successful <code>201 Created</code> response to this endpoint. Clients MUST treat these URLs as opaque and MUST NOT attempt to derive or construct them from the SSE prefix or any other path convention.</p> <p>Example</p> <pre><code>{\n \"subscriptions_url\": \"https://server.example/sse/control/subscriptions\",\n \"stream_url\": \"https://server.example/sse/stream\",\n \"expires_at\": \"2001-03-10T12:05:00Z\",\n \"wildcard_support\": true\n}\n</code></pre> <p>Cookie attributes (RECOMMENDED defaults):</p> Attribute Recommended value <code>Path</code> Scoped to the implementation's streaming API path <code>SameSite</code> <code>Lax</code> <code>Secure</code> <code>true</code> in production <code>HttpOnly</code> Server MAY set to <code>false</code> to allow JavaScript access <code>Max-Age</code> Cookie expiry age <p>The ticket MUST expire after a server-configured TTL. Clients MUST call <code>POST /control</code> again to renew before opening a new stream.</p>"},{"location":"fep/3ab2/#revoke-session-delete-streamingcontrol","title":"Revoke Session \u2014 <code>DELETE {streamingControl}</code>","text":"<p>Requires: authenticated Principal. The ticket cookie is read if present.</p> <p>The server: 1. Invalidates the ticket. 2. Sends a <code>Set-Cookie</code> header that clears the cookie (<code>Max-Age=0</code>). 3. Returns <code>204 No Content</code> if successfull.</p> <p>Client implementations SHOULD also close any active SSE connections associated with the revoked ticket.</p>"},{"location":"fep/3ab2/#subscription-management","title":"Subscription Management","text":"<p>All subscription endpoints require both: - A valid authentication credential. - A valid, non-expired ticket cookie associated with the authenticated Principal.</p> <p>Violating either condition MUST result in <code>401 Unauthorized</code>.</p>"},{"location":"fep/3ab2/#list-subscriptions-get-subscriptions_url","title":"List Subscriptions \u2014 <code>GET {subscriptions_url}</code>","text":"<p>Response:</p> <pre><code>{\n \"topics\": [\"notifications\", \"timeline:home\"]\n}\n</code></pre> Field Type Description <code>topics</code> string\u00a0array The full set of subscribed topics. An empty array indicates no active subscriptions."},{"location":"fep/3ab2/#add-subscription-post-subscriptions_url","title":"Add Subscription \u2014 <code>POST {subscriptions_url}</code>","text":"<p>Request body:</p> <pre><code>{\n \"topics\": [\n \"remote-server.example/note/abcd\"\n \"server.example/note/#\",\n ]\n}\n</code></pre> Field Type Required Description <code>topics</code> string\u00a0array yes The topic identifiers or patterns <p>Response:</p> <pre><code>{\n \"topics\": [\n \"remote-server.example/note/abcd\"\n \"server.example/note/#\",\n \"server.example/another-topic\"\n ]\n}\n</code></pre> Field Type Description <code>topics</code> string\u00a0array The full set of subscribed topics. The array reflects the full set of subscriptions after the operation. Adding a topic that is already subscribed is idempotent and MUST NOT return an error."},{"location":"fep/3ab2/#remove-subscription-delete-subscriptions_urltopictopic","title":"Remove Subscription \u2014 <code>DELETE {subscriptions_url}?topic={topic}</code>","text":"<p>The <code>topic</code> query parameter is required. Removing a non-existent topic MUST be idempotent (no error).</p> <p>Response:</p> <ul> <li>If successful, the server MUST respond with a <code>204 No Content</code> HTTP status.</li> </ul>"},{"location":"fep/3ab2/#sse-stream-endpoint","title":"SSE Stream Endpoint","text":""},{"location":"fep/3ab2/#opening-the-stream-get-stream_url","title":"Opening the Stream \u2014 <code>GET {stream_url}</code>","text":"<p>Requires: - A valid authentication credential. - A valid, non-expired ticket cookie whose associated identity matches the Principal.</p> <p>The client MUST send <code>Accept: text/event-stream</code>.</p> <p>On success the server: 1. Upgrades the connection to an SSE stream (<code>Content-Type: text/event-stream</code>). 2. Sets <code>Cache-Control: no-cache</code> and (if behind a reverse proxy) <code>X-Accel-Buffering: no</code>. 3. Begins delivering <code>ServerSentEvent</code> frames for all topics the user is currently subscribed to. 4. Keeps the connection open until the client disconnects, the ticket expires, the session is revoked, or the server restarts.</p> <p>Only one active stream per Principal SHOULD be supported at a time; if a second stream is opened the server MAY close the first.</p> <p>The ticket is single-use. The server MUST only create one stream per ticket.</p>"},{"location":"fep/3ab2/#activitypub-sse-event-format","title":"ActivityPub SSE Event Format","text":"<p>Each event follows the W3C EventSource wire format:</p> <pre><code>id: &lt;event-id&gt;\\n\nevent: activitypub\\n\ndata: &lt;JSON-object&gt;\\n\n\\n\n</code></pre> SSE field Value <code>id</code> The <code>id</code> field of the payload object, or a server-generated UUID hex <code>event</code> <code>activitypub</code> (implementations MAY define additional named event types) <code>data</code> A JSON-serialized Activitypub entity <p>Example ActivityPub Event Payload (data)</p> <pre><code>{\n \"@context\":\"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/abc123\",\n \"type\": \"Create\",\n \"published\": \"2026-03-10T11:00:00Z\",\n \"object\": { ... }\n}\n</code></pre>"},{"location":"fep/3ab2/#heartbeats","title":"Heartbeats","text":"<p>Servers SHOULD send periodic SSE heartbeat events (<code>event: heartbeat</code>) at regular intervals (RECOMMENDED: every 15\u201330 seconds) to prevent proxy timeouts and help clients detect dead connections. The <code>id</code> and <code>data</code> fields are optional in these messages.</p>"},{"location":"fep/3ab2/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/3ab2/#ticket-security","title":"Ticket Security","text":"<ul> <li>Tickets MUST be generated using a cryptographically secure random source with sufficient entropy to resist guessing attacks.</li> <li>Tickets MUST be invalidated on session revocation (<code>DELETE /control</code>).</li> <li>Servers MUST enforce ticket TTL; expired tickets MUST be rejected.</li> <li>Ticket values MUST NOT appear in server access logs in plain form.</li> </ul>"},{"location":"fep/3ab2/#cookie-security","title":"Cookie Security","text":"<ul> <li>The ticket cookie SHOULD be <code>Secure</code> (HTTPS only) in production.</li> <li><code>SameSite=Lax</code> or <code>SameSite=Strict</code> MUST be set to mitigate CSRF.</li> <li><code>HttpOnly</code> SHOULD be set unless the client is a first-party JavaScript application that must read the cookie.</li> </ul>"},{"location":"fep/3ab2/#authorization","title":"Authorization","text":"<ul> <li>The server MUST verify that the ticket's associated user matches the currently authenticated Principal on every request that requires a ticket. Mismatches MUST result in <code>401 Unauthorized</code>.</li> <li>Subscriptions are scoped to the authenticated Principal; one user MUST NOT be able to read or modify another user's subscriptions.</li> </ul>"},{"location":"fep/3ab2/#topic-namespace","title":"Topic Namespace","text":"<ul> <li>Servers SHOULD validate topic strings against an allowlist or length/character constraints to prevent abuse.</li> </ul>"},{"location":"fep/3ab2/#transport-security","title":"Transport Security","text":"<ul> <li>All endpoints MUST be served over HTTPS in production.</li> <li> <p>SSE connections SHOULD be protected against cross-origin misuse via <code>CORS</code> headers restricting <code>Origin</code> to trusted first-party origins. </p> </li> <li> <p>If your SPA is on a different first-party origin (example: <code>app.example</code> -&gt; <code>api.example</code>), then CORS is required and must be strict:</p> </li> <li><code>Access-Control-Allow-Origin</code> must be the exact SPA origin, not *.</li> <li><code>Access-Control-Allow-Credentials: true</code> is required if cookies are used.</li> <li><code>Vary: Origin</code> should be set.</li> <li>Server should validate <code>Origin</code> against an allowlist and reject others.</li> </ul>"},{"location":"fep/3ab2/#privacy-considerations","title":"Privacy Considerations","text":"<p>The SSE stream may carry sensitive information (notifications, timeline events). Implementations MUST: - Restrict stream access to the owning Principal. - Limit streaming events to content that the Principal is authorized to view. - Not include events from topics the Principal has not explicitly subscribed to. - Consider data minimization: deliver only the fields necessary for the client's use case.</p>"},{"location":"fep/3ab2/#implementations","title":"Implementations","text":"<ul> <li>FIRM</li> </ul>"},{"location":"fep/3ab2/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/3b86/","title":"Index","text":"<p>hide: - navigation description: \"\"Activity Intents\" extend the capabilities of an ActivityPub server beyond a user's outbox, and enable direct interactions with content on the wider social web. They do this by publishing a machine-readable list of public URLs where users can perform key activities (such as Follow, Like, or Announce) allowing other websites to initiate remote social interactions without cumbersome copying/pasting of URL strings.\"</p>"},{"location":"fep/3b86/#fep-3b86-activity-intents","title":"FEP-3b86: Activity Intents","text":"Authors Status Date received Tracking issue Discussions Repository Ben Pate \\&lt;@benpate@mastodon.social&gt; <code>DRAFT</code> 2024-04-19 #302 - codeberg"},{"location":"fep/3b86/#summary","title":"Summary","text":"<p>\"Activity Intents\" extend the capabilities of an ActivityPub server beyond a user's outbox, and enable direct interactions with content on the wider social web. They do this by publishing a machine-readable list of public URLs where users can perform key activities (such as <code>Follow</code>, <code>Like</code>, or <code>Announce</code>) allowing other websites to initiate remote social interactions without cumbersome copying/pasting of URL strings.</p>"},{"location":"fep/3b86/#1-requirements","title":"1. Requirements","text":"<p>The key words \"MUST\", \"SHOULD\", and \"MAY\" are to be interpreted as described in RFC2119.</p> <p>For the purposes of this document, a \"Home Server\" is the location where a user is logged in, and that publishes the Actor's Activity Intents via WebFinger. A \"Remote Server\" is another location on the Internet that the user wants to interact with, and that will request/receive those intents via WebFinger.</p>"},{"location":"fep/3b86/#2-history","title":"2. History","text":"<p>Most centralized social media services have widgets that allow users on the wider Internet to interact with their social services. These include \"like\" and \"share\" buttons that third-party websites embed into their content, and link users back to their corresponding social media account.</p> <p>This is difficult to achieve in a federated environment.</p> <p>There have been other attempts to make a \"Share on Mastodon\" button that performs a similar action on the Fediverse. But these tasks are implemented differently by different Fediverse servers. For example, Mastodon uses <code>/share</code>, while Hubzilla uses <code>/rpost</code>. Parameters to each application are often different, using variations of <code>text</code>, <code>title</code>, <code>url</code>, and other values.</p> <p>The lack of a unified standard has led developers to hard-code endpoints for each distinct application. This is brittle and vulnerable to changes by server authors. It also pins those URL endpoints, making them difficult for servers to change in the future without breaking an unknown number of \"share\" buttons out in the wild.</p> <p>What is needed is a systematic way for each server to announce the endpoint URLs that they support.</p>"},{"location":"fep/3b86/#3-activity-intents","title":"3. Activity Intents","text":"<p>In the most basic terms, Activity Intents expand on the common Fediverse use of WebFinger in FEP-4adb to include mappings between any Activity Type and the URL endpoint where that user can perform it. This expands and standardizes the \"remote follow\" workflow that was used by oStatus protocol, but has not been fully implemented by newer Fediverse applications and no longer has a public specification document.</p> <p>When generating a WebFinger result for a user account, servers supporting Activity Intents SHOULD respond with one or more intent links in the \"links\" property. Activity Intent links MUST have <code>rel</code> and <code>template</code> properties. All others properties are ignored.</p> <p>While the number and specific behavior of each Activity Intent is defined independently by each home server, to maximize compatibility home servers SHOULD publish all of the activities that they support. This gives remote servers the greatest opportunity to match as many activities as possible.</p>"},{"location":"fep/3b86/#31-example","title":"3.1. Example","text":"<p>Here is an example response from a WebFinger server which includes three Activity Intents appended to the end of its <code>links</code> property.</p> <pre><code>{\n \"subject\": \"acct:benpate@mastodon.social\",\n \"aliases\": [\n \"https://mastodon.social/@benpate\",\n \"https://mastodon.social/users/benpate\"\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://mastodon.social/@benpate\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://mastodon.social/users/benpate\"\n },\n {\n \"rel\": \"http://ostatus.org/schema/1.0/subscribe\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={uri}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Follow\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={object}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Create\",\n \"template\": \"https://mastodon.social/share?uri={object}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Like\",\n \"template\": \"https://mastodon.social/intents/like?id={object}\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/3b86/#32-required-link-properties","title":"3.2. Required Link Properties","text":"<p>rel: Activity Intents use the <code>https://w3id.org/fep/3b86/*</code> (as described in FEP-888d) to designate the kind of activity intent, where <code>*</code> represents the particular Activity the user intends to perform. These relations -- such as <code>https://w3id.org/fep/3b86/Follow</code>, and <code>https://w3id.org/fep/3b86/Create</code> -- are listed in detail below.</p> <p>template: Links use URI Templates as used in RFC-6415 to designate URLS and parameters to be injected by the caller -- as in <code>{uri}</code> or <code>{name}</code> values. Note: WebFinger does not specify the <code>template</code> parameter, but it is used here because 1) there's a semantic benefit in identifying values not a links, but as link templates, and 2) it is already in use by oStatus, which should make it familiar to Fediverse developers.</p> <p>Parameter names are chosen to correspond with Activity Vocabulary properties and may differ from parameters used by pre-existing implementations.</p> <p>To prevent unrecognized properties from corrupting a workflow: * Remote servers MUST be able to replace all recognized values with the appropriate string. * Remote servers MUST replace unrecognized values with an empty string.</p>"},{"location":"fep/3b86/#33-template-parameters","title":"3.3 Template Parameters","text":"<p>In all cases, Activity Intents intentionally use the property names defined in the W3C standard Activity Vocabulary. However, intents must fit into a URL, and must use commonly understood fields. So, Intent parameters can only use IDs -- URL references to JSON-LD resources available elsewhere on the Internet.</p> <p>In addition, remote servers MUST Percent Encode all values replaced in the URI Template.</p>"},{"location":"fep/3b86/#34-workflow-actions","title":"3.4 Workflow Actions","text":"<p>Activity Intents MAY include additional query parameters <code>on-success</code> and <code>on-cancel</code> that allow home servers to return users to their original workflow on the remote server:</p> <p>on-success - If present in the link template, this value identifies the action that the home server SHOULD take once the Activity Intent workflow is complete. If this parameter is missing, then the resulting page is left up to the home server to choose.</p> <p>on-cancel - If present in the link template, this value identifies the action that the home server SHOULD take if they abort the Activity Intent workflow. If this parameter is missing, then the resulting page is left up to the home server to choose.</p>"},{"location":"fep/3b86/#action-close","title":"Action: (close)","text":"<p>If the value of either <code>on-success</code> or <code>on-cancel</code> parameter is the string <code>(close)</code> then the home server can assume that it is running in a pop-up window, and SHOULD close the current window.</p>"},{"location":"fep/3b86/#action-redirecting-to-a-url","title":"Action: Redirecting to a URL","text":"<p>If the value of either <code>on-success</code> or <code>on-cancel</code> query parameter is a valid URL, then the home server SHOULD redirect clients to the provided URL. When redirecting to a new URL, the home server MUST use an interstitial page that notified the user that they are being redirected, and displays the URL of the new page to them.</p> <p>IMPORTANT! Unvalidated Redirects are a documented vulnerability that opens the home server up to phishing attacks. </p> <p>To avoid a potential security vulnerability when handling <code>on-success</code> and <code>on-cancel</code> workflows, it is important that the home server: \"Force all redirects to first go through a page notifying users that they are going off of your site, with the destination clearly displayed, and have them click a link to confirm.\" (owasp.org)</p>"},{"location":"fep/3b86/#35-endpoint-expectations","title":"3.5 Endpoint Expectations","text":"<p>The user's home server is a trusted environment that manages the user's sign-in status along with the rest of their social inbox and outbox. When the remote server links to an Activity Intent provided by the home server, the layout, fields, and UI are all determined by the home server</p> <p>Remote servers MAY open Activity Intent links in many different environments, such as: 1. a full browser window 2. a small pop-up window 3. a mobile app HTML view 4. or other constrained environments.</p> <p>Home servers SHOULD build their UI with minimal assumptions so that the endpoint will fit well into as many environments as possible. Home servers may not be aware if the remote server opens the Activity Intent link in a separate popup window or not, so remote servers SHOULD include <code>on-success</code> and <code>on-cancel</code> parameters that redirect back to its own pages so that it can close the pop-up itself.</p>"},{"location":"fep/3b86/#4-standard-intent-definitions","title":"4. Standard Intent Definitions","text":""},{"location":"fep/3b86/#41-accept-intent","title":"4.1. Accept Intent","text":"<p>This intent corresponds to the ActivityStreams Accept activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Accept</code>.</p> <p>The Accept intent publishes the API endpoint where the current user can \"accept\" the designated object.</p>"},{"location":"fep/3b86/#411-parameters","title":"4.1.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will accept when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform they abort the workflow.</li> </ul>"},{"location":"fep/3b86/#412-example","title":"4.1.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Accept\",\n \"template\": \"https://server.org/intents/accept?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#42-add-intent","title":"4.2. Add Intent","text":"<p>This intent corresponds to the ActivityStreams Add activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Add</code> .</p> <p>The Add intent publishes the API endpoint where the current user can add an object to the designated collection..</p>"},{"location":"fep/3b86/#421-parameters","title":"4.2.1. Parameters","text":"<ul> <li><code>{object}</code> ID of the object that the user will add when they use this workflow.</li> <li><code>{target}</code> ID of the collection being added to.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#422-example","title":"4.2.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Add\",\n \"template\": \"https://server.org/intents/add?objectId={object}&amp;targetId={target}\"\n}\n</code></pre>"},{"location":"fep/3b86/#43-announce-intent","title":"4.3. Announce Intent","text":"<p>This intent corresponds to the ActivityStreams Announce activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Announce</code>.</p> <p>The Announce intent publishes the API endpoint where the current user can announce, or \"boost\" the provided document in their home server.</p>"},{"location":"fep/3b86/#431-parameters","title":"4.3.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the document that the user will boost when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#432-example","title":"4.3.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Announce\",\n \"template\": \"https://server.org/intents/announce?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#44-arrive-intent","title":"4.4. Arrive Intent","text":"<p>This intent corresponds to the ActivityStreams Arrive activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Arrive</code>.</p> <p>The Arrive intent publishes the API endpoint where the current user can indicate that they have arrived at a particular location from their home server.</p>"},{"location":"fep/3b86/#441-parameters","title":"4.4.1. Parameters","text":"<ul> <li><code>{location}</code> - ID of the location object where the user will mark as \"arrived\" when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#442-example","title":"4.4.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Arrive\",\n \"template\": \"https://server.org/intents/arrive?hotelId={location}\"\n}\n</code></pre>"},{"location":"fep/3b86/#45-block-intent","title":"4.5. Block Intent","text":"<p>This intent corresponds to the ActivityStreams Block activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Block</code>.</p> <p>The Block intent publishes the API endpoint where the current user can block the provided object from their home server.</p>"},{"location":"fep/3b86/#451-parameters","title":"4.5.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object (document, user, etc) that the user will block when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#452-example","title":"4.5.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Block\",\n \"template\": \"https://server.org/intents/block?userId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#46-create-intent","title":"4.6. Create Intent","text":"<p>This intent corresponds to the ActivityStreams Create activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Create</code>.</p> <p>The Create intent publishes the API endpoint where the current user can create a new post in their own outbox.</p> <p>This is similar to the existing \"share\" endpoints supported by several Fediverse apps, where the user can create a new post in their inbox starting with some pre-populated content.</p> <p>This Intent differs slightly from others in that it does not take an object ID as a parameter because it expects a the user's home server to create a new object. Instead, the parameters for this Intent are meant to pre-populate into the new object that the user will create. </p>"},{"location":"fep/3b86/#461-parameters","title":"4.6.1. Parameters","text":"<ul> <li><code>{type}</code> - (optional) Type of object to create (Note, Article, etc). Home servers can determine whether to use or ignore this value.</li> <li><code>{name}</code> - (optional) Name to pre-populate into the created object.</li> <li><code>{summary}</code> - (optional) Summary to pre-populate into the created object.</li> <li><code>{content}</code> - (optional) Text content to pre-populate into the created object.</li> <li><code>{inReplyTo}</code> - (optional)The ID of the ActivityStreams Document that the user is replying to.</li> <li><code>{attachment}</code> - (optional) ID of an object (such as a link or an image) that should be included in the created object.</li> <li><code>{tag}</code> - (optional) ID of an object (such as a tag definition) that should be referenced by the created object</li> <li><code>{startTime}</code> - (optional) a date-time that identifies the actual or expected starting time of the created object. RFC3339 format </li> <li><code>{endTime}</code> - (optional) a date-time that identifies the actual or expected ending time of the created object. RFC3339 format</li> <li><code>{describes}</code> (optional) the ID of an object to be described, when creating a Profile object.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#462-example","title":"4.6.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Create\",\n \"template\": \"https://mastodon.social/share?text={content}\"\n}\n</code></pre>"},{"location":"fep/3b86/#47-delete-intent","title":"4.7. Delete Intent","text":"<p>This intent corresponds to the ActivityStreams Delete activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Delete</code>.</p> <p>The Delete intent publishes the API endpoint where the current user can initiate a \"delete\" request.</p>"},{"location":"fep/3b86/#471-parameters","title":"4.7.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will delete when they use this workflow.</li> <li><code>{origin}</code>- (optional) ID of the collection or context that the object will be deleted from.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#472-example","title":"4.7.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Delete\",\n \"template\": \"https://server.org/intents/Delete?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#48-dislike-intent","title":"4.8. Dislike Intent","text":"<p>This intent corresponds to the ActivityStreams Dislike activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Dislike</code>.</p> <p>The Dislike intent publishes the API endpoint where the current user can initiate a \"dislike\" request.</p>"},{"location":"fep/3b86/#481-parameters","title":"4.8.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the document that the user will dislike when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#482-example","title":"4.8.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Dislike\",\n \"template\": \"https://server.org/intent/dislike?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#49-flag-intent","title":"4.9. Flag Intent","text":"<p>This intent corresponds to the ActivityStreams Flag activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Flag</code>.</p> <p>The Flag intent publishes the API endpoint where the current user can initiate a \"flag\" request, which is used to report inappropriate content.</p>"},{"location":"fep/3b86/#491-parameters","title":"4.9.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object (document, user, etc) that the user will flag when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#492-example","title":"4.9.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Flag\",\n \"template\": \"https://server.org/intent/flag?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#410-follow-intent","title":"4.10. Follow Intent","text":"<p>This intent corresponds to the ActivityStreams Follow activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Follow</code>.</p> <p>The Follow intent publishes the API endpoint where the current user can initiate a \"follow\" request. This is similar to the remote follow workflow defined by oStatus that is still supported at various levels by several Fediverse apps, but is no longer formally documented.</p>"},{"location":"fep/3b86/#4101-parameters","title":"4.10.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the actor that the user will follow when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4102-example","title":"4.10.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Follow\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#411-ignore-intent","title":"4.11. Ignore Intent","text":"<p>This intent corresponds to the ActivityStreams Ignore activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Ignore</code>.</p> <p>The Ignore intent publishes the API endpoint where the current user can initiate an \"ignore\" request, which is similar to a Block, and is used to ignore or mute various actors and objects online</p>"},{"location":"fep/3b86/#4111-parameters","title":"4.11.1 Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will mark \"ignored\" when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4112-example","title":"4.11.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Ignore\",\n \"template\": \"https://server.org/intents/ignore?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#412-invite-intent","title":"4.12. Invite Intent","text":"<p>This intent corresponds to the ActivityStreams Invite activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Invite</code>.</p> <p>The Invite intent publishes the API endpoint where the current user can initiate an \"invite\" request, which is similar to an Offer, and is used to extend an invitation for the object to the target</p>"},{"location":"fep/3b86/#4121-parameters","title":"4.12.1. Parameters","text":"<ul> <li><code>{target}</code> - ID of the actor who will receive the invitation.</li> <li><code>{object}</code> - ID of the object (event, group, etc) that the actor will be invited to.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4122-example","title":"4.12.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Invite\",\n \"template\": \"https://server.org/intents/invite?actorId={object}&amp;eventId={target}\"\n}\n</code></pre>"},{"location":"fep/3b86/#413-join-intent","title":"4.13. Join Intent","text":"<p>This intent corresponds to the ActivityStreams Join activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Join</code>.</p> <p>The Join intent publishes the API endpoint where the current user can initiate a \"join\" request.</p>"},{"location":"fep/3b86/#4131-parameters","title":"4.13.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will join when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4132-example","title":"4.13.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Join\",\n \"template\": \"https://server.org/intents/join?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#414-leave-intent","title":"4.14. Leave Intent","text":"<p>This intent corresponds to the ActivityStreams Leave activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Leave</code>.</p> <p>The Leave intent publishes the API endpoint where the current user can initiate a \"leave\" request.</p>"},{"location":"fep/3b86/#4141-parameters","title":"4.14.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will leave when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4142-example","title":"4.14.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Leave\",\n \"template\": \"https://server.org/intents/leave?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#415-like-intent","title":"4.15. Like Intent","text":"<p>This intent corresponds to the ActivityStreams Like activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Like</code>.</p> <p>The Like intent publishes the API endpoint where the current user can like the current document.</p>"},{"location":"fep/3b86/#4151-parameters","title":"4.15.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will mark as \"liked\" when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4152-example","title":"4.15.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Like\",\n \"template\": \"https://server.com/intents/like?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#416-listen-intent","title":"4.16. Listen Intent","text":"<p>This intent corresponds to the ActivityStreams Listen activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Listen</code>.</p> <p>The Listen intent publishes the API endpoint where the current user can initiate a \"listen\" request.</p>"},{"location":"fep/3b86/#4161-parameters","title":"4.16.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will mark as \"listened\" to when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4162-example","title":"4.16.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Listen\",\n \"template\": \"https://server.org/intents/listen?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#417-move-intent","title":"4.17. Move Intent","text":"<p>This intent corresponds to the ActivityStreams Move activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Move</code>.</p> <p>The Move intent publishes the API endpoint where the current user can initiate a \"move\" request.</p>"},{"location":"fep/3b86/#4171-parameters","title":"4.17.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will move when they use this workflow.</li> <li><code>{target}</code> - ID of the collection that the object will be moved to.</li> <li><code>{origin}</code> - (optional) ID of the collection that the object will be moved from.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4172-example","title":"4.17.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Move\",\n \"template\": \"https://server.org/intents/move?objectId={object}&amp;destId={target}\"\n}\n</code></pre>"},{"location":"fep/3b86/#418-offer-intent","title":"4.18. Offer Intent","text":"<p>This intent corresponds to the ActivityStreams Offer activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Offer</code>.</p> <p>The Offer intent publishes the API endpoint where the current user can initiate an \"offer\" request.</p>"},{"location":"fep/3b86/#4181-parameters","title":"4.18.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will offer when they use this workflow.</li> <li><code>{target}</code> - ID of the actor that will receive the offer.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4182-example","title":"4.18.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Offer\",\n \"template\": \"https://server.org/intents/offer?objectId={object}&amp;to={target}\"\n}\n</code></pre>"},{"location":"fep/3b86/#419-question-intent","title":"4.19. Question Intent","text":"<p>This intent corresponds to the ActivityStreams Question activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Question</code>.</p> <p>The Question intent publishes the API endpoint where the current user can initiate an \"question\" workflow.</p>"},{"location":"fep/3b86/#4191-parameters","title":"4.19.1. Parameters","text":"<ul> <li><code>{name}</code> - The \"name\" property to pre-populate into the question the user will ask when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4192-example","title":"4.19.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Question\",\n \"template\": \"https://server.org/intents/question?name={name}\"\n}\n</code></pre>"},{"location":"fep/3b86/#420-read-intent","title":"4.20. Read Intent","text":"<p>This intent corresponds to the ActivityStreams Read activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Read</code>.</p> <p>The Read intent publishes the API endpoint where the current user can initiate an \"read\" request.</p>"},{"location":"fep/3b86/#4201-parameters","title":"4.20.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will mark as \"read\" when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4202-example","title":"4.20.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Read\",\n \"template\": \"https://server.org/intents/object?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#421-reject-intent","title":"4.21. Reject Intent","text":"<p>This intent corresponds to the ActivityStreams Reject activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Reject</code>.</p> <p>The Reject intent publishes the API endpoint where the current user can initiate an \"reject\" request.</p>"},{"location":"fep/3b86/#4211-parameters","title":"4.21.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will reject when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4212-example","title":"4.21.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Reject\",\n \"template\": \"https://server.org/intents/reject?offerId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#422-remove-intent","title":"4.22. Remove Intent","text":"<p>This intent corresponds to the ActivityStreams Remove activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Remove</code>.</p> <p>The Remove intent publishes the API endpoint where the current user can initiate a \"remove\" request.</p>"},{"location":"fep/3b86/#4221-parameters","title":"4.22.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will remove when they use this workflow.</li> <li><code>{target}</code> - (optional) The ID of the collection that the object will be removed from.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4222-example","title":"4.22.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Reject\",\n \"template\": \"https://server.org/intents/reject?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#423-tentativeaccept-intent","title":"4.23. TentativeAccept Intent","text":"<p>This intent corresponds to the ActivityStreams TentativeAccept activity, which itself is a specialization of the Accept activity and is defined using the link relation <code>https://w3id.org/fep/3b86/TentativeAccept</code>.</p> <p>The TentativeAccept intent publishes the API endpoint where the current user can initiate an \"tentative accept\" request, indicating that acceptance of the original offer is tentative.</p>"},{"location":"fep/3b86/#4231-parameters","title":"4.23.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will tentatively accept when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4232-example","title":"4.23.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/TentativeAccept\",\n \"template\": \"https://server.org/intents/tentativeAccept?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#424-tentativereject-intent","title":"4.24. TentativeReject Intent","text":"<p>This intent corresponds to the ActivityStreams TentativeReject activity, which itself is a specialization of the Reject activity and is defined using the link relation <code>https://w3id.org/fep/3b86/TentativeReject</code>.</p> <p>The TentativeReject intent publishes the API endpoint where the current user can initiate an \"tentative reject\" request, indicating that rejection of the original offer is tentative.</p>"},{"location":"fep/3b86/#4241-parameters","title":"4.24.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the user will tentatively reject when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4242-example","title":"4.24.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/TentativeReject\",\n \"template\": \"https://server.org/intents/tentativeReject?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#425-travel-intent","title":"4.25. Travel Intent","text":"<p>This intent corresponds to the ActivityStreams Travel activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Travel</code>.</p> <p>The Travel intent publishes the API endpoint where the user can initiate a \"travel\" request.</p>"},{"location":"fep/3b86/#4251-parameters","title":"4.25.1. Parameters","text":"<ul> <li><code>{target}</code> - (optional) The ID of the location that the actor will travel to.</li> <li><code>{origin}</code> - (optional) The ID of the location that the actor will travel from.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4252-example","title":"4.25.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Travel\",\n \"template\": \"https://server.org/intents/travel?from={origin}&amp;to={target}\"\n}\n</code></pre>"},{"location":"fep/3b86/#426-undo-intent","title":"4.26. Undo Intent","text":"<p>This intent corresponds to the ActivityStreams Undo activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Undo</code>.</p> <p>The Undo intent publishes the API endpoint where the current user can initiate a \"travel\" request.</p>"},{"location":"fep/3b86/#4261-parameters","title":"4.26.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the activity that the actor will undo.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) URL to redirect users if they abort the workflow.</li> </ul>"},{"location":"fep/3b86/#4262-example","title":"4.26.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Undo\",\n \"template\": \"https://server.org/intents/undo?activityId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#427-update-intent","title":"4.27. Update Intent","text":"<p>This intent corresponds to the ActivityStreams Update activity and is defined using the link relation <code>https://w3id.org/fep/3b86/Update</code>.</p> <p>The Update intent publishes the API endpoint where the user can initiate an \"update\" request.</p>"},{"location":"fep/3b86/#4271-parameters","title":"4.27.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the actor will update when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4272-example","title":"4.27.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Update\",\n \"template\": \"https://server.org/intents/update?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#428-view-intent","title":"4.28. View Intent","text":"<p>This intent corresponds to the ActivityStreams View activity and is defined using the link relation <code>https://w3id.org/fep/3b86/View</code>.</p> <p>The View intent publishes the API endpoint where the current user can initiate an \"update\" request.</p>"},{"location":"fep/3b86/#4281-parameters","title":"4.28.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the actor will mark as \"viewed\" when they use this workflow.</li> <li><code>{on-success}</code> - (optional) Workflow action to perform after the workflow completes.</li> <li><code>{on-cancel}</code> - (optional) Workflow action to perform if the user aborts the workflow.</li> </ul>"},{"location":"fep/3b86/#4282-example","title":"4.28.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/View\",\n \"template\": \"https://server.org/intents/view?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#50-additional-intent-definitions","title":"5.0. Additional Intent Definitions","text":"<p>In addition to the activities defined in the Activity Vocabulary spec, this FEP defines a few additional intents that home severs SHOULD publish, and remote servers MAY implement.</p>"},{"location":"fep/3b86/#51-object-intent","title":"5.1 Object Intent","text":"<p>This intent corresponds to the ActivityStreams Object definition, and is intended as a way for users to open an object from a remote server directly in their home server. This is analogous to copying the URL of the object in question, then pasting the URL into the search box of the user's home server.</p> <p>This intent only opens the object using the user's home server. It does not trigger an ActivityPub activity. Because of this, it does not use the <code>on-success</code> and <code>on-cancel</code> workflows because there is no action to succeed, and no action to cancel.</p>"},{"location":"fep/3b86/#511-parameters","title":"5.1.1. Parameters","text":"<ul> <li><code>{object}</code> - ID of the object that the actor will open in their home server when they use this workflow.</li> </ul>"},{"location":"fep/3b86/#512-example","title":"5.1.2. Example","text":"<pre><code>{\n \"rel\": \"https://w3id.org/fep/3b86/Object\",\n \"template\": \"https://server.org/intents/object?objectId={object}\"\n}\n</code></pre>"},{"location":"fep/3b86/#60-security-considerations","title":"6.0. Security Considerations","text":""},{"location":"fep/3b86/#61-csrf-issues","title":"6.1 CSRF issues","text":"<p>It is important to reiterate some key security practices to prevent Cross Site Request Forgery vulnerabilities.</p> <ul> <li>Remote Servers MUST only send <code>GET</code> requests to Home Servers.</li> <li>Home Servers MUST NOT change data based on GET requests.</li> <li>Home Servers SHOULD protect these published endpoints by generating CSRF tokens and including them with every POST request. This validates that the request originated on the user's home server, and was initiated by the user. See OWASP Related Controls for in-depth discussion.</li> </ul>"},{"location":"fep/3b86/#62-openunvalidated-redirects","title":"6.2 Open/Unvalidated Redirects","text":"<p>Activity Intents includes the ability for home servers to redirect browsers to new URLs based on the <code>on-success</code> and <code>on-cancel</code> query parameters. If improperly implemented, this has the potential to expose an Unvalidated Redirect vulnerability, which can open the home server up to phishing attack. </p> <p>To avoid a potential security vulnerability when handling <code>on-success</code> and <code>on-cancel</code> workflows, it is important that the home server: \"Force all redirects to first go through a page notifying users that they are going off of your site, with the destination clearly displayed, and have them click a link to confirm.\" (owasp.org)</p> <p>There is another good description of this issue OAuth 2.0 Security Best Current Practice \u00a7 4.11. Open Redirection</p>"},{"location":"fep/3b86/#70-remote-servers-the-rest-of-the-equation","title":"7.0. Remote Servers: The Rest of the Equation","text":"<p>This FEP provides the prerequisite information required for a \"Home Server\" publish Activity Intents for its Actors. It does not specify how \"Remote Servers\" will use this information - i.e., how they implement \"share\" and \"like\" buttons in their content.</p>"},{"location":"fep/3b86/#71-remote-server-example","title":"7.1. Remote Server Example","text":"<p>Here is a brief example of the workflow as implemented by a remote server:</p> <ol> <li>A user visits a remote server (i.e., any address on the Internet where they do not have an ActivityPub account)</li> <li>The remote server displays buttons such as \"like\" or \"share\" for a particular document or object.</li> <li>When the user clicks on one of these links, the remote server checks to see if the user is already recognized. This does not necessarily mean logged in, or authenticated, just whether or not the user has entered their home server credentials before. True authentication on the remote server is not necessary for Activity Intents to work.<ol> <li>If the user is not recognized, then they are prompted to enter their Fediverse ID (such as @benpate@mastodon.social). The remote server can save this information for use in the future (preferably in a private data store, such as <code>localStorage</code>)</li> <li>If the user is recognized, this means they have already completed step 3.1, and can continue to step 4.</li> </ol> </li> <li>The remote server uses a standard WebFinger query to look up the user's Fediverse ID and searches for Activity Intents supported by the user's home server.<ol> <li>If none are found, the remote server MAY try to substitute fallback values for known server types.</li> <li>If none are found (and no fallbacks substituted) the remote server SHOULD inform the user that their account is incompatible with the selected feature and halt the workflow.</li> </ol> </li> <li>The remote server replaces values into the designated URI Template and forwards the user to the assigned page on their home server. This initiates the Activity Intent workflow on their home server.</li> <li>When the user completes the workflow, the home server SHOULD use URL in the <code>on-success</code> parameter to redirect the user back to the correct page on the remote server.<ol> <li>Similarly, if the user cancels the workflow, the home server SHOULD use the URL in the <code>on-cancel</code> parameter to redirect the user back to the correct page on the remote server.</li> </ol> </li> </ol>"},{"location":"fep/3b86/#72-fallbacks-for-unpublished-links","title":"7.2. Fallbacks for Unpublished Links","text":"<p>Remote servers MAY also account for applications that do not publish Activity Intents, but whose endpoints are still well known. In this case, remote servers SHOULD use Activity Intents links if they are present, then fall back to older links (such as the oStatus <code>/authorize_interaction</code> endpoint) if they are present, then fall back to hard-coded values (such as the Mastodon <code>/share</code> endpoint) if no other values exist.</p>"},{"location":"fep/3b86/#80-implementations","title":"8.0. Implementations","text":""},{"location":"fep/3b86/#81-home-servers","title":"8.1 Home Servers","text":"<p>This is a list of \"home servers\" that publish Activity Intent endpoints.</p> <ul> <li>Emissary publishes <code>Create</code>, <code>Follow</code>, and <code>Like</code> intents.</li> <li>Forte publishes <code>Create</code> intents.</li> <li>Loops publishes <code>Follow</code> intents.</li> <li>PieFed publishes <code>Create</code> intents.</li> <li>the \"streams\" repository publishes <code>Create</code> intents.</li> <li>WordPress publishes <code>Create</code> and <code>Follow</code> intents</li> <li>Add your name to this list and win a cookie \ud83c\udf6a</li> </ul>"},{"location":"fep/3b86/#82-clients","title":"8.2 Clients","text":"<p>This is a list of client tools that allow end-users to use Activity Intents on remote websites.</p> <ul> <li>Emissary publishes \"share\" and \"like\" buttons</li> <li>Forte and also the \"streams\" repository publish \"wall-to-wall\" post/reply buttons, as an alternative to carrying out these same operations directly on the home server using OpenWebAuth (FEP-61cf). Other interactions in progress. </li> <li>Web Intents library (in progress)</li> </ul>"},{"location":"fep/3b86/#references","title":"References","text":"<ul> <li>Activity Vocabulary</li> <li>Twitter: Web Intents</li> <li>Facebook: Share Button</li> <li>Remote Follows - Tutorial on the original oStatus protocol</li> <li>Cross Site Request Forgery - Wikipedia</li> <li>Cross Site Request Forgery - OWASP</li> <li>FEP-888d - FEP-specific namespaces</li> <li>IETF RFC 7033 - WebFinger</li> <li>IETF RFC 3896 - Uniform Resource Identifier Generic Syntax</li> <li>IEFT RFC 2119 - Key words for use in RFCs to Indicate Requirement Levels</li> <li>IETF RFC 6415 - Web Host Metadata</li> <li>IEFT RFC-6570 - URI Template</li> <li>Unvalidated Redirects and Forwards Cheat Sheet - owasp.org)</li> <li>OAuth 2.0 Security Best Current Practice</li> </ul>"},{"location":"fep/3b86/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/400e/","title":"FEP-400e: Publicly-appendable ActivityPub collections","text":"Authors Status Date received Date final Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me <code>FINAL</code> 2021-02-16 2022-02-04 #5 - codeberg"},{"location":"fep/400e/#summary","title":"Summary","text":"<p>In social media, it's a frequent pattern when there's a collection owned by someone that other people can contribute to. Examples include:</p> <ul> <li>Walls where others can post</li> <li>Forums as well as topics within</li> <li>Photo albums in groups where group members can add photos</li> </ul> <p>Currently, there is no generic way to signify that an object was created as part of a collection and should only be considered in its context.</p> <p>This proposal describes how ActivityPub servers and clients could specify collections to which objects created by their actors belong.</p>"},{"location":"fep/400e/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].</p>"},{"location":"fep/400e/#publicly-appendable-collections","title":"Publicly-appendable collections","text":"<p>A publicly-appendable collection is any collection where it is expected that someone other than its owner could add items but over which its owner retains complete authority. For example, a wall is a collection to which other people could add posts (<code>Note</code>s), but from which its owner could delete any posts as well as restrict who can add them.</p> <p>A publicly-appendable collection SHOULD have a valid and globally-unique <code>id</code> that SHOULD point to either a <code>Collection</code> or an <code>OrderedCollection</code> object that contains the links to all its objects.</p>"},{"location":"fep/400e/#specifying-collections-in-actors","title":"Specifying collections in actors","text":"<p>If an actor has publicly-appendable collections, its server MAY include them as additional fields in its ActivityPub representation. For example, user actors may specify the link to their walls, or groups may specify the link to the collection of their photo albums.</p> <p>Implementations MAY use the presence or absence of specific collection to determine whether the actor's server supports features that depend on that collection and alter their UIs accordingly.</p>"},{"location":"fep/400e/#using-target-in-objects","title":"Using <code>target</code> in objects","text":"<p>If an ActivityPub object is being created as part of a collection, the object SHOULD include the <code>target</code> field that contains an abbreviated collection object, which SHOULD contain at least the following fields:</p> <ul> <li><code>type</code> \u2014 either <code>Collection</code> or <code>OrderedCollection</code>.</li> <li><code>id</code> \u2014 the <code>id</code> of the collection.</li> <li><code>attributedTo</code> \u2014 the <code>id</code> of the owner of the collection. This is necessary to simplify the database design on the receiving side.</li> </ul>"},{"location":"fep/400e/#discussion","title":"Discussion","text":"<p>While [Activity Vocabulary] specifies <code>target</code> as a field with similar semantics in activities, it's important to include it in objects themselves so any software that only sees the object without its enclosing <code>Create</code> activity, e.g. when following a link form another object or retrieving the object from a user-provided URI, unambiguously knows that it should only be considered in the context of its collection.</p>"},{"location":"fep/400e/#adding-an-object-to-a-collection","title":"Adding an object to a collection","text":"<p>When an ActivityPub server receives in its inbox a correctly signed <code>Create</code> activity with an object that has the <code>target</code> field, it does the following:</p> <ul> <li>Retrieve the collection owner using either <code>attributedTo</code> or <code>id</code> fields of the abbreviated collection object.</li> <li>If the collection owner does not exist, or if <code>attributedTo</code> doesn't match the actual owner of the collection specified by <code>id</code>, or if the collection owner is not a local actor, the server SHOULD abort processing and MAY return <code>400 Bad Request</code>.</li> <li>If the object could not be added to the collection, for example due to the privacy settings configured by its owner, the server SHOULD either respond with <code>403 Unauthorized</code> or respond with <code>200 OK</code> and later send a <code>Reject{Create}</code> activity to the originating server.</li> <li>Store either the entire object or its <code>id</code> in its local storage as belonging to the specified collection.</li> <li>Send an <code>Add</code> activity to any parties that might be concerned with it. The <code>target</code> field in the activity SHOULD only be the collection <code>id</code>, and the <code>object</code> field SHOULD be the <code>id</code> of the object being added. It is RECOMMENDED that this activity is sent to all the collection owner's followers for the sake of data consistency, and it SHOULD be sent to the actor that created the object being added.</li> <li>Perform any implementation-specific processing, like sending notifications.</li> </ul>"},{"location":"fep/400e/#receiving-an-add-activity","title":"Receiving an <code>Add</code> activity","text":"<p>When an ActivityPub server receives in its inbox a correctly signed <code>Add</code> activity, it SHOULD do the following:</p> <ul> <li>Retrieve the <code>actor</code> either from local storage or from the network.</li> <li>Retrieve the <code>object</code>.</li> <li>Check that <code>target</code> is the ID of a collection owned by <code>actor</code>. If it is not, abort processing and return <code>400 Bad Request</code>.</li> <li>Check that <code>target</code> in the activity matches <code>target.id</code> in the object and that <code>target.attributedTo</code> in the object matches the actor ID. If it does not, abort processing and return <code>400 Bad Request</code>.</li> <li>Store either the entire object or its <code>id</code> in its local storage as belonging to the specified collection.</li> <li>Perform any implementation-specific processing, like sending notifications.</li> </ul>"},{"location":"fep/400e/#example-of-an-add-activity","title":"Example of an <code>Add</code> activity","text":"<pre><code>{\n \"@context\":\"https://www.w3.org/ns/activitystreams\",\n \"actor\":\"https://example.com/users/1\",\n \"id\":\"https://example.com/posts/41864/activityAdd\",\n \"to\":[\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/1/followers\",\n \"https://example.com/users/6946\"\n ],\n \"type\":\"Add\",\n \"object\":\"https://example.com/posts/41864\",\n \"target\":\"https://example.com/users/1/wall\"\n}\n</code></pre>"},{"location":"fep/400e/#deleting-an-object-from-a-collection","title":"Deleting an object from a collection","text":"<p>Since the collection owner has complete authority over the contents of the collection, they can delete any objects from it. When an object is deleted from a collection by its owner, their server SHOULD send a <code>Delete</code> activity to at least the server of the actor that created the object; it's also RECOMMENDED that this activity is sent to all the servers that <code>Add</code> was sent to. Those servers then SHOULD delete the object as if the deletion was initiated by its creator.</p>"},{"location":"fep/400e/#moving-an-object-between-collections","title":"Moving an object between collections","text":"<p>In some use cases, it might make sense to allow objects to be moved between collections, for example, a group moderator might want to move a photo between photo albums in a group, or a forum moderator might want to split some messages into a separate thread. It's only possible to move objects between collections that are owned by the same actor.</p> <p>When moving an object between collections, the collection owner SHOULD send a <code>Move</code> activity to at least the server of the object creator, specifying the target collection and the <code>id</code> of the object; it's also RECOMMENDED that this activity is sent to all the servers that <code>Add</code> was sent to. Those servers then SHOULD update the <code>target</code> field in their stored copies of the object.</p>"},{"location":"fep/400e/#security-considerations","title":"Security considerations","text":"<p>The requirement of an <code>Add</code> activity sent by a collection owner largely prevents a bad actor from effectively adding something to a collection against the collection owner's will while also helping data consistency across servers. However, there's still one case when this is possible. When a server fetches an object that has a <code>target</code> field directly, for example when a user has entered its URL into a search box or when it is referenced by a field such as <code>inReplyTo</code>, there's presently no reliable way to verify whether the object actually belongs to the collection.</p>"},{"location":"fep/400e/#implementations","title":"Implementations","text":"<p>This proposal is implemented in Smithereen for both user and group walls since the following commit: https://github.com/grishka/Smithereen/commit/de013593dde06a3091ecfbd32960a694d79c146e</p>"},{"location":"fep/400e/#references","title":"References","text":"<ul> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> <li>[Activity Vocabulary], James M Snell, Evan Prodromou, Activity Vocabulary</li> </ul>"},{"location":"fep/400e/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/4adb/","title":"FEP-4adb: Dereferencing identifiers with webfinger","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.com <code>DRAFT</code> 2023-05-13 #100 - codeberg"},{"location":"fep/4adb/#summary","title":"Summary","text":"<p>In this FEP, we will formalize the process of dereferencing an URI using webfinger in order for usage in ActivityPub. The main goal is to enable the usage of URIs of the form <code>acct:user@domain</code> or <code>did:example:12345</code> as ids for objects used in ActivityPub. While this FEP only discusses this in the context of actors, it should be applicable for general objects. In order for a smooth introduction, it is recommended to start deployment with actor objects.</p> <p>This FEP first presents the algorithm and examples, then discusses the usage in the context of the Fediverse. This means the first two sections are for people wanting to implement this FEP, the following sections are for people wanting to decide if this FEP is a good idea.</p>"},{"location":"fep/4adb/#the-algorithm","title":"The algorithm","text":"<p>[Webfinger] allows us to associate an ActivityStreams object with a pair given by an URI and a domain. This is done through the following steps:</p> <ol> <li>Fetch <code>GET https://domain/.well-known/webfinger?resource={URI}</code>.</li> <li>Determine the link property with <code>\"type\":\"application/activity+json\"</code>.</li> <li>Dereference this object.</li> </ol> <p>We will denote this object by <code>ActivityStreams(URI, domain)</code>. There are now two cases to resolve an URI:</p> <ol> <li>The URI determines the domain denoted by <code>domain(URI)</code></li> <li>The URI doesn't determine the domain</li> </ol> <p>In case 1, it is clear that we associate <code>ActivityStreams(URI, domain(URI))</code> to the URI. In case 2, we will use the domain associated with the <code>@id</code> of the document the document the URI appeared in. If the document was received through a POST request and doesn't contain an <code>id</code>, or the <code>id</code> is an URI, the domain the POST request originated from should be used. In this case a verification that the URI can be associated with the object MUST be performed. This can for example be achieved through FEP-c390.</p>"},{"location":"fep/4adb/#examples","title":"Examples","text":"<p>We start with the acct URI Scheme. In order to deliver the activity</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Follow\",\n \"actor\": \"https://chatty.example/ben/\",\n \"object\": \"acct:alyssa@social.example\",\n \"to\": \"acct:alyssa@social.example\",\n}\n</code></pre> <p>the ActivityPub server should perform the lookup for <code>ActivityStreams(\"acct:alyssa@social.example\", \"social.example\")</code>, i.e. the request</p> <pre><code>GET https://social.example/.well-known/webfinger?resource=acct:alyssa@social.example\n</code></pre> <p>We now turn our attention to DIDs. In order to resolve the author of</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://chatty.example/ben/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"to\": \"acct:alyssa@social.example\",\n \"content\": \"Hello\"\n}\n</code></pre> <p>one has to resolve the URI <code>did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5</code>. As this URI does not contain a domain, the domain from the id, i.e. <code>chatty.example</code> is used, so the request</p> <pre><code>GET https://chatty.example/.well-known/webfinger?resource=did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\n</code></pre> <p>is made. The resulting ActivityStreams object should contain</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://chatty.example/bnm789\",\n \"attachment\": {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"alsoKnownAs\": \"https://chatty.social/bnm789\",\n \"proof\": { ... }\n },\n ...\n}\n</code></pre> <p>following FEP-c390 so that the authority of ben to use the did can be verified.</p>"},{"location":"fep/4adb/#background","title":"Background","text":"<p>Let's start with Section 3.1 of ActivityPub. It starts with the sentence</p> <p>All Objects in [ActivityStreams] should have unique global identifiers.</p> <p>We first note that the word unique does not appear in [ActivityStreams]. Second it is unclear why it is there and what it means:</p> <ul> <li>Does it mean that there is exactly one identifier associated with the object?</li> <li>Does it mean that the global identifier is only used for one object?</li> </ul> <p>We will assume that it is the second interpretation. In particular, this means that we can associated multiple identifiers with the same ActivityStreams object.</p> <p>Next comes the following line in [ActivityPub]</p> <p>Publicly dereferencable URIs, such as HTTPS URIs, with their authority belonging to that of their originating server. (Publicly facing content SHOULD use HTTPS URIs).</p> <p>The essential point of this FEP is to extend the range of publicly dereferencable URIs to contain basically any URI by using [Webfinger].</p> <p>The current usage of Webfinger in the Fediverse is asymmetric. As discussed in MastoGuide, one can associate many URIs of the form <code>acct:user@domain.tld</code> with the same Actor, by just making webfinger return an appropriate response. However, only one acct-URI can be associated with an Actor. This is done by:</p> <pre><code>acct:{preferredUsername}@{domain of actor id}\n</code></pre> <p>where <code>preferredUsername</code> is from the actor object.</p>"},{"location":"fep/4adb/#applications-to-the-actor-object","title":"Applications to the Actor Object","text":"<p>We now discuss applications of the approach of this FEP to the actor object.</p>"},{"location":"fep/4adb/#preferred-account","title":"Preferred Account","text":"<p>We will follow this suggestion in [alsoKnownAs and acct:]. The main idea is to associate multiple accounts. For this, we will return to <code>alyssa@social.example</code>, whose Actor object would look like the following omitting irrelevant details</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/abc123\",\n \"preferredUsername\": \"alyssa\",\n ...\n}\n</code></pre> <p>By the algorithm discussed in background, we can associate the URI <code>acct:alyssa@social.example</code> with this. Suppose now that Alyssa owns the domain <code>alyssa.cool</code>, and set up WebFinger to answer to</p> <pre><code>GET https://alyssa.cool/.well-known/webfinger?resource=me@alyssa.cool\n</code></pre> <p>with a link to the above actor. Alyssa would then be disappointed because Fediverse software is not displaying her cooler username. To remedy this, we propose the addition to the actor object</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"xrd\": \"http://docs.oasis-open.org/ns/xri/xrd-1.0#\",\n \"aliases\": {\n \"@id\": \"xrd:Alias\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n },\n }\n ],\n \"id\": \"https://social.example/abc123\",\n \"preferredUsername\": \"alyssa\",\n \"aliases\": [\"acct:me@alyssa.cool\", \"acct:alyssa@social.example\"],\n ...\n}\n</code></pre> <p>a new generation of Fediverse software will then be able to display the account of Alyssa as <code>me@alyssa.cool</code>.</p>"},{"location":"fep/4adb/#dns-names","title":"DNS Names","text":"<p>Continuing with Alyssa from above, by relying on FEP-612d, she may even associate <code>@alyssa.cool</code> with her account. Unfortunately, a proper URI format is still missing.</p>"},{"location":"fep/4adb/#using-didkey","title":"Using did:key","text":"<p>Let's return to Ben and him wanting to use a decentralized identifier, see did-core and did-method-key</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"xrd\": \"http://docs.oasis-open.org/ns/xri/xrd-1.0#\",\n \"aliases\": {\n \"@id\": \"xrd:Alias\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n },\n }\n ],\n \"id\": \"https://chatty.social/bnm789\",\n \"preferredUsername\": \"ben\",\n \"aliases\": [\"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\", \"acct:ben@chatty.social\"],\n ...\n}\n</code></pre> <p>The problem here is that, we do not know if the the did-key belongs to BEN. To remedy this, we will use FEP-c390. This means that we need to add</p> <pre><code> \"attachment\": {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"alsoKnownAs\": \"https://chatty.social/bnm789\",\n \"proof\": { ... }\n }\n</code></pre> <p>with an appropriate proof to Ben's actor object.</p>"},{"location":"fep/4adb/#references","title":"References","text":"<ul> <li>[acct URI Scheme] P. Saint-Andre RFC 7565, 2015</li> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[ActivityStreams] J. Snell, E. Prodromou, ActivityStreams, 2017</li> <li>alsoKnownAs and acct: on SocialHub</li> <li>[did-core] Manu Sporny, Dave Longley, Markus Sabadell, Drummond Reed, Orie Steele, Christopher Allen, Decentralized Identifiers (DIDs) v1.0, 2022</li> <li>[did-method-key], Dave Longley, Dmitri Zagidulin, Manu Sporny, did-method-key 20221</li> <li>[FEP-c390] silverpill, FEP-c390, 2022</li> <li>[FEP-612d] Helge, FEP-612d 2023</li> <li>[MastoGuide] jippi Use your own user @ domain for Mastodon discoverability</li> <li>[Webfinger] P. Jones, G. Salgueiro, M. Jones, J. Smarr, RFC 7033,2013</li> </ul>"},{"location":"fep/4adb/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/4ccd/","title":"FEP-4ccd: Pending Followers Collection and Pending Following Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.name <code>DRAFT</code> 2023-06-21 #129 Discussions codeberg"},{"location":"fep/4ccd/#summary","title":"Summary","text":"<p>This ActivityPub extension defines two collections, <code>pendingFollowers</code> and <code>pendingFollowing</code>, with which users can review and manage their pending follow requests.</p>"},{"location":"fep/4ccd/#motivation","title":"Motivation","text":"<p>ActivityPub represents a directed social graph with <code>followers</code> and <code>following</code> properties on actors. To initiate a relationship, a <code>Follow</code> activity is sent from the potential follower to the followed actor, who can <code>Accept</code> or <code>Reject</code> it.</p> <p>Between the time that a <code>Follow</code> is sent and the time that it is accepted or rejected, the relationship is in a pending state. This is not represented in the ActivityPub data model.</p> <p>The new <code>pendingFollowers</code> collection can be used to review incoming <code>Follow</code> activities to <code>Accept</code> or <code>Reject</code> them.</p> <p>The new <code>pendingFollowing</code> collection can be used to review outgoing <code>Follow</code> activities to <code>Undo</code> them.</p> <p>Because the full activity data is needed to <code>Accept</code>, <code>Reject</code> or <code>Undo</code>, these collections should include <code>Follow</code> activities, and not just the actors requesting to follow.</p> <p>Note that this extension is primarily useful for clients and servers that implement the ActivityPub API. Furthermore, it is primarily useful for actors that manually approve followers. The manuallyApprovesFollowers property in the ActivityPub Miscellaneous Terms can be used to indicate that an actor manually approves followers.</p>"},{"location":"fep/4ccd/#context","title":"Context","text":"<p>The context document for this ActivityPub extension is at <code>https://purl.archive.org/socialweb/pending</code>. Its contents are as follows:</p> <pre><code>{\n \"@context\": {\n \"pdg\": \"https://purl.archive.org/socialweb/pending#\",\n \"pendingFollowers\": {\n \"@id\": \"pdg:pendingFollowers\",\n \"@type\": \"@id\"\n },\n \"pendingFollowing\": {\n \"@id\": \"pdg:pendingFollowing\",\n \"@type\": \"@id\"\n },\n \"pendingFollowersOf\": {\n \"@id\": \"pdg:pendingFollowersOf\",\n \"@type\": \"@id\"\n },\n \"pendingFollowingOf\": {\n \"@id\": \"pdg:pendingFollowingOf\",\n \"@type\": \"@id\"\n }\n }\n}\n</code></pre>"},{"location":"fep/4ccd/#semantic-versioning","title":"Semantic versioning","text":"<p>For compatibility, the context document for this extension has aliases using semantic versioning.</p> <ul> <li><code>https://purl.archive.org/socialweb/pending/1.1.0</code>. This version is immutable.</li> <li><code>https://purl.archive.org/socialweb/pending/1.1</code>. This version may be updated with bug fixes, documentation, or minor changes, but no new terms.</li> <li><code>https://purl.archive.org/socialweb/pending/1</code>. This version may be updated with bug fixes or minor changes, and may include new terms, but will not include breaking changes.</li> <li><code>https://purl.archive.org/socialweb/pending</code>. This version may be updated with bug fixes or minor changes, may include new terms, and may include breaking changes. It is the latest version of the context document.</li> </ul> <p>All terms in the context document use the same namespace, <code>https://purl.archive.org/socialweb/pending#</code>.</p>"},{"location":"fep/4ccd/#properties","title":"Properties","text":"<p>As with other ActivityPub properties, the values of these properties can be included by a reference URL, or by an embedded node object.</p>"},{"location":"fep/4ccd/#pendingfollowers","title":"<code>pendingFollowers</code>","text":"URI <code>https://purl.archive.org/socialweb/pending#pendingFollowers</code> Notes <code>pendingFollowers</code> is a collection of <code>Follow</code> activities that have been sent to the actor, but have not yet been accepted or rejected. Items in the collection MUST be in reverse chronological order. Items in the collections MUST be <code>Follow</code> activities. They MUST be unique by <code>id</code>. Each <code>actor</code> of a <code>Follow</code> activity in the collection MUST be unique by <code>id</code>. Domain Object (ActivityPub actor) Range <code>OrderedCollection</code> or <code>Collection</code> Functional Yes"},{"location":"fep/4ccd/#pendingfollowing","title":"<code>pendingFollowing</code>","text":"URI <code>https://purl.archive.org/socialweb/pending#pendingFollowing</code> Notes <code>pendingFollowing</code> is a property of an actor. It is a collection of <code>Follow</code> activities that have been sent by the actor, but have not yet been accepted or rejected. They MUST be unique by <code>id</code>. Each <code>object</code> of a <code>Follow</code> activity in the collection MUST be unique by <code>id</code>. Domain Object (ActivityPub actor) Range <code>OrderedCollection</code> or <code>Collection</code> Functional Yes"},{"location":"fep/4ccd/#pendingfollowersof","title":"<code>pendingFollowersOf</code>","text":"URI <code>https://purl.archive.org/socialweb/pending#pendingFollowersOf</code> Notes This property identifies the actor for which the specified collection is the <code>pendingFollowers</code> collection. It is an inverse property of <code>pendingFollowers</code>. Domain <code>Collection</code> or <code>OrderedCollection</code> Range Object (ActivityPub actor) Functional Yes"},{"location":"fep/4ccd/#pendingfollowingof","title":"<code>pendingFollowingOf</code>","text":"URI <code>https://purl.archive.org/socialweb/pending#pendingFollowingOf</code> Notes This property identifies the actor for which the specified collection is the <code>pendingFollowing</code> collection. It is an inverse property of <code>pendingFollowing</code>. Domain <code>Collection</code> or <code>OrderedCollection</code> Range Object (ActivityPub actor) Functional Yes"},{"location":"fep/4ccd/#examples","title":"Examples","text":"<p>A publisher can include the <code>pendingFollowers</code> and <code>pendingFollowing</code> collection in the properties of an actor.</p>"},{"location":"fep/4ccd/#actor-with-pendingfollowers-and-pendingfollowing","title":"Actor with <code>pendingFollowers</code> and <code>pendingFollowing</code>","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"pendingFollowers\": \"https://example.com/evanp/pendingFollowers\",\n \"pendingFollowing\": {\n \"id\": \"https://example.com/evanp/pendingFollowing\",\n \"type\": \"Collection\",\n \"name\": \"Pending following for Evan Prodromou\",\n \"totalItems\": 2\n },\n \"manuallyApprovesFollowers\": true,\n \"to\": [\"as:Public\"]\n}\n</code></pre> <p>Notice that the <code>pendingFollowers</code> property is a URL, while the <code>pendingFollowing</code> property is an embedded object with useful additional properties.</p>"},{"location":"fep/4ccd/#pendingfollowers-collection","title":"<code>pendingFollowers</code> collection","text":"<p>Retrieving the <code>pendingFollowers</code> collection shows incoming follow requests for this actor.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\"\n ],\n \"id\": \"https://example.com/evanp/pendingFollowers\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"pendingFollowersOf\": \"https://example.com/evanp\",\n \"name\": \"Pending followers for Evan Prodromou\",\n \"orderedItems\": [\n {\n \"type\": \"Follow\",\n \"id\": \"https://example.net/alyssa/follow/7\",\n \"summary\": \"Alyssa wants to follow Evan\",\n \"content\": \"Hey, Evan! It's Alyssa from the conference.\",\n \"actor\": {\n \"id\": \"https://example.net/alyssa\",\n \"type\": \"Person\",\n \"name\": \"Alyssa P. Hacker\"\n },\n \"to\": \"https://example.com/evanp\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-06-21T12:00:00Z\"\n },\n {\n \"type\": [\"http://custom.example/ns/Archive\", \"Follow\"],\n \"id\": \"https://social.example/jokebot3000/follow/287\",\n \"summary\": \"Jokebot 3000 wants to follow Evan to archive his jokes\",\n \"actor\": {\n \"id\": \"https://social.example/jokebot3000\",\n \"type\": \"Application\",\n \"name\": \"Jokebot 3000\"\n },\n \"to\": \"https://example.com/evanp\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-05-07T12:00:00Z\"\n }\n ]\n}\n</code></pre> <p>Note that the second, earlier <code>Follow</code> activity has a custom <code>type</code> property. Note also that the <code>object</code> of the <code>Follow</code> activities, which will be the same for every activity, is elided for clarity and space.</p>"},{"location":"fep/4ccd/#pendingfollowing-collection","title":"<code>pendingFollowing</code> collection","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\",\n {\"sports\": \"https://sports.example/ns#\"}\n ],\n \"id\": \"https://example.com/evanp/pendingFollowing\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"pendingFollowingOf\": \"https://example.com/evanp\",\n \"name\": \"Pending following for Evan Prodromou\",\n \"items\": [\n {\n \"type\": [\"sports:Fan\", \"Follow\"],\n \"id\": \"https://example.com/evanp/fan/309\",\n \"summary\": \"Evan is a fan of Jimena\",\n \"actor\": \"https://example.com/evanp\",\n \"object\": {\n \"id\": \"https://tennis.example/jimena\",\n \"type\": \"Person\",\n \"name\": \"Jimena Suarez\"\n },\n \"to\": \"https://tennis.example/jimena\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-04-19T12:00:00Z\"\n },\n {\n \"type\": \"Follow\",\n \"id\": \"https://example.net/evanp/follow/214\",\n \"summary\": \"Evan wants to follow Montreal Weather Updates\",\n \"actor\": \"https://example.com/evanp\",\n \"object\": {\n \"id\": \"https://weather.example/canada/quebec/montreal\",\n \"type\": \"Service\",\n \"name\": \"Montreal Weather Updates\"\n },\n \"to\": \"https://weather.example/canada/quebec/montreal\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-02-11T12:00:00Z\"\n }\n ]\n}\n</code></pre> <p>Note that the first <code>Follow</code> activity has a custom <code>type</code> property. Also note that even though the collection's type is <code>Collection</code> and the items property is <code>items</code>, the activities still must be in reverse chronological order.</p>"},{"location":"fep/4ccd/#processing-requirements","title":"Processing requirements","text":"<p>An actor that manually approves followers SHOULD include the <code>manuallyApprovesFollowers</code> property in their actor object, with a value of <code>true</code>. This indicates that the actor will review and approve or reject incoming follow requests.</p> <p>When a server receives an otherwise valid <code>Follow</code> activity from a client, it SHOULD add that activity to the <code>pendingFollowing</code> collection of the sending actor. The server SHOULD also add the <code>Follow</code> activity to the <code>pendingFollowers</code> collection of the actor that is being followed, if the followed actor is on the same server.</p> <p>When a server receives an otherwise valid <code>Follow</code> activity from another server, it SHOULD add that activity to the <code>pendingFollowers</code> collection of the followed actor.</p> <p>When a server receives an <code>Accept</code> or <code>Reject</code> activity with a <code>Follow</code> activity as <code>object</code> from a client, it SHOULD remove that <code>Follow</code> activity from the <code>pendingFollowers</code> collection of the actor. The server SHOULD also remove the <code>Follow</code> activity from the <code>pendingFollowing</code> collection of the actor that initiated the follow, if the following actor is on the same server.</p> <p>When a server receives an <code>Accept</code> or <code>Reject</code> activity with a <code>Follow</code> activity as <code>object</code> from another server, it SHOULD remove that <code>Follow</code> activity from the <code>pendingFollowing</code> collection of the receiving actor.</p> <p>When a server receives an <code>Undo</code> activity with a <code>Follow</code> activity as <code>object</code> from a client, it SHOULD remove that <code>Follow</code> activity from the <code>pendingFollowing</code> collection of the actor that sent the <code>Follow</code>. It SHOULD also remove the <code>Follow</code> activity from the <code>pendingFollowers</code> collection of the actor that was followed, if the followed actor is on the same server.</p> <p>When a server receives an <code>Undo</code> activity with a <code>Follow</code> activity as <code>object</code> from another server, it SHOULD remove that <code>Follow</code> activity from the <code>pendingFollowers</code> collection of the receiving actor.</p> <p>The following Harel statechart illustrates the state transitions for a <code>Follow</code> activity between actors A1 and A2. The states show which objects belong to which collections, and the transitions show when activities are received and processed via the client API and then the server federation protocol.</p> <p></p>"},{"location":"fep/4ccd/#security-considerations","title":"Security considerations","text":"<p>The <code>pendingFollowers</code> and <code>pendingFollowing</code> collections are sensitive information about an actor's social connections. For privacy, some services and actors do not share the <code>following</code> or <code>followers</code> collections. If not similarly protected, the <code>pendingFollowers</code> and <code>pendingFollowing</code> collections could be used to infer information about the actor's social connections before they are established.</p> <p>Some services or actors do not forward <code>Reject</code> activities to the actor of a <code>Follow</code> activity. Harassing or abusive actors may try to determine if the actor has rejected their follow request by fetching the <code>pendingFollowers</code> collection.</p> <p>For these reasons, publishers SHOULD NOT make the <code>pendingFollowers</code> and <code>pendingFollowing</code> collections visible to unauthenticated users. Publishers SHOULD NOT make the <code>pendingFollowers</code> and <code>pendingFollowing</code> collections visible to authenticated users who are not the actor.</p>"},{"location":"fep/4ccd/#implementations","title":"Implementations","text":"<ul> <li>onepage.pub is a simple ActivityPub server that implements the <code>pendingFollowers</code> and <code>pendingFollowing</code> collections.</li> <li>ap is a command-line ActivityPub client. It has commands to list and manage the <code>pendingFollowers</code> and <code>pendingFollowing</code> collections.</li> </ul>"},{"location":"fep/4ccd/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Evan Prodromou, ActivityPub Miscellaneous Terms, 2023</li> <li>Tom Preston-Werner, Semantic Versioning 2.0.0, 2017</li> </ul>"},{"location":"fep/4ccd/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/4f05/","title":"Index","text":"<p>hide: - navigation description: \"The standard CRUD (Create, Read, Update, Delete) behaviours in ActivityPub specify a single Delete activity for use in all cases. This is insufficient to describe two-stage deletion, often referred to as \"soft\" and \"hard\" deletion. Not all software implements two-stage deletion, and so the behaviours described here progressively enhance the functionality for those supporting it, while retaining backward compatibility otherwise.\"</p>"},{"location":"fep/4f05/#fep-4f05-soft-deletion","title":"FEP-4f05: Soft Deletion","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org, Angus McLeod angus@pavilion.tech <code>DRAFT</code> 2025-04-15 #554 Discussions codeberg"},{"location":"fep/4f05/#summary","title":"Summary","text":"<p>The standard CRUD (Create, Read, Update, Delete) behaviours in ActivityPub specify a single <code>Delete</code> activity for use in all cases. This is insufficient to describe two-stage deletion, often referred to as \"soft\" and \"hard\" deletion.</p> <p>Not all software implements two-stage deletion, and so the behaviours described here progressively enhance the functionality for those supporting it, while retaining backward compatibility otherwise.</p>"},{"location":"fep/4f05/#assumptions","title":"Assumptions","text":"<p>A blog post by kaniini advocates for the treatment of copies of remote data as a cached representation. From there we derive the following assumptions:</p> <ul> <li>data living on the remote server is considered canonical.</li> <li>an incoming <code>Delete</code> activity should be treated as a request to refresh the locally cached copy or delete it otherwise.</li> </ul> <p>The Forums and Threaded Discussions Task Force (ForumWG) has identified a common nomenclature when referring to organized objects in a threaded discussion model.</p> <ul> <li>this FEP assumes the items in question are objects, although the concept described can apply to contexts as well.</li> </ul>"},{"location":"fep/4f05/#publishers","title":"Publishers","text":""},{"location":"fep/4f05/#soft-deletion","title":"Soft deletion","text":"<p>When an object is soft deleted, the object's ActivityPub representation MUST be updated to <code>Tombstone</code>. Servers SHOULD continue to respond to requests for the object with a 200-level response code; the object continues to exist in-place.</p> <p>A <code>Delete</code> activity SHOULD be published in order to propagate the soft deletion to other servers.</p>"},{"location":"fep/4f05/#hard-deletion","title":"Hard deletion","text":"<p>When an object is hard deleted, the object MUST no longer have an ActivityPub representation. Servers MUST respond to requests for the object with a 400-level response code. A <code>404 Not Found</code> is acceptable, although a <code>410 Gone</code> sends a more explicit signal that the object was explicitly removed. Security or privacy considerations may affect your decision to send anything more than a 404.</p> <p>A <code>Delete</code> activity MUST be published in order to propagate the hard deletion to other servers.</p>"},{"location":"fep/4f05/#receivers","title":"Receivers","text":"<p>When a <code>Delete</code> activity is encountered, the referenced <code>object</code> MAY be either the full object or a reference to one.</p> <p>Verifying authenticity of the embedded object is out of scope of this FEP. Follow the origin-based security model for verifying authenticity of any received <code>Delete</code> activity.</p> <p>If <code>object</code> is a reference, the server MUST request the object (via its <code>id</code>) from the origin server directly.</p> <p>Handle the received activity as below based on the received response code or object <code>type</code>.</p> <p>Note</p> <p>The <code>actor</code> may not match the <code>attributedTo</code> of the targeted object. This is allowed as moderators or privileged users may carry out deletions.</p>"},{"location":"fep/4f05/#tombstone","title":"<code>Tombstone</code>","text":"<p>The local object SHOULD be soft deleted as per the local implementor's standard behaviour.</p>"},{"location":"fep/4f05/#not-a-tombstone","title":"Not a <code>Tombstone</code>","text":"<p>Update the object's local representation if applicable.</p>"},{"location":"fep/4f05/#http-404-or-410","title":"HTTP 404 or 410","text":"<p>The local object SHOULD be hard deleted as per the local implementor's standard behaviour.</p>"},{"location":"fep/4f05/#unexpected-responses","title":"Unexpected responses","text":"<p>The section above (\"Receivers\") details how a received <code>Delete</code> activity is handled. If during the course of the backreference check the object type or response code differs from expectations, the retrieved state supercedes the activity.</p> <p>e.g. A <code>Delete</code> is received, but a backreference check returns a <code>200</code> with a <code>Note</code>-type object. Therefore it is \"Not a <code>Tombstone</code>\" even though the activity received stated otherwise.</p> <p>The inverse also applies.</p> <p>e.g. An <code>Undo(Delete)</code> is received, but a backreference check returns a <code>Tombstone</code>. There fore it is still treated as a <code>Tombstone</code> even though the activity received stated otherwise.</p>"},{"location":"fep/4f05/#additional-considerations","title":"Additional Considerations","text":""},{"location":"fep/4f05/#update-activity","title":"Update activity","text":"<p>An earlier implementation of two-stage object deletion published an <code>Update(Tombstone)</code>, but this approach was deemed superfluous as it signified the same effect as a <code>Delete</code>\u2014to proceed with a cache invalidation and update.</p>"},{"location":"fep/4f05/#broad-support-or-lack-thereof","title":"Broad support (or lack thereof)","text":"<p>It is safe to assume the majority of ActivityPub-enabled software does not support two-stage object deletion. Publishing a <code>Delete</code> ensures that the intended behaviour of a soft deletion\u2014that the object's content is no longer visible\u2014is carried through to other servers.</p>"},{"location":"fep/4f05/#implementor-ux","title":"Implementor UX","text":"<p>Implementors are free to handle a soft deletion in the way they prefer (e.g. NodeBB will continue to associate the post (object) with the original actor, and simply blank out the content for non-privileged users.) This FEP explicitly does not specify how individual implementors should handle local representations of remote data.</p>"},{"location":"fep/4f05/#who-to-address","title":"Who to address","text":"<p>The recipients list of the published <code>Delete</code> activity is outside the scope of this document.</p>"},{"location":"fep/4f05/#implementors","title":"Implementors","text":"<ul> <li>NodeBB</li> <li>Discourse</li> </ul>"},{"location":"fep/4f05/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>kaniini, The Delete Activity And It's Misconceptions, 2019 (archive)</li> <li>Julian Lam, ForumWG Nomenclature, 2024</li> <li>silverpill, FEP-fe34: Origin-based security model, 2024</li> </ul>"},{"location":"fep/4f05/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/521a/","title":"FEP-521a: Representing actor's public keys","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>FINAL</code> implementation 2023-07-08 2025-06-14 #130 Discussions codeberg"},{"location":"fep/521a/#summary","title":"Summary","text":"<p>This proposal describes how to represent public keys associated with ActivityPub actors.</p>"},{"location":"fep/521a/#rationale","title":"Rationale","text":"<p>Historically, Fediverse services used publicKey property to represent actor's public key. Implementations usually allow only one key per actor, therefore a new approach is needed to support use cases where additional keys are required.</p> <p>Furthermore, <code>publicKey</code> property is considered deprecated and is not present in the latest version of Security Vocabulary.</p>"},{"location":"fep/521a/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/521a/#multikey","title":"Multikey","text":"<p>Each public key MUST be represented as an object with <code>Multikey</code> type, as defined in section 2.2.2 Multikey of Controlled Identifiers specification. This object MUST have the following properties:</p> <ul> <li><code>id</code>: the unique global identifier of the public key.</li> <li><code>type</code>: the value of this property MUST be the string <code>Multikey</code>.</li> <li><code>controller</code>: the value of this property MUST match actor ID.</li> <li><code>publicKeyMultibase</code>: a Multibase encoded value of a Multicodec prefix and the public key. Implementations MUST use the <code>base-58-btc</code> alphabet.</li> </ul>"},{"location":"fep/521a/#key-ids","title":"Key IDs","text":"<p>Key identifiers MUST be absolute URIs.</p> <p>Identifiers SHOULD be generated by appending a fragment identifier to the actor ID. That allows consumers to retrieve both actor object and associated keys with a single HTTP request. If actor's keys are identified using fragment identifiers, each key MUST have a unique fragment identifier.</p> <p>Resolution of a URI containing a fragment identifier is performed using the algorithm specified in section 3.4 Fragment Resolution of Controlled Identifiers specification.</p>"},{"location":"fep/521a/#key-types","title":"Key types","text":"<p>Implementers can use cryptographic keys of any type for which a Multicodec prefix is registered.</p>"},{"location":"fep/521a/#adding-keys-to-an-actor-object","title":"Adding keys to an actor object","text":"<p><code>Multikey</code> objects are added to the actor object, which is considered a controlled identifier document, as described in Controlled Identifiers specification.</p> <p>If the key is intended to be used for signing ActivityPub objects, it MUST be added to the <code>assertionMethod</code> array in the actor object.</p> <p>Other use cases are out of scope of this proposal.</p> <p>Implementations are discouraged from adding any objects to the <code>assertionMethod</code> array that do not conform to this specification. Implementations encountering non-conformant entries in the <code>assertionMethod</code> array SHOULD ignore them.</p> <p>An actor MAY have no associated public keys.</p>"},{"location":"fep/521a/#example","title":"Example","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/cid/v1\"\n ],\n \"type\": \"Person\",\n \"id\": \"https://server.example/users/alice\",\n \"inbox\": \"https://server.example/users/alice/inbox\",\n \"outbox\": \"https://server.example/users/alice/outbox\",\n \"assertionMethod\": [\n {\n \"id\": \"https://server.example/users/alice#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://server.example/users/alice\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/521a/#security-considerations","title":"Security considerations","text":"<p>Each actor is expected to have a different secret key.</p> <p>To prevent side-channel attacks, secret keys need to be stored in binary form. If stored as text, they need to be decoded using a constant-time algorithm.</p> <p>Warning</p> <p>Controlled Identifiers specification requires implementers to use the <code>base-58-btc</code> alphabet for multibase-encoded secret keys. This practice is discouraged because available implementations of Base58 decode algorithm may not be constant-time.</p>"},{"location":"fep/521a/#test-vectors","title":"Test vectors","text":"<p>See fep-521a.feature</p>"},{"location":"fep/521a/#implementations","title":"Implementations","text":"<ul> <li>Mitra</li> <li>streams</li> <li>Hubzilla</li> <li>Fedify</li> <li>tootik</li> <li>squidcity</li> </ul>"},{"location":"fep/521a/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>Ivan Herman, Manu Sporny, Dave Longley, Security Vocabulary, 2023</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Dave Longley, Manu Sporny, Markus Sabadello, Drummond Reed, Orie Steele, Christopher Allen, Controlled Identifiers v1.0, 2025</li> <li>Protocol Labs, Multicodec</li> <li>T. Berners-Lee, R. Fielding, L. Masinter, Uniform Resource Identifier (URI): Generic Syntax, 2005</li> </ul>"},{"location":"fep/521a/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/521a/fep-521a-test-case/","title":"Actor Objects must express signing key as assertionMethod Multikey","text":""},{"location":"fep/521a/fep-521a-test-case/#background","title":"Background","text":"<p>FEP-521a defines how Actor object MUST express the public key for its signing key in the <code>assertionMethod</code> property, as a Multikey object of a given shape and <code>id</code> URI shape.</p>"},{"location":"fep/521a/fep-521a-test-case/#about-this-test","title":"About this Test","text":"<p>This is a Test Case testing conformance with the one behavior specified in FEP-521a.</p>"},{"location":"fep/521a/fep-521a-test-case/#test-subject","title":"Test Subject","text":"<p>The subject of this test is any data claiming to conform to the specification of an ActivityPub Actor Object and to the FEP-521a extension thereof.</p> <p>This test is not directly applicable to an ActivityPub Server. An ActivityPub Server serves 0 or more Actor Objects. An ActivityPub Server for a big community might serve hundreds of ActivityPub Actor Objects. An ActivityPub Server for a single human may serve only that person's ActivityPub Actor Object.</p> <p>This test applies to Actor Objects, but not all Actor Objects are addressable by an HTTPS URL. The URI that addresses an Actor Object is not the same as the Actor Object. A given URL may resolve to different Actor Objects in different contexts, and a given Actor Object may not be universally addressable across context by any one URL.</p>"},{"location":"fep/521a/fep-521a-test-case/#inputs","title":"Inputs","text":"<p>This test requires the following inputs:</p> <ul> <li><code>actor</code> - the actor object under tested</li> <li>type: binary data</li> <li>constraints<ul> <li>will be interpreted as JSON.</li> <li>If not parseable as JSON, the test result MUST be <code>inapplicable</code>.</li> <li>must be an actor as per core AP test e7ee491d-88d7-4e67-80c8-f74781bb247c</li> <li>i.e. has inbox and outbox</li> <li>If not actor, the test result outcome MUST be <code>inapplicable</code>.</li> <li>must contain a non-empty <code>assertionMethod</code> array</li> <li>if does not, the test result outcome MUST be <code>inapplicable</code>.</li> </ul> </li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#applicability","title":"Applicability","text":"<p>This test applies directly to the <code>actor</code> input.</p> <ul> <li>If <code>actor</code> is not an Actor Object, the outcome MUST be <code>inapplicable</code>.</li> <li>If <code>actor</code> is not a JSON object, the outcome MUST be <code>inapplicable</code>.</li> <li>If <code>actor</code> JSON does not have a <code>type</code> property, the outcome MUST be <code>inapplicable</code>.</li> <li>If <code>actor</code> JSON does not have an <code>assertionMethod</code> property, the outcome MUST be <code>inapplicable</code>.</li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#test-targets","title":"Test Targets","text":"<ul> <li>each entry in <code>assertionMethod</code> array is a distinct test target, referred to below as <code>assertionMethod[x]</code></li> <li>each entry should be a JSON object; each entry that is not is inapplicable (warning)</li> <li>each entry not typed as <code>Multikey</code> is inapplicable (warning)</li> <li>each entry typed as <code>Multikey</code> passes or fails the tests of its validity as a Multikey</li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#expectations","title":"Expectations","text":"<ol> <li><code>assertionMethod[x].id</code> - MUST be a string</li> <li><code>assertionMethod[x].controller</code> - MUST match the <code>id</code> property, if present, of the <code>assertionMethod</code> array's parent (i.e., the Actor object)</li> <li><code>assertionMethod[x].publicKeyMultibase</code> - MUST be a base58btc-encoded and appropriately-prefixed (in this case, beginning with <code>z</code>) expression of a binary public key expression</li> </ol>"},{"location":"fep/521a/fep-521a-test-case/#assumptions","title":"Assumptions","text":""},{"location":"fep/521a/fep-521a-test-case/#1-how-to-determine-whether-actor-object-is-expressing-a-key-in-the-fep-defined-shape","title":"1. How to Determine Whether Actor Object is expressing a key in the FEP-defined shape","text":"<p>For the purposes of determining whether the input <code>actor</code> is expressing a key in the shape defined by the FEP:</p> <ul> <li>the input is valid JSON</li> <li>the input, once parsed as JSON</li> <li>has an <code>assertionMethod</code> property, containing an array of 0 or more objects</li> <li>if present, each object therein...<ul> <li>has an <code>id</code> property, containing a string</li> <li>has a <code>type</code> property, containing a string</li> <li>has a <code>controller</code> property, containing a string</li> <li>...has a <code>publicKeyMultibase</code> property, containing a string</li> </ul> </li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#2-property-value-expectations","title":"2. Property value expectations","text":"<p>For the purposes of determining whether the input <code>actor</code> is expressing 1 or more keys all the following must be true:</p> <ul> <li>the input is valid JSON</li> <li>each member of the <code>assertionMethod</code> array, once parsed as JSON, ...</li> <li>has an <code>id</code> property, which is a URI</li> <li>has an <code>assertionMethod.id</code> property, which is a string unique within the actor object</li> <li>has an <code>assertionMethod.type</code> property whose value is the string \"Multikey\" or an Array containing the string \"Multikey\"</li> <li>has an <code>assertionMethod.controller</code> property whose value is a string identical to the parent object's <code>id</code> property value</li> <li>has an <code>assertionMethod.publicKeyMultibase</code> property equal to a string of base58btc-alphabet characters, i.e. matching the regular expression <code>[1-9A-HJ-NP-Za-km-z]+</code></li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#test-cases","title":"Test Cases","text":"<p>These are test cases for this test case, and can be used to verify that an implementation of this test case specification will be consistent with other implementations.</p>"},{"location":"fep/521a/fep-521a-test-case/#missing-assertionmethod","title":"Missing assertionMethod","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n</code></pre> <p>test result</p> <ul> <li>outcome: <code>inapplicable</code></li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#misshapen-assertionmethod-array","title":"Misshapen assertionMethod Array","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n}\n</code></pre> <p>test targets</p> <ul> <li>none</li> <li>outcome: <code>inapplicable</code>, warning (malformed assertionMethod array)</li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#misshapen-assertionmethod-members","title":"Misshapen assertionMethod Members","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": [{\n \"inappropriateKey\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }]\n}\n</code></pre> <p>test targets</p> <ul> <li>none</li> <li>outcome: <code>inapplicable</code>, warning (malformed assertionMethod member)</li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#malformed-publickeymultibase-value","title":"Malformed publicKeyMultibase Value","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": {[\n \"id\": \"https://example.com/#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://example.com/\",\n \"publicKeyMultibase\": \"6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n ]}\n}\n</code></pre> <p>test targets</p> <ul> <li>actor.assertionMethod[0]</li> <li>outcome: <code>inapplicable</code>, warning (malformed assertionMethod member)</li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#valid-actor","title":"Valid Actor","text":"<p>input</p> <ul> <li> <p><code>actor</code>:</p> <pre><code>{\n\"type\": \"Person\",\n\"inbox\": \"https://example.com/inbox\",\n\"outbox\": \"https://example.com/outbox\",\n\"id\": \"https://example.com/\",\n\"assertionMethod\": [\n {\n \"id\": \"https://example.com/#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://https://example.com/\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n },\n {\n \"inappropriateKey\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n]\n}\n</code></pre> </li> </ul> <p>test targets</p> <ul> <li><code>assertionMethod[0]</code></li> <li>outcome: <code>passed</code></li> <li><code>assertionMethod[1]</code></li> <li>outcome: <code>inapplicable</code>, warning (non-conformant entries)</li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#glossary","title":"Glossary","text":""},{"location":"fep/521a/fep-521a-test-case/#outcome","title":"<code>outcome</code>","text":"<p>An outcome is a conclusion that comes from evaluating a test on a test subject. An outcome can be one of the three following types:</p> <ul> <li><code>inapplicable</code>: No part of the test subject matches the applicability</li> <li><code>passed</code>: A test target meets all expectations</li> <li><code>failed</code>: A test target does not meet all expectations</li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#requirements-mapping","title":"Requirements Mapping","text":"<ul> <li>FEP requirement: - Actor Objects must express signing key as assertionMethod Multikey</li> <li>Required for Conformance to FEP-521a</li> <li>Outcome Mapping<ul> <li>when test target <code>assertionMethod</code> has outcome <code>passed</code>, requirement is satisfied</li> <li>when test target <code>assertionMethod</code> has outcome <code>failed</code>, requirement is not satisfied</li> <li>when test target <code>assertionMethod</code> has outcome <code>inapplicable</code>, further testing is needed to determine whether this requirement is satisfied</li> </ul> </li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#change-log","title":"Change Log","text":"<ul> <li>2024-06-25T00:00:00.000Z - implemented as a free-standing package and submitted to FEP repo</li> <li>2024-04-11T21:41:20.725Z - first draft by bumblefudge</li> </ul>"},{"location":"fep/521a/fep-521a-test-case/#issues-list","title":"Issues List","text":""},{"location":"fep/5624/","title":"FEP-5624: Per-object reply control policies","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com <code>WITHDRAWN</code> 2022-08-23 2025-06-24 #18 Discussions codeberg"},{"location":"fep/5624/#summary","title":"Summary","text":"<p>Sometimes, users may want to share an information or a story without inviting replies from outside their circles or from anyone at all. In particular, individuals may want to restrict who can reply to them in order to avoid \u201creply guys\u201d or limit outright harassment, while instutions may want to disable replies on their posts to provide information without having to deal with a moderation burden.</p> <p>This can be broken into an advisory part advertising what sets of actors are expected to be able to reply, and a collaborative verification process where third-parties check with the actor being replied to that the reply is indeed allowed.</p>"},{"location":"fep/5624/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].</p> <p>In the remaining of this FEP, \u201cdistribution authority\u201d (or \u201cauthority\u201d for short) refers to an actor that controls the distribution and audience of replies. The purpose of this wording is to make this FEP applicable both for models where replies are first-class posts, and for \u201cpost and comments\u201d models where comments only exist in the context of a post and the post author decides who gets to see the comments. In the absence of extensions, the \u201cauthority\u201d is the author of the post being replied to.</p>"},{"location":"fep/5624/#declaring-a-reply-policy","title":"Declaring a reply policy","text":"<p>In order to advertise who is allowed to reply to an object, an author MAY set the <code>canReply</code> (<code>http://joinmastodon.org/ns#canReply</code>) property on their objects. If set, this property MUST be an empty array or one or more actors or collections.</p> <p>To ease implementation, collections SHOULD be restricted to one of the following: - <code>as:Public</code>, to indicate that anyone can reply - the authority's followers collection (if defined) - the authority's followed collection (if defined)</p> <p>In addition, <code>canReply</code> SHOULD contain every actor mentioned in the original object.</p> <p>Whenever one of these collections is used, the receiving end can easily know whether they are expected to be able to reply.</p>"},{"location":"fep/5624/#example-object","title":"Example object","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"canReply\": \"toot:canReply\"\n }\n ],\n \"attributedTo\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/users/1/statuses/1\",\n \"type\": \"Note\",\n \"content\": \"Hello world\",\n \"canReply\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n</code></pre>"},{"location":"fep/5624/#checking-whether-the-user-can-reply-and-submitting-the-reply-to-the-authority","title":"Checking whether the user can reply and submitting the reply to the authority","text":"<p>When an object with <code>canReply</code> is set, it SHOULD be conveyed in human-readable form to the user if possible, for instance with something like \u201cOnly mentioned users can reply\u201d or \u201cOnly people Authority follows and mentioned users can reply\u201d.</p> <p>The software SHOULD NOT offer the user to reply unless it is directly mentioned in the object's <code>tag</code> attribute or listed in <code>canReply</code> (either directly or through a collection), or <code>canReply</code> contains a collection for which the recipient cannot efficiently check the membership of the would-be replier.</p> <p>After locally verifying that the replier should be allowed to reply, the replier's end SHOULD <code>POST</code> the <code>Create</code> activity for the reply to the authority's inbox only, and consider the reply to be pending approval.</p>"},{"location":"fep/5624/#receiving-and-accepting-a-reply","title":"Receiving and accepting a reply","text":"<p>When receiving a reply to an object with a <code>canReply</code> property, the authority decides whether the reply is acceptable.</p> <p>If the reply is considered acceptable, the authority MUST reply with an <code>ApproveReply</code> (<code>http://joinmastodon.org/ns#ApproveReply</code>) activity with the <code>object</code> property set to the <code>id</code> of the reply object, and its <code>inReplyTo</code> property set to the object it is in reply to.</p> <p>That <code>ApproveReply</code> activity SHOULD be publicly dereferenceable and MUST be dereferenceable by all parties allowed to see the original post. It MUST NOT embed its <code>object</code> nor its <code>inReplyTo</code> as to avoid possible information leaks.</p> <p>Additionally, the authority MAY forward an accepted reply according to its own rules.</p> <p>If the reply is considered unacceptable, the authority SHOULD reply with a <code>RejectReply</code> (<code>http://joinmastodon.org/ns#RejectReply</code>) activity. This activity MAY be publicly accessible, but this is not a requirement.</p> <p>The reason for using the new activity types <code>ApproveReply</code> and <code>RejectReply</code> is to be explicit about the purpose of the approval, as one could imagine other kinds of approvals, and remaining implicit may cause ambiguities with other potential extensions.</p>"},{"location":"fep/5624/#example-approvereply-activity","title":"Example <code>ApproveReply</code> activity","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"ApproveReply\": \"toot:ApproveReply\"\n }\n ],\n \"actor\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/reply_approvals/1\",\n \"type\": \"ApproveReply\",\n \"object\": \"https://example.org/users/bob/statuses/3\",\n \"inReplyTo\": \"https://example.com/users/1/statuses/1\"\n}\n</code></pre>"},{"location":"fep/5624/#example-rejectreply-activity","title":"Example <code>RejectReply</code> activity","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"RejectReply\": \"toot:RejectReply\"\n }\n ],\n \"actor\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/reply_approvals/1\",\n \"type\": \"RejectReply\",\n \"object\": \"https://example.org/users/bob/statuses/3\"\n}\n</code></pre>"},{"location":"fep/5624/#receiving-approval-and-distributing-the-reply","title":"Receiving approval and distributing the reply","text":"<p>After sending the initial <code>Create</code>, the replier SHOULD wait for an <code>ApproveReply</code> activity such as described above.</p> <p>Once the <code>ApproveReply</code> has been received, the replier SHOULD add a <code>replyApproval</code> (<code>http://joinmastodon.org/ns#replyApproval</code>) property to their reply object pointing to the <code>ApproveReply</code> activity they received, and then MAY send a <code>Create</code> activity with the modified object to its intended audience.</p> <p>If it instead receives a <code>RejectReply</code>, the reply SHOULD be immediately deleted and the replier MAY be notified.</p>"},{"location":"fep/5624/#example-reply-object-with-replyapproval","title":"Example reply object with <code>replyApproval</code>","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"canReply\": \"toot:canReply\",\n \"replyApproval\": \"toot:replyApproval\"\n }\n ],\n \"attributedTo\": \"https://example.com/users/1\",\n \"id\": \"https://example.org/users/bob/statuses/3\",\n \"type\": \"Note\",\n \"content\": \"@alice hello!\",\n \"inReplyTo\": \"https://example.com/users/1/statuses/1\",\n \"canReply\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"replyApproval\": \"https://example.com/reply_approvals/1\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/1\"\n }\n}\n</code></pre>"},{"location":"fep/5624/#verifying-third-party-replies","title":"Verifying third-party replies","text":"<p>When processing a reply from a remote actor to a remote authority, a recipient SHOULD discard any reply that does not match any of the following conditions: - the object it is in reply to does not set a <code>canReply</code> property - the object has been received through the remote authority - the object it is in reply to has a <code>canReply</code> containing the <code>as:Public</code> collection - the author of the reply appears in a <code>Mention</code> object in the <code>tag</code> property of the object it is in reply to - the object it is in reply to sets a non-empty <code>canReply</code>, and <code>replyApproval</code> can be dereferenced and is a valid <code>ApproveReply</code> activity</p> <p>To be considered valid, the <code>ApproveReply</code> activity referenced in <code>replyApproval</code> MUST satisfy the following properties: - its <code>actor</code> property is the authority - its authenticity can be asserted - its <code>object</code> property is the reply under consideration - its <code>inReplyTo</code> property matches that of the reply under consideration</p> <p>In addition, if the reply is considered valid, but has no valid <code>replyApproval</code> despite the object it is in reply to having a <code>canReply</code> property, the recipient MAY hide the reply from certain views.</p>"},{"location":"fep/5624/#revoking-a-previously-accepted-reply","title":"Revoking a previously-accepted reply","text":"<p>The authority may want to perform /a posteriori/ moderation of their replies.</p> <p>To do this, the authority SHOULD send a <code>RejectReply</code> activity to the sender and the reply's audience, with the reply URI as the <code>object</code> property. The <code>object</code> property MUST NOT be embedded, as to avoid possible information leaks.</p> <p>The URI at which the previously-offered <code>ApproveReply</code> was available should return HTTP 404 or redirect to the newly-issued <code>RejectReply</code> activity.</p>"},{"location":"fep/5624/#handling-a-revocation","title":"Handling a revocation","text":"<p>Upon receiving a <code>RejectReply</code> activity for a previously-accepted reply, third-parties SHOULD check that the <code>RejectReply</code> is valid and SHOULD delete or hide the revoked reply if it is.</p> <p>To be considered valid, the <code>RejectReply</code> activity MUST satisfy the following properties: - its <code>actor</code> property is the authority - its authenticity can be asserted - its <code>object</code> property is the reply under consideration</p>"},{"location":"fep/5624/#deployment-considerations","title":"Deployment considerations","text":"<p>Because it is unrealistic to expect all implementations and deployments to implement this proposal at the same time, deployment SHOULD be gradual, with verification of third-party replies only performed once the other steps are widely implemented. To encourage adoption without breaking compatibility altogether, implementations MAY want to hide non-validated replies from certain views (e.g. requiring a click to see \u201chidden replies\u201d, or not showing the reply to non-followers).</p>"},{"location":"fep/5624/#security-considerations","title":"Security considerations","text":"<p>By not adding a hash or copy of the reply in the <code>ApproveReply</code> activity, malicious actors could exploit this in a split horizon setting, sending different versions of the same activity to different actors. This is, however, already a concern in pretty much all contexts in ActivityPub, and enshrining that information in the <code>ApproveReply</code> activity would have many drawbacks:</p> <ul> <li>significantly more complex implementation</li> <li>inability to change the JSON-LD representation after the fact</li> <li>possibly leaking private information if the <code>ApproveReply</code> activity is publicly dereferenceable</li> </ul>"},{"location":"fep/5624/#implementations","title":"Implementations","text":"<p>None so far.</p>"},{"location":"fep/5624/#references","title":"References","text":"<ul> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> </ul>"},{"location":"fep/5624/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/5711/","title":"FEP-5711: Inverse Properties for Collections","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org <code>DRAFT</code> 2025-03-24 #539 Discussions codeberg"},{"location":"fep/5711/#summary","title":"Summary","text":"<p>This FEP defines inverse properties for collections that are important in ActivityPub.</p>"},{"location":"fep/5711/#motivation","title":"Motivation","text":"<p>The Activity Vocabulary defines a number of important collection properties for organizing reactions to an object. ActivityPub augments these with additional properties that specify the activity streams and social graph for actors.</p> <p>Out of context, though, it can be hard to determine if a collection has a defined relationship to an object or actor. This can be important for clients that give users the ability to manipulate collections with the <code>Add</code>, <code>Remove</code>, <code>Update</code> and <code>Delete</code> activities, since the special collections defined in ActivityPub are usually maintained with different flows.</p> <p>Other methods exist to identify collections that are the value of properties of actors. It's possible to use the <code>attributedTo</code> property to identify the actor, and then compare against all of the collection properties of the actor (<code>inbox</code>, <code>outbox</code>, and so on). This is a good fallback mechanism, but the inverse properties defined in this document are a more direct way to determine the relationship. There is no method for determining if a collection is the property of an object, however.</p> <p>Additionally, one form of spoofing that a server could use is referring to collections as properties of objects that in fact are related to other objects. For example, a server could include a collection of likes as a property of an object that is not the object that the likes are for. This could be used to inflate the apparent popularity of an object. Inverse properties can help prevent this kind of spoofing by providing a two-way verification mechanism.</p> <p>These inverse properties are most useful when the collections are represented separately from the object or actor they are related to. For example, this representation can help identify the object that the collection contains the <code>likes</code> of:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n</code></pre> <p>This representation, on the other hand, is redundant, since the collection is represented as the property of the object:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Image\",\n \"id\": \"https://example.com/image/7pqs5i7c1e\",\n \"likes\": {\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n }\n}\n</code></pre>"},{"location":"fep/5711/#user-stories","title":"User stories","text":"<ul> <li>As an ActivityPub user, I want to confirm that a collection referred to in the property of an object is actually intended for use with that property, to prevent spoofing.</li> <li>As an ActivityPub developer, I want to know that a collection is intended for a particular use, so that I can use it correctly in my application.</li> </ul>"},{"location":"fep/5711/#specification","title":"Specification","text":"<p>The following terms are defined in this vocabulary extension.</p> <p>The examples in the following sections refer to the following example object:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Image\",\n \"id\": \"https://example.com/image/7pqs5i7c1e\",\n \"name\": \"A beautiful image\",\n \"url\": \"https://files.example.com/image/29m2p4kdq15.jpg\",\n \"likes\": \"https://example.com/collection/b25kr36ief\",\n \"shares\": \"https://example.com/collection/hvjgqhpv3e\",\n \"replies\": \"https://example.com/collection/u9hwmfih7r\"\n}\n</code></pre> <p>...and/or the following actor:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.com/person/bup9a8eqm\",\n \"name\": \"Alice\",\n \"inbox\": \"https://example.com/collection/tgiqwh6p3b\",\n \"outbox\": \"https://example.com/collection/hvf8crcd6r\",\n \"followers\": \"https://example.com/collection/x48dtnhlt9\",\n \"following\": \"https://example.com/collection/r9w7krxua\",\n \"liked\": \"https://example.com/collection/jzc50wc28l\"\n}\n</code></pre>"},{"location":"fep/5711/#likesof","title":"likesOf","text":"<ul> <li>URI: <code>https://w3id.org/fep/5711#likesOf</code></li> <li>Domain: <code>as:Collection</code></li> <li>Range: <code>as:Object</code></li> <li>Functional: True</li> </ul> <p>Defines an object for which the collection is the value of the <code>likes</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n</code></pre>"},{"location":"fep/5711/#sharesof","title":"sharesOf","text":"<ul> <li>URI: <code>https://w3id.org/fep/5711#sharesOf</code></li> <li>Domain: <code>as:Collection</code></li> <li>Range: <code>as:Object</code></li> <li>Functional: True</li> </ul> <p>Defines an object for which the collection is the value of the <code>shares</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/hvjgqhpv3e\",\n \"sharesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n</code></pre>"},{"location":"fep/5711/#repliesof","title":"repliesOf","text":"<ul> <li>URI: <code>https://w3id.org/fep/5711#repliesOf</code></li> <li>Domain: <code>as:Collection</code></li> <li>Range: <code>as:Object</code></li> <li>Functional: True</li> </ul> <p>Defines an object for which the collection is the value of the <code>replies</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"OrderedCollection\",\n \"id\": \"https://example.com/collection/u9hwmfih7r\",\n \"repliesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n</code></pre>"},{"location":"fep/5711/#inboxof","title":"inboxOf","text":"<ul> <li>URI: <code>https://w3id.org/fep/5711#inboxOf</code></li> <li>Domain: <code>as:Collection</code></li> <li>Range: <code>as:Object</code> (an ActivityPub actor)</li> <li>Functional: True</li> </ul> <p>Defines an actor for which the collection is the value of the <code>inbox</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/tgiqwh6p3b\",\n \"inboxOf\": \"https://example.com/person/bup9a8eqm\"\n}\n</code></pre>"},{"location":"fep/5711/#outboxof","title":"outboxOf","text":"<ul> <li>URI: <code>https://w3id.org/fep/5711#outboxOf</code></li> <li>Domain: <code>as:Collection</code></li> <li>Range: <code>as:Object</code> (an ActivityPub actor)</li> <li>Functional: True</li> </ul> <p>Defines an actor for which the collection is the value of the <code>outbox</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/hvf8crcd6r\",\n \"outboxOf\": \"https://example.com/person/bup9a8eqm\"\n}\n</code></pre>"},{"location":"fep/5711/#followersof","title":"followersOf","text":"<ul> <li>URI: <code>https://w3id.org/fep/5711#followersOf</code></li> <li>Domain: <code>as:Collection</code></li> <li>Range: <code>as:Object</code> (an ActivityPub actor)</li> <li>Functional: True</li> </ul> <p>Defines an actor for which the collection is the value of the <code>followers</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/x48dtnhlt9\",\n \"followersOf\": \"https://example.com/person/bup9a8eqm\"\n}\n</code></pre>"},{"location":"fep/5711/#followingof","title":"followingOf","text":"<ul> <li>URI: <code>https://w3id.org/fep/5711#followingOf</code></li> <li>Domain: <code>as:Collection</code></li> <li>Range: <code>as:Object</code> (an ActivityPub actor)</li> <li>Functional: True</li> </ul> <p>Defines an actor for which the collection is the value of the <code>following</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/r9w7krxua\",\n \"followingOf\": \"https://example.com/person/bup9a8eqm\"\n}\n</code></pre>"},{"location":"fep/5711/#likedof","title":"likedOf","text":"<ul> <li>URI: <code>https://w3id.org/fep/5711#likedOf</code></li> <li>Domain: <code>as:Collection</code></li> <li>Range: <code>as:Object</code> (an ActivityPub actor)</li> <li>Functional: True</li> </ul> <p>Defines an actor for which the collection is the value of the <code>liked</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/jzc50wc28l\",\n \"likedOf\": \"https://example.com/person/bup9a8eqm\"\n}\n</code></pre>"},{"location":"fep/5711/#implementation-notes","title":"Implementation notes","text":"<p>There should be only one inverse relationship property for a collection. There are conceivable structures where the <code>replies</code> collection of an object is also the <code>liked</code> collection of an actor, but these are rare.</p> <p>All of these properties are functional, meaning that there is only one value for each collection.</p>"},{"location":"fep/5711/#security-considerations","title":"Security considerations","text":"<p>Claims made by these properties should be verified by retrieving the referenced object or actor and checking the appropriate property. For example, if a server claims that a collection is the <code>shares</code> property of an object using the <code>sharesOf</code> property, the consumer should retrieve the referenced object and check that the <code>shares</code> property references the same collection.</p> <p>Use of these inverse properties can help prevent spoofing of collections, but they do not provide verification of the contents of the collection. For example, a collection that uses the <code>likesOf</code> property to claim that it is the <code>likes</code> collection of a video, and which is in turn referenced by the video, may still contain <code>Like</code> activities that are not actually likes of the video.</p>"},{"location":"fep/5711/#references","title":"References","text":"<ul> <li>James Snell, Evan Prodromou, Activity Vocabulary, 2018</li> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> </ul>"},{"location":"fep/5711/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/5bf0/","title":"FEP-5bf0: Collection sorting and filtering","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Michael Puckett michael@puckett.contact <code>WITHDRAWN</code> 2023-04-10 2025-06-14 #80 - codeberg"},{"location":"fep/5bf0/#summary","title":"Summary","text":"<p>This proposal would allow Collections to have a <code>streams</code> property, as Actors do. The streams would be of the type CollectionView, a proposed vocabulary extension that represents a sorted and/or filtered version of a Collection. ActivityPub clients could then render CollectionViews without having to perform such filtering or sorting operations themselves.</p> <p>Metadata about how the sorting or filtering has been applied would be applied using new proposed vocabulary extensions that leverage SHACL for describing constraints.</p>"},{"location":"fep/5bf0/#motivations","title":"Motivations","text":"<p>Some ActivityPub clients rely only on C2S protocols for accessing Collections and displaying the nested items.</p> <p>Currently, in order to support filtering or sorting, these clients need to retrieve all paginated items, assemble them, manually sort or filter them, and then re-paginate them.</p> <p>This proposal would allow servers to perform these kinds of operations, either at runtime or ahead of time, to ease the burden on clients.</p> <p>Other servers should be free to explore the Collections, but they can be easily ignored, along with the new properties.</p>"},{"location":"fep/5bf0/#implementation","title":"Implementation","text":"<p>A CollectionView extends from OrderedCollection and represents a filtered and/or sorted version of a Collection. Similarly, a CollectionViewPage extends from OrderedCollectionPage.</p> <p>The method of filtering applied to the CollectionView can be indicated via its \"filter\" property, which maps to one or more SHACL Shapes.</p> <p>The vocabulary would also provide a SHACL Shape for indicating that a property's value is among the items in a particular ActivityStreams Collection.</p> <p>The method of sorting applied to the CollectionView can be indicated via its \"sort\" property, which maps to a SortShape. A SortShape extends SHACL's PropertyShape and adds the \"order\" property which can be mapped to \"Ascending\" or \"Descending\".</p> <p>If there is no \"sort\" property, the order is the same as that of the original Collection.</p>"},{"location":"fep/5bf0/#examples","title":"Examples","text":"<p>Here, an Actor's Inbox returns all Activities posted by the Actor, and the server also provides filtered versions as streams for client consumption.</p> <p>The first CollectionView returns only the Like Activities.</p> <p>The second CollectionView returns only created Articles with replies, demonstrating multiple filters and filtering on nested properties.</p> <p>The third CollectionView demonstrates how to indicates that a given property's value is in a partiular ActivityStreams Collection. In the example, the CollectionView is returning Activities by Alyssa's Co-workers. (Alyssa has a custom stream of mutual followers who she has labeled as Co-workers.)</p> <pre><code>{\n \"@context\": {\n \"@vocab\": \"https://www.w3.org/ns/activitystreams#\",\n \"fep\": \"https://w3id.org/fep#\",\n \"CollectionView\": \"fep:CollectionView\",\n \"viewOf\": \"fep:viewOf\",\n \"filter\": \"fep:filter\",\n \"sort\": \"fep:sort\",\n \"inCollection\": \"fep:inCollection\",\n \"SortShape\": \"fep:SortShape\",\n \"order\": \"fep:order\",\n \"sh\": \"http://www.w3.org/ns/shacl#\",\n \"PropertyShape\": \"sh:PropertyShape\",\n \"path\": \"sh:path\",\n \"hasValue\": \"sh:hasValue\",\n \"minCount\": \"sh:minCount\"\n },\n \"id\": \"https://example.social/@alyssa/inbox\",\n \"type\": \"OrderedCollection\",\n \"name\": \"Inbox\",\n \"totalItems\": 1000,\n \"first\": \"https://example.social/@alyssa/inbox/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/page/2\",\n \"streams\": [\n {\n \"id\": \"https://example.social/@alyssa/inbox/likes\",\n \"type\": \"CollectionView\",\n \"name\": \"Likes\",\n \"filter\": {\n \"type\": \"PropertyShape\",\n \"path\": \"type\",\n \"hasValue\": \"Like\"\n },\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/likes/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/likes/page/1\"\n },\n {\n \"id\": \"https://example.social/@alyssa/inbox/posts-with-replies\",\n \"type\": \"CollectionView\",\n \"name\": \"Posts with Replies\",\n \"filter\": [\n {\n \"type\": \"PropertyShape\",\n \"path\": \"type\",\n \"hasValue\": \"Create\"\n },\n {\n \"type\": \"PropertyShape\",\n \"path\": [\"object\", \"inReplyTo\"],\n \"minCount\": 1\n }\n ],\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/blog-posts/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/blog-posts/page/1\",\n \"viewOf\": \"https://example.social/@alyssa/inbox\"\n },\n {\n \"id\": \"https://example.social/@alyssa/inbox/notes-by-coworkers\",\n \"type\": \"CollectionView\",\n \"name\": \"Posts by Co-Workers\",\n \"filter\": {\n \"type\": \"InCollectionShape\",\n \"path\": \"actor\",\n \"inCollection\": \"https://example.social/@alyssa/friends/coworkers\"\n },\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/notes-by-coworkers/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/notes-by-coworkers/page/1\",\n \"viewOf\": \"https://example.social/@alyssa/inbox\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/5bf0/#vocabulary-extensions","title":"Vocabulary Extensions","text":"<p>Here are the terms that would needed to be added to the FEP vocabulary:</p> <pre><code>{\n \"@context\": {\n \"fep\": \"https://w3id.org/fep#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\",\n \"sh\": \"http://www.w3.org/ns/shacl#\",\n \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\",\n \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\"\n },\n \"fep:CollectionView\": {\n \"@id\": \"fep:CollectionView\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"as:OrderedCollection\",\n \"rdfs:label\": \"Collection View\",\n \"rdfs:comment\": \"Represents a sorted and/or filtered version of a Collection\"\n },\n \"fep:CollectionViewPage\": {\n \"@id\": \"fep:CollectionViewPage\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"as:OrderedCollectionPage\",\n \"rdfs:label\": \"Collection View Page\",\n \"rdfs:comment\": \"Represents a sorted and/or filtered version of a CollectionPage\"\n },\n \"fep:SortShape\": {\n \"@id\": \"fep:SortShape\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"sh:PropertyShape\",\n \"rdfs:label\": \"Sort Shape\",\n \"rdfs:comment\": \"A PropertyShape with an 'order' property\"\n },\n \"fep:filter\": {\n \"@id\": \"fep:filter\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"sh:Shape\",\n \"rdfs:label\": \"Filter\",\n \"rdfs:comment\": \"The method of filtering applied to the CollectionView\"\n },\n \"fep:sort\": {\n \"@id\": \"fep:sort\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"fep:SortShape\",\n \"rdfs:label\": \"Sort\",\n \"rdfs:comment\": \"The method of sorting applied to the CollectionView\"\n },\n \"fep:order\": {\n \"@id\": \"fep:order\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:SortShape\",\n \"rdfs:range\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Order\",\n \"rdfs:comment\": \"Indicates whether the sort order is 'Ascending' or 'Descending'\"\n },\n \"fep:SortOrderType\": {\n \"@id\": \"fep:SortOrderType\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:label\": \"Sort Order Type\",\n \"rdfs:comment\": \"For indicating the sort order\"\n },\n \"fep:Ascending\": {\n \"@id\": \"fep:Ascending\",\n \"@type\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Ascending\",\n \"rdfs:comment\": \"Indicates ascending sort order\"\n },\n \"fep:Descending\": {\n \"@id\": \"fep:Descending\",\n \"@type\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Descending\",\n \"rdfs:comment\": \"Indicates descending sort order\"\n },\n \"fep:InCollectionShape\": {\n \"@id\": \"fep:InCollectionShape\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"sh:PropertyShape\",\n \"rdfs:label\": \"In Collection Shape\",\n \"rdfs:comment\": \"For filtering on whether a property's value is in a Collection.\",\n \"sh:js\": \"fep:inCollectionFunction\"\n },\n \"fep:inCollection\": {\n \"@id\": \"fep:inCollection\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:InCollectionShape\",\n \"rdfs:range\": \"sh:IRI\",\n \"rdfs:label\": \"In Collection\",\n \"rdfs:comment\": \"Maps a Collection URL to fep:inCollectionFunction\"\n },\n \"fep:inCollectionFunction\": {\n \"@id\": \"fep:inCollectionFunction\",\n \"@type\": \"sh:JSFunction\",\n \"sh:jsFunction\": \"inCollection\",\n \"sh:jsLibrary\": \"https://w3id.org/fep/functions.js\",\n \"sh:parameter\": {\n \"@type\": \"sh:Parameter\",\n \"sh:path\": \"fep:inCollection\"\n }\n },\n \"fep:viewOf\": {\n \"@id\": \"fep:viewOf\",\n \"@type\": \"rdf:Property\",\n \"rdfs:label\": \"View of\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"as:Collection\",\n \"rdfs:comment\": \"A reference back to the original Collection\"\n }\n}\n</code></pre>"},{"location":"fep/5bf0/#security","title":"Security","text":"<p>Servers could in theory make available a templated URL endpoint that allows for arbitrary sorting or filtering. This should be discouraged, as it could lead to database injections. Instead, only predetermined sorted/filtered CollectionViews should be made available via the <code>streams</code> property.</p>"},{"location":"fep/5bf0/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> </ul>"},{"location":"fep/5bf0/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/5e53/","title":"FEP-5e53: Opt-out Preference Signals","text":"Authors Status Date received Tracking issue Discussions Repository Don Marti dmarti@zgp.org <code>DRAFT</code> 2024-06-09 #327 Discussions codeberg"},{"location":"fep/5e53/#summary","title":"Summary","text":"<p>Some users have concerns about how their content and/or personal information are used. For example, some users do not want the content they created to be used for training generative AI systems, and some users do not want to have their personal information shared or sold.</p> <p>Several opt-out preference signals (OOPSs) have been standardized or proposed in the form of HTTP headers that can apply to a connection between a user and a central server. In some jurisdictions, companies that administer web sites are required to process and act on OOPSs. </p> <p>This FEP extends ActivityPub to support passing OOPSs along with the content and user information to which they may apply. This FEP refers to existing OOPSs that have already been documented, and does not propose new ones.</p>"},{"location":"fep/5e53/#declaring-an-oops","title":"Declaring an OOPS","text":"<p>In order to apply an OOPS to an object, an author MAY set the JSON-LD properties <code>xRobotsTag</code> and/or <code>SPC</code>. For example,</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://fep.example/ns/privacyHeaders\"],\n \"type\": \"Note\",\n \"content\": \"Don't surveil me bro\",\n \"xRobotsTag\": \"noai\",\n \"SPC\": 1\n}\n</code></pre> <p>A recipient SHOULD process the content and metadata of the object as if the recipient had received it over an HTTP connection with the corresponding header.</p> <ul> <li> <p><code>SPC</code>: process as if the object has been received in an HTTP connection with the <code>Sec-GPC</code> HTTP request header.</p> </li> <li> <p><code>xRobotsTag</code>: process as if the object had been received with an <code>X-Robots-Tag</code> HTTP response header with a value equal to this property.</p> </li> </ul>"},{"location":"fep/5e53/#deployment-considerations","title":"Deployment considerations","text":"<p>The existing Global Privacy Control (GPC) standard allows for individuals in an increasing number of jurisdictions to pass a legally binding opt-out preference signal indicating a commonly held privacy norm. However, GPC is implemented as an HTTP request header, which makes it only works from client to server. This limits the ability of users to pass a privacy opt-out in a federated communications medium. A federated system where a user's information may be passed from one server to another is currently at a disadvantage in serving users who wish to opt out, because the opt-out preference signal does not travel with the information to which it applies. SPC is intended to have the same effects as GPC, but for cases where the user does not have a direct HTTP connection to the recipient of some personal information. Adding the SPC opt-out to ActivityPub would bring parity with centralized social sites.</p> <p>Fediverse instances should make SPC configurable by users, and reflect a user's actual intent to opt out. Fediverse instances should detect when a user has turned on GPC or some other privacy opt-out or setting, and offer to apply SPC to that user's objects.</p> <p>Content sharing platforms that operate in a centralized manner are now frequently promoting their support for signaling opt-outs to generative AI training, using the noai value for the X-Robots-Tag header. See RobotsMeta and RobotExclusion. In order for federated content sharing systems to provide an attractive alternative to centralized ones, federated systems will likely need to also offer an opt out signaling feature.</p>"},{"location":"fep/5e53/#security-considerations","title":"Security considerations","text":"<p>This proposal depends on compliant processing of OOPSs by all actors.</p> <p>Some jurisdictions require privacy OOPSs to be registered in order to have legal effect. In the event this FEP is accepted, the author intends to pursue registration.</p>"},{"location":"fep/5e53/#implementations","title":"Implementations","text":"<p>None so far.</p>"},{"location":"fep/5e53/#related-proposal","title":"Related proposal","text":"<p>FEP-c118 suggests establishing a content licensing framework. This may be possible by adapting this FEP to include the license header from WebLinking.</p>"},{"location":"fep/5e53/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>DeviantArt team, UPDATE All Deviations Are Opted Out of AI Datasets, 2022</li> <li>Google Search Central, Robots meta tag, data-nosnippet, and X-Robots-Tag specifications, undated</li> <li>M. Nottingham, Web Linking, 2010</li> <li>Martijn Koster, A Standard for Robot Exclusion, 1994</li> <li>Sebastian Zimmeck, Peter Snyder, Justin Brookman, Aram Zucker-Scharff, Global Privacy Control, 2024</li> </ul>"},{"location":"fep/5e53/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/5feb/","title":"FEP-5feb: Search indexing consent for actors","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com <code>DRAFT</code> 2023-09-06 #154 - codeberg"},{"location":"fep/5feb/#summary","title":"Summary","text":"<p>This FEP introduces an actor-level attribute that can be used to explicitly express an actor's consent (or lack thereof) to their public objects being indexed for search purposes.</p> <p>Akin to <code>robots.txt</code> and <code>noindex</code> meta tags, this attribute is advisory and relies on the indexers respecting the directive, as public objects can not technically be prevented from being indexed.</p>"},{"location":"fep/5feb/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].</p>"},{"location":"fep/5feb/#specifying-search-indexing-consent-at-the-actor-level","title":"Specifying search indexing consent at the actor-level","text":"<p>Actors can use the <code>indexable</code> (<code>http://joinmastodon.org/ns#indexable</code>) attribute to specify whether they consent to their public objects being indexed for search or not.</p> <p>A missing <code>indexable</code> attribute SHOULD be handled as <code>indexable: false</code>.</p>"},{"location":"fep/5feb/#example","title":"Example","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\":\"http://joinmastodon.org/ns#\",\n \"indexable\":\"toot:indexable\",\n }\n ],\n \"id\": \"https://example.com/users/1\",\n \"type\":\"Person\",\n \"inbox\": \"https://example.com/users/1/inbox\",\n \"outbox\": \"https://example.com/users/1/outbox\",\n \"preferredUsername\": \"bob\",\n \"indexable\": true\n}\n</code></pre>"},{"location":"fep/5feb/#searchable-objects","title":"Searchable objects","text":"<p>Objects authored by an actor with <code>indexable: true</code> and addressed <code>to</code> the <code>as:Public</code> special collection SHOULD be made available for search.</p> <p>Objects authored by an actor with <code>indexable: false</code> MUST NOT be made available for search to other users unless they were previously displayed and interacted with (e.g. it is allowed for a user to search posts they have bookmarked).</p>"},{"location":"fep/5feb/#handling-updates-to-the-indexable-attribute","title":"Handling updates to the <code>indexable</code> attribute","text":"<p>Whenever an actor is updated and its attribute is set to <code>indexable: true</code>, its objects SHOULD be made available for search as described in the previous section.</p> <p>Whenever an actor is updated and its attribute is set to <code>indexable: false</code>, its objects MUST be removed from search as described in the previous section.</p>"},{"location":"fep/5feb/#security-considerations","title":"Security considerations","text":"<p>Considering this attribute is purely advisory, special care SHOULD be given to the user interface to make sure users are not made to believe the attribute will ensure they never get indexed.</p>"},{"location":"fep/5feb/#implementations","title":"Implementations","text":"<ul> <li>Mastodon, as of v4.2.0</li> </ul>"},{"location":"fep/5feb/#references","title":"References","text":"<ul> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> </ul>"},{"location":"fep/5feb/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/612d/","title":"FEP-612d: Identifying ActivityPub Objects through DNS","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Helge helge.krueger@gmail.com <code>WITHDRAWN</code> 2023-04-18 2025-10-10 #89 - codeberg"},{"location":"fep/612d/#summary","title":"Summary","text":"<p>In [ActivityPub], objects are identified through their id, which is a dereferenciable URI. For this, one adds a TXT record to DNS with name <code>_apobjid</code> and value corresponding to the URI of the ActivityPub object. If a domain name is then passed to a FediVerse application, it can then perform the DNS lookup, and resolve it to the ActivityPub object.</p>"},{"location":"fep/612d/#discussion","title":"Discussion","text":"<p>This FEP is inspired by [BlueSky], and it gets the FediVerse half of the way there. It allows one to look up the ActivityPub Actor from an associated domain name, it does not address the issue of diplaying the domain name instead of the FediVerse handle. One first step would be to include the domain name in alsoKnownAs. Then use some property of the actor to specify, which object to display.</p>"},{"location":"fep/612d/#other-mechanisms","title":"Other Mechanisms","text":"<p>Two other mechanisms to identify ActivityPub objects beside their id are currently in use:</p>"},{"location":"fep/612d/#webfinger","title":"Webfinger","text":"<p>The FediVerse handle <code>username@domain.tld</code> is commonly used as an identifier for users on the FediVerse. This handle can be resolved by performing a Webfinger lookup via</p> <pre><code>GET https://domain.tld/.well-known/webfinger?resource=acct:username@domain.tld\n</code></pre> <p>the activity pub object is then contained in the link with type <code>application/activity+json</code>.</p>"},{"location":"fep/612d/#html-link-header","title":"HTML Link header","text":"<p>The second method, that is less commonly used, is to provide a HTML link header of the form</p> <pre><code>&lt;link\n href=\"https://activty.pub/object/id\"\n rel=\"alternate\"\n type=\"application/activity+json\"\n/&gt;\n</code></pre> <p>A FediVerse object receiving this HTML as a response to a lookup, can then parse it and use the link to lookup the corresponding object.</p>"},{"location":"fep/612d/#content-negotiation","title":"Content Negotiation","text":"<p>On a server supporting content negotiation, a request with Accept header \"application/activity+json\" will be either served or redirected to the corresponding ActivityPub object. The author does not a method to identify where the ActivityPub object is, as it is being directly served.</p>"},{"location":"fep/612d/#example-of-using-dns-to-identify-an-activitypub-object","title":"Example of using DNS to identify an ActivityPub object","text":"<p>The following shows an example configuration for the domain <code>mymath.rocks</code> correspond to the actor with FediVerse handle <code>@helge@mymath.rocks</code>, i.e. the author,</p> <pre><code>$ dig _apobjid.mymath.rocks\n\n;; ANSWER SECTION:\n_apobjid.mymath.rocks. 7200 IN TXT \"https://mymath.rocks/endpoints/SYn3cl_N4HAPfPHgo2x37XunLEmhV9LnxCggcYwyec0\"\n</code></pre> <p>The corresponding object being</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\"],\n \"id\":\"https://mymath.rocks/endpoints/SYn3cl_N4HAPfPHgo2x37XunLEmhV9LnxCggcYwyec0\",\n \"name\":\"Helge\",\n \"preferredUsername\":\"helge\",\n \"summary\":\"&lt;p&gt;I like Math, cows, and wrote &lt;a href=\\\"https://codeberg.org/bovine/bovine/\\\"&gt;bovine&lt;/a&gt;.&lt;/p&gt;\",\n \"type\":\"Person\", ...\n}\n</code></pre>"},{"location":"fep/612d/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[BlueSky] Jay Graber Domain Names as Handles in Bluesky, 2023</li> </ul>"},{"location":"fep/612d/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/61cf/","title":"Index","text":"<p>hide: - navigation description: \"OpenWebAuth is the \"single sign-on\" mechanism used by Hubzilla, (streams) and other related projects. It allows a browser-based user to log in to services across the Fediverse using a single identity. Once logged in, they can be recognised by other OpenWebAuth-compatible services, without third-party cookies and often without any explicit user interaction. This is not a specification, a proposal, or a \"best practice\" document. The aim is to describe the existing protocol in detail as an aid to implementers, evaluators, and anyone who wants to understand its operation. It is mostly based on reverse-engineering the existing implementations and focuses on the minimal requirements for basic interoperability. In OpenWebAuth, each user is identified by a public/private key pair. The protocol relies on there being a mechanism for other nodes on the network to discover a user's public key. This document assumes that ActivityPub actor objects will be used for this purpose. OpenWebAuth can also work with other protocols such as Zot6 and Nomad but these are not considered here.\"</p>"},{"location":"fep/61cf/#fep-61cf-the-openwebauth-protocol","title":"FEP-61cf: The OpenWebAuth Protocol","text":"Authors Status Type Date received Tracking issue Discussions Repository FenTiger @FenTiger@zotum.net <code>DRAFT</code> implementation 2024-02-06 #263 - codeberg <p>OpenWebAuth is a federated remote authentication protocol. It can be used alongside protocols such as ActivityPub and Zot to add single sign-on to the Fediverse or to other \"social web\" projects such as blogs.</p>"},{"location":"fep/61cf/#summary","title":"Summary","text":"<p>OpenWebAuth is the \"single sign-on\" mechanism used by Hubzilla, (streams) and other related projects. It allows a browser-based user to log in to services across the Fediverse using a single identity. Once logged in, they can be recognised by other OpenWebAuth-compatible services, without third-party cookies and often without any explicit user interaction.</p> <p>This is not a specification, a proposal, or a \"best practice\" document. The aim is to describe the existing protocol in detail as an aid to implementers, evaluators, and anyone who wants to understand its operation. It is mostly based on reverse-engineering the existing implementations and focuses on the minimal requirements for basic interoperability.</p> <p>In OpenWebAuth, each user is identified by a public/private key pair. The protocol relies on there being a mechanism for other nodes on the network to discover a user's public key. This document assumes that ActivityPub actor objects will be used for this purpose. OpenWebAuth can also work with other protocols such as Zot6 and Nomad but these are not considered here.</p>"},{"location":"fep/61cf/#overview","title":"Overview","text":"<p>The protocol takes place between two participants:</p> <ul> <li> <p>The home instance, which hosts the user's identity, and is equivalent to the Identity Provider (IdP) in SAML and OpenID Connect.</p> </li> <li> <p>The target instance, which allows remote users to log in to it using the provided identity. This is equivalent to the Relying Party (RP) in SAML and OpenID Connect.</p> </li> </ul>"},{"location":"fep/61cf/#starting-the-login-flow","title":"Starting the login flow","text":"<p>The OpenWebAuth login flow can begin in one of two ways:</p> <ul> <li> <p>The user visits the target instance and sees a login screen. They type their Fediverse ID into a form field and click \"Login\".</p> </li> <li> <p>The user follows a link to the target instance. This link has a query parameter, <code>zid=</code>, which specifies the user's Fediverse ID.</p> </li> </ul> <p>This <code>zid=</code> mechanism is not necessarily specific to OpenWebAuth. It can be used to start an OAuth2-based login flow in just the same way. Implementers should note, though, that it can make attacks easier to launch; an attacker who can trick a victim into clicking a malicious link can cause them to start the login flow using a maliciously constructed identity, perhaps leading to a MixUpAttack.</p>"},{"location":"fep/61cf/#operation-of-the-protocol","title":"Operation of the protocol","text":"<p>The protocol can be summarised in a sequence diagram as below. This diagram shows the <code>zid=</code> case, where the user is not prompted to log in.</p> <pre><code>sequenceDiagram\n participant browser as Browser\n participant target as Target instance\n participant home as Home instance\n\n browser -&gt;&gt; target: GET /page?zid=user@home\n target -&gt;&gt; home: webfinger user@home\n home --&gt;&gt; target: location of redirection endpoint\n target --&gt;&gt; browser: Location: https://home.example/magic?...\n browser -&gt;&gt; home: GET /magic?...\n Note over home: Check user is logged in,&lt;br/&gt;eg by checking session cookie\n home -&gt;&gt; target: webfinger /\n target --&gt;&gt; home: location of token endpoint\n rect rgb(216, 255, 216)\n Note over home,target: Protected by actor's private key\n home -&gt;&gt; target: GET /token&lt;br/&gt;(signed)\n target --&gt;&gt; home: &lt;token&gt;&lt;br/&gt;(encrypted)\n end\n home --&gt;&gt; browser: Location: https://target.example/page?owt=&lt;token&gt;\n browser -&gt;&gt; target: GET /page?owt=&lt;token&gt;\n target --&gt;&gt; browser: &lt;contents of page&gt;</code></pre> <p>Regardless of how the flow is started, the protocol begins with the user's browser making a request to the target instance.</p>"},{"location":"fep/61cf/#1-redirection-to-home-instance","title":"1. Redirection to home instance","text":"<p>First the target instance identifies the home instance's \"redirection endpoint\".</p> <p>Some existing implementations hard-code this to <code>/magic</code>. New implementations should perform a webfinger lookup on the provided user ID looking for a link with <code>rel</code> set to <code>http://purl.org/openwebauth/v1#redirect</code>. If found, this link's <code>href</code> should be used as the redirection endpoint.</p> <p>The target instance constructs a URL from the redirection endpoint with the following query parameters: - <code>owa</code>: must be set to 1 - <code>bdest</code>: The URL which the browser will be returned to after acquiring a token. This is encoded as UTF-8 and then converted to a hexadecimal string. This is equivalent to the <code>redirect_uri</code> in OAuth2. The <code>bdest</code> URL can include query parameters.</p> <p>The user's browser is redirected to this URL. The target instance should check that the URL has the same origin as the webfinger ID, to avoid acting as an open redirector.</p>"},{"location":"fep/61cf/#2-home-instance-requests-a-token","title":"2. Home instance requests a token","text":"<p>The <code>/magic</code> endpoint at the user's home instance first checks that the user's browser has a valid session cookie.</p> <p>If so, it decodes the <code>bdest</code> destination URL. It performs a webfinger lookup on the root URL of the destination site and looks for a link with <code>rel</code> set to <code>http://purl.org/openwebauth/v1</code>. This identifies the target instance's \"token endpoint\".</p> <p>If an error occurs during this step, the home instance should not redirect to the 'bdest' URL; this would allow it to be used as an open redirector. Instead it should respond with a suitable HTTP error code.</p> <p>On success, the home instance constructs and issues a signed HTTPS request to the discovered token endpoint. The request also contains an additional signed header, <code>X-Open-Web-Auth</code>, containing a random string. Target instances do not use this header; it is provided to add additional entropy to the signature calculation.</p>"},{"location":"fep/61cf/#3-target-instance-provides-a-token","title":"3. Target instance provides a token","text":"<p>The target instance's token endpoint extracts the <code>keyId</code>, fetches the actor record, extracts the public key and verifies the signature.</p> <p>On success, it generates an URL-safe random string to use as a token. This token is stored locally, associated with the actor who signed the message. The token is also encrypted using the actor's public key and the RSA PKCS #1 v1.5 encryption scheme. The encrypted result is encoded as URL-safe Base64 with no '=' padding bytes.</p> <p>Next it constructs the following JSON object in response:</p> <pre><code>{\n \"success\": true,\n \"encrypted_token\": &lt;the base64-encoded token&gt;\n}\n</code></pre> <p>On failure it can also return a result with <code>success</code> set to false.</p>"},{"location":"fep/61cf/#4-home-instance-receives-the-token","title":"4. Home instance receives the token","text":"<p>The signed request issued in step 2 completes. The home instance decodes the JSON response and verifies that <code>success</code> is true. Next it decodes the Base64-encoded encrypted token and decrypts it using the actor's private key.</p> <p>If successful, it takes the <code>bdest</code> destination URL, adds the query parameter: <code>owt=&lt;decrypted token&gt;</code>, and redirects the user's browser to it.</p>"},{"location":"fep/61cf/#5-user-returns-to-target-instance","title":"5. User returns to target instance","text":"<p>The user arrives back at the target instance. The target instance sees the <code>owt=</code> query parameter and checks its local storage for the token which it saved in step 3.</p> <p>If found, this token identifies the remote user, and the target instance logs them in, overriding any existing login they may have. The token is also deleted from local storage so that it cannot be redeemed more than once.</p>"},{"location":"fep/61cf/#what-next","title":"What Next?","text":"<p>(This section is non-normative.)</p> <p>What does the target instance allow the user to do, now that they have signed in? The simplest option is to treat them as though they had signed in using a local username and password, but this isn't really enough.</p> <p>In an ActivityPub context, a remotely authenticated user will expect to be able to post to the target instance using their existing identity. This poses the question of how the target instance should sign the post, since it does not have access to the user's private key. Hubzilla solves this by attributing the post to the user's identity, but signing it with a key belonging to the target instance; any other instance which receives the post must be aware of this mechanism, or the signature will be rejected.</p>"},{"location":"fep/61cf/#additional-notes","title":"Additional notes","text":""},{"location":"fep/61cf/#http-signatures","title":"HTTP Signatures","text":"<p>An OpenWebAuth signed request must have an <code>Authorization</code> header starting with the word <code>Signature</code> followed by the encoded HTTP signature. See ActPubSig for more details on signing Fediverse requests.</p>"},{"location":"fep/61cf/#target-instances-login-check","title":"Target instance's login check","text":"<p>To support logged in users, the target instance needs some logic to identify their requests. Normally this is done by checking for a valid session cookie. To support OpenWebAuth this logic must be extended to also check for the <code>zid=</code> and <code>owt=</code> query parameters.</p> <p>Some corner cases are possible here. For instance, the user could already be logged in to the target instance when the OWA login flow begins.</p> <p>When the OpenWebAuth flow succeeds, the <code>owt=</code> token will identify the logged-in user. It is important that the target instance trusts this token rather than the original <code>zid=</code> query parameter, to prevent an impersonation attack.</p>"},{"location":"fep/61cf/#target-instances-token-endpoint","title":"Target instance's token endpoint","text":"<p>This endpoint should accept both GET and POST requests. Some home instances will issue POSTs with random bodies.</p>"},{"location":"fep/61cf/#home-instances-magic-endpoint","title":"Home instance's <code>/magic</code> endpoint","text":"<p>The implementation of this endpoint needs to request a login token from the target instance. This requires it to prove possession of the user's private key, first to calculate a signature for the request and then to decrypt the returned token. These are the only places in the protocol where the private key is needed, implying that only the home instance needs to be a Fediverse instance. The target instance only needs access to public keys, meaning that OpenWebAuth can be used to allow users to log into things that are not instances.</p>"},{"location":"fep/61cf/#implementations","title":"Implementations","text":"<ul> <li>Hubzilla</li> <li>Streams</li> <li>Forte</li> <li>Friendica (partial)</li> <li>FedIAM</li> </ul>"},{"location":"fep/61cf/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/61cf/#information-leakage","title":"Information leakage","text":"<p>The purpose of OpenWebAuth is to provide a strong guarantee of a user's identity to the web sites that they visit. This is often considered undesirable and consideration should be given to preventing this information from leaking to sites which may not be acting in the user's best interests.</p> <p>This consideration may involve policies such as displaying a consent screen to the user or otherwise allowing them to choose which target instances they are willing to authenticate themselves to. The user's browser is redirected to their home instance at step 2, giving it an opportunity to implement policies such as these.</p>"},{"location":"fep/61cf/#denial-of-service-attack","title":"Denial-of-service attack","text":"<p>Unused <code>owt=</code> login tokens are deleted after a couple of minutes. This protects against a potential DoS attack which could fill up the target instance's storage with unused tokens.</p>"},{"location":"fep/61cf/#impersonation-attack","title":"Impersonation attack","text":"<p>Suppose Mallory is logged into his home instance as <code>mallory@example.social</code>. It is easy for him to manually navigate to a URL on Alice's instance which looks like <code>https://alice.social/somepage?zid=bob@example.social</code>. When the OpenWebAuth flow completes, Mallory will return to Alice's instance with an <code>owt=</code> token identifying him as <code>mallory@example.social</code>. It is important that Alice's instance trusts this <code>owt=</code> token. If it mistakenly trusts the original <code>zid=</code> parameter, Mallory will be able to impersonate Bob.</p>"},{"location":"fep/61cf/#open-redirection","title":"Open redirection","text":"<p>OpenWebAuth involves redirecting the user's browser twice. It is important to verify the relevant URLs to avoid an OpenRedirectionAttack.</p> <p>The first redirection sends the user to their home instance's redirection endpoint (often <code>/magic</code>). If this endpoint is discovered via webfinger, the target instance should check that its hostname portion matches the hostname in the user's claimed ID.</p> <p>The second redirection sends the user back to the URL specified in the <code>bdest</code> query parameter. This redirection should only take place if * the token endpoint discovery step succeeded * the discovered token endpoint URL has the same origin as the <code>bdest</code> URL</p> <p>In any other case the home instance's redirection endpoint should return a suitable HTTP error code.</p>"},{"location":"fep/61cf/#mixup-attack","title":"Mixup attack","text":"<p>The <code>zid=</code> query parameter may make a MixUpAttack easier to launch, as discussed briefly above. This does not apply to OpenWebAuth but should be considered carefully when trying to generalise the <code>zid=</code> mechanism to other authentication protocols.</p>"},{"location":"fep/61cf/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Ryan Barrett, nightpool, ActivityPub and HTTP Signatures, 2024</li> <li>RFC9700, Best Current Practice for OAuth 2.0 Security, 2025</li> <li>OWASP, Unvalidated Redirects and Forwards Cheat Sheet</li> </ul>"},{"location":"fep/61cf/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/6481/","title":"FEP-6481: Specifying ActivityPub extension support with NodeInfo","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository James Smith james@floppy.org.uk <code>WITHDRAWN</code> 2024-03-12 2024-10-31 #279 - codeberg"},{"location":"fep/6481/#summary","title":"Summary","text":"<p>Many FediVerse services extend ActivityPub and ActivityStreams to add their own behaviour, such as custom object types. In order to interoperate with other servers running different software, the service needs to know whether or not the remote server supports these same extensions.</p> <p>This FEP defines a standard method of specifying support for particular extensions in the server's NodeInfo file (as described in FEP-f1d5), so that compatibility information can be automatically discovered.</p>"},{"location":"fep/6481/#history","title":"History","text":"<p>Many FediVerse software platforms such as Bookwyrm and Pixelfed have used the NodeInfo <code>software</code> field to determine if a remote server is running the same software, and is therefore compatible with their extensions. This works, however it restricts full interoperability with different software that could potentially use the same types.</p>"},{"location":"fep/6481/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/6481/#declaring-extension-support","title":"Declaring extension support","text":"<p>FediVerse software that extends ActivityPub SHOULD indicate extension support in its NodeInfo file.</p> <p>Extension identifiers MUST be valid IRIs. IRIs MAY be created with the w3id.org Permanent Identifier service, and if so they SHOULD redirect to a document that describes the extension.</p> <p>To comply with this FEP, extensions MUST be specified in the <code>metadata</code> section of the NodeInfo file, as shown below:</p> <pre><code> {\n \"version\": \"2.2\",\n ...,\n \"protocols\": [\"activitypub\"],\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/my-project/my-extension#v1\"\n ]\n }\n }\n}\n</code></pre> <p>Note: The metadata key <code>activitypub</code> is chosen to match the string in <code>protocols</code>, and is refers to extensions in any of ActivityPub, ActivityStreams and the Activity Vocabulary.</p> <p>It is RECOMMENDED that extension developers specify version information in their IRIs, for instance in a fragment identifier as above.</p> <p>Once defined, IRIs MUST be constant - they cannot be changed while retaining compatibility. For this reason, it is RECOMMENDED to use a long-term service (e.g. w3id.org) for IRIs.</p>"},{"location":"fep/6481/#defining-iris","title":"Defining IRIs","text":"<p>No specific recommendation is made for IRI design, although if using w3id.org, their Naming Policy should be adhered to.</p>"},{"location":"fep/6481/#examples","title":"Examples","text":"<p>This section is non-normative.</p> <p>Manyfold intends to define a new Activity Vocabulary Object type for its own rich content, <code>3dModel</code>. The specification will define an extension IRI, for instance <code>https://w3id.org/manyfold/3dModel#v1</code>.</p> <p>Applications that wish to receive Activities with this type can state their support for the extension in their NodeInfo file as follows; Manyfold instances will then know that they can send that object type and it will be understood.</p> <p><pre><code>{\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/manyfold/3dModel#v1\"\n ]\n }\n }\n}\n</code></pre> A similar (entirely theoretical) example for Bookwyrm's <code>Review</code> object type might be:</p> <pre><code>{\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://joinbookwyrm.org/ns/activitypub#Review\"\n ]\n }\n }\n}\n</code></pre>"},{"location":"fep/6481/#references","title":"References","text":"<ul> <li>FEP-f1d5, NodeInfo in Fediverse Software</li> <li>James Smith, Manyfold ActivityPub design document, 2024</li> <li>James M. Snell &amp; Evan Prodromou, ActivityStreams 2.0, 2017</li> <li>Christine Lemmer-Webber &amp; Jessica Tallon, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> </ul>"},{"location":"fep/6481/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/6606/","title":"FEP-6606: ActivityPub client to server collections addressing conventions","text":"Authors Status Date received Tracking issue Discussions Repository Marius Orcsik marius@federated.id <code>DRAFT</code> 2024-12-04 #453 Discussions codeberg"},{"location":"fep/6606/#summary","title":"Summary","text":"<p>This document tries to describe a simple set of conventions to better enable the adressing of [ActivityPub] objects on servers that support [Client to Server Interactions]. Its main purpose is to formalize a basic vocabulary for defining subsets of IRIs [RFC-3987] for collections in a way that can be generalized to both servers and clients. It builds upon the definition of query parametrs [RFC-3986], by introducing a set of additional operators that can be applied to values.</p>"},{"location":"fep/6606/#what-we-are-trying-to-solve","title":"What we are trying to solve","text":"<p>The main reason that is often cited as making client to server interactions problematic is that the official specification leaves too much to the implementors' discretion. The fact that there is no proscribed method of filtering collections content is one of the stop gaps that prevents a wider adoption of C2S in services and clients. </p>"},{"location":"fep/6606/#syntax","title":"Syntax","text":"<p>The following syntax is extracted form RFC-3986 section 3.4, and it formalizes the URL query parameters as representing any string that conforms to the restrictions below between the first \"?\" character and the end of the URL or a \"#\" character marking the start of the fragment part of the URL.</p> <pre><code>query = *( pchar / \"/\" / \"?\" )\n\npchar = unreserved / pct-encoded / sub-delims / \":\" / \"@\"\n\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n\npct-encoded = \"%\" HEXDIG HEXDIG\n\nsub-delims = \"!\" / \"$\" / \"&amp;\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n</code></pre> <p>As we can see this is not formal definition of the query string as a group of key and value pairs, but for our intended use, this is paramount.</p> <p>So we will take this extra step ourselves and extend the definition to be:</p> <pre><code>query = *( query-pair [ qconcat ] )\n\nquery-pair = pname [ \"=\" ( pvalue ) ]\n\npname = 1*( pchar )\n\npvalue = *( pchar )\n\npchar = unreserved / pct-encoded / new-delims / \":\" / \"@\"\n\nqconcat = \"&amp;\"\n\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n\npct-encoded = \"%\" HEXDIG HEXDIG\n\nnew-delims = \"!\" / \"$\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\"\n</code></pre> <p>To allow for the operators that we want to append to the query values we extend this version with the following elements:</p> <pre><code>query-pair = p-name [ \"=\" ( [ p-operator ] p-value ) ]\n\np-operator = \"!\" / \"~\"\n\np-value = empty-value / *( pchar )\n\nempty-value = \"-\"\n</code></pre> <p>The operator symbols are \"!\" and \"~\" that correspond to negation respectively fuzzy matching of the query value.</p> <p>Explicitly, when encountering a URL parameter value that has a \"!\" symbol in front of it we mean it as \"different than\". Similarly when encountering a value prepended with the \"~\" symbol, we mean it as \"similar with\" in a textual manner.</p> <p>Additionally we added a nil value symbol \"-\" that can be used for operations where the value to compare against is absent.</p> <p>Please look at the following examples to see how these rules apply and compound with one another:</p> <pre><code>?element=value\n// resources matching exactly \"value\"\n\n?element=1&amp;element=2\n// resources matching exactly \"1\" or \"2\"\n\n?element=!value1\n// resources inversly matching \"value1\"\n\n?element=!1&amp;element=!2\n// resources inversly matching \"1\" and \"2\"\n\n?element=~fuzzy\n// resources fuzzy matching \"fuzzy\"\n\n?element=~one&amp;element=~two\n// resources fuzzy matching \"one\" or \"two\"\n\n?element=-\n// resources matching empty element values\n\n?element=!-\n// resources matching all non empty element values\n</code></pre>"},{"location":"fep/6606/#applicability-for-activitypub-collections","title":"Applicability for ActivityPub Collections","text":"<p>In this ection we will define how we are going to apply this newly defined schema to use URL query parameters to filter ActivityPub collections.</p> <p>We will assume a random ActvityPub collection can be found at the folowing URL:</p> <pre><code>https://example.com/random-collection\n</code></pre> <p>The response to a GET request to this IRI could return something like:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/random-collection\",\n \"name\": \"Random Collection\",\n \"type\": \"OrderedCollection\",\n \"updated\": \"2021-04-09T08:16:05Z\",\n \"first\": \"https://example.com/random-collection?maxItems=10\",\n \"totalItems\": 12,\n \"orderedItems\": [ /* skipping items for brevity */ ]\n}\n</code></pre> <p>The filtering for the elements in the collection will be done by using URL query parameters with the names corresponding to the different properties that we want to filter on for the objects in the collection.</p> <p>Example:</p> <pre><code>https://example.com/random-collection?type=Place\n\n// will return only objects with the type `Place` that exist in the\n// Random Collection.\n\nhttps://example.com/random-collection?summary=~test%20example\n\n// will return only objects containing a summary property that contains\n// the \"test example\" text.\n</code></pre>"},{"location":"fep/6606/#implementations","title":"Implementations","text":"<p>The grammar described in this document represents a simplified version of the mechanism that services using the [GoActivityPub] library use for their C2S addressing.</p>"},{"location":"fep/6606/#references","title":"References","text":"<ul> <li>[RFC-3987] M. Duerst, M. Suignard Internationalized Resource Identifiers, 2005</li> <li>[RFC-3986] T. Berners-Lee Query component of URIs, 2005</li> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[Client to Server Interactions] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> <li>[GoActivityPub] M. Orcsik, Github</li> </ul>"},{"location":"fep/6606/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/67ff/","title":"FEP-67ff: FEDERATION.md","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>FINAL</code> 2023-09-05 2024-09-22 #157 Discussions codeberg"},{"location":"fep/67ff/#summary","title":"Summary","text":"<p><code>FEDERATION.md</code> is a file containing information necessary for achieving interoperability with a federated service. It was originally proposed by Darius Kazemi on SocialHub forum in Documenting federation behavior in a semi-standard way? topic.</p>"},{"location":"fep/67ff/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/67ff/#structure","title":"Structure","text":"<p>The <code>FEDERATION.md</code> file can have arbitrary structure and content. The only requirements are:</p> <ul> <li>It MUST be a valid Markdown document.</li> <li>It MUST be located in the root of a project's code repository. If project's documentation is located in another place, the <code>FEDERATION.md</code> file may contain a link to that location.</li> <li>It SHOULD include a list of implemented federation protocols.</li> <li>It SHOULD include a list of supported Fediverse Enhancement Proposals (FEPs).</li> </ul>"},{"location":"fep/67ff/#template","title":"Template","text":"<p>(This section is non-normative.)</p> <pre><code># Federation\n\n## Supported federation protocols and standards\n\n- [ActivityPub](https://www.w3.org/TR/activitypub/) (Server-to-Server)\n- [WebFinger](https://webfinger.net/)\n- [Http Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures)\n- [NodeInfo](https://nodeinfo.diaspora.software/)\n\n## Supported FEPs\n\n- [FEP-67ff: FEDERATION.md](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md)\n\n## ActivityPub\n\n&lt;!-- Describe activities and extensions. --&gt;\n\n## Additional documentation\n\n&lt;!-- Add links to documentation pages. --&gt;\n</code></pre>"},{"location":"fep/67ff/#implementations","title":"Implementations","text":"<ul> <li>gathio</li> <li>Streams</li> <li>Smithereen</li> <li>Mastodon</li> <li>Hometown</li> <li>Mitra</li> <li>Emissary</li> <li>Vervis</li> <li>WordPress</li> <li>Postmarks</li> <li>Bovine in repo and the symlink</li> <li>BookWyrm</li> <li>Hatsu</li> <li>tootik</li> <li>Bridgy Fed</li> <li>Friendica</li> <li>PieFed</li> <li>Akkoma</li> <li>Iceshrimp.NET</li> <li>Forte</li> <li>NeoDB</li> <li>FIRM</li> <li>Vernissage</li> <li>apkit</li> <li>Tvmarks</li> <li>Manyfold</li> <li>Cryap</li> <li>ActivityPub Fuzzer</li> <li>Comments (Raw)</li> <li>Loops</li> <li>snac</li> <li>squidcity</li> <li>badgefed</li> <li>Agora</li> <li>Ktistec</li> </ul>"},{"location":"fep/67ff/#references","title":"References","text":"<ul> <li>Darius Kazemi, Documenting federation behavior in a semi-standard way?, 2020</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> </ul>"},{"location":"fep/67ff/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/6fcd/","title":"FEP-6fcd: Account Export Container Format","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop <code>DRAFT</code> 2024-07-11 #357 Discussions codeberg"},{"location":"fep/6fcd/#summary","title":"Summary","text":"<p>This FEP describes a lightweight general purpose account export container format, with the following properties:</p> <ul> <li>General purpose, allowing for easy adaptation of existing ActivityPub, social media, and cryptographic key material export formats</li> <li>Extensible, upgradable, and self-documenting (in the human-readable sense)</li> <li>Works with FEP-7952: Roadmap for Actor and Object Portability</li> <li>Serves as a concrete serialization of the result of the Export operation described in FEP-9091: Export Actor Service Endpoint</li> </ul> <p>Out of scope:</p> <ul> <li>Encryption -- handled in a separate layer</li> <li>Compression -- handled in a separate layer (how to turn a <code>.tar</code> file into a <code>.tar.gz</code> is well known)</li> </ul>"},{"location":"fep/6fcd/#inspirations-and-prior-art","title":"Inspirations and Prior Art","text":"<ul> <li>(Undocumented) Mastodon Account Export features</li> <li>IndieWeb Blog Archive Format</li> <li>WordPress Export Format</li> <li>DIF Universal Wallet Backup Containers spec (in progress)</li> </ul>"},{"location":"fep/6fcd/#overall-concept","title":"Overall Concept","text":"<ol> <li>Serialize export data into files and directories</li> <li>Add a lightweight <code>manifest.yml</code> file that describes what's in the files and directories</li> <li>Wrap everything into a <code>.tar</code> file.</li> </ol>"},{"location":"fep/6fcd/#manifest-file","title":"Manifest File","text":""},{"location":"fep/6fcd/#reserved-properties","title":"Reserved Properties","text":"<ul> <li>(Required) <code>ubc-version</code>: Version of the Account Export Container Format spec</li> <li>(Required) <code>contents</code>: A listing of files and directories in this .tar file</li> <li>(Optional) <code>meta</code>: A metadata section describing who this export belongs to, what app or service created it, and so on.</li> </ul>"},{"location":"fep/6fcd/#activitypub-export-example","title":"ActivityPub Export Example","text":"<p>Example result of exporting an ActivityPub account:</p> <pre><code>$ tar -vtf ap-account-export-2024-06-11.tar\n\n-rw-rw-r-- 0 0 1K Jun 11 15:38 manifest.yml\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 activitypub/\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 key/\n</code></pre> <p>Example corresponding <code>manifest.yml</code> file:</p> <pre><code># (Required) Universal Backup Container spec version\nubc-version: 0.1\n\n# (Optional) Metadata section\nmeta:\n created: 2024-01-01\n createdBy:\n # (Optional) URL to a Controller document, such as an ActivityPub profile using FEP-521a\n # @see https://codeberg.org/fediverse/fep/src/branch/main/fep/521a/fep-521a.md\n controller: https://alice-personal-site.example/actor\n # (Optional) The app or service that created this export\n client:\n name: \"Example Exporter App\"\n url: https://codeberg.example.com/example-export-app\n\n# (Required, but can be empty) Contents section, listing the other files and directories\ncontents:\n # This file\n manifest.yml:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#manifest-file\n # Directory with ActivityPub-relevant exports\n activitypub:\n contents:\n # Serialized ActivityPub Actor profile\n actor.json:\n url: https://www.w3.org/TR/activitypub/#actor-objects\n # ActivityStreams OrderedCollection representing the contents of the actor's Outbox\n outbox.json:\n url: https://www.w3.org/TR/activitystreams-core/#collections\n following_accounts.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n followers.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n lists.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n bookmarks.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n blocks.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n mutes.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n # Directory of object attachments (post images, etc)\n attachments:\n url: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment\n contents:\n # Actor profile avatar\n avatar.jpg:\n url: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-icon\n # 'key' dir, serialized private/public key pairs,\n # such as those declared in a FEP-521a Actor profile\n key:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/521a/fep-521a.md\n contents:\n key-1234.json:\n url: https://www.w3.org/TR/vc-di-eddsa/#representation-eddsa-rdfc-2022\n</code></pre> <p>Example exported key file:</p> <pre><code>$ cat key/key-1234.json\n</code></pre> <pre><code>{\n \"@context\": [\"https://w3id.org/security/multikey/v1\"],\n \"type\": \"Multikey\",\n \"id\": \"https://alice-personal-site.example/actor#key1234\",\n \"controller\": \"https://alice-personal-site.example/actor\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"privateKeyMultibase\": \"z3u2en7t5LR2WtQH5PfFqMqwVHBeXouLzo6haApm8XHqvjxq\"\n}\n</code></pre>"},{"location":"fep/6fcd/#example-blog-archive-format-export","title":"Example Blog Archive Format Export","text":"<pre><code>$ tar -vtf bar-account-export-2024-06-11.tar\n\n-rw-rw-r-- 0 0 1K Jun 11 15:38 manifest.yml\n-rw-rw-r-- 0 0 100K Jun 11 15:38 index.html\n-rw-rw-r-- 0 0 50K Jun 11 15:38 feed.json\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 uploads/\n</code></pre> <p>Example corresponding <code>manifest.yml</code> file:</p> <pre><code>ubc-version: 0.1\n\nmeta:\n created: 2024-01-01\n\ncontents:\n # This file\n manifest.yml:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#manifest-file\n index.html:\n url: https://indieweb.org/blog_archive_format\n feed.json:\n url: https://indieweb.org/blog_archive_format\n uploads:\n url: https://indieweb.org/blog_archive_format\n</code></pre>"},{"location":"fep/6fcd/#references","title":"References","text":"<ul> <li>FEP-521a: Representing actor's public keys</li> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Dave Longley, Manu Sporny, Data Integrity EdDSA Cryptosuites v1.0, 2023</li> </ul>"},{"location":"fep/6fcd/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/73cd/","title":"FEP-73cd: Migration User Stories","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge &lt;@learningproof.xyz&gt; / @by_caballero@mastodon.social <code>DRAFT</code> 2024-02-07 #265 - codeberg"},{"location":"fep/73cd/#summary","title":"Summary","text":"<p>In the interest of clarifying and aligning on the problem-space of user account migration, multiple-account management, and export/import/migration of content/activity history, these user stories are offered to organize discussion and solution-sharing.</p>"},{"location":"fep/73cd/#contributing","title":"Contributing","text":"<p>Any one of these user stories could be further elaborated in the spirit of the SWAT exercise that served as ActivityPub's initial \"definition of done\", perhaps in a separate FEP and/or SocialHub thread.</p>"},{"location":"fep/73cd/#migration-user-stories","title":"Migration User Stories","text":"<p>PRs welcome! Add or update!</p> <p>1, 2, and 3 describe follower/profile migration, while 4,5,6 and 7 describe migration of posted activities.</p> <ol> <li>Alice wants to move her account from Alpha to Gamma, both of which are online and federated to one another, without losing her follow relationships in either direction. Four possible variants, not mutually exclusive:<ul> <li>A. Alice would like her account on Alpha terminated with some kind of human-readable redirect, i.e., links to old Alice@Alpha content display a warning that \u201cAlice doesn't live here anymore\u201d.</li> <li>B. Alice would like Alpha to dynamically redirect any links to Alice@Alpha or to any specific content she posted/generated there to a reasonable default \u201chomepage\u201d for Alice@Gamma.</li> <li>C. Alice would like Alpha to dynamically redirect any links to Alice@Alpha content to the migrated contents @Gamma (i.e. 301 HTTP codes and nginx-style URL rewrites).</li> <li>D. Alice would like her account on Alpha to remain active and accept new posts as a personal account, but would like her followers to know about the new professional account as well. A selection of professional posts from Alpha will be ported over so that her new professional account can carry over a little backhistory (with a disclaimer that they were originally uploaded to Alpha).</li> <li>E. In addition to her followers and followees, Alice would like to bring her \"unfollows\" from Alpha to Gamma, i.e. the specific accounts she has manually blocked or muted.</li> <li>F. In addition to her followers, followees, and \"unfollows,\" Alice would like to bring her Alpha's carefully-curated \"defederation list,\" i.e. its server-level blocklist, from Alpha to Gamma, since Gamma is a younger and less robustly administered community than Alpha.</li> </ul> </li> <li>Bob is asked to leave Alpha by its moderation team, who have disabled new posts on that account but are allowing Bob to execute a one-way, permanent migration to a new server of Bob's choosing as a courtesy. Bob logs in to Alpha and permanently migrates to a new server with which Alpha is federated, allowing a server-to-server connection. Bob instead finds a new home on the server Beta, which is specifically de-federated by Alpha for incompatible moderation policies. Bob would like to announce to his followers his new account, without Alpha and Beta having to communicate with one another (since Alpha refuses connections with Beta altogether).</li> <li>Charlie would like to move his profile, followers, and his years of original content to Gamma from Delta, because the latter was recently and unexpectedly taken offline by government intervention. Before going dark, Gamma had already authorized a custom client for Charlie, which he used to sign each posts with a self-managed private key, and Charlie had backed up his followers/following lists. Delta is able to load all of the above and authenticate them to display on his new account.</li> <li>Bob would also like to move his previous content from Alpha to Beta, which are defederated. After having his service partially limited, he is able to export his archive of Alpha content (including media uploads but no moderation records, as Alpha does not generate any) and import it to Beta to be considered for republication.</li> <li>Charlie also managed to expert an archive of his Gamma content after authorizing Charlie's custom client (including media uploads and moderation records), and is able to use said client to authenticate this content to Delta, who can consider it for republication despite Gamma being offline.</li> <li>After using Delta for some time, Charlie moves on to server Epsilon, which Daniel wants to migrate to from Delta. Epsilon has no moderation policy because Daniel is its only user and he has full admin rights over it. Epsilon loads a recent backup, skipping over the moderation policies exported by Delta. </li> <li>At time of import, Delta does not support all the same features and Activity types that Gamma did at time of export. Two variants, not mutually exclusive:<ul> <li>A. Delta's import wizard warns Charlie to keep his backup and try again later. Years later, he does, and additional content is imported now that Delta supports a bigger subset of Gamma's Activity types, without duplicating the content previously imported.</li> <li>B. Delta stored all the un-imported Gamma Activities in a separate archive for Charlie. Years later, when Charlie exports this historic data along with Delta data to Epsilon, both the un-imported Gamma content, the imported Gamma content, and the newer Delta content alike get included in his new backup, and it all gets imported to Epsilon.</li> </ul> </li> </ol>"},{"location":"fep/73cd/#use-case-to-fep-mapping","title":"Use-Case to FEP Mapping","text":"<p>Note: this section is largely subjective and intended as an aid to decision-making, NOT as any kind of definitive statement about the utility or necessity of other FEPs. Feel free to PR in links to blog posts or other tutorials that explain a given implementation of any user stories to the \"profile\" row in the form <code>[1](&lt;link&gt;), [2](&lt;link&gt;)...</code></p> <ul> <li>Key:</li> <li>[R]equired</li> <li>[H]elpful</li> <li>[U]nknown</li> <li>[1/2/3] Optionality sets</li> </ul> Use cases: 1A 1B 1C 1D 2 3 4 5 6 7A 7B FEP-7628 \"Move Actor\" (<code>push</code> mode) R1 R1 R FEP-7628 \"Move Actor\" (<code>pull</code> mode) R2 R FEP-c390 \"Identity Proofs\" H H R FEP-ae97 \"Client-Side Activity Signing\" R FEP-ef61 \"Portable Objects\" R R H H FEP-8b32 \"Object Integrity Proofs\" (req'd by ^) R^ R^ H^ H^ FEP-521a \"Actor's Public Keys\" H H H H FEP-TBD [Per-user? Per-server? both?] \"Moderation Records\" R H FEP-TBD \"Forwarding and Redirecting Migrated Actors\" H R R? H H R FEP-TBD \"Activity Archives/Export Format\" R R Profiles needed to combine the above?"},{"location":"fep/73cd/#open-questions","title":"Open Questions","text":"<ul> <li>arbitrary Actor Metadata k/v pairs as per FEP-fb2a might be useful as a legacy/fallback for some of the above?</li> <li>likewise the custom TXT record that FEP-612d proposes for mapping domains to actors might be a useful way of doing \"migration from dead server\" or some such?</li> <li>exporting activity-histories should probably also export histories of each activity in that activity history, as per FEP-bad1, Object History collection</li> <li>do FEP-bad1 histories span multiple services?<ul> <li>if so, does \"this content was originally uploaded to another server\" belong in a new FEP or what?</li> </ul> </li> <li>migrating accounts should probably migrate PENDING follows as per FEP-4ccd, and/or do a full follower sync as per FEP-8fcf</li> </ul>"},{"location":"fep/73cd/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[ABC] Alyssa P. Hacker, An example proposal, 2020</li> </ul>"},{"location":"fep/73cd/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/7458/","title":"FEP-7458: Using the replies collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2024-04-26 #307 - codeberg"},{"location":"fep/7458/#summary","title":"Summary","text":"<p>Replies are not defined in ActivityPub, as there are no specified side effects for simply encountering objects or activities with <code>inReplyTo</code> set. Most current implementations implicitly reconstruct replies at consumption time, and may silently and implicitly maintain the <code>replies</code> collection at the producer's side. This FEP provides guidance for explicitly using the <code>replies</code> collection, and explicitly managing it.</p>"},{"location":"fep/7458/#problem-statement","title":"Problem statement","text":"<p>(This section is non-normative.)</p> <p>ActivityPub defines side effects for activities, not side effects for encountering objects. One popular use-case in the social web is replying to an object with your own object; this indicates that your object is a response to the other object. However, this is about as far as most implementations go in maintaining the \"reply\" relation. It is generally assumed that objects will be delivered to other servers via a Create activity, and that the object of that Create activity will be cached, and that the \"reply\" will somehow be tracked and attached to the referent object. The ActivityPub spec only mentions \"replies\" in one place, in the context of inbox forwarding as intended to avoid the problem of \"ghost replies\". The implication of this is that senders are expected to address all relevant parties ahead-of-time, as a matter of convention.</p> <p>What is missing is a mechanism for managing replies that would recognize the canonicity and authority of the actor managing their own replies explicitly, rather than depending on implicit reconstruction after-the-fact. FEP-5624 takes a different approach that doubles down on this implicit reconstruction.</p> <p>Meanwhile, FEP-7888 specifies a similar mechanism for managing contexts that recognizes the canonicity and authority of the actor managing the conversation explicitly, rather than depending on implicit reconstruction after-the-fact. Implicit reconstruction is usually based on reply trees, but explicit construction is based on Add and Remove activities. The current FEP aims to be written in a way that supports explicit <code>replies</code> management while being orthogonal to the use of <code>context</code>. In other words, the authoring actor has the option to use <code>inReplyTo</code> as metadata, while separately choosing to participate in a <code>context</code> or not.</p>"},{"location":"fep/7458/#example-flow","title":"Example flow","text":"<p>(This section is non-normative.)</p> <p>Alice makes a post:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/1\",\n \"summary\": \"Alice created a note\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://alice.example/objects/1\",\n \"type\": \"Note\",\n \"content\": \"Hello\",\n \"replies\": \"https://alice.example/objects/1/replies\"\n },\n \"cc\": [\"https://alice.example/followers\", \"as:Public\"]\n}\n</code></pre> <p>Bob makes a reply:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://bob.example/outbox/2\",\n \"summary\": \"Bob replied to Alice's note\",\n \"actor\": \"https://bob.example\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://bob.example/objects/2\",\n \"type\": \"Note\",\n \"content\": \"Hi!\",\n \"inReplyTo\": \"https://alice.example/objects/1\"\n },\n \"to\": \"https://alice.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n</code></pre> <p>Alice explicitly approves Bob's reply, either automatically or manually. Alice forwards Bob's activity to Alice's followers, as requested by Bob. Alice also adds Bob's reply to the replies collection:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/3\",\n \"summary\": \"Alice added Bob's note as a reply\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Add\",\n \"object\": \"https://bob.example/objects/2\",\n \"target\": \"https://alice.example/objects/1/replies\",\n \"to\": \"https://bob.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n</code></pre> <p>Bob sees this and updates the original reply to something offensive:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://bob.example/outbox/4\",\n \"summary\": \"Bob updated a note\",\n \"actor\": \"https://bob.example\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://bob.example/objects/2\",\n \"type\": \"Note\",\n \"content\": \"Alice sucks lol\",\n \"inReplyTo\": \"https://alice.example/objects/1\"\n },\n \"to\": \"https://alice.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n</code></pre> <p>Perhaps Alice forwards this activity automatically, or perhaps not; at some later point, Alice notices that the reply has been edited. Alice therefore decides to remove the reply:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/5\",\n \"summary\": \"Alice removed a reply\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Remove\",\n \"object\": \"https://bob.example/objects/2\",\n \"target\": \"https://alice.example/objects/1/replies\",\n \"to\": \"https://bob.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n</code></pre> <p>Bob maliciously chooses not to forward this removal activity.</p> <p>Charlie follows Bob, but not Alice. Charlie's client has received activities 2, 3, and 4 in Charlie's inbox.</p> <p>Under current behavior, Charlie would have fetched Alice's post, but not have checked Alice's <code>replies</code> collection or used it in any way. Bob's reply would still be visible as a reply to Alice's post.</p> <p>Under proposed behavior, Charlie would instead defer to checking Alice's <code>replies</code> collection. Bob's reply would not be contained in that collection, as Alice has removed it. Therefore, Charlie does not see or treat Bob's reply as a verified reply.</p>"},{"location":"fep/7458/#behavioral-requirements","title":"Behavioral requirements","text":"<ul> <li>When encountering an object with <code>inReplyTo</code> set, ActivityPub clients SHOULD verify that the current object is indeed a valid member of the <code>inReplyTo.replies</code> collection. (A further FEP may provide a mechanism to establish proof of this without having to check the entire collection.)<ul> <li>If the current object is not present in the replies collection, then ActivityPub clients MAY choose to mark this reply as unverified, or otherwise hide it entirely when presenting replies from a cache.</li> </ul> </li> <li>Upon encountering an activity or its object where <code>inReplyTo</code> is set to some object <code>attributedTo</code> the current actor, an ActivityPub actor SHOULD send an activity with the following properties:<ul> <li><code>type</code> array MUST include <code>Add</code></li> <li><code>object</code> MUST be the object declaring an <code>inReplyTo</code></li> <li><code>target</code> MUST be your own object's <code>replies</code> collection</li> <li>the activity's addressing properties SHOULD include the <code>object.attributedTo</code> actor, as well as potentially their <code>followers</code>, in addition to any other addressed actors (such as your own <code>followers</code> or some other <code>audience</code>)</li> </ul> </li> <li>ActivityPub actors SHOULD signal removal of an object from a <code>replies</code> collection that they own by sending an activity with the following properties:<ul> <li><code>type</code> array MUST include <code>Remove</code></li> <li><code>object</code> MUST be the object declaring an <code>inReplyTo</code></li> <li><code>target</code> MUST be your own object's <code>replies</code> collection</li> <li>the activity's addressing properties SHOULD include the <code>object.attributedTo</code> actor, as well as potentially their <code>followers</code>, in addition to any other addressed actors (such as your own <code>followers</code> or some other <code>audience</code>)</li> </ul> </li> </ul>"},{"location":"fep/7458/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Dennis Schubert, ActivityPub: One protocol to rule them all? # Replies and Interactions, 2018</li> <li>Claire, FEP-5624: Per-object reply control policies, 2022</li> <li>a, FEP-7888: Demystifying the context property, 2023</li> </ul>"},{"location":"fep/7458/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/7502/","title":"FEP-7502: Limiting visibility to authenticated actors","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2023-12-24 #218 - codeberg"},{"location":"fep/7502/#summary","title":"Summary","text":"<p>Some servers require authentication for all requests made via ActivityPub, even for GET requests on public objects addressed to <code>as:Public</code>. This violates the requirement that anything addressed to <code>as:Public</code> is made available without requiring authentication. This FEP proposes an alternative addressing that may be used in such scenarios, signaling that the object is not fully public but is otherwise available to any actor.</p>"},{"location":"fep/7502/#prior-art","title":"Prior art","text":"<p>(This section is non-normative.)</p> <p>ActivityPub issue 339 discusses the shortcomings of <code>as:Public</code> and proposed a new delivery/addressing target of <code>as:Authenticated</code>, but this proposal was never adopted. The distinction between the two is as follows:</p> <p>Public : Available to anyone without authentication</p> <p>Authenticated : Available to any actor after authentication</p> <p>Issues raised with this approach include:</p> <ul> <li>the lack of semantic clarity around addressing pseudo-collections (including as:Public) as it relates to post-delivery use cases</li> <li>the ease of creating puppet actors making this ineffective for anti-abuse use cases</li> <li>the potential for tracking authenticated fetches that may occur</li> </ul> <p>The concern with semantic clarity is unaddressed, as replacing as:Public with a different mechanism to signal fully public objects is out-of-scope for this FEP. However, this mechanism is still useful for addressing different concerns:</p> <ul> <li>signaling an intent to disallow unauthenticated access, instead of lying about the object being fully \"public\"</li> <li>a minimal level of tracking authentication being desirable in cases where blocks are to be enforced, by adding friction to fetching</li> </ul> <p>In WAC, there is a distinction made between an \"agent\" (<code>http://xmlns.com/foaf/0.1/Agent</code>) and an \"authenticated agent\" (<code>http://www.w3.org/ns/auth/acl#AuthenticatedAgent</code>). This FEP proposes an addressing target for the latter.</p>"},{"location":"fep/7502/#proposal","title":"Proposal","text":"<p>In addition to collections and objects, Activities may additionally be addressed to a special \"authenticated\" collection, with the identifier <code>http://www.w3.org/ns/auth/acl#AuthenticatedAgent</code>.</p> <p>Activities addressed to this special URI shall be accessible to all actors, with authentication. Implementations MUST NOT deliver to the \"authenticated\" special collection; it is not capable of receiving actual activities.</p> <p>An activity SHOULD NOT be addressed to both \"public\" and \"authenticated\". In such a case, addressing to \"authenticated\" has no effect while also addressed to \"public\".</p>"},{"location":"fep/7502/#overview","title":"Overview","text":"<ul> <li>ActivityPub Clients MAY author an activity that is addressed <code>to</code> or <code>cc</code> or includes in the <code>audience</code> the \"authenticated agents\" special identifier.</li> <li>ActivityPub Servers receiving such an activity addressed to \"authenticated agents\" MUST enforce some method of authenticating actors on fetch, if the activity is persisted. (No specific method is required by this FEP, but in most practical cases it is currently sufficient to use the HTTP Signatures Cavage draft and sign the GET request with a domain-wide internal fetch actor representing the service running on that domain.)</li> <li>ActivityPub Federated Servers that operate as an \"instance\" receiving such an activity addressed to \"authenticated agents\" MUST NOT serve representations of the received activity to any logged-out users.</li> </ul>"},{"location":"fep/7502/#implementations","title":"Implementations","text":"<p>Pixelfed intends to implement this FEP when \"signed fetch\" is enabled, such that the resulting data returned after a signed fetch does not include <code>as:Public</code>.</p>"},{"location":"fep/7502/#terms-defined","title":"Terms defined","text":"Authenticated URI <code>http://www.w3.org/ns/auth/acl#AuthenticatedAgent</code> Label Anyone authenticated Subclass of Agent Is defined by Basic Access Control ontology"},{"location":"fep/7502/#references","title":"References","text":"<ul> <li>ActivityPub Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>339 kaniini, as:Authenticated proposal, 2019</li> <li>WAC Sarven Capadisli, Tim Berners-Lee, Henry Story, Web Access Control, 2023</li> </ul>"},{"location":"fep/7502/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/7628/","title":"FEP-7628: Move actor","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> informational 2023-09-20 #168 Discussions codeberg"},{"location":"fep/7628/#summary","title":"Summary","text":"<p>Migration of followers from one ActivityPub actor to another.</p>"},{"location":"fep/7628/#history","title":"History","text":"<p><code>Move</code> activity is defined in Activity Vocabulary.</p> <p>Mastodon started using <code>Move</code> activity for migrating accounts in 2019. The activity is sent by the old server to actor's followers and actors who receive this activity un-follow the old account and follow the new account.</p>"},{"location":"fep/7628/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/7628/#linked-actors","title":"Linked actors","text":"<p>One persona can be represented by one or more actors. Different actors are considered associated with one persona if and only if they are linked. Possible ways to link actors:</p> <ul> <li><code>alsoKnownAs</code> property of an actor object. This method is the most popular, it is used by Mastodon and many other ActivityPub servers.</li> <li>FEP-c390 identity proofs. Actors are linked if they have identity proofs with the same <code>subject</code> property.</li> <li>rel-me links. Actors are linked if their profile metadata contains verified rel-me links pointing to the same website.</li> </ul>"},{"location":"fep/7628/#migration","title":"Migration","text":"<p>Among actors associated with a persona, there MUST be one primary actor. Migration of an account means the change of a primary actor.</p>"},{"location":"fep/7628/#move-activity","title":"Move activity","text":"<p><code>Move</code> activity is used to notify other actors about the migration. It MUST have the following properties:</p> <ul> <li><code>type</code>: the type of activity MUST be <code>Move</code>.</li> <li><code>actor</code>: the actor performing the migration.</li> <li><code>object</code>: the old primary actor ID.</li> <li><code>target</code>: the new primary actor ID.</li> </ul> <p>The activity is considered valid if <code>object</code> and <code>target</code> actors are linked.</p> <p>Upon receiving valid <code>Move</code> activity, all actors following <code>object</code> MAY un-follow it by sending <code>Undo(Follow)</code> activity and MUST either send <code>Follow</code> activity to the <code>target</code> or otherwise notify the user that their contact has moved.</p> <p>This activity comes in two sub-types:</p> <ol> <li><code>Move</code> sent by the old actor (push mode). In this case, the old server needs to be online.</li> <li><code>Move</code> sent by the new actor (pull mode). In this case, the old server doesn't need to be online, but the person using the actor needs to have a reserve copy of the follower list.</li> </ol> <p>Example (activity subtype 1, push mode):</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://server1.example/activities/9b496346-fa69-40ac-bc4e-7ac06192abe1\",\n \"type\": \"Move\",\n \"actor\": \"https://server1.example/users/alice\",\n \"object\": \"https://server1.example/users/alice\",\n \"target\": \"https://server2.example/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://server1.example/users/alice/followers\"\n}\n</code></pre> <p>Example (activity subtype 2, pull mode):</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://server2.example/activities/9b496346-fa69-40ac-bc4e-7ac06192abe1\",\n \"type\": \"Move\",\n \"actor\": \"https://server2.example/users/alice\",\n \"object\": \"https://server1.example/users/alice\",\n \"target\": \"https://server2.example/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://server2.example/users/alice/followers\"\n}\n</code></pre>"},{"location":"fep/7628/#movedto-and-copiedto-properties","title":"<code>movedTo</code> and <code>copiedTo</code> properties","text":"<p>If previous primary actor was deactivated after migration, it MUST have <code>movedTo</code> property containing the ID of the new primary actor. Publishers SHOULD NOT deliver activities to actor's inbox if <code>movedTo</code> property is present.</p> <p>If previous primary actor is not deactivated, <code>copiedTo</code> property MUST be used.</p> <p>After adding the <code>movedTo</code> or <code>copiedTo</code> property, the actor SHOULD notify its followers about the change by publishing an <code>Update</code> activity.</p> movedTo <p>The location of the new primary actor. The actor containing this property should be considered inactive.</p> <ul> <li>URI: <code>https://w3id.org/fep/7628#movedTo</code></li> </ul> copiedTo <p>The location of the new primary actor.</p> <ul> <li>URI: <code>https://w3id.org/fep/7628#copiedTo</code></li> </ul>"},{"location":"fep/7628/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>silverpill, FEP-c390: Identity Proofs, 2022</li> <li>indieweb.org editors, rel-me, 2013</li> </ul>"},{"location":"fep/7628/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/76ea/","title":"FEP-76ea: Conversation Threads","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org <code>DRAFT</code> 2024-10-04 #407 Discussions codeberg"},{"location":"fep/76ea/#summary","title":"Summary","text":"<p>This FEP defines a way to identify the conversation thread of an object with Activity Streams 2.0.</p>"},{"location":"fep/76ea/#motivation","title":"Motivation","text":"<p>Threaded conversations are a common data structure for social software. This is defined as a tree with the original post at its root, replies to that post as child nodes, all replies to those replies as their children, and so on recursively.</p> <p>Some social software restricts the depth of the thread, while others allow for unlimited depth.</p> <p>Identifying the thread that an AS2 object is part of allows for the construction of a conversation view of the thread.</p> <p>It is possible with Activity Streams 2.0 to construct a conversation thread by following the <code>inReplyTo</code> property of an object until the original post is found, and then expanding the <code>replies</code> property of the original post recursively. With ActivityPub, however, this can require a lot of different HTTPS requests to different servers, which can be slow and inefficient.</p> <p>This FEP defines an extension property, <code>thread</code>, that can be used to identify the conversation thread of an object.</p> <p>ActivityPub is the primary use case for Activity Streams 2.0, but not the only one. Where specific processing requirements of ActivityPub implementations are made, they are specifically noted. General processing hints for other use cases are also provided.</p>"},{"location":"fep/76ea/#user-stories","title":"User stories","text":"<p>These are some user stories for threading in conversations.</p> <ol> <li>As an ActivityPub developer, I want to be able to determine if two objects are part of the same conversation thread, so that I can display them together in a conversation view. This is possible to do by walking up the <code>inReplyTo</code> chain and following the <code>replies</code> collections, but it can be slow and inefficient. Comparing a thread identifier found in each object can be much faster.</li> <li>As an ActivityPub developer, I want to get the full conversation thread of an object efficiently, so I can show the object in context. Again, using the <code>inReplyTo</code> chain and <code>replies</code> collections can be slow and inefficient, especially if the thread is deep or has many objects.</li> <li>As an ActivityPub developer, I want to get the most-recently-added objects in a conversation thread, so I can keep my view of the thread up-to-date. This is hard to do with <code>inReplyTo</code> and <code>replies</code>, and possibly requires fetching every single <code>replies</code> collection in the tree.</li> <li>As an ActivityPub developer, I want to be able to identify the original post of a conversation thread, so I can show the thread in context. This content object is usually treated differently by clients, so it's useful to be able to identify it.</li> <li>As a commenter, I want my reply to a content object to be part of the same thread, so I am participating in a larger context.</li> <li>As the original poster, I want to be able to curate the conversation thread, so I can remove spam, off-topic, or abusive content from the thread. Maintaining a curated thread is an important service that the original poster provides. Curating the <code>replies</code> collection is not sufficient, because it only contains direct replies to the object, not the full conversation tree.</li> <li>As a user, I want to comment on or about a content object without being part of the conversation thread, so I can discuss it in my own social sphere. Not all continued conversation should be part of the original reply tree.</li> <li>As a user, I want to branch a reply into its own conversation thread, so I can have a separate conversation about a sub-topic. This is common in forums and other threaded discussion software.</li> <li>As a user, I want to graft part of a conversation thread into another thread, so I can consolidate related conversations. This is common in forums and other threaded discussion software.</li> </ol>"},{"location":"fep/76ea/#context","title":"Context","text":"<p>The context URL for this FEP is <code>https://purl.archive.org/socialweb/thread</code>.</p> <p>The context is as follows:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"thr\": \"https://purl.archive.org/socialweb/thread#\",\n \"thread\": {\n \"@id\": \"thr:thread\",\n \"@type\": \"@id\"\n },\n \"root\": {\n \"@id\": \"thr:root\",\n \"@type\": \"@id\"\n }\n}\n</code></pre>"},{"location":"fep/76ea/#terms","title":"Terms","text":"<p>The context defines two properties.</p>"},{"location":"fep/76ea/#thread","title":"<code>thread</code>","text":"<p>The <code>thread</code> property is an <code>OrderedCollection</code> that contains all of the objects in the conversation thread. The collection is ordered in reverse chronological order, with the most recent object first.</p> <p>The <code>thread</code> collection does not directly represent the tree structure of the conversation thread; it is a flat list of objects. The tree structure can be reconstructed by following the <code>inReplyTo</code> and/or <code>replies</code> properties of each object in the collection.</p> <p>The <code>thread</code> property extends the <code>context</code> property from the Activity Vocabulary.</p> <p>The <code>thread</code> property does not replace the <code>replies</code> property of an object. <code>replies</code> contains the possibly curated collection of direct replies to the object; <code>thread</code> contains the full conversation tree, up- and down-thread.</p>"},{"location":"fep/76ea/#root","title":"<code>root</code>","text":"<p>The <code>root</code> property is an <code>Object</code> that is the original post of the conversation thread. The <code>root</code> property is usually the last (earliest) object in the <code>thread</code> collection.</p> <p>This property gives an easy way for a consumer to find the root post of the thread without having to search the <code>orderedItems</code> collection, navigate through multiple <code>OrderedCollectionPage</code> pages, or traverse the <code>inReplyTo</code> properties of the objects in the collection.</p> <p>Note that <code>thread</code> and <code>root</code> are partially inverse properties. The <code>thread</code> property of the <code>root</code> property of a collection SHOULD contain the <code>id</code> of the <code>thread</code> collection. However, the <code>root</code> property of the <code>thread</code> property of an object MAY not contain the object's <code>id</code>, because the object is in the thread, but is not the root.</p>"},{"location":"fep/76ea/#behavioral-specification","title":"Behavioral specification","text":"<p>This covers recommended behavior for processors that implement the <code>thread</code> property.</p>"},{"location":"fep/76ea/#original-posts","title":"Original posts","text":"<p>When a publisher creates a new content object that is not a reply to any others, it should include a new, unique collection as its <code>thread</code> property. The collection should contain only the new object. The thread collection should be addressed to all the same addressees as the original object.</p>"},{"location":"fep/76ea/#replies-in-the-thread","title":"Replies in the thread","text":"<p>When a publisher is creating a new content object with an <code>inReplyTo</code> property, the publisher SHOULD use the <code>thread</code> property of the object being replied to as the <code>thread</code> property of the new object. The addressees of the new object should include the creator of the original post, identified by the <code>attributedTo</code> property of the original post or the <code>attributedTo</code> property of the thread collection.</p> <p>Replies can be created to multiple other objects; the <code>inReplyTo</code> property can be an array. The <code>thread</code> property can also be an array, with more or fewer values than the <code>inReplyTo</code>. Each <code>thread</code> property should correspond to the <code>thread</code> property of an object in the <code>inReplyTo</code> array.</p>"},{"location":"fep/76ea/#branching","title":"Branching","text":"<p>To branch a content object into its own conversation thread, the publisher should create an <code>Announce</code> activity that includes the new object as the <code>object</code> property. The <code>Announce</code> activity should have a new, unique <code>thread</code> property. The <code>Announce</code> activity can include a <code>content</code> property.</p>"},{"location":"fep/76ea/#grafting","title":"Grafting","text":"<p>To graft a content object into a different thread than the ones it is already part of, the publisher should create an <code>Announce</code> activity that includes the new object as the <code>object</code> property. The <code>Announce</code> activity should have the <code>thread</code> property of the new thread, and an <code>inReplyTo</code> property that matches one of the objects in the thread. The <code>Announce</code> activity can include a <code>content</code> property.</p>"},{"location":"fep/76ea/#thread-maintenance","title":"Thread maintenance","text":"<p>As with the <code>replies</code> property, the processor implementing the original post of a thread SHOULD maintain the <code>thread</code> collection by adding new objects to the collection as they are received.</p> <p>In ActivityPub, this could be done when the processor receives an object with an <code>inReplyTo</code> property that matches an object in the <code>thread</code> collection.</p> <p>To facilitate collection synchronization, the processor SHOULD distribute an <code>Add</code> activity to the audience of the original object with the new object as the <code>object</code> property and the thread as the <code>target</code> property.</p> <p>However, private replies \"down-thread\" may not be addressed to the author of the original post and may not be available to the processor for the original post.</p> <p>The processor implementing the original post MAY curate the thread collection by filtering objects from the collection. This could be done to remove spam, off-topic, or abusive content from the thread.</p> <p>In ActivityPub, if an object is removed from the thread, he processor SHOULD distribute a <code>Remove</code> activity to the audience of the original object with the new object as the <code>object</code> property and the thread as the <code>target</code> property.</p> <p>The tree structure of the thread should be maintained; every object in the thread collection, except the root, should have an <code>inReplyTo</code> property that matches the <code>id</code> of another object in the collection. If the processor removes an object from the collection, it SHOULD remove all objects that are in reply to that object, and their replies, and so on.</p> <p>The <code>replies</code> property of objects in the thread collection MAY be maintained by other processors. Curation of the <code>replies</code> collections or of the <code>thread</code> collection may mean that objects may be omitted from one collection or the other. However, the <code>replies</code> collection of the original post SHOULD be a subset of the <code>thread</code> collection.</p>"},{"location":"fep/76ea/#examples","title":"Examples","text":""},{"location":"fep/76ea/#example-1","title":"Example 1","text":"<p>An example of a <code>Note</code> object with a <code>thread</code> property:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/123\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": [\n \"https://remote.example/user/17\",\n \"https://remote.example/user/17/followers\"\n ],\n \"content\": \"I concur!\",\n \"thread\": \"https://remote.example/thread/117\",\n \"inReplyTo\": \"https://remote.example/note/117\"\n}\n</code></pre>"},{"location":"fep/76ea/#example-2","title":"Example 2","text":"<p>An example of an <code>Image</code> object with a <code>thread</code> property. The <code>Image</code> is a root or original post with no <code>inReplyTo</code> property:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/image/123\",\n \"type\": \"Image\",\n \"name\": \"A photo of a cat\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"https://example.com/user/1/followers\",\n \"url\": {\n \"type\": \"Link\",\n \"mediaType\": \"image/jpeg\",\n \"href\": \"https://example.com/image/123.jpg\"\n },\n \"replies\": \"https://example.com/replies/123\",\n \"thread\": {\n \"id\": \"https://example.com/thread/123\",\n \"to\": \"https://example.com/user/1/followers\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 4,\n \"orderedItems\": [\n {\n \"id\": \"https://fourth.example/note/721\",\n \"attributedTo\": \"https://fourth.example/user/4\",\n \"to\": [\n \"https://example.com/user/1\",\n \"https://example.com/user/1/followers\",\n \"https://other.example/user/2\"\n ],\n \"inReplyTo\": \"https://other.example/note/338\"\n },\n {\n \"id\": \"https://third.example/note/992\",\n \"attributedTo\": \"https://third.example/user/3\",\n \"to\": \"https://example.com/user/1\",\n \"inReplyTo\": \"https://example.com/image/123\"\n },\n {\n \"id\": \"https://other.example/note/338\",\n \"attributedTo\": \"https://other.example/user/2\",\n \"to\": [\n \"https://example.com/user/1\",\n \"https://example.com/user/1/followers\"\n ],\n \"inReplyTo\": \"https://example.com/image/123\"\n },\n \"https://example.com/image/123\"\n ]\n }\n}\n</code></pre> <p>Note that not all objects in the <code>thread</code> collection need to be addressed to the same audience. The audience of the <code>thread</code> collection is the audience of the original post.</p>"},{"location":"fep/76ea/#example-3","title":"Example 3","text":"<p>This is a <code>Note</code> object that is a reply to two different objects, and thus is part of two different threads.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/789\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"These are both good points.\",\n \"inReplyTo\": [\n \"https://remote.example/note/57\",\n \"https://other.example/note/456\"\n ],\n \"thread\": [\n \"https://remote.example/thread/57\",\n \"https://other.example/thread/456\"\n ]\n}\n</code></pre>"},{"location":"fep/76ea/#example-4","title":"Example 4","text":"<p>Objects in a thread that have been deleted by their author can be represented in the <code>thread</code> collection with a <code>Tombstone</code> object.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/345\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"Activity Streams 2.0 is awesome!\",\n \"replies\": \"https://example.com/replies/345\",\n \"thread\": {\n \"id\": \"https://example.com/thread/345\",\n \"to\": \"as:Public\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://third.example/note/567\",\n },\n {\n \"type\": \"Tombstone\",\n \"id\": \"https://remote.example/note/456\",\n \"inReplyTo\": \"https://example.com/note/345\",\n \"deleted\": \"2024-10-03T00:00:00Z\"\n },\n \"https://example.com/note/345\"\n ]\n }\n}\n</code></pre>"},{"location":"fep/76ea/#example-5","title":"Example 5","text":"<p>The <code>thread</code> collection can be paged, as with other collections.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/678\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"Is Wario A Libertarian?\",\n \"replies\": \"https://example.com/replies/678\",\n \"thread\": {\n \"id\": \"https://example.com/thread/678\",\n \"to\": \"as:Public\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 244780,\n \"first\": \"https://example.com/thread/678/page/12239\",\n \"last\": \"https://example.com/thread/678/page/1\"\n }\n}\n</code></pre>"},{"location":"fep/76ea/#example-6","title":"Example 6","text":"<p>The <code>root</code> property can be used to identify the original post of a thread.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/thread/654\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 457,\n \"first\": \"https://example.com/thread/654/page/23\",\n \"last\": \"https://example.com/thread/654/page/1\",\n \"root\": \"https://example.com/note/654\"\n}\n</code></pre>"},{"location":"fep/76ea/#example-7","title":"Example 7","text":"<p>To branch an object to a new conversation, an <code>Announce</code> activity is used.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/announce/123\",\n \"to\": \"as:Public\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/user/1\",\n \"thread\": \"https://example.com/thread/123\",\n \"content\": \"I think this note is important and I want to start a separate discussion about it.\",\n \"object\": {\n \"id\": \"https://example.com/note/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.org/user/2\",\n \"thread\": \"https://example.net/thread/789\",\n \"inReplyTo\": \"https://example.net/note/foo\",\n \"to\": \"as:Public\",\n \"content\": \"Trains are great.\"\n }\n}\n</code></pre>"},{"location":"fep/76ea/#example-8","title":"Example 8","text":"<p>To graft an object to an existing conversation, an <code>Announce</code> activity is used.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/announce/456\",\n \"to\": \"as:Public\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/user/1\",\n \"thread\": \"https://social.example/thread/222\",\n \"inReplyTo\": \"https://social.example/note/888\",\n \"content\": \"This comment about trains from another thread seems relevant here.\",\n \"object\": {\n \"id\": \"https://example.com/note/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.org/user/2\",\n \"thread\": \"https://example.net/thread/789\",\n \"inReplyTo\": \"https://example.net/note/foo\",\n \"to\": \"as:Public\",\n \"content\": \"Trains are great.\"\n }\n}\n</code></pre>"},{"location":"fep/76ea/#security-considerations","title":"Security Considerations","text":"<p>Not all objects in the <code>thread</code> collection may be addressed to the same audience. Representations of the collection SHOULD NOT include the <code>content</code> property or other sensitive information from objects in the collection that are not addressed to the recipient of the representation.</p> <p>In ActivityPub, the <code>orderedItems</code> property of the <code>thread</code> collection MAY be filtered for the recipient of the representation.</p>"},{"location":"fep/76ea/#previous-work","title":"Previous work","text":"<p>The <code>ostatus:conversation</code> property is used in Mastodon and elsewhere to identify the thread of an object, but it is not necessarily dereferenceable.</p> <p>Some implementations of ActivityPub use the <code>context</code> property to represent the thread of an object. This FEP provides a more specific property, which frees up the \"intentionally vague\" <code>context</code> property for other uses. It also avoids the confusing clash with the <code>@context</code> property of JSON-LD.</p>"},{"location":"fep/76ea/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> </ul>"},{"location":"fep/76ea/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/7888/","title":"Index","text":"<p>hide: - navigation description: \"ActivityStreams Vocabulary defines the context property, but it is \"intentionally vague\". Unfortunately, this makes the definition so vague as to be practically useless. This FEP aims to provide more guidance on possible uses of the context property, as well as formalizing some best practices.\"</p>"},{"location":"fep/7888/#fep-7888-demystifying-the-context-property","title":"FEP-7888: Demystifying the context property","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2023-03-14 #68 Discussions codeberg"},{"location":"fep/7888/#summary","title":"Summary","text":"<p>ActivityStreams Vocabulary defines the <code>context</code> property, but it is \"intentionally vague\". Unfortunately, this makes the definition so vague as to be practically useless. This FEP aims to provide more guidance on possible uses of the <code>context</code> property, as well as formalizing some best practices.</p>"},{"location":"fep/7888/#overview","title":"Overview","text":"<p>(This section is non-normative.)</p> <p>See \"Appendix A: Rationale\" for fuller analysis of the definition, as well as use cases that can be mapped onto <code>context</code>.</p> <p>In short:</p> <ul> <li>It is possible for objects to exist within implicit contexts. For example, you might group all objects sharing a certain property value. Properties like <code>context</code>, <code>audience</code> and <code>tag</code> can be used for similar purposes.</li> <li><code>context</code> roughly corresponds to the label \"was created in relation to\". Compare to <code>audience</code>, which roughly corresponds to the label \"is considered relevant to\". Compare also to <code>tag</code>, which roughly corresponds to the label \"is associated with a topic of\".</li> <li>An explicit <code>context</code> embodies purpose. Things grouped by the same <code>context</code> \"belong together\" in a way that can't be said about things grouped by the same <code>tag</code>.</li> <li>Objects with a <code>context</code> exist within that context, and are meant to be seen and interpreted in context of something else. In most cases, you do not want to view the object on its own; it should be viewed together with other objects, contextually. Deleting a context might reasonably delete or garbage-collect objects within that context.</li> <li>You might use <code>context</code> to represent a \"thread\", \"topic\", \"conversation\", \"room\", \"channel\", \"forum\", \"wall\", \"guild\", \"space\", \"project\", or so on.</li> </ul> <p>The requirements below can be summarized like so:</p> <ul> <li>Publishers can use <code>context</code> for signaling which objects belong together purposefully, i.e. objects that are meant to be viewed or processed together. Ideally, make the <code>context</code> resolve to something useful. Depending on which properties the resolved context has, various use cases can make use of those properties.</li> <li>For example, if the context is <code>attributedTo</code> some owner, others can keep that owner in the loop when interacting with their context. Similar considerations apply to <code>followers</code> and <code>audience</code>.</li> <li>If the context has some canonical collection associated with it that represents the contents of that context, then that collection can be used for backfill, authorization, moderation, synchronization, and so on.</li> <li>Consumers can use <code>context</code> to group related objects by the context's <code>id</code>. The graph source for those objects is up to you, but in the case where your source is an SQL database, it probably makes sense to have the <code>context.id</code> be the value of a column which is indexed so you can efficiently use a WHERE clause in your query.</li> <li>Declaring a context does not imply that the context owner acknowledges that object. A canonical collection can help with verifying this for the purpose of authorization or moderation.</li> <li>Interactions with an object that has a <code>context</code> might exist within that same context, or they might declare their own context, or they might not declare a context. If you're declaring someone else's context, then you might want to keep them in the loop, in the same way that you'd keep someone in the loop if you replied to their object or tagged their object. This would be signaled via <code>context.attributedTo</code> similarly to <code>inReplyTo.attributedTo</code> or <code>tag[*].attributedTo</code>.</li> </ul>"},{"location":"fep/7888/#publishing-context","title":"Publishing <code>context</code>","text":"<p>When generating an object with a <code>context</code> as a publisher:</p>"},{"location":"fep/7888/#purpose","title":"Purpose","text":"<p>A <code>context</code> SHOULD have a purpose; consider <code>tag</code> for looser references. Objects sharing a certain <code>context</code> SHOULD be strongly related and intended to be viewed in the same grouping. Deleting the <code>context</code> MAY delete all objects within that context.</p>"},{"location":"fep/7888/#dereferencing-and-resolving","title":"Dereferencing and resolving","text":"<p>A <code>context</code> SHOULD be resolvable. The resolved object or link can describe the context with at least the additional information needed to fully process the activity or object. Examples of generally useful properties include but are not limited to:</p> <ul> <li><code>attributedTo</code> denotes the authority for that context. (This authority might be a good target for addressing and delivery of related activities.)</li> <li><code>audience</code> indicates intent for or potential interest by some entities. (These entities might be a good target for addressing and delivery of related activities.)</li> <li><code>followers</code> signals that the context might be a followable object. (This followers collection might be a good target for addressing and delivery of related activities.)</li> <li><code>outbox</code> might contain relevant Activities performed by the context, if it is an actor.</li> </ul> <p>Ideally, the resolved <code>context</code> SHOULD in some way have an associated Collection which can contain the related items. (The exact semantics of discovering this Collection are out of scope of this FEP.)</p>"},{"location":"fep/7888/#consuming-context","title":"Consuming <code>context</code>","text":"<p>When encountering an object with a <code>context</code> as a consumer or browser:</p>"},{"location":"fep/7888/#group-objects-by-context","title":"Group objects by <code>context</code>","text":"<p>At minimum, you SHOULD consider the current object alongside other objects referencing the same <code>context</code> (by <code>id</code>) instead of considering the current object independently. By default, the graph source for objects that are being considered for inclusion is arbitrary. This can be some dataset, or it can be some relevant collection's items. For example, you might conssider the outbox and/or inbox of one or more actors, or you might consider a specific property path on the <code>context</code> (if resolvable).</p>"},{"location":"fep/7888/#canonical-collections-of-objects-within-an-authoritative-context","title":"Canonical collections of objects within an authoritative context","text":"<p>If the <code>context</code> resolves to an object of a certain type, then that type MAY indicate that a certain relation represents a canonical Collection of all objects that the authority considers to be included. (The definition of such types and relations is out of scope of this FEP.)</p> <p>For authoritative contexts that include such a canonical Collection, you SHOULD NOT assume that an object has been accepted into that collection simply because it declares <code>context</code>. Consumers SHOULD make efforts to verify reverse claims of inclusion. If a client or user-agent is unable to verify this claim, then the client or user-agent SHOULD indicate to users that the object's claim of being included in the authoritative context is unverified. Criteria for establishing proof of inclusion in a collection is out of scope for this FEP, but might include:</p> <ul> <li>Viewing the collection directly and encountering the object as a collection item</li> <li>Querying the collection via some querying mechanism that allows determining if a given object is included in a collection.</li> <li>Having knowledge that the authority Added the object to the collection, with knowledge that the object wasn't subsequently Removed.</li> </ul>"},{"location":"fep/7888/#interacting-with-context","title":"Interacting with <code>context</code>","text":""},{"location":"fep/7888/#choosing-whether-to-participate-in-the-same-context","title":"Choosing whether to participate in the same context","text":"<p>When encountering an object with a <code>context</code> and choosing to author your own object or activity that interacts with this object:</p> <ul> <li>You MAY copy a <code>context</code> as-is, if you wish for your object to be included in that same context.</li> <li>You MAY set your own <code>context</code>, if you wish for your object to exist in a different context.</li> <li>You MAY remove the <code>context</code> entirely, if you wish for your object to exist on its own.</li> </ul> <p>Note that <code>context</code> can be present on either the object, the activity, or both. It is also possible for different <code>context</code> references to be placed on each. This depends on how <code>context</code> is used within a given protocol. Protocol considerations for when to use certain contexts are out-of-scope for this FEP. Protocol considerations for how to negotiate participation in someone else's context are also out-of-scope for this FEP.</p>"},{"location":"fep/7888/#keeping-relevant-entities-in-the-loop","title":"Keeping relevant entities in the loop","text":"<p>Per PUB Section 6.1 \"Client Addressing\":</p> <p>Clients SHOULD look at any objects attached to the new Activity via the <code>object</code>, <code>target</code>, <code>inReplyTo</code> and/or <code>tag</code> fields, retrieve their actor or attributedTo properties, and MAY also retrieve their addressing properties, and add these to the <code>to</code> or <code>cc</code> fields of the new Activity being created. Clients MAY recurse through attached objects, but if doing so, SHOULD set a limit for this recursion. (Note that this does not suggest that the client should \"unpack\" collections of actors being addressed as individual recipients).</p> <p>Clients MAY give the user the chance to amend this addressing in the UI.</p> <p>This FEP extends the recommendation to look at <code>object</code>, <code>target</code>, <code>inReplyTo</code>, and/or <code>tag</code> to also include <code>context</code>.</p> <p>If copying someone else's context, you SHOULD send your activity to the owner(s) of the context(s), defined via <code>context.attributedTo</code> if resolvable. This is similar to how one might address the author of an object that they are responding to via <code>inReplyTo.attributedTo</code>, as a social courtesy. You MAY also want to address <code>context.followers</code> and/or addressing properties like <code>context.audience</code>.</p>"},{"location":"fep/7888/#appendix-a-rationale","title":"Appendix A: Rationale","text":"<p>(This section is non-normative.)</p>"},{"location":"fep/7888/#the-existing-definition","title":"The existing definition","text":"<p>From the current definition in VOCAB: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-context</p> <p>Identifies the context within which the object exists or an activity was performed.</p> <p>The notion of \"context\" used is intentionally vague. The intended function is to serve as a means of grouping objects and activities that share a common originating context or purpose. An example could be all activities relating to a common project or event.</p> <p>Aside from being \"intentionally vague\", the definition is also somewhat circular; it requires knowing what a context is and having some conceptual understanding of the notion of \"context\". However, we are given some guidance towards its \"intended function\", which is to group objects by some common purpose or origin.</p>"},{"location":"fep/7888/#supporting-statements-from-spec-authors","title":"Supporting statements from spec authors","text":"<p>In a GitHub issue from ActivityStreams 2.0's development cycle, James Snell provides the following example:</p> <pre><code>{\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"scope\": {\n \"type\": \"Organization\",\n \"name\": \"My Employer\"\n },\n \"to\": [\"john@example.com\", \"sally@example.com\"],\n \"context\": {\n \"type\": \"http://example.org/types/Project\",\n \"name\": \"A Project\"\n }\n}\n</code></pre> <p>James Snell then comments that:</p> <ul> <li>The <code>scope</code> indicates that the audience for the note is only members of the Organization.</li> <li>The <code>to</code> indicates specific people who should be actively notified.</li> <li>The <code>context</code> indicates a larger context within which the note exists.</li> </ul> <p>James Snell then clarifies (emphasis added):</p> <p><code>scope</code> is not access control [...] a consuming implementation may include the note on the activity timeline of anyone associated with the \"My Employer\" organization, but it would only notify two individuals listed by the <code>to</code> property. The <code>context</code> property, on the other hand, has absolutely nothing to do with audience targeting. The above note is essentially saying, \"This is a note that was created in relation to <code>A Project</code>. Make the note available to anyone in the <code>My Employer</code> organization but specifically notify John and Sally\"</p> <p>Therefore, we can establish that <code>context</code> as a property roughly translates to a label of \"was created in relation to\".</p> <p>In a separate issue, James Snell provides another explanation:</p> <p>The context is really intended to allow objects and activities to be logically grouped. For instance, in an enterprise setting, the context may group activities by project while the scope would identify one or more teams for which the activity is considered relevant, while the to/cc fields are used to indicate specific individuals to notify.</p> <p><code>scope</code> was later renamed to <code>audience</code>, but the two properties remain closely related and are presented together in AS2-VOCAB Section 5.1.1 \"Audience and Context\":</p> <p>Activities are rarely isolated events. Often, multiple individual activities will be performed around a similar context or audience. For instance, a collaborators working on a shared project might perform multiple related activities in the process of achieving some goal. Such activities can be logically grouped together using the context property, and scoped to a particular audience using the audience property.</p>"},{"location":"fep/7888/#purpose-and-intent-or-why-not-use-a-tag","title":"Purpose and intent; or, why not use a tag?","text":"<p>We might similarly use a <code>tag</code> for grouping objects and activities. Several fediverse projects often include a <code>Hashtag</code> (defined as an extension within the ActivityStreams namespace, but not actually adopted or defined formally). This <code>Hashtag</code> signals an intent to be included or discovered through a collection of objects bearing the same <code>Hashtag</code>, uniquely identified by its <code>name</code>. The maintenance of such implicit collections is assumed to be the responsibility of the receiving server, although an <code>href</code> might be provided for convenience, in order to browse the implicit collection of tagged objects as seen from that origin server. (This also makes the <code>Hashtag</code> a sub-type of <code>Link</code>.)</p> <p>The key property of such a tag is to signal a general, implicit association by reference. We might then consider a context to be an explicit association, but such an explicit association requires an explicit definition.</p>"},{"location":"fep/7888/#the-different-types-of-context-and-how-they-are-actually-the-same","title":"The different types of context, and how they are actually the same","text":"<p>Various dictionaries define context generally as something that helps you understand the situation. Following from this, the context should be something that helps you process the activity or object. Ignoring the context may lead to misunderstanding the activity or object; the object or activity exists within that context, and should be understood in context of that context.</p> <p>Specific contexts can be thought of in several applications:</p> <ul> <li>the \"authoritative context\" is a context in which some authority can be applied;</li> <li>the \"conversational context\" is a context which represents some conversation and possibly its history;</li> <li>the \"originating context\" is a context which represents some intended starting point that you might look at first.</li> </ul> <p>We might continue to articulate further types of contexts, but the general pattern that emerges is that a context exists to form a purposeful grouping, regardless of the specific purpose. For example, if we had the notion of a conversation, then we might reasonably say that someone owns this conversation and can apply their authority to it. Looking at some object or activity within this context is generally not recommended on its own; it is better to view the entire conversation or some page of it rather than viewing a singular object.</p>"},{"location":"fep/7888/#sample-workflows-and-use-cases-involving-context","title":"Sample workflows and use-cases involving context","text":"<p>The context may be presented using the following abstractions:</p> <ul> <li>A \"topic\" in a forum presentation</li> <li>A \"conversation\" in a social networking presentation</li> <li>A \"room\" in a chatting or messaging presentation</li> <li>A \"thread\" in any of the above contexts (forum thread, social media thread, chat thread)</li> </ul> <p>Contexts may be associated with other contexts:</p> <ul> <li>A forum topic/thread may be nested in a \"forum\" or \"forum category\", and may be nested in another parent forum as a sub-forum.</li> <li>A \"wall\" on a social networking profile may contain conversations, which in turn contain the posts/comments</li> <li>A \"guild\" or \"space\" may contain multiple chat rooms with a common audience</li> </ul> <p>It is also possible to not have a context. Such objects exist only in the general context of their author (via <code>attributedTo</code>) or other implicit contexts, and are otherwise self-sufficient.</p> <p>Considerations on when to use context include:</p> <ul> <li>If deleting a context, then objects within that context might reasonably be deleted or garbage-collected since they have lost their purpose.</li> </ul>"},{"location":"fep/7888/#appendix-b-examples","title":"Appendix B: Examples","text":"<p>(This section is non-normative.)</p>"},{"location":"fep/7888/#example-1-a-minimal-example-for-grouping-objects-by-context","title":"Example 1: A minimal example for grouping objects by context","text":"<p>This example demonstrates how objects sharing the same context can be logically grouped together.</p> <p>You encounter the following object:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"&lt;some-object&gt; exists in &lt;some-context&gt;.\"\n}\n</code></pre> <p>You wish to participate in the same context, so you dereference the context in order to learn more about it:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"summary\": \"&lt;some-context&gt; is owned by &lt;context-owner&gt;.\"\n}\n</code></pre> <p>You create an object, while copying that context onto your object:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/your-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"&lt;your-object&gt; exists in &lt;some-context&gt; as well.\"\n}\n</code></pre> <p>Distribution occurs somehow; you may want to notify the <code>&lt;context-owner&gt;</code> or seek their acknowledgement of your object, but these things are out-of-scope of this example. A graph source or dataset containing these two objects may be queried for objects sharing the same context:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/results-for-your-query\",\n \"type\": \"Collection\",\n \"summary\": \"The &lt;results-for-your-query&gt; show that 2 items have a context of &lt;some-context&gt;. They are &lt;some-object&gt; and &lt;your-object&gt;.\",\n \"totalItems\": 2,\n \"items\": [\n \"https://domain.example/some-object\",\n \"https://domain.example/your-object\"\n ]\n}\n</code></pre>"},{"location":"fep/7888/#example-2-choosing-not-to-participate-in-the-same-context","title":"Example 2: Choosing not to participate in the same context","text":"<p>This example demonstrates how objects can have different contexts, indicating that they were created for different purposes. Though they may be grouped by other criteria, they do not share a primary reason for existing.</p> <p>You encounter the following object:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"&lt;some-object&gt; exists in &lt;some-context&gt;.\"\n}\n</code></pre> <p>You want to establish your own context, separately from the current object's context:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/a-different-context\",\n \"attributedTo\": \"https://domain.example/you\",\n \"summary\": \"&lt;a-different-context&gt; is owned by &lt;you&gt;.\"\n}\n</code></pre> <p>You may declare that your object is in some way a response to the object that you encountered, but because the contexts are the same, they do not share a primary grouping:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/your-object\",\n \"inReplyTo\": {\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"&lt;some-object&gt; exists in &lt;some-context&gt;.\"\n },\n \"context\": \"https://domain.example/a-different-context\",\n \"summary\": \"&lt;your-object&gt; is a response to &lt;some-object&gt;, but &lt;some-object&gt; exists in &lt;some-context&gt; while &lt;your-object&gt; exists in &lt;a-different-context&gt;.\"\n}\n</code></pre> <p>Querying replies for the original object might surface your object, but querying the context for the original object will not surface your object.</p> <p>Later, <code>&lt;some-context&gt;</code> is deleted. In some cases, <code>&lt;some-object&gt;</code> might be garbage-collected, since it has lost its reason or purpose for existing; at best, it is considered orphaned. However, <code>&lt;your-object&gt;</code> continues to exist because it was created in <code>&lt;a-different-context&gt;</code> which still exists.</p>"},{"location":"fep/7888/#example-3-encountering-multiple-contexts","title":"Example 3: Encountering multiple contexts","text":"<p>This example demonstrates how one might deal with objects that have multiple contexts.</p> <p>You encounter an object with multiple contexts:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": [\"https://domain.example/some-context\", \"https://domain.example/some-other-context\"],\n \"summary\": \"&lt;some-object&gt; exists in &lt;some-context&gt; and &lt;some-other-context&gt;.\"\n}\n</code></pre> <p>You dereference the two contexts:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"summary\": \"&lt;some-context&gt; is owned by &lt;context-owner&gt;.\"\n}\n</code></pre> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-other-context\",\n \"attributedTo\": \"https://domain.example/other-context-owner\",\n \"type\": \"Object\",\n \"summary\": \"&lt;some-other-context&gt; is owned by &lt;other-context-owner&gt;.\"\n}\n</code></pre> <p>As a third-party observer, you can choose to browse either context.</p> <p>As a third-party interactor, you can choose to declare an object in either context, both contexts, a different context, or no context. </p> <p>The protocol considerations for which contexts are considered valid or acceptable are out-of-scope of this FEP, but dereferencing the contexts can provide more information that can help you make this choice. Perhaps you expect a certain type to be declared, or perhaps you require an owner, or perhaps some other criteria is enforced.</p>"},{"location":"fep/7888/#example-4-publishing-consuming-and-interacting-with-authoritative-contexts-that-have-canonical-collections","title":"Example 4: Publishing, consuming, and interacting with authoritative contexts that have canonical collections","text":"<p>This example demonstrates how one might expose all objects acknowledged by a context owner to exist within the context.</p> <p>You encounter the following object:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"&lt;some-object&gt; exists in &lt;some-context&gt;.\"\n}\n</code></pre> <p>You wish to browse that context, so you dereference the context:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"type\": \"https://w3id.org/fep/xxxx/Conversation\",\n \"https://w3id.org/fep/xxxx/posts\": {\n \"id\": \"https://domain.example/some-context/posts\",\n \"type\": \"OrderedCollection\",\n \"items\": [\n \"https://domain.example/some-object\",\n // ...\n ]\n }\n \"summary\": \"&lt;some-context&gt; is owned by &lt;context-owner&gt;. It is a &lt;Conversation&gt; and it has a canonical collection of &lt;posts&gt;, which is &lt;some-context/posts&gt;.\"\n}\n</code></pre> <p>As a consumer, you can browse or backfill the conversation by loading the context's canonical collection. In the above representation of <code>&lt;some-context&gt;</code>, the use of the hypothetical <code>https://w3id.org/fep/xxxx/Conversation</code> type would indicate that the associated canonical collection is exposed via the hypothetical <code>https://w3id.org/fep/xxxx/posts</code> property.</p>"},{"location":"fep/7888/#appendix-c-creating-and-maintaining-contexts-and-their-associated-collections-using-activitypub-c2s","title":"Appendix C: Creating and maintaining contexts and their associated collections using ActivityPub C2S","text":"<p>(This section is non-normative.)</p> <p>Because PUB does not define the use of <code>context</code> as a property or the notion of a canonical collection, it is up to ActivityPub Clients to manage contexts and their canonical collections for themselves. The following algorithm may be used to create an object within a context that has a canonical collection:</p> <ol> <li><code>Create</code> the canonical <code>Collection</code> that will be associated with the context. Save the generated Collection <code>id</code> to be used in the next step.</li> <li>Create the Object that will be used as <code>context</code>. If the Object has a canonical Collection associated with it, then specify the appropriate property relation using the <code>id</code> from the previous step. Save the generated context <code>id</code> to be used in the next step.</li> <li>Create the Object that will exist within the context, and specify the <code>context</code> as the <code>id</code> from the previous step. Set an appropriate <code>audience</code> or use <code>to</code>/<code>cc</code> to deliver the Create activity as-is. Save the generated object <code>id</code> to be used in the next step.</li> <li>Add the Object to the context's canonical Collection, using the <code>id</code>s obtained from the responses for steps 1 and 3. You may wish to deliver this Add activity via <code>to</code>/<code>cc</code>/<code>audience</code> targeting your intended recipients, especially if you did not deliver the Create Object from step 3.</li> </ol>"},{"location":"fep/7888/#references","title":"References","text":"<ul> <li>VOCAB James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>PUB Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> </ul>"},{"location":"fep/7888/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/7952/","title":"FEP-7952: Roadmap For Actor and Object Portability","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop, bumblefudge bumblefudge@learningproof.xyz <code>DRAFT</code> 2024-06-20 #435 Discussions codeberg"},{"location":"fep/7952/#summary","title":"Summary","text":"<p>Portability: A set of design choices, data models, and protocols, that enable an end-user to automatically migrate from one service provider to another with the least amount of data loss and service disruption, including loss of or disruption to the user's social graph (Followers and Following collections, etc).</p> <p>We focus on automated migration because the option of fully manual migration always exists, such as re-typing all of one's messages and content, manually re-adding everyone to one's following collection, manually contacting everyone on one's former followers list and asking them to re-follow, and so on. However, that's an extreme failure state in usability terms; we want to avoid that.</p> <p>This FEP targets two main categories of migration, covering user stories 1A-1F and 2 + 3 in FEP-73cd: User Migration Stories, respectively. It also draws inspiration from an earlier information document, FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases.</p>"},{"location":"fep/7952/#migration-from-a-live-cooperating-server","title":"Migration from a live (cooperating) server","text":"<p>Note that the data handling and formatting recommendations made here will be strictly complementary to the in-progress LOLA proposal, which target the same user stories.</p>"},{"location":"fep/7952/#migration-from-a-backupexport-produced-by-a-dead-or-non-cooperating-server","title":"Migration from a backup/export produced by a dead (or non-cooperating) server","text":""},{"location":"fep/7952/#current-approaches","title":"Current Approaches","text":"<p>Most current Fediverse implementations tend to follow a pattern of comprehensive service providers (or instances) that bundle the following concerns into a single service running on a web domain:</p> <ol> <li>Identity hosting (hosting of the Actor profile object, which serves as the source of truth for all others in the list)</li> <li>Authentication and authorship/provenance of objects</li> <li>Key management (used for HTTP Signatures for Server to Server request authentication)</li> <li>ActivityPub Object and Collection storage (typically in a database)</li> <li>Message Transfer Services (hosting of inboxes and outboxes, delivering of outgoing objects, notifications, etc)</li> <li>Instance-based content moderation</li> <li>Community discovery (via the instance's Local feed)</li> <li>Front-end clients, i.e. \u201cwebsite\u201d and/or mobile app (most current implementations bundle the backend system with a made-for-purpose front end that runs off the same server)</li> </ol> <p>This tight coupling of concerns constrains the possibilities for migrating from a live server, and the daunting task of automating migrations to other, less-bundled architectures has yet to be attempted. Its strict reliance on interactive HTTP signatures in concern #2 also excludes migration from a dead server altogether, severely degrading user experience when servers go offline.</p> <p>For example, a typical current Move Actor implementation (such as the one implemented by Mastodon, or specified in FEP-7628) only assists the user with automatically migrating their Followers list (by setting up a bi-directional <code>alsoKnownAs</code> links and sending the <code>Move</code> activity to all of their followers). In addition, some implementations allow the manual export and re-import of the Following lists, mutes, blocks and bookmarks. However, important content like posts, media attachments, reactions, reposts and so on, does not get automatically transferred.</p> <p>This FEP proposes a roadmap (a set of steps, design concepts, and recommendations) that enables more complete live migrations, as well as enabling migrations from dead server backups.</p>"},{"location":"fep/7952/#roadmap-for-portability","title":"Roadmap For Portability","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/7952/#1-unbundle-the-services-and-concerns-of-a-typical-instance","title":"1. Unbundle the services and concerns of a typical instance","text":"<ol> <li>Sign everything: Recognize client-side cryptographic signatures as proof of authorship, in addition to the current practice of relying solely on the instance URL:</li> <li>add verification key(s) to the Actor object via FEP-521a</li> <li>Sign individual activities using the same key(s), as specified in FEP-8b32: Object Integrity Proofs</li> <li>B.Y.O. Actor ID: Using Object Integrity proofs enables Identity Hosting to be separated from the other instance concerns. Actor profiles can now be hosted separately from the instance, which in turn enables service providers to offer their users a \u201cBYO (Bring Your Own) domain name\u201d feature.</li> <li>Separate Inbox/Outbox: (Optional) The previous steps enable message transfer and Inbox/Outbox hosting to be outsourced to separate service providers (the Actor profile links to these in the usual manner).</li> <li>Separate Object and Collection hosting: (Optional) Similarly, AP Objects and Collections can now be stored on domains separate from the Actor's domain (since authorship and controller-ship can be proven cryptographically, in a domain-independent way). This enables the user to migrate storage service providers without having to change their Actor ID.</li> </ol>"},{"location":"fep/7952/#2-implement-portable-migration-stable-object-ids","title":"2. Implement Portable (migration-stable) Object IDs","text":"<p>Separating the object storage provider from the Actor profile hosting provider enables the implementation of Object IDs that can remain unchanged through migrations and export/import cycles. Specifically, we're looking for Object IDs that remain unchanged even after migrating the objects to be hosted at a different storage provider.</p> <p>There are several methods of achieving these types of portable IDs, including:</p> <ul> <li>FEP-ef61: Portable Objects, which introduces a new <code>ap://</code> URI scheme.</li> <li>Actor-Relative URLs (regular <code>https</code> URLs that are redirected to their current location), specified in FEP-e3e9: Actor-Relative URLs</li> <li>Various content-based identifier schemes (such as IPFS CIDs, Named Information Hashes, etc.)</li> </ul> <p>As long as a user's identity provider's web domain remains constant, they can migrate to different inbox/outbox and storage providers, without breaking portable links. That is, users will be able to switch hosting and service providers to different domains, without breaking signatures, replies and threading, likes, following and followers, or anything else.</p> <p>This means that:</p> <ul> <li>Object and Collection hosting can now be extracted to general-purpose storage providers that are on different domains than the Actor profiles</li> <li>Actors can migrate between different storage providers without changing the Actor id and url.</li> <li>Moderation actions gain additional features for fine-grained handling of services distinct from individual objects:</li> <li>Individual Actor IDs can be blocked/moderated, as is currently possible</li> <li>Actor profile providers (in the example above, <code>https://alice-personal-site.example</code> or <code>https://example.com/users/alice</code>) can now also be blocked/moderated or accrue reputational weighting</li> <li>Object-hosting or inbox providers (in the example below, <code>https://storage-provider.example</code>) might also be worth tracking separately for some moderation or spam-/sybil-protection use-cases.</li> </ul>"},{"location":"fep/7952/#3-create-account-exportimport-features-that-assume-actor-relative-urls-and-segmented-services","title":"3. Create account export/import features that assume Actor-Relative URLs and segmented services","text":"<p>Exporting an account based on this model segments neatly and brings with it verifiability, if each object was separately signed by a key published in the still-live Actor object. Importing from such a file is trivial to implement compared to today's account migrations that need to be aware of the data structures and addressing schemes of the previous server.</p>"},{"location":"fep/7952/#4-enable-verifiable-actor-url-migration","title":"4. Enable verifiable Actor URL migration","text":"<p>If the user wants to move their Actor to a different domain, or switch identity hosting services, a future FEP could define this in a simple, Verifiable Credential-style data model containing attestations and proofs that the new actor is equivalent to the old, controlled by the same subject, etc. This would generalize and standardize one currently thorny requirement of interoperable migration, that of authenticating migration requests.</p> <p>Note: one possible extension of this authentication artefact would be to incorporate \"pre-rotation\" style commitments of the sort used in distributed key management.</p>"},{"location":"fep/7952/#end-result-stable-migration-aware-distributed-data","title":"End Result: Stable, migration-aware distributed data","text":"<p>Taken together, these items allow for painless automated migration between service providers, without broken links or effort duplication, and enables restoration from backup and thus migration from dead servers.</p>"},{"location":"fep/7952/#examples","title":"Examples","text":""},{"location":"fep/7952/#example-1-standalone-activitypub-actor-profile","title":"Example 1: Standalone ActivityPub Actor Profile","text":"<p>Hosted at <code>https://alice-personal-site.example/actor</code></p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\",\n \"https://w3id.org/security/multikey/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"name\": \"Alice\",\n \"preferredUsername\": \"alice\",\n \"inbox\": \"https://service-provider.example/users/alice/inbox\",\n \"outbox\": \"https://service-provider.example/users/alice/outbox\",\n \"assertionMethod\": [\n {\n \"id\": \"https://alice-personal-site.example/actor#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://alice-personal-site.example/actor\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example/users/1234\"\n }]\n}\n</code></pre> <p>Things to note:</p> <ul> <li>The Actor profile's URL is hosted on <code>https://alice-personal-site.example/actor</code>, but the Inbox and Outbox services are hosted separately, at <code>https://service-provider.example/users/alice/inbox</code></li> <li>The <code>assertionMethod</code> section, as defined in FEP-521a.</li> <li>The <code>service</code> section, which sets up the ability to use Actor-Relative URLs for mapping (and thus routing) all requests to their current locations at service providers (see FEP-e939).</li> </ul>"},{"location":"fep/7952/#references","title":"References","text":"<ul> <li>FEP-521a: Representing actor's public keys</li> <li>FEP-73cd: Migration User Stories</li> <li>FEP-8b32: Object Integrity Proofs</li> <li>FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases</li> <li> <p>FEP-e939: Actor-Relative URLs</p> </li> <li> <p>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</p> </li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Dave Longley, Manu Sporny, Verifiable Credential Data Integrity 1.0, 2023</li> <li>Manu Sporny, Dave Longley, Markus Sabadell, Drummond Reed, Orie Steele, Christopher Allen, Decentralized Identifiers (DIDs) v1.0, 2022</li> <li>Dave Longley, Manu Sporny, Data Integrity EdDSA Cryptosuites v1.0, 2023</li> <li>A. Rundgren, B. Jordan, S. Erdtman, JSON Canonicalization Scheme (JCS), 2020</li> </ul>"},{"location":"fep/7952/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/7d8c/","title":"FEP-7d8c: Documentation: Automation of FEP","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocks <code>DRAFT</code> 2025-01-20 #470 Discussions codeberg"},{"location":"fep/7d8c/#summary","title":"Summary","text":"<p>This FEP discusses scripts and woodpecker configuration used to automate parts of the FEP process. The FEP process is described in FEP-a4ed.</p> <p>As FEP-a4ed, this is a living document, and should be updated as the FEP process evolves.</p>"},{"location":"fep/7d8c/#vision","title":"Vision","text":"<p>Automation has the goal of reducing the burden on facilitators by:</p> <ul> <li>allowing people without deep knowledge of git to become facilitators. Basically, the job will boil down to read a document and ensure it meets our standards, then click \"merge\".</li> <li>performing as many checks as possible via automation</li> </ul> <p>Once one has simplified this job, one can seek broader adoption of the FEP process..</p>"},{"location":"fep/7d8c/#todos","title":"Todos","text":"<p>This is a living document. So let's have a todo list</p> <ul> <li>[ ] Check for broken links</li> <li>[ ] Simplify creating FEPs. Does one really need git and a codeberg account?</li> <li>[ ] Code documentation to enable more people to write tools, e.g. an automatic generation of which FEP is used where</li> </ul> <p>The first two items have been on my personal todo list for a long time, and might stay there ... so feel free to pick them up.</p>"},{"location":"fep/7d8c/#automation","title":"Automation","text":"<p>Automation for the FEP repository uses the codeberg woodpecker. There are two jobs:</p> <ul> <li>test runs basic checks on the contained FEPs</li> <li>readme updates the <code>README.md</code> file and creates the tracking issue</li> <li>create a static website</li> </ul> <p>Automation code is written in python and is contained in the script folder.</p>"},{"location":"fep/7d8c/#scripts-and-tests","title":"Scripts and tests","text":"<p>FEP uses python scripts to manage some aspects of it. These should be runnable without installing packages. In order to ensure these scripts stay working, include them in .woodpecker/test.yml</p>"},{"location":"fep/7d8c/#configuration-variables","title":"Configuration variables","text":"<p>To be able to do these configuration tasks, you need to be in the admin group. See the first discussion here.</p> <p>The configuration .woodpecker/readme.yml uses two secrets:</p> <ul> <li>codeberg_api_token, a codeberg API token used to create the tracking issues.</li> <li>deploy_key, an SSH private key used to push to the codeberg repository.</li> </ul> <p>Furthermore, the environment variable <code>CI_REPO_CLONE_SSH_URL</code> is used see woodpecker documentation.</p> <p>We will now discuss how to obtain these secrets.</p>"},{"location":"fep/7d8c/#the-api-token","title":"The api token","text":"<p>API tokens can be generated on codeberg under user settings -&gt; <code>applications</code> -&gt; <code>select permissions</code> -&gt; <code>issue</code>: <code>Read and Write</code> and giving it a name. They are hexstrings with 40 characters (if I counted correctly), e.g.</p> <pre><code>33104dd6847e87ef3d6925effdfc852349233034\n</code></pre>"},{"location":"fep/7d8c/#deploy-key","title":"Deploy key","text":"<p>As already mentioned the deploy key is an ssh key. One can generate these on linux via</p> <pre><code>ssh-keygen -t ed25519 -f deploy_key\n</code></pre> <p>Do not specify a passphrase. Examples:</p> <pre><code>$ cat deploy_key\n-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACCQ5XqPCdvHPHsukhPS+RMDpMnOCCLW0csGGoFgijQRiwAAAJDpM7fx6TO3\n8QAAAAtzc2gtZWQyNTUxOQAAACCQ5XqPCdvHPHsukhPS+RMDpMnOCCLW0csGGoFgijQRiw\nAAAEB2ofBDBwUcZs5FHsaFMvyO95Qrvn8+PO3BchraucMRwJDleo8J28c8ey6SE9L5EwOk\nyc4IItbRywYagWCKNBGLAAAADWhlbGdlQHJhaW5ib3c=\n-----END OPENSSH PRIVATE KEY-----\n$ cat deploy_key.pub\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJDleo8J28c8ey6SE9L5EwOkyc4IItbRywYagWCKNBGL helge@rainbow\n</code></pre> <p>The public key <code>deploy_key.pub</code> should be added to the codeberg repo under <code>settings</code> -&gt; <code>deploy keys</code> -&gt; <code>add deploy key</code> and checking the <code>enable write access</code> checkbox.</p> <p>See here.</p>"},{"location":"fep/7d8c/#adding-the-secrets-to-woodpecker","title":"Adding the secrets to woodpecker","text":"<p>One can add these secrets on woodpecker, then selecting the settings wheel -&gt; <code>secrets</code> -&gt; <code>add secret</code>. The secrets only need to available for the <code>push</code> action (corresponding to a merged pull request).</p> <p>The deploy_key needs to be end with a new line when pasting into the woodpecker ui.</p>"},{"location":"fep/7d8c/#development","title":"Development","text":"<p>The tools are located in the <code>scripts</code> folder, with the <code>scripts/fep_tools</code> folder containing the python package <code>fep_tools</code>. Most of this tooling only uses python, e.g. <code>scripts/new_proposal.py</code> and <code>scripts/create_readme.md</code>, so they can be run from any environment.</p>"},{"location":"fep/7d8c/#running-tests","title":"Running tests","text":"<p>To run the tests, one needs pytest. We note that the tests run from the base folder. The following commands demonstrate how to setup a virtual environment, install the dependencies, and run the tests.</p> <pre><code>python -mvenv .venv\npip install ./scripts\npytest\n</code></pre> <p>One can leave the venv, by running <code>deactivate</code>.</p>"},{"location":"fep/7d8c/#building-the-website","title":"Building the website","text":"<p>To build the website, first install the dependencies</p> <pre><code>python -mvenv .venv\npip install ./scripts\n./scripts/create_site_pages.py\n</code></pre> <p>Then run mkdocs via</p> <pre><code>mkdocs serve --config-file scripts/mkdocs.yml\n</code></pre> <p>References:</p> <ul> <li>The website is build with mkdocs</li> <li>and uses mkdocs-material for its many plugins</li> </ul>"},{"location":"fep/7d8c/#automatic-deployment","title":"Automatic deployment","text":"<p>See website.yml.</p>"},{"location":"fep/7d8c/#changes","title":"Changes","text":"<ul> <li>Added discussion link to tracking issue, use table to format (@helge, 2025-03-07)</li> <li>Added static website stuff fep#673</li> </ul>"},{"location":"fep/7d8c/#references","title":"References","text":"<ul> <li>pukkamustard, FEP-a4ed: The Fediverse Enhancement Proposal Process, 2020</li> </ul>"},{"location":"fep/7d8c/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/82f6/","title":"FEP-82f6: Actor statuses","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me <code>DRAFT</code> 2025-05-12 #600 Discussions codeberg"},{"location":"fep/82f6/#summary","title":"Summary","text":"<p>This proposal describes an ActivityPub extension to allow actors to publish a short status text, with optional expiration, link attachment, and history.</p> <p>Some centralized communication services provide their users with the ability to set a status on their account, which is usually displayed on their profile and sometimes next to their name in other places in the UI. These are distinct from regular posts because they can not be interacted with in any way whatsoever, can't contain media attachments, and usually have a short character limit on the order of several hundred characters at most. Statuses are always visible to anyone who can see the actor itself.</p>"},{"location":"fep/82f6/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].</p>"},{"location":"fep/82f6/#representation-of-statuses","title":"Representation of statuses","text":"<p>A status object is of type <code>ActorStatus</code>, which extends the ActivityPub <code>Object</code>. It has the following fields:</p> <ul> <li><code>attributedTo</code>: the ID of the actor whose status this is. REQUIRED.</li> <li><code>content</code>: the plain text content of the status. REQUIRED.</li> <li><code>published</code>: the timestamp when this status update was created. REQUIRED.</li> <li><code>id</code>: a unique identifier for this status update. REQUIRED.</li> <li><code>endTime</code>: a timestamp when this status expires. OPTIONAL. If present, an implementation MUST check this field and, if it is in the past, MUST NOT display this status on the actor's profile and in any other places where statuses are displayed.</li> <li><code>attachment</code>: an object that contains any extra information about what the actor is currently doing, e.g. a song they're listening to or a video game they're playing. OPTIONAL. If present, <code>content</code> MUST contain a fallback human-readable plain text representation of this object. For example, for a song, it may simply be \"Artist \u2014 Title\".</li> </ul>"},{"location":"fep/82f6/#actor-fields","title":"Actor fields","text":"<p>The following two OPTIONAL fields are added to actors:</p> <ul> <li><code>status</code>: the last, or current, status update of this actor. MUST NOT be present if the last status has expired. MUST contain an inlined <code>ActorStatus</code>.</li> <li><code>statusHistory</code>: a collection of all past status updates of this actor. If present, implementations MAY provide a UI to view this actor's status history. If absent, implementations SHOULD NOT store this actor's past status updates and MUST NOT expose them in the UI.</li> </ul>"},{"location":"fep/82f6/#activities","title":"Activities","text":""},{"location":"fep/82f6/#creating-a-status-update","title":"Creating a status update","text":"<p>A status update is created by sending a <code>Create{ActorStatus}</code> activity to followers. Upon receiving this activity, an implementation updates the actor's latest status, and, if it has the <code>statusHistory</code> collection, adds it to the history. After sending this activity, the <code>status</code> field of the actor object MUST be updated with this new status. An <code>Update{Actor}</code> MUST NOT be sent because the <code>Create</code> already implicitly updates that field in the copies of this actor stored on remote servers.</p> <p>If the actor does not have a <code>statusHistory</code>, the previous status is considered no longer existing, as if it was <code>Delete</code>d.</p>"},{"location":"fep/82f6/#clearing-the-status","title":"Clearing the status","text":"<p>The latest status is cleared by sending a <code>Remove{ActorStatus}</code> activity to followers. Upon receiving this activity, if the ID of the <code>object</code> matches the actor's current status, an implementation removes the actor's current status. If the actor has the <code>statusHistory</code> collection, it keeps it there. Same considerations for <code>status</code> apply here.</p>"},{"location":"fep/82f6/#deleting-the-status","title":"Deleting the status","text":"<p>A status is completely deleted by sending a <code>Delete{ActorStatus}</code> activity to followers. If the actor has the <code>statusHistory</code> collection, the status is removed from there as well as being cleared from the profile. Otherwise, this is activity is identical to <code>Remove</code>.</p>"},{"location":"fep/82f6/#other-activities-in-relation-to-statuses","title":"Other activities in relation to statuses","text":"<p>Statuses can not be interacted with. Therefore, implementations SHOULD NOT send any activities that refer to statuses as their <code>object</code> attributed to actors other than the actor that owns this status. Implementations MUST either ignore any such activities by returning a 2xx response, or reject them by returning a 4xx.</p> <p>Once published, a status object can not be <code>Update</code>d.</p>"},{"location":"fep/82f6/#context","title":"Context","text":"<p>The JSON-LD context for this proposal is as follows: <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sm\": \"http://smithereen.software/ns#\",\n \"ActorStatus\": \"sm:ActorStatus\",\n \"status\": {\n \"@type\": \"@id\",\n \"@id\": \"sm:status\"\n },\n \"statusHistory\": {\n \"@type\": \"@id\",\n \"@id\": \"sm:statusHistory\"\n }\n }\n ]\n}\n</code></pre></p>"},{"location":"fep/82f6/#character-limit","title":"Character limit","text":"<p>Implementations MUST support statuses of up to 100 characters or emoji. Implementations MAY allow statuses that are longer, but they MAY be truncated on the receiving side depending on display and storage constraints. It is RECOMMENDED to apply a 100-character limit to the text field where the user enters their status, but be more permissive when processing status updates from other servers.</p>"},{"location":"fep/82f6/#ui-examples","title":"UI examples","text":"<p>This section is non-normative.</p> <p>This section shows examples of how implementations might display status updates to users.</p> <p>Simple UI without history, similar to Discord or GitHub: </p> <p>Updating one's status, with the ability to set it to expire: </p> <p>UI with history, similar to early Facebook: </p>"},{"location":"fep/82f6/#example-status-object","title":"Example status object","text":"<pre><code>{\n \"type\": \"ActorStatus\",\n \"id\": \"https://example.social/users/1/statuses/1747286633\",\n \"attributedTo\": \"https://example.social/users/1\",\n \"content\": \"is desperately trying to bring the old internet back\",\n \"published\": \"2025-05-15T05:23:53.539Z\",\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sm\": \"http://smithereen.software/ns#\",\n \"ActorStatus\": \"sm:ActorStatus\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/82f6/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> </ul>"},{"location":"fep/82f6/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/844e/","title":"FEP-844e: Capability discovery","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2025-06-14 #624 Discussions codeberg"},{"location":"fep/844e/#summary","title":"Summary","text":"<p>Capability discovery for ActivityPub applications.</p> <p>This document is based on the idea described in FEP-aaa3: Listing Implemented Specifications on the Application Actor.</p>"},{"location":"fep/844e/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/844e/#application-object","title":"Application object","text":"<p>An application can advertise its capabilities using the <code>implements</code> property on an <code>Application</code> object.</p> <p>This object might not be an actor. The value of <code>implements</code> property MUST be an array of objects, each containing the following properties:</p> <ul> <li><code>href</code> (REQUIRED): the unique identifier of the capability. The value MUST be an URI.</li> <li><code>name</code> (RECOMMENDED): the short description of the capability.</li> </ul> <p>The order of objects in the array is not important.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/844e\"\n ],\n \"type\": \"Application\",\n \"id\": \"https://social.example/server\",\n \"implements\": [\n {\n \"href\": \"https://datatracker.ietf.org/doc/html/rfc9421\",\n \"name\": \"RFC-9421: HTTP Message Signatures\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/844e/#discovery-through-an-actor","title":"Discovery through an actor","text":"<p>An <code>Application</code> object can be linked to an actor using the <code>generator</code> property. The value of this property SHOULD be a partial object containing the <code>implements</code> property. That object MAY be anonymous (without an identifier).</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/844e\"\n ],\n \"id\": \"https://social.example/actors/1\",\n \"type\": \"Person\",\n \"inbox\": \"https://social.example/actors/1/inbox\",\n \"outbox\": \"https://social.example/actors/1/outbox\",\n \"generator\": {\n \"type\": \"Application\",\n \"implements\": [\n {\n \"href\": \"https://datatracker.ietf.org/doc/html/rfc9421\",\n \"name\": \"RFC-9421: HTTP Message Signatures\"\n }\n ]\n }\n}\n</code></pre>"},{"location":"fep/844e/#discovery-through-a-well-known-endpoint","title":"Discovery through a well-known endpoint","text":"<p>The mechanism of discovering an <code>Application</code> object using WebFinger is described in FEP-d556.</p> <p>Capability discovery through NodeInfo endpoint is discouraged.</p>"},{"location":"fep/844e/#intended-use","title":"Intended use","text":"<p>The reliance on the mechanism described in this document might increase implementation complexity and hinder interoperability.</p> <p>Implementers MUST NOT use it in cases where capabilities can be inferred from properties and types of objects.</p>"},{"location":"fep/844e/#registry-of-software-capabilities","title":"Registry of software capabilities","text":"<p>(This section is non-normative.)</p> Name Identifier RFC-9421: HTTP Message Signatures https://datatracker.ietf.org/doc/html/rfc9421 RFC-9421 signatures using the Ed25519 algorithm https://datatracker.ietf.org/doc/html/rfc9421#name-eddsa-using-curve-edwards25"},{"location":"fep/844e/#implementations","title":"Implementations","text":"<ul> <li>Streams</li> <li>Forte</li> <li>Mitra</li> <li>ActivityPub for WordPress</li> <li>tootik</li> </ul>"},{"location":"fep/844e/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Helge, FEP-aaa3: Listing Implemented Specifications on the Application Actor, 2024</li> <li>Steve Bate, FEP-d556: Server-Level Actor Discovery Using WebFinger, 2024</li> </ul>"},{"location":"fep/844e/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/888d/","title":"FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2023-04-10 #83 Discussions codeberg"},{"location":"fep/888d/#summary","title":"Summary","text":"<p>It is considered best practice in the linked-data ecosystem to have IRIs be HTTPS URIs that resolve to a definition of the term being used, and it is desirable to define such terms in a JSON-LD context file that is referenced by its IRI rather than having the full <code>@context</code> object embedded in every single document. ActivityStreams 2.0 and ActivityPub do this with the normative context and namespace provided at <code>https://www.w3.org/ns/activitystreams</code>, but this namespace is not generally open to extensions or to experimental terms. This FEP therefore proposes using <code>https://w3id.org/fep</code> as a base IRI for the FEP process, allowing sub-namespaces for each FEP.</p>"},{"location":"fep/888d/#acknowledgements","title":"Acknowledgements","text":"<p>(This section is non-normative.)</p> <p>The core idea behind this FEP is attributed to helge on SocialHub 1. Specifically, the proposal to register <code>fep</code> at the W3ID service is adopted wholesale, with alterations made to the specifics of implementing the redirect mappings using .htaccess rules. These alterations are intended to allow easier sub-namespace allocation for each FEP.</p>"},{"location":"fep/888d/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"SHOULD\", \"MAY\" are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/888d/#introduction","title":"Introduction","text":"<p>(This section is non-normative.)</p> <p>The Resource Description Framework (RDF), of which JSON-LD is a serialization, uses URIs to identify nodes on a graph, define properties of those nodes, and create relationships between those nodes. Each statement in RDF represents a fact that is constructed by linking a subject to an object with a predicate; for example, in the statement \"Alice knows Bob\", the subject <code>Alice</code> is related to the object <code>Bob</code> by the predicate <code>knows</code>. To avoid ambiguity, we can specify a URI for what it means to \"know\" someone. Such a URI represents a named property or named predicate, and it exists within a namespace, often associated with some ontology or vocabulary. ActivityStreams 2.0 provides and defines the Activity Vocabulary at <code>https://www.w3.org/ns/activitystreams</code>, and terms defined within may use either the base IRI <code>https://www.w3.org/ns/activitystreams#</code> or the compact IRI <code>as:</code>.</p> <p>For example, we can consider the definition of \"Public\" addressing within ActivityPub, represented by the <code>Public</code> magic collection. When the normative ActivityStreams 2.0 context is applied, the IRI for this collection may be equivalently expressed as <code>Public</code>, <code>as:Public</code>, or <code>https://www.w3.org/ns/activitystreams#Public</code>.</p> <p>Historically, extension terms within early ActivityPub implementations assumed that those extension terms would be readily adopted within the ActivityStreams namespace, but this did not happen. Currently, terms such as <code>Hashtag</code>, <code>manuallyApprovesFollowers</code>, <code>movedTo</code>, and <code>sensitive</code> are manually defined with compact IRIs using the <code>as:</code> prefix, in effect making it so that any implementation that wishes to understand these properties and types must manually define those terms as well, in the exact same way.</p> <p>Later extension terms were defined within vendor-specific namespaces such as <code>http://joinmastodon.org/ns</code> or <code>https://joinpeertube.org/ns</code>. With the increased adoption of ActivityPub by software projects and the diverse needs of such projects, each project will often define its own vendor-specific namespace to contain its own terms. This has led to a multitude of namespaces and extension terms, which must be cherry-picked as needed by JSON-LD consumers wishing to maintain compatibility. Additionally, some of these terms are defined incorrectly within <code>@context</code>, leading to the necessity of compatibility hacks on a per-project basis. Even terms defined correctly may overlap with other terms, and proposed new terms must be parented within a vendor namespace, creating the potential for conflict on which vendor should adopt which term.</p> <p>The aim of this FEP is to provide a vendor-independent namespace under which extension terms can be defined pursuant to the FEP process and the above problems can be reduced.</p>"},{"location":"fep/888d/#prior-art","title":"Prior art","text":"<p>(This section is non-normative.)</p> <p>Within the XMPP ecosystem, the core XMPP specification is defined within an RFC, and further functionality is afforded by the \"eXtensible\" nature of XMPP. The XMPP Standards Foundation (XSF) maintains the process for stewarding new extensions via XMPP Extension Protocols (XEPs). XML namespacing for such extensions is provided by <code>urn:xmpp:</code>, as the XML ecosystem generally prefers using URNs rather than using HTTPS URIs. Such URNs are fully location-independent and not vulnerable to DNS expiry, lapsing, or insolvency. Instead, they are assigned within the authority of the XSF. The XSF maintains an XMPP Registrar and allows XEPs to request and define sub-namespaces beneath <code>urn:xmpp:</code>. In exchange, these URNs are not generally dereferencable without a resolver that looks up the URN within the XEP database.</p> <p>Within the RDF and linked-data ecosystems, there is a strong preference for HTTP or HTTPS URIs, as these can usually be dereferenced via the HTTP protocol for additional information about the subject of the URI. In cases where the URI does not resolve, the URI serves as an identifier not much different than a URN, but with its authority derived from DNS domain rather than from some organizational authority. The reliance on DNS domain creates an issue where the primary domain associated with a group or organization might change. If a previously-used domain is reassigned to a different party, then the new party can mint URIs that accidentally or intentionally conflict with previously-assigned URIs.</p> <p>To mitigate the DNS authority reassignment issue, trusted intermediary services can maintain a \"persistent URL\" (PURL) service, which allows assigning identifiers on the intermediary domain that will redirect to some other URI. This layer of indirection allows changing the location of the resource by simply changing the redirect's target. W3ID is one such service, operated by the W3C Permanent Identifier Community Group and available at https://w3id.org. At the time of writing this FEP, top-level directory names can be claimed by individuals who submit pull requests to the w3id.org repository on GitHub, and .htaccess files allow redirection based on rewrite rules that transform incoming requests to some other target.</p>"},{"location":"fep/888d/#specification","title":"Specification","text":""},{"location":"fep/888d/#design-goals","title":"Design goals","text":"<p>Broad design goals for the redirect mapping include:</p> <ul> <li>Content negotiation for JSON-LD consumers. IRIs SHOULD return machine-friendly context documents or term definitions when requested via the <code>Accept: application/ld+json</code> HTTP header, and SHOULD otherwise return human-friendly proposal documents or term definitions by default.</li> <li>Sub-namespaces for each FEP. Identifiers for each term SHOULD be allocated within the namespace of the FEP that defines them.</li> </ul> <p>At minimum, the following redirects SHOULD resolve as follows:</p> <ul> <li><code>https://w3id.org/fep</code></li> <li><code>Accept: *</code> =&gt; the FEP repository or current home page</li> <li><code>https://w3id.org/fep/(:id)</code></li> <li><code>Accept: application/ld+json</code> =&gt; a specific FEP's context document</li> <li><code>Accept: *</code> =&gt; a specific FEP's proposal document</li> </ul> <p>Additionally, the following MAY resolve:</p> <ul> <li><code>https://w3id.org/fep/(:id)/(:term)</code></li> <li><code>Accept: application/ld+json</code> =&gt; a specific FEP's specific term definition in JSON-LD ontology/schema</li> <li><code>Accept: application/rdf+xml</code> =&gt; a specific FEP's specific term definition in RDF/XML ontology/schema</li> <li><code>Accept: text/turtle</code> =&gt; a specific FEP's specific term definition in Turtle ontology/schema</li> <li><code>Accept: *</code> =&gt; a specific FEP's specific term's folder</li> </ul>"},{"location":"fep/888d/#mapping-w3idorgfep-to-fediversefep-on-codeberg","title":"Mapping w3id.org/fep to fediverse/fep on Codeberg","text":"<p>At the time of writing this FEP, the Codeberg repository at <code>https://codeberg.org/fediverse/fep</code> is used to host FEP-related files, and can similarly be used to host context documents.</p>"},{"location":"fep/888d/#example","title":"Example","text":"<p>(This section is non-normative.)</p> <p>An example .htaccess file is co-located with this FEP, and reproduced below for convenience:</p> <pre><code>RewriteEngine on\n\n\n# catch root request\nRewriteRule ^\\/?$ https://codeberg.org/fediverse/fep [R=302,L]\n\n\n\n# Catch FEP documents\n\n## By content negotiation\n\n### JSON-LD\nRewriteCond %{HTTP_ACCEPT} application/ld\\+json\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.jsonld [R=302,L]\n\n### RDF+XML\nRewriteCond %{HTTP_ACCEPT} application/rdf\\+xml\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/fep-$1.rdf [R=302,L]\n\n### Turtle\nRewriteCond %{HTTP_ACCEPT} text/turtle\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/fep-$1.ttl [R=302,L]\n\n## By URL hacking\nRewriteRule ^([A-Za-z0-9]+).jsonld$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.jsonld [R=302,L]\nRewriteRule ^([A-Za-z0-9]+).rdf$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.rdf [R=302,L]\nRewriteRule ^([A-Za-z0-9]+).ttl$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.ttl [R=302,L]\n\n## By default, take you to the FEP document\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://codeberg.org/fediverse/fep/src/branch/main/fep/$1/fep-$1.md [R=302,L]\n\n\n\n# Catch term definitions/schemas/ontologies\n\n## By content negotiation\n\n### JSON-LD\nRewriteCond %{HTTP_ACCEPT} application/ld\\+json\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://raw.codeberg.page/fediverse/fep/fep/$1/$2/$2.jsonld [R=302,L]\n\n### RDF+XML\nRewriteCond %{HTTP_ACCEPT} application/rdf\\+xml\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.rdf [R=302,L]\n\n### Turtle\nRewriteCond %{HTTP_ACCEPT} text/turtle\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.ttl [R=302,L]\n\n### test html\nRewriteCond %{HTTP_ACCEPT} ^text/html$\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.html [R=302,L]\n\n## By URL hacking\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).jsonld$ https://raw.codeberg.page/fediverse/fep/fep/$1/$2/$2.jsonld [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).rdf$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.rdf [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).ttl$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.ttl [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).html$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.html [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).md$ https://fediverse.codeberg.page/fep/fep/$1/$2/README.md [R=302,L]\n\n## By default, just take you to the term's folder\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://codeberg.org/fediverse/fep/src/branch/main/fep/$1/$2 [R=302,L]\n\n\n\n# a generic catch-all rule\nRewriteRule ^(.*)\\/?$ https://codeberg.org/fediverse/fep/raw/branch/main/fep/$1 [R=302,L]\n</code></pre>"},{"location":"fep/888d/#defining-terms-associated-with-an-fep","title":"Defining terms associated with an FEP","text":"<p>FEPs that wish to define extension terms within the w3id.org/fep namespace MUST provide a JSON-LD document co-located within their FEP folder with a filename of the form <code>fep-xxxx.jsonld</code>, where <code>xxxx</code> is the FEP's slug. This document MUST include at least a <code>@context</code> key, whose value is a JSON object containing term definitions. Simple term definitions map a <code>term</code> key to an IRI value. Expanded term definitions contain <code>@id</code> for each term, with <code>@type</code> of <code>@id</code> if the term links to another node on the graph. Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.</p> <p>The document MAY include additional metadata outside of the <code>@context</code>, such as schema or ontology definitions. If this is done, then you SHOULD NOT include any prefixes in your <code>@context</code> that are only used within the graph, as these can pollute the usage as a context document.</p> <p>Once the FEP is marked <code>FINAL</code>, the context document MAY be cached forever if referenced. FEPs that define extension terms MAY instead define extension terms within a vendor-specific namespace, but generally this SHOULD NOT be done.</p>"},{"location":"fep/888d/#example-using-paths","title":"Example using paths","text":"<p>(This section is non-normative.)</p> <p>For example, say we wanted to define the following terms within the current FEP-888d:</p> <ul> <li><code>SomeType</code> is a term for some type</li> <li><code>exampleA</code> is a term for a property with some literal value (string, boolean, number)</li> <li><code>exampleB</code> is a term for a property that links to another node on the graph (for example, another object)</li> <li><code>exampleC</code> is a term for a property that is an ordered list of literal values that are specifically non-negative integers</li> </ul> <p>The context document might look like this, at minimum:</p> <pre><code>{\n \"@context\": {\n \"SomeType\": \"https://w3id.org/fep/888d/SomeType\",\n \"exampleA\": \"https://w3id.org/fep/888d/exampleA\",\n \"exampleB\": {\n \"@id\": \"https://w3id.org/fep/888d/exampleB\",\n \"@type\": \"@id\"\n },\n \"exampleC\": {\n \"@id\": \"https://w3id.org/fep/888d/exampleC\",\n \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\",\n \"@container\": \"@list\"\n }\n }\n}\n</code></pre> <p>Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.</p> <p>A folder within the FEP's directory should be used to provide additional documentation for the term, such as ontology or schema definitions via JSON-LD, RDF/XML, and/or Turtle.</p>"},{"location":"fep/888d/#example-using-fragment-identifiers","title":"Example using fragment identifiers","text":"<p>(This section is non-normative.)</p> <p>Depending on convenience or preference, the context document might instead look like this:</p> <pre><code>{\n \"@context\": {\n \"SomeType\": \"https://w3id.org/fep/888d#SomeType\",\n \"exampleA\": \"https://w3id.org/fep/888d#exampleA\",\n \"exampleB\": {\n \"@id\": \"https://w3id.org/fep/888d#exampleB\",\n \"@type\": \"@id\"\n },\n \"exampleC\": {\n \"@id\": \"https://w3id.org/fep/888d#exampleC\",\n \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\",\n \"@container\": \"@list\"\n }\n }\n}\n</code></pre> <p>Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.</p> <p>In such a case, the FEP document should include an element with an HTML identifier that exactly matches the term name, so that the IRI fragment resolves properly. In practice, this means one of the following:</p> <ul> <li>Using a heading with a name that exactly matches the term name. This should be autolinked correctly by most Markdown processors. Be warned that this may cause problems for FEPs that define terms conflicting with common header names, such as <code>summary</code>, <code>acknowledgements</code>, <code>requirements</code>, <code>references</code>, <code>copyright</code>, and so on, including any headers that the FEP author includes for purposes other than explicitly defining the term.</li> <li>Using a heading with a custom attribute containing an ID. Some Markdown processors such as Goldmark will handle cases such as <code>### h3 {#custom-identifier}</code> and render <code>&lt;h3 id=\"custom-identifier\"&gt;h3&lt;/h3&gt;</code>. Markdown specifications such as CommonMark currently do not support custom attributes, but some Markdown processors such as Goldmark support custom attributes on headers (but not on arbitrary elements). See CM-ATTRS for more discussion of this feature.</li> <li>Using an HTML definition list, with <code>id</code> attributes exactly matching the term name. HTML within Markdown files is generally rendered as-is, although it may be sanitized, stripped, or disallowed for security purposes. In cases where it is allowed, however, it can be an effective way to express term definitions within an FEP document.</li> </ul>"},{"location":"fep/888d/#defining-terms-in-a-machine-readable-way","title":"Defining terms in a machine-readable way","text":"<p>(This section is non-normative.)</p> <p>If terms are defined within HTML using RDFa, and this HTML is embedded in the Markdown contents of the FEP document, then these term definitions can be used to programmatically generate machine-readable term definitions, schemas, and ontologies in multiple formats. A Python script is provided within the FEP repository at <code>scripts/make_definitions.py</code> for convenience.</p> <p>To make use of the script, it is necessary to structure term definitions in a specific way. We define a \"term definition\" to be any HTML element that adheres to the following requirements:</p> <ul> <li>The HTML element has an HTML <code>id</code> attribute equal to the shorthand term name.</li> <li>The HTML element has an RDFa <code>resource</code> attribute equal to the term's full IRI.</li> </ul> <p>Generally, this HTML element should be a <code>&lt;section&gt;</code> tag, and it should contain a definition list (<code>&lt;dl&gt;&lt;/dl&gt;</code>) where each term (<code>&lt;dt&gt;&lt;/dt&gt;</code>) and definition (<code>&lt;dd&gt;&lt;/dd&gt;</code>) provide a key-value pair representing a property of that term definition. Examples may be provided within a preformatted code block (<code>&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;</code>), and multiple examples may be provided. Example code blocks can have a <code>title</code> attribute on the <code>&lt;pre&gt;</code> tag. It is generally recommended to put any term definitions below a heading called something like \"Terms defined\".</p> <p>For all term definitions regardless of type, it is recommended to include the following properties in your definition list:</p> <ul> <li><code>rdfs:label</code> is a natural language label that can replace the shorthand term. For example, a term that is normally expressed with the shorthand <code>hasValue</code> might be labelled <code>has value</code> in plain English.</li> <li><code>rdfs:comment</code> is a natural language description that gives more detail about what the term means.</li> <li><code>rdfs:isDefinedBy</code> is a reference pointing to where the term is defined. Usually this is the current FEP, but meta-FEPs that bundle other FEPs may use this property to refer to the original FEP that defined a term.</li> </ul> <p>For term definitions that define a class (such as <code>rdfs:Class</code> or <code>owl:Class</code>), it is recommended to include the following properties in your definition list if appropriate:</p> <ul> <li><code>rdfs:seeAlso</code> is a reference to some other resource that provides more information about the current term. Usually this can be used to link a class to the properties that instances of that class may have.</li> <li><code>rdfs:subClassOf</code> is a property that declares the current class to inherit from the object. Logically, if something is an instance of the current class, it is also an instance of the superclass. For example, in AS2-Vocab, <code>OrderedCollection</code> is a subclass of <code>Collection</code>, so all <code>OrderedCollection</code> objects are also implied to have a type of <code>Collection</code> as well, even if not explicitly declared to be one.</li> </ul> <p>For term definitions that define a property (such as <code>rdf:Property</code>, <code>owl:DatatypeProperty</code>, or <code>owl:ObjectProperty</code>), it is recommended to include the following properties in your definition list if appropriate:</p> <ul> <li><code>rdfs:domain</code> is the Domain of the property. Logically, this implies that if a property <code>p</code> has a domain <code>D</code>, and a given resource has the property <code>p</code>, then that resource has a type of <code>D</code>. For example, in AS2-Vocab the domain of <code>actor</code> is <code>Activity</code>, so anything that has an <code>actor</code> is implied to be an <code>Activity</code> even if not explicitly declared to be one.</li> <li><code>rdfs:range</code> is the Range of the property. Logically, this implies that if a property <code>p</code> has a range <code>R</code>, and a given resource is the value of <code>p</code>, then that resource has a type of <code>R</code>. For example, in AS2-Vocab the range of <code>replies</code> is <code>Collection</code>, so anything that is the value of <code>replies</code> is implied to be a <code>Collection</code> even if not explicitly declared to be one.</li> <li>In cases where a property is Required, this can be stated by declaring that the <code>property</code> of <code>owl:minCardinality</code> has a <code>content</code> of <code>1</code> (with a <code>datatype</code> of <code>xsd:nonNegativeInteger</code>). In other words, a property with a minimum cardinality of 1 must have at least one value. If the property is not Required, then this statement can be amended to have a <code>content</code> of <code>0</code>, or in other words, you are stating that this property must have at least 0 values (which allows having no values).</li> <li>In cases where a property is Functional, this can be stated by declaring that the <code>property</code> of <code>owl:maxCardinality</code> has a <code>content</code> of <code>1</code> (with a <code>datatype</code> of <code>xsd:nonNegativeInteger</code>). In other words, a property with a maximum cardinality of 1 can have at most one value. If the property is not Functional, then this statement can be omitted.</li> <li><code>rdfs:seeAlso</code> is a reference to some other resource that provides more information about the current term. Usually this can be used to link a property to other related terms, such as term definitions that can be used as the value of this property.</li> </ul> <p>If the term definition contains JSON-LD keywords like <code>@id</code>, <code>@vocab</code>, <code>@set</code>, or <code>@list</code> within a <code>&lt;dd&gt;</code> element whose corresponding <code>&lt;dt&gt;</code> element's inner text exactly matches <code>Range</code>, then these keywords can be used to automatically generate a context document. Set the attribute <code>excluded</code> on the term definition to a non-empty value to exclude it from auto-generated context term definitions.</p>"},{"location":"fep/888d/#using-the-python-script","title":"Using the python script","text":"<p>Source code for the script is available in <code>make_definition.py</code>, currently co-located in the FEP-888d folder.</p> <p>The script will operate according to the following logic:</p>"},{"location":"fep/888d/#options-available","title":"Options available","text":"<ul> <li>GENERATE_CONTEXT_FROM_TERMDEFS: if true, will automatically generate a context. The generated context can be manipulated by the presence of JSON-LD keywords like <code>@id</code>, <code>@vocab</code>, <code>@set</code>, and <code>@list</code>.</li> <li>OUTPUT_FORMATS: a list of formats to be used when generating outputs for both singular term definitions as well as FEP-wide schema or ontology files.</li> <li>PATH_TO_CONTEXT: a pre-written context document whose context mapping will be injected into the JSON-LD output of the FEP. Defaults to the JSON-LD alternate format of the FEP (<code>fep-$SLUG.jsonld</code> co-located within the FEP folder). Failure to read this path as valid JSON will lead to an empty context mapping.</li> </ul>"},{"location":"fep/888d/#algorithm","title":"Algorithm","text":"<ul> <li>(1) Render markdown as HTML.</li> <li>(2) Find all HTML elements that have <code>id</code> and <code>resource</code>. These are considered \"term definitions\".</li> <li>(3) Initialize an empty graph for the entire FEP.</li> <li>(4) Initialize an empty context mapping for the entire FEP.</li> <li>(4.1) If GENERATE_CONTEXT_FROM_TERMDEFS is set to false, then try to load an existing context mapping from PATH_TO_CONTEXT.</li> <li>(5) Loop over all elements identified as \"term definitions\".</li> <li>(5.1) Initialize an empty graph for the current term.</li> <li>(5.2) Set the subject <code>s</code> equal to the <code>resource</code> attribute of the element.</li> <li>(5.3) Set <code>term_type</code> equal to the <code>typeof</code> attribute of the element.</li> <li>(5.4) Add a statement to the current term graph, of the form <code>s rdf:type term_type</code>.</li> <li>(5.5) Loop over all child elements with a <code>property</code> attribute. These are considered \"property definitions\".<ul> <li>(5.5.1) Set the predicate <code>p</code> equal to the <code>property</code> attribute.</li> <li>(5.5.2) Set the object <code>o</code> equal to the <code>resource</code> attribute. If not present, then set the object <code>o</code> equal to the <code>href</code> attribute. If not present, then set the object <code>o</code> equal to the <code>content</code> attribute. If not present, then set the object <code>o</code> equal to the inner text of the current element.</li> <li>(5.5.3) Set the language <code>lang</code> equal to the <code>lang</code> attribute.</li> <li>(5.5.4) Set the datatype <code>datatype</code> equal to the <code>datatype</code> attribute.</li> <li>(5.5.5) Add a statement to the current term's graph using <code>s</code>, <code>p</code>, <code>o</code>, <code>lang</code>, and <code>datatype</code>, making sure to expand any CURIEs according to the RDFa initial context.</li> <li>(5.5.5.1) If there is a <code>datatype</code> or <code>lang</code>, then the object <code>o</code> is a Literal.</li> <li>(5.5.5.2) Otherwise, the object <code>o</code> is an IRI reference.</li> </ul> </li> <li>(5.6) If GENERATE_CONTEXT_FROM_TERMDEFS is set to true and the current element does not have an <code>excluded</code> attribute, then extract JSON-LD keywords and automatically generate a context term definition.<ul> <li>(5.6.1) Set the shorthand <code>term_name</code> equal to the <code>id</code> attribute of the element.</li> <li>(5.6.2) Initialize an <code>options</code> mapping to keep track of extended term definitions.</li> <li>(5.6.3) Pairwise combine each child <code>&lt;dt&gt;</code> element with its corresponding child <code>&lt;dd&gt;</code> element.</li> <li>(5.6.4) Find a <code>&lt;dt&gt;</code> element whose inner text is exactly \"Range\". [TODO: something less fragile?]</li> <li>(5.6.5) Check the corresponding <code>&lt;dd&gt;</code> element for a <code>resource</code> attribute. If there is one, extract this IRI as <code>type_iri</code>.</li> <li>(5.6.5.1) If this <code>type_iri</code> starts with <code>xsd</code>, then insert <code>@type: type_iri</code> into the <code>options</code> mapping.</li> <li>(5.6.6) Check the text content of the <code>&lt;dd&gt;</code> element for a substring <code>@id</code>. If found, then insert <code>@type: @id</code> into the <code>options</code> mapping.</li> <li>(5.6.7) Check the text content of the <code>&lt;dd&gt;</code> element for a substring <code>@vocab</code>. If found, then insert <code>@type: @vocab</code> into the <code>options</code> mapping.</li> <li>(5.6.8) Check the text content of the <code>&lt;dd&gt;</code> element for a substring <code>@set</code>. If found, then insert <code>@container: @set</code> into the <code>options</code> mapping.</li> <li>(5.6.9) Check the text content of the <code>&lt;dd&gt;</code> element for a substring <code>@list</code>. If found, then insert <code>@container: @list</code> into the <code>options</code> mapping.</li> <li>(5.6.10) If the <code>options</code> mapping is not empty, then insert <code>@id: s</code> into the <code>options</code> mapping.</li> <li>(5.6.11) If the <code>options</code> mapping is not empty, then map <code>term_name</code> to the <code>options</code> mapping. Otherwise, map <code>term_name</code> to the string <code>s</code>. This is considered a \"context term definition\".</li> <li>(5.6.12) Insert the context term definition into the context mapping.</li> </ul> </li> <li>(5.7) Merge the current term graph into the FEP-wide graph.</li> <li>(5.8) If the subject <code>s</code> is not a fragment identifier, then for each alternate output format in OUTPUT_FORMATS, serialize the current term graph to that format and write it to a file within a co-located subfolder with the name <code>term_name</code>.<ul> <li>(5.8.1) If HTML+RDFa is one of the OUTPUT_FORMATS, then the term definition element can be copied as-is.</li> <li>(5.8.2) If Markdown is one of the OUTPUT_FORMATS:</li> <li>(5.8.2.1) Write a first-level heading with the shorthand <code>term_name</code> set equal to the <code>id</code> attribute of the term definition.</li> <li>(5.8.2.2) Pairwise combine each child <code>&lt;dt&gt;</code> element with its corresponding child <code>&lt;dd&gt;</code> element.</li> <li>(5.8.2.3) For each pair of <code>&lt;dt&gt;</code> and <code>&lt;dd&gt;</code> element, write a definition list term and definition list definition.</li> <li>(5.8.2.4) Extract examples by finding all HTML elements that are <code>&lt;pre&gt;</code>.</li> <li>(5.8.2.5) If any examples were found, write a second-level heading with the text set to <code>Examples</code>.</li> <li>(5.8.2.5) For each example block found:<ul> <li>(5.8.2.5.1) If a <code>title</code> attribute is present, then write a paragraph with the text set to the value of <code>title</code>.</li> <li>(5.8.2.5.2) Write the start of the code block with backticks. If a <code>lang</code> attribute is present, then append its value immediately afterward.</li> <li>(5.8.2.5.3) Write the inner text of the example.</li> <li>(5.8.2.5.4) Write the closing backticks for closing the code block.</li> </ul> </li> </ul> </li> <li>(6) For each alternate output format in OUTPUT_FORMATS, serialize the FEP-wide graph to that format and write it to a file co-located with the name <code>fep-$SLUG.$FORMAT</code>. For example, if Turtle output is enabled, then <code>fep-xxxx.ttl</code> will be written. For JSON-LD output, use the current context mapping when serializing the FEP-wide graph. For HTML+RDFa or Markdown output, ignore these formats. (Outputting HTML can be done as part of a separate process of rendering the Markdown with a static site generator. Outputting the Markdown makes no sense since it is the source material and should not be overwritten.)</li> <li>(6.1) If GENERATE_CONTEXT_FROM_TERMDEFS is set to true but JSON-LD is not one of the output formats, then initialize and serialize an empty graph, using the generated context term definition.<ul> <li>(6.1.1) Remove the graph from the context document.</li> <li>(6.1.2) Save the generated context document as <code>fep-xxxx.jsonld</code>.</li> </ul> </li> </ul>"},{"location":"fep/888d/#usage","title":"Usage","text":"<ul> <li>Either install required python dependencies, for example in a virtual environment:</li> <li>Python-Markdown (<code>markdown</code> on PyPI)</li> <li>BeautifulSoup4 (<code>beautifulsoup4</code>)</li> <li>RDFLib (<code>rdflib</code>)</li> <li>markdownify (<code>markdownify</code>)</li> <li>python-frontmatter (<code>python-frontmatter</code>)</li> <li>...or use <code>uv run</code> instead.</li> <li>The script should be run in the base directory of the fediverse/fep repo.</li> <li><code>python fep/888d/make_definitions.py $SLUG</code> will read term definitions from the contents of <code>fep/$SLUG/fep-$SLUG.md</code>, then generate alternate formats for the FEP. If your terms are defined with fragment identifiers (of the form <code>https://w3id.org/fep/xxxx#term</code>), then only the FEP itself will be generated in alternate formats. If your terms are defined with absolute identifiers (of the form <code>https://w3id.org/fep/xxxx/term</code>), then co-located subdirectories will be created as well, and singular term definitions will be generated in chosen output formats. If a <code>context.jsonld</code> document is present in the FEP folder, then it will be copied into the JSON-LD alternate format output. The current default output formats are:</li> <li>.ttl (Turtle)</li> <li>.rdf (RDF/XML)</li> <li>.jsonld (JSON-LD)</li> <li>.html (HTML+RDFa) (singular term definitions only)</li> <li>.md (README.md for the Codeberg repo viewer) (singular term definitions only)</li> <li><code>python fep/888d/make_definitions.py $SLUG -c</code> will do everything in the above bullet point, except for copying <code>context.jsonld</code>. Instead, it will check for JSON-LD keywords and attempt to auto-generate a context document based on those keywords. If JSON-LD output is disabled, the script will generate only a context document instead of a combined context document and schema or ontology.</li> </ul>"},{"location":"fep/888d/#example-terms-defined-by-this-fep","title":"Example terms defined by this FEP","text":"<p>(This section is non-normative.)</p> <p>Using the same fictitious terms from the above examples:</p> <ul> <li><code>SomeType</code> is a term for some type</li> <li><code>exampleA</code> is a term for a property with some literal value (string, boolean, number)</li> <li><code>exampleB</code> is a term for a property that links to another node on the graph (for example, another object)</li> <li><code>exampleC</code> is a term for a property that is an ordered list of literal values that are specifically non-negative integers</li> <li><code>ExcludedExample</code> is a term for some class that is useful for ontology but not intended to be used for the context mapping</li> </ul> <p>We can formulate the following machine-readable term definition blocks, which are intended to provide a range of examples of various RDFa syntactical constructs for demonstration purposes. Specifically, the following mechanisms are demonstrated:</p> <ul> <li>Declaring a <code>resource</code> with a <code>typeof</code> some class</li> <li>Declaring a natural language <code>property</code> with a specified <code>lang</code></li> <li>Specifically, the rdfs:label and rdfs:comment properties provide natural language representations and descriptions of the defined term</li> <li>Declaring that a <code>property</code> has a value that is a <code>resource</code></li> <li>Declaring a <code>property</code> where the object value is referred to by <code>href</code> instead of <code>resource</code> (for example, in an anchor link)</li> <li>Declaring multiple values for a given <code>property</code> by including multiple tags</li> <li>Providing additional schema metadata that wasn't provided in the <code>@context</code>, such as signaling that a property is required or functional</li> <li>Declaring that a <code>property</code> has literal <code>content</code> instead of referring to a resource, and that this content can have its own <code>datatype</code> to which it can be coerced</li> <li>Excluding a term definition from the automatically generated context document, using the <code>excluded</code> attribute</li> </ul> SomeType URI <code>https://w3id.org/fep/888d/SomeType</code> Label Some Type Comment Some type. Subclass of Object See also exampleA | exampleB | exampleC Is defined by FEP-888d <pre>\n<code>\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n</code>\n</pre> exampleA URI <code>https://w3id.org/fep/888d/exampleA</code> Label has example relation A with the literal value Comment A property with some literal value (string, boolean, number) Domain SomeType Range A literal value Required Yes Functional Yes Is defined by FEP-888d <pre>\n<code>\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n</code>\n</pre> exampleB URI <code>https://w3id.org/fep/888d/exampleB</code> Label has example relation B with Comment A property that links to another node on the graph (for example, another object) Domain SomeType Range An object (<code>@id</code>) Required No Functional No Is defined by FEP-888d <pre>\n<code>\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n</code>\n</pre> exampleC URI <code>https://w3id.org/fep/888d/exampleC</code> Label has an exampleC value of Comment A property that is an ordered list of literal values that are specifically non-negative integers Domain SomeType Range A list of non-negative integers (@list) Required No Functional Yes Is defined by FEP-888d <pre>\n<code>\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n</code>\n</pre> ExcludedExample URI <code>https://w3id.org/fep/888d#ExcludedExample</code> Label Excluded Example Comment A class that is useful for ontology but not intended to be used for the context mapping Subclass of Object Is defined by FEP-888d"},{"location":"fep/888d/#references","title":"References","text":"<ul> <li>ActivityPub Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>AS2-Core James M Snell, Evan Prodromou, Activity Streams 2.0, 2017</li> <li>AS2-Vocab James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>CM-ATTRS mb21, Consistent attribute syntax, 2014</li> <li>LD-TERM-DFN Gregg Kellogg, Pierre-Antoine Champin, Dave Longley, JSON-LD 1.1 - Section 9.15.1 \"Expanded term definition\", 2020</li> <li>RFC-2119 S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> <li>1 helge, FEP-2e40: The FEP Vocabulary Extension Process, 2023</li> </ul>"},{"location":"fep/888d/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/888d/SomeType/","title":"SomeType","text":"<p>URI : <code>https://w3id.org/fep/888d/SomeType</code></p> <p>Label : Some Type</p> <p>Comment : Some type.</p> <p>Subclass of : Object</p> <p>See also : exampleA | exampleB | exampleC</p> <p>Is defined by : FEP-888d</p>"},{"location":"fep/888d/SomeType/#examples","title":"Examples","text":"<p>Example of a SomeType with properties exampleA, exampleB, exampleC</p> <pre><code>{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n</code></pre>"},{"location":"fep/888d/exampleA/","title":"exampleA","text":"<p>URI : <code>https://w3id.org/fep/888d/exampleA</code></p> <p>Label : has example relation A with the literal value</p> <p>Comment : A property with some literal value (string, boolean, number)</p> <p>Domain : SomeType</p> <p>Range : A literal value</p> <p>Required : Yes</p> <p>Functional : Yes</p> <p>Is defined by : FEP-888d</p>"},{"location":"fep/888d/exampleA/#examples","title":"Examples","text":"<p>Example of a SomeType with properties exampleA, exampleB, exampleC</p> <pre><code>{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n</code></pre>"},{"location":"fep/888d/exampleB/","title":"exampleB","text":"<p>URI : <code>https://w3id.org/fep/888d/exampleB</code></p> <p>Label : has example relation B with</p> <p>Comment : A property that links to another node on the graph (for example, another object)</p> <p>Domain : SomeType</p> <p>Range : An object (@id)</p> <p>Required : No</p> <p>Functional : No</p> <p>Is defined by : FEP-888d</p>"},{"location":"fep/888d/exampleB/#examples","title":"Examples","text":"<p>Example of a SomeType with properties exampleA, exampleB, exampleC</p> <pre><code>{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n</code></pre>"},{"location":"fep/888d/exampleC/","title":"exampleC","text":"<p>URI : <code>https://w3id.org/fep/888d/exampleC</code></p> <p>Label : has an exampleC value of</p> <p>Comment : A property that is an ordered list of literal values that are specifically non-negative integers</p> <p>Domain : SomeType</p> <p>Range : A list of non-negative integers (@list)</p> <p>Required : No</p> <p>Functional : Yes</p> <p>Is defined by : FEP-888d</p>"},{"location":"fep/888d/exampleC/#examples","title":"Examples","text":"<p>Example of a SomeType with properties exampleA, exampleB, exampleC</p> <pre><code>{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n</code></pre>"},{"location":"fep/8967/","title":"FEP-8967: Generating link previews for attached links","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2025-09-16 #681 Discussions codeberg"},{"location":"fep/8967/#summary","title":"Summary","text":"<p>A common feature in social applications is to show users a rich preview of a link included in the content of a message or post, before the user clicks the link. Currently, applications like Mastodon generate link previews for the first link found in the content, without considering the publisher's possible intent. This FEP allows publishers to explicitly signal which links are intended for special processing, using the existing attachment model. Optionally, publishers can include their own link preview information so that trusting consumers can skip generating their own previews.</p>"},{"location":"fep/8967/#link-in-attachment","title":"Link in attachment","text":"<p>The attachment model can be used to indicate that something performs a particular function as an extension of the main content. AS2-VOCAB defines <code>attachment</code> as \"semantically similar to attachments in email\" for something that \"potentially requires special handling\".</p> <p>Publishers MAY include a Link in <code>attachment</code> to signal that this link should be processed semantically as an attached link preview card, appearing similarly to attached images, attached videos, or attached audio. The link attachment MUST have an <code>href</code>, indicating that it is a <code>Link</code>.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"attachment\": {\n \"href\": \"https://foo.example/\"\n }\n}\n</code></pre> <p>Upon encountering an <code>attachment</code> that is a <code>Link</code>, consumers SHOULD show this link as \"attached\" to the object. At minimum, the <code>href</code> can be rendered directly, perhaps alongside an icon representing a link.</p> <p></p>"},{"location":"fep/8967/#link-preview","title":"Link preview","text":"<p>Publishers MAY include link preview information using the <code>preview</code> property:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sensitive\": \"as:sensitive\"\n }\n ],\n \"attachment\": {\n \"href\": \"https://foo.example/\",\n \"preview\": {\n \"type\": \"Article\",\n \"name\": \"Example Essay\",\n \"summary\": \"In which some information is provided...\",\n \"image\": {\n \"sensitive\": true,\n \"url\": {\n \"href\": \"https://cover-image.example/file.jpg\",\n \"mediaType\": \"image/jpg\",\n \"width\": 1200,\n \"height\": 630\n }\n },\n \"attributedTo\": {\n \"name\": \"The Author\",\n \"icon\": {\n \"url\": {\n \"href\": \"https://avatar.example/file.png\",\n \"mediaType\": \"image/png\",\n \"width\": 48,\n \"height\": 48\n }\n },\n \"url\": {\n \"href\": \"https://author.example/\",\n }\n }\n }\n }\n}\n</code></pre> <p>The exact form of the <code>preview</code> and its processing model is out of scope of this FEP (as each consumer is free to render information according to their own design language and understanding), but some properties may be useful as equivalents of OpenGraph properties which are widely used for link previews:</p> <ul> <li><code>name</code> -- similar to <code>og:title</code>, indicate the preview card's primary text.</li> <li><code>summary</code> -- similar to the <code>og:description</code>, indicate the preview card's secondary text.</li> <li><code>image</code> -- similar to <code>og:image</code>, indicate the preview card's image.</li> <li><code>type</code> -- similar to <code>og:type</code>, indicate the type of the target resource. This can be used to select an appropriate icon representing the resource.</li> <li><code>attributedTo</code> -- loosely similar to <code>article:author</code>, <code>music:musician</code>, <code>music:creator</code>, <code>book:author</code>, and other such properties, indicate the preview card's attribution.<ul> <li><code>name</code> -- the name that should be attributed</li> <li><code>icon</code> -- the icon that should be displayed alongside the attributed name</li> <li><code>url</code> -- the link that should wrap the attribution</li> </ul> </li> </ul> <p>Consumers that do not trust the publisher's provided <code>preview</code> information MAY generate their own preview through whichever means they find appropriate, such as for example fetching the link target and extracting OpenGraph information or HTML tags such as <code>&lt;title&gt;</code> or <code>&lt;meta&gt;</code>.</p> <p></p>"},{"location":"fep/8967/#alternative-approaches","title":"Alternative approaches","text":"<p>(This section is non-normative.)</p>"},{"location":"fep/8967/#attaching-objects-directly","title":"Attaching objects directly","text":"<p>Rather than attaching a Link with an optional preview, resources can be attached directly by their <code>id</code> along with arbitrary optional claims.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"attachment\": {\n \"id\": \"https://foo.example/\",\n \"type\": \"Article\",\n \"name\": \"Example Essay\",\n // ...\n }\n}\n</code></pre> <p>The difference between attaching an Object versus attaching a Link is that attaching an Object creates a direct relation between the current object and the attached object, whereas attaching a Link does not create this relation between two objects. For example, consider the difference between attaching an Image versus attaching a Link that targets an image. In most cases, directly attaching the Image is probably more appropriate. However, if the publisher attaches a Link instead, it might be because the link is present in the <code>content</code> and the publisher wishes to indicate this for special processing; for whatever reason, the publisher does not want to directly attach the Image. This depends on the specific details of the processing model, which is out of scope for this FEP.</p>"},{"location":"fep/8967/#implementations","title":"Implementations","text":"<ul> <li>Mastodon 4.5: Intent to publish Link in attachment, per Mastodon-PR. Publishing link preview information is not currently planned at this time; Mastodon will instead consume attached links by their <code>href</code> as a signal to generate link previews with their existing OpenGraph logic. In the future, Mastodon plans to stop automatically extracting the first link in <code>content</code>, but for now the first link is used as a fallback in case no <code>attachment</code> is present.</li> </ul>"},{"location":"fep/8967/#references","title":"References","text":"<ul> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>Gargron, mastodon/mastodon#36104: Add support for Link objects in attachment, 2025</li> </ul>"},{"location":"fep/8967/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/8a8e/","title":"Index","text":"<p>hide: - navigation description: \"ActivityStreams defines the Object Type Event. In real-world applications, the event object immediately showed the need for extension. Applications featuring Event objects have often chosen to add additional attributes and clarifications (i.e., interpretations) in order to implement their particular use case. This proposal clarifies and extends the ActivityPub standard to address the needs that have arisen in real-world implementations. This includes guidelines for the minimal interoperable event, handling of RSVP (\"r\u00e9pondez s'il vous pla\u00eet\", i.e., attendee management, and side effects), attendee capacities, physical location addresses, virtual locations, timezone, and clarification of how to control the visibility of events in federation. These differences in how the aforementioned features are implemented have led to fragmentation in how events are published, discovered, and managed across platforms.\"</p>"},{"location":"fep/8a8e/#fep-8a8e-a-common-approach-to-using-the-event-object-type","title":"FEP-8a8e: A common approach to using the Event object type","text":"Authors Status Date received Tracking issue Discussions Repository Andr\u00e9 Menrath andre.menrath@posteo.de, les lesion@autistici.org <code>DRAFT</code> 2025-04-23 #565 Discussions codeberg"},{"location":"fep/8a8e/#summary","title":"Summary","text":"<p>ActivityStreams defines the Object Type <code>Event</code>. In real-world applications, the event object immediately showed the need for extension. Applications featuring <code>Event</code> objects have often chosen to add additional attributes and clarifications (i.e., interpretations) in order to implement their particular use case. This proposal clarifies and extends the ActivityPub standard to address the needs that have arisen in real-world implementations.</p> <p>This includes guidelines for the minimal interoperable event, handling of RSVP (\"r\u00e9pondez s'il vous pla\u00eet\", i.e., attendee management, and side effects), attendee capacities, physical location addresses, virtual locations, timezone, and clarification of how to control the visibility of events in federation.</p> <p>These differences in how the aforementioned features are implemented have led to fragmentation in how events are published, discovered, and managed across platforms.</p>"},{"location":"fep/8a8e/#what-is-not-covered","title":"What is not covered","text":"<p>We do not cover recurrence, multiple-scheduled, and connected events.</p> <p>For more information on that, check out for example: - RFC 5545 Section 3.8.5.3 - RFC 5545 Section 3.8.4.5</p>"},{"location":"fep/8a8e/#history","title":"History","text":"<p>Fediverse applications, such as Mobilizon, Friendica, Gancio, and Hubzilla, have adopted varying approaches for handling ActivityPub events. Their different aims and the varying use cases have led to fragmentation in how events are published, discovered, and managed across federation.</p> <p>Friendica and Hubzilla, for instance, follow the example set by ActivityStreams 2.0, using <code>Invite</code> for events, which seems to be very suited for smaller, more private gatherings, such as a birthday party. <code>Invite</code> seems natural when visibility is intentionally limited. On the other hand, applications like Mobilizon or Gancio have a focus on announcing public events and therefore chose to make use of the <code>Create</code> activity. Maybe another reason was that using creates eases interoperability with applications that do not support the <code>Invite</code> activity (yet). Furthermore, this may be justified because public events should as well be discoverable by anonymous actors, even by people who do not have an account in the Fediverse. This is achieved through federated event calendars, which are assembled by the instance administrators, who can add various federated event sources by following federated ActivityPub actors.</p> <p>Within Mobilizon, for historical and internal reasons, the federated visibility of events is currently still controlled by whether a <code>Group</code> has published the <code>Event</code> or not, and whether the <code>Event</code> has also been announced at least once, rather than using <code>to</code>/<code>cc</code> (e.g., like Mastodon's \"public silent\"). In federation with other applications, this and other discrepancies led to issues, which have proven to be huge blockers. That's why this FEP sets out to define a common ground.</p>"},{"location":"fep/8a8e/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.</p> <p>The context of attributes: Attributes that have no context prefix are to be interpreted as being in the <code>https://www.w3.org/ns/activitystreams</code> context.</p>"},{"location":"fep/8a8e/#object-type-event","title":"Object Type Event","text":"<p>The ActivityStreams specification already defines an object type <code>Event</code> which is inheriting all properties from the base <code>Object</code>, so some properties useful for describing an event are already specified, but some others are missing, and an enhanced specification will help.</p> <p>For an <code>Event</code> at least the following properties are REQUIRED:</p> <ul> <li> <p><code>name</code>: A simple, human-readable, plain-text title for the event. HTML markup MUST NOT be included. Values for the name MUST NOT be an empty string and the name MAY be expressed using multiple language-tagged values.</p> </li> <li> <p><code>startTime</code>: The date and time describing the moment the event began or is scheduled to begin in the format as specified in the Activity Stream 2.0 specification. The <code>startTime</code> is not REQUIRED in case an event is postponed and a <code>https://w3id.org/fep/8a8e/previousStartTime</code> is set instead.</p> </li> <li> <p><code>endTime</code>: The date and time when the event ends or is scheduled to end in the format as specified in the Activity Stream 2.0 specification. The <code>endTime</code> MUST be a later date than the <code>startTime</code>. If the event is open-ended, it must include an end time at which applications (not humans) can treat the event as having ended.</p> </li> <li> <p><code>organizers</code></p> </li> </ul> <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"startTime\": \"2014-12-31T23:00:00Z\",\n \"endTime\": \"2015-01-01T06:00:00Z\",\n \"organizers\": null\n}\n</code></pre>"},{"location":"fep/8a8e/#events-with-open-end","title":"Events with Open End","text":"<p>If the event is open-ended, or the <code>endTime</code> is not intended to be displayed to humans, the <code>https://w3id.org/fep/8a8e/displayEndTime</code> attribute SHOULD be used and set to <code>false</code>.</p> <p>Receiving an <code>Event</code> with an <code>endTime</code> set and no <code>https://w3id.org/fep/8a8e/displayEndTime</code> set, it MUST be treated as if <code>https://w3id.org/fep/8a8e/displayEndTime</code> is set to <code>true</code>, requiring the <code>endTime</code> to be displayed.</p> <p>If an <code>Event</code> is received with no <code>endTime</code> set, the <code>Event</code> MUST be treated as if <code>https://w3id.org/fep/8a8e/displayEndTime</code> were set to <code>false</code> to avoid misleading users. In such cases, the receiving application MAY infer an appropriate <code>endTime</code> based on its requirements.</p>"},{"location":"fep/8a8e/#time-zone","title":"Time zone","text":"<p>This section is non-normative.</p> <p>The <code>https://w3id.org/fep/8a8e/timezone</code> property is introduced to support time zone information for events, enabling consistent handling of local \"wall times\".</p> <p>It is important to note that the \"time-offset\" component in <code>startTime</code> and <code>endTime</code> property does not correlate to time-zones, and while times that include the \"time-offset\" component work well for timestamps, they cannot be reliably converted to and from local \"wall times\" without additional information and processing. https://www.w3.org/TR/activitystreams-core/#dates</p> <p>For this reason, if the <code>Event</code> is primarily conducted in a single specific time-zone, the application SHOULD set the <code>https://w3id.org/fep/8a8e/timezone</code>.</p> <p>In case the <code>https://w3id.org/fep/8a8e/timezone</code> property is specified it MUST be a specific IANA time zone identifier included in the IANA Time Zone Database (often called tz or zoneinfo, see https://www.w3.org/TR/timezone/#time-zone-identifiers).</p> <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00+01:00\",\n \"endTime\": \"2015-01-01T04:00:00+01:00\",\n \"timezone\": \"Europe/Rome\"\n}\n</code></pre>"},{"location":"fep/8a8e/#location","title":"Location","text":"<p>ActivityStreams provides a flexible framework for representing locations, as outlined in this section of the specification. However, its flexibility can sometimes lead to ambiguity.</p> <p>This section aims to establish a clear and consistent format for representing the locations of events, whether they are physical, virtual, or mixed.</p> <p>Rather than creating a new schema, this specification adopts the <code>location</code> property from Schema.org with some adjustments to better suite federated event handling:</p> <ul> <li> <p>Exclusion of <code>Text</code> as a valid type: Unlike the original Schema.org definition, Text is not a permitted type for location and MUST NOT be used. If only a name is required to describe a location, the Place type can be used.</p> </li> <li> <p>Support for multiple locations: To specify mixed-format events (e.g., hybrid online and offline events), the location property can contain an array of locations. Each entry in the array can represent either a physical <code>Place</code> or a <code>VirtualLocation</code>.</p> </li> </ul>"},{"location":"fep/8a8e/#physical","title":"Physical","text":"<p>For adding information about a physical location of an <code>Event</code> the <code>Place</code> object within the events <code>location</code> property SHOULD be extended via the attribute <code>address</code> which is of type <code>https://schema.org/address</code> or <code>https://schema.org/Place</code> MAY be used directly.</p> <p>This allows the address to be given as a simple text input as well as a structured <code>https://schema.org/PostalAddress</code>. Applications MUST be able to transmogrify both incoming address types to their internal representation.</p> <p>It is RECOMMENDED to always provide geolocation data via <code>longitude</code> and <code>latitude</code>, as filtering by these properties is crucial in federation.</p> <p>Note Even if users do not provide this data directly, attempting to look up the geolocation of a physical location once on the source side can save a lot of resources caused by multiple lookups on the receivers in federation.</p> <p>Example with pure text address:</p> <pre><code>{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00+01:00\",\n \"endTime\": \"2015-01-01T04:00:00+01:00\",\n \"location\": {\n \"type\": \"Place\",\n \"name\": \"Foo Bar\",\n \"address\": \"15 Fediverse Street, 8010 Fediverse Town, Austria\",\n \"latitude\": 47.06829,\n \"longitude\": 15.45634\n }\n}\n</code></pre> <p>Example with address of type <code>https://schema.org/PostalAddress</code>:</p> <pre><code>{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": {\n \"id\": \"\",\n \"type\": \"Place\",\n \"name\": \"Foo Bar\",\n \"address\": {\n \"type\": \"PostalAddress\",\n \"addressCountry\": \"Austria\",\n \"addressLocality\": \"Fediverse Town\",\n \"addressRegion\": \"Steiermark\",\n \"postalCode\": \"8010\",\n \"streetAddress\": \"15 Fediverse Street\"\n },\n \"latitude\": 47.077892,\n \"longitude\": 15.460744\n }\n}\n</code></pre>"},{"location":"fep/8a8e/#virtual","title":"Virtual","text":"<p>An online Location SHOULD be represented by using a type <code>VirtualLocation</code> as specified in https://schema.org/VirtualLocation.</p> <p>Applications SHOULD at least set and be able to make use of <code>name</code> and <code>url</code> properties.</p> <pre><code>{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"Fediverse Moderation Meeting\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": {\n \"type\": \"VirtualLocation\",\n \"name\": \"Jitsi Meet Meeting Link\",\n \"url\": \"https://jitsi.example.org/fediverse-moderation-meeting\"\n }\n}\n</code></pre>"},{"location":"fep/8a8e/#hybrid","title":"Hybrid","text":"<p>Using multiple entries in <code>location</code> makes it easy to define multiple virtual and physical locations.</p> <p>When not supporting multiple entries in <code>location</code> applications MUST use the first supported item.</p> <pre><code>{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"Discussion about the Fediverse\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": [\n {\n \"type\": \"Place\",\n \"name\": \"Fediverse University\",\n \"url\": \"https://university.example.com\",\n \"address\": \"15 Fediverse Street, 1337 Fediverse Town, Fediverse Country\"\n },\n {\n \"type\": \"VirtualLocation\",\n \"name\": \"Big Blue Button Participation Link\",\n \"url\": \"https://bbb.example.com/meeting-room-1\"\n },\n {\n \"type\": \"VirtualLocation\",\n \"name\": \"Live Stream\",\n \"url\": \"https://peertube.example.com/fediverse-discussion\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/8a8e/#event-status","title":"Event status","text":"<p>To indicate the status of an <code>Event</code>, the <code>https://w3id.org/fep/8a8e/eventStatus</code> property MUST be used. As a fallback, it is RECOMMENDED to also support the <code>status</code> property, especially for incoming remote events, which might contain a value from <code>https://www.w3.org/2002/12/cal/ical#status</code>.</p> <p>The possible values for the <code>https://w3id.org/fep/8a8e/eventStatus</code> are: - EventCancelled - EventMovedOnline - EventPostponed - EventRescheduled - EventScheduled - EventTentative</p> <p>Note <code>https://w3id.org/fep/8a8e/eventStatus</code> is almost identical to <code>https://schema.org/eventStatus</code>, however it has an additional status type for the event being tentative, which is leaned on iCalendar's <code>TENTATIVE</code>.</p>"},{"location":"fep/8a8e/#rsvp-attendee-management","title":"RSVP (Attendee Management)","text":""},{"location":"fep/8a8e/#not-handling-rsvp","title":"Not handling RSVP","text":"<p>If the event creator or the event creator's application does not handle joins via ActivityPub it MUST always respond to <code>Join</code> activities with an <code>Ignore</code> response, where the attribute <code>object</code> MUST either contain the ID of the <code>Join</code> activity, or contain a <code>Join</code> object with the <code>target</code> being set to the event-objects ID, where the former is preferred.</p> <p>This SHOULD be made clear beforehand by setting <code>https://w3id.org/fep/8a8e/joinMode</code> to <code>none</code>.</p>"},{"location":"fep/8a8e/#signaling-how-joins-are-handled","title":"Signaling how joins are handled","text":"<p>If an application does not handle joins of an <code>Event</code> via ActivityPub, but knows of an external URL which handles the attendee management <code>https://w3id.org/fep/8a8e/joinMode</code> SHOULD be set to <code>external</code>. If <code>joinMode</code> is set to <code>external</code>, also <code>https://w3id.org/fep/8a8e/externalParticipationUrl</code> SHOULD be set.</p> <p>Note This is compatible with Mobilizon's definition of <code>joinMode</code>, but it is not identical.</p> <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://example.org/foo#register\"\n}\n</code></pre> <p>Note <code>externalParticipationUrl</code> could also be a <code>mailto</code>-URI, which is a way of handling event participants that is still used quite frequently by many organizers.</p> <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/fediverse-workshop\",\n \"organizers\": null,\n \"name\": \"Fediverse Workshop\",\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2025-01-01T04:00:00-08:00\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"mailto:fediverse-workshop@example.org\"\n}\n</code></pre>"},{"location":"fep/8a8e/#indicating-visibility-and-target-audience-of-rsvp-requests","title":"Indicating visibility and target audience of RSVP requests","text":"<p>Who the participants of an event are should possibly not be public, but should, for example, only be visible to the organisers of the event, in other cases also to other confirmed participants, or, for example, completely anonymously. An Event SHOULD provide information about the visibility scope of valid <code>Join</code> requests targeting the event via utilizing <code>https://w3id.org/fep/8a8e/requiredJoinVisibility</code>.</p>"},{"location":"fep/8a8e/#showing-rsvp-status-via-activitypub","title":"Showing RSVP status via ActivityPub","text":"<p>To specify the maximum number of attendees for an <code>Event</code>, applications SHOULD use the <code>https://schema.org/maximumAttendeeCapacity</code>.</p> <p>Remaining attendee capacity SHOULD be shown indirectly via using the Collection <code>https://w3id.org/fep/8a8e/attendees</code>. This is a list of all event attendees. Every <code>Actor</code> that has accepted an <code>Invite</code> containing the <code>Event</code> as an <code>object</code> or which <code>Join</code> targeting the <code>Event</code> has been accepted is added as a side effect. Note that also other object types other than <code>Actor</code> (and it's subclasses) might be part of that collection, e.g. when an application allows for attendees to register by e-mail. In that case using <code>https://schema.org/Person</code> or <code>https://schema.org/Organization</code> is RECOMMENDED. The <code>attendees</code> collection MUST be either an <code>OrderedCollection</code> or a <code>Collection</code> and MAY be filtered by the privileges of an authenticated user, or as appropriate if no authentication is given.</p> <pre><code>{\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo-bar-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"maximumAttendeeCapacity\": 100,\n \"attendees\": {\n \"type\": \"Collection\",\n \"id\": \"https://example.org/foo-bar-party/attendees\",\n \"totalItems\": 84\n }\n}\n</code></pre>"},{"location":"fep/8a8e/#attendees-collection","title":"Attendees Collection","text":"<p>Every <code>Event</code> MAY have an <code>attendees</code> collection. This is a list of all <code>actor</code>s that meet one or more of the following conditions, added as a side effect.</p> <ul> <li>The actor has sent a <code>Join</code> activity with this object as the <code>object</code> property that has been answered with an <code>Accept</code>.</li> <li>The actor has responded to an <code>Invite</code> activity from the <code>Event</code>'s owner (where the Event is specified as the <code>object</code> property) with an <code>Accept</code> activity.</li> </ul> <p>The <code>attendees</code> collection MUST be either an <code>OrderedCollection</code> or a <code>Collection</code> and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given. This collection MAY have the <code>totalItems</code> set regardless of authentication. If <code>https://schema.org/maximumAttendeeCapacity</code> is used <code>totalItems</code> SHOULD be public to disclose remaining capacity.</p>"},{"location":"fep/8a8e/#event-banner-and-poster-images","title":"Event Banner and Poster Images","text":""},{"location":"fep/8a8e/#flyer","title":"Flyer","text":"<p>If the event has an image in the style of a flyer or a share-pic which is representing the whole event (i.e., is also containing at least the name and start-date in the image) this image SHOULD be set using the <code>image</code> attribute.</p>"},{"location":"fep/8a8e/#banner","title":"Banner","text":"<p>If the <code>Event</code> has a particular banner image, this image SHOULD be set as the first <code>attachment</code> of type <code>Image</code> (type <code>Document</code> is also valid but not RECOMMENDED). To indicate that an image from within the <code>attachment</code> attribute of an <code>Event</code> should be preferred over the flyer/share-pic image(s) within <code>image</code> attribute, for the use of list pages and header images, the attribute <code>https://w3id.org/fep/8a8e/isBannerImage</code> SHOULD be set.</p> <p>Federated event calendars usually have thumbnail images with fixed aspect ratios. Therefore, <code>https://docs.joinmastodon.org/spec/activitypub/#focalPoint</code> SHOULD be set.</p> <p>Note Providing multiple versions of banner images and specifying <code>width</code> and <code>height</code> gives receiving applications the most flexibility to choose the most appropriate one based on aspect ratio.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"focalPoint\": {\n \"@container\": \"@list\",\n \"@id\": \"toot:focalPoint\"\n }\n }\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"image\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-flyer.png\",\n \"focalPoint\": [\n -0.55,\n 0.43\n ]\n }\n}\n</code></pre>"},{"location":"fep/8a8e/#event-categories","title":"Event Categories","text":"<p>Federated event platforms benefit from a shared understanding of general event categories. These categories serve a distinct purpose from user-generated tags or hashtags. While hashtags are typically entered freely and reflect a specific language, event categories are drawn from a limited, generalized set, making them more suitable for tasks like filtering events in aggregated or joined calendars.</p> <p>To represent categories, this FEP introduces the property https://w3id.org/fep/8a8e/category.</p> <p>In most cases, a single category should be sufficient. However, multiple categories may be included when relevant. Applications SHOULD utilize at least the following recommended set of event categories:</p> <ul> <li>ARTS</li> <li>AUTO_BOAT_AIR</li> <li>BOOK_CLUBS</li> <li>BUSINESS</li> <li>CAUSES</li> <li>CLIMATE_ENVIRONMENT</li> <li>COMMUNITY</li> <li>COMEDY</li> <li>CRAFTS</li> <li>CREATIVE_JAM</li> <li>DIY_MAKER_SPACES</li> <li>FAMILY_EDUCATION</li> <li>FASHION_BEAUTY</li> <li>FESTIVALS</li> <li>FILM_MEDIA</li> <li>FOOD_DRINK</li> <li>GAMES</li> <li>INCLUSIVE_SPACES</li> <li>LANGUAGE_CULTURE</li> <li>LEARNING</li> <li>LGBTQ</li> <li>MEETING</li> <li>MEDITATION_WELLBEING</li> <li>MOVEMENTS_POLITICS</li> <li>MUSIC</li> <li>NETWORKING</li> <li>OUTDOORS_ADVENTURE</li> <li>PARTY</li> <li>PERFORMING_VISUAL_ARTS</li> <li>PETS</li> <li>PHOTOGRAPHY</li> <li>SCIENCE_TECH</li> <li>SPIRITUALITY_RELIGION_BELIEFS</li> <li>SPORTS</li> <li>THEATRE</li> <li>WORKSHOPS_SKILL_SHARING</li> </ul> <p>Note Implementing applications may extend this list or allow user-generated categories. However, such additions may not be understood by all consuming platforms and could default to a generic or \"unknown\" category.</p>"},{"location":"fep/8a8e/#example-of-an-event-with-a-single-category","title":"Example of an event with a single category","text":"<pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/event/1\",\n \"name\": \"Demonstration againt fascism\",\n \"startTime\": \"2014-05-01T12:00:00-08:00\",\n \"startTime\": \"2014-05-01T18:00:00-08:00\",\n \"organizer\": null,\n \"category\": \"MOVEMENTS_POLITICS\"\n}\n</code></pre>"},{"location":"fep/8a8e/#example-of-an-event-with-multiple-categories","title":"Example of an event with multiple categories","text":"<pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/open-mic-jam\",\n \"name\": \"Open Mic Jam at Sunny's\",\n \"startTime\": \"2014-12-12T20:00:00-08:00\",\n \"endTime\": \"2014-12-12T23:00:00-08:00\",\n \"organizer\": null,\n \"category\": [\n \"MUSIC\",\n \"CREATIVE_JAM\"\n ]\n}\n</code></pre>"},{"location":"fep/8a8e/#discoverability","title":"Discoverability","text":"<p>Fediverse applications that feature <code>Event</code> objects MAY provide public calendars, search functions or grouped visualization pages (e.g., for tags or categories) that display events from multiple federated sources. In both cases these MUST never include events from federation that do not have <code>https://www.w3.org/ns/activitystreams#Public</code> (also <code>as:Public</code> or <code>Public</code> is valid) set in <code>to</code>. However, they MAY appear within a local representation of a remote actor, if <code>Public</code> set in <code>cc</code>.</p> <p>The discoverability of an incoming event from federation MUST NOT be controlled by the type of the actor set in <code>attributedTo</code> or by the <code>Activity</code> the event was received (e.g., <code>Announce</code>, <code>Create</code>).</p>"},{"location":"fep/8a8e/#organizers-collection","title":"Organizers Collection","text":"<p>Events SHOULD indicate their organizers by using <code>https://w3id.org/fep/8a8e/organizers</code>. The <code>organizers</code> collection helps provide clarity about the event's management, supports federated discovery, and improves interoperability across platforms.</p> <p>The value of the <code>organizers</code> field MUST be either an <code>OrderedCollection</code> or a <code>Collection</code>. The <code>items</code> within this collection MAY include actors, or links. If the organizer is not an ActivityPub entity (e.g., a website or organization without an actor), it is recommended to either use a <code>Link</code> or <code>https://schema.org/Person</code> or <code>https://schema.org/Organization</code>.</p> <pre><code>{\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo-bar-party\",\n \"name\": \"New Year's Party\",\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"id\": \"https://example.org/foo-bar-party/organizers\",\n \"totalItems\": 3,\n \"first\": {\n \"type\": \"CollectionPage\",\n \"partOf\": \"https://example.org/foo-bar-party/organizers\",\n \"items\": [\n \"https://example.social/users/johnmastodon\",\n \"https://example.org/somewebsite\",\n {\n \"type\": \"Organization\",\n \"id\": \"https://example.events/actors/eventorganizer\",\n \"context\": \"https://example.events/actors/eventorganizer/accept/example.org-foo-bar-party-offer\"\n }\n ]\n }\n }\n}\n</code></pre>"},{"location":"fep/8a8e/#explicit-non-disclosure","title":"Explicit Non-Disclosure","text":"<p>If the event\u2019s organizers are intentionally not disclosed, the <code>organizers</code> field MUST be set to <code>null</code>, have <code>totalItems: 0</code>, or contain an empty <code>items</code> array. This explicitly indicates a deliberate choice to withhold organizer information and takes precedence over any assumptions about organizers based on other fields. In such a case, when receiving an event, clients MUST NOT infer organizers from other event data.</p>"},{"location":"fep/8a8e/#fallback-to-attributedto","title":"Fallback to <code>attributedTo</code>","text":"<p>If the <code>organizers</code> property is omitted, it MAY be inferred that the actors in the <code>attributedTo</code> property are the event's organizers. For events with multiple organizers, it is RECOMMENDED to provide an <code>organizers</code> collection to avoid and ensure compatibility with applications that do not support arrays in <code>attributedTo</code>.</p>"},{"location":"fep/8a8e/#upcoming-events-collection-for-actors","title":"Upcoming Events Collection for Actors","text":"<p>Every <code>actor</code> that has published <code>Event</code>s SHOULD have a <code>https://w3id.org/fep/8a8e/upcomingEvents</code> collection to list their future or ongoing events. This collection MUST follow these rules:</p> <ul> <li> <p>Collection Type: The collection MUST be an <code>OrderedCollection</code> or one of its subtypes.</p> </li> <li> <p>Ordering: Events MUST be ordered in ascending order by their <code>startTime</code>, with the earliest events appearing first.</p> </li> <li> <p>Items: Each item in the collection MUST meet the following criteria: </p> </li> <li>Type: The item MUST be of type <code>Event</code> or one of its subtypes. </li> <li>Time: The <code>Event</code>'s <code>endTime</code> MUST NOT be earlier than the current time, i.e., the event is ongoing or scheduled in the future. </li> <li>Actor Involvement: At least one of the following conditions MUST apply: <ul> <li>The <code>Actor</code> is included in the <code>Event</code>'s <code>organizers</code> collection.</li> <li>The <code>Actor</code> owns the <code>Event</code>, i.e., is part of the <code>Event</code>'s <code>attributedTo</code>.</li> <li>The <code>Event</code> is in the actor's <code>shares</code> collection, and the <code>Event</code> shares the same host with the <code>actor</code>. This use case is primarily intended to cover relay and group <code>Actor</code>s.</li> </ul> </li> </ul>"},{"location":"fep/8a8e/#terms","title":"Terms","text":"<p>Note that the terms <code>joinMode</code> and <code>externalParticipationUrl</code> are compatible with and also leaned on the extensions defined by Mobilizon.</p> displayEndTime URI <code>https://w3id.org/fep/8a8e/displayEndTime</code> Label Whether to display the endTime Comment A property that defines whether the end time of an event should be displayed. Domain displayEndTime Range Boolean Is defined by FEP-8a8e <pre>\n <code>\n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"displayEndTime\": false\n }\n </code>\n </pre> upcomingEvents URI <code>https://w3id.org/fep/8a8e/upcomingEvents</code> Label Ordered collection of upcoming Events Comment An ActivityStreams OrderedCollection of Event objects that have a startTime property in the future, sorted by startTime with the earliest first. Range An OrderedCollection (@id) Is defined by FEP-8a8e <pre>\n <code>\n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Organization\",\n \"upcomingEvents\": {\n \"type\": \"OrderedCollection\",\n \"items\": [\n { \"type\": \"Event\", \"startTime\": \"2025-06-01T12:00:00Z\" },\n { \"type\": \"Event\", \"startTime\": \"2025-07-15T15:30:00Z\" }\n ]\n }\n }\n </code>\n </pre> OrganizersCollection URI <code>https://w3id.org/fep/8a8e/OrganizersCollection</code> Label An Event's Organizer Collection Comment Inherits all properties from <code>https://www.w3.org/ns/activitystreams#Collection</code> with the addition that the <code>items</code> may also include <code>https://schema.org/Person</code> or <code>https://schema.org/Organization</code>. Subclass of Object See also organizers Is defined by FEP-8a8e <pre>\n <code>\n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org/\"\n }\n ],\n \"type\": \"OrganizersCollection\",\n \"items\": [\n { \"type\": \"Group\", \"name\": \"ActivityPub Group Actor\", \"id\": \"https://example.org/actors/group1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"Organization\", \"name\": \"Event Co.\" }\n ]\n }\n </code>\n </pre> organizers URI <code>https://w3id.org/fep/8a8e/organizers</code> Label Organizers Collection Comment An ActivityStreams Collection collection that lists the entities that are disclosed to as organizers of an event. If set to <code>null</code> this explicitly indicates a deliberate choice to withhold organizer information. Range OrganizersCollection (SubType of of the Collection or OrderedCollection ActivityStreams Type) or <code>null</code> Required Yes Functional No Is defined by FEP-8a8e <pre>\n <code>\n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org/\"\n }\n ],\n \"type\": \"Event\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"totalItems\": 4,\n \"items\": [\n { \"type\": \"Person\", \"name\": \"ActivityPub Person Actor\", \"id\": \"https://example.org/actors/1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" }\n ]\n }\n }\n </code>\n </pre> AttendeesCollection URI <code>https://w3id.org/fep/8a8e/AttendeesCollection</code> Label A Collection of the Event's attendees Comment Inherits all properties from <code>https://www.w3.org/ns/activitystreams#Collection</code> with the addition that the <code>items</code> may also include <code>https://schema.org/Person</code> or <code>https://schema.org/Organization</code>. The items of the <code>AttendeesCollection</code> are entities that are confirmed attendees by an Events organizer(s). It contains all ActivityPub actors that meet one or more of the following conditions, added as a side effect. <ul> <li>The actor has sent a Join activity with this object as the object property that has been answered with an Accept.</li> <li>The actor has responded to an Invite activity from the Event's owner (where the Event is specified as the object property) with an Accept activity.</li> </ul> The items MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given. This collection *SHOULD* have the totalItems set in any case. Subclass of Object See also attendees Is defined by FEP-8a8e <pre>\n <code>\n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"https://schema.org/\"\n }\n ],\n \"type\": \"AttendeesCollection\",\n \"totalItems\": 2,\n \"id\": \"https://example.org/events/alice-birthday-party\"\n \"items\": [\n { \"type\": \"Person\", \"name\": \"Bob\", \"id\": \"https://example.org/actors/bob\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\", \"email\": \"alice@example.org\"}\n ]\n }\n </code>\n </pre> attendees URI <code>https://w3id.org/fep/8a8e/attendees</code> Label Attendees of an Event Comment Is defined by FEP-8a8e Range Collection (ActivityStreams Type) Required No Functional No <pre>\n <code>\n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"https://schema.org#\"\n }\n ],\n \"type\": \"Event\",\n \"attendees\": {\n \"type\": \"OrganizersCollection\",\n \"totalItems\": 4,\n \"items\": [\n { \"type\": \"Person\", \"name\": \"ActivityPub Person\", \"id\": \"https://example.org/actors/1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" }\n ]\n }\n }\n </code>\n </pre> timezone URI <code>https://w3id.org/fep/8a8e/timezone</code> Label The timezone of an Event Comment Indicates the timezone for which the time(s) indicated in the event are given. The value provided should be among those listed in the IANA Time Zone Database. Is defined by FEP-8a8e Range IANA Time Zone identifier Required No Functional No <pre>\n <code>\n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/events/new-years-party\",\n \"name\": \"New years party\",\n \"startTime\": \"2014-12-31T23:00:00Z\",\n \"endTime\": \"2015-01-01T06:00:00Z\",\n \"timezone\": \"Europe/Vienna\",\n \"organizers\": null\n }\n </code>\n </pre> joinMode URI <code>https://w3id.org/fep/8a8e/joinMode</code> rdfs:label Join mode rdfs:comment Indicator of how new members may be able to join an event. Accepted values: <code>free</code>, <code>restricted</code>, <code>external</code>, <code>none</code>, <code>invite</code>. If <code>external</code>, you must also set <code>externalParticipationUrl</code>. rdfs:domain as:Event rdfs:range A string rdfs:isDefinedBy FEP\u20118a8e <pre>\n<code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"restricted\"\n}\n</code>\n </pre> <pre>\n<code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.escample.org/events/1234/participate\"\n}\n</code>\n </pre> requiredJoinVisibility Label Required Join Visibility Comment Specifies the minimum audience that must be addressed in a valid <code>Join</code> activity related to the event. This can include individual actors, groups, the <code>as:Public</code> collection, or any other URI. The <code>Join</code> activity must be addressed accordingly (e.g., using <code>to</code>, <code>cc</code>, <code>bto</code>, or <code>audience</code>). Domain as:Event Range A list of any addressed targets, i.e. URIs (@list) Is defined by FEP-8a8e <pre>\n<code>{\n \"@context\": [\n \"https://schema.org\",\n \"https://https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/yoga-workshop\",\n \"name\": \"Yoga Workshop with Alice and Bob\",\n \"startTime\": \"2014-12-12T18:00:00-08:00\",\n \"endTime\": \"2014-12-12T19:30:00-08:00\",\n \"attributedTo\": \"https://example.org/groups/fediyoga\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"id\": \"https://example.org/yoga-workshop/organizers\",\n \"totalItems\": 3,\n \"first\": {\n \"type\": \"CollectionPage\",\n \"partOf\": \"https://example.org/yoga-workshop/organizers\",\n \"items\": [\n \"https://example.org/users/bob\",\n \"https://example.org/users/alice\",\n \"https://example.org/groups/fediyoga\"\n ]\n },\n \"joinMode\": \"restricted\",\n \"requiredJoinVisibility\": [\n \"https://example.org/yoga-workshop/organizers\"\n ]\n}</code>\n </pre> externalParticipationUrl URI <code>https://w3id.org/fep/8a8e/externalParticipationUrl</code> rdfs:label External participation URL rdfs:comment A URL that points to an external platform where people can join the event or where they can buy tickets for the event. Required if <code>joinMode</code> is set to <code>external</code>. rdfs:domain as:Event rdfs:range xsd:anyURI rdfs:isDefinedBy FEP\u20118a8e <pre>\n<code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.meetup.com/somegroup/events/00000/\"\n}\n</code>\n </pre> isBannerImage URI <code>https://w3id.org/fep/8a8e/isBannerImage</code> rdfs:label Image is a banner image rdfs:comment Whether an image is an (events) banner image. rdfs:domain as:Image rdfs:range Boolean rdfs:isDefinedBy FEP\u20118a8e <pre>\n<code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"focalPoint\": {\n \"@container\": \"@list\",\n \"@id\": \"toot:focalPoint\"\n }\n }\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"image\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-flyer.png\",\n \"focalPoint\": [\n -0.55,\n 0.43\n ]\n },\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-banner.png\",\n \"width\": 1000,\n \"height\": 500,\n \"isBannerImage\": true\n }\n ]\n}\n</code>\n </pre> eventStatus URI <code>https://w3id.org/fep/8a8e/eventStatus</code> Label The events status is Comment An eventStatus of an event represents its status; particularly useful when an event is cancelled or rescheduled. Domain Event Range EventStatusType (@vocab) Required No Functional Yes See also EventCancelled | EventScheduled | EventTentative | EventMovedOnline | EventPostponed | EventRescheduled Is defined by FEP-8a8e <pre>\n<code>\n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://domain.example/events/0\",\n \"type\": \"Event\",\n \"eventStatus\": \"EventScheduled\"\n}\n</code>\n</pre> EventStatusType URI <code>https://w3id.org/fep/8a8e/8a8e</code> Label 8a8e Comment EventStatusType is an enumeration type whose instances represent several states that an Event may be in. Subclass of https://schema.org/EventStatusType See also eventStatus Is defined by FEP-8a8e EventScheduled URI <code>https://w3id.org/fep/8a8e/EventScheduled</code> Label The event is scheduled. Comment The event is normally scheduled and planned. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI <code>https://w3id.org/fep/8a8e/EventPostponed</code> Label The event has been postponed. Comment The event has been postponed and no new date has been set. The event's previousStartTime should be set. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventRescheduled URI <code>https://w3id.org/fep/8a8e/EventRescheduled</code> Label The event has been rescheduled. Comment The event's previousStartTime should be set to the old date and the startTime should be set to the event's new start date. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI EventPostponed | EventRescheduled <code>https://w3id.org/fep/8a8e/EventCancelled</code> Label The event has been cancelled. Comment The event has been cancelled. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI <code>https://w3id.org/fep/8a8e/EventCancelled</code> Label The event has been cancelled. Comment The event has been cancelled. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventTentative URI <code>https://w3id.org/fep/8a8e/EventTentative</code> Label The event is tentative Comment The event is currently being planned but not confirmed. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventMovedOnline URI <code>https://w3id.org/fep/8a8e/EventMovedOnline</code> Label The event moved online. Comment Indicates that the event was changed to allow online participation. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e previousStartTime URI <code>https://w3id.org/fep/8a8e/previousStartTime</code> Label The event previous start time. Comment The events previous start time is the old start time before an event got postponed or rescheduled. Domain Event Range xsd:dateTime (@vocab) See also EventPostponed | EventRescheduled Is defined by FEP-8a8e category URI <code>https://w3id.org/fep/8a8e/category</code> Label The category of an Event Comment Recommended event categories include: <code>ARTS</code>, <code>AUTO_BOAT_AIR</code>, <code>BOOK_CLUBS</code>, <code>BUSINESS</code>, <code>CAUSES</code>, <code>CLIMATE_ENVIRONMENT</code>, <code>COMMUNITY</code>, <code>COMEDY</code>, <code>CRAFTS</code>, <code>CREATIVE_JAM</code>, <code>DIY_MAKER_SPACES</code>, <code>FAMILY_EDUCATION</code>, <code>FASHION_BEAUTY</code>, <code>FESTIVALS</code>, <code>FILM_MEDIA</code>, <code>FOOD_DRINK</code>, <code>GAMES</code>, <code>INCLUSIVE_SPACES</code>, <code>LANGUAGE_CULTURE</code>, <code>LEARNING</code>, <code>LGBTQ</code>, <code>MEETING</code>, <code>MEDITATION_WELLBEING</code>, <code>MOVEMENTS_POLITICS</code>, <code>MUSIC</code>, <code>NETWORKING</code>, <code>OUTDOORS_ADVENTURE</code>, <code>PARTY</code>, <code>PERFORMING_VISUAL_ARTS</code>, <code>PETS</code>, <code>PHOTOGRAPHY</code>, <code>SCIENCE_TECH</code>, <code>SPIRITUALITY_RELIGION_BELIEFS</code>, <code>SPORTS</code>, <code>THEATRE</code>, <code>WORKSHOPS_SKILL_SHARING</code> Domain Event Range xsd:string (@list) Is defined by FEP-8a8e"},{"location":"fep/8a8e/#credits","title":"Credits","text":"<p>This work would not have been possible without the support of NLnet foundation and NGI Zero.</p> <p>Thanks are also expressed to the people at OFFDEM who laid out the beginnings of this document.</p> <p>Special thanks go to Laurin Weger, who was available for many time-consuming discussions. Further thanks go to the Cloudfest Hackathon 2025 for providing space and a platform for further discussion of this FEP and especially to Andreas Heigl for his rich feedback and ideas.</p>"},{"location":"fep/8a8e/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/8a8e/AttendeesCollection/","title":"AttendeesCollection","text":"<p>URI : <code>https://w3id.org/fep/8a8e/AttendeesCollection</code></p> <p>Label : A Collection of the Event's attendees</p> <p>Comment : Inherits all properties from <code>https://www.w3.org/ns/activitystreams#Collection</code> with the addition that the <code>items</code> may also include <code>https://schema.org/Person</code> or <code>https://schema.org/Organization</code>. The items of the <code>AttendeesCollection</code> are entities that are confirmed attendees by an Events organizer(s). It contains all ActivityPub actors that meet one or more of the following conditions, added as a side effect.</p> <ul> <li>The actor has sent a Join activity with this object as the object property that has been answered with an Accept.</li> <li>The actor has responded to an Invite activity from the Event's owner (where the Event is specified as the object property) with an Accept activity.</li> </ul> <p>The items MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given. This collection *SHOULD* have the totalItems set in any case.</p> <p>Subclass of : Object</p> <p>See also : attendees</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/AttendeesCollection/#examples","title":"Examples","text":"<p>Example of an AttendeesCollection with different items</p> <p><code>json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"https://schema.org/\" } ], \"type\": \"AttendeesCollection\", \"totalItems\": 2, \"id\": \"https://example.org/events/alice-birthday-party\" \"items\": [ { \"type\": \"Person\", \"name\": \"Bob\", \"id\": \"https://example.org/actors/bob\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\", \"email\": \"alice@example.org\"} ] }</code></p>"},{"location":"fep/8a8e/EventCancelled/","title":"EventCancelled","text":"<p>URI : <code>https://w3id.org/fep/8a8e/EventCancelled</code></p> <p>Label : The event has been cancelled.</p> <p>Comment : The event has been cancelled.</p> <p>Subclass of : EventStatusType</p> <p>See also : eventStatus</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/EventMovedOnline/","title":"EventMovedOnline","text":"<p>URI : <code>https://w3id.org/fep/8a8e/EventMovedOnline</code></p> <p>Label : The event moved online.</p> <p>Comment : Indicates that the event was changed to allow online participation.</p> <p>Subclass of : EventStatusType</p> <p>See also : eventStatus</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/EventPostponed/","title":"EventPostponed","text":"<p>URI : <code>https://w3id.org/fep/8a8e/EventPostponed</code></p> <p>Label : The event has been postponed.</p> <p>Comment : The event has been postponed and no new date has been set. The event's previousStartTime should be set.</p> <p>Subclass of : EventStatusType</p> <p>See also : eventStatus</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/EventRescheduled/","title":"EventRescheduled","text":"<p>URI : <code>https://w3id.org/fep/8a8e/EventRescheduled</code></p> <p>Label : The event has been rescheduled.</p> <p>Comment : The event's previousStartTime should be set to the old date and the startTime should be set to the event's new start date.</p> <p>Subclass of : EventStatusType</p> <p>See also : eventStatus</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/EventScheduled/","title":"EventScheduled","text":"<p>URI : <code>https://w3id.org/fep/8a8e/EventScheduled</code></p> <p>Label : The event is scheduled.</p> <p>Comment : The event is normally scheduled and planned.</p> <p>Subclass of : EventStatusType</p> <p>See also : eventStatus</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/EventStatusType/","title":"EventStatusType","text":"<p>URI : <code>https://w3id.org/fep/8a8e/8a8e</code></p> <p>Label : 8a8e</p> <p>Comment : EventStatusType is an enumeration type whose instances represent several states that an Event may be in.</p> <p>Subclass of : https://schema.org/EventStatusType</p> <p>See also : eventStatus</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/EventTentative/","title":"EventTentative","text":"<p>URI : <code>https://w3id.org/fep/8a8e/EventTentative</code></p> <p>Label : The event is tentative</p> <p>Comment : The event is currently being planned but not confirmed.</p> <p>Subclass of : EventStatusType</p> <p>See also : eventStatus</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/OrganizersCollection/","title":"OrganizersCollection","text":"<p>URI : <code>https://w3id.org/fep/8a8e/OrganizersCollection</code></p> <p>Label : An Event's Organizer Collection</p> <p>Comment : Inherits all properties from <code>https://www.w3.org/ns/activitystreams#Collection</code> with the addition that the <code>items</code> may also include <code>https://schema.org/Person</code> or <code>https://schema.org/Organization</code>.</p> <p>Subclass of : Object</p> <p>See also : organizers</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/OrganizersCollection/#examples","title":"Examples","text":"<p>Example of an OrganizersCollection with different items</p> <p><code>json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"http://schema.org/\" } ], \"type\": \"OrganizersCollection\", \"items\": [ { \"type\": \"Group\", \"name\": \"ActivityPub Group Actor\", \"id\": \"https://example.org/actors/group1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"Organization\", \"name\": \"Event Co.\" } ] }</code></p>"},{"location":"fep/8a8e/attendees/","title":"attendees","text":"<p>URI : <code>https://w3id.org/fep/8a8e/attendees</code></p> <p>Label : Attendees of an Event</p> <p>Comment : </p> <p>Is defined by : FEP-8a8e</p> <p>Range : Collection (ActivityStreams Type)</p> <p>Required : No</p> <p>Functional : No</p>"},{"location":"fep/8a8e/attendees/#examples","title":"Examples","text":"<p>Example usage of attendees</p> <p><code>json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"https://schema.org#\" } ], \"type\": \"Event\", \"attendees\": { \"type\": \"OrganizersCollection\", \"totalItems\": 4, \"items\": [ { \"type\": \"Person\", \"name\": \"ActivityPub Person\", \"id\": \"https://example.org/actors/1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" } ] } }</code></p>"},{"location":"fep/8a8e/category/","title":"category","text":"<p>URI : <code>https://w3id.org/fep/8a8e/category</code></p> <p>Label : The category of an Event</p> <p>Comment : Recommended event categories include: <code>ARTS</code>, <code>AUTO_BOAT_AIR</code>, <code>BOOK_CLUBS</code>, <code>BUSINESS</code>, <code>CAUSES</code>, <code>CLIMATE_ENVIRONMENT</code>, <code>COMMUNITY</code>, <code>COMEDY</code>, <code>CRAFTS</code>, <code>CREATIVE_JAM</code>, <code>DIY_MAKER_SPACES</code>, <code>FAMILY_EDUCATION</code>, <code>FASHION_BEAUTY</code>, <code>FESTIVALS</code>, <code>FILM_MEDIA</code>, <code>FOOD_DRINK</code>, <code>GAMES</code>, <code>INCLUSIVE_SPACES</code>, <code>LANGUAGE_CULTURE</code>, <code>LEARNING</code>, <code>LGBTQ</code>, <code>MEETING</code>, <code>MEDITATION_WELLBEING</code>, <code>MOVEMENTS_POLITICS</code>, <code>MUSIC</code>, <code>NETWORKING</code>, <code>OUTDOORS_ADVENTURE</code>, <code>PARTY</code>, <code>PERFORMING_VISUAL_ARTS</code>, <code>PETS</code>, <code>PHOTOGRAPHY</code>, <code>SCIENCE_TECH</code>, <code>SPIRITUALITY_RELIGION_BELIEFS</code>, <code>SPORTS</code>, <code>THEATRE</code>, <code>WORKSHOPS_SKILL_SHARING</code></p> <p>Domain : Event</p> <p>Range : xsd:string (@list)</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/displayEndTime/","title":"displayEndTime","text":"<p>URI : <code>https://w3id.org/fep/8a8e/displayEndTime</code></p> <p>Label : Whether to display the endTime</p> <p>Comment : A property that defines whether the end time of an event should be displayed.</p> <p>Domain : displayEndTime</p> <p>Range : Boolean</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/displayEndTime/#examples","title":"Examples","text":"<p>Example usage of displayEndTime</p> <p><code>json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"displayEndTime\": false }</code></p>"},{"location":"fep/8a8e/eventStatus/","title":"eventStatus","text":"<p>URI : <code>https://w3id.org/fep/8a8e/eventStatus</code></p> <p>Label : The events status is</p> <p>Comment : An eventStatus of an event represents its status; particularly useful when an event is cancelled or rescheduled.</p> <p>Domain : Event</p> <p>Range : EventStatusType (@vocab)</p> <p>Required : No</p> <p>Functional : Yes</p> <p>See also : EventCancelled | EventScheduled | EventTentative | EventMovedOnline | EventPostponed | EventRescheduled</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/eventStatus/#examples","title":"Examples","text":"<p>Example of a forward chronological OrderedCollection with additional context</p> <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://domain.example/events/0\",\n \"type\": \"Event\",\n \"eventStatus\": \"EventScheduled\"\n}\n</code></pre>"},{"location":"fep/8a8e/externalParticipationUrl/","title":"externalParticipationUrl","text":"<p>URI : <code>https://w3id.org/fep/8a8e/externalParticipationUrl</code></p> <p>rdfs:label : External participation URL</p> <p>rdfs:comment : A URL that points to an external platform where people can join the event or where they can buy tickets for the event. Required if <code>joinMode</code> is set to <code>external</code>.</p> <p>rdfs:domain : as:Event</p> <p>rdfs:range : xsd:anyURI</p> <p>rdfs:isDefinedBy : FEP\u20118a8e</p>"},{"location":"fep/8a8e/externalParticipationUrl/#examples","title":"Examples","text":"<p>Example usage of externalParticipationUrl</p> <p><code>json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"url\": \"http://example.org/events/1234\", \"joinMode\": \"external\", \"externalParticipationUrl\": \"https://www.meetup.com/somegroup/events/00000/\" }</code></p>"},{"location":"fep/8a8e/isBannerImage/","title":"isBannerImage","text":"<p>URI : <code>https://w3id.org/fep/8a8e/isBannerImage</code></p> <p>rdfs:label : Image is a banner image</p> <p>rdfs:comment : Whether an image is an (events) banner image.</p> <p>rdfs:domain : as:Image</p> <p>rdfs:range : Boolean</p> <p>rdfs:isDefinedBy : FEP\u20118a8e</p>"},{"location":"fep/8a8e/isBannerImage/#examples","title":"Examples","text":"<p>Example usage of isBannerImage</p> <p><code>json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"toot\": \"http://joinmastodon.org/ns#\", \"focalPoint\": { \"@container\": \"@list\", \"@id\": \"toot:focalPoint\" } } ], \"type\": \"Event\", \"id\": \"https://example.org/new-year-party\", \"name\": \"New years party\", \"organizers\": null, \"startTime\": \"2014-12-31T23:00:00-08:00\", \"endTime\": \"2015-01-01T04:00:00-08:00\", \"image\": { \"type\": \"Image\", \"mediaType\": \"image/jpeg\", \"url\": \"https://example.com/images/new-year-party-flyer.png\", \"focalPoint\": [ -0.55, 0.43 ] }, \"attachment\": [ { \"type\": \"Image\", \"mediaType\": \"image/jpeg\", \"url\": \"https://example.com/images/new-year-party-banner.png\", \"width\": 1000, \"height\": 500, \"isBannerImage\": true } ] }</code></p>"},{"location":"fep/8a8e/joinMode/","title":"joinMode","text":"<p>URI : <code>https://w3id.org/fep/8a8e/joinMode</code></p> <p>rdfs:label : Join mode</p> <p>rdfs:comment : Indicator of how new members may be able to join an event. Accepted values: <code>free</code>, <code>restricted</code>, <code>external</code>, <code>none</code>, <code>invite</code>. If <code>external</code>, you must also set <code>externalParticipationUrl</code>.</p> <p>rdfs:domain : as:Event</p> <p>rdfs:range : A string</p> <p>rdfs:isDefinedBy : FEP\u20118a8e</p>"},{"location":"fep/8a8e/joinMode/#examples","title":"Examples","text":"<p>Example: restricted</p> <p>```json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"url\": \"http://example.org/events/1234\", \"joinMode\": \"restricted\" } <pre><code>Example: external\n\n```json{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.escample.org/events/1234/participate\"\n}\n</code></pre></p>"},{"location":"fep/8a8e/organizers/","title":"organizers","text":"<p>URI : <code>https://w3id.org/fep/8a8e/organizers</code></p> <p>Label : Organizers Collection</p> <p>Comment : An ActivityStreams Collection collection that lists the entities that are disclosed to as organizers of an event. If set to <code>null</code> this explicitly indicates a deliberate choice to withhold organizer information.</p> <p>Range : OrganizersCollection (SubType of of the Collection or OrderedCollection ActivityStreams Type) or <code>null</code></p> <p>Required : Yes</p> <p>Functional : No</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/organizers/#examples","title":"Examples","text":"<p>Example usage of organizers</p> <p><code>json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"http://schema.org/\" } ], \"type\": \"Event\", \"organizers\": { \"type\": \"OrganizersCollection\", \"totalItems\": 4, \"items\": [ { \"type\": \"Person\", \"name\": \"ActivityPub Person Actor\", \"id\": \"https://example.org/actors/1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" } ] } }</code></p>"},{"location":"fep/8a8e/previousStartTime/","title":"previousStartTime","text":"<p>URI : <code>https://w3id.org/fep/8a8e/previousStartTime</code></p> <p>Label : The event previous start time.</p> <p>Comment : The events previous start time is the old start time before an event got postponed or rescheduled.</p> <p>Domain : Event</p> <p>Range : xsd:dateTime (@vocab)</p> <p>See also : EventPostponed | EventRescheduled</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/requiredJoinVisibility/","title":"requiredJoinVisibility","text":"<p>Label : Required Join Visibility</p> <p>Comment : Specifies the minimum audience that must be addressed in a valid <code>Join</code> activity related to the event. This can include individual actors, groups, the <code>as:Public</code> collection, or any other URI. The <code>Join</code> activity must be addressed accordingly (e.g., using <code>to</code>, <code>cc</code>, <code>bto</code>, or <code>audience</code>).</p> <p>Domain : as:Event</p> <p>Range : A list of any addressed targets, i.e. URIs (@list)</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/requiredJoinVisibility/#examples","title":"Examples","text":"<p>Example usage of requiredJoinVisibility</p> <p><code>json{ \"@context\": [ \"https://schema.org\", \"https://https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"id\": \"https://example.org/yoga-workshop\", \"name\": \"Yoga Workshop with Alice and Bob\", \"startTime\": \"2014-12-12T18:00:00-08:00\", \"endTime\": \"2014-12-12T19:30:00-08:00\", \"attributedTo\": \"https://example.org/groups/fediyoga\", \"organizers\": { \"type\": \"OrganizersCollection\", \"id\": \"https://example.org/yoga-workshop/organizers\", \"totalItems\": 3, \"first\": { \"type\": \"CollectionPage\", \"partOf\": \"https://example.org/yoga-workshop/organizers\", \"items\": [ \"https://example.org/users/bob\", \"https://example.org/users/alice\", \"https://example.org/groups/fediyoga\" ] }, \"joinMode\": \"restricted\", \"requiredJoinVisibility\": [ \"https://example.org/yoga-workshop/organizers\" ] }</code></p>"},{"location":"fep/8a8e/timezone/","title":"timezone","text":"<p>URI : <code>https://w3id.org/fep/8a8e/timezone</code></p> <p>Label : The timezone of an Event</p> <p>Comment : Indicates the timezone for which the time(s) indicated in the event are given. The value provided should be among those listed in the IANA Time Zone Database.</p> <p>Is defined by : FEP-8a8e</p> <p>Range : IANA Time Zone identifier</p> <p>Required : No</p> <p>Functional : No</p>"},{"location":"fep/8a8e/timezone/#examples","title":"Examples","text":"<p>Example usage of timezone</p> <p><code>json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"id\": \"https://example.org/events/new-years-party\", \"name\": \"New years party\", \"startTime\": \"2014-12-31T23:00:00Z\", \"endTime\": \"2015-01-01T06:00:00Z\", \"timezone\": \"Europe/Vienna\", \"organizers\": null }</code></p>"},{"location":"fep/8a8e/upcomingEvents/","title":"upcomingEvents","text":"<p>URI : <code>https://w3id.org/fep/8a8e/upcomingEvents</code></p> <p>Label : Ordered collection of upcoming Events</p> <p>Comment : An ActivityStreams OrderedCollection of Event objects that have a startTime property in the future, sorted by startTime with the earliest first.</p> <p>Range : An OrderedCollection (@id)</p> <p>Is defined by : FEP-8a8e</p>"},{"location":"fep/8a8e/upcomingEvents/#examples","title":"Examples","text":"<p>Example usage of upcomingEvents</p> <p><code>json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Organization\", \"upcomingEvents\": { \"type\": \"OrderedCollection\", \"items\": [ { \"type\": \"Event\", \"startTime\": \"2025-06-01T12:00:00Z\" }, { \"type\": \"Event\", \"startTime\": \"2025-07-15T15:30:00Z\" } ] } }</code></p>"},{"location":"fep/8b32/","title":"FEP-8b32: Object Integrity Proofs","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2022-11-12 #29 Discussions codeberg"},{"location":"fep/8b32/#summary","title":"Summary","text":"<p>This proposal describes how ActivityPub servers and clients could create self-authenticating activities and objects.</p> <p>HTTP signatures are often used for authentication during server-to-server interactions. However, this ties authentication to activity delivery, and limits the flexibility of the protocol.</p> <p>Integrity proofs are sets of attributes that represent digital signatures and parameters required to verify them. These proofs can be added to any activity or object, allowing recipients to verify the identity of the actor and integrity of the data. That decouples authentication from the transport, and enables various protocol improvements such as activity relaying, embedded objects and client-side signing.</p>"},{"location":"fep/8b32/#history","title":"History","text":"<p>Mastodon supports Linked Data signatures since 2017, and a number of other platforms added support for them later. These signatures are similar to integrity proofs, but are based on outdated Linked Data Signatures 1.0 specification, which has been superseded by other standards.</p>"},{"location":"fep/8b32/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/8b32/#integrity-proofs","title":"Integrity proofs","text":"<p>The proposed authentication mechanism is based on Data Integrity specification.</p>"},{"location":"fep/8b32/#proof-generation","title":"Proof generation","text":"<p>The proof MUST be created according to the Data Integrity specification, section 4.2 Add Proof.</p> <p>The process of proof generation consists of the following steps:</p> <ul> <li>Canonicalization is a transformation of a JSON object into the form suitable for hashing, according to some deterministic algorithm.</li> <li>Hashing is a process that calculates an identifier for the transformed data using a cryptographic hash function.</li> <li>Signature generation is a process that calculates a value that protects the integrity of the input data from modification.</li> </ul> <p>The resulting proof is added to the original JSON object under the key <code>proof</code>. Objects SHOULD NOT contain more than one integrity proof.</p> <p>The list of attributes used in integrity proof is defined in Data Integrity specification, section 2.1 Proofs. The proof type SHOULD be <code>DataIntegrityProof</code>, as specified in section 3.1 DataIntegrityProof. The value of <code>proofPurpose</code> attribute MUST be <code>assertionMethod</code>.</p> <p>The value of the <code>verificationMethod</code> attribute of the proof can be an HTTP(S) URI or a DID URL.</p> <p>The controlled identifier document where the verification method is expressed MUST be an actor object or a DID document that is provably associated with an ActivityPub actor (e.g. using a mechanism described in FEP-c390 or FEP-ef61). The verification method MUST be associated with the <code>assertionMethod</code> property of the controlled identifier document. If controlled identifier document is an actor object, implementers SHOULD use <code>assertionMethod</code> property as described in FEP-521a.</p>"},{"location":"fep/8b32/#proof-verification","title":"Proof verification","text":"<p>Recipients of an object SHOULD perform proof verification if it contains integrity proofs. Verification process MUST follow the Data Integrity specification, section 4.4 Verify Proof. It starts with the removal of the <code>proof</code> value from the JSON object. Then verification method is retrieved from the controlled identifier document as described in Controlled Identifiers specification, section 3.3 Retrieve Verification Method. Then the object is canonicalized, hashed and signature verification is performed according to the parameters specified in the proof.</p> <p>The subject of the controlled identifier document where the verification method is expressed MUST be the owner of the signed object, or a DID that is provably associated with that actor (e.g. using a mechanism described in FEP-c390 or FEP-ef61).</p> <p>If both HTTP signature and integrity proof are used, the integrity proof MUST be given precedence over HTTP signature. The HTTP signature MAY be dismissed.</p>"},{"location":"fep/8b32/#algorithms","title":"Algorithms","text":"<p>Implementers are expected to pursue broad interoperability when choosing algorithms for integrity proofs.</p> <p>eddsa-jcs-2022 cryptosuite is RECOMMENDED:</p> <ul> <li>Canonicalization: JCS</li> <li>Hashing: SHA-256</li> <li>Signatures: EdDSA</li> </ul>"},{"location":"fep/8b32/#backward-compatibility","title":"Backward compatibility","text":"<p>Integrity proofs and linked data signatures can be used together, as they rely on different properties (<code>proof</code> and <code>signature</code>, respectively).</p> <p>If compatiblity with legacy systems is desired, the integrity proof MUST be created and inserted before the generation of the linked data signature.</p> <p>If both <code>proof</code> and <code>signature</code> are present in a received object, the linked data signature MUST be removed before the verification of the integrity proof.</p>"},{"location":"fep/8b32/#security-considerations","title":"Security considerations","text":"<p>Implementers using integrity proofs as an authentication mechanism are advised to follow the recommendations given in FEP-fe34: Origin-based security model.</p>"},{"location":"fep/8b32/#privacy-considerations","title":"Privacy considerations","text":"<p>If a private object is signed, its authenticity can be proven if it is distributed beyond the intended recipients. This risk can be mitigated by encrypting private content.</p>"},{"location":"fep/8b32/#examples","title":"Examples","text":""},{"location":"fep/8b32/#signed-object","title":"Signed object","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\",\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n</code></pre>"},{"location":"fep/8b32/#signed-activity","title":"Signed activity","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/activities/1\",\n \"type\": \"Create\",\n \"actor\": \"https://server.example/users/alice\",\n \"object\": {\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\"\n },\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n</code></pre>"},{"location":"fep/8b32/#signed-activity-with-embedded-signed-object","title":"Signed activity with embedded signed object","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/activities/1\",\n \"type\": \"Create\",\n \"actor\": \"https://server.example/users/alice\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\",\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n },\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n</code></pre>"},{"location":"fep/8b32/#test-vectors","title":"Test vectors","text":"<ul> <li>fep-8b32.feature</li> <li>eddsa-jcs-2022 test vectors</li> </ul>"},{"location":"fep/8b32/#implementations","title":"Implementations","text":"<ul> <li>Mitra</li> <li>Vervis (generation, verification)</li> <li>Streams</li> <li>Hubzilla</li> <li>Fedify</li> <li>apsig</li> <li>tootik</li> <li>Gush! (commit)</li> <li>squidcity </li> </ul>"},{"location":"fep/8b32/#use-cases","title":"Use cases","text":"<ul> <li>Forwarding from inbox</li> <li>Conversation Containers</li> <li>FEP-ef61: Portable Objects</li> <li>FEP-ae97: Client-side activity signing</li> </ul>"},{"location":"fep/8b32/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Dave Longley, Manu Sporny, Verifiable Credential Data Integrity 1.0, 2024</li> <li>Manu Sporny, Dave Longley, Markus Sabadello, Drummond Reed, Orie Steele, Christopher Allen, Decentralized Identifiers (DIDs) v1.0, 2022</li> <li>Dave Longley, Manu Sporny, Markus Sabadello, Drummond Reed, Orie Steele, Christopher Allen, Controlled Identifiers v1.0, 2025</li> <li>silverpill, FEP-521a: Representing actor's public keys, 2023</li> <li>silverpill, FEP-c390: Identity Proofs, 2022</li> <li>silverpill, FEP-ef61: Portable Objects, 2023</li> <li>Dave Longley, Manu Sporny, Data Integrity EdDSA Cryptosuites v1.0, 2025</li> <li>A. Rundgren, B. Jordan, S. Erdtman, JSON Canonicalization Scheme (JCS), 2020</li> <li>silverpill, FEP-fe34: Origin-based security model, 2024</li> </ul>"},{"location":"fep/8b32/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/8c3f/","title":"FEP-8c3f: Web Monetization","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Diogo Peralta Cordeiro mail@diogo.site, Phablulo Joel phablulo@gmail.com <code>WITHDRAWN</code> 2022-01-18 2023-10-27 #3 - codeberg"},{"location":"fep/8c3f/#summary","title":"Summary","text":"<p>Web Monetization federation via [ActivityPub].</p>"},{"location":"fep/8c3f/#history","title":"History","text":"<p>The ability to transfer money has been a long-standing omission from the web platform. As a result, the web suffers from a flood of advertising and corrupt business models. Web Monetization provides an open, native, efficient, and automatic way to compensate creators, pay for API calls, and support crucial web infrastructure.</p> <p>[Web Monetization] is being proposed as a W3C standard at the Web Platform Incubator Community Group.</p>"},{"location":"fep/8c3f/#requirements","title":"Requirements","text":"<p>In GNU social this is implemented on a plugin using an extra property <code>gs:webmonetizationWallet</code> on the actor object.</p>"},{"location":"fep/8c3f/#example","title":"Example","text":"<pre><code>{\n \"type\": \"Person\",\n \"streams\": [],\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gs\": \"https://www.gnu.org/software/social/ns#\"\n },\n {\n \"webmonetizationWallet\": {\n \"@id\": \"gs:webmonetizationWallet\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"id\": \"https://social.gnusocial.rocks/actor/42\",\n \"inbox\": \"https://social.gnusocial.rocks/actor/42/inbox.json\",\n \"outbox\": \"https://social.gnusocial.rocks/actor/42/outbox.json\",\n \"following\": \"https://instance.gnusocial.test/actor/42/subscriptions\",\n \"followers\": \"https://instance.gnusocial.test/actor/42/subscribers\",\n \"preferredUsername\": \"alice\",\n \"name\": \"Alyssa P.Hacker\",\n \"url\": \"https://social.gnusocial.rocks/@alice\",\n \"webmonetizationWallet\": \"$wallet.example.com/alice\"\n}\n</code></pre>"},{"location":"fep/8c3f/#about-the-value-of-gswebmonetizationwallet","title":"About the value of <code>gs:webmonetizationWallet</code>","text":"<p>That string is the same as the example one in Web Monetization specification and it consists on a payment pointer.</p> <p>Payment Pointers start with a <code>$</code> character to distinguish them from other identifiers and make it obvious that they are related to payments. To convert a Payment Pointer to a URL the $ is replaced with the standard prefix of a secure URL, <code>https://</code>.</p>"},{"location":"fep/8c3f/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[Web Monetization] Adrian Hope-Bailie, Ben Sharafian, Nick Dudfield, Web Monetization, 2021</li> </ul>"},{"location":"fep/8c3f/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/8fcf/","title":"FEP-8fcf: Followers collection synchronization across servers","text":"Authors Status Date received Date final Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com <code>FINAL</code> 2020-10-24 2022-02-07 #6 - codeberg"},{"location":"fep/8fcf/#summary","title":"Summary","text":"<p>In ActivityPub, follow relationships are established, updated and removed by sending activities such as <code>Follow</code>, <code>Accept</code> or <code>Reject</code>, which are assumed to be correctly and promptly processed upon receipt.</p> <p>However, due to incompatible protocol extensions, software bugs, server crashes or database rollbacks, the two ends of a <code>Follow</code> relationship may end up out of sync.</p> <p>This can be especially damaging when a remote instance has outdated information about follow relationships that should have been revoked, as some implementations may deliver activities addressed to the sender's <code>followers</code> collection by using the <code>sharedInbox</code> mechanism and letting the recipient use the sender's <code>followers</code> collection for local delivery and access control.</p> <p>This proposal describes an optional mechanism for detecting discrepancies in following relationships across instances, with minimal overhead and without loss of privacy.</p>"},{"location":"fep/8fcf/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].</p> <p>The proposed protocol for followers collection synchronization makes a number of assumptions that may not be suitable to every implementation or deployment.</p> <p>Implementations and deployments MUST NOT implement the mechanisms described in this proposal unless they match the following requirements: - actors managed by an instance are required to all share the same exact URI scheme and authority for their <code>id</code>, <code>inbox</code> and <code>sharedInbox</code> URIs - such instances are required to manage all actors using the same URI scheme and authority for either their <code>id</code>, <code>inbox</code> or <code>sharedInbox</code> URIs (that is, for instance, two fediverse implementations cannot implement this proposal if they are set up on the same exact same domain name, unless implementing an additional mechanism to share follower information between them, which is out of scope for this proposal).</p> <p>The reason for those requirements is to prevent the partial followers collection described below from missing legitimate followers, which could result in followers being removed for no reason.</p> <p>Failing to implement this proposed synchronization mechanism should not impact compatibility with other implementations, as it is completely optional.</p>"},{"location":"fep/8fcf/#partial-follower-collection","title":"Partial follower collection","text":"<p>For efficiency and privacy purposes, we consider a subset of an actor's followers collection. This subset is the set of an actor's followers whose <code>id</code> shares an instance's specific URI scheme and authority.</p> <p>For instance, if <code>https://example.org/users/1</code> has the following followers: - <code>https://example.org/users/2</code> - <code>https://testing.example.org/users/1</code> - <code>https://next.example.org/users/foo</code> - <code>https://testing.example.org/users/2</code></p> <p>The partial follower collection of <code>https://example.org/users/1</code> for the instance serving <code>https://testing.example.org/users/1</code> is: - <code>https://testing.example.org/users/1</code> - <code>https://testing.example.org/users/2</code></p>"},{"location":"fep/8fcf/#partial-follower-collection-digest","title":"Partial follower collection digest","text":"<p>To enable quick checking of partial followers consistency across instances, a partial follower collection digest is computed.</p> <p>This digest is created by XORing together the individual SHA256 digests of each follower's <code>id</code>.</p> <pre><code>partialCollectionDigest = SHA256(follower1) XOR SHA256(follower2) XOR ... XOR SHA256(followerN)\n</code></pre> <p>For instance, the partial follower collection digest of <code>https://example.org/users/1</code> for the instance serving <code>https://testing.example.org/users/1</code> is: <code>3a06e99569547f444c352ab7f52e4bab207abec5ca6f07b0045cfdc9723f8fa9 XOR f939a1585d4a8f02ee339210dbe7315d7003476663d6095f7d996fc4bc7a49b6 = c33f48cd341ef046a206b8a72ec97af65079f9a3a9b90eef79c5920dce45c61f</code></p>"},{"location":"fep/8fcf/#the-collection-synchronization-http-header","title":"The <code>Collection-Synchronization</code> HTTP Header","text":"<p>The <code>Collection-Synchronization</code> HTTP header provides a mechanism for quickly checking whether the sender's followers collection part that is relevant to the recipient is consistent with the recipient's knowledge.</p> <p>The header field name is <code>Collection-Synchronization</code> and its value is a list of parameters and values, formatted according to the <code>signature</code> syntax defined in [HTTP-Signatures], Section 4.1.</p> <p>Example: <pre><code>Collection-Synchronization: collectionId=\"https://example.org/users/1/followers\", url=\"https://example.org/users/1/followers_synchronization\", digest=\"c33f48cd341ef046a206b8a72ec97af65079f9a3a9b90eef79c5920dce45c61f\"\n</code></pre></p>"},{"location":"fep/8fcf/#collection-synchronization-header-parameters","title":"Collection Synchronization Header Parameters","text":"<p>The <code>Collection-Synchronization</code> header's parameters are defined as follows:</p> <ul> <li><code>collectionId</code>: this is URI of the collection that supports synchronization. It must be the sender's <code>followers</code> collection.</li> <li><code>url</code>: this is the URL of the partial followers collection intended for the receiving instance. Accessing it should require authentication from the receiving instance.</li> <li><code>digest</code>: the partial follower collection digest intended for the receiving instance.</li> </ul>"},{"location":"fep/8fcf/#synchronization-procedure","title":"Synchronization procedure","text":""},{"location":"fep/8fcf/#on-the-sender-end","title":"On the sender end","text":"<p>When delivering an Activity to an <code>inbox</code> (or <code>sharedInbox</code>), an instance MAY set a <code>Collection-Synchronization</code> header intended for the corresponding instance (determined by the <code>inbox</code> URI scheme and authority).</p> <p>When exactly to set this header is up to the sender, but it is recommended to at least send it for any <code>Create</code> activity addressed specifically to the sender's <code>followers</code> collection.</p>"},{"location":"fep/8fcf/#on-the-receiving-end","title":"On the receiving end","text":"<p>On the receiving end, upon receiving an Activity delivery with a signed <code>Collection-Synchronization</code> header, the receiver MUST check that: - the <code>collectionId</code> attribute matches the sender's <code>followers</code> collection <code>id</code> - the <code>url</code> attribute also matches the same authority (so that the instance cannot get tricked into requesting the followers list of a third-party individual)</p> <p>If any of those checks fails, the receiver MUST ignore the <code>Collection-Synchronization</code> header.</p> <p>The receiver SHOULD then compute the partial collection digest for the sender's followers based on its own knowledge. If the digest does not match the <code>digest</code> attribute of the header, it SHOULD then query the <code>url</code>, authenticating itself to the remote server using [HTTP-Signatures] or another method.</p> <p>Having fetched the up-to-date partial followers collection from the autoritative server, the receiving end: - SHOULD remove from its local copy of the followers collection any local actor not listed in the partial followers collection. - MAY consider any pending outgoing follow listed in the partial followers collection as accepted. - SHOULD send an <code>Undo Follow</code> for any other local follower listed in the partial followers collection but not known locally.</p>"},{"location":"fep/8fcf/#implementations","title":"Implementations","text":"<ul> <li>Implemented by Mastodon since v3.3.0</li> <li>Implemented by Fedify since Fedify 0.8.0, made optional in Fedify 1.5.0</li> <li>Implemented by Tootik</li> </ul>"},{"location":"fep/8fcf/#references","title":"References","text":"<ul> <li>[RFC-2119] S. Bradner, [Key words for use in RFCs to Indicate Requirement Levels](https://tools.ietf.org/html/rfc2119.html</li> <li>[HTTP-Signatures] A. Backman, J. Richer, M. Sporny, Signing HTTP Messages</li> </ul>"},{"location":"fep/8fcf/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/9091/","title":"Index","text":"<p>hide: - navigation description: \"This FEP defines an API endpoint used to initiate the \"Export Actor\" operation. The output and semantics of the result of the export operation is out of scope, and left to subsequent FEPs. The endpoint only specifies how to start the operation, and by extension, how to tell if a given Actor's server supports this operation.\"</p>"},{"location":"fep/9091/#fep-9091-export-actor-service-endpoint","title":"FEP-9091: Export Actor Service Endpoint","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop <code>DRAFT</code> 2024-07-08 #358 Discussions codeberg"},{"location":"fep/9091/#summary","title":"Summary","text":"<p>This FEP defines an API endpoint used to initiate the \"Export Actor\" operation. The output and semantics of the result of the export operation is out of scope, and left to subsequent FEPs. The endpoint only specifies how to start the operation, and by extension, how to tell if a given Actor's server supports this operation.</p>"},{"location":"fep/9091/#data-model-export-service-endpoint","title":"Data Model: Export Service Endpoint","text":"<p>Conforming ActivityPub Actor profiles MUST specify their export API endpoints using the <code>service</code> descriptor property (inspired by the DID Core Services section, and used in other FEPs such as FEP-e3e9: Actor Relative URLs):</p> <ul> <li>The expanded <code>type</code> of the service descriptor object MUST be <code>https://w3id.org/fep/9091#Export</code> (see example below).</li> <li>The <code>service</code> property MUST NOT contain more than one node with the <code>#Export</code> type.<ul> <li>If a client encounters multiple service descriptor objects with type ending in <code>#Export</code>, the client SHOULD just use the first entry.</li> </ul> </li> </ul>"},{"location":"fep/9091/#example-actor-profile-with-export-service-endpoint","title":"Example Actor Profile with Export Service Endpoint","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"name\": \"Alice\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#export\",\n \"type\": \"https://w3id.org/fep/9091#Export\",\n \"serviceEndpoint\": \"https://alice-personal-site.example/actor/accountExport\"\n }]\n}\n</code></pre>"},{"location":"fep/9091/#protocol-initiating-the-export-actor-operation","title":"Protocol: Initiating the Export Actor Operation","text":"<p>If a client conforming to this FEP wishes to start the 'Export Actor' process, such as to create a backup of the Actor profile, its private key material, and all content associated with this actor, the client MUST perform the following steps:</p> <ol> <li>Fetch the Actor profile object.</li> <li>Locate the <code>service</code> description object with the expanded <code>type</code> equal to <code>https://w3id.org/fep/9091#Export</code>.</li> <li> <p>Locate the corresponding <code>serviceEndpoint</code> URL, in that description object.</p> <ul> <li>If no such service description item exists, this means that the server hosting the Actor's profile does not support this operation.</li> </ul> </li> <li> <p>The client makes an HTTP <code>POST</code> to the URL in the <code>serviceEndpoint</code> to initiate the export operation.</p> <ul> <li>The client MUST include appropriate authorization credentials that indicate that it is authorized to perform this operation (that it's authorized to receive the highly private result of account export). The exact format of these authorization credentials is out of scope of this FEP.</li> </ul> </li> </ol>"},{"location":"fep/9091/#example-request-and-response","title":"Example Request and Response","text":"<p>Example synchronous request (with an empty body) and response:</p> <pre><code>POST /actor/accountExport HTTP/1.1\nHost: alice-personal-site.example\nAuthorization: Bearer ....\nAccept: application/x-tar\nContent-Length: 0\n</code></pre> <pre><code>HTTP/1.1 200 OK\nContent-type: application/x-tar\nTransfer-Encoding: chunked\n\n&lt;binary data of the resulting .tar file&gt;\n</code></pre>"},{"location":"fep/9091/#references","title":"References","text":"<ul> <li> <p>FEP-e3e9: Actor Relative URLs</p> </li> <li> <p>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</p> </li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Manu Sporny, Dave Longley, Markus Sabadell, Drummond Reed, Orie Steele, Christopher Allen, Decentralized Identifiers (DIDs) v1.0, 2022</li> </ul>"},{"location":"fep/9091/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/9098/","title":"FEP-9098: Custom emojis","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2025-07-06 #648 Discussions codeberg"},{"location":"fep/9098/#summary","title":"Summary","text":"<p>A custom emoji is a small image used to express an idea or emotion. Custom emojis are different from Unicode emojis, which are sequences of characters.</p> <p>This document describes how custom emojis are implemented in the ActivityPub network.</p>"},{"location":"fep/9098/#history","title":"History","text":"<p>Custom emojis were introduced by Pleroma (commits) and Mastodon (PR) in 2017.</p>"},{"location":"fep/9098/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/9098/#emoji-object","title":"Emoji object","text":"<p>Custom emojis are represented as <code>Emoji</code> objects (the full type IRI is <code>http://joinmastodon.org/ns#Emoji</code>). The <code>Emoji</code> object has the following properties:</p> <ul> <li><code>id</code> (RECOMMENDED): the unique identifier of the custom emoji. Some implementations omit this property (i.e. the object is anonymous).</li> <li><code>type</code> (REQUIRED): the literal string <code>Emoji</code>.</li> <li><code>name</code> (REQUIRED): the shortcode of the custom emoji (the name of the emoji, enclosed in colons; see Microsyntax section).</li> <li><code>updated</code> (OPTIONAL): the date when custom emoji was updated, formatted as RFC-3339 date and time string.</li> <li><code>icon</code> (REQUIRED): the <code>Image</code> object describing the emoji image.</li> <li><code>type</code> (REQUIRED): the literal string <code>Image</code>.</li> <li><code>url</code> (REQUIRED): the URI of the image to use as the emoji.</li> </ul> <p>Note</p> <p>The definition of the <code>icon</code> property in ActivityStreams Vocabulary recommends an aspect ratio of one to one, but in practice custom emojis have varied aspect ratios.</p> <p>Example:</p> <pre><code>{\n \"id\": \"https://social.example/emoji/blobcat\",\n \"type\": \"Emoji\",\n \"name\": \":blobcat:\",\n \"updated\": \"1970-01-01T00:00:00Z\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://social.example/media/blobcat.png\"\n }\n}\n</code></pre>"},{"location":"fep/9098/#uniqueness","title":"Uniqueness","text":"<p>The primary unique identifier of a custom emoji is a combination of its name and the domain name. The domain name can be extracted from the <code>id</code> of <code>Emoji</code> object or from the <code>id</code> of the object within which it is embedded.</p> <p>If a publisher doesn't guarantee that emoji <code>id</code> is globally unique, it MUST NOT add this property.</p>"},{"location":"fep/9098/#accessibility","title":"Accessibility","text":"<p>The short description of a custom emoji can be specified with the <code>alternateName</code> property (the full IRI is <code>http://schema.org/alternateName</code>).</p>"},{"location":"fep/9098/#using-custom-emojis","title":"Using custom emojis","text":""},{"location":"fep/9098/#microsyntax","title":"Microsyntax","text":"<p>In text a custom emoji is represented by its shortcode, which is a name of an emoji enclosed in colons (e.g. <code>:blobcat:</code>). This is also the value of the <code>Emoji</code> object's <code>name</code> property.</p> <p>These textual representations are often inserted into values of <code>name</code>, <code>summary</code> and <code>content</code> properties of the object that embeds the custom emoji. E.g., the <code>content</code> of a <code>Note</code>, or the <code>name</code> of an <code>Actor</code>.</p> <p>The corresponding <code>Emoji</code> objects are added to the <code>tag</code> array of the object. Within that array custom emojis can appear in any order.</p> <p>Example:</p> <pre><code>{\n \"type\": \"Note\",\n \"id\": \"https://social.example/notes/1234\",\n \"content\": \"&lt;p&gt;:blobcat:&lt;/p&gt;\",\n \"tag\": [\n {\n \"id\": \"https://social.example/emoji/blobcat\",\n \"type\": \"Emoji\",\n \"name\": \":blobcat:\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://social.example/media/blobcat.png\"\n }\n }\n ]\n}\n</code></pre>"},{"location":"fep/9098/#right-to-left-text","title":"Right-to-left text","text":"<p>Custom emoji shortcodes are always written left-to-right, even in right-to-left text.</p>"},{"location":"fep/9098/#rendering","title":"Rendering","text":""},{"location":"fep/9098/#general-client-considerations","title":"General client considerations","text":"<p>The aspect ratio of an image SHOULD be preserved. The maximum width of an image MAY be limited.</p> <p>Shortcodes inside <code>&lt;code&gt;</code> and <code>&lt;pre&gt;</code> HTML elements MUST NOT be replaced.</p>"},{"location":"fep/9098/#web-client-considerations","title":"Web client considerations","text":"<p>Web applications typically replace custom emoji shortcodes with <code>&lt;img&gt;</code> HTML tags and render the output as HTML.</p> <p>To prevent Cross-Site Scripting (XSS) attacks, implementers MUST ensure that:</p> <ul> <li>Emoji names, descriptions, URLs and other strings that are used in replacements do not contain reserved HTML characters (<code>&amp;&lt;&gt;\"'</code>).</li> <li>When HTML content (e.g. <code>summary</code>, <code>content</code>) is processed, shortcodes are replaced only inside Text nodes.</li> <li>Reserved HTML characters in text are escaped before replacing shortcodes.</li> </ul>"},{"location":"fep/9098/#compatibility","title":"Compatibility","text":"<p>In order to be compatible with as many servers as possible, implementations SHOULD comply with the following additional requirements:</p> <ul> <li>Name contains at least two characters (Mastodon).</li> <li>Name contains only characters from the <code>[a-zA-Z0-9_]</code> set (Mastodon).</li> <li>Image media type is <code>image/png</code>, <code>image/gif</code> or <code>image/webp</code> (Mastodon).</li> <li>Image size is not greater than 256 KB (Mastodon).</li> <li>Image is a square (some clients may display non-square emojis with a wrong aspect ratio).</li> <li>Shortcode is placed between two characters that are not unicode alphanumerics, colons or line endings (Mastodon, see also issue #7364).</li> </ul>"},{"location":"fep/9098/#implementations","title":"Implementations","text":"<p>This document is based on implementations of custom emojis in Pleroma, Mastodon, Misskey and Fedibird.</p>"},{"location":"fep/9098/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>G. Klyne, C. Newman, Date and Time on the Internet: Timestamps, 2002</li> </ul>"},{"location":"fep/9098/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/96ff/","title":"Index","text":"<p>hide: - navigation description: \"A number of vulnerabilities have occurred in ActivityPub implementations due to \"type confusion\" attacks - where unrelated files on the same hostnmae as an ActivityPub implementation are processed as obejcts with ActivityPub semantics. Such attacks have been mitigated by carefuly validating the Content-Type header (and by implementations ensuring that users cannot create files with the application/activity+json or application/ld+json content types), but it would bolster such defences if messages intended to be processed with ActivityPub semantics Additionally, ActivityPub nominally supports transfer syntaxes other than JSON-LD (such as any other RDF syntax like Turtle; or potentially a more bandwidth efficient syntax such as a hypothetical CBOR-LD). Strict content type filtering permanently prevents usage of such syntaxes in the future The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \" SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC2119. \"</p>"},{"location":"fep/96ff/#fep-96ff-explicit-signalling-of-activitypub-semantics","title":"FEP-96ff: Explicit signalling of ActivityPub Semantics","text":"Authors Status Date received Tracking issue Discussions Repository Erin Shepherd fep@erinshepherd.net <code>DRAFT</code> 2024-02-17 #272 - codeberg"},{"location":"fep/96ff/#summary","title":"Summary","text":"<p>A number of vulnerabilities have occurred in ActivityPub implementations due to \"type confusion\" attacks - where unrelated files on the same hostnmae as an ActivityPub implementation are processed as obejcts with ActivityPub semantics. </p> <p>Such attacks have been mitigated by carefuly validating the <code>Content-Type</code> header (and by implementations ensuring that users cannot create files with the <code>application/activity+json</code> or <code>application/ld+json</code> content types), but it would bolster such defences if messages intended to be processed with ActivityPub semantics </p> <p>Additionally, ActivityPub nominally supports transfer syntaxes other than JSON-LD (such as any other RDF syntax like Turtle; or potentially a more bandwidth efficient syntax such as a hypothetical CBOR-LD). Strict content type filtering permanently prevents usage of such syntaxes in the future</p> <p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \" SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC2119. </p>"},{"location":"fep/96ff/#signalling-of-activitypub-semantics","title":"Signalling of ActivityPub Semantics","text":"<p>An implementation signifies its intention to use ActivityPub semantics for a request or response body by including a Link header with relation type <code>type</code> and contents of <code>https://www.w3.org/TR/activitypub/</code>. This header usage is borrowed from LDP, which uses the same relation type to indicate support of LDP semantics.</p> <pre><code>Link: &lt;https://www.w3.org/TR/activitypub/&gt;;rel=\"type\"\n</code></pre> <p>A conformant implementation MUST include this link relation in any HTTP messages it intends to be processed with ActivityPub semantics.</p> <p>A conformant implementation MUST process a HTTP message and where this link relation is present and the content type is an implementation supported transport syntax for ActivityStreams 2 with ActivityPub semantics.</p> <p>A conformant implementation MAY process a HTTP message without any Link header with relation \"type\", where the <code>Content-Type</code> header contains exactly the value <code>application/activity+json</code> or <code>application/ld+json</code> with a profile parameter containing the token <code>https://www.w3.org/ns/activitystreams</code> with ActivityPub semantics (This enables backwards compatibility with existing implementations.)</p> <p>A conformant implementation MUST NOT process any other HTTP messages with ActivityPub semantics.</p> <p>Conformant implementations MUST support messages containing multiple Link headers, including multiple link headers with the \"type\" relation. The following examples are all valid and indicate that processing should occur with ActivityPub semantics:</p> <pre><code>Link: &lt;https://www.w3.org/TR/activitypub/&gt;;rel=\"type\"\n\nLink: &lt;https://www.w3.org/TR/activitypub/&gt;;rel=\"type\", &lt;https://example.com/&gt;;rel=\"test\"\n\nlink: &lt;https://www.w3.org/TR/activitypub/&gt;;rel=\"type\", &lt;https://example.com/&gt;;rel=\"type\"\n\nLINK: &lt;https://www.w3.org/TR/activitypub/&gt;;rel=\"type\"\nLink: &lt;https://example.com/&gt;;rel=\"test\"\n\nLink: &lt;https://www.w3.org/TR/activitypub/&gt;;rel=\"type\"\nlInK: &lt;https://example.com/&gt;;rel=\"type\"\n</code></pre> <p>Implementations are encouraged to periodically add additional Link relations to their messages to ensure support by counterparties. (This process is typically termed greasing, after the GREASE extension to TLS)</p> <p>For the avoidance of doubt, ActivityPub semantics apply only in cases where implementations expect to transfer ActivityStreams 2 documents with the semantics described in the ActivityPub specification. This link relation MUST NOT be included for other exchanges that an ActivityPub implementation may perform, including but not limited to WebFinger queries.</p>"},{"location":"fep/96ff/#why-not-just-rely-upon-the-content-type","title":"Why not just rely upon the content type?","text":"<p>The content type alone does not specify semantics. The ActivityStreams 2 syntax can be used independently of ActivityPub, and non-ActivityPub systems such as Cohost produce ActivityStreams 2 documents.</p> <p>Additionally, even though ActivityPub implementations can prevent the creation of unexpected resources with an ActivityStreams content type, they cannot prevent other applications running on the same name from permitting the untrusted creation of such resources. </p> <p>It is hoped that a future version of this specification (after widespread deployment of explicit tagging of messages with explicit semantics) can omit the permission to determine semantics based upon media type.</p>"},{"location":"fep/96ff/#vulnerabilities-non-normative","title":"Vulnerabilities (Non-Normative)","text":"<p>The following is a list of known implementation vulnerabilities which would have been prevented by implementation of this mechanism:</p> <ul> <li>CVE-2024-25623: Lack of media type verification of Activity Streams objects allows impersonation of remote accounts (Mastodon)</li> <li>GHSA-qqrm-9grj-6v32: Lack of media type verification of Activity Streams objects allows impersonation and takeover of remote accounts (Misskey)</li> <li>Strict content type validation, Pixelfed (Same as previous two), no vulnerability ID assigned</li> <li>add stricter checks during all stages of dereferencing remote AS objects, GoToSocial, No vulnerability ID assigned</li> <li>GHSA-xmw2-875x-rq88: Possibility of a fake account on a domain with arbitrary user content (Kitsune)</li> <li>Pleroma Issue 1948: Messages can be spoofed</li> </ul> <p>If all implmenetations strictly required implementation of the link relation check, the following vulnerabilities would also be prevented:</p> <ul> <li>CVE-2023-36460, Arbitrary file creation through media attachments (Mastodon)</li> </ul> <p>(This is not presently proposed because it would break compatibility with existing implementations)</p> <p>These lists are likely not exhaustive.</p>"},{"location":"fep/96ff/#references","title":"References","text":"<ul> <li>ActivityPub Christine Lemmer Webber, Jessica Tallon et al, 2018</li> <li>Linked Data Platform 1.0, Steve Speicher, John Arwe, Ashok Malhotra, 2015</li> </ul>"},{"location":"fep/96ff/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/9967/","title":"FEP-9967: Polls","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2025-01-23 #478 Discussions codeberg"},{"location":"fep/9967/#summary","title":"Summary","text":"<p>How to make polls in ActivityPub network.</p>"},{"location":"fep/9967/#history","title":"History","text":"<p>Mastodon implemented polls in 2019 (documentation).</p> <p>ActivityStreams specification provides recommendations for representing questions, but these recommendations were found to be impractical and the de-facto standard differs from them.</p>"},{"location":"fep/9967/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/9967/#overview","title":"Overview","text":"<pre><code>sequenceDiagram\n actor Alice\n actor Bob\n actor Charlie\n Note right of Alice: Alice publishes a poll\n Alice -&gt;&gt; Bob: Create(Question)\n Alice -&gt;&gt; Charlie: Create(Question)\n Note left of Bob: Bob votes\n Bob -&gt;&gt; Alice: Create(Note)\n Note right of Alice: Alice receives the vote and updates poll results\n Alice -&gt;&gt; Bob: Update(Question)\n Alice -&gt;&gt; Charlie: Update(Question)</code></pre>"},{"location":"fep/9967/#question-object","title":"Question object","text":"<p>A poll is represented by a <code>Question</code> object. It MUST be published with <code>Create</code> activity (in contrast with ActivityStreams vocabulary where <code>Question</code> itself is defined as intransitive activity).</p> <p>A <code>Question</code> object is typically structured similarly to a <code>Note</code>. In addition to common properties such as <code>attributedTo</code> and <code>content</code>, it MUST have one of the following:</p> <ul> <li><code>oneOf</code>, if the poll requires choosing a single option.</li> <li><code>anyOf</code>, if the poll allows choosing multiple options.</li> </ul> <p>The value of this property MUST be an array of objects representing poll options:</p> <ul> <li><code>type</code>: the <code>Note</code> string.</li> <li><code>name</code>: the text of the poll option.</li> <li><code>replies</code>: the total number of votes, as a collection:<ul> <li><code>type</code>: the <code>Collection</code> string.</li> <li><code>totalItems</code>: the total number of votes for this poll option.</li> </ul> </li> </ul> <p>The <code>name</code> of a poll option MUST be unique within a poll. If the text contains custom emoji microsyntax, the corresponding <code>Emoji</code> objects are added to the <code>tag</code> array of the <code>Question</code> object.</p> <p>The number of poll options is often limited, but the limit differs between implementations.</p> <p><code>Question</code> objects MAY have an <code>endTime</code> property indicating the time when the poll ends. Some implementations use <code>closed</code> property instead of <code>endTime</code> or in addition to it (even if the poll is open). Consumers MUST process <code>closed</code> in the same way as <code>endTime</code>.</p> <p><code>Question</code> objects SHOULD have an <code>updated</code> property.</p> <p>Example:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/polls/1\",\n \"type\": \"Question\",\n \"attributedTo\": \"https://social.example/actors/1\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"content\": \"&lt;p&gt;Question&lt;/p&gt;\",\n \"endTime\": \"2024-07-17T18:18:17Z\",\n \"updated\": \"2024-07-16T20:53:05Z\",\n \"oneOf\": [\n {\n \"type\": \"Note\",\n \"name\": \"Answer 1\",\n \"replies\": {\n \"type\": \"Collection\",\n \"totalItems\": 596\n }\n },\n {\n \"type\": \"Note\",\n \"name\": \"Answer 2\",\n \"replies\": {\n \"type\": \"Collection\",\n \"totalItems\": 379\n }\n }\n ]\n}\n</code></pre>"},{"location":"fep/9967/#voting","title":"Voting","text":""},{"location":"fep/9967/#vote-object","title":"Vote object","text":"<p>A vote is represented by a <code>Note</code> object with the following structure:</p> <ul> <li><code>id</code>: the ID of the vote.</li> <li><code>type</code>: the <code>Note</code> string.</li> <li><code>attributedTo</code>: the actor that is making this vote.</li> <li><code>inReplyTo</code>: the ID of the <code>Question</code> object.</li> <li><code>name</code>: the value of the <code>name</code> property of the chosen poll option.</li> <li><code>to</code>: the author of the poll.</li> </ul> <p>The object MUST NOT have a <code>content</code> property.</p> <p>This object is wrapped in a <code>Create</code> activity and sent to the author of the poll. If the poll is anonymous, this activity MUST NOT be delivered to anyone else.</p> <p>When a poll allows multiple choices, each vote MUST be sent as a separate activity.</p> <p>Example of a vote:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/votes/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://social.example/actors/2\",\n \"inReplyTo\": \"https://social.example/polls/1\",\n \"name\": \"Answer 1\",\n \"to\": \"https://social.example/actors/1\"\n}\n</code></pre>"},{"location":"fep/9967/#receiving-a-vote","title":"Receiving a vote","text":"<p>The structure of a vote is very similar to a direct reply. It can be identified as a <code>Note</code> with <code>name</code> and <code>inReplyTo</code> properties, but without a <code>content</code> property.</p> <p>Upon receving a vote, the author of the poll performs the following checks:</p> <ul> <li>The voter has a permission to vote (if the poll is not public).</li> <li>The poll is still active (if <code>endTime</code> is specified).</li> <li>The vote with the specified <code>id</code> is not already registered.</li> <li>The vote with the specified <code>name</code> is not already registered (if multiple choices are not allowed).</li> </ul> <p>If any of these checks fails, the vote MUST be ignored.</p> <p>Otherwise, the author of the poll updates the vote count for the specified poll option (the <code>totalItems</code> property of a corresponding <code>replies</code> collection). The value of the <code>updated</code> property is changed to the current date and time.</p>"},{"location":"fep/9967/#publishing-results","title":"Publishing results","text":"<p>When actor that published a poll receives a vote, it publishes an <code>Update</code> activity containing the <code>Question</code> object with updated results. This activity MUST be delivered to the audience of the poll and to every actor that voted in the poll.</p>"},{"location":"fep/9967/#editing-options","title":"Editing options","text":"<p>The type of a poll (single choice / multiple choices) and its options might be changed at any time. In that case the author of the poll MUST reset the vote counts.</p>"},{"location":"fep/9967/#future-work","title":"Future work","text":"<p>The method of identifying votes described in this document is not reliable because a reply can look exactly like a vote. Alternative representations are being discussed (e.g. the <code>Respond</code> activity).</p>"},{"location":"fep/9967/#implementations","title":"Implementations","text":"<p>This document is based on implementations of polls in Mastodon and Pleroma.</p>"},{"location":"fep/9967/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Mastodon contributors, Polls</li> <li>silverpill, FEP-9098: Custom emojis, 2025</li> </ul>"},{"location":"fep/9967/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/9fde/","title":"FEP-9fde: Mechanism for servers to expose supported operations","text":"Authors Status Date received Tracking issue Discussions Repository Nik Clayton nik@ngo.org.uk <code>DRAFT</code> 2024-03-14 #282 - codeberg Version Date Changes 3.1 2024-03-13 - Convert to FEP format- Incorporate feedback from Johannes Ernst and Steve Bates- Renamed <code>clientApis</code> to <code>operations</code> so this is usable for server-server operations too- Added \"Open questions\" section 3.0 2024-01-22 Re-write; include the operation information in the nodeinfo 2.0 2023-09-25 Re-write; replace the original suggestion to use the OpenAPI definition with a simpler specification 1.0 2023-08-14 Initial draft"},{"location":"fep/9fde/#summary","title":"Summary","text":"<p>This document proposes an extension to the NodeInfo schema (FEP-f1d5) that would allow developers of Mastodon and Mastodon-like servers to unambigiously communicate the operations their servers support, and allow developers of software that interoperates with those servers to detect those features, promoting interoperability and easier adoption of new features.</p>"},{"location":"fep/9fde/#synopsis","title":"Synopsis","text":"<p>This document is written for:</p> <ul> <li>The maintainers of the NodeInfo specification</li> <li>Developers of Mastodon and Mastodon-like servers</li> <li>Developers of clients for those servers</li> </ul> <p>After reading this document you should:</p> <ul> <li>Understand the general problem this is intended to solve</li> <li>Understand the proposed solution</li> <li>Understand alternatives to the solution, and why they are not appropriate</li> <li>Understand the open questions for this proposal</li> <li>Be able to provide feedback on the proposal</li> </ul>"},{"location":"fep/9fde/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/9fde/#overview","title":"Overview","text":"<p>[!IMPORTANT] Other servers are also clients</p> <p>The primary perspective of this document's author is as an end-user client developer dealing with the Mastodon client API, and the examples are written from that perspective. However, this proposal can also be used to describe operations between different servers in the Fediverse using other protocols (e.g., ActivityPub). For any given interaction between a pair of servers one of them is going to be acting as the client in the interaction, and will need to know the operations the server supports.</p> <p>For the purposes of this document a \"Mastodon or Mastodon-like\" server is a server that presents the Mastodon client API, optionally with extensions to that API that provide additional functionality. These servers include, but are not limited to:</p> <ul> <li>Mastodon</li> <li>Glitch</li> <li>Hometown</li> <li>Pleroma</li> <li>Akkoma</li> <li>Firefish</li> <li>Iceshrimp</li> <li>Sharkey</li> <li>Friendica</li> <li>GoToSocial</li> </ul> <p>Clients of these servers have an API discovery problem. Since different servers support different (but similar) APIs the client has to determine what API operations the server supports.</p> <p>Given the wide variety of servers that are available, and their many forks, it's not feasible for clients to maintain an accurate list of all the possible server software names while mapping the names to API features.</p> <p>Instead the server should have a mechanism for advertising the operations it supports.</p> <p>The client would use this when determining what features to show the user, without needing to employ complex, error-prone heuristics.</p> <p>This would also provide a clear mechanism for Mastodon and Mastodon-like servers to incrementally deploy new features and deprecate old ones without inconveniencing clients.</p> <p>It also provides a clear mechanism to advertise server functionality without continually bolting it on to the \"instance info\" mechanism in the inconsistent fashion that has been done so far.</p> <p>The rest of this document sets out the specific problems I'm interested in solving, with motivating examples, and then describes how the new approach would solve these problems.</p>"},{"location":"fep/9fde/#problems","title":"Problems","text":""},{"location":"fep/9fde/#the-supported-api-is-not-easily-discoverable","title":"The supported API is not easily discoverable","text":"<p>Changes are made to the Mastodon API in a manner that is not easily discoverable by clients.</p> <p>For example, Add POST /api/v1/conversations/:id/unread by ClearlyClaire \u00b7 Pull Request #25509 \u00b7 mastodon/mastodon \u00b7 GitHub adds a new API endpoint (<code>api/v1/conversations/:id/unread</code>).</p> <p>The only way a client can discover that this API exists is to maintain, per-client, a mapping between Mastodon server version and the API supported at each version.</p> <p>This is:</p> <ol> <li>A lot of work for each client</li> <li>Something that every client needs to do</li> <li>Easy to get wrong</li> <li>Doesn't scale across multitudes of different servers</li> </ol>"},{"location":"fep/9fde/#no-standard-way-for-mastodon-servers-to-advertise-that-some-functionality-is-disabled-or-unavailable","title":"No standard way for Mastodon servers to advertise that some functionality is disabled or unavailable","text":"<p>The Instance information contains a <code>configuration</code> block that has some, but not all the information necessary to determine the features a server supports.</p> <p>Other servers have extended this information in incompatible ways (e.g., the <code>pleroma</code> block).</p> <p>Other servers may not implement the functionality at all. For example, GoToSocial did not implement support for the Mastodon client-side filter API until version 0.15.0.</p>"},{"location":"fep/9fde/#no-standard-way-for-mastodon-like-servers-to-advertise-their-additional-functionality-to-clients","title":"No standard way for Mastodon-like servers to advertise their additional functionality to clients","text":"<p>Mastodon-like servers implement some or all of the Mastodon API.</p> <p>In many cases they also extend the API, providing additional functionality (local-only posting, quoting, markdown formatting, bookmarks, etc.)</p> <p>In some cases that functionality has already been incorporated in Mastodon (e.g., bookmarks), in other cases there are plans to include that functionality in Mastodon (e.g., quoting, markdown formatting).</p> <p>This leads to three problems.</p> <ol> <li>There is no simple way for clients to know which parts of the Mastodon API the server supports</li> <li>There is no simple way for clients to know if the server supports additional operations</li> <li>If Mastodon decides to implement an API that was first introduced in a Mastodon-like server there is no way for clients to detect this, without recompiling the client with new information about what features a given Mastodon server version implements</li> </ol>"},{"location":"fep/9fde/#server-developers-have-too-much-to-do","title":"Server developers have too much to do","text":"<p>Server developers already have a lot of work to do. Any proposal should therefore be straightforward to implement. Additional complexity, such as changing the contents of existing API responses, or requiring developers of different servers to tightly coordinate when new functionality is introduced is going to make it less likely that groups adopt any proposed solutions.</p>"},{"location":"fep/9fde/#proposed-solution","title":"Proposed solution","text":"<p>A given Mastodon or Mastodon-like server supports a set of operations.</p> <p>To expose those to the user a Mastodon client needs to know:</p> <ul> <li>Which operations does the server support?</li> <li>What's the overlap between the operations the server supports and the operations the client supports?</li> </ul> <p>Therefore we need:</p> <ol> <li>A unique identifer for each operation that a set of servers supports identically</li> <li>A mechanism for a server to report the operations it supports</li> </ol>"},{"location":"fep/9fde/#a-unique-identifier-for-each-operation","title":"A unique identifier for each operation","text":"<p>Operations are identified by the reverse FQDN of the server software that first implemented that operation, then an arbitrary number of additional dot-separated labels determined by the server authors.</p> <p>This ensures that operation IDs are unique without needing tight coordination between different server developer groups.</p> <p>For example:</p> <ul> <li><code>org.joinmastodon.api.statuses.post</code></li> <li><code>org.joinmastodon.api.statuses.translate</code></li> <li><code>io.github.glitch-soc.api.statuses.bookmark</code></li> <li><code>dev.iceshrimp.api.notes.reactions.create</code></li> </ul> <p>[!NOTE] Precise reverse FQDN to use for each server is to be decided</p> <p>This example use the reverse FQDNs for the server's primary websites or documentation sites, but each server group would determine and document the reverse FQDN for their server's operations.</p> <p>[!NOTE] Dot-separated labels SHOULD map 1:1 to API endpoint components</p> <p>In these examples the dotted components after the <code>api</code> correspond to the path components of the API endpoint. While there is no requirement that they do so server implementors are RECOMMENDED to do so, as it makes it easier for developers to mentally map between the operation identifier and the API path when they are working with the code.</p> <p>Each label within the operation identifier is represented in lower-case US-ASCII (a character set containing 128 characters associated with code points in the range 0..7F) . If a server team wishes to define an operation that uses a non-US-ASCII label they MUST encode the operation label to US-ASCII using the <code>ToASCII</code> transformation described in RFC 3490 - Internationalizing Domain Names in Applications (IDNA).</p>"},{"location":"fep/9fde/#each-operation-has-one-or-more-versions","title":"Each operation has one or more versions","text":"<p>Each operation exists at one or more semver-compatible (v2.0.0) versions. Semver is used because it is a widely deployed standard, easily understandable, and client libraries that can parse this format are available across many different programming languages.</p> <p>For example, in the Mastodon API documentation \"Post a new status\" describes the API for posting a new status. That API has changed three times in the Mastodon server implementation.</p> <ol> <li>Initial implementation</li> <li>Support for <code>scheduled_at</code></li> <li>Support for <code>poll</code></li> </ol> <p>There are no backwards-incompatible breaking changes across those versions so this is the same operation at three different versions; per Semver the major version stays the same and the minor version is incremented.</p> <ul> <li><code>1.0.0</code> - initial implementation</li> <li><code>1.1.0</code> - support for <code>scheduled_at</code></li> <li><code>1.2.0</code> - support for <code>polls</code></li> </ul> <p>[!IMPORTANT] These version numbers are unrelated to the version number of the software that introduced the operation</p>"},{"location":"fep/9fde/#example-bookmarks","title":"Example: Bookmarks","text":"<p>Bookmarking statuses originated in the glitch-soc fork and was incorporated in to Mastodon.</p> <p>Therefore, the ID for the bookmark operations -- if they are compatible with the glitch-soc implementation -- use the <code>io.github.glitch-soc.api</code> prefix.</p> <ul> <li><code>io.github.glitch-soc.api.statuses.bookmark</code> @ <code>1.0.0</code> - bookmark a status</li> <li><code>io.github.glitch-soc.api.statuses.unbookmark</code> @ <code>1.0.0</code> - remove a status from bookmarks</li> <li><code>io.github.glitch-soc.api.timeline.bookmarks</code> @ <code>1.0.0</code> - fetch a timeline of the user's bookmarks</li> <li><code>io.github.glitch-soc.api.timeline.bookmarks</code> @ <code>1.1.0</code> - fetch a timeline of the user's bookmarks, supporting <code>min_id</code> and <code>max_id</code> simultaneously</li> </ul>"},{"location":"fep/9fde/#client-discovery-of-supported-operations-and-endpoints","title":"Client discovery of supported operations and endpoints","text":"<p>Clients must be able to discover which operations the server supports and the endpoints to use for those operations.</p> <p>To do this the nodeinfo (determined via <code>/.well-known/nodeinfo</code>) schema should be extended to support a new <code>operations</code> property.</p> <p>The property's value is a map from a string key -- the operation ID -- to a set of one or more Semver versions of the operation that the server supports.</p> <p>For example:</p> <pre><code>\"operations\": {\n ...\n \"org.joinmastodon.api.some.operation\": [\"1.0.0\", \"1.1.0\", \"1.2.0\", \"2.0.0\"]\n ...\n}\n</code></pre> <p>[!NOTE] Unordered versions</p> <p>The supported version operations is not ordered; client code MUST treat this as a set, not a list.</p> <p>[!NOTE] Not limited to Mastodon / Mastodon-like servers</p> <p>This <code>operations</code> map is not limited to operations supported by Mastodon/Mastodon-like servers. This is a general mechanism that can be used by servers to expose information about their supported operations and could be used by other Fediverse software like Lemmy, KBin, etc.</p> <p>Because of the Semver rules for breaking changes servers MAY omit earlier versions from the list if they are included in a later version. In the previous example the <code>1.0.0</code> and <code>1.1.0</code> versions can be omitted as a server supporting <code>v1.2.0</code> of an operation implicitly supports all preceding versions with the same major number.</p> <pre><code>\"operations\": {\n ...\n \"org.joinmastodon.api.some.operation\": [\"1.2.0\", \"2.0.0\"]\n ...\n}\n</code></pre> <p>[!NOTE] There is no need to specify the operation semantics</p> <p>The semantics of each {operation, version} pair are already known by the client (for each operation it supports). Semantics like whether these endpoints are <code>GET</code>, <code>POST</code>, <code>DELETE</code>, or <code>PATCH</code>, the exact names of the URL query parameters, the API endpoint, etc.</p> <p>In other words, servers MUST NOT advertise support for an existing operation ID and change anything about how that operation works. The server developers should either define and document a new operation ID, or implement the operation as a new version (bumping the major version if it is a breaking change).</p>"},{"location":"fep/9fde/#activitypub-extension-support","title":"ActivityPub extension support","text":"<p>Most of this document has presented examples using the Mastodon client API (and related APIs) but, as noted, this proposal also provides a clean mechanism to allow servers to report other extensions they support.</p> <p>Here is an example from FEP 6481 - # Specifying ActivityPub extension support with NodeInfo which is presented as:</p> <p>Manyfold intends to define a new Activity Vocabulary Object type for its own rich content, <code>3dModel</code>. The specification will define an extension IRI, for instance <code>https://w3id.org/manyfold/3dModel#v1</code>.</p> <p>Applications that wish to receive Activities with this type can state their support for the extension in their NodeInfo file as follows; Manyfold instances will then know that they can send that object type and it will be understood.</p> <pre><code>{\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/manyfold/3dModel#v1\"\n ]\n }\n }\n}\n</code></pre> <p>In this proposal that could represented like this:</p> <pre><code>\"operations\": {\n \"app.manyfold.activitypub.accept.3dmodel\": [\"1.0.0\"],\n ...\n}\n</code></pre> <p>(the precise name of the operation would need to be determined)</p> <p>Any other server that can accept a <code>3dModel</code> would expose the same operation.</p>"},{"location":"fep/9fde/#to-deploy-this","title":"To deploy this...","text":""},{"location":"fep/9fde/#server-developers","title":"Server developers","text":"<p>Servers where the set of supported operations is not user configurable would need to maintain a static map of operations to versions, and return that map as part of the nodeinfo response.</p> <p>If the set of operations is user configurable (e.g., perhaps the server software supports a translation API but the server operator has not enabled translation support) the nodeinfo response would need to be dynamically generated from the current software configuration.</p> <p>In both cases developing a new operation or changing an existing operation would require the developers to:</p> <ol> <li>Determine the operation's version number, following semver backwards-compatible rules</li> <li>Document the behaviour of the new operation / version</li> <li>Include the new operation / version in the server's response</li> </ol>"},{"location":"fep/9fde/#client-developers","title":"Client developers","text":"<p>To provide the best user experience client developers SHOULD fetch the operations map when the user logs in.</p> <p>If the client supports a particular operation at a particular version the client can query the map and determine whether the concrete version they need is in the map, or met by a higher version. Semver client libraries are available for Kotlin and Java (Android) and Swift (iOS), as well as many other languages.</p> <p>If the server does not support the operation the client MAY fall back to a different operation, or disable the particular operation in the UI.</p> <p>To use the example from earlier, Add POST /api/v1/conversations/:id/unread by ClearlyClaire \u00b7 Pull Request #25509 \u00b7 mastodon/mastodon \u00b7 GitHub which adds a new API endpoint (<code>api/v1/conversations/:id/unread</code>).</p> <p>The server would report this as:</p> <pre><code>\"operations\": {\n ...\n \"org.joinmastodon.api.conversations.id.unread\": [\"1.0.0\"]\n ...\n}\n</code></pre> <p>and a client that wanted to conditionally support this would query the operations map for <code>org.joinmastodon.api.conversations.id.unread</code> with any version entry with a major version of <code>1</code>, and if the operation/version pair is not found then disable the \"Mark a conversation unread\" UI affordances where they occur.</p>"},{"location":"fep/9fde/#is-there-a-proof-of-concept","title":"Is there a proof of concept?","text":"<p>Yes.</p> <p>I have started implementing the client side of this in Pachli. At the moment this uses server version parsing heuristics to maintain a Pachli-specific map of operations and supported versions (Server.kt ) and then query the server's reported capabilities and adjust the UI accordingly.</p> <p>For example, this snippet conditionally enables the \"edit filters\" UI only if the user's server supports filtering.</p> <p>Maintaining the server-specific operations map in Pachli is error prone, slow to update, and does not benefit the wider ecosystem of Mastodon clients and servers, hence this proposal.</p>"},{"location":"fep/9fde/#this-solves","title":"This solves...","text":"<p>This solves the problems described earlier:</p> <ul> <li>\"[[#The supported API is not easily discoverable]]\"<ul> <li>The client can easily discover the specific operations the server supports, and adjust UX accordingly</li> </ul> </li> <li>\"[[#No standard way for Mastodon servers to advertise that some functionality is disabled]]\"<ul> <li>The <code>operations</code> property must reflect the active configuration of the server.</li> </ul> </li> <li>\"[[#No standard way for Mastodon-like servers to advertise their functionality to clients]]<ul> <li>If a Mastodon-like server implements a Mastodon-compatible API endpoint it lists that endpoint using the relevant <code>org.mastodon...</code> operation identifier.</li> </ul> </li> <li>[[#Server developers have too much to do]]<ul> <li>This proposal doesn't modify any existing API responses</li> <li>For a given server the list of supported operations can be statically configured, and does not change after the server has launched</li> <li>The work of developing a dictionary of supported operations can be sharded amongst different groups<ul> <li>Server developers have a vested interest in contributing details of operations specific to their server, so more third party clients support them</li> <li>Client developers have a vested interest in reviewing and contributing details of operations specific to servers their users use, to make their clients more attractive to potential users</li> <li>No coordination is required between different groups of server developers to develop operation IDs</li> </ul> </li> <li>Developers are incentivised to re-use existing operations instead of inventing new ones<ul> <li>Implementing an existing operation in a compatible manner with another server increases the speed with which your users will be able to use the feature in their preferred clients.</li> </ul> </li> </ul> </li> </ul>"},{"location":"fep/9fde/#not-in-scope","title":"Not in scope","text":"<p>This proposal doesn't address how clients can discover any limits associated with the operations. For example, how many characters are allowed per post, or the number of options that can be included in a poll.</p> <p>That information is already included in the server's <code>/api/v2/instance</code> call (in the language of this proposal, the <code>org.mastodon.api.instance</code> operation).</p> <p>I did consider extending the <code>operations</code> definition so that each operation mapped to an object that contained multiple keys, like this:</p> <pre><code>\"operations\": {\n \"org.joinmastodon.api.statuses.post\": {\n \"1.0.0\": {\n \"endpoint\": \"/api/v1/statuses\",\n \"limits\": {\n \"max_characters\": 500,\n // ...\n },\n \"mimeTypes\": [\"text/plain\"],\n // ...\n },\n \"1.1.0\": { /* ... */ }\n }\n}\n</code></pre> <p>That would significantly complicate this proposal, increasing the risk that it's not adopted. There's also no clear value in doing this.</p>"},{"location":"fep/9fde/#alternatives-considered","title":"Alternatives considered","text":""},{"location":"fep/9fde/#reporting-capabilities-alongside-operations","title":"Reporting capabilities alongside operations","text":"<p>It's tempting to think that operations could be broken down in to smaller parts.</p> <p>For example, instead of different versions for the \"post a status\" operation you could include more specific capabilities in the operation description:</p> <pre><code>\"operations\": {\n ...\n \"org.joinmastodon.api.statuses.post\": {\n \"contentWarning\": true,\n \"polls\": true,\n \"media\": true,\n ...\n }\n ...\n}\n</code></pre> <p>This indicates this server supports the \"post a new status\" operation with statuses that include content warnings, polls, and media.</p> <p>You don't do that because it results in a combinatorial explosion of the different sub-types of operations that clients need to support, without any significant benefit.</p> <p>Even the example above is incomplete; for example, some servers support including images in content warnings, so a simple boolean for the <code>contentWarning</code> property is insufficient.</p> <p>So treating the thing-that-has-to-be-versioned as the operation (post a status, translate, reblog, etc) seems to be the better level of granularity.</p>"},{"location":"fep/9fde/#reporting-capabilities-in-api-responses","title":"Reporting capabilities in API responses","text":"<p>A server could include metadata in each response that contains an object that describes the operations that can be performed on that object. For example, the Status object could be modified to include an <code>operations</code> property that looks like this:</p> <pre><code>{\n \"id\": \"103270115826048975\",\n \"created_at\": \"2019-12-08T03:48:33.901Z\",\n ...\n \"operations\": {\n \"org.joinmastodon.api.statuses.reply\": [\"POST\", \"https://example.com/api/v1/statuses\"],\n \"org.joinmastodon.api.statuses.view\": [\"GET\", \"https://example.com/api/v1/statuses/103270115826048975\"],\n \"org.joinmastodon.api.statuses.favourite\": [\"POST\", \"http/api/v1/statuses/103270115826048975/favourite\"],\n ... etc\n }\n }\n}\n</code></pre> <p>This is the Hypermedia as the engine of application state (HATEOAS) model.</p> <p>It's an interesting approach, and a possible future direction. But it would require significant work on the part of server developers to implement as it would affect every response returned by the server.</p> <p>On the other hand the approach in this proposal is static content in the nodeinfo response. It's significantly easier to implement and iterate on.</p>"},{"location":"fep/9fde/#clients-keep-a-hardcoded-server-version-capabilities-map","title":"Clients keep a hardcoded server version : capabilities map","text":"<p>This could go the other way, and instead require servers to have a consistent name and parseable version number, and expect clients to keep a map of \"server A at version V can perform operations X, Y, and Z\".</p> <p>I think this is the wrong approach for two reasons:</p> <p>First:</p> <ol> <li>It requires every client development team to independently maintain a mapping between server versions and capabilities</li> <li>It requires client updates whenever a server is released that supports a capability the client already supports on another server</li> </ol> <p>Re that last point a worked example might make it clearer.</p> <p>Suppose there are two server types, A and B. A supports operations X and Y, B supports X, Y, and Z.</p> <p>A client is released which supports operations X, Y, and Z, and is hardcoded with knowledge about which server type supports a given operation.</p> <p>A new version of server type A is released which now supports operation Z as well. But users of the client who connect to server type A cannot benefit from this until a new version of the client is released with updated information about the capabilities of server type A.</p> <p>With the proposal in this document this problem does not occur; if a client supports operation Z (at a given version) and a server advertises that it supports that operation then the client can choose to use it without needing a new release.</p> <p>This is better for our users.</p> <p>Second:</p> <p>Server developers do not seem to be interested in reporting useful versions for their software, for example:</p> <ul> <li>Sharkey: feat: report server version following semver.org requirements (#371) \u00b7 Issues \u00b7 TransFem.org / Sharkey \u00b7 GitLab</li> <li>Iceshrimp: #502 - Please use semver compatible versions - iceshrimp/iceshrimp - Iceshrimp development</li> <li>Firefish: Enforce semver for reported Firefish versions (#10844) \u00b7 Issues \u00b7 firefish / firefish \u00b7 GitLab</li> <li>Mastodon: Server should refuse to start if configured version is not semver.org compatible \u00b7 Issue #28843 \u00b7 mastodon/mastodon \u00b7 GitHub</li> </ul>"},{"location":"fep/9fde/#return-an-openapi-definition-for-the-supported-api","title":"Return an OpenAPI definition for the supported API","text":"<p>OpenAPI is a popular schema for defining an API. The server could just return the OpenAPI schema for the API that it supports.</p> <p>I did consider this (an earlier version of this proposal was built around it). But it complicates the data the client needs to process, and includes data that the client will ignore.</p> <p>Consider the <code>/api/v1/timelines/home</code> endpoint, which would have an operation ID something like <code>org.joinmastodon.api.timelines.home</code> under this proposal.</p> <p>This is the OpenAPI definition for that endpoint, copied from the GoToSocial project's OpenAPI definition (swagger.yaml, the descriptions have been deleted to keep this a reasonable length):</p> <pre><code> /api/v1/timelines/home:\n get:\n description: |-\n The statuses [... deleted ...]\n operationId: homeTimeline\n parameters:\n - description: [deleted]\n in: query\n name: max_id\n type: string\n - description: [deleted]\n in: query\n name: since_id\n type: string\n - description: [deleted]\n in: query\n name: min_id\n type: string\n - default: 20\n description: [deleted]\n in: query\n name: limit\n type: integer\n produces:\n - application/json\n responses:\n \"200\":\n description: Array of statuses.\n headers:\n Link:\n description: [deleted]\n type: string\n schema:\n items:\n $ref: '#/definitions/status'\n type: array\n \"400\":\n description: bad request\n \"401\":\n description: unauthorized\n security:\n - OAuth2 Bearer:\n - read:statuses\n summary: See statuses/posts by accounts you follow.\n tags:\n - timelines\n</code></pre> <p>Most of the information in that definition is redundant for the client.</p> <p>It's absolutely essential information to have for the server developer, and for producing documentation.</p> <p>But the client should already have this compiled in. The contract between the client and the server is that if the server reports that it supports the <code>org.joinmastodon.api.timelines.home</code> operation at v1.0.0:</p> <ul> <li>the endpoint is <code>/api/v1/timelines/home</code></li> <li>the valid parameters are <code>max_id</code>, <code>since_id</code>, <code>min_id</code>, and <code>limit</code>.</li> <li>the response is JSON, encoding an array of <code>Status</code></li> <li>there will be pagination details in the <code>Link</code> header</li> <li>authorization is required</li> </ul> <p>So returning an OpenAPI definition to the client significantly complicates things for no benefit.</p> <p>OpenAPI is also endpoint-oriented; by which I mean that the definition leads with the endpoint (<code>/api/v1/statuses</code>) and then describes the single operation that is present at that endpoint.</p> <p>This is backwards to what we need, where the operation comes first, and multiple operations might be supported at the same endpoint.</p>"},{"location":"fep/9fde/#use-the-api-path-as-the-key","title":"Use the API path as the key","text":"<p>Instead of the proposed format, use the API path as the map key, like this:</p> <pre><code>\"operations\": {\n \"/api/some/path\": [\"org.joinmastodon.x.y.z:1.0.0\", \"org.joinmastodon.x.y.z:1.1.0\", ...]\n}\n</code></pre> <p>I reject this for two reasons:</p> <ol> <li>It suggests that servers might decide to implement the same operation under different URL paths. That is unnecessary extra complexity, and per the previous section, the contract between the client and the server is if the server advertises support for operation X the definition of that operation includes that URL path it is served under.</li> <li>It's unfriendly to client developers; a client using this information is trying to answer the question \"Does the server support operation X?\", not \"Does API endpoint /a/b/c exist?\". So the operation identifier should be the lookup key for the map, not the endpoint.</li> </ol>"},{"location":"fep/9fde/#open-questions","title":"Open questions","text":"<p>These are questions this proposal does not explicitly answer. I have suggestions, but feedback is appreciated.</p>"},{"location":"fep/9fde/#can-servers-set-an-api-prefix","title":"Can servers set an API prefix?","text":"<p>While I do not believe servers should be able to arbitrarily change all aspects of the path an operation is served under it may be useful for servers to be able to specify a path prefix for some or all operations.</p> <p>E.g., a server that supports Mastodon and Friendica APIs might want to offer them at paths that starts <code>mastodon</code> and <code>friendica</code> respectively.</p> <p>To do this the map value would be a second object with <code>prefix</code> and <code>versions</code> keys.</p> <pre><code>\"operations\": {\n \"org.joinmastodon.api.conversations.id.unread\": {\n \"prefix\": \"/mastodon\",\n \"versions\": [\"1.0.0\"]\n },\n \"ca.friendi.api.conversations.id.unread\": {\n \"prefix\": \"/friendica\",\n \"versions\": [\"1.0.0\"]\n }\n}\n</code></pre> <p>If the <code>prefix</code> was omitted the default would be <code>/</code>.</p> <p>Adopting this format, even if the only key is <code>versions</code> is probably a good idea initially to support future changes in a backwards-compatible manner.</p>"},{"location":"fep/9fde/#what-happens-if-an-operations-owner-rebrands-or-disappears","title":"What happens if an operations \"owner\" rebrands or disappears?","text":"<p>If a FQDN owner entity rebrands or disappears (e.g., Calckey renamed to Firefish) what happens to the operation identifiers they have defined?</p> <p>I don't think a rebrand should necessitate a wholesale renaming of the operation identifiers, that's unnecessary churn. They may decide to define future operations using the new name.</p> <p>Since the operation identifies are not actual domains / URLs there is no requirement for them to actually resolve to anything, so I don't foresee a technical problem with this approach if a project that defined some operation identifiers shuts down. As long as there is still documentation that defines the API for those operations nothing is lost.</p>"},{"location":"fep/9fde/#is-the-reverse-fqdn-with-suffix-approach-the-best-format-for-operations-keys","title":"Is the reverse-FQDN-with-suffix approach the best format for operations keys?","text":"<p>I think so.</p> <p>I did consider using Uniform Resource Name (URN) or Internationalised Resource Identifiers (IRI) . There are advantages; for example, the operation identifier could be a URN or IRI that resolves to a document (perhaps the OpenAPI definition) for the API for that operation.</p> <p>However, that introduces the difficult topic of case-sensitivity when clients are comparing the list of operations the server returns with the list of operations they support.</p> <p>If the operation key was a URN or IRI it is, by definition, case-sensitive in parts. This provides an opportunity for server developers to incorrectly report an identifier with the wrong case, and for client developers to look for operations with the wrong case, impeding interoperability.</p> <p>Suggesting \"Clients should case-fold operation identifiers to lower case before comparing them\" does not solve the problem, as rules for correctly lower-casing text are not well specified, and may differ from implementation to implementation, again impeding interoperability.</p> <p>The approach of using a reverse FQDN with additional labels, and ensuring it is encoded with IDN sidesteps those problems.</p>"},{"location":"fep/9fde/#is-there-a-11-mapping-between-operations-and-api-calls","title":"Is there a 1:1 mapping between operations and API calls?","text":"<p>Should every supported API call have an associated operation, or is it OK to consider multiple API calls as a single operation the server supports?</p> <p>I think a 1:1 mapping makes most sense, as it allows server developers to incrementally deploy new features.</p> <p>For example, at the time of writing Friendica only partially supports voting; posts with votes can be displayed and will be returned by the API, but the operations to create a post with a poll, or to vote on a poll are not implemented.</p> <p>With a 1:1 mapping beween API and operations a Friendica server can advertise \"I can return posts that can contain polls; I do not support API calls that post polls or vote on them\".</p> <p>If \"voting\" was treated as a single operation then Friendica would be forced to advertise \"I do not support polls\", and a client might unnecessarily not show polls attached to Friendica posts.</p> <p>Anything other than a 1:1 mapping could also lead to different server developer teams \"bundling\" API calls into single operations in different ways, making things more complex for client developers, not less.</p>"},{"location":"fep/9fde/#should-clients-indicate-the-operation-identifier-when-making-a-request","title":"Should clients indicate the operation identifier when making a request?","text":"<p>When a server supports multiple versions of an API at a single endpoint it can be challenging to reliably determine the version of the API the client is calling. Server software often has to resort to \"sniffing\" the request and deducing the client's intentions by the presence or absence of specific properties in the request.</p> <p>This makes it more difficult to write the code to process the request and to determine what error details should be returned to the client.</p> <p>I think trying to solve this problem is outside the scope of this proposal. However, if this proposal is adopted I recommend server developers include the operation ID as a mandatory specific property in all future changes to their API, so this problem is gradually solved.</p>"},{"location":"fep/9fde/#does-this-need-to-be-a-new-top-level-key-in-the-nodeinfo","title":"Does this need to be a new top-level key in the nodeinfo?","text":"<p>No. This could also be in the metadata, exposed as:</p> <pre><code>{\n \"version\": \"2.2\",\n ...\n \"metadata\": {\n \"operations\": {\n // operation data here\n }\n }\n}\n</code></pre>"},{"location":"fep/9fde/#related-links-prior-art","title":"Related links / prior art","text":"<p>Not an exhaustive list:</p> <ul> <li>Mastodon Issues<ul> <li>API Documentation via Swagger / RAML / Others \u00b7 Issue #1404 \u00b7 mastodon/mastodon \u00b7 GitHub</li> <li>How to get OpenAPI spec (JSON or YAML) \u00b7 Issue #16328 \u00b7 mastodon/mastodon \u00b7 GitHub</li> <li>OpenAPI documentation generation with rswag specs \u00b7 Issue #20572 \u00b7 mastodon/mastodon \u00b7 GitHub</li> </ul> </li> <li>Mastodon PRs (these all attempted to add OpenAPI definitions)<ul> <li>docs(open-api): Add OpenAPI Specification by oneslash \u00b7 Pull Request #20000 \u00b7 mastodon/mastodon \u00b7 GitHub</li> <li>Feat/add rswag in order to generate verified openapi docs by casaper \u00b7 Pull Request #20607 \u00b7 mastodon/mastodon \u00b7 GitHub</li> <li>[proposal] Machine readable API specification via OpenAPI by takayamaki \u00b7 Pull Request #25043 \u00b7 mastodon/mastodon \u00b7 GitHub</li> </ul> </li> <li>Blog posts<ul> <li>Automatically Generating OpenAPI Docs for Mastodon - DEV Community</li> </ul> </li> <li>Mastodon-like servers<ul> <li>GotoSocial OpenAPI definition of the Mastodon API</li> <li>[feature] Mastodon-compatible API extensions discovery mechanism \u00b7 Issue #1985 \u00b7 superseriousbusiness/gotosocial \u00b7 GitHub</li> </ul> </li> <li>FEPs<ul> <li>FEP 6481 - # Specifying ActivityPub extension support with NodeInfo</li> </ul> </li> </ul>"},{"location":"fep/9fde/#references","title":"References","text":"<ul> <li>NodeInfo</li> <li>RFC-2119 - Key words for use in RFCs to Indicate Requirement Levels</li> <li>RFC 3490 - Internationalizing Domain Names in Applications (IDNA)</li> <li>Semantic Versioning</li> <li>OpenAPI Specification</li> </ul>"},{"location":"fep/9fde/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/a070/","title":"Index","text":"<p>hide: - navigation description: \"In a [Github-issue] filed against the normative AS2 context, it was pointed out that attachment and tag are unordered by default, although some implementations of \"fediverse\" software blindly assume them to always be ordered. This can be made unambiguous by using @list in JSON-LD, but for plain JSON consumers, a separate shorthand term must be defined. This FEP attempts to disambiguate between unordered and ordered arrays for those plain JSON consumers.\"</p>"},{"location":"fep/a070/#fep-a070-ordered-properties-for-plain-json-consumers","title":"FEP-a070: Ordered properties for plain JSON consumers","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2023-06-13 #121 - codeberg"},{"location":"fep/a070/#summary","title":"Summary","text":"<p>In a [Github-issue] filed against the normative AS2 context, it was pointed out that <code>attachment</code> and <code>tag</code> are unordered by default, although some implementations of \"fediverse\" software blindly assume them to always be ordered. This can be made unambiguous by using <code>@list</code> in JSON-LD, but for plain JSON consumers, a separate shorthand term must be defined. This FEP attempts to disambiguate between unordered and ordered arrays for those plain JSON consumers.</p>"},{"location":"fep/a070/#prior-art-in-items-vs-ordereditems","title":"Prior art in <code>items</code> vs <code>orderedItems</code>","text":"<p>(This section is non-normative.)</p> <p>In the [AS2-vocab], there is <code>items</code>, which is used to express the items included in a Collection or OrderedCollection. However, in [ActivityPub], the OrderedCollection type is mandated to be reverse chronological order. Tangentially, it is valid to have a Collection with <code>items</code> that are either ordered or unordered.</p> <p>The normative [AS2-context] deals with issue by defining two different terms: <code>items</code> and <code>orderedItems</code>. Both of these terms have the same <code>@id</code> of <code>https://www.w3.org/ns/activitystreams#items</code> and the same <code>@type</code> of <code>@id</code> to indicate that they contain nodes on the graph, but <code>orderedItems</code> is additionally defined with a <code>@container</code> of an ordered <code>@list</code>, overriding the default <code>@container</code> of an unordered <code>@set</code>.</p> <p>The use of <code>orderedItems</code> is present in the examples for <code>items</code> within [AS2-vocab], but it is not defined separately because it is not a separate term. Following this pattern, we can define similarly \"ordered\" counterparts to \"unordered\" existing properties.</p>"},{"location":"fep/a070/#an-exploration-of-properties-that-producers-may-wish-to-explicitly-order","title":"An exploration of properties that producers may wish to explicitly order","text":"<p>(This section is non-normative)</p> <p>attachment : There is nothing that requires attachments to an object to be ordered; however, it is a popular expectation in several cases that the attachments should be ordered. For example, someone authoring an object may wish to attach three images in order, representing a triptych. If the array order of <code>attachment</code> were to be changed, this would change the author's intended representation.</p> <p>tag : Tags are generally freeform and not meant to be parsed in any order, but some use-cases may wish to order tags as well. For example, Tumblr allows users to set the order their tags are presented in, and users sometimes use these tags to communicate a series of comments or thoughts without having them be present in the conversational context.</p> <p>name : A thing may have multiple names, some of which are preferred more than others. Having an array for <code>name</code> is likely to confuse existing implementations at the time of writing this FEP, but it is something that could conceivably be useful for several use-cases.</p> <p>oneOf/anyOf : When a Question represents a poll with predefined options for possible answers, the order of the options may be relevant to understanding the poll. Consider a self-referential poll that asks you to predict which option will receive the most votes: \"option 2\", \"option 3\", or \"option 1\".</p>"},{"location":"fep/a070/#terms-defined-by-this-fep","title":"Terms defined by this FEP","text":"<p>In the associated context, we define the following terms:</p>"},{"location":"fep/a070/#orderedattachment","title":"orderedAttachment","text":"<p>URI : <code>https://www.w3.org/ns/activitystreams#attachment</code></p> <p>Notes : https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment</p> <p>JSON-LD definition:</p> <pre><code>{\n \"@context\": {\n \"orderedAttachment\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#attachment\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n }\n }\n}\n</code></pre>"},{"location":"fep/a070/#orderedtag","title":"orderedTag","text":"<p>Term : <code>orderedTag</code></p> <p>URI : <code>https://www.w3.org/ns/activitystreams#tag</code></p> <p>Notes : https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tag</p> <p>JSON-LD definition:</p> <pre><code>{\n \"@context\": {\n \"orderedTag\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#tag\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n }\n }\n}\n</code></pre>"},{"location":"fep/a070/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[AS2-context] W3C, activitystreams.jsonld</li> <li>[AS2-vocab] James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>[Github-issue] trwnh, orderedAttachment (and orderedTag?), 2023</li> </ul>"},{"location":"fep/a070/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/a427/","title":"FEP-a427: Server Domain Migration","text":"Authors Status Date received Tracking issue Discussions Repository Dmitry Skavish skavish@gmail.com <code>DRAFT</code> 2026-02-25 #767 Discussions codeberg"},{"location":"fep/a427/#summary","title":"Summary","text":"<p>This FEP defines a best-effort protocol for migrating an entire ActivityPub server from one domain to another when the operator controls both domains and can keep the old domain online.</p> <p>Example scenario used throughout this document:</p> <ul> <li>Old server: <code>sunset.social</code></li> <li>New server: <code>dawn.network</code></li> <li>Remote peer: <code>forest.instance</code></li> </ul> <p>This proposal introduces:</p> <ul> <li>A ServerMigration manifest (a durable migration description object).</li> <li>A ServerMigrationAcceptance object (destination confirmation).</li> <li>A ServerMove activity (notification to peers).</li> <li>Deterministic mapping rules for rewriting identifiers.</li> <li>A verification model that prevents identity hijacking.</li> </ul> <p>This FEP is explicitly best-effort. It does not guarantee preservation of all follows across all peers.</p>"},{"location":"fep/a427/#scope","title":"Scope","text":"<p>This FEP addresses server domain renaming: one source server migrating to one destination server with a one-to-one mapping of all identifiers. It does not cover merging multiple source servers into a single destination.</p>"},{"location":"fep/a427/#motivation","title":"Motivation","text":""},{"location":"fep/a427/#the-core-problem","title":"The Core Problem","text":"<p>In practice, ActivityPub identifiers are domain-bound: <code>https://sunset.social/users/alice</code></p> <p>Remote servers treat that URL as Alice's identity.</p> <p>If <code>sunset.social</code> changes its domain to <code>dawn.network</code>, then: <code>https://dawn.network/users/alice</code> is treated as a completely different actor unless peers are informed and update their local state.</p> <p>Current practice requires:</p> <ul> <li>Emitting <code>Move</code> for each actor.</li> <li>Hoping remote servers re-follow.</li> <li>Maintaining long-lived reverse proxies.</li> </ul> <p>For large instances, this is inefficient and unreliable.</p>"},{"location":"fep/a427/#why-not-just-send-old-domain-new-domain","title":"Why Not Just Send \"Old Domain \u2192 New Domain\"?","text":"<p>It might seem sufficient to send:</p> <pre><code>ServerMove { from: sunset.social, to: dawn.network }\n</code></pre> <p>That is insufficient for three independent reasons.</p>"},{"location":"fep/a427/#1-deterministic-rewriting-why-mapping-exists","title":"1. Deterministic Rewriting (Why Mapping Exists)","text":"<p>Remote servers need a deterministic algorithm for rewriting identifiers.</p> <p>If actor paths are preserved:</p> <pre><code>https://sunset.social/users/alice\n\u2192 https://dawn.network/users/alice\n</code></pre> <p>then a rule-based mapping works: replace origin, preserve path.</p> <p>But if:</p> <ul> <li>URL structure changed,</li> <li>routes differ,</li> </ul> <p>then simple domain substitution fails.</p> <p>Mapping exists to define precisely how old identifiers derive new identifiers.</p> <p>Without mapping:</p> <ul> <li>Peers must guess.</li> <li>Guessing causes broken follows and incorrect mentions.</li> <li>Full actor lists would need to be published (privacy leak).</li> </ul>"},{"location":"fep/a427/#2-identity-hijacking-prevention-why-acceptance-exists","title":"2. Identity Hijacking Prevention (Why Acceptance Exists)","text":"<p>If only one signed message were required, an attacker could send: <code>ServerMove sunset.social \u2192 evil.example</code></p> <p>If peers trusted that blindly, they would rewrite all identifiers and effectively transfer followers to the attacker.</p> <p>A signature proves who sent the message, not that they control both domains.</p> <p>Therefore migration requires:</p> <ul> <li>A statement from <code>sunset.social</code></li> <li>A corresponding acceptance from <code>dawn.network</code></li> </ul> <p>Both domains must independently assert the migration.</p> <p>Only when both sides agree may peers apply changes.</p>"},{"location":"fep/a427/#3-durability-and-idempotency-why-manifest-exists","title":"3. Durability and Idempotency (Why Manifest Exists)","text":"<p>A single notification message is:</p> <ul> <li>Not cacheable</li> <li>Not versioned</li> <li>Not lifecycle-aware</li> <li>Not retry-safe</li> </ul> <p>Peers require a durable object that:</p> <ul> <li>Has a stable <code>id</code></li> <li>Has a <code>state</code></li> <li>Can be fetched repeatedly</li> <li>Supports rollback</li> <li>Can carry proofs</li> </ul> <p>That durable object is the ServerMigration manifest.</p>"},{"location":"fep/a427/#terminology","title":"Terminology","text":""},{"location":"fep/a427/#servermigration-manifest","title":"ServerMigration (Manifest)","text":"<p>A persistent ActivityStreams object describing:</p> <ul> <li>Source server (<code>sunset.social</code>)</li> <li>Destination server (<code>dawn.network</code>)</li> <li>Mapping rules</li> <li>Lifecycle state</li> <li>Reference to destination acceptance</li> </ul> <p>It is the canonical description of the migration.</p> <p>Think of it as: \"The official migration document.\"</p>"},{"location":"fep/a427/#servermigrationacceptance","title":"ServerMigrationAcceptance","text":"<p>A persistent object hosted on the destination server confirming:</p> <ul> <li>It accepts migration from the source.</li> <li>It references the same manifest.</li> </ul> <p>Think of it as: \"We agree to receive these identities.\"</p>"},{"location":"fep/a427/#servermove","title":"ServerMove","text":"<p>A lightweight ActivityPub activity sent to peers that says:</p> <p>\"Please fetch and apply this migration manifest.\"</p> <p>It does not contain full migration details.</p>"},{"location":"fep/a427/#mapping","title":"Mapping","text":"<p>A deterministic rule that converts old URIs to new URIs. Given the same mapping rules and the same input URI, every implementation MUST produce the same output URI.</p> <p>Three types exist:</p> <ol> <li> <p>Origin-based mapping Replace origin (scheme + host + port), preserve path.</p> </li> <li> <p>Prefix-based mapping Replace URI prefixes, preserving remaining path segments.</p> </li> <li> <p>Regex-based mapping Rewrite URIs using regular expression pattern matching (RE2 semantics).</p> </li> </ol> <p>All mapping rules MUST be reversible: for every old URI that maps to a new URI, it MUST be possible to recover the original old URI from the new URI using the same mapping definition. For <code>OriginReplace</code> and <code>PrefixReplace</code>, this is satisfied by swapping from/to values. For <code>RegexReplace</code>, explicit reverse rules are required.</p> <p>Mapping prevents guesswork and avoids publishing global user directories.</p>"},{"location":"fep/a427/#uri-rewriting-scope","title":"URI Rewriting Scope","text":"<p>Mapping rules are used to compute new URIs for actor identifiers originating from the source server. Peers establish aliases between old and new actor URIs but MUST NOT rewrite non-actor object IDs in place (see Applying the Migration Locally).</p> <p>Non-actor URIs (object IDs, collection URLs, activity IDs, media URLs) are resolved via HTTP redirects served by the source domain, not by rewriting stored values.</p>"},{"location":"fep/a427/#conformance","title":"Conformance","text":"<p>The key words MUST, SHOULD, MAY, etc. are to be interpreted as described in RFC 2119.</p>"},{"location":"fep/a427/#uri-normalization","title":"URI Normalization","text":"<p>This specification uses origin (scheme + host + port) as defined in RFC 6454 to identify servers. An origin is the tuple <code>(scheme, host, port)</code>.</p> <p>When comparing or matching URIs, implementations MUST apply the following normalization:</p> <ol> <li>Scheme: lowercase (e.g., <code>HTTPS</code> \u2192 <code>https</code>).</li> <li>Host: lowercase, converted to ASCII via punycode for internationalized domain names (IDN) per RFC 5891.</li> <li>Port: the default port for the scheme MUST be omitted. For <code>https</code>, port <code>443</code> is default and MUST NOT appear explicitly. <code>https://example.com:443/</code> and <code>https://example.com/</code> are the same origin.</li> <li>Path: preserved exactly as-is. No normalization of path segments, percent-encoding, or trailing slashes (except that the empty path is equivalent to <code>/</code>).</li> </ol> <p>Two URIs are same-origin if and only if their normalized origins are identical.</p> <p>The <code>fromOrigin</code> and <code>toOrigin</code> values in <code>OriginReplace</code> mappings MUST be normalized origins (e.g., <code>https://sunset.social</code>, not <code>https://Sunset.Social:443</code>).</p>"},{"location":"fep/a427/#high-level-flow","title":"High-Level Flow","text":"<pre><code>sequenceDiagram\nautonumber\nparticipant sunset as sunset.social\nparticipant sunsetActor as sunset.social server actor\nparticipant dawn as dawn.network\nparticipant dawnActor as dawn.network server actor\nparticipant Forest as forest.instance\nparticipant ForestActor as forest.instance server actor\n\nsunset-&gt;&gt;sunsetActor: Create ServerMigration manifest (M)\ndawn-&gt;&gt;dawnActor: Create ServerMigrationAcceptance (A)\n\nsunsetActor-&gt;&gt;Forest: Discover server actor via WebFinger (FEP-d556)\nForest--&gt;&gt;sunsetActor: Returns ForestActor\n\nsunsetActor-&gt;&gt;ForestActor: POST ServerMove {object=M}\nForestActor-&gt;&gt;sunset: GET M\nForestActor-&gt;&gt;dawn: GET A\nForestActor-&gt;&gt;ForestActor: Verify proofs, actor match, cross-references\nForestActor-&gt;&gt;ForestActor: Establish actor aliases (old \u2192 new URIs)\nForestActor-&gt;&gt;dawn: Fetch new actor objects\nForestActor-&gt;&gt;ForestActor: Verify alsoKnownAs linkage\nForestActor-&gt;&gt;ForestActor: Update delivery endpoints, keys, follows\n\nloop Poll until completed or rolledBack\nForestActor-&gt;&gt;sunset: GET M (check state)\nend</code></pre>"},{"location":"fep/a427/#specification","title":"Specification","text":""},{"location":"fep/a427/#1-server-actor-discovery","title":"1. Server Actor Discovery","text":"<p>Server actor discovery MUST follow FEP-d556: Server Actor Discovery, which defines a \"server\" as an origin (scheme + host + port) and specifies how to discover the server-level actor via WebFinger.</p> <p>The source server, destination server, and all peers MUST expose a server-level actor discoverable via FEP-d556.</p> <p>Peers MUST discover and use the server actor inbox for <code>ServerMove</code> delivery.</p>"},{"location":"fep/a427/#1a-webfinger-behavior-during-migration","title":"1a. WebFinger Behavior During Migration","text":"<p>During an active or completed migration, the source server's WebFinger responses MUST reflect the migration state so that peers performing fresh lookups discover the canonical identities.</p> <p>For actor lookups (e.g., <code>?resource=acct:alice@sunset.social</code>):</p> <p>During the <code>active</code> and <code>completed</code> phases, the source server MUST return a WebFinger response that includes an <code>aliases</code> array containing the new actor URI:</p> <pre><code>{\n \"subject\": \"acct:alice@sunset.social\",\n \"aliases\": [\n \"https://sunset.social/users/alice\",\n \"https://dawn.network/users/alice\"\n ],\n \"links\": [\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://dawn.network/users/alice\"\n }\n ]\n}\n</code></pre> <p>The <code>rel=\"self\"</code> link MUST point to the new canonical actor URI on the destination server. This ensures that peers performing a fresh WebFinger lookup are directed to the new identity even if they have not yet processed the <code>ServerMove</code>.</p> <p>For server actor lookups (e.g., <code>?resource=https://sunset.social/</code>):</p> <p>The source server MUST continue to return its own server actor during the <code>active</code> phase (the server actor is needed to verify the manifest proof). After the migration is <code>completed</code>, the server actor WebFinger response SHOULD include an alias pointing to the new server actor.</p>"},{"location":"fep/a427/#2-servermigration-object","title":"2. ServerMigration Object","text":"<p>A <code>ServerMigration</code> object MUST include:</p> Property Type Description <code>@context</code> Array MUST include <code>\"https://www.w3.org/ns/activitystreams\"</code>, <code>\"https://w3id.org/fep/a427\"</code>, and <code>\"https://w3id.org/security/data-integrity/v1\"</code> <code>id</code> URI Stable, dereferenceable URI hosted on the source server <code>type</code> String <code>\"ServerMigration\"</code> <code>source</code> URI Server actor ID of the source server <code>target</code> URI Server actor ID of the destination server <code>mapping</code> Object Mapping rules (see Mapping Rules) <code>state</code> String One of: <code>active</code>, <code>completed</code>, <code>rolledBack</code> <code>published</code> <code>xsd:dateTime</code> When the manifest was first published <code>updated</code> <code>xsd:dateTime</code> When the manifest state last changed (MUST be present when <code>state</code> is not <code>active</code>) <code>acceptance</code> URI Dereferenceable URL of the <code>ServerMigrationAcceptance</code> on the destination server <code>proof</code> Object FEP-8b32 Object Integrity Proof (see Cryptographic Proofs)"},{"location":"fep/a427/#pre-generating-ids","title":"Pre-generating IDs","text":"<p>Because the <code>ServerMigration</code> references its <code>ServerMigrationAcceptance</code> (via <code>acceptance</code>) and the acceptance references the manifest (via <code>migration</code>), both IDs MUST be determined before either object is published. Since both objects are controlled by the same operator, the recommended approach is:</p> <ol> <li>Generate both IDs deterministically (e.g., based on a shared migration identifier such as a date or UUID).</li> <li>Publish the <code>ServerMigration</code> manifest on the source server.</li> <li>Publish the <code>ServerMigrationAcceptance</code> on the destination server.</li> </ol> <p>Example ID scheme:</p> <pre><code>ServerMigration: https://sunset.social/.well-known/server-migration/2026-02-23\nAcceptance: https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\n</code></pre>"},{"location":"fep/a427/#full-example","title":"Full Example","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/a427\",\n \"https://w3id.org/security/data-integrity/v1\"\n ],\n \"id\": \"https://sunset.social/.well-known/server-migration/2026-02-23\",\n \"type\": \"ServerMigration\",\n \"source\": \"https://sunset.social/actor\",\n \"target\": \"https://dawn.network/actor\",\n \"mapping\": {\n \"type\": \"OriginReplace\",\n \"fromOrigin\": \"https://sunset.social\",\n \"toOrigin\": \"https://dawn.network\"\n },\n \"state\": \"active\",\n \"published\": \"2026-02-23T00:00:00Z\",\n \"acceptance\": \"https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\",\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://sunset.social/actor#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"z...\"\n }\n}\n</code></pre>"},{"location":"fep/a427/#3-servermigrationacceptance-object","title":"3. ServerMigrationAcceptance Object","text":"<p>Hosted on the destination server (<code>dawn.network</code>), MUST include:</p> Property Type Description <code>@context</code> Array MUST include <code>\"https://www.w3.org/ns/activitystreams\"</code>, <code>\"https://w3id.org/fep/a427\"</code>, and <code>\"https://w3id.org/security/data-integrity/v1\"</code> <code>id</code> URI Stable, dereferenceable URI hosted on the destination server <code>type</code> String <code>\"ServerMigrationAcceptance\"</code> <code>migration</code> URI Reference to the <code>ServerMigration</code> manifest <code>source</code> URI Server actor ID of the source server (MUST match manifest <code>source</code>) <code>target</code> URI Server actor ID of the destination server (MUST match manifest <code>target</code>) <code>proof</code> Object FEP-8b32 Object Integrity Proof signed by the destination server actor"},{"location":"fep/a427/#full-example_1","title":"Full Example","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/a427\",\n \"https://w3id.org/security/data-integrity/v1\"\n ],\n \"id\": \"https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\",\n \"type\": \"ServerMigrationAcceptance\",\n \"migration\": \"https://sunset.social/.well-known/server-migration/2026-02-23\",\n \"source\": \"https://sunset.social/actor\",\n \"target\": \"https://dawn.network/actor\",\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://dawn.network/actor#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"z...\"\n }\n}\n</code></pre>"},{"location":"fep/a427/#4-servermove-activity","title":"4. ServerMove Activity","text":"<p>A lightweight activity delivered to peers to trigger migration processing.</p> Property Type Description <code>@context</code> Array MUST include <code>\"https://www.w3.org/ns/activitystreams\"</code> and <code>\"https://w3id.org/fep/a427\"</code> <code>type</code> String <code>\"ServerMove\"</code> <code>actor</code> URI Server actor of the source server <code>object</code> URI URL of the <code>ServerMigration</code> manifest <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/a427\"\n ],\n \"type\": \"ServerMove\",\n \"actor\": \"https://sunset.social/actor\",\n \"object\": \"https://sunset.social/.well-known/server-migration/2026-02-23\"\n}\n</code></pre> <p>This MUST be delivered via signed HTTP POST to the peer's server actor inbox.</p>"},{"location":"fep/a427/#5-cryptographic-proofs","title":"5. Cryptographic Proofs","text":"<p>Both <code>ServerMigration</code> and <code>ServerMigrationAcceptance</code> objects MUST include a <code>proof</code> property conforming to FEP-8b32: Object Integrity Proofs.</p> <p>FEP-8b32 proofs are based on the W3C Data Integrity specification and attach a cryptographic signature directly to the object. This ensures authenticity can be verified regardless of how the object was obtained (direct fetch, cache, forwarding).</p>"},{"location":"fep/a427/#requirements","title":"Requirements","text":"<ul> <li>The <code>ServerMigration</code> manifest MUST be signed by the source server actor's key.</li> <li>The <code>ServerMigrationAcceptance</code> MUST be signed by the destination server actor's key.</li> <li>Implementations SHOULD support the <code>eddsa-jcs-2022</code> cryptosuite. Note that this cryptosuite is not yet finalized; implementations SHOULD be prepared to support additional cryptosuites as they stabilize and SHOULD NOT reject objects solely because they use a different supported cryptosuite.</li> <li>Peers MUST verify both proofs before applying any mapping.</li> </ul>"},{"location":"fep/a427/#verification-method-resolution","title":"Verification Method Resolution","text":"<p>Per FEP-8b32, the <code>verificationMethod</code> in each proof references a key in a controlled identifier document (typically the actor object). Peers MUST:</p> <ol> <li>Fetch the server actor object identified by <code>source</code> (or <code>target</code> for the acceptance).</li> <li>Verify that the <code>verificationMethod</code> URI resolves to a key listed in the actor's <code>assertionMethod</code> property (see also FEP-521a).</li> <li>Verify that the <code>verificationMethod</code> is same-origin with the signed document. Cross-origin verification methods MUST be rejected unless the implementation explicitly supports decentralized identifier (DID) based methods.</li> </ol>"},{"location":"fep/a427/#proof-stability","title":"Proof Stability","text":"<p>The <code>proof</code> property covers the object at a point in time. When the <code>state</code> field changes (e.g., from <code>active</code> to <code>completed</code>), the source server MUST re-sign the manifest with an updated proof. Peers MUST re-verify the proof each time they fetch the manifest during polling.</p> <p>The <code>ServerMove</code> activity itself is authenticated via HTTP Signatures on the POST request, as is standard for ActivityPub server-to-server delivery.</p>"},{"location":"fep/a427/#6-fetch-safety","title":"6. Fetch Safety","text":"<p>Processing a <code>ServerMove</code> requires fetching URLs supplied by a remote party. Without constraints, this creates SSRF (Server-Side Request Forgery) and resource-exhaustion vectors. Implementations MUST enforce the following:</p>"},{"location":"fep/a427/#origin-constraints","title":"Origin Constraints","text":"<ul> <li>All URLs MUST use the <code>https</code> scheme. Peers MUST reject <code>http</code>, <code>file</code>, <code>ftp</code>, or any other scheme.</li> <li>The <code>ServerMigration</code> manifest URL (<code>ServerMove.object</code>) MUST be same-origin with <code>ServerMove.actor</code>. Peers MUST reject the activity if the manifest URL points to a different origin than the actor.</li> <li>The <code>ServerMigrationAcceptance</code> URL (<code>ServerMigration.acceptance</code>) MUST be same-origin with <code>ServerMigration.target</code>. Peers MUST reject the manifest if the acceptance URL points to a different origin than the target server actor.</li> <li>When following HTTP redirects during manifest or acceptance fetches, peers MUST verify that the final resolved URL remains same-origin with the expected origin. Cross-origin redirects MUST be rejected.</li> <li>Peers MUST NOT fetch URLs that resolve to private or reserved IP address ranges (e.g., <code>127.0.0.0/8</code>, <code>10.0.0.0/8</code>, <code>172.16.0.0/12</code>, <code>192.168.0.0/16</code>, <code>169.254.0.0/16</code>, <code>::1</code>, <code>fc00::/7</code>).</li> </ul>"},{"location":"fep/a427/#resource-limits","title":"Resource Limits","text":"<ul> <li>Implementations SHOULD enforce a maximum response body size when fetching migration objects. A limit of 1 MB is RECOMMENDED.</li> <li>Implementations SHOULD enforce a fetch timeout. A timeout of 30 seconds per request is RECOMMENDED.</li> <li>Implementations SHOULD limit the number of HTTP redirects followed to a maximum of 3.</li> </ul>"},{"location":"fep/a427/#receiving-rules","title":"Receiving Rules","text":"<p>Upon receiving <code>ServerMove</code>, a peer:</p> <ol> <li>MUST verify the HTTP Signature on the delivery request.</li> <li>MUST fetch the <code>ServerMigration</code> manifest from the URL in <code>object</code>.</li> <li>MUST verify that <code>ServerMove.actor</code> equals <code>ServerMigration.source</code>. If they differ, the peer MUST reject the migration.</li> <li>MUST verify the manifest's FEP-8b32 proof by resolving the <code>verificationMethod</code> from the source server actor's <code>assertionMethod</code> (see Cryptographic Proofs).</li> <li>MUST fetch the <code>ServerMigrationAcceptance</code> from the URL in <code>acceptance</code>.</li> <li>MUST verify the acceptance's FEP-8b32 proof by resolving the <code>verificationMethod</code> from the destination server actor's <code>assertionMethod</code>.</li> <li>MUST verify that <code>acceptance.migration</code> references the manifest's <code>id</code>.</li> <li>MUST verify that <code>source</code> and <code>target</code> are consistent across both objects.</li> <li>MUST apply the migration locally only if all verification succeeds (see Applying the Migration Locally).</li> <li>MUST begin polling the manifest for lifecycle state changes (see Polling).</li> </ol>"},{"location":"fep/a427/#mapping-resolution","title":"Mapping Resolution","text":"<p>Mapping rules define how to derive a new URI from an old URI. Mapping is applied only to actor URIs (see Applying the Migration Locally).</p> <pre><code>flowchart TD\nA[Old actor URI&lt;br/&gt;e.g. https://sunset.social/users/alice]\n --&gt; B{Does URI origin match&lt;br/&gt;source origin?}\n\nB --&gt;|No| G[Leave unchanged]\nB --&gt;|Yes| C{Mapping type?}\n\nC --&gt;|OriginReplace| D[Replace origin&lt;br/&gt;https://sunset.social \u2192 https://dawn.network]\nD --&gt; E[New URI&lt;br/&gt;https://dawn.network/users/alice]\n\nC --&gt;|PrefixReplace| F[Try rules in order&lt;br/&gt;first match wins]\nF --&gt;|Match found| E\nF --&gt;|No match| H[Leave unchanged&lt;br/&gt;Log warning]\n\nC --&gt;|RegexReplace| I[Try regex rules in order&lt;br/&gt;first match wins]\nI --&gt;|Match found| J{Result is valid&lt;br/&gt;HTTPS URI on target?}\nJ --&gt;|Yes| E\nJ --&gt;|No| H\nI --&gt;|No match| H</code></pre> <p>When a <code>PrefixReplace</code> or <code>RegexReplace</code> mapping contains multiple rules, peers MUST evaluate them in the order they appear in the <code>rules</code> array. The first matching rule wins. If no rule matches a URI whose origin matches the source server, the URI is left unchanged and the peer SHOULD log a warning for operator review.</p> <p>Peers MUST treat migration as idempotent per manifest <code>id</code>.</p>"},{"location":"fep/a427/#applying-the-migration-locally","title":"Applying the Migration Locally","text":"<p>Peers MUST NOT interpret migration as a naive string-replace across all stored data. A peer's local state includes delivery endpoints, public keys, follow relationships with internal identifiers, moderation decisions, cached objects with provenance information, and historical activities that reference old actor IDs. Blindly rewriting all occurrences would break signature verification on historical objects, corrupt <code>Undo</code>/<code>Accept</code> semantics (which reference the ID at the time the original activity was created), and bypass blocklists and moderation state.</p> <p>Instead, peers MUST apply the following model.</p>"},{"location":"fep/a427/#step-1-establish-actor-aliases","title":"Step 1: Establish Actor Aliases","text":"<p>For each actor whose URI origin matches the source server, compute the new URI using the mapping rules. Register the old URI as an alias of the new URI:</p> <ul> <li>The new URI becomes the canonical identity used for all future outbound delivery and display.</li> <li>The old URI is retained as an alias so that inbound activities, cached objects, and <code>Undo</code> operations referencing the old ID continue to resolve correctly.</li> </ul>"},{"location":"fep/a427/#step-2-fetch-and-verify-new-actors","title":"Step 2: Fetch and Verify New Actors","text":"<p>For each mapped actor that the peer has a local relationship with (follows, followers, cached profiles), the peer MUST:</p> <ol> <li>Fetch the new actor object from the new URI.</li> <li>Verify linkage: the new actor SHOULD include an <code>alsoKnownAs</code> property (as an array) containing the old actor URI. If present, this confirms the migration from the actor's perspective.</li> <li>Update locally-stored delivery metadata: <code>inbox</code>, <code>sharedInbox</code>, <code>endpoints</code>, and <code>publicKey</code> MUST be replaced with the values from the fetched new actor object.</li> </ol> <p>If the new actor cannot be fetched (network error, 404), the peer SHOULD retain the alias but MUST NOT update delivery metadata. The peer SHOULD retry on subsequent delivery attempts.</p>"},{"location":"fep/a427/#step-3-carry-over-moderation-state","title":"Step 3: Carry Over Moderation State","text":"<p>Block lists, mutes, silences, and other moderation decisions associated with the old actor URI MUST be carried over to the new canonical URI. The old alias MUST also continue to match moderation rules so that references in historical content remain covered.</p>"},{"location":"fep/a427/#step-4-non-actor-uris-objects-collections-media","title":"Step 4: Non-Actor URIs (Objects, Collections, Media)","text":"<p>Peers MUST NOT rewrite non-actor object IDs (notes, articles, activity IDs, collection URLs, media URLs) in place. Object IDs are treated as identifiers in the ActivityPub data model, not merely as addresses. Rewriting them in place would create:</p> <ul> <li>Duplicate objects (old ID and new ID both exist in the database).</li> <li>Broken thread links (<code>inReplyTo</code> chains referencing the old ID would split).</li> <li>Mismatched deletes/undos (<code>Delete</code> activities reference the original ID).</li> <li>Signature mismatches if the <code>id</code> in the stored payload differs from the database key.</li> </ul> <p>Instead, non-actor URIs from the source server continue to be stored under their original IDs. When the old domain serves HTTP 301 redirects (see Old Domain Lifetime), peers that need to re-fetch these objects will be transparently redirected to the new location.</p> <p>Peers MAY maintain an internal equivalence index (old URI \u2192 new URI) for non-actor URIs to optimize lookups, but the stored canonical ID MUST remain the original.</p>"},{"location":"fep/a427/#step-5-transfer-follow-relationships","title":"Step 5: Transfer Follow Relationships","text":"<p>All existing follow relationships involving actors from the source server MUST be automatically transferred to the new canonical URIs:</p> <ul> <li>If <code>bob@forest.instance</code> follows <code>alice@sunset.social</code>, after migration <code>bob</code> follows <code>alice@dawn.network</code>.</li> <li>If <code>alice@sunset.social</code> follows <code>carol@forest.instance</code>, after migration <code>alice@dawn.network</code> follows <code>carol</code>.</li> </ul> <p>This is an automatic, server-side operation. Individual follow approval policies on the receiving server are not re-triggered \u2014 the follow relationship is preserved, not re-established.</p>"},{"location":"fep/a427/#source-server-requirements","title":"Source Server Requirements","text":"<p>To support the fetch-and-verify step, the source server (or destination server, since they are operated by the same party) SHOULD ensure that each migrated actor on the new domain includes an <code>alsoKnownAs</code> property referencing the old URI as an array:</p> <pre><code>{\n \"id\": \"https://dawn.network/users/alice\",\n \"type\": \"Person\",\n \"alsoKnownAs\": [\"https://sunset.social/users/alice\"],\n \"inbox\": \"https://dawn.network/users/alice/inbox\",\n \"publicKey\": { \"...\" }\n}\n</code></pre> <p>This provides per-actor verification that complements the server-level manifest proofs.</p>"},{"location":"fep/a427/#actor-opt-out","title":"Actor Opt-Out","text":"<p>Individual actors cannot opt out of a server migration. Domain migration is an operational decision made by the server operator and applies to all actors hosted on that server. This is analogous to other server-wide operational changes (TLS certificate rotation, IP address changes) that do not require per-actor consent.</p> <p>Actors who disagree with the migration may independently move to a different server using per-actor <code>Move</code> after the migration completes.</p>"},{"location":"fep/a427/#polling","title":"Polling","text":"<p>After applying a migration, peers MUST periodically poll the <code>ServerMigration</code> manifest to track lifecycle state changes.</p>"},{"location":"fep/a427/#polling-strategy","title":"Polling Strategy","text":"<p>Peers SHOULD use the following polling schedule:</p> Time Since Migration Applied Poll Interval First 24 hours Every 1 hour Days 2\u20137 Every 6 hours Days 8\u201330 Every 24 hours After 30 days Every 7 days <p>Peers MUST continue polling until the manifest state changes to <code>completed</code> or <code>rolledBack</code>.</p>"},{"location":"fep/a427/#on-state-change-to-completed","title":"On State Change to <code>completed</code>","text":"<p>The peer marks the migration as finalized. No further polling is needed for this manifest.</p>"},{"location":"fep/a427/#on-state-change-to-rolledback","title":"On State Change to <code>rolledBack</code>","text":"<p>The peer MUST reverse the migration by restoring old actor aliases as canonical and demoting the new URIs. Since actors were migrated using the alias model (Step 1), the old URI is still stored. No mapping rule inversion is needed for actor URIs.</p> <p>The peer MUST re-fetch actor objects from the original (source) server to restore delivery metadata (inbox, keys).</p> <p>Since non-actor URIs were never rewritten (Step 4), no reversal is needed for object IDs, collection URLs, or media URLs.</p> <p>However, if an individual actor performed a per-actor <code>Move</code> activity during the <code>active</code> phase (i.e., the actor independently moved to a different server while the server-wide migration was in progress), that per-actor <code>Move</code> takes precedence. The server-wide rollback MUST NOT override an actor's intentional individual move. Peers SHOULD check whether each actor has a more recent per-actor <code>Move</code> on record before reversing the alias.</p> <p>After processing a rollback, the peer MUST mark this migration ID as permanently invalid. If a <code>ServerMove</code> referencing this same manifest <code>id</code> is received again in the future, the peer MUST reject it.</p>"},{"location":"fep/a427/#on-fetch-failure","title":"On Fetch Failure","text":"<p>If a poll request fails (network error, HTTP 5xx), the peer SHOULD retry using exponential backoff starting at 1 hour, capped at 24 hours.</p> <p>If the manifest becomes permanently unreachable (HTTP 404 or 410 for 7 consecutive days), the peer SHOULD stop polling. However, the peer MUST NOT infer <code>completed</code> from unreachability alone. The migration remains in whatever state was last observed and verified. The peer SHOULD log a warning so operators can investigate.</p>"},{"location":"fep/a427/#lifecycle","title":"Lifecycle","text":"<pre><code>stateDiagram-v2\n[*] --&gt; Active\nActive --&gt; Completed\nActive --&gt; RolledBack</code></pre> <p><code>RolledBack</code> and <code>Completed</code> are terminal states. A rolled-back migration cannot be reactivated. If the operator wishes to attempt the migration again, a new <code>ServerMigration</code> manifest with a new <code>id</code> MUST be created.</p>"},{"location":"fep/a427/#grace-period","title":"Grace Period","text":"<p>Once a migration reaches the <code>completed</code> state, it MUST NOT be changed to <code>rolledBack</code>. Rollback is only available from the <code>active</code> state. This prevents long-term state instability where peers that already finalized the migration would need to reverse months-old changes.</p> <p>Operators who need to reverse a completed migration MUST treat it as a new migration in the opposite direction (from <code>dawn.network</code> back to <code>sunset.social</code>), using a new manifest.</p>"},{"location":"fep/a427/#per-actor-move-precedence","title":"Per-Actor Move Precedence","text":"<p>Per-actor <code>Move</code> activities always take precedence over server-wide migration state. If an individual actor performed a <code>Move</code> to a third-party server while the server-wide migration was <code>active</code>, that actor's individual move MUST NOT be overridden by a subsequent server-wide rollback. Peers SHOULD track per-actor moves independently of server-wide migrations.</p> <p>Peers MUST:</p> <ul> <li>Respect <code>rolledBack</code> by restoring actor aliases (except where per-actor <code>Move</code> takes precedence).</li> <li>Avoid duplicate processing (track applied migration IDs).</li> <li>Permanently reject reuse of a rolled-back manifest ID.</li> </ul>"},{"location":"fep/a427/#migration-history","title":"Migration History","text":"<p>Servers MUST retain all <code>ServerMigration</code> manifests indefinitely, regardless of their terminal state. This allows peers that were offline during the migration window to discover and process migrations after the fact.</p> <p>Both the source and destination servers SHOULD serve the manifest at its original URL. The destination server SHOULD additionally serve a copy of the manifest so that peers can discover it even if the source domain eventually becomes unavailable.</p>"},{"location":"fep/a427/#conflicting-migrations","title":"Conflicting Migrations","text":"<p>A source server MUST NOT have more than one migration in the <code>active</code> state at any time. If a peer receives a <code>ServerMove</code> for a source server that already has an active migration with a different manifest <code>id</code>, the peer MUST reject the new migration and SHOULD log a warning.</p> <p>Once a migration reaches a terminal state (<code>completed</code> or <code>rolledBack</code>), a new migration from the same source server is permitted.</p>"},{"location":"fep/a427/#chained-migrations","title":"Chained Migrations","text":"<p>If a server migrates from A to B (completed), and later from B to C, peers that processed the first migration will encounter the second migration naturally. Peers that missed the first migration and later discover A\u2192B (completed) and B\u2192C (active or completed) SHOULD apply both in sequence.</p>"},{"location":"fep/a427/#security-considerations","title":"Security Considerations","text":"<p>Migration requires two-party agreement.</p> <pre><code>sequenceDiagram\nparticipant Evil as evil.example\nparticipant Forest as forest.instance\n\nEvil-&gt;&gt;Forest: ServerMove sunset.social-&gt;evil.example\nForest-&gt;&gt;sunset.social: GET Manifest\nForest-&gt;&gt;Forest: Verify proof \u2014 signed by sunset.social? No.\nForest-&gt;&gt;Forest: Reject migration</code></pre> <p>A migration is valid only if:</p> <ul> <li>The <code>ServerMigration</code> manifest is signed by <code>sunset.social</code>'s server actor key (FEP-8b32 proof).</li> <li>The <code>ServerMigrationAcceptance</code> is signed by <code>dawn.network</code>'s server actor key (FEP-8b32 proof).</li> <li>Both objects reference each other.</li> <li><code>ServerMove.actor</code> matches <code>ServerMigration.source</code>.</li> </ul> <p>Without both proofs, identity hijacking is prevented.</p>"},{"location":"fep/a427/#ssrf-and-resource-exhaustion","title":"SSRF and Resource Exhaustion","text":"<p>Because peers fetch URLs supplied by remote parties, implementations MUST enforce the origin constraints and resource limits defined in Fetch Safety. In particular:</p> <ul> <li>Same-origin checks prevent an attacker from directing peers to fetch internal network resources (<code>http://127.0.0.1</code>, <code>http://169.254.169.254</code>, internal admin panels).</li> <li>HTTPS-only enforcement prevents downgrade attacks and plaintext interception.</li> <li>Body size and timeout limits prevent tarpit-style denial of service (e.g., a server that streams an infinite response).</li> </ul>"},{"location":"fep/a427/#domain-takeover-after-migration","title":"Domain Takeover After Migration","text":"<p>If the source domain (<code>sunset.social</code>) expires after migration and is re-registered by a different party, the new registrant could stand up a server actor with new keys and attempt to publish a fraudulent <code>ServerMigration</code> manifest.</p> <p>To mitigate this risk:</p> <ul> <li>Peers SHOULD only accept a <code>ServerMove</code> if they have previously cached the source server actor's public key (key continuity). A <code>ServerMove</code> from an origin whose server actor key has never been seen before SHOULD be treated as suspicious and MAY be rejected or flagged for manual review.</li> <li>Peers SHOULD reject migrations where the manifest's <code>published</code> timestamp is significantly later than the last known activity from that origin (e.g., more than 90 days after the last seen interaction). This heuristic helps detect stale-domain takeover attempts.</li> <li>Operators SHOULD maintain control of the old domain for as long as feasible. Allowing a domain to expire after migration creates a permanent risk.</li> <li>For stronger key continuity guarantees, server actors MAY use decentralized identifier (DID) based <code>verificationMethod</code> values, which are not tied to domain ownership. This is compatible with FEP-8b32's controlled identifier model but is not required by this specification.</li> </ul>"},{"location":"fep/a427/#privacy-considerations","title":"Privacy Considerations","text":"<ul> <li>Rule-based mapping avoids publishing user lists.</li> <li>Explicit actor lists are not used in this protocol.</li> <li>Follow relationships are automatically transferred; no re-follow is required, and no follow requests are publicly visible during migration.</li> </ul>"},{"location":"fep/a427/#fallback","title":"Fallback","text":"<p>If <code>forest.instance</code> does not support this FEP:</p> <ul> <li><code>sunset.social</code> SHOULD emit per-actor <code>Move</code> activities for each actor.</li> <li><code>sunset.social</code> SHOULD keep old domain operational.</li> <li>Old actor URLs SHOULD continue resolving (via redirects or proxying).</li> </ul>"},{"location":"fep/a427/#old-domain-lifetime","title":"Old Domain Lifetime","text":"<p>This FEP does not mandate a specific duration for maintaining the old domain. However, the following recommendations apply:</p> <ul> <li>The source server SHOULD keep the old domain operational for at least 90 days after marking the migration as <code>completed</code>.</li> <li>During this period, the old domain SHOULD serve HTTP 301 redirects to corresponding new-domain URIs for all actor and object URLs.</li> <li>The <code>ServerMigration</code> manifest SHOULD remain fetchable on the old domain indefinitely (or as long as feasible).</li> <li>The destination server SHOULD also serve a copy of the manifest to ensure discoverability if the old domain eventually goes offline.</li> </ul> <p>Operators should be aware that some peers may be offline for extended periods. The longer the old domain remains operational, the more peers will successfully process the migration.</p>"},{"location":"fep/a427/#old-uri-resolution-tombstones-vs-redirects","title":"Old URI Resolution (Tombstones vs Redirects)","text":"<p>When the old domain is still operational, old URIs SHOULD be handled as HTTP 301 (Moved Permanently) redirects to the corresponding new URIs. This is preferred over returning <code>Tombstone</code> objects because:</p> <ul> <li>301 redirects work transparently with HTTP clients that do not understand ActivityPub semantics.</li> <li>They support the fallback scenario where peers have not processed the <code>ServerMove</code>.</li> <li>They enable gradual, passive migration for peers that discover the new location through normal fetching.</li> <li>Since peers do not rewrite non-actor object IDs (Step 4), 301 redirects are the primary mechanism for resolving old object URIs to new locations.</li> </ul> <p><code>Tombstone</code> objects SHOULD NOT be used for migrated resources because they signal deletion, which may cause peers to remove cached content rather than update references.</p> <p>The source server SHOULD return the manifest itself (not a redirect) at its <code>.well-known</code> URL, so that peers can always fetch it for verification.</p>"},{"location":"fep/a427/#deployment-phases","title":"Deployment Phases","text":"<pre><code>gantt\ntitle sunset.social \u2192 dawn.network Migration\ndateFormat YYYY-MM-DD\n\nsection Prepare\nStand up dawn.network :a1, 2026-02-23, 3d\nGenerate manifest and acceptance IDs :a1b, after a1, 1d\nPublish manifests on both servers :a2, after a1b, 1d\n\nsection Notify\nDiscover peers :b1, after a2, 3d\nSend ServerMove waves :b2, after b1, 7d\n\nsection Fallback\nPer-actor Move :c1, after b1, 14d\nMaintain old domain :c2, 2026-02-23, 90d\n\nsection Complete\nMark completed :d1, after b2, 1d</code></pre>"},{"location":"fep/a427/#mapping-rules","title":"Mapping Rules","text":"<p>Scenario used in examples:</p> <ul> <li>Source server: <code>sunset.social</code> (origin: <code>https://sunset.social</code>)</li> <li>Destination server: <code>dawn.network</code> (origin: <code>https://dawn.network</code>)</li> </ul>"},{"location":"fep/a427/#1-simple-origin-based-mapping","title":"1. Simple Origin-Based Mapping","text":""},{"location":"fep/a427/#use-case","title":"Use Case","text":"<p>URL structure remains identical. Only the origin (scheme + host) changes.</p>"},{"location":"fep/a427/#rule-definition-conceptual","title":"Rule Definition (Conceptual)","text":"<pre><code>If URI origin equals \"https://sunset.social\"\nReplace origin with \"https://dawn.network\"\nPreserve path\n</code></pre>"},{"location":"fep/a427/#example-transformations","title":"Example Transformations","text":"<pre><code>https://sunset.social/users/alice\n\u2192 https://dawn.network/users/alice\n\nhttps://sunset.social/@bob\n\u2192 https://dawn.network/@bob\n\nhttps://sunset.social/groups/astronomy\n\u2192 https://dawn.network/groups/astronomy\n\nhttps://sunset.social/notes/12345\n\u2192 https://dawn.network/notes/12345\n</code></pre>"},{"location":"fep/a427/#manifest-example","title":"Manifest Example","text":"<pre><code>{\n \"type\": \"ServerMigration\",\n \"mapping\": {\n \"type\": \"OriginReplace\",\n \"fromOrigin\": \"https://sunset.social\",\n \"toOrigin\": \"https://dawn.network\"\n }\n}\n</code></pre>"},{"location":"fep/a427/#2-prefix-based-mapping-path-changes","title":"2. Prefix-Based Mapping (Path Changes)","text":""},{"location":"fep/a427/#use-case_1","title":"Use Case","text":"<p>Old server used <code>/users/</code> New server uses <code>/profile/</code></p>"},{"location":"fep/a427/#transformations","title":"Transformations","text":"<pre><code>https://sunset.social/users/alice\n\u2192 https://dawn.network/profile/alice\n</code></pre>"},{"location":"fep/a427/#rule-definition","title":"Rule Definition","text":"<pre><code>Match prefix: https://sunset.social/users/\nRewrite to: https://dawn.network/profile/\nAppend remaining path segment\n</code></pre>"},{"location":"fep/a427/#manifest-example_1","title":"Manifest Example","text":"<pre><code>{\n \"mapping\": {\n \"type\": \"PrefixReplace\",\n \"rules\": [\n {\n \"fromPrefix\": \"https://sunset.social/users/\",\n \"toPrefix\": \"https://dawn.network/profile/\"\n }\n ]\n }\n}\n</code></pre> <p>Deterministic. Still does not expose actor lists.</p>"},{"location":"fep/a427/#3-multiple-actor-types-users-groups","title":"3. Multiple Actor Types (Users + Groups)","text":""},{"location":"fep/a427/#use-case_2","title":"Use Case","text":"<p>Users and groups have different routes.</p> <pre><code>https://sunset.social/users/alice\n\u2192 https://dawn.network/u/alice\n\nhttps://sunset.social/groups/astronomy\n\u2192 https://dawn.network/g/astronomy\n</code></pre>"},{"location":"fep/a427/#manifest-example_2","title":"Manifest Example","text":"<pre><code>{\n \"mapping\": {\n \"type\": \"PrefixReplace\",\n \"rules\": [\n {\n \"fromPrefix\": \"https://sunset.social/users/\",\n \"toPrefix\": \"https://dawn.network/u/\"\n },\n {\n \"fromPrefix\": \"https://sunset.social/groups/\",\n \"toPrefix\": \"https://dawn.network/g/\"\n }\n ]\n }\n}\n</code></pre> <p>Rules are evaluated in order. The first matching rule is applied.</p>"},{"location":"fep/a427/#4-catch-all-with-prefix-rules","title":"4. Catch-All with Prefix Rules","text":"<p>When using <code>PrefixReplace</code>, it is RECOMMENDED to include a catch-all rule as the last entry to handle URIs that don't match specific prefix patterns:</p> <pre><code>{\n \"mapping\": {\n \"type\": \"PrefixReplace\",\n \"rules\": [\n {\n \"fromPrefix\": \"https://sunset.social/users/\",\n \"toPrefix\": \"https://dawn.network/u/\"\n },\n {\n \"fromPrefix\": \"https://sunset.social/groups/\",\n \"toPrefix\": \"https://dawn.network/g/\"\n },\n {\n \"fromPrefix\": \"https://sunset.social/\",\n \"toPrefix\": \"https://dawn.network/\"\n }\n ]\n }\n}\n</code></pre>"},{"location":"fep/a427/#5-regex-based-mapping","title":"5. Regex-Based Mapping","text":""},{"location":"fep/a427/#use-case_3","title":"Use Case","text":"<p>URL structure changed in ways that cannot be expressed with simple prefix substitution:</p> <pre><code>https://sunset.social/@alice\n\u2192 https://dawn.network/users/alice\n</code></pre>"},{"location":"fep/a427/#manifest-example_3","title":"Manifest Example","text":"<pre><code>{\n \"mapping\": {\n \"type\": \"RegexReplace\",\n \"rules\": [\n {\n \"pattern\": \"^https://sunset\\\\.social/@([^/]+)$\",\n \"replacement\": \"https://dawn.network/users/$1\"\n },\n {\n \"pattern\": \"^https://sunset\\\\.social/(.*)$\",\n \"replacement\": \"https://dawn.network/$1\"\n }\n ]\n }\n}\n</code></pre> <p>Rules are evaluated in order. The first matching rule is applied.</p>"},{"location":"fep/a427/#safety-requirements","title":"Safety Requirements","text":"<p>Regex-based mapping is powerful but carries implementation risks. Peers processing <code>RegexReplace</code> mappings:</p> <ul> <li>MUST use a regex engine that guarantees linear-time matching (e.g., RE2 semantics). Backtracking engines are vulnerable to ReDoS (Regular Expression Denial of Service) attacks via crafted patterns.</li> <li>MUST reject patterns that exceed 256 characters in length.</li> <li>MUST enforce a per-match timeout of no more than 10 milliseconds.</li> <li>MUST validate that the replacement result is a valid HTTPS URI with an origin matching the <code>target</code> server. Any result that does not match MUST be discarded and the URI left unchanged.</li> <li>SHOULD log a warning if a pattern fails to compile or produces an invalid result.</li> </ul> <p>Implementations that cannot guarantee linear-time regex matching SHOULD reject <code>RegexReplace</code> mappings and treat the <code>ServerMove</code> as unsupported, falling back to per-actor <code>Move</code> processing.</p>"},{"location":"fep/a427/#vocabulary","title":"Vocabulary","text":"<p>This FEP defines the following terms under the namespace <code>https://w3id.org/fep/a427#</code>:</p>"},{"location":"fep/a427/#types","title":"Types","text":"Term URI Description <code>ServerMigration</code> <code>https://w3id.org/fep/a427#ServerMigration</code> A durable manifest describing a server domain migration <code>ServerMigrationAcceptance</code> <code>https://w3id.org/fep/a427#ServerMigrationAcceptance</code> Confirmation from the destination server accepting the migration <code>ServerMove</code> <code>https://w3id.org/fep/a427#ServerMove</code> Activity notifying peers to process a migration <code>OriginReplace</code> <code>https://w3id.org/fep/a427#OriginReplace</code> Mapping type: replace URI origin, preserve path <code>PrefixReplace</code> <code>https://w3id.org/fep/a427#PrefixReplace</code> Mapping type: replace URI prefixes <code>RegexReplace</code> <code>https://w3id.org/fep/a427#RegexReplace</code> Mapping type: regex-based URI rewriting (RE2 semantics required)"},{"location":"fep/a427/#properties","title":"Properties","text":"Term URI Domain Range Description <code>source</code> <code>https://w3id.org/fep/a427#source</code> <code>ServerMigration</code>, <code>ServerMigrationAcceptance</code> URI Server actor of the source server <code>target</code> <code>https://w3id.org/fep/a427#target</code> <code>ServerMigration</code>, <code>ServerMigrationAcceptance</code> URI Server actor of the destination server <code>mapping</code> <code>https://w3id.org/fep/a427#mapping</code> <code>ServerMigration</code> Object Mapping rules for URI rewriting <code>state</code> <code>https://w3id.org/fep/a427#state</code> <code>ServerMigration</code> String Lifecycle state: <code>active</code>, <code>completed</code>, <code>rolledBack</code> <code>acceptance</code> <code>https://w3id.org/fep/a427#acceptance</code> <code>ServerMigration</code> URI Reference to the <code>ServerMigrationAcceptance</code> <code>migration</code> <code>https://w3id.org/fep/a427#migration</code> <code>ServerMigrationAcceptance</code> URI Reference to the <code>ServerMigration</code> manifest <code>fromOrigin</code> <code>https://w3id.org/fep/a427#fromOrigin</code> <code>OriginReplace</code> String Normalized source origin (scheme + host + port) <code>toOrigin</code> <code>https://w3id.org/fep/a427#toOrigin</code> <code>OriginReplace</code> String Normalized destination origin <code>rules</code> <code>https://w3id.org/fep/a427#rules</code> <code>PrefixReplace</code>, <code>RegexReplace</code> Array Ordered list of replacement rules <code>fromPrefix</code> <code>https://w3id.org/fep/a427#fromPrefix</code> Prefix rule object String URI prefix to match <code>toPrefix</code> <code>https://w3id.org/fep/a427#toPrefix</code> Prefix rule object String Replacement URI prefix <code>pattern</code> <code>https://w3id.org/fep/a427#pattern</code> Regex rule object String RE2-compatible regex pattern <code>replacement</code> <code>https://w3id.org/fep/a427#replacement</code> Regex rule object String Replacement string with capture group references <p>Note: This specification uses <code>published</code> and <code>updated</code> from the ActivityStreams 2.0 vocabulary (<code>https://www.w3.org/ns/activitystreams#published</code>, <code>https://www.w3.org/ns/activitystreams#updated</code>) rather than defining custom date-time properties.</p>"},{"location":"fep/a427/#json-ld-context","title":"JSON-LD Context","text":"<p>The associated context document at <code>https://w3id.org/fep/a427</code> defines the term mappings:</p> <pre><code>{\n \"@context\": {\n \"fep-a427\": \"https://w3id.org/fep/a427#\",\n \"ServerMigration\": \"fep-a427:ServerMigration\",\n \"ServerMigrationAcceptance\": \"fep-a427:ServerMigrationAcceptance\",\n \"ServerMove\": \"fep-a427:ServerMove\",\n \"OriginReplace\": \"fep-a427:OriginReplace\",\n \"PrefixReplace\": \"fep-a427:PrefixReplace\",\n \"RegexReplace\": \"fep-a427:RegexReplace\",\n \"source\": { \"@id\": \"fep-a427:source\", \"@type\": \"@id\" },\n \"target\": { \"@id\": \"fep-a427:target\", \"@type\": \"@id\" },\n \"mapping\": \"fep-a427:mapping\",\n \"state\": \"fep-a427:state\",\n \"acceptance\": { \"@id\": \"fep-a427:acceptance\", \"@type\": \"@id\" },\n \"migration\": { \"@id\": \"fep-a427:migration\", \"@type\": \"@id\" },\n \"fromOrigin\": \"fep-a427:fromOrigin\",\n \"toOrigin\": \"fep-a427:toOrigin\",\n \"rules\": { \"@id\": \"fep-a427:rules\", \"@container\": \"@list\" },\n \"fromPrefix\": \"fep-a427:fromPrefix\",\n \"toPrefix\": \"fep-a427:toPrefix\",\n \"pattern\": \"fep-a427:pattern\",\n \"replacement\": \"fep-a427:replacement\"\n }\n}\n</code></pre>"},{"location":"fep/a427/#relationship-to-existing-work","title":"Relationship to Existing Work","text":"<p>This FEP:</p> <ul> <li>Complements per-actor <code>Move</code> (used as fallback for non-supporting peers).</li> <li>Relies on FEP-8b32 for Object Integrity Proofs.</li> <li>Relies on FEP-d556 for server actor discovery.</li> <li>Is compatible with FEP-521a for verification method resolution.</li> <li>Does not replace WebFinger.</li> <li>Does not define content portability.</li> <li>Provides a scalable alternative to N-per-actor migration.</li> </ul>"},{"location":"fep/a427/#final-notes","title":"Final Notes","text":"<p>Server-wide domain migration is inherently messy because identifiers are authority-bound.</p> <p>This FEP does not attempt to make migration perfect.</p> <p>It attempts to make it:</p> <ul> <li>Verifiable</li> <li>Deterministic</li> <li>Idempotent</li> <li>Resistant to identity hijacking</li> <li>Operationally feasible at scale.</li> </ul>"},{"location":"fep/a427/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/a4ed/","title":"FEP-a4ed: The Fediverse Enhancement Proposal Process","text":"Authors Status Date received Date final Tracking issue Discussions Repository pukkamustard pukkamustard@posteo.net <code>FINAL</code> 2020-10-16 2021-01-18 #201 - codeberg"},{"location":"fep/a4ed/#summary","title":"Summary","text":"<p>A Fediverse Enhancement Proposal (FEP) is a document that provides information to the Fediverse community. The goal of a FEP is to improve interoperability and well-being of diverse services, applications and communities that form the Fediverse.</p> <p>This document describes the scope, format and process of publishing Fediverse Enhancement Proposals.</p>"},{"location":"fep/a4ed/#scope-and-objectives","title":"Scope and Objectives","text":"<p>A Fediverse Enhancement Proposal (FEP) should be a concise and focused documentation of a specific topic that is of interest to the Fediverse community.</p> <p>A proposal should always have the intention of improving the interoperability and well-being of diverse services, applications and community of the Fediverse.</p> <p>The Fediverse includes applications, services and communities using the ActivityPub protocol and other protocols that foster decentralized and diverse social media and culture.</p> <p>Proposals may include descriptions of technical protocols and mechanisms, documentation of experimental work or current best practices.</p> <p>Proposals are not limited to technical topics and may focus on social and cultural aspects.</p> <p>Proposals may be entertaining and humorous (unlike this proposal).</p>"},{"location":"fep/a4ed/#language-document-structure-and-format","title":"Language, Document Structure and Format","text":"<p>All Fediverse Enhancement Proposals must be written in English, be properly formatted as CommonMark and be reasonably grammatical.</p> <p>Authors should use inclusive language and examples and refrain from using oppressive terminology.</p>"},{"location":"fep/a4ed/#proposal-title-and-identifier","title":"Proposal Title and Identifier","text":"<p>Every Fediverse Enhancement Proposal must have a descriptive title.</p> <p>An identifier is computed from the proposal title as the first 4 digits of the sha256 hash (in hex). The identifier can be computed from the title with standard Unix tools:</p> <pre><code>$ echo -n \"The Fediverse Enhancement Proposal Process\" | sha256sum | cut -c-4\na4ed\n</code></pre> <p>By using the hash of the title as identifier we reduce the burden on facilitators to assign unique ids. This requires proposal titles to be unique.</p>"},{"location":"fep/a4ed/#proposal-type","title":"Proposal Type","text":"<p>Fediverse Enhancement Proposals are classified into following types:</p> <ul> <li>Informational documents</li> <li>Implementation proposals</li> </ul> <p>The type is chosen by the author. If the type is not specified, the proposal is assumed to be informational.</p>"},{"location":"fep/a4ed/#metadata","title":"Metadata","text":"<p>Proposal metadata is placed at the top of the document as key-value pairs between opening and closing ~---~.</p> <p>Following metadata key-value pairs may be placed at the top of a proposal:</p> <ul> <li><code>type</code>: Indicates the type of the proposal. Can be either <code>informational</code> or <code>implementation</code>.</li> <li><code>slug</code>: the proposal identifier. The value must be enclosed within double quote marks, e.g. <code>\"a4ed\"</code>.</li> <li><code>authors</code>: A comma separated list of authors of the proposal. Each item of the list should contain an author's name followed by a contact address in angle brackets (e.g. <code>Alyssa P. Hacker &lt;alyssa.p.hacker@email.example&gt;</code>, <code>Alyssa P. Hacker &lt;@alyssa.p.hacker@fediverse.example&gt;</code>).</li> <li><code>status</code>: Indicates the proposal status. Can be either <code>DRAFT</code>, <code>WITHDRAWN</code> or <code>FINAL</code>.</li> <li><code>dateReceived</code>: Date of when the proposal was added to the repository (when status is set to <code>DRAFT</code>).</li> <li><code>dateWithdrawn</code>: Date of when the proposal status was set to <code>WITHDRAWN</code> (only for proposals with status <code>WITHDRAWN</code>).</li> <li><code>dateFinalized</code>: Date of when the proposal status was set to <code>FINAL</code> (only for proposals with status <code>FINAL</code>).</li> <li><code>trackingIssue</code>: Link to the tracking issue for the proposal.</li> <li><code>discussionsTo</code>: Link to the discussion.</li> <li><code>relatedFeps</code>: A comma separated list of related FEPs (e.g. <code>FEP-a4ed, FEP-141a, FEP-686f</code>).</li> <li><code>replaces</code>: A comma separated list of FEPs that are replaced by the proposal.</li> <li><code>replacedBy</code>: Identifier of a FEP that replaces the proposal.</li> </ul>"},{"location":"fep/a4ed/#required-sections","title":"Required Sections","text":"<p>Every FEP should include at least the following sections:</p> <ul> <li>Summary: A short (no more than 200 words) summary of the proposal.</li> <li>Copyright: Indicating that the proposal has been placed in the public domain.</li> </ul> <p>Following sections may be included in a proposal:</p> <ul> <li>History: An overview of previous related efforts and how they relate to the proposal.</li> <li>Implementations: If applicable an overview of services or applications that implement the proposal at time of submission.</li> <li>References: A list of documents and resources referenced by the proposal.</li> </ul>"},{"location":"fep/a4ed/#copyright","title":"Copyright","text":"<p>Fediverse Enhancement Proposals must be placed in the public domain by the authors with a CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.</p>"},{"location":"fep/a4ed/#the-fediverse-enhancement-proposals-process","title":"The Fediverse Enhancement Proposals Process","text":"<pre><code>stateDiagram-v2\n accTitle: FEP process flowchart\n accDescr {\n The process begins from the \"DRAFT\" status, followed by either of the \"FINAL\" or \"WITHDRAWN\" status.\n The \"WITHDRAWN\" status can go back to the \"DRAFT\" status.\n }\n [*] --&gt; DRAFT\n DRAFT --&gt; FINAL\n DRAFT --&gt; WITHDRAWN\n WITHDRAWN --&gt; DRAFT\n FINAL --&gt; [*]</code></pre> <ol> <li>A Fediverse Enhancement Proposal can be submitted by individuals or groups of individuals (authors). See the <code>SUBMISSION.md</code> file for a list of accepted submission methods.</li> <li>Within seven days one of the facilitators will read and respond to the proposal. The facilitator checks if the proposal conforms to the required structure and fits the scope and objective of the FEPs. The facilitator may request the authors to clarify, justify, or withdraw the proposal. Such a request must not reflect the personal bias of a facilitator. Rather, it will be made strictly to maintain a high quality of submissions. The facilitators reserve the right to reject a submission when a proposal amounts to blatant misuse of the process. The authors may seek feedback from the wider community if the submitted proposal is rejected or clarifications are requested.</li> <li>If a FEP facilitator approves a submission it receives the status <code>DRAFT</code> and is added to the repository. The facilitator also creates a tracking issue for the proposal and adds <code>trackingIssue</code> attribute to the proposal metadata.</li> <li>Registered proposals must have the following metadata attributes: <code>slug</code>, <code>authors</code>, <code>status</code>, <code>dateReceived</code>, <code>trackingIssue</code> and <code>discussionsTo</code>.</li> <li>When the status is changed to <code>FINAL</code> or <code>WITHDRAWN</code>, facilitator adds <code>dateFinalized</code> or <code>dateWithdrawn</code> attributes to the proposal metadata.</li> <li>While a proposal has the status <code>DRAFT</code>:</li> <li>Authors are responsible for initiating community discussion and collecting feedback.</li> <li>Authors may submit updates to the proposal which will be checked in to the repository by a facilitator. Others may also submit updates, but in this case a change must be approved by at least one author.</li> <li>Authors may withdraw the submission upon which a facilitator will set the status of the submission to <code>WITHDRAWN</code>.</li> <li>After at least 60 days the authors may request the proposal to be finalized. This is done by requesting final comments on the proposal.</li> <li>If there are no community objections within 14 days and the authors can show that they have initiated sufficient awareness and discussion of the proposal, a facilitator will set the status of the submission to <code>FINAL</code>.</li> <li>If authors have not requested the proposal to be finalized, and there were no updates for 2 years or longer, a facilitator will set the status of the submission to <code>WITHDRAWN</code>.</li> <li>A proposal with status <code>FINAL</code> can not be changed or updated in a way that would lead to adjustments to implementations. Minor corrections are allowed.</li> <li> <p>Any substantial change to finalized proposal must be submitted as a separate FEP.</p> </li> <li> <p>The new FEP MUST include a <code>replaces</code> metadata attribute pointing to FEPs it supersedes.</p> </li> <li> <p>If the new FEP becomes <code>FINAL</code>, the original one MUST point to it in an added <code>replacedBy</code> metadata attribute.</p> </li> <li> <p>A proposal with status <code>WITHDRAWN</code> remains in the repository and can be resubmitted.</p> </li> <li>After a proposal becomes <code>FINAL</code> a facilitator will archive all discussions linked in the tracking issue and add the resulting archive links to the tracking issue.</li> </ol>"},{"location":"fep/a4ed/#facilitators","title":"Facilitators","text":"<p>A list of facilitators is maintained in the <code>FACILITATORS.md</code> file at the root of the FEP repository.</p>"},{"location":"fep/a4ed/#submission-methods","title":"Submission Methods","text":"<p>A list of methods in which a proposal may be submitted is maintained in the <code>SUBMISSION.md</code> file at the root of the FEP repository.</p>"},{"location":"fep/a4ed/#fep-process-governance","title":"FEP Process Governance","text":"<p>FEP-a4ed (The Fediverse Enhancement Proposal Process) is a living document and can be updated despite having the <code>FINAL</code> status.</p> <p>Anyone can propose a change to FEP-a4ed (or other documents related to the FEP process) using any method listed in <code>SUBMISSION.md</code> file. The change must be approved by at least two facilitators (one of them could be the submitter). For any part of the FEP process, there must be a sufficient number of facilitators who agreed to do the maintenance work. Changes should not be accepted before at least 1 month passes after the last update, to give facilitators and the community time to review them and provide feedback.</p> <p>Minor changes (i.e. a correction of a typo or of a broken link) can be accepted immediately after getting two approvals from facilitators.</p>"},{"location":"fep/a4ed/#history","title":"History","text":"<p>The process and format described in this proposal is influenced by other community driven documentation efforts such as the BitTorrent Enhancement Proposal Process, Scheme Request for Implementation and the IETF RFC Series.</p>"},{"location":"fep/a4ed/#references","title":"References","text":"<ul> <li>Dave Mason, Scheme Request For Implementation - Process</li> <li>David Harrison, The BitTorrent Enhancement Proposal Process, 2008</li> <li>Housley, R., Ed., and L. Daigle, Ed., The RFC Series and RFC Editor, 2020</li> <li>John MacFarlane, CommonMark Spec Version 0.29, 2019</li> <li>Mallory Knodel, Terminology, Power and Oppressive Language</li> </ul>"},{"location":"fep/a4ed/#copyright_1","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/a5c5/","title":"FEP-a5c5: Web Syndication Methods","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com <code>DRAFT</code> 2024-07-15 #368 Discussions codeberg"},{"location":"fep/a5c5/#summary","title":"Summary","text":"<p>This document proposes a standard for web syndication methods across the Fediverse by appending <code>.rss</code> or <code>.atom</code> to object URLs. This will allow users to easily subscribe to feeds of timelines, posts, and other objects. Additionally, this proposal addresses whether syndication methods should be applicable to mirrored profiles across the Fediverse, recommending optional but preferred implementation.</p>"},{"location":"fep/a5c5/#syndication-methods","title":"Syndication Methods","text":""},{"location":"fep/a5c5/#creating-feeds","title":"Creating Feeds","text":"<p>To create a syndication feed, servers must append <code>.rss</code> or <code>.atom</code> to the URL of an object. For example:</p> <ul> <li>For a user timeline: <code>https://example.org/@user.rss</code> or <code>https://example.org/@user.atom</code></li> <li>For a post: <code>https://example.org/posts/12345.rss</code> or <code>https://example.org/posts/12345.atom</code></li> </ul>"},{"location":"fep/a5c5/#mirrored-profiles","title":"Mirrored Profiles","text":"<p>For mirrored profiles, such as <code>https://example-mirror.org/@user@example.org</code>, appending <code>.rss</code> or <code>.atom</code> should be supported, but it is optional. The recommendation is to implement this feature to maintain consistency and user convenience across different instances.</p> <p>Example URLs:</p> <ul> <li>For a mirrored user timeline: <code>https://example-mirror.org/@user@example.org.rss</code> or <code>https://example-mirror.org/@user@example.org.atom</code></li> </ul>"},{"location":"fep/a5c5/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/a974/","title":"FEP-a974: All Actor types should be followable","text":"Authors Status Date received Tracking issue Discussions Repository James Smith james@floppy.org.uk <code>DRAFT</code> 2025-02-05 #487 Discussions codeberg"},{"location":"fep/a974/#summary","title":"Summary","text":"<p>In order to foster interoperability and good semantics, any valid unblocked Actor should be visible and followable on any platform when searched for. The <code>type</code> of the Actor should not matter for initial following, though can be used later as appropriate.</p>"},{"location":"fep/a974/#motivation","title":"Motivation","text":"<p>In order to federate, an ActivityPub service will expose a set of Actors; these are ActivityPub objects that (a) can perform activities and (b) can be followed (see the ActivityPub Specification).</p> <p>Each Actor has a <code>type</code>. The Activity Vocabulary defines five \"core\" types: <code>Person</code>, <code>Group</code>, <code>Service</code>, <code>Organization</code>, and <code>Application</code>. These five types work well for many applications, but not all, and ActivityPub explicitly allows Actors to have any <code>type</code>.</p> <p>In many services, it is desirable to use other object types for actors; either defined Object types or even custom types. For instance:</p> <ul> <li>on a reading-oriented platform, a user may want to follow a specific series of books to see when new items are added. The book series would be semantically represented as an <code>OrderedCollection</code>, rather than a <code>Group</code>.</li> <li>on a music platform, a user may want to follow a playlist; this would also be an <code>OrderedCollection</code>, or could even a custom <code>Playlist</code> type if the platform wanted to communicate specific semantics.</li> </ul> <p>However, some ActivityPub platforms may choose to only show actors of the five core types in search; if a platform wanted to use more semantically-meaningful types for its actors, those actors would be unavailable on such sites, which will inevitably lead to bug reports to the services using the non-core types.</p> <p>While filtering at the Activity/Object type is sensible and inevitable for any ActivityPub platform, filtering at the Actor type level limits the ability of new services to use semantically-correct types as appropriate, and will over time make the type effectively useless, as new services will choose one of the five core types even if inappropriate, in order for their actors to be visible.</p> <p>This issue has been discussed in the community before (see Mastodon issue #22322); this FEP attempts to move that discussion into a single simple statement of compatibility.</p>"},{"location":"fep/a974/#decision","title":"Decision","text":"<p>Compliant ActivityPub services should not filter on Actor <code>type</code> in searches or actor-level activities (Follow, Accept, Undo, Block, etc). Any unblocked Actor should be followable on any service.</p>"},{"location":"fep/a974/#impact","title":"Impact","text":"<p>Services are of course free to filter activities that are delivered later on; this FEP puts no limit on that. The feed for an Actor that posts only <code>Document</code> object activities may appear completely empty to a user of a microblogging platform; it remains the right and privilege of any ActivityPub service to only process the activities it wants to.</p> <p>However, by making all Actor types followable, new services can choose what activities to send where, safe in the knowledge that their actors will at least be visible and that the activities will be received.</p> <p>A practical example: Manyfold allows Fediverse users to follow individual 3D Models (which could have a <code>Document</code> or even <code>3DModel</code> actor type), and when they change, it posts <code>Update</code> activities where those models are the <code>object</code>. However, Manyfold knows that microblog applications don't understand those activities (and nor should they). Therefore, for compatibility, it sends what it terms a \"compabitility note\", a human-readable <code>Create Note</code> activity with the same information as the <code>Update 3DModel</code>, thus allowing microblog users to follow models and get updates wherever convenient. A decision on what to send where could use the compatibility detection proposed in FEP-9fde in future.</p> <p>Potential negative impacts may happen if a service makes extra assumptions about an Actor based on its use of the core types (e.g. the background to Mastodon issue #22322 which talks about different semantics being used for <code>Group</code> actors), and discussion is invited on those impacts.</p>"},{"location":"fep/a974/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>James Smith, Manyfold ActivityPub Documentation, 2025</li> </ul>"},{"location":"fep/a974/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/ae0c/","title":"FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePub","text":"Authors Status Date received Date final Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net <code>FINAL</code> 2024-10-19 2025-03-14 #424 Discussions codeberg"},{"location":"fep/ae0c/#summary","title":"Summary","text":"<p>Relays are important components within the decentralized Fediverse architecture. They act as intermediary servers that facilitate communication between different instances, enabling users on Fediverse platforms to share public content without requiring actor following relationships.</p> <p>These relays benefit small instances by enabling them to effectively participate in the federated social network, both as consumers and producers of Fediverse content. </p> <p>Several styles of relays existing in the Activity Fediverse. This FEP describe two popular styles of relays:</p> <ul> <li>Mastodon-style relays</li> <li>LitePub-style relays</li> </ul> <p>NOTE: This is an informational FEP documenting the current status quo. It uses RFC-2119 requirements keywords only as a convenience. Also, these are not standardized protocols. They will generally not be conformant with the ActivityPub standard although they use some concepts from it.</p>"},{"location":"fep/ae0c/#terminology","title":"Terminology","text":"<p>For the purposes of this document, the following terminology will be used:</p> Term Description relay client actor An actor in a server that is a Relay Server subscriber. May also be referred to as a client actor. relay client server A server hosting one or more Relay Client Actors. May also be referred to as a client server. relay subscription A relationship established between a Relay Client Actor and a Relay Server using an ActivityPub <code>Follow</code> activity. relay server actor An actor in a server that provides relaying of messages between Relay Client Actors. May also be referred to as a relay actor. relay server A server hosting one or more Relay Server Actors. May also be referred to as a relay server or a relay. HTTP Signature HTTP-based signature (Cavage) used to verify message sender and contents. LD Signature JSON-LD signature used to verify messages regardless of transport."},{"location":"fep/ae0c/#mastodon-relay-protocol","title":"Mastodon Relay Protocol","text":"<p>The Mastodon relay protocol relies on LD Signatures to verify relayed messages. This allows Mastodon to verify the relayed message although it is being sent by a different actor (the relay server actor).</p>"},{"location":"fep/ae0c/#relay-client-actor","title":"Relay Client Actor","text":"<p>A Relay Client Actor establishes following relationship with a relay server actor and then processes relayed messages sent to the actor's ActivityPub inbox. The relay client server will add the relay inboxes to the delivery target for content with public visibility.</p>"},{"location":"fep/ae0c/#relay-subscription","title":"Relay Subscription","text":"<p>Mastodon will POST an ActivityPub <code>Follow</code> request to the relay ActivityPub <code>inbox</code> URI. The <code>object</code> of the <code>Follow</code> request MUST be the fully expanded URI of the Public pseudo-collection (<code>https://www.w3.org/ns/activitystreams#Public</code>). The relay then responds to the <code>Follow</code> request with an <code>Accept</code> or <code>Reject</code> activity. The response time for the acknowledgement can be arbitrarily long since the subscription MAY require manual approval. </p> <p>The request MUST be signed using the same HTTP Signature (Cavage) algorithms that Mastodon uses for ActivityPub federation. The relay will fetch the relay client actor document to obtain the actor's public key. For best interoperability, the actor ActivityPub document SHOULD be Mastodon-compatible. For example, <code>preferredUsername</code> SHOULD be provided in addition to all actor fields required by ActivityPub and the actor SHOULD provide a <code>sharedInbox</code> endpoint URL.</p> <p>The relay client actor type SHOULD accurately reflect the actor type. However, note that some relay server implementations constrain the ActivityPub type of a client actor. For example, the relay server implementation might require the client actor to be an <code>Application</code> type and reject other types.</p> <p>Example Follow Request</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/6ae15297\",\n \"type\": \"Follow\",\n \"actor\": \"https://client.example/actor\",\n \"object\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n</code></pre> <p>Example Follow Accept Response</p> <p>The <code>Accept</code> activity MAY respond with the accepted <code>Follow</code> activity URI as the <code>object</code> or it may embed a copy of the original <code>Follow</code> activity. A <code>Reject</code> activity will have a similar structure.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://relay.example/15c0b99f-23d4-4488-ba9d-d0c7bc2876a5\",\n \"type\": \"Accept\",\n \"actor\": \"https://relay.example/actor\",\n \"object\": {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/6ae15297\",\n \"type\": \"Follow\",\n \"actor\": \"https://client.example/actor\",\n \"object\": \"https://www.w3.org/ns/activitystreams#Public\"\n }\n}\n</code></pre>"},{"location":"fep/ae0c/#relay-unsubscription","title":"Relay Unsubscription","text":"<p>To unsubscribe from a relay send an <code>Undo</code> with the original <code>Follow</code> activity (embedded, or the URI) as the <code>object</code>. There is typically no response to the <code>Undo</code>.</p> <p>Example Undo/Follow Request</p> <pre><code>{\n \"@context\": \"https: //www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/3f5ebd6d\",\n \"type\": \"Undo\",\n \"actor\": \"https://client.example/actor\",\n \"published\": \"2024-10-14T14:42:17.650139+00:00\",\n \"object\": \"https://client.example/6ae15297\"\n}\n</code></pre>"},{"location":"fep/ae0c/#publishing-messages-to-a-relay","title":"Publishing Messages to a Relay","text":"<p>To publish an activity to a Mastodon-style relay, the publisher MUST sign the message using the Mastodon-specific LD Signature algorithm. The benefit of using LD Signatures is that the receiving servers can verify the message content without refetching from the client server. This lowers the server load on the client server.</p> <p>The disadvantage is that the LD Signatures are not easy to implement and Mastodon uses an outdated nonstandard form of the algorithm. The Mastodon documentation recommends not supporting LD Signatures for these reasons. Furthermore, the Mastodon documentation does not accurately describe the LD Signature algorithm it implements. For more details, see the additional information about Mastodon LD Signatures in this document.</p> <p>The posted activity MUST be signed with a Mastodon-compatible HTTP Signature.</p> <p>Mastodon will relay the following activity types: <code>Create</code>, <code>Update</code>, <code>Delete</code>, <code>Move</code>. A relay actor MAY only forward those types although Mastodon will accept other relayed activities, such as <code>Announce</code>, without an LD Signature. In the <code>Announce</code> case, it will fetch the announced <code>object</code>.</p>"},{"location":"fep/ae0c/#receiving-messages-from-relays","title":"Receiving Messages from Relays","text":"<p>Relayed messages are posted to the relay client actor's <code>inbox</code>. Relayed messages MUST have an HTTP Signature signed by the Relay Actor.</p> <p>Messages received from Relay Server Actors MAY have an LD Signature. If the HTTP Signature and the LD Signature are both present, the activity <code>actor</code> becomes the effective sender after LD Signature verification.</p> <p>If no LD Signature is present and the received message is an <code>Announce</code> activity, then a relay client MUST ensure the content is legitimate (not spoofed). This may be done by fetching the announced activity from the originating server or using remote content from a local cache. However, if the announced activity is already locally cached, then there would typically be no processing to be done with it since it is already known to the client server.</p> <p>A client server receiving a relayed message MAY also deliver the messages to local recipients based on the ActivityPub audience targeting properties.</p>"},{"location":"fep/ae0c/#relay-server-actor","title":"Relay Server Actor","text":"<p>The following behaviors describe the typical implementation of a Mastodon-style relay server actor.</p>"},{"location":"fep/ae0c/#follow","title":"Follow","text":"<p>Ensure that <code>https://www.w3.org/ns/activitystreams#Public</code> is in the <code>object</code> property. Use the <code>actor</code> as the relay client actor URI and save the information about the subscriber. The relay server MAY decide to deny access based on factors like the signer's domain.</p>"},{"location":"fep/ae0c/#undofollow","title":"Undo/Follow","text":"<p>Verify that the <code>actor</code> is a known relay client and, if so, remove the client actor from the set of relay actor followers.</p>"},{"location":"fep/ae0c/#activity-relaying","title":"Activity Relaying","text":"<p>When a message is received from a client actor, the relay MUST verify the HTTP Signature of the activity and identify the originating actor. If the message is valid, it is then posted (with the relay actor's HTTP Signature) to the inboxes of the relay's followers. No delivery is performed based on the ActivityPub audience targeting properties. The relay MUST not send the relayed message to the originating relay client actor.</p> <p>Typically a message is forwarded unchanged. However, a relay MAY do other processing on the message, such as wrapping a message without an LD Signature using an ActivityPub <code>Announce</code> activity before forwarding it (See pub-relay). Extended behaviors like this are not described in this FEP.</p> <p>A relay actor SHOULD only relay messages from followers. A relay actor SHOULD only relay activities that it hasn't already relayed. Addressing properties, like <code>to</code>, MUST be in a list even for a single URI.</p>"},{"location":"fep/ae0c/#mastodon-ld-signatures","title":"Mastodon LD Signatures","text":"<p>Note that the Mastodon documentation for their LD Signatures is incomplete and inaccurate. This section provides more details, but it may be necessary to review the Mastodon source code for additional clarification.</p> <p>An activity signed with a Mastodon LD Signature will have a signature document in the activity (using the <code>signature</code> property).</p> <p>Example Signature Document</p> <pre><code>{\n \"@context\": [\n \"https: //www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/v1\"\n ],\n \"id\": \"https://client.example/3f5ebd6d\",\n # ...\n \"signature\": {\n \"type\": \"RsaSignature2017\",\n \"creator\": \"https://client.example/actor#main-key\",\n \"created\": \"2024-12-08T03:48:33.901Z\",\n \"signatureValue\": \"s69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\"\n }\n}\n</code></pre> <p>The <code>https://w3id.org/security/v1</code> JSON-LD context defines the <code>signature</code> and related properties, but is not used by Mastodon for LD Signature processing.</p> <p>When performing signature operations the signature document and the activity (without the signature document) are initially processed (hashed) separately. The SHA256 hash digests are concatenated that string is then signed.</p>"},{"location":"fep/ae0c/#signing-a-json-ld-activity","title":"Signing a JSON-LD Activity","text":"<ol> <li>Create a signature document with only the <code>creator</code> and <code>created</code> properties. Set the <code>@context</code> to <code>https://w3id.org/identity/v1</code>. (Note that this context doesn't appear to be accessible on the web any more. You may need a local copy with a custom JSON-LD context loader.) </li> <li>Create a canonical RDF representation of the signature document. This requires JSON-LD expansion using standard algorithms (JSON-LD-ALGO) and conversion to RDF using the Universal RDF Dataset Canonicalization Algorithm 2015 (RDF-CANON). The serialized RDF is then hashed using SHA256 and a hexdigest is created.</li> <li>Create a SHA256 hex digest for the activity document (without the signature document) using a similar procedure.</li> <li>Concatenate the SHA256 hex digests for the signature and activity documents and sign the result using SHA256 and the client actor's private key.</li> <li>Encode the signature using Base64 and set the <code>signatureValue</code> of the signature document to the result. </li> <li>Set the signature document <code>type</code> to \"RsaSignature2017\". </li> <li>Set the <code>signature</code> property of the activity to the signature document.</li> </ol>"},{"location":"fep/ae0c/#verifying-a-json-ld-signature","title":"Verifying a JSON-LD Signature","text":"<ol> <li>The signature document is retrieved from the activity and the type is checked to be the nonstandard \"RsaSignature2017\". If not, verification fails.</li> <li>Save the <code>signatureValue</code> from the signature document.</li> <li>Remove the <code>type</code> and <code>id</code> and <code>signatureValue</code> properties from the signature document and generate a SHA256 hex digest for the modified signature document using the procedure described for signing documents.</li> <li>Remove the <code>signature</code> from the activity and generate a SHA256 hex digest for it.</li> <li>Concatenate the hex digests for the modified signature and activity documents.</li> <li>Use the client's public key to verify the signature using SHA256.</li> </ol>"},{"location":"fep/ae0c/#litepub-relay-protocol","title":"LitePub Relay Protocol","text":"<p>The LitePub protocol is based on ActivityPub and is used in Pleroma-compatible servers. The reference implementation is the Pleroma Relay.</p>"},{"location":"fep/ae0c/#relay-client","title":"Relay Client","text":"<p>A LitePub relay client actor must have a type of <code>Application</code> and an actor ID ending with <code>/relay</code>. For best interoperability, it should be compatible with Mastodon actor documents and have WebFinger support. Other implementations may use different actor ID structures (e.g., AodeRelay apparently uses <code>/actor</code> and works with Pleroma). General relay interoperability of these LitePub variants is not known.</p>"},{"location":"fep/ae0c/#relay-subscription_1","title":"Relay Subscription","text":"<p>The client relay actor will send a <code>Follow</code> to the relay server. The <code>Follow</code> <code>object</code> is the relay server actor URI.</p> <p>The relay server MUST respond to the <code>Follow</code> request with an <code>Accept</code> or <code>Reject</code>. If accepted, the relay server sends a reciprocal <code>Follow</code> request for the LitePub client actor. The client server SHOULD respond with <code>Accept</code> or <code>Reject</code> activity. A relay server MAY decide to ignore the subscription if no acknowledgement is received within a reasonable time interval.</p> <p>Example Relay Follow Request</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"actor\": \"https://pleroma.example/relay\",\n \"bcc\": [],\n \"bto\": [],\n \"cc\": [],\n \"id\": \"https://pleroma.example/activities/3fe13910-73f4-4cdc-9c84-ec7013a3e764\",\n \"object\": \"https://relay.example/actor\",\n \"state\": \"pending\",\n \"to\": [\n \"https://relay.example/actor\"\n ],\n \"type\": \"Follow\"\n}\n</code></pre> <p>Notes: 1. The JSON-LD context is not valid for JSON-LD processing. The litepub-0.1.jsonld document contains an invalid WebFinger-related context URL. 2. The <code>state</code> property is not defined in the JSON-LD context.</p>"},{"location":"fep/ae0c/#relay-unsubscription_1","title":"Relay Unsubscription","text":"<p>To unsubscribe from a relay send an <code>Undo</code> with the original <code>Follow</code> activity as the <code>object</code>. There is typically no response to the <code>Undo</code>.</p> <p>Example Undo/Follow Request</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"id\": \"https://pleroma.example/activities/cf9c85e9-f83f-4a02-b598-880f15423f68\",\n \"object\": {\n \"actor\": \"https://pleroma.example/relay\",\n \"bcc\": [],\n \"bto\": [],\n \"cc\": [],\n \"context\": \"https://pleroma.example/contexts/d493d02b-7cc9-49dc-995c-d949af0b5417\",\n \"id\": \"https://pleroma.example/activities/3fe13910-73f4-4cdc-9c84-ec7013a3e764\",\n \"object\": \"https://relay.example/actor\",\n \"published\": \"2024-10-18T14:04:11.029802Z\",\n \"state\": \"cancelled\",\n \"to\": [\n \"https://relay.example/actor\"\n ],\n \"type\": \"Follow\"\n },\n \"published\": \"2024-10-18T14:04:11.029791Z\",\n \"to\": [ \"https://relay.example/actor\" ],\n \"cc\": [],\n \"type\": \"Undo\",\n \"actor\": \"https://pleroma.example/relay\",\n \"context\": \"https://pleroma.example/contexts/d493d02b-7cc9-49dc-995c-d949af0b5417\"\n}\n</code></pre>"},{"location":"fep/ae0c/#publishing-messages-to-a-relay_1","title":"Publishing Messages to a Relay","text":"<p>A LitePub relay client actor will send an <code>Announce</code> for a relayed object (like a <code>Note</code>). For best interoperability <code>Announce</code> should refer to the announced object using an URI (instead of embedding the object).</p> <p>The <code>Announce</code> activity MUST be address to the relay server actor's followers collection. (TODO it's not known if the admin addressing is also required). The <code>published</code> property should be included since some relay server will reject activities without it.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"actor\": \"https://pleroma.example/relay\",\n \"to\": [\n \"https://pleroma.example/relay/followers\",\n \"https://pleroma.example/users/admin\"\n ],\n \"bto\": [],\n \"cc\": [],\n \"context\": \"https://pleroma.example/contexts/a59117d9-7f7c-48ec-83b4-5e183e7179b5\",\n \"id\": \"https://pleroma.example/activities/e24e46a2-8926-4a20-9f5f-638e06102159\",\n \"object\": \"https://pleroma.example/objects/c13bba3c-e7c1-45ac-939f-aa292d23ee8c\",\n \"published\": \"2024-10-18T14:06:37.736295Z\",\n \"type\": \"Announce\"\n}\n</code></pre>"},{"location":"fep/ae0c/#receiving-messages-from-a-relay","title":"Receiving Messages from a Relay","text":"<p>Messages received from a relay are typically wrapped in an <code>Announce</code> activity. AFter the <code>object</code> of the announce is fetched and validated, it is displayed on the federated timeline. It appears that <code>Pleroma</code> will accept a relayed <code>Create</code> activity (for Mastodon compatibility), but will refetch the <code>Create</code> <code>object</code> since the LD Signature isn't processed. (TODO verify this behavior.)</p>"},{"location":"fep/ae0c/#other-relay-server-considerations","title":"Other Relay Server Considerations","text":"<p>The relay server hosting relay actors will have other functionality other than activity relaying.</p>"},{"location":"fep/ae0c/#webfinger","title":"WebFinger","text":"<p>A relay server MUST implement WebFinger support for relay actors. This is necessary because of Mastodon's actor fetching implementation. It's possible that it's not required for LitePub-only relay servers.</p>"},{"location":"fep/ae0c/#nodeinfo","title":"NodeInfo","text":"<p>A relay server MAY implement NodeInfo to advertise server activity and metadata.</p>"},{"location":"fep/ae0c/#optional-relay-server-behaviors","title":"Optional Relay Server Behaviors","text":"<p>A relay server MAY support multiple relay protocols. However, there is no standard way to advertise those capabilities.</p> <p>A relay server often hosts a single actor, but any number of relay actors may be hosted. For example, a relay server may have a relay actor for specific topics, hashtags, or moderation categories. A relay client can subscribe to any number of relay actors in a given server.</p> <p>Some servers implement dynamic relay actor creation. A relay actor's <code>inbox</code> URI might be based on a hashtag or a topic name. When a client actor subscribes to this kind of inbox URI, the relay actor is created automatically. Obviously, there are risks to this approach when used by misbehaved clients.</p>"},{"location":"fep/ae0c/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>Mastodon Documentation, LD Signatures, HTTP Signatures</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, RFC-2119, 1997</li> <li>Matthew Sporny, Dave Longley, JSON-LD 1.1, JSON-LD, 2020</li> <li>Dave Longley, Gregg Kellogg, JSON-LD 1.1 Processing Algorithms and API, JSON-LD-ALGO, 2018</li> <li>Graham Klyne, Jeremy J. Carroll, RDF 1.1 Concepts and Abstract Syntax, RDF, 2014</li> <li>Dave Longley, RDF Dataset Canonicalization, (URDNA2015) RDF-CANON, 2022</li> <li>NIST, Secure Hash Standard (SHS), SHA256, 2015</li> <li>Wikipedia, Base64</li> <li>P. Jones, WebFinger, RFC-7033 WebFinger, 2013 </li> <li>Jonne Ha\u00df, NodeInfo, GitHub</li> <li>Pleroma Relay, pleroma-relay</li> <li>LitePub Protocol Suite, litepub</li> <li>Takeshi Umeda, pub-relay pub-relay</li> </ul>"},{"location":"fep/ae0c/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/ae97/","title":"FEP-ae97: Client-side activity signing","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2023-08-14 #148 Discussions codeberg"},{"location":"fep/ae97/#summary","title":"Summary","text":"<p>Existing Fediverse servers manage signing keys on behalf of their users. This proposal describes a new kind of ActivityPub client that lets users sign activities with their own keys, and a server that can distribute client-signed activities to other servers.</p>"},{"location":"fep/ae97/#history","title":"History","text":"<p>Initial version of this proposal relied on linking of cryptographic identities to actor objects via FEP-c390 identity proofs. That mechanism was superseded by FEP-ef61 which achieves full data portability.</p>"},{"location":"fep/ae97/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/ae97/#discovery","title":"Discovery","text":"<p>A server that supports clients capable of managing signing keys MUST have a discovery endpoint at the path <code>/.well-known/apgateway</code>.</p> <p>When the server receives an HTTP GET request to this endpoint, it MUST respond with a JSON object containing information about itself. That object MAY be empty.</p> <p>Example:</p> <pre><code>{\n \"uploadMedia\": \"https://gateway.example/.well-known/apgateway-media\"\n}\n</code></pre>"},{"location":"fep/ae97/#registering-an-actor","title":"Registering an actor","text":"<p>Client creates portable actor objects according to FEP-ef61 and stores them. When a portable actor is created, the client MUST create a key for signing requests (the main actor key) and add a <code>Multikey</code> representation of it to the <code>assertionMethod</code> array as described in FEP-521a. The identifier of that key MUST NOT be a compatible identifier.</p> <p>Before registering a portable actor on the server, the client MUST add the server URL to the <code>gateways</code> array of the actor object.</p> <p>To register the actor, the client sends an HTTP POST request to the gateway endpoint at <code>/.well-known/apgateway</code> path. The body of the request MUST be an actor object.</p> <p>The server SHOULD limit registrations (for example, by requiring an invite code). If the server accepts the registration request, it generates an RSA key and returns it in a response. The response MUST have the <code>201 Created</code> status code. The body of the response is a JSON object with an <code>assertionMethod</code> property. The value of this property is an array containing the RSA public key generated by the server in the Multikey format.</p> <p>Example:</p> <pre><code>{\n \"assertionMethod\": [\n {\n \"type\": \"Multikey\",\n \"publicKeyMultibase\": \"z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i\"\n }\n ]\n}\n</code></pre> <p>If the server can't register the actor, it MUST return a <code>400 Bad Request</code> status code.</p> <p>If the registration is successful, the client MUST attach the RSA key to the actor object via <code>publicKey</code> property, and also add it to the <code>assertionMethod</code> array as described in FEP-521a. If the server's response contains other keys, they SHOULD be added to the <code>assertionMethod</code> array as well.</p> <p>If the client uses compatible identifiers, then key identifiers MUST be generated with the server's origin.</p> <p>After updating the actor object, the client MUST publish an <code>Update</code> activity for it.</p>"},{"location":"fep/ae97/#sending-activities","title":"Sending activities","text":"<p>The client submits signed FEP-ef61 activities to actor's outbox. Contrary to what ActivityPub specification prescribes in section 6. Client to Server Interactions, the server MUST NOT overwrite the ID of an activity. Instead of assigning a new ID, the server MUST verify that provided ID has not been used before. If the server accepts activity, its response MUST have <code>202 Accepted</code> status code.</p> <p>If activity contains a wrapped object (as in <code>Create</code> and <code>Update</code> activities), it MUST be a portable object created according to FEP-ef61. The server MUST validate object IDs in the same way it validates activity IDs.</p> <p>The server MUST deliver activities to their indended audiences without altering them. When signing HTTP requests, the server uses the RSA key generated during the registration.</p> <p>If the owner of the outbox is not registered, the server MUST return a <code>404 Not Found</code> status code.</p> <p>If the actor of the submitted activity is different from the outbox owner, the server MUST return a <code>403 Forbidden</code> status code.</p>"},{"location":"fep/ae97/#receiving-activities","title":"Receiving activities","text":"<p>Client receives activities by polling the actor's inbox.</p> <p>Requests to inbox endpoint MUST have an HTTP signature created using the main actor key (the one generated by the client, not a server-generated key).</p> <p>If the signature is not valid, the server MUST return a <code>401 Unauthorized</code> status code.</p> <p>If the owner of the inbox is not registered, the server MUST return a <code>404 Not Found</code> status code.</p> <p>If the actor that generated the signature is different from the inbox owner, the server MUST return a <code>403 Forbidden</code> status code.</p>"},{"location":"fep/ae97/#media-api","title":"Media API","text":""},{"location":"fep/ae97/#upload-media","title":"Upload media","text":"<p>To upload a media file, the client sends an HTTP POST request to the gateway endpoint at the <code>/.well-known/apgateway-media</code> path. The body of the request MUST be the media in the form of binary data. The request MUST contain a <code>Content-Type</code> header and MUST be signed with the main key of the actor.</p> <p>If the media is processed successfully, the server MUST return a response with a <code>201 Created</code> status code and a JSON object as the body. This JSON object MUST have a <code>url</code> property whose value is a hashlink to the processed media file.</p> <p>Example:</p> <pre><code>{\n \"type\": \"Document\",\n \"url\": \"hl:zQmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e\"\n}\n</code></pre> <p>The server MUST serve the processed media file at a <code>/.well-known/apgateway-media/{hashlink}</code> path.</p> <p>If the signature is not valid, the server MUST return a <code>401 Unauthorized</code> status code. If the signature is valid, but the actor is not registered, the server MUST return a <code>403 Forbidden</code> status code.</p> <p>If the media type is not supported, the server MUST return a <code>400 Bad Request</code> status code.</p> <p>If the media is too large, the server MUST return a <code>413 Payload Too Large</code> status code.</p>"},{"location":"fep/ae97/#delete-media","title":"Delete media","text":"<p>To delete a previously uploaded media file, the client sends an HTTP DELETE request to an endpoint at path <code>/.well-known/apgateway-media/{hashlink}</code>. The request MUST be signed with the main key of the actor.</p> <p>If the signature is valid and the actor owns the media file, the server MUST delete the file.</p> <p>If the signature is not valid, the server MUST return a <code>401 Unauthorized</code> status code. If the signature is valid, but the actor is not registered, the server MUST return a <code>403 Forbidden</code> status code.</p> <p>If the media file doesn't exist or not owned by the actor, the server MUST return a <code>404 Not Found</code> status code.</p>"},{"location":"fep/ae97/#security-considerations","title":"Security considerations","text":"<p>If the server accepts portable objects with compatible identifiers, it MUST ensure that all objects served by the gateway are valid and that registered actors are sufficiently isolated from each other. Specifically, the server MUST verify that actors and activities generated by the client are permitted in the origin-based security model:</p> <ul> <li>Objects MUST NOT represent any actions that actors are not authorized to perform.</li> <li>Objects MUST NOT represent server-controlled public keys or verification methods. Such objects can be identifed using the algorithm from FEP-2277.</li> </ul> <p>Precautions need also be taken when objects with compatible IDs are delivered to inboxes or fetched from remote servers.</p>"},{"location":"fep/ae97/#implementations","title":"Implementations","text":"<ul> <li>fep-ae97-client (client)</li> <li>Mitra (server)</li> </ul>"},{"location":"fep/ae97/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>silverpill, FEP-c390: Identity Proofs, 2022</li> <li>silverpill, FEP-ef61: Portable Objects, 2023</li> <li>silverpill, FEP-521a: Representing actor's public keys, 2023</li> <li>silverpill, FEP-fe34: Origin-based security model, 2024</li> <li>silverpill, FEP-2277: ActivityPub core types, 2025</li> <li>Dave Longley, Manu Sporny, Markus Sabadello, Drummond Reed, Orie Steele, Christopher Allen, Controlled Identifiers v1.0, 2025</li> <li>Ryan Barrett, nightpool, ActivityPub and HTTP Signatures, 2024</li> <li>M. Sporny, L. Rosenthol, Cryptographic Hyperlinks, 2021</li> </ul>"},{"location":"fep/ae97/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/b06c/","title":"FEP-b06c: ActivityPoll","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org <code>DRAFT</code> 2025-06-25 #638 Discussions codeberg"},{"location":"fep/b06c/#summary","title":"Summary","text":"<p>ActivityPoll is a proper subset of ActivityPub that excludes activity delivery, making it easier to implement for static Web sites or content management systems. It meets an equivalent need to RSS or Atom feeds.</p>"},{"location":"fep/b06c/#introduction","title":"Introduction","text":"<p>In ActivityPub (AP), an actor is an ActivityPub object that produces activities. The actor's <code>outbox</code> is the collection of activities that the actor has produced.</p> <p>ActivityPub has optional features for real-time delivery of activities. ActivityPub actors MAY deliver new activities to the actors addressed in the activity's <code>to</code>, <code>cc</code>, <code>bto</code>, <code>bcc</code> and <code>audience</code> properties using the ActivityPub federation protocol. The actor MAY also receive activities from other actors in its <code>inbox</code>, such as comments, likes, and other feedback.</p> <p>Supporting the federation protocol adds significant complexity in the implementation of ActivityPub. Handling incoming and outgoing activities is a significant barrier to entry for AP implementation.</p> <p>One alternative is to shift the burden of initiating delivery from the publisher to the consumer. Instead of pushing activities from the publishing server as they are generated, the activities can instead be pulled by the consumer at regular intervals.</p> <p>There are three main downsides to a polling structure.</p> <ol> <li>Activities are not delivered in near-real-time; there is a potential delay, up to the polling interval of the consumer (which may be variable).</li> <li>The consumer can request updates when no new activities have been published, which takes both consumer and publisher resources.</li> <li>Activities are not reliably delivered to the addressees. Unless the addressees' servers poll the feed, the addressees do not receive the activities in their <code>inbox</code>.</li> </ol> <p>The primary upside is that polling opens up the social web to many more implementers. Any publisher that can publish an RSS feed can publish an ActivityPoll actor. Polling also opens up the network to simpler, power-constrained devices, as well as static web sites.</p> <p>Having more content producers is better for all users, and thus better for more mature and full-featured social web platforms. Shifting this burden of delivery, if it results in more information on the network, can be good for the entire ecosystem.</p> <p>This subset of ActivityPub, called ActivityPoll, describes the reduced responsibilities of the publisher, the increased responsibilities of the consumer, and methods for indicating use of polling.</p>"},{"location":"fep/b06c/#publishers","title":"Publishers","text":"<p>ActivityPoll publishers host one or more ActivityPoll actors. ActivityPoll actors are valid ActivityPub actors and ActivityPub objects. They MUST have at least the following properties:</p> <ul> <li><code>id</code>: A unique identifier for the actor, which MUST be a dereferenceable IRI.</li> <li><code>type</code>: The type of the actor.</li> <li><code>outbox</code>: An <code>OrderedCollection</code> of activities, with its own dereferenceable IRI.</li> <li><code>inbox</code>: An <code>OrderedCollection</code> of activities, with its own dereferenceable IRI.</li> </ul> <p>Other properties of an Activity Streams 2.0 object MAY be included, including extension properties.</p> <p>Other properties of an ActivityPub actor object MAY be included.</p>"},{"location":"fep/b06c/#inbox","title":"Inbox","text":"<p>If an actor is poll-only, and does not support the ActivityPub federation protocol, the IRI of the <code>inbox</code> property of the actor MUST respond with a 405 Method Not Allowed response to HTTP POST requests.</p> <p>Publishers MAY also indicate that delivery is not supported using the <code>pollOnly</code> property defined in this specification, setting it to <code>true</code>. This does not override the requirement to have an <code>inbox</code> property that returns a 405 HTTP status on POST.</p>"},{"location":"fep/b06c/#outbox","title":"Outbox","text":"<p>The actor's <code>outbox</code> property is an <code>OrderedCollection</code> in reverse chronological order. It MAY be paginated.</p> <p>The IRI for the <code>outbox</code> collection SHOULD support HTTP Caching, including the <code>If-Modified-Since</code> and <code>If-None-Match</code> request headers. The IRI for each page of a paginated <code>outbox</code> collection SHOULD support HTTP Caching.</p> <p>The <code>outbox</code> collection object SHOULD include a <code>totalItems</code> property, so that changes in any page of the collection are reflected in the collection itself.</p> <p>Activities in the <code>outbox</code>, and all the object properties of the activities, MUST be valid ActivityPub objects, with dereferenceable IRIs.</p> <p>To make incremental polling manageable, ordering of activities in the <code>outbox</code> collection SHOULD be stable. New activities SHOULD be prepended to the collection. Deleted activities SHOULD be replaced by a Tombstone object.</p>"},{"location":"fep/b06c/#authentication","title":"Authentication","text":"<p>ActivityPoll servers MAY require authentication, such as OAuth 2.0 or HTTP Signatures. Collections such as <code>outbox</code> MAY be filtered according to the access level of the authenticated client.</p>"},{"location":"fep/b06c/#lack-of-activity-side-effects","title":"Lack of activity side effects","text":"<p>Without an <code>inbox</code> that can receive incoming activities, an ActivityPoll publisher might not maintain many of the state changes that are typical side effects of such activities. A non-exhaustive list of collections affected by inbound activities:</p> <ul> <li>maintaining the <code>followers</code> collection for an actor</li> <li>maintaining the <code>replies</code> collection for an object</li> <li>maintaining the <code>shares</code> collection for an object</li> <li>maintaining the <code>likes</code> collection for an object</li> </ul> <p>Publishers SHOULD omit these properties from objects and actors if they will not be maintained.</p>"},{"location":"fep/b06c/#webfinger","title":"Webfinger","text":"<p>ActivityPoll servers MAY implement Webfinger to allow <code>user@hostname</code> addressing for actors. Because Webfinger requires different content based on a query parameter, it is difficult to implement for static web sites, unless they only support a single actor.</p>"},{"location":"fep/b06c/#consumers","title":"Consumers","text":"<p>ActivityPoll consumers read the <code>outbox</code> collection of an actor to retrieve activities and distribute them.</p> <p>ActivityPoll consumers SHOULD support HTTP Caching when fetching the <code>OrderedCollection</code>, its pages, and any activities and related objects in the collection.</p> <p>Because the <code>outbox</code> property is ordered in reverse chronological order, consumers SHOULD use the results of their last poll to limit requests to the publishing server. Keeping a \"last read\" activity ID between polls can be used to limit the requests to only activities and pages published since that ID.</p>"},{"location":"fep/b06c/#following","title":"Following","text":"<p>ActivityPoll consumers SHOULD include remote ActivityPoll actors in their actors' <code>following</code> collections.</p>"},{"location":"fep/b06c/#polling-frequency","title":"Polling frequency","text":"<p>Polling frequency in syndication systems is important. If polling is too infrequent, new activities aren't processed in a timely fashion. If polling is too frequent, it abuses publisher and consumer resources without any new activities being delivered.</p> <p>The <code>updateInterval</code> property of an actor can indicate how often the actor generates activities. Consumers SHOULD use this information to schedule polling requests. Consumers MAY use heuristics to schedule polling requests, such as median time between activities previously seen. Consumers MAY set a minimum frequency for polling actors, such as daily or monthly.</p>"},{"location":"fep/b06c/#local-delivery","title":"Local delivery","text":"<p>If the addressing properties of an activity by an ActivityPoll actor include actors on the consumer server, the consumer SHOULD deliver the activities to those actors' <code>inbox</code> collections. ActivityPoll consumers MAY deliver activities by ActivityPoll publisher actors to other servers, for example for inbox forwarding.</p>"},{"location":"fep/b06c/#activity-side-effects","title":"Activity side effects","text":"<p>Some activities, or their objects, MAY be added to special collections on the consumer server. These include:</p> <ul> <li>Objects with an <code>inReplyTo</code> property with the value of an ActivityPub object on the consuming server MAY be added to the <code>replies</code> collection for that object.</li> <li><code>Like</code> activities with an <code>object</code> value on the consumer server MAY be added to the <code>likes</code> collection of that object.</li> <li><code>Announce</code> activities with an <code>object</code> value on the consumer server MAY be added to the <code>shares</code> collection of that object.</li> <li><code>Follow</code> activities with an <code>object</code> value on the consumer server MAY result in the actor being added to the <code>object</code> value's <code>followers</code> collection.</li> </ul> <p>Other side effects MAY be implemented by the consuming server.</p>"},{"location":"fep/b06c/#context","title":"Context","text":"<p>This specification includes a context document that defines the optional properties of an ActivityPoll actor.</p> <pre><code>{\n \"@context\": {\n \"poll\": \"https://w3id.org/fep/b06c#\",\n \"pollOnly\": \"poll:pollOnly\",\n \"updateInterval\": \"poll:updateInterval\"\n }\n}\n</code></pre> <p>The context can be included using the context URL <code>https://w3id.org/fep/b06c</code>, as illustrated in the examples below.</p>"},{"location":"fep/b06c/#pollonly","title":"<code>pollOnly</code>","text":"<p>This is a boolean property that can be used to determine if an actor does not deliver activities remotely. Its default value is <code>false</code>.</p>"},{"location":"fep/b06c/#updateinterval","title":"<code>updateInterval</code>","text":"<p>This property represents a typical interval between activities in the <code>outbox</code> of the actor. It is expressed as an xsd:duration value. Some example values:</p> <ul> <li><code>P15M</code> - new activities are added every 15 minutes</li> <li><code>P4H</code> - new activities are added once every 4 hours</li> <li><code>P1D</code> - updated daily</li> <li><code>P3M</code> - updated quarterly</li> <li><code>P1Y</code> - updated annually</li> </ul> <p>This interval conveys a rough estimate of update frequency, not a commitment. Different types of actors often have different posting profiles. Automated actors often have regular intervals for activities. Real-world humans, on the other hand, tend to cluster into \"sessions\", when a human being makes a number of activities in only a few minutes or hours.</p>"},{"location":"fep/b06c/#examples","title":"Examples","text":""},{"location":"fep/b06c/#minimal-activitypoll-actor","title":"Minimal ActivityPoll Actor","text":"<p>This minimal actor provides just enough information to be a useful ActivityPoll object.</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://device.example/actor.jsonld\",\n \"type\": \"Application\",\n \"name\": \"Low-power device #69883\",\n \"to\": \"as:Public\",\n \"pollOnly\": true,\n \"inbox\": \"https://device.example/actor/inbox.jsonld\",\n \"outbox\": \"https://device.example/actor/outbox.jsonld\"\n}\n</code></pre> <p>The inbox for this actor is an empty collection.</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/5711\"],\n \"id\": \"https://device.example/inbox.jsonld\",\n \"type\": \"OrderedCollection\",\n \"summary\": \"Inbox of low-power device #69883\",\n \"attributedTo\": \"https://device.example/actor.jsonld\",\n \"to\": \"as:Public\",\n \"inboxOf\": \"https://device.example/actor.jsonld\",\n \"totalItems\": 0\n}\n</code></pre> <p>The outbox for this actor is not paginated.</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/5711\"],\n \"id\": \"https://device.example/outbox.jsonld\",\n \"type\": \"OrderedCollection\",\n \"summary\": \"Outbox of low-power device #69883\",\n \"attributedTo\": \"https://device.example/actor.jsonld\",\n \"to\": \"as:Public\",\n \"outboxOf\": \"https://device.example/actor.jsonld\",\n \"totalItems\": 5,\n \"items\": [\n \"https://device.example/create-note-3.jsonld\",\n \"https://device.example/like-note-1.jsonld\",\n \"https://device.example/delete-note-2.jsonld\",\n \"https://device.example/create-note-2.jsonld\",\n \"https://device.example/create-note-1.jsonld\"\n ]\n}\n</code></pre>"},{"location":"fep/b06c/#publication","title":"Publication","text":"<p>This example models a simple ActivityPoll actor for a Web magazine. By embedding node objects into the document, the publisher can dramatically reduce the number of HTTP requests required to check for updates.</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"type\": \"Organization\",\n \"name\": \"ActivityPoll Magazine\",\n \"summary\": \"The premier magazine for the poll-oriented social web\",\n \"pollOnly\": true,\n \"updateInterval\": \"P1D\",\n \"inbox\": \"https://publisher.example/magazine/activitypoll/inbox.jsonld\",\n \"outbox\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/outbox.jsonld\",\n \"totalItems\": 4,\n \"first\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/outbox/1.jsonld\",\n \"orderedItems\": [\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/3.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/3.jsonld\",\n \"type\": \"Article\",\n \"name\": \"Polling strategies for ActivityPoll consumers\",\n \"summary\": \"&lt;p&gt;An overview of strategies for polling intervals&lt;/p&gt;\",\n \"url\": \"https://publisher.example/magazine/activitypoll/article/3.html\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250621T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/delete/article/2.jsonld\",\n \"type\": \"Delete\",\n \"summary\": \"ActivityPoll Magazine deleted an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/2.jsonld\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Create\",\n \"published\": \"20250615T00:00:00Z\",\n \"deleted\": \"20250618T00:00:00Z\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250618T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/2.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/2.jsonld\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Create\",\n \"published\": \"20250615T00:00:00Z\",\n \"deleted\": \"20250618T00:00:00Z\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250615T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/1.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/1.jsonld\",\n \"type\": \"Article\",\n \"name\": \"Announcing ActivityPoll Magazine\",\n \"summary\": \"&lt;p&gt;This is a new magazine for posts about the poll-oriented social web.&lt;/p&gt;\",\n \"url\": \"https://publisher.example/magazine/activitypoll/article/1.html\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250609T00:00:00Z\"\n }\n ]\n }\n }\n}\n</code></pre>"},{"location":"fep/b06c/#social-network-interactions","title":"Social Network Interactions","text":"<p>This example models a simple social network user, with reaction activities to content published elsewhere.</p> <pre><code>{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://social.example/user/activitypoll.jsonld\",\n \"type\": \"Person\",\n \"name\": \"Social Networkuser\",\n \"summary\": \"A typical social network user\",\n \"pollOnly\": true,\n \"updateInterval\": \"P8H\",\n \"inbox\": \"https://social.example/user/activitypoll/inbox.jsonld\",\n \"outbox\": {\n \"id\": \"https://social.example/user/activitypoll/outbox.jsonld\",\n \"totalItems\": 3,\n \"first\": {\n \"id\": \"https://social.example/user/activitypoll/outbox/1.jsonld\",\n \"orderedItems\": [\n {\n \"id\": \"https://social.example/user/activitypoll/activity/3.jsonld\",\n \"type\": \"Like\",\n \"summary\": \"Social Networkuser liked this object\",\n \"object\": \"https://other.example/object/D6D11370-8BD1-4E52-849B-AF62121AB58E\"\n },\n {\n \"id\": \"https://social.example/user/activitypoll/activity/2.jsonld\",\n \"type\": \"Announce\",\n \"summary\": \"Social Networkuser shared this object\",\n \"object\": \"https://third.example/object/50D4C4E9-DEB4-4EC5-8353-F079A63533F0\"\n },\n {\n \"id\": \"https://social.example/user/activitypoll/activity/3.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"Social Networkuser created a note\",\n \"object\": {\n \"id\": \"https://social.example/user/activitypoll/note/1.jsonld\",\n \"type\": \"Note\",\n \"inReplyTo\": \"https://other.example/object/0BDF12F8-1148-4BDF-B911-3B9CBEA95678\",\n \"content\": \"I agree!\"\n }\n }\n ]\n }\n }\n}\n</code></pre>"},{"location":"fep/b06c/#privacy-considerations","title":"Privacy considerations","text":"<p>This specification allows publishers to implement authentication for ActivityPub objects, actors, and collections. However, authentication is difficult to implement for static web sites. Features that depend on authentication, such as non-public objects or blocking users or domains, will be hard to implement or enforce with ActivityPoll.</p>"},{"location":"fep/b06c/#implementation-notes","title":"Implementation notes","text":"<p>The subset of ActivityPub outlined in this specification does not preclude the use of the ActivityPub API for publishing activities, even if they are not delivered to remote addressees.</p>"},{"location":"fep/b06c/#history","title":"History","text":"<p>Read-only, poll-based syndication feeds are a common pattern on the Web. RSS 1.0, RSS 2.0, Atom, and other syndication formats have been used for decades to provide a simple way to transfer content from one Web site to another. Atom Activity Streams 1.0 enhanced Atom with richer data about social networking activities, such as comments, likes, and shares.</p>"},{"location":"fep/b06c/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>Aaron Swartz, RSS1, 2000</li> <li>Harvard Law School Berkman Klein Center for Internet &amp; Society, RSS2, 2002</li> <li>Mark Nottingham, R. Sayer, Atom, 2005</li> <li>Martin Atkins, Will Norris, Chris Messina, Monica Wilkinson, Rob Dolin, AS1, 2010</li> <li>James Snell, Evan Prodromou, AS2, 2018</li> <li>Dick Hardt, OAuth2, 2012</li> <li>Ryan Barrett, nightpool, HTTPSignatures, 2024</li> <li>R. Fielding, M. Nottingham, J. Reschke, CACHING, 2022</li> <li>a, Evan Prodromou, Webfinger, 2024</li> </ul>"},{"location":"fep/b06c/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/b2b8/","title":"FEP-b2b8: Long-form Text","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org <code>DRAFT</code> 2024-11-07 #441 Discussions codeberg"},{"location":"fep/b2b8/#summary","title":"Summary","text":"<p>Multi-paragraph text is an important content type on the Social Web. This FEP defines best practices for representing and using properties of a long-form text object in Activity Streams 2.0.</p>"},{"location":"fep/b2b8/#motivation","title":"Motivation","text":"<p>Blog posts, magazine articles, and forum posts are often made up of multiple paragraphs of text, sometimes with embedded images, video, audio or other media. This important content type is documented in the Activity Vocabulary, but this FEP provides additional guidance for publishers and consumers and collects the relevant properties in one place.</p> <p>Well-defined behaviour for supporting long-form text provides multiple benefits. Not only does it allow publishers to integrate content in a dependable way across different platforms, but it also gives users of Activity Streams 2.0 consumer applications more control over their reading experience -- including filtering or sorting long-form text objects in their stream.</p> <p>This FEP does not provide guidance for book-length or longer text.</p> <p>This document provides information for multiple protocols that use Activity Streams 2.0 as a representation format. Where ActivityPub use is different than AS2, it is noted.</p> <p>Because long-form text is often syndicated using RSS 2.0, the properties in this FEP are compared to the properties in that format where appropriate.</p>"},{"location":"fep/b2b8/#type","title":"Type","text":"<p>The Article type is used to represent multi-paragraph text. The Activity Streams 2.0 primer provides guidance on when to use the <code>Article</code> type and when to use the <code>Note</code> type.</p> <p>Some consumers do not display <code>Article</code> objects with their full content. Some publishers work around this by using a <code>Note</code>-type object with much more content than expected for a note.</p> <p>Publishers should avoid this workaround, and instead give consumers the full information they need to display the content correctly in their own interfaces. The preview property can be used to provide a simpler version of the content for consumers that don't support <code>Article</code> directly.</p> <p>Forcing long-form text into a <code>Note</code> object can cause problems for consumers that expect <code>Note</code> objects to be short and well-formatted for stream display. Maintaining a clear distinction between <code>Note</code> and <code>Article</code> objects is important for interoperability.</p> <p>Consumers that only display short text should show the <code>name</code>, <code>summary</code> and a link to the <code>url</code> property so that users can view the full content in a web browser. As a fallback, they can use the <code>preview</code> property if it is present.</p>"},{"location":"fep/b2b8/#properties","title":"Properties","text":""},{"location":"fep/b2b8/#id","title":"<code>id</code>","text":"<p>A unique identifier for the text. For ActivityPub, this should be an HTTPS URL that resolves to the object. It should be a single string, unique for all objects.</p> <p>This property provides the same functionality as the <code>guid</code> property in RSS 2.0.</p>"},{"location":"fep/b2b8/#name","title":"<code>name</code>","text":"<p>The title of the text should be in the <code>name</code> property. The property should be short enough to be displayed in a line or two on a browser interface; 75-150 characters is a good rule of thumb. Longer descriptions should be in the <code>summary</code> property.</p> <p>The <code>name</code> property should be plain text, not HTML or other markup. In particular, no HTML entities like <code>&amp;amp;</code> or <code>&amp;lt;</code> should be used.</p> <p>This property provides the same functionality as the <code>title</code> property in RSS 2.0.</p>"},{"location":"fep/b2b8/#url","title":"<code>url</code>","text":"<p>The location of the full text should be in the <code>url</code> property. This can be a single string, in which case it is the URL of the HTML representation of the text. It can also be a Link object, which can include additional metadata about the link. If it is a <code>Link</code> object, the <code>mediaType</code> should be 'text/html' and <code>href</code> property should be the URL of the HTML representation of the text.</p> <p>The <code>url</code> property can also be an array of strings or <code>Link</code> objects or both. Multiple <code>Link</code> objects can be used to represent different media types or provide different URL protocols. At least one of the <code>Link</code> objects should have a <code>mediaType</code> of 'text/html' and a <code>href</code> property with the protocol 'https'.</p> <p>This property provides the same functionality as the <code>link</code> property in RSS 2.0.</p>"},{"location":"fep/b2b8/#summary_1","title":"<code>summary</code>","text":"<p>This property provides a brief description, teaser, abstract or \"lede\" for the text. It should be a maximum of about 500 characters; a few sentences; or a short paragraph.</p> <p>This property can include HTML markup. It should not include embedded media like images, video or audio. It should not include navigation or interaction elements like \"favourite\", \"like\", \"bookmark\" or other buttons. It should not include links to the publisher's home page or category pages. It should not include a \"Read more...\" link to the full text.</p> <p>This property provides the same functionality as the <code>description</code> property in RSS 2.0.</p>"},{"location":"fep/b2b8/#attributedto","title":"<code>attributedTo</code>","text":"<p>This property provides the authors of the text, either as a string, an object, or an array.</p> <p>As a string, it is a single <code>id</code> for the author. For ActivityPub, the <code>id</code> should be a URL that resolves to an ActivityPub actor.</p> <p>As an object, it can be an AS2 object with a type like <code>Person</code>, <code>Application</code> or <code>Organization</code>. It should have an <code>id</code> and a <code>name</code> property and can also include an <code>icon</code> property for the author's avatar. A <code>summary</code> property can be used to provide a brief description of the author, including HTML. An <code>url</code> property can be used to provide a link to the author's profile page.</p> <p>If the author does not have an AS2 representation, the <code>attributedTo</code> property can be an object with a <code>type</code> of <code>Link</code> and an <code>href</code> property with the URL of the author's profile page. The <code>name</code> property can be used to provide the author's name.</p> <p>As an array, the <code>attributedTo</code> property can include multiple authors, either as strings or objects.</p> <p>The <code>attributedTo</code> property provides the same functionality as the <code>author</code> property of an item in RSS 2.0, with additional features.</p>"},{"location":"fep/b2b8/#published","title":"<code>published</code>","text":"<p>The publication date of the text should be in the published property. This should be a dateTime string in the format <code>YYYY-MM-DDTHH:MM:SSZ</code>.</p> <p>This property provides the same functionality as the <code>pubDate</code> property in RSS 2.0.</p>"},{"location":"fep/b2b8/#updated","title":"<code>updated</code>","text":"<p>If the object has been updated, the date of the last update should be in the updated property. This should be a dateTime string in the format <code>YYYY-MM-DDTHH:MM:SSZ</code>. If the property is not present, consumers can assume that the object has not been modified since the <code>published</code> date.</p>"},{"location":"fep/b2b8/#image","title":"<code>image</code>","text":"<p>The image property provides a notable or representative image for the text. It can be included by reference as an <code>id</code> or with an <code>Image</code> type object.</p> <p>There can be multiple values for the <code>image</code> property, either as an array of <code>id</code> strings or <code>Image</code> objects. Publishers should provide these in order of importance, with the most important image first. Consumers can use as many or as few as needed.</p>"},{"location":"fep/b2b8/#content","title":"<code>content</code>","text":"<p>The full text of the article or blog post should be in the content property. This should be HTML. Using <code>mediaType</code> to set a different media type presumes that consumers will be able to display that type.</p> <p>The HTML elements in the <code>content</code> property should include a sanitized subset of the full HTML element set. It should not include any CSS or JavaScript. This subset should include:</p> <ul> <li><code>&lt;p&gt;</code></li> <li><code>&lt;span&gt;</code> (class)</li> <li><code>&lt;h2&gt;</code>, <code>&lt;h3&gt;</code>, <code>&lt;h4&gt;</code>, <code>&lt;h5&gt;</code>, <code>&lt;h6&gt;</code></li> <li><code>&lt;br&gt;</code></li> <li><code>&lt;a&gt;</code> (href, rel, class)</li> <li><code>&lt;del&gt;</code></li> <li><code>&lt;pre&gt;</code></li> <li><code>&lt;code&gt;</code></li> <li><code>&lt;em&gt;</code></li> <li><code>&lt;strong&gt;</code></li> <li><code>&lt;b&gt;</code></li> <li><code>&lt;i&gt;</code></li> <li><code>&lt;u&gt;</code></li> <li><code>&lt;ul&gt;</code></li> <li><code>&lt;ol&gt;</code> (start, reversed)</li> <li><code>&lt;li&gt;</code> (value)</li> <li><code>&lt;blockquote&gt;</code></li> <li><code>&lt;img&gt;</code> (src, alt, title, width, height, class)</li> <li><code>&lt;video&gt;</code> (src, controls, loop, poster, width, height, class)</li> <li><code>&lt;audio&gt;</code> (src, controls, loop, class)</li> <li><code>&lt;source&gt;</code> (src, type)</li> <li><code>&lt;ruby&gt;</code></li> <li><code>&lt;rt&gt;</code></li> <li><code>&lt;rp&gt;</code></li> </ul> <p>The HTML should only include the content of the text. Additional navigation to other pages on the originating site, like category links or home page links, should not be included. Other affordances like \"favourite\", \"like\", \"bookmark\" or other buttons should not be included. It should not include a \"Read more...\" link to the full article.</p> <p>Any embedded media like images, video or audio in the <code>content</code> property should also be listed in the <code>attachment</code> property so that consumers can pre-fetch the media.</p>"},{"location":"fep/b2b8/#source","title":"<code>source</code>","text":"<p>If the text was originally created in a different format, the original source should be in the source property to allow editing the content. It should include the <code>mediaType</code> of the source format and the <code>content</code> property with the original content.</p>"},{"location":"fep/b2b8/#replies","title":"<code>replies</code>","text":"<p>Comments on the text should be linked in the replies property. This should be a URL that resolves to a collection of objects for the replies.</p> <p>Comments are usually <code>Note</code> objects, but can be other types of objects like <code>Article</code> or <code>Question</code>.</p> <p>This property provides the same functionality as the <code>comments</code> property in RSS 2.0.</p>"},{"location":"fep/b2b8/#inreplyto","title":"<code>inReplyTo</code>","text":"<p>If the text is a reply to another ActivityPub object, such as an <code>Article</code> or <code>Note</code>, the <code>inReplyTo</code> property can include a string with the URL of the object being replied to, or a JSON object representing that object.</p> <p>If the text is a commentary on or review of a particular link on the Web, the <code>inReplyTo</code> property can include a <code>Link</code> object with a <code>href</code> property that is the URL of the linked resource.</p>"},{"location":"fep/b2b8/#attachment","title":"<code>attachment</code>","text":"<p>The attachment property provides additional media that is part of the text. This can include images, video, audio, or other media. Consumers can use this property to pre-fetch media for display without needing to load and parse the full <code>content</code> property.</p>"},{"location":"fep/b2b8/#tag","title":"<code>tag</code>","text":"<p>The tag property provides additional metadata about the text. There are two important types of tags:</p> <ul> <li>Hashtag objects, which represent a topic or category that the text is about. These should have a <code>name</code> property with the tag text.</li> <li>Mention objects, which represent a mention of an actor, such as an ActivityPub actor. These should have an <code>href</code> property with the URL of the actor's profile page.</li> </ul>"},{"location":"fep/b2b8/#context","title":"<code>context</code>","text":"<p>If the text is part of a larger collection, the context property can provide a link to the collection. An example might be an article in a series, a newspaper column, a blog category (although <code>tag</code> may be better here) or a section of a magazine.</p> <p>There can be multiple <code>context</code> properties, either as an array of strings or objects or both.</p>"},{"location":"fep/b2b8/#generator","title":"<code>generator</code>","text":"<p>The generator property provides information about the software that generated the text. This is usually an <code>Application</code> or <code>Service</code> object with an <code>id</code> and a <code>name</code> property.</p>"},{"location":"fep/b2b8/#preview","title":"<code>preview</code>","text":"<p>In AS2, the <code>preview</code> property provides an abbreviated version of the content of the object. Especially for microblogging applications, the <code>preview</code> property is a useful fallback for supporting unrecognized object types like <code>Article</code>.</p> <p>For an article, the <code>preview</code> can be a <code>Note</code> that gives a well-formatted preview of the article content in its <code>content</code> property. For example, the <code>name</code> and <code>summary</code>. The preview content SHOULD NOT include a link to the HTML representation for the article. Additional navigation to other pages on the originating site, like category links, home page links, and other affordances like \"favourite\", \"like\", \"bookmark\" or other buttons should not be included.</p> <p>The <code>content</code> property of the <code>preview</code> should include a minimal set of HTML elements, as described in ActivityPub Primer HTML.</p> <p>Metadata on the <code>Article</code> that applies equally to the preview, such as <code>attributedTo</code>, <code>published</code>, <code>updated</code>, and <code>tag</code> can be repeated in the <code>preview</code> property. The consumer should fall back to the <code>Article</code> properties if they are not present in the <code>preview</code>.</p> <p>The <code>image</code> property of the <code>Article</code> may be included in the <code>preview</code> property as <code>attachment</code> items.</p> <p>The <code>preview</code> property may have an <code>id</code> property.</p>"},{"location":"fep/b2b8/#to-cc-bcc-bto-audience","title":"<code>to</code>, <code>cc</code>, <code>bcc</code>, <code>bto</code>, <code>audience</code>","text":"<p>As with other AS2 object types, the <code>to</code>, <code>cc</code>, <code>bcc</code>, <code>bto</code>, and <code>audience</code> properties identify the addressees of the text. For ActivityPub, they also determine the delivery targets of the text.</p> <p>The addressing properties provide an access control mechanism for AS2. Publishers and consumers should not disclose the properties of any AS2 object type, including the <code>Article</code> type, with anyone except the addressees, listed in these addressing properties, or the creator(s), listed in the <code>attributedTo</code> property.</p>"},{"location":"fep/b2b8/#sensitive","title":"<code>sensitive</code>","text":"<p>sensitive marks an article as potentially sensitive, controversial, or disturbing in the author's opinion. As a non-exhaustive list and depending on context, nudity, sexual activity, violence, or spoilers for a movie or book may be considered sensitive.</p> <p>If the sensitive flag is set, the consumer should obscure the content of the article until the user conveys intent to read the article or view embedded media.</p> <p>To help the user decide whether to read the article or view its media, the consumer should show these properties, if provided, in order:</p> <ul> <li><code>dcterms:subject</code>: a property from the Dublin Core metadata terms. Value should be a string or array of strings indicating the topic or topics discussed in the article.</li> <li><code>tag</code>: in particular, <code>Hashtag</code> names. (<code>subject</code> is preferred because hashtags are often less human-readable).</li> <li><code>name</code>: as described above, the title of the <code>Article</code>. Authors are more likely to leak sensitive material in the title, so the subject and/or hashtags should be used first.</li> <li><code>summary</code>: as described above. Only as a last resort when other properties are not defined; a well-written summary is likely to include significant excerpts or summation of the sensitive content.</li> </ul>"},{"location":"fep/b2b8/#examples","title":"Examples","text":"<p>This section includes examples of long-form text objects. Note that for brevity, the content is not actually multi-paragraph text.</p>"},{"location":"fep/b2b8/#long-form-text-with-included-content","title":"Long-form text with included content","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text.jsonld\",\n \"name\": \"Long-form text with included content\",\n \"url\": \"https://example.com/2024/11/07/long-form-text.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"&lt;p&gt;This is a long-form text object with included content. It has a title, a summary, and a full text.&lt;/p&gt;\",\n \"content\": \"&lt;p&gt;This is a long-form text object with included content. It has a title, a summary, and a full text.&lt;/p&gt;\",\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n</code></pre>"},{"location":"fep/b2b8/#long-form-text-with-external-content","title":"Long-form text with external content","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-no-content.jsonld\",\n \"name\": \"Long-form text with included content\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-no-content.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"&lt;p&gt;This is a long-form text object with external content. It has a title, a summary, and a link to the full text.&lt;/p&gt;\"\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n</code></pre>"},{"location":"fep/b2b8/#long-form-text-with-full-author-information","title":"Long-form text with full author information","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-author.jsonld\",\n \"name\": \"Long-form text with full author information\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-author.html\",\n \"attributedTo\": {\n \"type\": \"Person\",\n \"id\": \"https://example.com/evan\",\n \"name\": \"Evan Prodromou\",\n \"summary\": \"&lt;p&gt;Founder of Social Web Foundation&lt;/p&gt;\",\n \"url\": \"https://example.com/evan\",\n \"icon\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"url\": \"https://example.com/evan.png\"\n }\n },\n \"summary\": \"&lt;p&gt;This is a long-form text object with full author information. It has a title, a summary, and an URL to the full text.&lt;/p&gt;\"\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n</code></pre>"},{"location":"fep/b2b8/#long-form-text-with-embedded-images","title":"Long-form text with embedded images","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-images.jsonld\",\n \"name\": \"Long-form text with embedded images\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-images.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"&lt;p&gt;This is a long-form text object with embedded images.&lt;/p&gt;\",\n \"content\": \"&lt;p&gt;This is a long-form text object with embedded images.&lt;/p&gt;&lt;img src=\\\"https://example.com/image1.jpg\\\" alt=\\\"Image 1\\\"&gt;&lt;img src=\\\"https://example.com/image2.jpg\\\" alt=\\\"Image 2\\\"&gt;\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"id\": \"https://example.com/image1.jpg\",\n \"mediaType\": \"image/jpeg\"\n },\n {\n \"type\": \"Image\",\n \"id\": \"https://example.com/image2.jpg\",\n \"mediaType\": \"image/jpeg\"\n }\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n</code></pre>"},{"location":"fep/b2b8/#long-form-text-with-tags","title":"Long-form text with tags","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-tags.jsonld\",\n \"name\": \"Long-form text with tags\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-tags.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"&lt;p&gt;This is a long-form text object with tags.&lt;/p&gt;\",\n \"content\": \"&lt;p&gt;@&lt;a href='https://example.com/evan'&gt;evan&lt;/a&gt; made this #&lt;a href='https://example.com/tag/example'&gt;example&lt;/a&gt;.&lt;/p&gt;\",\n \"tag\": [\n {\n \"type\": \"Hashtag\",\n \"name\": \"example\",\n \"href\": \"https://example.com/tag/example\"\n },\n {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/evan\"\n }\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n</code></pre>"},{"location":"fep/b2b8/#long-form-text-with-context","title":"Long-form text with context","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-context.jsonld\",\n \"name\": \"Long-form text with context\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-context.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"&lt;p&gt;This is a long-form text object with context.&lt;/p&gt;\",\n \"content\": \"&lt;p&gt;This is a long-form text object with context.&lt;/p&gt;\",\n \"context\": [\n \"https://example.com/2024/11/07/series\",\n \"https://example.com/2024/11/07/category\"\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n</code></pre>"},{"location":"fep/b2b8/#long-form-text-with-preview","title":"Long-form text with preview","text":"<pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2025/02/17/long-form-text-preview.jsonld\",\n \"name\": \"Long-form text with preview\",\n \"url\": \"https://example.com/2025/02/17/long-form-text-preview.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"&lt;p&gt;This is the summary for a long-form text with a preview.&lt;/p&gt;\",\n \"content\": \"&lt;p&gt;This is the content for a long-form text with a preview.&lt;/p&gt;\",\n \"published\": \"2024-11-07T12:00:00Z\",\n \"image\": {\n \"type\": \"Link\",\n \"href\": \"https://example.com/image.jpg\",\n \"mediaType\": \"image/jpeg\"\n },\n \"preview\": {\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/evan\",\n \"content\": \"&lt;p&gt;&lt;strong&gt;Long-form text with preview&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;This is the summary for a long-form text with a preview.&lt;/p&gt;\",\n \"published\": \"2024-11-07T12:00:00Z\",\n \"attachment\": {\n \"type\": \"Link\",\n \"href\": \"https://example.com/image.jpg\",\n \"mediaType\": \"image/jpeg\"\n }\n }\n}\n</code></pre>"},{"location":"fep/b2b8/#long-form-text-with-senstive-content","title":"Long-form text with senstive content","text":"<p>This article includes a spoiler about the 1941 film Citizen Kane. The <code>sensitive</code> property is set to <code>true</code>, and the <code>dcterms:subject</code> property is used to indicate the topic of the article.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/miscellany\",\n {\"dcterms\": \"http://purl.org/dc/terms/\"}\n ],\n \"id\": \"https://example.com/article/1\",\n \"type\": \"Article\",\n \"name\": \"Spoiler for Citizen Kane\",\n \"summary\": \"&lt;p&gt;I am going to tell you what Rosebud was.&lt;/p&gt;\",\n \"sensitive\": true,\n \"dcterms:subject\": [\"Citizen Kane\"],\n \"contents\": \"&lt;p&gt;Rosebud was his sled!&lt;/p&gt;\",\n \"tag\": {\n \"id\": \"https://example.com/tag/citizenkane\",\n \"name\": \"citizenkane\",\n \"type\": \"Hashtag\"\n }\n}\n</code></pre>"},{"location":"fep/b2b8/#user-interface-guidance","title":"User interface guidance","text":"<p>Consumers should use their native interfaces to handle <code>Article</code> objects in an intuitive way that integrates well with other object types. The following illustrations provide examples of how <code>Article</code> objects might be displayed in a stream-oriented social web interface, such as a microblogging application. The UI elements are labelled with the properties of the <code>Article</code> object that most likely correspond to them.</p>"},{"location":"fep/b2b8/#in-stream-with-image","title":"In stream, with image","text":"<p>An example of a long-form text object with an <code>image</code> property displayed in a social stream.</p> <p></p>"},{"location":"fep/b2b8/#in-stream-without-image","title":"In stream, without image","text":"<p>An example of a long-form text object without an <code>image</code> property displayed in a social stream.</p> <p></p>"},{"location":"fep/b2b8/#in-stream-no-title","title":"In stream, no title","text":"<p>An example of a long-form text object without a <code>name</code> property displayed in a social stream.</p> <p></p>"},{"location":"fep/b2b8/#in-stream-sensitive-content","title":"In stream, sensitive content","text":"<p>An example of a long-form text object with a <code>sensitive</code> property displayed in a social stream with a content warning.</p> <p></p>"},{"location":"fep/b2b8/#in-stream-with-preview","title":"In stream, with preview","text":"<p>An example of a long-form text object with a <code>preview</code> property displayed in a social stream. Note that the consumer is responsible for displaying a link to the article's full content.</p> <p></p>"},{"location":"fep/b2b8/#references","title":"References","text":"<ul> <li>James Snell, Evan Prodromou, Activity Streams 2.0, 2017</li> <li>James Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Dave Winer, RSS 2.0 Specification, 2003</li> </ul>"},{"location":"fep/b2b8/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/bad1/","title":"Index","text":"<p>hide: - navigation description: \"[AS2-Core] provides examples 18, 19, 32 which represent the \"history\" of an object. Particularly in example 32, we see an object being Created, Updated, and Deleted. However, there is no property dedicated to advertising a collection fit for this purpose. This FEP attempts to define one.\"</p>"},{"location":"fep/bad1/#fep-bad1-object-history-collection","title":"FEP-bad1: Object history collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2023-06-15 #124 - codeberg"},{"location":"fep/bad1/#summary","title":"Summary","text":"<p>[AS2-Core] provides examples 18, 19, 32 which represent the \"history\" of an object.</p> <p>Particularly in example 32, we see an object being Created, Updated, and Deleted. However, there is no property dedicated to advertising a collection fit for this purpose. This FEP attempts to define one.</p>"},{"location":"fep/bad1/#examples-taken-from-activitystreams-20","title":"Examples taken from ActivityStreams 2.0","text":"<p>(This section is non-normative.)</p> <p>For convenience, the following examples are reproduced from the ActivityStreams 2.0 [AS2-Core] specification. Examples 18 and 19 are found in normative sections; example 32 is found in a non-normative section.</p> <p>Example 18:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"summary\": \"Object history\",\n \"type\": \"Collection\",\n \"totalItems\": 2,\n \"items\": [\n {\n \"type\": \"Create\",\n \"actor\": \"http://www.test.example/sally\",\n \"object\": \"http://example.org/foo\"\n },\n {\n \"type\": \"Like\",\n \"actor\": \"http://www.test.example/joe\",\n \"object\": \"http://example.org/foo\"\n }\n ]\n}\n</code></pre> <p>Example 19:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"summary\": \"Object history\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 2,\n \"orderedItems\": [\n {\n \"type\": \"Create\",\n \"actor\": \"http://www.test.example/sally\",\n \"object\": \"http://example.org/foo\"\n },\n {\n \"type\": \"Like\",\n \"actor\": \"http://www.test.example/joe\",\n \"object\": \"http://example.org/foo\"\n }\n ]\n}\n</code></pre> <p>Example 32:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"oa\": \"http://www.w3.org/ns/oa#\",\n \"prov\": \"http://www.w3.org/ns/prov#\",\n \"dcterms\": \"http://purl.org/dc/terms/\",\n \"dcterms:created\": {\n \"@id\": \"dcterms:created\",\n \"@type\": \"xsd:dateTime\"\n }\n }\n ],\n \"summary\": \"Editing history of a note\",\n \"type\": \"Collection\",\n \"items\": [\n {\n \"id\": \"http://example.org/activity/20150101000000\",\n \"type\": [ \"Create\", \"prov:Activity\" ],\n \"actor\": {\n \"id\": \"http://example.org/#eric\",\n \"name\": \"Eric\"\n },\n \"summary\": \"Eric wrote a note.\",\n \"object\": {\n \"id\": \"http://example.org/entry/20150101000000\",\n \"type\": [ \"Note\", \"prov:Entity\" ],\n \"attributedTo\": \"http://example.org/#eric\",\n \"content\": \"Remember... all I'm offering is the trooth. Nothing more.\"\n },\n \"published\": \"2015-01-01T00:00:00Z\"\n },\n {\n \"id\": \"http://example.org/activity/20150101000059\",\n \"type\": [ \"Update\", \"prov:Activity\", \"oa:Annotation\" ],\n \"summary\": \"Eric edited a note.\",\n \"dcterms:created\": \"2015-01-01T00:00:59Z\",\n \"dcterms:creator\": { \"@id\": \"http://example.org/#eric\" },\n \"oa:hasBody\": {\n \"id\": \"http://example.org/entry/20150101000059\",\n \"type\": [ \"Note\", \"prov:Entity\" ],\n \"content\": \"Remember... all I'm offering is the truth. Nothing more.\",\n \"prov:wasAttributedTo\": { \"@id\": \"http://example.org/#eric\" },\n \"prov:wasRevisionOf\": { \"@id\": \"http://example.org/entry/20150101000000\" }\n },\n \"oa:hasTarget\": { \"@id\": \"http://example.org/entry/20150101000000\" },\n \"oa:motivatedBy\": { \"@id\": \"oa:editing\" },\n \"prov:generated\": { \"@id\": \"http://example.org/entry/20150101000059\" },\n \"prov:wasInformedBy\": { \"@id\": \"http://example.org/activity/20150101000000\" }\n },\n {\n \"id\": \"http://example.org/activity/20150101010101\",\n \"type\": [ \"Delete\", \"prov:Activity\" ],\n \"actor\": \"http://example.org/#eric\",\n \"summary\": \"Eric deleted a note.\",\n \"object\": \"http://example.org/entry/20150101000059\",\n \"published\": \"2015-01-01T01:01:01Z\"\n }\n ]\n}\n</code></pre> <p>From these examples, Example 18 is nearly identical to Example 19, except for the type being <code>Collection</code> in Example 18, and <code>OrderedCollection</code> in Example 19. An object's history collection will necessarily be ordered chronologically, although whether the ordering should be forward chronological or reverse chronological is an open question; at the time of writing this FEP, [ActivityPub] Section 5 currently contains the following language:</p> <p>An OrderedCollection MUST be presented consistently in reverse chronological order.</p> <p>This language indicates that if <code>OrderedCollection</code> is used, the ordering MUST be reverse chronological.</p>"},{"location":"fep/bad1/#defining-the-history-special-collection","title":"Defining the <code>history</code> special collection","text":"<p>An object's history is discovered through the <code>history</code> property of an object. The <code>history</code> MUST be an <code>OrderedCollection</code>.</p> <p>The history stream contains all activities which target the object as <code>object</code>, where the <code>actor</code> matches the <code>attributedTo</code> actor. This might include Create, Update, and/or Delete activities.</p> <p>This is differentiated from <code>context</code>, which per FEP-7888 may be a collection containing related objects and activities. It is possible in certain cases to obtain an object's history by filtering such a collection for all items that contain an <code>object</code> referencing a given object, but this is not a consistent or straightforward way of doing so.</p>"},{"location":"fep/bad1/#terms-defined-by-this-fep","title":"Terms defined by this FEP","text":"<p>In the associated context, we define the following terms:</p>"},{"location":"fep/bad1/#history","title":"history","text":"<p>URI : <code>https://w3id.org/fep/bad1/history</code></p> <p>Notes : A collection containing all activities performed by the author related to this object's representation.</p> <p>JSON-LD definition (<code>@id</code> pending adoption of FEP-9606):</p> <pre><code>{\n \"@context\": {\n \"history\": {\n \"@id\": \"https://w3id.org/fep/bad1/history\",\n \"@type\": \"@id\"\n }\n }\n}\n</code></pre> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://w3id.org/fep/bad1/history\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/some-file\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Document\",\n \"url\": \"https://example.com/404\",\n \"history\": {\n \"id\": \"https://example.com/some-object/log\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://example.com/some-file/log/3\",\n \"type\": \"Delete\",\n \"object\": \"https://example.com/some-file\"\n },\n {\n \"id\": \"https://example.com/some-file/log/2\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-file\",\n \"url\": \"https://example.com/storage/hash2\"\n }\n },\n {\n \"id\": \"https://example.com/some-file/log/1\",\n \"type\": \"Create\",\n \"object\": {\n \"type\": \"Document\",\n \"url\": \"https://example.com/storage/hash1\"\n }\n }\n ]\n }\n}\n</code></pre>"},{"location":"fep/bad1/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[AS2-Core] James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> </ul>"},{"location":"fep/bad1/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c0e0/","title":"FEP-c0e0: Emoji reactions","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2024-08-08 #384 Discussions codeberg"},{"location":"fep/c0e0/#summary","title":"Summary","text":"<p>This document describes how emoji reactions are implemented in ActivityPub network.</p>"},{"location":"fep/c0e0/#history","title":"History","text":"<p>Misskey supports emoji reactions since version 10.97.0 (2019). Pleroma supports emoji reactions since version 2.0.0 (2020).</p>"},{"location":"fep/c0e0/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/c0e0/#emojireact-activity","title":"EmojiReact activity","text":"<p><code>EmojiReact</code> activity type is considered to be a part of LitePub vocabulary. Its full IRI is <code>http://litepub.social/ns#EmojiReact</code>.</p> <p>This activity is similar to <code>Like</code> activity. In addition to standard properties of <code>Like</code> activity, <code>EmojiReact</code> activity MUST have a <code>content</code> property. Reaction content MUST be either a single unicode grapheme, or a name of a custom emoji enclosed in colons (shortcode).</p> <p>If custom emoji is used, <code>EmojiReact</code> activity MUST have a <code>tag</code> property containing a single <code>Emoji</code> object. The value of its <code>name</code> property MUST contain the name of the custom emoji, and it SHOULD be enclosed in colons. The embedded <code>Emoji</code> can originate from a server that is different from the actor's server.</p> <p>An actor can generate multiple <code>EmojiReact</code> activities for a single <code>object</code>. However, implementers MAY choose to not allow more than one reaction with the same emoji, or more than one reaction per object.</p> <p>Example with unicode emoji:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"litepub\": \"http://litepub.social/ns#\",\n \"EmojiReact\": \"litepub:EmojiReact\"\n }\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"content\": \"\ud83d\udd25\",\n \"id\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"object\": \"https://bob.social/objects/57caeb99-424c-4692-b74f-0a6682050932\",\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"EmojiReact\"\n}\n</code></pre> <p>Example with custom emoji:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"Emoji\": \"toot:Emoji\",\n \"litepub\": \"http://litepub.social/ns#\",\n \"EmojiReact\": \"litepub:EmojiReact\"\n }\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"content\": \":blobwtfnotlikethis:\",\n \"id\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"object\": \"https://bob.social/objects/57caeb99-424c-4692-b74f-0a6682050932\",\n \"tag\": [\n {\n \"icon\": {\n \"mediaType\": \"image/png\",\n \"type\": \"Image\",\n \"url\": \"https://alice.social/files/1b0510f2-1fb4-43f5-a399-10053bbd8f0f\"\n },\n \"id\": \"https://alice.social/emojis/blobwtfnotlikethis\",\n \"name\": \":blobwtfnotlikethis:\",\n \"type\": \"Emoji\",\n \"updated\": \"2024-02-07T02:21:46.497Z\"\n }\n ],\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"EmojiReact\"\n}\n</code></pre>"},{"location":"fep/c0e0/#like-with-content","title":"Like with content","text":"<p>Emoji reaction can also be represented as a <code>Like</code> activity. This variant of emoji reaction will processed by non-supporting implementations as a regular \"like\", and when that is preferable, implementers MAY use <code>Like</code> type instead of <code>EmojiReact</code> type.</p> <p>Implementations MUST process <code>Like</code> with <code>content</code> in the same way as <code>EmojiReact</code> activities.</p>"},{"location":"fep/c0e0/#undo-reaction","title":"Undo reaction","text":"<p>Emoji reactions can be retracted using a standard <code>Undo</code> activity:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"id\": \"https://alice.social/activities/99b8f47b-f3a9-4cf5-94a2-95352e7462d6\",\n \"object\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"Undo\"\n}\n</code></pre>"},{"location":"fep/c0e0/#emojireactions-collection","title":"<code>emojiReactions</code> collection","text":"<p>The list of emoji reactions to an object can be exposed using the <code>emojiReactions</code> property. Its full IRI is <code>http://fedibird.com/ns#emojiReactions</code>.</p> <p>The URL specified by this property MUST resolve to a collection containing <code>Like</code> (with <code>content</code>) and <code>EmojiReact</code> activities.</p>"},{"location":"fep/c0e0/#implementations","title":"Implementations","text":"<p>This document is based on implementations of emoji reactions in Misskey, Pleroma and Fedibird.</p> <p>A number of other projects announced support for this FEP after it was published:</p> <ul> <li>Hollo</li> <li>Mitra</li> <li>PieFed</li> <li>Iceshrimp.NET</li> </ul>"},{"location":"fep/c0e0/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>LitePub contributors, LitePub protocol suite, 2019</li> <li>silverpill, FEP-9098: Custom emojis, 2025</li> </ul>"},{"location":"fep/c0e0/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c118/","title":"FEP-c118: Content licensing support","text":"Authors Status Date received Tracking issue Discussions Repository Tim Bray tbray@texgtuality.com <code>DRAFT</code> 2023-01-16 #57 - codeberg"},{"location":"fep/c118/#summary","title":"Summary","text":"<p>Currently, popular Fediverse software does very little to establish the legal status of posts. Controversy over indexing and scraping the Fediverse is common. The hope is that providing a legal framework to express the desires of users as to how their content may be re-used might bring order to this debate.</p>"},{"location":"fep/c118/#scenario","title":"Scenario","text":"<p>Dramatis Personae: * A somewhat-technical new Fediverse arrival, hereinafter Noob * Existing Fediverse Voices, hereinafer EFV1, EFV2, etc</p> <p>Noob: Hey, I want to crawl the Fediverse and build an index so we can\u2026</p> <p>EFV1: Stop! Go back! Don't!</p> <p>EFV2: We prefer not to do that here because we want to avoid into big-tech surveillance capitalism.</p> <p>Noob: But I mean they're all just URLs, right?</p> <p>EFV3: Burn the witch!</p> <p>EFV4: Unclean!</p> <p>Noob: Oh, I guess you don't want me to do this?</p> <p>EFV5: Never!</p> <p>Noob: Shouldn't you post the rules somewhere? I mean, lots of people are already doing this.</p> <p>EFV6: Defederate his instance!</p>"},{"location":"fep/c118/#discussion","title":"Discussion","text":"<p>While Fediverse search has become less controversial since the previous revision of this FEP, the issue remains highly relevant with the advent of crawlers from AI companies gathering material to feed into LLMs.</p> <p>At the moment, the Fediverse privacy story is unsatisfactory. Unless a user really understands the visibility levels, every post they make instantly becomes part of the permanent indelible Internet memory. There are no technical and very few legal barriers to prevent anyone from doing anything with retrieved post content.</p> <p>In developed societies, when there are behaviors that are logistically possible but society would like to regulate, legal tools are frequently applied. Examples would be dangerous driving and public smoking.</p> <p>A content-licensing framework for the Fediverse could include some or all of the following:</p> <ol> <li>Prevent access to posts without the accessor having somehow acknowledged the content license that applies.</li> <li>Build a menu of content-license choices, probably starting from a Creative Commons basis.</li> <li>One parameter of the content licenses should be temporal; i.e., license access to posts but only for two weeks (or some other interval).</li> <li>Make it easy and straightforward for individuals posting to the Fediverse to pick a default content license for their posts, and also to apply other licensing choices to individual posts.</li> <li>Arrange that when one user follows another, the following user must acknowledge the default content licensing options of the followed user.</li> <li>Since most users will just take the defaults, each instance should carefully choose and prominently display its content-licensing defaults.</li> </ol> <p>I note that this document template comes with a carefully-considered Creative Commons license. Do not the contributions of the humans who are enriching the Fediverse deserve a comparable level of protection?</p>"},{"location":"fep/c118/#caveat","title":"Caveat","text":"<p>Note that establishing a legal content-licensing framework will not prevent certain bad actors from scraping the Fediverse and mis-using the harvested data. That's OK because it will establish a legal framework that will present a significant barrier to commercial data harvesters, and provide a tool to combat certain classes of mis-use.</p>"},{"location":"fep/c118/#see-also","title":"See also:","text":"<ul> <li>Eugen Rochko, Cage the Mastodon in particular \"Design Decisions\"</li> <li>@pamela@bsd.network, Hacky folks, please resist finding ways to scrape the fediverse\u2026.</li> <li>GitHub discussion, Controlling availability to search</li> <li>Tim Bray Private and Public Mastodon</li> <li>IETF AI Preferences Working Group</li> </ul>"},{"location":"fep/c118/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c16b/","title":"FEP-c16b: Formatting MFM functions","text":"Authors Status Date received Tracking issue Discussions Repository ilja ilja@ilja.space <code>DRAFT</code> 2024-08-10 #383 Discussions codeberg"},{"location":"fep/c16b/#summary","title":"Summary","text":"<p>This FEP recommends a method for formatting MFM in ActivityPub post content using HTML with custom classes and [data-* attributes]. Furthermore, this FEP provides a new extension term to indicate that this HTML representation is used.</p>"},{"location":"fep/c16b/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119]. \u201cFediverse implementation\u201d or \"implementation\u201d is to be interpreted as an ActivityPub conformant Client, ActivityPub conformant Server or ActivityPub conformant Federated Server as described in [ActivityPub].</p>"},{"location":"fep/c16b/#acknowledgements","title":"Acknowledgements","text":"<p>(This section is non-normative.)</p> <p>The core idea behind this FEP is attributed to Johan150 on the Foundkey issue tracker[1]. Specifically, the proposal for representing the MFM functions in HTML using <code>span</code> elements with custom classes and <code>data-*</code> attributes.</p>"},{"location":"fep/c16b/#history","title":"History","text":"<p>(This section is non-normative.)</p> <p>It is common for a Fediverse implementation to allow a markup language as input for text. Federation of this content generally happens by converting this text input to a proper HTML representation that another implementation can easily understand. This HTML representation is federated over ActivityPub using the <code>content</code> property of the [ActivityStreams] Object. Meanwhile, the <code>source</code> property, which was added by ActivityPub, can optionally be used to provide the original input and input format.</p> <p>Misskey has been using its own [Markup language For Misskey], also known as MFM. MFM is mostly composed of a combination of HTML, Markdown, Katex, and custom MFM functions of the form <code>$[name content]</code>. Properly displaying what these MFM functions intent, generally requires complex CSS or even Javascript. As such, only a watered down HTML representation is provided in the <code>content</code>. This representation can strip out so much information that a receiving implementation cannot always properly display what the author meant to convey. The only option for a receiving implementation who wants to display the MFM correctly, is to re-parse the <code>content</code> of the <code>source</code> property when it's <code>mediaType</code> has value <code>text/x.misskeymarkdown</code>. This causes not only unnecessary overhead, but also compatibility issues, especially when two implementations are using a different parser.</p>"},{"location":"fep/c16b/#mfm-functions","title":"MFM Functions","text":"<p>(This section is non-normative.)</p> <p>An MFM Function consists of a name, optionally one or more attributes who may or may not have a value, and a content. It has the form <code>$[name.attribute1,attribute2=value content]</code>.</p>"},{"location":"fep/c16b/#examples","title":"Examples","text":"<p>(This section is non-normative.)</p> <pre><code>$[x2 Misskey expands the world of the Fediverse]\n$[jelly.speed=2s Misskey expands the world of the Fediverse]\n$[spin.x,speed=0.5s Misskey expands the world of the Fediverse]\n</code></pre>"},{"location":"fep/c16b/#html-representation-of-mfm-functions","title":"HTML representation of MFM functions","text":"<p>When representing the MFM function in HTML, a <code>span</code> element MUST be used. The <code>span</code> element MUST have a class <code>mfm-name</code> where <code>name</code> is the name of the MFM function. When the MFM function has attributes, the <code>span</code> element MUST have a <code>data-*</code> attribute <code>data-mfm-attributename</code> for each attribute, where <code>attributename</code> is the name of the attribute in question. If the attribute of the MFM function has a value, the <code>data-*</code> attribute MUST have the same value.</p>"},{"location":"fep/c16b/#examples_1","title":"Examples","text":"<p>(This section is non-normative.)</p> <p>This turns the previous examples into</p> <pre><code>&lt;span class=\"mfm-x2\"&gt;Misskey expands the world of the Fediverse&lt;/span&gt;\n&lt;span class=\"mfm-jelly\" data-mfm-speed=\"2s\"&gt;Misskey expands the world of the Fediverse&lt;/span&gt;\n&lt;span class=\"mfm-flip\" data-mfm-x data-mfm-speed=\"0.5s\"&gt;Misskey expands the world of the Fediverse&lt;/span&gt;\n</code></pre>"},{"location":"fep/c16b/#other-mfm-components","title":"Other MFM components","text":"<p>While this FEP focuses on the representation of MFM functions, MFM consist of more than just these MFM functions. The HTML representation in the <code>content</code> property MUST be correct and complete such that a receiving implementation can use it to correctly display what the MFM conveys.</p> <p>HTML and Markdown are generally expressed correctly in the <code>content</code> property, and are both widely used on the Fediverse. Therefor these are not considered problematic in the same sense as the MFM functions are.</p> <p>Katex suffers the same problem of generally not being expressed properly in the <code>content</code> property. For properly expressing the Katex input as HTML, [FEP-dc88] SHOULD be used.</p>"},{"location":"fep/c16b/#discovery","title":"Discovery","text":"<p>(This section is non-normative.)</p> <p>When compatibility with an MFM capable, but FEP-c16b non-compliant, implementation is desired, the <code>source</code> may still need to be federated using <code>\"mediaType\": \"text/x.misskeymarkdown\"</code>. Meanwhile, the incoming <code>source</code> from this implementation may still need to be re-parsed. As such, a discovery mechanism is required to signal to a FEP-c16b compliant implementation that the <code>content</code> may be used directly.</p> <p>For this purpose, a new extension term is proposed, as described in [FEP-888d].</p>"},{"location":"fep/c16b/#htmlmfm","title":"htmlMfm","text":"<p>To signal that the <code>content</code> is FEP-c16b compliant, the implementation MAY use the extension term <code>htmlMfm</code> with value <code>true</code>. When the <code>content</code> is not FEP-c16b compliant, the implementation MUST NOT use the extension term <code>htmlMfm</code> with value <code>true</code>, but MAY use the extension term <code>htmlMfm</code> with value <code>false</code>.</p> <ul> <li>Description: A flag to indicate that the <code>content</code> is FEP-c16b compliant.</li> <li>URI: <code>https://w3id.org/fep/c16b#htmlMfm</code></li> <li>Domain: <code>https://www.w3.org/ns/activitystreams#Object</code></li> <li>Range: Boolean</li> </ul>"},{"location":"fep/c16b/#example","title":"Example","text":"<p>(This section is non-normative.)</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"htmlMfm\": \"https://w3id.org/fep/c16b#htmlMfm\"\n }\n ],\n \"content\": \"&lt;span class=\\\"mfm-spin\\\" data-mfm-x data-mfm-speed=\\\"0.5s\\\"&gt;Misskey expands the world of the Fediverse&lt;/span&gt;\",\n \"source\": {\n \"content\": \"$[spin.x,speed=0.5s Misskey expands the world of the Fediverse]\",\n \"mediaType\": \"text/x.misskeymarkdown\"\n },\n \"htmlMfm\": true\n}\n</code></pre>"},{"location":"fep/c16b/#implementations","title":"Implementations","text":"<ul> <li>Akkoma</li> </ul>"},{"location":"fep/c16b/#references","title":"References","text":"<ul> <li>[data-* attributes]: Part of the HTML Living Standard</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[1] Johan150, Federate MFM in content field using HTML, 2023</li> <li>[ActivityStreams] James M Snell, Evan Prodromou, ActivityStreams 2.0, 2017</li> <li>[Markup language For Misskey], MFM</li> <li>[FEP-dc88] Calvin Lee, FEP-dc88: Formatting Mathematics, 2023</li> <li>[FEP-888d] a, FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces, 2023</li> </ul>"},{"location":"fep/c16b/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c180/","title":"FEP-c180: Problem Details for ActivityPub","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org <code>DRAFT</code> 2025-03-11 #531 Discussions codeberg"},{"location":"fep/c180/#summary","title":"Summary","text":"<p>ActivityPub is a RESTful API and HTTP-based protocol for standards-based social networking, but does not specify an error format. This document provides a profile of the Problem Details for HTTP APIs specification (RFC 9457) for use with ActivityPub.</p>"},{"location":"fep/c180/#introduction","title":"Introduction","text":"<p>ActivityPub is the W3C standard for federated social networking. It describes a standard RESTful API for social applications that allows people to create and share social content like text, images, audio and video, as well as reacting to social content and building a social graph of connections between people. ActivityPub also includes a standard protocol for federating social content between servers, so that people on different social platforms can interact with each other.</p> <p>Both the client API and the server-to-server protocol are based on HTTP, and use HTTP status codes to indicate the success or failure of requests. However, HTTP status codes are not always sufficient to describe the nature of an error, or to provide enough information for a client to recover from an error.</p> <p>The Problem Details for HTTP APIs specification (RFC 9457) describes a way to provide more detailed information about errors in an HTTP response. The format includes a machine-readable description of the error, as well as a human-readable explanation, additional data about the error, and a link to more information about the error.</p> <p>This document describes a number of specific error types that are relevant to ActivityPub, and provides guidance on how to use the Problem Details for HTTP APIs format with ActivityPub.</p>"},{"location":"fep/c180/#motivating-use-cases","title":"Motivating use cases","text":"<ul> <li>As an ActivityPub API client developer, I want a machine-readable description of an error from the server, so that I can provide actionable and localised error messages to the user.</li> <li>As an ActivityPub API client developer, I want to know if I can recover from an erroneous request, so that I can provide a more robust experience.</li> <li>As a server developer, I want to know if I can recover from an erroneous request, so that I can provide more reliable communications.</li> <li>As a server developer, I want to know if a remote server would ever accept the activity I am sending, so that I can save resources by not sending activities that will be rejected.</li> </ul>"},{"location":"fep/c180/#specification","title":"Specification","text":"<p>ActivityPub servers SHOULD use the Problem Details for HTTP APIs format to describe errors in responses to HTTP requests. The format is described in RFC 9457.</p> <p>These types of HTTP request in the ActivityPub API and federation protocol SHOULD use the Problem Details format for errors (abbreviations used in this document are in parentheses):</p> <ul> <li>GET requests for objects, including activities, actors, and collections (\"get\")</li> <li>POST requests to the outbox of an actor, for the creation of new activities (\"outbox\")</li> <li>POST requests for media uploads (\"media upload\")</li> <li>POST requests for proxy URLs (\"proxy\")</li> <li>POST requests to the inbox of an actor, for the delivery of remote activities (\"inbox\")</li> <li>POST requests to the shared inbox for a collection of actors (\"shared inbox\")</li> </ul> <p>Other ActivityPub requests MAY use the Problem Details format.</p> <p>The <code>about:blank</code> type defined in RFC 9457 MAY be used for problems that do not have a specific type. Other types registered in the IANA Problem Type Registry MAY be used for specific problems.</p>"},{"location":"fep/c180/#problem-types-for-activitypub","title":"Problem types for ActivityPub","text":"<p>Problem types in this vocabulary use the https://w3id.org/fep/c180 prefix.</p> <p>Each of the following problem types lists the applicability of the problem (per the list of request types above), the type URI, the title of the problem, the HTTP status code that SHOULD be used, and additional fields that MAY be included in the response.</p>"},{"location":"fep/c180/#unsupported-type","title":"Unsupported type","text":"<ul> <li>applicability: inbox, outbox, sharedInbox, media upload</li> <li>type: https://w3id.org/fep/c180#unsupported-type</li> <li>title: Unsupported type</li> <li>status: 400 Bad Request</li> <li>additional fields:</li> <li>id: The <code>id</code> of the object with the unsupported type</li> <li>type: The <code>type</code> that is not supported</li> </ul> <p>This indicates that the type of the activity, or one of the objects referred to by the activity, is not supported by the API server or the receiving federation protocol server.</p>"},{"location":"fep/c180/#object-does-not-exist","title":"Object does not exist","text":"<ul> <li>applicability: inbox, outbox, sharedInbox, media upload</li> <li>type: https://w3id.org/fep/c180#object-does-not-exist</li> <li>title: Object does not exist</li> <li>status: 400 Bad Request</li> <li>additional fields:</li> <li>id: The <code>id</code> of the object that does not exist</li> </ul> <p>The activity refers to an object in one of its properties, such as <code>object</code>, <code>target</code>, or an addressing property, but the object does not exist. Recursively connected objects, like the <code>inReplyTo</code> property of the <code>object</code> property, can also be checked.</p> <p>Note that this type is distinct from an endpoint returning a 404 Not Found status code for a GET request for an object that does not exist, or for posting to an endpoint that does not exist.</p>"},{"location":"fep/c180/#duplicate-delivery","title":"Duplicate delivery","text":"<ul> <li>applicability: inbox, sharedInbox</li> <li>type: https://w3id.org/fep/c180#duplicate-delivery</li> <li>title: Duplicate delivery</li> <li>status: 400 Bad Request</li> <li>additional fields:</li> <li>id: The <code>id</code> of the activity that was previously delivered</li> </ul> <p>The activity has already been delivered to the <code>inbox</code> or to all accounts using the <code>sharedInbox</code>.</p> <p>Note that this is different from Redundant activity. Duplicate delivery is when the same activity is delivered multiple times. Redundant activity is when two different activities that do the same thing are received.</p>"},{"location":"fep/c180/#redundant-activity","title":"Redundant activity","text":"<ul> <li>applicability: inbox, outbox, sharedInbox, media upload</li> <li>type: https://w3id.org/fep/c180#redundant-activity</li> <li>title: Redundant activity</li> <li>status: 400 Bad Request</li> <li>additional fields:</li> <li>duplicate: The <code>id</code> of the previous activity</li> </ul> <p>The activity is a duplicate of a previous activity which has already been processed by the server, and which has not been reverted with an <code>Undo</code> activity or with other activities. The <code>duplicate</code> property contains the <code>id</code> of the previous activity.</p> <p>Activity types that are often treated as idempotent and can only be processed once include <code>Create</code>, <code>Delete</code>, <code>Follow</code>, <code>Accept</code>, <code>Reject</code>, <code>Add</code>, <code>Remove</code>, <code>Block</code>, <code>Undo</code>, and <code>Like</code>. Other activity types like <code>Announce</code> are treated as idempotent by some servers.</p> <p>Note that this is different from Duplicate delivery.Redundant activity is when two different activities that do the same thing are received. Duplicate delivery is when the same activity is delivered multiple times.</p>"},{"location":"fep/c180/#approval-required","title":"Approval required","text":"<ul> <li>applicability: inbox, outbox, sharedInbox, media upload</li> <li>type: https://w3id.org/fep/c180#approval-required</li> <li>title: Approval required</li> <li>status: 202 Accepted</li> <li>additional fields:</li> <li>approver: The <code>id</code> of the actor who must approve the activity</li> </ul> <p>The activity will be delivered to the addressees, but may not have side effects applied until it is approved by an administrator, moderator, or one of the addressees.</p> <p>For example, a <code>Follow</code> activity may be delivered to the addressee's inbox, but the <code>Accept</code> activity may not be returned until the addressee approves the follow request.</p> <p>As another example, a <code>Create</code> activity with an <code>object</code> property with an <code>inReplyTo</code> property may require approval by the author of the replied-to object before it is added to that object's <code>replies</code> collection.</p> <p>This problem type would be used for activities that are manually approved, not automatically approved.</p>"},{"location":"fep/c180/#not-an-actor","title":"Not an actor","text":"<ul> <li>applicability: inbox, outbox, sharedInbox, media upload</li> <li>type: https://w3id.org/fep/c180#not-an-actor</li> <li>title: Not an actor</li> <li>status: 400 Bad Request</li> <li>additional fields:</li> <li>id: The <code>id</code> of the object that is not an actor</li> </ul> <p>The activity refers to an object in one of its properties, such as <code>object</code> or an addressing property, that requires an ActivityPub actor to be correctly processed, but the object is not an actor.</p>"},{"location":"fep/c180/#principal-actor-mismatch","title":"Principal-actor mismatch","text":"<ul> <li>applicability: inbox, outbox, sharedInbox, media upload</li> <li>type: https://w3id.org/fep/c180#principal-actor-mismatch</li> <li>title: Principal-actor mismatch</li> <li>status: 400 Bad Request</li> <li>additional fields:</li> <li>principal: The <code>id</code> of the principal</li> <li>actor: The <code>id</code> of the actor</li> </ul> <p>The security principal of the request, such as the authenticated user, does not match the actor that is the subject of the activity.</p> <p>For example, the authenticated user is trying to send a <code>Follow</code> activity to another actor, but the <code>actor</code> property of the activity is not the authenticated user.</p> <p>Note that it is possibly valid for the <code>actor</code> property of an activity to not be the same as the authenticated user; for example, with inbox forwarding.</p>"},{"location":"fep/c180/#actor-not-authorized","title":"Actor not authorized","text":"<ul> <li>applicability: inbox, outbox, sharedInbox, media upload</li> <li>type: https://w3id.org/fep/c180#actor-not-authorized</li> <li>title: Actor not authorized</li> <li>status: 403 Forbidden</li> <li>additional fields:</li> <li>actor: The <code>id</code> of the actor</li> <li>resource: The <code>id</code> of the resource the actor is unauthorized to access</li> </ul> <p>The actor is not authorized to perform the given activity on, to, or from a given object.</p> <p>For example, with an <code>Add</code> activity, the actor is not authorized to add the <code>object</code> to the <code>target</code> collection.</p> <p>As another example, with a <code>Delete</code> activity, the actor is not authorized to delete the <code>object</code>.</p> <p>Another example would be a <code>Like</code> activity for an object where the actor has been blocked by the creator of the object.</p> <p>Note that this type is distinct from Principal not authorized, which indicates that the authenticated user is not authorized to perform the activity.</p>"},{"location":"fep/c180/#principal-not-authorized","title":"Principal not authorized","text":"<ul> <li>applicability: inbox, outbox, sharedInbox, media upload, proxy, get</li> <li>type: https://w3id.org/fep/c180#principal-not-authorized</li> <li>title: Principal not authorized</li> <li>status: 403 Forbidden</li> <li>additional fields:</li> <li>principal: The <code>id</code> of the principal</li> <li>resource: The <code>id</code> of the resource the principal is unauthorized to access</li> </ul> <p>This problem type indicates that the security principal, such as the authenticated user, is not authorized to perform the given activity on, to, or from a given object.</p> <p>It can also be used to indicate that the authenticated user is not authorized to GET an object, either directly or through a proxy.</p> <p>This type is distinct from actor not authorized. This type should only be used when the principal and the actor are distinct, or when there is no actor (such as with GET requests).</p>"},{"location":"fep/c180/#client-not-authorized","title":"Client not authorized","text":"<ul> <li>applicability: outbox, media upload, proxy, get</li> <li>type: https://w3id.org/fep/c180#client-not-authorized</li> <li>title: Client not authorized</li> <li>status: 403 Forbidden</li> <li>additional fields:</li> <li>client: The <code>id</code> of the client</li> </ul> <p>This problem type is applicability GET and POST requests.</p> <p>This indicates that the client is not authorized to perform the given activity. The security principal, like the authenticated user, may be authorized, but the client is not.</p> <p>An example would be a client that uses OAuth 2.0 to authenticate, perhaps with FEP-d8c2, but has not been granted the proper scopes to perform the activity.</p> <p>This error type implies, but does not promise, that the security principal would be authorized to perform the activity with a different client.</p> <p>This problem type is primarily for the ActivityPub API, between a client and a server. In the case of the federation protocol, where the principal is closely tied to the platform that is sending the activity, there may not be a meaningful way for the principal to interact without the client (in this case, their server).</p>"},{"location":"fep/c180/#unsupported-media-type","title":"Unsupported media type","text":"<ul> <li>applicability: media upload</li> <li>type: https://w3id.org/fep/c180#unsupported-media-type</li> <li>title: Unsupported media type</li> <li>status: 400 Bad Request</li> <li>additional fields:</li> <li>filename: The filename of the media</li> <li>mediaType: The unsupported media type</li> </ul> <p>The media type of the uploaded file is not supported by the server.</p>"},{"location":"fep/c180/#media-too-large","title":"Media too large","text":"<ul> <li>applicability: media upload</li> <li>type: https://w3id.org/fep/c180#media-too-large</li> <li>title: Media too large</li> <li>status: 413 Payload Too Large</li> <li>additional fields:</li> <li>filename: The filename of the media</li> <li>size: The size of the media in bytes</li> <li>maxSize: The maximum size of the media in bytes</li> </ul> <p>The uploaded file is too large to be processed by the server.</p>"},{"location":"fep/c180/#no-applicable-addressees","title":"No applicable addressees","text":"<ul> <li>applicability: inbox, sharedInbox</li> <li>type: https://w3id.org/fep/c180#no-applicable-addressees</li> <li>title: No applicable addressees</li> <li>status: 400 Bad Request</li> </ul> <p>The activity does not have any addressees that are applicable to the server. This could be because the activity has no <code>to</code>, <code>cc</code>, or <code>bcc</code> properties, or because the addressees do not have inboxes on the server.</p> <p>Another case is where the addressees are Collections, and no actor in the collection has an inbox on the server. For example, if an activity is addressed to the actor's followers collection, but none of the followers have inboxes on the server.</p>"},{"location":"fep/c180/#rate-limit-exceeded","title":"Rate limit exceeded","text":"<ul> <li>applicability: outbox, media upload, get, proxy, inbox, sharedInbox</li> <li>type: https://w3id.org/fep/c180#rate-limit-exceeded</li> <li>title: Rate limit exceeded</li> <li>status: 429 Too Many Requests</li> </ul> <p>The client or the security principal has exceeded the rate limit for the given activity. The server MAY include a <code>Retry-After</code> header in the response to indicate when the rate limit will be reset.</p> <p>This problem type is primarily applicable to the ActivityPub API, between a client and a server. It is unusual for a server to rate limit incoming activities over the federation protocol.</p>"},{"location":"fep/c180/#privacy-considerations","title":"Privacy considerations","text":"<p>Some of the problem types in this document may reveal information about the server's internal state, such as the existence of an object, the relationship of an object to an actor, or a relationship between actors. Servers should be careful to avoid revealing sensitive information in error messages.</p>"},{"location":"fep/c180/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Christine Lemmer Webber, Amy Guy, et. al., Media Upload, 2018</li> <li>M. Nottingham, E. Wilde, S. Dalal, RFC 9457: Problem Details for HTTP APIs, 2023</li> </ul>"},{"location":"fep/c180/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c390/","title":"FEP-c390: Identity Proofs","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2022-11-23 #34 Discussions codeberg"},{"location":"fep/c390/#summary","title":"Summary","text":"<p>This proposal describes a mechanism of creating verifiable links between Decentralized Identifiers and ActivityPub actor profiles.</p> <p>Potential applications include: identity verification, end-to-end encryption and account migrations.</p>"},{"location":"fep/c390/#history","title":"History","text":"<ul> <li>Mastodon implemented identity proofs in 2019. Keybase platform was used as an identity provider, but the integration was later removed.</li> <li>Keyoxide can create off-protocol identity proofs for Fediverse profiles using OpenPGP.</li> </ul>"},{"location":"fep/c390/#identity-proofs","title":"Identity proofs","text":"<p>Identity proof is a JSON document that represents a verifiable bi-directional link between a Decentralized Identifier and an ActivityPub actor.</p> <p>It MUST contain the following properties:</p> <ul> <li><code>type</code> (REQUIRED): the value of <code>type</code> property MUST be the string <code>VerifiableIdentityStatement</code>.</li> <li><code>subject</code> (REQUIRED): the decentralized identifier (DID) that represents a cryptographic key belonging to an actor.</li> <li><code>alsoKnownAs</code> (REQUIRED): the value of this property MUST match the actor ID.</li> <li><code>proof</code> (REQUIRED): the data integrity proof, as defined by Data Integrity specification.</li> </ul> <p>The document MAY contain additional properties.</p> <p>Identity proofs SHOULD be attached to an actor object, under the <code>attachment</code> property.</p>"},{"location":"fep/c390/#proof-generation","title":"Proof generation","text":"<p>The identity proof document MUST contain a data integrity proof, which includes a cryptographic proof and parameters required to verify it. It MUST be created according to the Data Integrity specification, section 4.2 Add Proof. The value of <code>verificationMethod</code> property of the data integrity proof MUST match the value of <code>subject</code> property of the identity proof document.</p> <p>The resulting data integrity proof MUST be added to identity proof document under the <code>proof</code> key.</p> <p>Example of an actor object linked to a <code>did:key</code> identifier:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/c390\"\n ],\n \"type\": \"Person\",\n \"id\": \"https://server.example/users/alice\",\n \"inbox\": \"https://server.example/users/alice/inbox\",\n \"outbox\": \"https://server.example/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"alsoKnownAs\": \"https://server.example/users/alice\",\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n }\n ]\n}\n</code></pre> <p>WARNING: The example above uses eddsa-jcs-2022 cryptosuite, which doesn't have stable specification.</p>"},{"location":"fep/c390/#proof-verification","title":"Proof verification","text":"<p>The consuming implementations MUST check the authenticity of identity proof document by verifying its data integrity proof. If the proof can not be verified, or if the value of <code>verificationMethod</code> property of the data integrity proof doesn't match the value of <code>subject</code> property of the identity proof, or if the value of <code>alsoKnownAs</code> property of the identity proof doesn't match the actor ID, the identity proof MUST be discarded.</p> <p>Verification process MUST follow the Data Integrity specification, section 4.4 Verify Proof.</p>"},{"location":"fep/c390/#linking-identities","title":"Linking identities","text":"<p>The consuming implementations SHOULD treat identities denoted by <code>subject</code> and <code>alsoKnownAs</code> properties of identity proof as belonging to the same entity.</p> <p>If two actors have identity proofs with the same <code>subject</code> property, they SHOULD be treated as different identities of the same entity.</p>"},{"location":"fep/c390/#implementation-notes","title":"Implementation notes","text":"<p>Servers MUST present identity proofs to clients in their original form. Clients MAY perform independent verification of identity proofs if needed.</p>"},{"location":"fep/c390/#test-vectors","title":"Test vectors","text":"<p>See fep-c390.feature</p>"},{"location":"fep/c390/#implementations","title":"Implementations","text":"<ul> <li>Mitra</li> <li>Streams</li> <li>Forte</li> </ul>"},{"location":"fep/c390/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Manu Sporny, Dave Longley, Markus Sabadell, Drummond Reed, Orie Steele, Christopher Allen, Decentralized Identifiers (DIDs) v1.0, 2022</li> <li>Dave Longley, Manu Sporny, Verifiable Credential Data Integrity 1.0, 2025</li> </ul>"},{"location":"fep/c390/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c4ad/","title":"Index","text":"<p>hide: - navigation description: \"This document proposes a standard for managing viewership history across the Fediverse. It addresses the common issue of posts being repeatedly shown to users on different clients. The goal is to enable servers to track which posts have been viewed by individual users and ensure that clients do not display these posts again. This proposal aims to enhance user experience by preventing the redundant display of already seen posts, commonly requested as \"Hide already seen posts\" or \"stop repeating already seen posts\".\"</p>"},{"location":"fep/c4ad/#fep-c4ad-viewership-history","title":"FEP-c4ad: Viewership History","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com <code>DRAFT</code> 2024-07-15 #367 Discussions codeberg"},{"location":"fep/c4ad/#summary","title":"Summary","text":"<p>This document proposes a standard for managing viewership history across the Fediverse. It addresses the common issue of posts being repeatedly shown to users on different clients. The goal is to enable servers to track which posts have been viewed by individual users and ensure that clients do not display these posts again. This proposal aims to enhance user experience by preventing the redundant display of already seen posts, commonly requested as \"Hide already seen posts\" or \"stop repeating already seen posts\".</p>"},{"location":"fep/c4ad/#creating-a-viewership-record","title":"Creating a Viewership Record","text":"<p>When a user views a post, the server should create a record of this event. This record will include the following properties:</p> <ul> <li><code>id</code> (REQUIRED): the unique identifier of the viewership record.</li> <li><code>type</code> (REQUIRED): the type of the object MUST be <code>ViewershipRecord</code>.</li> <li><code>actor</code> (REQUIRED): the actor who viewed the post.</li> <li><code>object</code> (REQUIRED): the unique identifier of the post that was viewed.</li> <li><code>published</code> (RECOMMENDED): the date and time at which the post was viewed.</li> </ul>"},{"location":"fep/c4ad/#querying-viewership-records","title":"Querying Viewership Records","text":"<p>Clients should query the server for viewership records to determine whether a post has been viewed by the user. The server should provide an endpoint for clients to fetch viewership records for a specific user.</p>"},{"location":"fep/c4ad/#handling-viewed-posts","title":"Handling Viewed Posts","text":"<p>When fetching posts, clients must filter out posts that have been viewed by the user based on the viewership records. This ensures that users are not shown the same posts repeatedly.</p>"},{"location":"fep/c4ad/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c551/","title":"FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals","text":"Authors Status Date received Tracking issue Discussions Repository bengo ben@bengo.co <code>DRAFT</code> 2024-07-11 #366 - codeberg"},{"location":"fep/c551/#summary","title":"Summary","text":"<p>This is a proposal to enhance the fediverse by creating test cases for FEPs as ECMAScript Modules.</p>"},{"location":"fep/c551/#contents","title":"Contents","text":"<ul> <li>Context</li> <li>Test Specifications</li> <li>Test Modules</li> <li>Test Objects</li> <li>Test Functions</li> <li>Test Inputs</li> <li>Test Results</li> </ul>"},{"location":"fep/c551/#context","title":"Context","text":"<p>FEP-d9ad proposes to Create Conformance Tests for Fediverse Enhancement Proposals, and specifies components that all Conformance Tests may use and describe in their Test Specifications. It does not specify a format for implementing FEP-d9ad Conformance Tests in any programming language.</p> <p>This FEP-c551 proposes to supplement human-readable FEP-d9ad Conformance Tests with implementations of the test as Test Objects exported from Test Modules. Each Test Object has a <code>run</code> function parameterized by a [Test Input] and returning a Promise of a Test Result.</p>"},{"location":"fep/c551/#overview","title":"Overview","text":"<p>When a tester comes up with a new test for a FEP, they create a human-readable Test Specification describing how to test whether some subject conforms to the FEP.</p> <p>ECMAScript developers implement Test Specifications as automatable code by using ECMAScript to create Test Functions that execute the test logic and Test Objects that group the Test Function with more info like the test's name, required input, and possible outcomes. Test Objects are distributed in ECMAScript Modules published on the web, e.g. in <code>.js</code> or <code>.mjs</code> files.</p> <p>Testers invoke the Test Function once for each Test Input, await any returned Promises, and receive a Test Result describing the <code>outcome</code> of running the test.</p>"},{"location":"fep/c551/#test-specifications","title":"Test Specifications","text":"<p>Test Specifications are human-readable documents that specify the behavior of a test.</p> <p>Test Specifications SHOULD include Conformance Test Component specifications from FEP-d9ad.</p> <p>An example of a test specification is fep-521a-test-case.md.</p>"},{"location":"fep/c551/#test-modules","title":"Test Modules","text":"<p>FEP testers MAY publish implementations of their test specifications as an ECMAScript Module following the recommendations in this proposal. Such modules may be referred to as Test Modules.</p> <p>Test Modules SHOULD have no imports. This is to ensure portability of the test modules.</p> <p>Test Modules MAY export a default export object that is a Test Object</p> <p>Test Modules SHOULD be resilient to being parsed and evaluated in various ECMAScript runtimes (e.g. node.js or a web browser like Firefox).</p> <p>An example of a test module can be found in activitypub-testing-fep-521a.</p>"},{"location":"fep/c551/#example-test-module","title":"Example Test Module","text":"<pre><code>export default {\n name: 'invalid script module name',\n run: (input) =&gt; ({ outcome: 'passed' }),\n type: ['https://w3id.org/fep/c551#Test'],\n '@context': [\"https://www.w3.org/ns/activitystreams\"],\n}\n</code></pre>"},{"location":"fep/c551/#test-objects","title":"Test Objects","text":"<p>Test Objects are ECMAScript Objects that represent a named, runnable test, e.g. a test specified by a FEP-d9ad Conformance Test.</p> <p>Test Objects MUST have a property named <code>type</code> whose value is either the string <code>https://w3id.org/fep/c551#Test</code> or an Array containing that string.</p> <p>Test Objects MUST have a property named <code>name</code> whose value is a string.</p> <p>Test Objects SHOULD have a property named <code>@context</code> whose value is an Array containing <code>https://www.w3.org/ns/activitystreams</code>.</p> <p>An example of a test object is exported in activitypub-testing-fep-521a.</p> <p>Test Objects SHOULD have a property named <code>run</code> whose value is a Test Function].</p>"},{"location":"fep/c551/#test-functions","title":"Test Functions","text":"<p>Test Object <code>run</code> calls SHOULD return a <code>Promise</code> that resolves to a Test Result.</p> <p>Test Object <code>run</code> functions SHOULD be resilient to being run in various ECMAScript runtimes (e.g. node.js or a web browser like Firefox).</p>"},{"location":"fep/c551/#test-inputs","title":"Test Inputs","text":"<p>A Test Input is the first parameter to a test's <code>run</code> function.</p> <p>Test Input MUST be an object. A test with several logically distinct inputs should give each input a name, and add each named input as a property within a top-level input object.</p> <p>Test Input values SHOULD conform to the specification of the called test's Input spec.</p>"},{"location":"fep/c551/#test-results","title":"Test Results","text":"<p>Test Results MUST have a property named <code>outcome</code> whose value is a string.</p> <p>Test Results SHOULD have a property named <code>info</code> whose value is a string.</p> <p>Test Results MAY have a property named <code>pointer</code> that contextualizes the <code>outcome</code>, e.g. an object with a property for each value that led to the <code>outcome</code>. For example, if a test outcome is <code>failed</code> because some number was too low, you can set the result <code>info</code> to \"number too low\" and <code>pointer</code> to <code>{ number: 100 }</code>.</p> Conformance requirements are indicated by sentences containing MUST a la RFC2119."},{"location":"fep/c551/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c551/tests/","title":"fep-c551-tests","text":"<p>A test suite that tests for conformance to FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals.</p>"},{"location":"fep/c551/tests/#tests","title":"Tests","text":""},{"location":"fep/c551/tests/#fep-c551-module-must-export-test-object","title":"fep-c551 module must export test object","text":"<ul> <li>slug: <code>fep-c551-module-must-export-test-object</code></li> <li>Test Module</li> </ul>"},{"location":"fep/c551/tests/#usage","title":"Usage","text":""},{"location":"fep/c551/tests/#running-local-test-files-via-data-url","title":"Running local test files via Data URL","text":"<pre><code>activitypub-testing run test \\\n--url=\"$(data-url ./fep-c551-module-must-export-test-object.js)\" \\\n--input.module=\"$(cat &lt;&lt;EOF\nexport default {\n name: 'sample test',\n run: () =&gt; {\n return { outcome: 'inapplicable' }\n }\n}\nEOF\n)\"\n</code></pre> <p>The <code>data-url</code> command is provided by the following shell function:</p> <pre><code>data-url() {\n if [ -z \"$1\" ]; then\n echo \"usage: data-url file\" &gt;&amp;2\n exit 1\n fi\n mimetype=$(file -bN --mime-type \"$1\")\n content=$(base64 &lt; \"$1\")\n echo \"data:$mimetype;base64,$content\"\n}\n</code></pre>"},{"location":"fep/c5a1/","title":"FEP-c5a1: To-do's","text":"Authors Status Date received Tracking issue Discussions Repository Lynn Foster lynn@mikorizal.org <code>DRAFT</code> 2024-01-31 #256 - codeberg"},{"location":"fep/c5a1/#summary","title":"Summary","text":"<p>This document describes an implementation of simple to-do's or tasks based on ActivityPub protocol and Valueflows vocabulary. A to-do is a simple work commitment, and can be created for oneself or another person. Optionally, when the to-do is done, that can be recorded also.</p>"},{"location":"fep/c5a1/#history","title":"History","text":"<p>This is a parallel submission to FEP-3264 Federated Work Coordination, which supports much more complex project or production planning. Both planning submissions continue the idea of Valueflows extensions to ActivityPub/ActivityStreams based on use case, started by silverpill with FEP-0837 Federated Marketplace.</p>"},{"location":"fep/c5a1/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/c5a1/#creating-a-to-do","title":"Creating a to-do","text":"<p>A to-do is implemented as a Valueflows <code>Commitment</code>.</p> <p>Consuming implementations which don't have planning features MAY display plan-related objects similarly to <code>Note</code> objects.</p> <p>In all cases, the objects can also be discussed using <code>Note</code> objects and <code>replyTo</code>.</p> <p>Valueflows defines a commitment is defined as A planned economic flow that has been promised by an agent to another agent.</p> <ul> <li><code>id</code> (REQUIRED): the commitment's unique global identifier.</li> <li><code>type</code> (REQUIRED): the type of the object MUST be <code>Commitment</code>.</li> <li><code>attributedTo</code> (REQUIRED): the actor who published the commitment.</li> <li><code>published</code> (RECOMMENDED): the date and time at which the commitment was published.</li> <li><code>resourceConformsTo</code> (OPTIONAL): the skill or type of work involved. Could be any URI.</li> <li><code>effortQuantity</code> (OPTIONAL): the estimated or expected amount and unit of the work. This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification.</li> <li><code>hasNumericalValue</code> (REQUIRED): amount of the resource.</li> <li><code>provider</code> (RECOMMENDED): the actor who commits to providing the resource, including doing the work. If not included, it is assumed to be the <code>to</code> actor.</li> <li><code>receiver</code> (RECOMMENDED): the actor who will be receiving the resource. If not included, it is assumed to be the <code>attributedTo</code> actor.</li> <li><code>content</code> (RECOMMENDED): the description of what is to be done. The type of content SHOULD be <code>text/html</code>.</li> <li><code>to</code> (REQUIRED): the audience of the commitment.</li> </ul> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc788/create\",\n \"actor\": \"https://project.example/actors/alice\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"content\": \"Please proofread the document at https://project.example/docs/45, and let me know what you think could be improved.\",\n \"published\": \"2024-05-18T19:22:03.918737Z\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q834191\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"2\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/alice\"\n },\n \"to\": \"https://project.example/actors/bob\"\n}\n</code></pre>"},{"location":"fep/c5a1/#accepting-a-to-do","title":"Accepting a To-do","text":"<p>Accepting or rejecting a to-do is OPTIONAL for this FEP, although it may be required based on the understanding between the actors.</p> <p>The <code>object</code> of <code>Accept</code> activity MUST be the <code>id</code> of the <code>Commitment</code> object previously sent to the actor.</p> <p>Activity MAY contain <code>content</code> property for further coordination.</p> <p>Example:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Accept\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-9kh93jcmljei\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"content\": \"Should be done within a couple days.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n</code></pre>"},{"location":"fep/c5a1/#rejecting-a-to-do","title":"Rejecting a To-do","text":"<p>Accepting or rejecting a to-do is OPTIONAL for this FEP, although it may be required based on the understanding between the actors.</p> <p>The <code>object</code> of <code>Reject</code> activity MUST be the <code>id</code> of the <code>Commitment</code> object previously sent to the actor.</p> <p>Activity MAY contain <code>content</code> property indicating the reason for rejection.</p> <p>Example:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://project.example/todos/8c05f97f-1531-4b70-9ca8-4ee4a09f36a3\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"content\": \"Sorry, my schedule is full for a month.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n</code></pre>"},{"location":"fep/c5a1/#recording-what-actually-was-done","title":"Recording what actually was done","text":"<p>Recording what was done is OPTIONAL for the FEP, although it may be required by the actors involved.</p> <p>Actual economic activity is represented with an <code>EconomicEvent</code> in Valueflows. In the case of to-do's, it would only occur in response to an earlier to-do commitment. More than one <code>EconomicEvent</code> can be recorded for one <code>Commitment</code> to-do.</p> <p>The representation of an economic event is a JSON document with the following properties:</p> <ul> <li><code>id</code> (REQUIRED): the economic event's unique global identifier.</li> <li><code>type</code> (REQUIRED): the type of the object SHOULD be <code>EconomicEvent</code>. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as <code>Note</code>.</li> <li><code>attributedTo</code> (REQUIRED): the actor who published the economic event.</li> <li><code>content</code> (OPTIONAL): the description of the economic event or communication about the economic event. The type of content SHOULD be <code>text/html</code>.</li> <li><code>published</code> (RECOMMENDED): the date and time at which the economic event was published.</li> <li><code>to</code> (REQUIRED): the audience of the economic event.</li> <li><code>fulfills</code> (REQUIRED): the commitment the economic event is completely or partially fulfilling.</li> <li><code>resourceConformsTo</code> (OPTIONAL): the type of an economic resource (for to-do's, can be a skill or type of work). Could be any URI. It does not have to match the commitment, but if not included, can be assumed it is the same.</li> <li><code>effortQuantity</code> (OPTIONAL): the amount and unit of the work done (for to-do's, usually a time quantity). This is an object with two properties:</li> <li><code>hasUnit</code> (REQUIRED): name of the unit, according to Ontology of units of Measure classification.</li> <li><code>hasNumericalValue</code> (REQUIRED): amount of the resource.</li> <li><code>provider</code> (RECOMMENDED): the actor who commits to doing the work. If not included, it is assumed to be the <code>attributedTo</code> actor.</li> <li><code>receiver</code> (RECOMMENDED): the actor who will be receiving the benefit. If not included, it is assumed to be the <code>to</code> actor.</li> <li><code>finished</code> (OPTIONAL): set to true if this economic event completes the commitment, or the commitment is no longer open for some reason.</li> </ul> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"EconomicEvent\": \"vf:EconomicEvent\",\n \"fulfills\": \"vf:fulfills\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/create\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": {\n \"type\": \"EconomicEvent\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc932\",\n \"attributedTo\": \"https://project.example/actors/bob\",\n \"published\": \"2024-10-21T14:16:41.843794Z\",\n \"fulfills\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"1.5\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/alice\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n</code></pre> <p>The <code>Commitment</code> can alternatively be marked as <code>finished</code>, without recording an <code>EconomicEvent</code>. This could occur in cases where no work will be done, or work was done but will not be recorded, or some work was recorded earlier without marking the to-do <code>finished</code>. So, <code>finished</code> does not imply that the to-do was done, only that it is not waiting to be done any more.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Update\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/update\",\n \"actor\": \"https://project.example/actors/alice\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"published\": \"2024-10-24T16:16:41.843794Z\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n</code></pre>"},{"location":"fep/c5a1/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[Valueflows] Lynn Foster, elf Pavlik, Bob Haugen Valueflows, 2024</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>[Activity Vocabulary] James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>[Ontology of units of Measure] Hajo Rijgersberg, Don Willems, Xin-Ying Ren, Mari Wigham, Jan Top, Ontology of units of Measure, 2017</li> </ul>"},{"location":"fep/c5a1/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c648/","title":"FEP-c648: Blocked Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.name <code>DRAFT</code> 2023-06-14 #123 - codeberg"},{"location":"fep/c648/#summary","title":"Summary","text":"<p>Users need to review and revise the list of actors they have blocked. This FEP defines a new collection property, the Blocked Collection, which contains the actors that a user has blocked. It also defines a collection of <code>Block</code> activities, which can be used to undo blocks. Finally, it defines inverse properties for both collections, to aid in navigating between the collections and the actors that own them.</p>"},{"location":"fep/c648/#motivation","title":"Motivation","text":"<p>The Activity Vocabulary defines a <code>Block</code> activity type. The ActivityPub specification defines how to use a <code>Block</code> activity in the API to block another actor.</p> <p>However, ActivityPub does not define an efficient way to retrieve the list of actors that a user has blocked. The only way to get this information is to scan the <code>outbox</code> collection for <code>Block</code> activities that were not the object of a later <code>Undo</code> activity. With a large <code>outbox</code>, this can be inefficient and slow.</p> <p>The <code>followers</code> and <code>following</code> properties of an actor are collections of objects in the actor's social graph. By analogy, the new <code>blocked</code> property defined in this document is a collection of other actors that the actor has blocked.</p> <p>One use case for the list of blocked actors is to allow the user to review and potentially undo the blocks. However, the ActivityPub specification requires the <code>id</code> of the <code>Block</code> activity to undo the block.</p> <p>To make it easier to undo blocks, the <code>blocks</code> property of an actor is a collection of <code>Block</code> activities, which include the <code>id</code> of the activity, the <code>object</code> that was blocked. These activities can be used to undo the block.</p> <p>Of the two properties, <code>blocks</code> is most useful for client applications, since the collection of blocked actors can be derived from the <code>object</code> property of each <code>Block</code> activity. Some implementers, however, may prefer the <code>blocked</code> property for parallelism with the <code>followers</code> and <code>following</code> properties. This specification defines both properties.</p>"},{"location":"fep/c648/#user-stories","title":"User stories","text":"<ul> <li>As an ActivityPub user, I want to see a list of actors that I have blocked, so I can confirm who I have and have not restricted.</li> <li>As an ActivityPub user, I want to undo a Block activity, so that I can interact with that actor again.</li> </ul>"},{"location":"fep/c648/#context","title":"Context","text":"<p>The context document for the <code>blocked</code> property is as follows:</p> <pre><code>{\n \"@context\": {\n \"bl\": \"https://purl.archive.org/socialweb/blocked#\",\n \"blocked\": {\n \"@id\": \"bl:blocked\",\n \"@type\": \"@id\"\n },\n \"blocks\": {\n \"@id\": \"bl:blocks\",\n \"@type\": \"@id\"\n },\n \"blockedOf\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#blockedOf\",\n \"@type\": \"@id\"\n },\n \"blocksOf\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#blocksOf\",\n \"@type\": \"@id\"\n },\n }\n}\n</code></pre> <p>The context document is available at the URL <code>https://purl.archive.org/socialweb/blocked</code>.</p>"},{"location":"fep/c648/#version-stamped-context-urls","title":"Version-stamped context URLs","text":"<p>The main context URL will always have the latest version of the context document for this vocabulary. Additional context URLs are available with version numbers, to allow implementers to define their own level of tolerance to changes in the vocabulary. The version stamps use semantic versioning. The version-stamped context URLs are:</p> Context URL Purpose <code>https://purl.archive.org/socialweb/blocked/2.0.0</code> The exact version of the context used in this document. This alias is useful for implementers that need an immutable document. <code>https://purl.archive.org/socialweb/blocked/2.0</code> The exact set of terms defined in this document, possibly with bug fixes or documentation changes. This alias is useful to get the latest version of the context document, without introducing any new terms that might conflict with other vocabularies. <code>https://purl.archive.org/socialweb/blocked/2</code> The set of terms defined in this document, plus possibly new terms, as well as bug fixes and documentation changes. This alias allows implementers to get backwards-compatible changes to the vocabulary, without having to change their code."},{"location":"fep/c648/#properties","title":"Properties","text":""},{"location":"fep/c648/#blocked","title":"<code>blocked</code>","text":"URI <code>https://purl.archive.org/socialweb/blocked#blocked</code> Notes The <code>blocked</code> property of an actor is an ordered collection of ActivityPub actors. The <code>blocked</code> collection SHOULD include all actors blocked by the actor, except for those that have been unblocked by an <code>Undo</code> activity. Each actor in the <code>blocked</code> collection MUST be unique. The <code>blocked</code> collection MUST be sorted in reverse chronological order, with the most recently added actor first. (Users are most likely to want to see who they have blocked recently, so these actors should be ordered first.) As with other ActivityPub properties, the <code>blocked</code> property MAY be referenced in the actor by <code>id</code> or as an embedded node object. Domain Object (an ActivityPub actor) Range OrderedCollection Functional true"},{"location":"fep/c648/#blocks","title":"<code>blocks</code>","text":"URI <code>https://purl.archive.org/socialweb/blocked#blocks</code> Notes The <code>blocks</code> property of an actor is an ordered collection of <code>Block</code> activities. The <code>blocks</code> collection SHOULD include all <code>Block</code> activities by the actor, except for those that have been reverted by an <code>Undo</code> activity. Each activity in the <code>blocks</code> collection MUST be unique. The <code>blocks</code> collection MUST be sorted in reverse chronological order, with the most recent activity first. (Users are most likely to want to see who they have blocked recently, and possibly unblock them, so these activities should be ordered first.) As with other ActivityPub properties, the <code>blocks</code> property MAY be referenced in the actor object by <code>id</code> or as an embedded node object. Domain Object (an ActivityPub actor) Range OrderedCollection Functional true"},{"location":"fep/c648/#blockedof","title":"<code>blockedOf</code>","text":"URI <code>https://purl.archive.org/socialweb/blocked#blockedOf</code> Notes The value of the <code>blockedOf</code> property of a collection is the actor for whom the collection is the value of its <code>blocked</code> property. It is the inverse property of <code>blocked</code>. As with other ActivityPub properties, the <code>blockedOf</code> property MAY be referenced in the actor by <code>id</code> or as an embedded node object. Domain OrderedCollection Range Object (an ActivityPub actor) Functional true"},{"location":"fep/c648/#blocksof","title":"<code>blocksOf</code>","text":"URI <code>https://purl.archive.org/socialweb/blocked#blocksOf</code> Notes The value of the <code>blocksOf</code> property of a collection is the actor for whom the collection is the value of its <code>blocks</code> property. It is the inverse property of <code>blocks</code>. As with other ActivityPub properties, the <code>blocksOf</code> property MAY be referenced in the actor by <code>id</code> or as an embedded node object. Domain OrderedCollection Range Object (an ActivityPub actor) Functional true"},{"location":"fep/c648/#examples","title":"Examples","text":""},{"location":"fep/c648/#blocked-property","title":"<code>blocked</code> property","text":"<p>A publisher can include the <code>blocked</code> collection in the properties of an actor.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"blocked\": \"https://example.com/evanp/blocked\",\n \"to\": \"as:Public\"\n}\n</code></pre>"},{"location":"fep/c648/#a-blocked-collection","title":"A <code>blocked</code> collection","text":"<p>Retrieving the <code>blocked</code> collection would provide an ordered collection of ActivityPub actors that have been blocked.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\",\n {\"custom\": \"https://example.com/ns/custom\"}\n ],\n \"id\": \"https://example.com/evanp/blocked\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"blockedOf\": \"https://example.com/evanp\",\n \"name\": \"Evan Prodromou's Blocked Collection\",\n \"orderedItems\": [\n {\n \"type\": \"Person\",\n \"id\": \"https://spam.example/spammer\",\n \"name\": \"Irritating Spammer\"\n },\n {\n \"type\": \"Application\",\n \"id\": \"https://alarmclock.example/alarmclock\",\n \"name\": \"Badly-Behaved Alarm Clock App\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/c648/#blocks-property","title":"<code>blocks</code> property","text":"<p>Similarly, a publisher can include the <code>blocks</code> collection in the properties of an actor. Note that here, the <code>blocks</code> collection is referenced as an embedded node object, rather than a URL. It includes useful metadata about the collection.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"blocks\": {\n \"id\": \"https://example.com/evanp/blocks\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 2,\n },\n \"to\": \"as:Public\"\n}\n</code></pre>"},{"location":"fep/c648/#a-blocks-collection","title":"A <code>blocks</code> collection","text":"<p>Retrieving the <code>blocks</code> property value would provide an ordered collection of <code>Block</code> activities that the user has performed and not undone.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\",\n {\"custom\": \"https://example.com/ns/custom\"}\n ],\n \"id\": \"https://example.com/evanp/blocks\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"blocksOf\": \"https://example.com/evanp\",\n \"name\": \"Evan Prodromou's Blocks Collection\",\n \"orderedItems\": [\n {\n \"type\": \"Block\",\n \"id\": \"https://example.com/evanp/block/2\",\n \"object\": {\n \"type\": \"Person\",\n \"id\": \"https://spam.example/spammer\",\n \"name\": \"Irritating Spammer\"\n },\n \"published\": \"2023-04-15T00:00:00Z\"\n },\n {\n \"type\": [\"custom:Disallow\", \"Block\"],\n \"id\": \"https://example.com/evanp/block/2\",\n \"object\": {\n \"type\": \"Application\",\n \"id\": \"https://alarmclock.example/alarmclock\",\n \"name\": \"Badly-Behaved Alarm Clock App\"\n },\n \"published\": \"2022-12-25T00:00:00Z\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/c648/#processing-requirements","title":"Processing requirements","text":"<p>When a server receives a <code>Block</code> activity from a client through the ActivityPub API, it SHOULD add the activity to the <code>blocks</code> collection of the actor that performed the block. The server MAY also add the blocked actor to the <code>blocked</code> collection of the blocking actor.</p> <p>When a server receives an <code>Undo</code> activity for a <code>Block</code> activity through the ActivityPub API, it SHOULD remove the <code>Block</code> activity from the <code>blocks</code> collection of the actor that undid the block. The server MAY also remove the blocked actor from the <code>blocked</code> collection of the blocking actor.</p>"},{"location":"fep/c648/#security-considerations","title":"Security considerations","text":"<p>The <code>blocked</code> and <code>blocks</code> collections are very sensitive. Actors on the blocked list may be harassing or abusive. If they find themselves on a user's blocklist, they may retaliate against the user. Consequently, the <code>blocked</code> and <code>blocks</code> collections SHOULD NOT be publicly readable.</p> <p>By default, implementations SHOULD NOT allow read access to the <code>blocked</code> or <code>blocks</code> collections to any actor other than the user that owns the collection.</p> <p>Some users may want to share their blocklist with other actors. Shared blocklists are an important tool for user safety on monolithic social networks and on the social web. Implementations MAY allow a user to share their <code>blocked</code> collection with other actors. Implementations SHOULD inform the user of the risks of sharing their blocklist with the wrong actors.</p> <p>Sharing the <code>blocks</code> collection is less useful, since <code>Block</code> activities are usually not readable by anyone but the blocking actor.</p>"},{"location":"fep/c648/#implementations","title":"Implementations","text":"<p>The onepage.pub server implements the <code>blocked</code> collection.</p>"},{"location":"fep/c648/#history","title":"History","text":"<p>activitypub-express implements a <code>blocked</code> property in the <code>streams</code> collection of an actor, including the blocked objects only. The developers' experience was that storing objects only made it hard to <code>Undo</code> a block, since the full Activity object's <code>id</code> is needed. Metadata about the block activity, such as the date, is also lost.</p>"},{"location":"fep/c648/#references","title":"References","text":"<ul> <li>James Snell, Evan Prodromou, Activity Streams 2.0 Vocabulary, 2017</li> <li>Christine Lemmer Webber, Jessica Tallon, Erin Shephard, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>Tom Preston-Werner, Semantic Versioning 2.0.0, 2017</li> </ul>"},{"location":"fep/c648/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c7d3/","title":"FEP-c7d3: Ownership","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>WITHDRAWN</code> 2024-06-04 2025-02-21 #323 Discussions codeberg"},{"location":"fep/c7d3/#summary","title":"Summary","text":"<p>In this document we discuss the concept of ownership, as applied to ActivityPub objects.</p> <p>Warning</p> <p>This FEP has been superseded by FEP-fe34.</p>"},{"location":"fep/c7d3/#rationale","title":"Rationale","text":"<p>ActivityPub standard does not specify authentication and authorization mechanisms.</p> <p>Many implementations use <code>actor</code> and <code>attributedTo</code> properties (defined in Activity Vocabulary) to determine the validity of activities and objects. This proposal attempts to formalize the current practices and provide guidance for implementers.</p>"},{"location":"fep/c7d3/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/c7d3/#ownership","title":"Ownership","text":"<p>Ownership is indicated by a property of an ActivityPub object. The name of this property differs depending on the object type:</p> <ul> <li>Owner of an actor is indicated by its <code>id</code> property.</li> <li>Activities have an <code>actor</code> property, which describes the actor that performed the activity. This actor is considered to be the owner of the activity.</li> <li>An object (that is, not an actor and not an activity) can have an <code>attributedTo</code> property, which describes the actor to which the object is attributed. This actor is considered to be the owner of the object.</li> <li>Public keys and verification methods have <code>owner</code> and <code>controller</code> properties.</li> </ul> <p>The owner of an object MUST be an actor.</p> <p>Warning</p> <p>According to Activity Vocabulary, <code>actor</code> and <code>attributedTo</code> properties can contain references to multiple actors. These scenarios are not covered by this document and implementers are expected to determine the appropriate authentication and authorization procedures on a case-by-case basis.</p> <p>Note</p> <p>In subsequent sections, \"objects\" and \"activities\" will be referred to as simply \"objects\".</p>"},{"location":"fep/c7d3/#origin","title":"Origin","text":"<p>Object identifiers are grouped together into protection domains called \"origins\". This concept is similar to the \"web origin\" concept described in RFC-6454, and origins of object IDs are computed by the same algorithm.</p> <p>The same-origin policy determines when a relationship between objects can be trusted.</p> <p>Note</p> <p>There might be other ways to establish trust, but they are not covered by this document.</p>"},{"location":"fep/c7d3/#identifiers-and-ownership","title":"Identifiers and ownership","text":"<p>Identifier of an object and identifier of its owner MUST have the same origin.</p>"},{"location":"fep/c7d3/#authentication","title":"Authentication","text":"<p>The object is considered authentic if any of the following conditions are met:</p> <ol> <li>It was fetched from the location that has the same origin as its owner's ID.</li> <li>It was delivered to inbox and the <code>POST</code> request contained a valid HTTP signature created using a key whose owner has the same origin as the object owner.</li> <li>It contains a valid FEP-8b32 integrity proof created using a key whose owner has the same origin as the object owner.</li> <li>If it is embedded within another object, and its owner has the same origin as the owner of the containing object.</li> </ol> <p>If none of these conditions are met, the object MUST be discarded.</p> <p>If signature verification is performed, the key owner SHOULD match the object owner.</p> <p>Note</p> <p>In some cases, consumers can process unauthenticated objects if the risk is deemed acceptable.</p>"},{"location":"fep/c7d3/#delivered-to-inbox","title":"Delivered to inbox","text":"<p>If the object was delivered to inbox and its authentication fails, the recipient SHOULD fetch it and repeat the authentication procedure.</p>"},{"location":"fep/c7d3/#emdedded-objects","title":"Emdedded objects","text":"<p>If the embedded and the containing objects have owners with different origins, the authenticity of the embedded object MUST be verified independently either by fetching it from the server of origin, or by verifying its FEP-8b32 integrity proof.</p>"},{"location":"fep/c7d3/#anonymous-objects","title":"Anonymous objects","text":"<p>An object without an ID can only exist when embedded within another object. It has the same owner as the parent object and it is considered authentic when the parent object is authentic.</p>"},{"location":"fep/c7d3/#unattributed-objects","title":"Unattributed objects","text":"<p>An object without an explicit owner is owned by the server. Such object MUST be considered authentic only if fetched from the location that has the same origin as its ID.</p>"},{"location":"fep/c7d3/#authorization","title":"Authorization","text":"<p>The actor that creates the object MUST be its owner.</p> <p>If activity modifies or deletes an object, its owner SHOULD match the object's owner. If owners are different, their IDs MUST have the same origin.</p> <p>Examples:</p> <ul> <li><code>Create</code>, <code>Update</code> and <code>Delete</code> activities, and objects indicated by their <code>object</code> property SHOULD have the same owner.</li> <li><code>Undo</code> activity and object indicated by its <code>object</code> property SHOULD have the same owner.</li> <li><code>Add</code> and <code>Remove</code> activities, and objects indicated by their <code>target</code> property SHOULD have the same owner.</li> <li><code>Announce</code> and <code>Like</code> activities don't modify objects indicated by their <code>object</code> property, therefore their owners can be different.</li> </ul>"},{"location":"fep/c7d3/#ownership-transfer","title":"Ownership transfer","text":"<p>When ownership changes, the new owner ID MUST have the same origin as the old owner ID.</p>"},{"location":"fep/c7d3/#access-control","title":"Access control","text":"<p>When a protected object is fetched, the <code>GET</code> request MUST contain a HTTP signature created using a key whose owner SHOULD belong to object's intended audience. If key owner doesn't belong to intended audience, its ID MUST have the same origin as one of the actors in object's intended audience.</p>"},{"location":"fep/c7d3/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>A. Barth, The Web Origin Concept, 2011</li> <li>silverpill, FEP-8b32: Object Integrity Proofs, 2022</li> <li>Ryan Barrett, nightpool, ActivityPub and HTTP Signatures, 2024</li> </ul>"},{"location":"fep/c7d3/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/c893/","title":"FEP-c893: DOAP","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com <code>DRAFT</code> 2024-07-15 #372 Discussions codeberg"},{"location":"fep/c893/#summary","title":"Summary","text":"<p>This proposal introduces a standardized method for describing Fediverse projects using the Description of a Project (DOAP) format. The proposal outlines the creation of <code>doap.jsonld</code> file that includes details about implemented federation protocols and supported Fediverse Enhancement Proposals (FEPs). This makes it easier for developers and users to understand the capabilities and compatibility of various Fediverse projects.</p>"},{"location":"fep/c893/#example-doapjsonld-file","title":"Example <code>doap.jsonld</code> File","text":"<p>Here is an example structure for the <code>doap.jsonld</code> file:</p> <pre><code>{\n \"@context\": {\n \"doap\": \"http://usefulinc.com/ns/doap#\",\n \"foaf\": \"http://xmlns.com/foaf/0.1/\",\n \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\",\n \"doap:description\": {\n \"@id\": \"doap:description\",\n \"@container\": \"@language\"\n },\n \"doap:shortdesc\": {\n \"@id\": \"doap:shortdesc\",\n \"@container\": \"@language\"\n }\n },\n \"@type\": \"doap:Project\",\n \"doap:name\": \"ExampleProject\",\n \"doap:homepage\": \"https://example.org\",\n \"doap:description\": {\n \"en\": \"ExampleProject is a Fediverse client/server that supports multiple protocols and enhancements.\",\n \"es\": \"ExampleProject es un cliente/servidor de Fediverse que admite m\u00faltiples protocolos y mejoras.\"\n },\n \"doap:shortdesc\": {\n \"en\": \"Tools and vocabulary for describing community-based software projects.\",\n \"es\": \"Vocabulario y herramientas para describir proyectos de software comunitarios.\"\n },\n \"doap:created\": \"2022-01-01\",\n \"doap:logo\": \"https://example.org/logo.png\",\n \"doap:screenshots\": [\n \"https://example.org/screenshot1.png\",\n \"https://example.org/screenshot2.png\"\n ],\n \"doap:category\": [\n \"http://software.freshmeat.net/browse/1020/\",\n \"http://osdir.com/Downloads+index-req-viewsdownload-sid-201.phtml\"\n ],\n \"doap:repository\": {\n \"@type\": \"doap:GitRepository\",\n \"doap:browse\": \"https://github.com/example/exampleproject/\",\n \"doap:location\": \"https://github.com/example/exampleproject.git\"\n },\n \"doap:release\": {\n \"@type\": \"doap:Version\",\n \"doap:created\": \"2024-07-15\",\n \"doap:name\": \"v1.0.0\",\n \"doap:revision\": \"1.0.0\"\n },\n \"doap:maintainer\": [\n {\n \"@type\": \"foaf:Person\",\n \"foaf:name\": \"John Doe\",\n \"foaf:homepage\": \"https://github.com/johndoe\"\n },\n {\n \"@type\": \"foaf:Person\",\n \"foaf:name\": \"Jane Smith\",\n \"foaf:homepage\": \"https://github.com/janesmith\"\n }\n ],\n \"doap:implements\": [\n \"https://activitypub.rocks/specification\",\n \"https://diasporafoundation.org\"\n ],\n \"doap:supportedFEPs\": [\n \"https://fediverse.org/fep-0001\",\n \"https://fediverse.org/fep-0002\"\n ]\n}\n</code></pre>"},{"location":"fep/c893/#references","title":"References","text":"<ul> <li>Description of a Project (DOAP) Specification</li> <li>XEP-0453: DOAP Usage in XMPP</li> </ul>"},{"location":"fep/c893/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/cb76/","title":"FEP-cb76: Content Addressed Vocabulary","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository a a@trwnh.com <code>WITHDRAWN</code> 2022-11-29 2025-04-07 #41 Discussions codeberg"},{"location":"fep/cb76/#summary","title":"Summary","text":"<p>JSON-LD context definitions typically live at some URI which gets used as a namespace. It is generally expected that the URI is long-lived, and often the context document is retrievable from that URI, but sometimes these links break due to technical errors, expired domains, and other such issues. This FEP proposes adopting a solution proposed by [CAV] for any extension terms defined within other FEPs, as well as optionally for standard vocabulary.</p>"},{"location":"fep/cb76/#1-defining-fep-extension-terms","title":"1. Defining FEP extension terms","text":"<p>Each extension term MUST have a specification text.</p> <p>This specification is then used to calculate a SHA256 hash, which can be used as a URN within <code>@context</code> in lieu of a namespaced property.</p>"},{"location":"fep/cb76/#example-1-specifying-a-new-extension-term","title":"Example 1: Specifying a new extension term","text":"<p>Say we want to define a new ActivityStreams Vocabulary [AS2V] term, which will be a sub-type of Activity called <code>Message</code>, with the following specification text:</p> <pre><code>Indicates that the actor is sending a direct message.\n</code></pre> <p>We obtain the SHA256 hash of this specification text:</p> <pre><code>$ echo -n \"Indicates that the actor is sending a direct message.\" | sha256sum\n\nbab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa -\n</code></pre> <p>We can now use this hash to extend ActivityPub [AP] with a <code>Message</code> activity:</p> <pre><code>{\n\"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"Message\": \"urn:sha256:bab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa\"\n }\n],\n\"actor\": \"https://social.example/~alice\",\n\"type\": \"Message\",\n\"to\": \"https://bob.example.com\",\n\"content\": \"hi friend\"\n}\n</code></pre>"},{"location":"fep/cb76/#2-storing-fep-extension-terms-within-the-fep-git-repository","title":"2. Storing FEP extension terms within the FEP Git repository","text":"<p>(This section is non-normative.)</p> <p>A directory named <code>context/</code> should be created to store content addressed vocabulary extensions associated with FEPs. FEPs that propose vocabulary extensions MUST create a text file called <code>fep-abcd_property.txt</code> within this directory, where <code>abcd</code> is the identifier of the associated FEP, <code>property</code> is the recommended shorthand name of the property, and the contents of the text file are the specification text. FEP authors SHOULD try to use unique property names within the existing FEP vocabulary, unless an FEP is intended to supersede or replace an older FEP.</p> <p>A <code>context.jsonld</code> file SHOULD be generated any time a new vocabulary term is finalized, with its contents being a definition of every finalized vocabulary term.</p>"},{"location":"fep/cb76/#example-2-standardizing-an-fep-with-a-new-extension-term","title":"Example 2: Standardizing an FEP with a new extension term","text":"<p>In Example 1, we defined a <code>Message</code> activity type to be used for ActivityPub [AP] direct messaging. Say we now want to draft an FEP for this definition.</p> <p>We follow the FEP process as defined in FEP-a4ed to title and identify our FEP proposal. Applying this process to our proposal of <code>A Message activity for direct messaging</code> yields the identifier <code>FEP-0ac6</code>. Combined with our specification text of <code>Indicates that the actor is sending a direct message</code>, this yields the following text file <code>fep-0ac6_Message.txt</code>:</p> <pre><code>Indicates that the actor is sending a direct message\n</code></pre> <p>The text file is then stored at <code>context/fep-0ac6_Message.txt</code>, and when it is finalized, its SHA256 hash is added to <code>context/context.jsonld</code>:</p> <pre><code>{\n \"@context\": {\n \"Message\": \"urn:sha256:bab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa\"\n }\n}\n</code></pre>"},{"location":"fep/cb76/#references","title":"References","text":"<ul> <li>[AS2V] James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>[AP] Christine Lemmer-Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[CAV] Christine Lemmer-Webber, Content Addressed Vocabulary, 2020</li> </ul>"},{"location":"fep/cb76/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/cd47/","title":"FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge bumblefudge@learningproof.xyz <code>DRAFT</code> 2024-05-31 #319 Discussions codeberg"},{"location":"fep/cd47/#summary","title":"Summary","text":"<p>A proposed taxonomy of ways to make various kinds of ActivityPub data identifiable across locations to simplify higher-order functions like moderation receipts, tracking for trust and safety purposes, data migration, compliance, etc. This is intended as a light-weight and informational/meta-technical design document, not a specification or an extension.</p>"},{"location":"fep/cd47/#rationale","title":"Rationale","text":"<p>While Camille Fran\u00e7oise's originial \"ABCs\" paper was focused on disinformation specifically, it lays out the basic taxonomy I will be using here between Actors, Behavior, and Content, as three different vectors for disinformation, but also for abuse, trust and safety issues, inauthentic activity, copyright compliance, hate speech compliance, etc. The goal of this document is to collect the addressable and deduplication requirements for all of these related \"content-specific\" liabilities and use-cases, and inform the design of addressing and deduplication primitives in future FEPs.</p>"},{"location":"fep/cd47/#terminology","title":"Terminology","text":"<p>If we scope this exercise to data conformant to the ActivityStreams data model as extended and federated by ActivityPub, we could scope these categories as:</p> <ol> <li>Actors are what ActivityPub calls <code>Actors</code>: fediverse \"accounts\", bots, etc.</li> <li>Behavior can here be limited to \"the Greater Activity Streams,\" meaning protocol-native activities wrapped in AS objects defined in AS, AP, or in a FEP with a @Context so as to be fully compatible with the protocol in a ffpr, still canonicalizable as RDF.</li> <li>Content can here be a catch-all for three different sub-types of data: the \"contents\" of an Activity with arbitrary/open-ended fields (like the user-generated content of a <code>Note</code> object, for example), media \"attachments\" (which we could further constrain to defined media-types in the HTML sense, for simplicity), and links out to other data (URLs, but also other URIs TBD?)</li> </ol> <p>What we mean by \"addressing\" given the scope defined above is tricky, because there is a URI-based system of <code>id</code>s inherent to the JSON-LD data model that the AS and AP specifications build on, and most references to addressing or identification in the core specifications refer only to this graph-friendly but static scheme of resources and links. In practice to date, this has been coextensive with HTTPS URLs and DNS-resolveable domains. Whether non-HTTPS URLs could or should be used as <code>id</code>s or <code>@id</code>s is out-of-scope of this use-case document, and should be considered a design/implementation decision treated elsewhere. Regardless, today's Fediverse data is overwhelmingly (perhaps exclusively?) addressed by domain-based (and domain-dependent) <code>id</code>s that any new system should be able to handle and deduplicate in its current form, at least as \"legacy\" data if any breaking changes were proposed to these practices or to the core specifications.</p> <p>Today, most actors and behaviors are addressed by their <code>id</code>s that are also their current HTTPS \"location\"; content is similarly location-addressed most of the time, except insofar as some attachments are sometimes renamed in bucket storage configurations. All of these addresses are brittle vis-a-vis duplication, migration, and \"server death\"-- moving them to a new location creates a new address for identical content/referent, often without a \"forwarding\" redirection (HTTP-code-based or otherwise), or a backlink to its previous address, while a server going down suddenly just breaks all those links with no verifiable remedy.</p> <p>One form of location-independent addressing is \"content-addressing\", the most common form of which is addressing canonicalizable contents by hashing them in canonicalized form, often used in key/value stores, package managers, etc. It can sometimes be trickier than expected, however, to excise the location (or all properties that break if location changes) from the canonical form hashed to identify a piece of content, or an actor or a specific behavior.</p>"},{"location":"fep/cd47/#high-level-use-cases","title":"High-level Use-Cases","text":"<ol> <li>Abusive activity often evades or actor-based attribution and moderation; one common form of this on social media is copy-pasted allegations or rumors which bad actors deploy across throwaway accounts, bots, parrot-accounts, friends/accomplices, etc. For this reason, circulating content of an activity like a Note might need to be tracked independent of URI/location, server, and actor in a uniform way, rather than per-activity or per-property (the prevalence of copy-pasta might even be used as a health or authenticity metric for networks, although of course this should not be overapplied as it might return false positives for some of the funniest copypasta meta-memes of the Terminally Online)</li> <li>Hate-Speech and interpersonal Trust and safety issues- Relying on the moderation of a given server or service-provider benefits greatly from interoperable logs of moderation decisions; per-decision receipts or events might benefit from indirection or multi-addressing actors, behaviors, and/or content that moves or is deleted, as well as potentially cacheing it privately for later replay/re-assesment/etc.</li> <li>Being able to sort moderation by categories or topics enables selective reliance or inheritance of moderation (and re-moderation in categories where servers differ on policy or obligations)</li> <li>Some regulations require reporting and/or storing for a period of time toxic or offending content, even if the Actor is removed from the service or deletes their offending Behavior or Content-- a location-independent key/value store by content-address can sever the \"server's copy\" from the \"user's/published copy\" for these purposes.</li> <li>Inauthentic activity, whether it be \"synthetic users\" (AI impersonating users for whatever commercial or analytics-inflating purposes) or AI flotsam or deliberate and targetted impersonation, deliberately exploits weaknesses or good-faith in the actor system. Being able to attribute many actors or \"sybils\" to a common \"shadow actor\" (a sybil factory, a script, a coordination point of a bot army, etc.) often requires identifying the behavioral pattern or network traffic properties, even probabilistically and then mapping that back to actors that do not otherwise share location-addressing or domain provenance.</li> <li>\"Chain-Letter\" Behaviors and Content is a common pattern in end-to-end encrypted systems like WhatsApp, which has been grappling with the ethical, legal, and political/disinformation consecuences of \"chain-letter\" style viral communications for decades. To mitigate such messaging patterns without violating the privacy assumptions of users of E2EE systems, traffic is analyzed to mark outbox content identical to content in the same user's inbox so that future recipients see it marked as a \"forward\", and \"replication speed limits\" are imposed to prevent sensational disinformation or deliberately inauthentic replication moving faster than good information.</li> <li>Deduplication for thorough Copyright Takedowns: Some copyright regulations (if not in the letter of the regulation, at least in the best-practice or spirit of the law) put the burden of deduplication on a service provider, hoster, and/or aggregator of user-generated content, i.e., a copyright takedown does not apply to a specific URL or a specific file but to the copyrighted material (film, book, etc) that you are hosting and aggregating, across multiple users or new accounts a user might create, and even if others users download and re-uploads it in a slightly altered form. For this reason, many copyright-enforcement regimes today are highly centralized because they require both a mammoth data lake of all reported violations, and some kind of matching algorithm that deduplicates in media-specific and encoding-specific ways across slight edits, crops, tints, tinges, etc. </li> <li>Note: One way of doing this is a \"distance hash\" or \"locality-sensitive hashing\" to create hashes that index commonalities between inputs (patterns, segments in common, structures) or, in audio-visual media, \"perceptual hashing\" that tries to canonicalize the final visual output of a visual encoding rather than its underlying data structure, compression, codecs, etc. One open-source example of this is ISCC</li> </ol>"},{"location":"fep/cd47/#user-stories","title":"User Stories","text":"<ol> <li>As a server offering data migration to users bringing over old data, I can ingest content-addressed versions of each all actor collections, behavior and content without having to know server-specific paths, <code>id</code> patterns, annotations, bucket-storage URL schemes, etc.</li> <li>I can also check imported datasets of behavior and/or content against content-addressed moderation records from the server that originally hosted them, assuming that server is still live and federated with me.</li> <li>I can also access a mirror or snapshot of that data, if the server is no longer online or disfederated from me.</li> <li>As a server (A) auditing another server (B)'s moderation track record, I can parse receipts or logs of moderation actions that refer to behaviors and content by addresses internal to Server B (not necessarily content-addressed!) and use those address to retrieve the behavior and/or content moderated, even if the actors, behaviors, and/or content in question have been deleted. (Note: this use case assumes Server B is authorized appropriately by Server A and assumes significant trust between servers.)</li> <li>As a server promising its users the \"right to be forgotten\" (or just honoring UX expectations of deletion), I can request receipts of deletion from the servers of followers and followers' followers' by content-address of the behavior or content being deleted. (See fediverse-ideas#55 on fediverse-ideas reference; may be more pertinent for attachments than activities per se)</li> </ol>"},{"location":"fep/cd47/#open-questions","title":"Open Questions","text":"<ol> <li>Ephemeral, \"in-memory\" and/or service-internal objects (which the AP spec recommends to be excluded from the axiom that all Activity objects should have a unique and dereferenceable <code>id</code>!) are perhaps the simplest to content-address. These do not need to be addressed by unknown, external, or future parties, but MAY benefit from addressing the same way other content is addressed, e.g. when string-comparing uploads or pre-published content to a content-addressed blocklist before publishing.</li> <li>Should the Sub-Resource Integrity hashes of <code>@Context</code> files or javascript files, Emoji sets, or other shared resources that might mutate at a static address be brought into scope? Is it redundant or dangerous to have similar-but-different content addressing mechanisms for those kinds of resource verifiability running parallel to Actor, Behavior, and Content verifiability?</li> <li>Do Actors, Behaviors, and/or Contents need to be RDF-canonicalized, or is JCS more appropriate for portability between JSON-LD native and JSON-only implementations? What translation corner-cases arise from assuming one or the other canonicalization?</li> <li>Thanks to @pukkamustard for sharing their IETF RFC draft for RDF/CBOR, explainer, and relevant prior art-- seems a well-designed and standardizable way forward, at least for Behaviors, particularly for implementations that already have RDF-canonicalization in the dependency graph and featureset.</li> <li>Similarly, how are attachments, links, images, videos, etc canonicalized for hashing in content-type-aware ways? How to deal with filesystems, bucket storage, etc? How are arbitrary files (uploads?) canonicalized?</li> <li>Is there a property for Actor objects to backlink to previous <code>id</code>s? How to deduplicate across migrations that change <code>id</code> property?</li> </ol>"},{"location":"fep/cd47/#references","title":"References","text":"<p>Normative</p> <ul> <li>Christine Lemmer Webber, Jessica Tallon, et al. ActivityPub, 2018</li> <li>James Snell, Evan Promodorou, ActivityStreams, 2017</li> <li>Actors, Behaviors, Content: A Disinformation ABC: Highlighting Three Vectors of Viral Deception to Guide Industry &amp; Regulatory Responses, C. Fran\u00e7ois, September 20, 2019 (from the Annals of the Transatlantic Working Group Sessions)</li> <li>ISCC - International Standard Content Codes, specified at ISO</li> </ul> <p>Informational</p> <ul> <li>SocialHub: We need to build \u201ctrust\u201d in this space and the fediverse (2023)</li> <li>Fediverse-ideas: Delete Receipts for responsive and responsible federation?</li> <li>Fediverse-ideas: Emoji Sets</li> </ul>"},{"location":"fep/cd47/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/d36d/","title":"Index","text":"<p>hide: - navigation description: \"New instances on the threadiverse (servers that implement ActivityPub with FEP-1b12) are often seeded with forums for common interests, leading to multiple servers having similar forums. Users may dislike having to follow what they perceive to be \"duplicate\" forums or keep up with multiple discussions on the same topic across multiple servers. This document describes a method for allowing Group actors to share content to reduce posting of a single link multiple times, which reduces what users see as \"duplicate\" posts and fragmented conversations across multiple forums.\"</p>"},{"location":"fep/d36d/#fep-d36d-sharing-content-across-federated-forums","title":"FEP-d36d: Sharing Content Across Federated Forums","text":"Authors Status Date received Tracking issue Discussions Repository Zack Dunn zack@tilde.team <code>DRAFT</code> 2023-07-01 #125 - codeberg"},{"location":"fep/d36d/#summary","title":"Summary","text":"<p>New instances on the threadiverse (servers that implement ActivityPub with FEP-1b12) are often seeded with forums for common interests, leading to multiple servers having similar forums. Users may dislike having to follow what they perceive to be \"duplicate\" forums or keep up with multiple discussions on the same topic across multiple servers. This document describes a method for allowing <code>Group</code> actors to share content to reduce posting of a single link multiple times, which reduces what users see as \"duplicate\" posts and fragmented conversations across multiple forums.</p>"},{"location":"fep/d36d/#history","title":"History","text":"<p>FEP-1b12 introduces federated forums and is implemented by Lemmy, /kbin, and Friendica.</p> <p>The site reddit has a feature for grouping its forums, called subreddits, into a new forum, called a multireddit. A multireddit is a feed made up of the combination of each of its constituent subreddits and allows a user an easy way to keep up with multiple related forums. Because subreddits can be in multiple multireddits, multireddits don't affect moderation of links posted to individual subreddits and a link can be posted to more than one constituent subreddit within a multireddit.</p>"},{"location":"fep/d36d/#group-to-group-follows","title":"Group to Group Follows","text":"<p>When a moderator of a federated forum determines that their forum overlaps in topic with another forum, they can direct the <code>Group</code> actor to send a <code>Follow</code> activity to the other forum's <code>Group</code> actor. FEP-1b12 specifices that a group should automatically respond with an <code>Accept/Follow</code>, but this document overrides that for <code>Follow</code> activities with an <code>actor</code> of type <code>Group</code>. After receiving a <code>Follow</code> activity from another <code>Group</code>, the group MAY automatically respond with an <code>Accept/Follow</code> or a moderator may instruct the group to reply with a <code>Reject/Follow</code>. After replying with a <code>Accept/Follow</code> activity, the group that received the <code>Follow</code> activity MAY automatically add the first group to its <code>following</code> collection, creating a symmetric relationship.</p> <p>This document makes no change to the handling of an <code>Undo/Follow</code> activity. If a group receives an <code>Undo/Follow</code> from a <code>Group</code> actor, it MAY automatically remove the other group from its <code>following</code> collection.</p>"},{"location":"fep/d36d/#activity-handling","title":"Activity Handling","text":"<p>When a group receives an activity in its <code>inbox</code>, it SHOULD perform automatic validation as described in FEP-1b12. If that validation includes deduplication (via the <code>url</code> property of the activity's <code>object</code>, the <code>url</code> of any attachements, or any other method), that deduplication validation MUST include objects received from followed groups. If an activity fails this deduplication validation, the group MUST respond with a <code>Reject</code> activity where the <code>object</code> property is the <code>object</code> from the inbox activity and the <code>target</code> object is the object that the new object duplicates. This ensures that content is posted only once across related forums and a forum can provide navigation to an original post when a user tries to post a duplicate.</p> <p>If the incoming activity is deemed valid, the group MUST handle it according to FEP-1b12 handling of valid activities.</p>"},{"location":"fep/d36d/#references","title":"References","text":"<p>[FEP-1b12] Felix Ableitner, FEP-1b12: Group federation</p>"},{"location":"fep/d36d/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/d556/","title":"FEP-d556: Server-Level Actor Discovery Using WebFinger","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net <code>FINAL</code> implementation 2024-01-20 2025-03-15 #243 - codeberg"},{"location":"fep/d556/#summary","title":"Summary","text":"<p>Server-level ActivityPub actors support server-wide functionality rather than representing a user or the software equivalent (sometimes called a bot). This proposal describes how to discover a server-level actor's URI using WebFinger.</p>"},{"location":"fep/d556/#terminology","title":"Terminology","text":"<p>The term server is not well-defined. For the purposes of this document, an server is an origin SameOriginPolicy having the same URL prefix (scheme, host, port). The term does not imply anything about network or software architecture. An server could consist of many server processes behind a load-balancing reverse proxy. Or, inversely, a single server process could host many servers (multi-tenant architecture).</p> <p>Some implementations could have multiple actors to support different server-level roles (moderation, administration, etc.). In this document, the term server-level actor will be used to describe these kind of actors. The term Server Actor or Application Actor is a special, but common, case where there is a single server-level actor.</p> <p>The term Server is used extensively in the ActivityPub Recommendation, although it is mostly undefined beyond which activities a server may process. The term is closely related to Mastodon's use of the word instance, although this is not the only way the word is used in online discussions.</p> <p>NOTE: The standard role and responsibilities of server-level actors are not defined here (or elsewhere, at the time of this submission). Several implementations have something they call an Instance Actor or Application Actor, but they may or may not be interoperable since no standard behaviors have been defined at this time.</p>"},{"location":"fep/d556/#use-cases","title":"Use Cases","text":"<p>Although this FEP does not define specific uses of server-level actors, it's useful to know how they are, or could be, used in practice. The following are a some potential use cases:</p> <ul> <li> <p>Signing Fetch Requests: This appears to be the most common use case. The requirement for this is a combination of: limiting access to actor profiles by requiring HTTP Signatures (i.e., \"authorized fetch\") and tightly-coupling actor profiles with their public keys. This results in profile/key fetch loops. (InstanceActor). To mitigate this undesirable behavior, one technique is to have a third-party actor (often called an \"instance actor\") sign every fetch request. Actor discovery is not required for this use case, but it's mentioned here because it appears to be the motiving use case for FEP-2677, which has some similarities to this one.</p> </li> <li> <p>Relay Support: A server-level actor can be used for subscribing to a relay (often using an ActivityPub <code>Follow</code> request) and receiving <code>inbox</code> messages.</p> </li> <li> <p>Server-level Subscriptions: Some implementations, like Pleroma, provide an actor that can be followed to receive all messages from an \"instance\".</p> </li> <li> <p>Moderation: A server-level actor may be used to federate moderation-related content (actor or domain blocks, post flags, etc.) or provide a publication proxy to shield the identity of moderators performing the actions.</p> </li> <li> <p>Announcements: An server-level actor could be used to public server news. For example, it could publish content including announcements about new features, maintenance schedules, or updates.</p> </li> <li> <p>Object Attribution: Some server implementations allow some objects to be attributed to the server rather than an individual user or account.</p> </li> <li> <p>Administration: A server-level actor could be used to share information about software issue (including reports from users), available updates, and security vulnerabilities and mitigations.</p> </li> </ul>"},{"location":"fep/d556/#discovery","title":"Discovery","text":"<p>To discover an server-level actor's URI, query WebFinger with the server prefix as the resource query parameter.</p> <p>Example Request: <pre><code>GET /.well-known/webfinger?resource=https://server.example/\n</code></pre> Response: <pre><code>{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\"\n }\n ]\n}\n</code></pre> The <code>subject</code> would typically be the resource URI. This proposal does not depend on any specific URI for <code>subject</code>, although the ActivityPub actor URI is recommended.</p> <p>The Server-level Actor's URI will be the <code>href</code> property of a <code>link</code> with a <code>rel</code> (relation type) property of <code>https://www.w3.org/ns/activitystreams#Service</code> (W3C AS2 Service Primer). The type of the Server-level Actor itself is not required to be the same as the relation type.</p> <p>The <code>https://www.w3.org/ns/activitystreams#Service</code> <code>rel</code> value may be replaced with <code>self</code> if there is no ambiguity between the server-level actor and user's actor in a single actor server (see discussion of single-actor servers).</p> <p>A <code>http://webfinger.net/rel/profile-page</code> <code>rel</code> (WebFinger Relations) can be used to link to server metadata (possibly with multiple content types). However, the structure of the target metadata has not been defined at this time. For example, the following links refer to profile data in HTML and JSON-LD formats.</p> <pre><code>{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\"\n },\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://server.example/profile\"\n },\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"application/ld+json\",\n \"href\": \"https://server.example/profile\"\n }\n ]\n}\n</code></pre> <p>If multiple server-level actor links are returned, the links can be disambiguated by adding metadata to the links using standard WebFinger properties. For example, an implementation could have different server-level actors that serve different purposes. </p> <p>It's also possible that another FEP will define standard <code>rel</code> URIs for common roles. In that case, those FEP role URIs SHOULD be preferred. </p> <p>NOTE: The definition of standard server-level actor roles is outside the scope of this FEP.</p> <pre><code>{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\",\n \"properties\": {\n \"http://schema.org/roleName\": \"administration\"\n }\n },\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\",\n \"properties\": {\n \"http://schema.org/roleName\": \"moderation\"\n }\n }\n ]\n}\n</code></pre> <p>In this example, the same actor used used for administration and moderation. However, the example would also be valid if the actors were different. It's possible that for some use cases a role might be further refined. For example, additional properties might specify a geographical region for a role.</p>"},{"location":"fep/d556/#single-actor-servers","title":"Single Actor Servers","text":"<p>A developer of a single-actor (user actor) server may want that user to have a URI corresponding to the server prefix although it's not intended to be an server-level actor. This scenario, which is not expected to be a common one, can be supported by returning multiple links in the WebFinger response.</p> <pre><code>{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/server-actor\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/user-actor\"\n }\n ]\n}\n</code></pre> <p>If an application is only interested in a the Server Actor or User Actor specifically, it can use the <code>rel</code> query parameter to filter the links, as described in the WebFinger specification (if supported by the Webfinger service implementation).</p> <p>For example, to only query the User Actor URI, the query would be:</p> <pre><code>GET /.well-known/webfinger?resource=https://server.example/&amp;rel=self\n</code></pre> <pre><code>{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/user-actor\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/d556/#implementations","title":"Implementations","text":"<p>Known implementations include:</p> <ul> <li>FIRM</li> <li>Mastodon implements something similar to this proposal.</li> <li>Streams</li> <li>Mitra</li> <li>squidcity</li> <li>ActivityPub for WordPress</li> <li>tootik</li> </ul>"},{"location":"fep/d556/#mastodon-example","title":"Mastodon Example","text":"<p><pre><code>GET /.well-known/webfinger?resource=https://mastodon.social/\nHost: https://mastodon.social\n</code></pre> or using Mastodon account-based URI: <pre><code>GET /.well-known/webfinger?resource=acct:mastodon.social@mastodon.social\nHost: https://mastodon.social\n</code></pre></p> <pre><code>{\n \"subject\": \"acct:mastodon.social@mastodon.social\",\n \"aliases\": [\n \"https://mastodon.social/actor\"\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://mastodon.social/about/more?instance_actor=true\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://mastodon.social/actor\"\n },\n {\n \"rel\": \"http://ostatus.org/schema/1.0/subscribe\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={uri}\"\n }\n ]\n}\n</code></pre> <p>Some differences between the Mastodon implementation and this proposal include:</p> <ul> <li> <p>It does not support standard WebFinger filtering by <code>rel</code>.</p> </li> <li> <p>The <code>subject</code> is the Mastodon-specific account URI for the server-level actor rather than the recommended ActivityPub actor URI.</p> </li> </ul> <p>Since no user-related actor link is provided for the server resource, the <code>self</code> <code>rel</code> value can be used without ambiguity</p>"},{"location":"fep/d556/#related-proposals","title":"Related Proposals","text":"<p>FEP-2677 suggests using NodeInfo for a similar purpose. There are several disadvantages of this compared to using WebFinger. </p> <ul> <li>Although WebFinger is not required by the ActivityPub Recommendation, it is required for federation with most ActivityPub-based implementations (e.g., Mastodon and compatible implementations). NodeInfo is not required for federation, so requiring it's use for this purpose increases the complexity of federation with no benefits.</li> <li>WebFinger has been standardized by the Internet Engineering Task Force (IETC). NodeInfo is defined informally.</li> <li>WebFinger is already used to resolve resource identifiers and provide links to server-level metadata (e.g., profile page URLs). NodeInfo is primarily used for gathering and aggregating server metadata.</li> <li>FEP-2677 adds a new non-standard <code>rel</code> relation to the NodeInfo index document. This may have surprising effects on some consuming implementations. This proposal is using WebFinger in standard ways.</li> <li>Given an ActivityVocabulary actor type is being used for the WebFinger <code>rel</code> value, a <code>as:Service</code> (Primer) is the type suggested by the W3C ActivityStreams Primers for this kind of resource rather than <code>as:Application</code> (Primer). (Note this is distinct from the type specified in the server-level actor resource that's linked from WebFinger.)</li> <li>FEP-2677 only defines a singleton server-level actor. This proposal allows that use case but has more flexibility for advanced implementations.</li> <li>FEP-2677 Requires actors to have an <code>as:Application</code> type. This proposal has no constraints on the actor type. The <code>as:Service</code> URI is only used for the link relation type.</li> </ul> <p>Although the definition isn't clear, the \"Application Actor\" in FEP-2677 appears to be a proxy for a software \"application\" (not defined, but appears to be a similar concept to \"server\" in this proposal). For example, there's a discussion about attaching application metadata to the actor. In this proposal, there is no server proxy actor (although that's not prohibited). There is a server WebFinger resource with linked server-level service actors, but the server resource is not necessarily an actor itself.</p> <p>FEP-2c59 discusses how to discover WebFinger resource URIs from an ActivityPub actor resource. This is not related to server-level actor discovery.</p> <p>FEP-4adb discusses dereferencing identifiers with WebFinger. It's similar to this proposal but not specifically related to discovering server-level actors.</p>"},{"location":"fep/d556/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, ActivityStreams Vocabulary, 2017</li> <li>W3C ActivityStreams Primer - Application type</li> <li>W3C ActivityStreams Primer - Service type</li> <li>Eugen Rochko, Mastodon, 2016</li> <li>Jonne Ha\u00df, NodeInfo 2.1</li> <li>MDN, Same-origin Policy</li> <li>Brad Fitzpatrick, WebFinger, 2013</li> <li>WebFinger.net Link Relations</li> </ul>"},{"location":"fep/d556/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/d767/","title":"FEP-d767: Extend ActivityPub with Valueflows","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Lynn Foster lynnfoster@social.coop <code>WITHDRAWN</code> 2023-04-02 2024-02-03 #76 - codeberg"},{"location":"fep/d767/#summary","title":"Summary","text":"<p>A standard method to extend ActivityPub/ActivityStream with Valueflows vocabulary, to enable varied economic networking activity in the fediverse.</p>"},{"location":"fep/d767/#history","title":"History","text":"<p>Valueflows was started in 2015 by a group of developers who met around the concept of an \"open app ecosystem\". The goal was to define one of the vocabularies needed to enable modular apps and components to communicate in a standard way, using distributed architecture. By extension, the goal was for people and organizations to more easily work together to coordinate the creation, distribution, and exchange of economic resources.</p> <p>Valueflows got a first version out in 2017, and started a period of learning from implementations.</p> <p>Valueflows met the fediverse through Mayel de Borniol and Ivan Minutillo, and work was begun in 2017 to integrate Valueflows into the software that became Bonfire.</p> <p>Valueflows would like to get to a stable v1.0 this year. The core is stable, but there are several features around the edges that are either not yet implemented or not yet tested enough.</p>"},{"location":"fep/d767/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in [RFC-2119].</p> <p>Fediverse software that supports economic activity MAY implement the Valueflows extension.</p>"},{"location":"fep/d767/#vocabulary-integration","title":"Vocabulary Integration","text":"<p>Note this is not at all finalized, will be seeking feedback in SocialHub.</p> <p>The following general patterns MUST be followed to integrate Valueflows objects into ActivityPub messages.</p> <ul> <li>Any Valueflows construct (a class or a more complex bundle of related classes) will be an Object in the AP message.</li> <li>The activities Create, Update, and Delete will be used.</li> <li>Other possibilities to be investigated: Add, Remove, Accept, Reject.</li> <li> </li> </ul> <p>The general idea is that AP/AS will be used basically for messaging. Valueflows does have some concepts that could be considered activities, but they don't fit nicely into the current list.</p> <p>Note: The overlap between the Agent portion of the Valueflows model and the AP actors will not be addressed at this time, due to the complexity of mapping to the actor model, as well as the in-process suggestions for AP groups, organizations, communities, etc. Until it is explicitly addressed, when there is overlap, any Person, Group, Organization actor with agency can be used as a Valueflows Agent. In addition, Valueflows will include Agents that are not AP actors, and these can be handled with the standard Create, Update, Delete.</p> <p>Examples (loosely documented atm, just for discussion): <pre><code>{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n },\n \"summary\": \"Sally created a project plan.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Sally\"\n },\n \"object\": {\n \"type\": \"vf:Plan\",\n \"name\": \"Define AP-VF Extension\",\n \"content\": \"Collaborate on defining the VF extension to AP/AS. Propose as a FEP (Fediverse Enhancement Proposal).\" # or use skos:note per vf, different meanings?\n }\n}\n\n{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n },\n \"summary\": \"Sally created a process.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Sally\"\n },\n \"object\": {\n \"type\": \"vf:Process\",\n \"vf:name\": \"Draft AP-VF examples\",\n \"content\": \"Collaborate on examples to get discussion going on the VF extension to AP/AS.\",\n \"vf:inScopeOf\": {\n \"type\": \"Organization\",\n \"vf:name\": \"HUMANs\", # could use target for this?\n },\n \"vf:plannedWithin\": {\n \"type\": \"Plan\",\n \"vf:name\": \"Define AP-VF Extension\", # this would be an id?\n },\n \"vf:outputs\": {\n \"object\": {\n \"type\": \"vf:Commitment\",\n \"vf:action\": \"produce\",\n \"vf:conformsTo\": {\n \"type\": \"ResourceSpecification\",\n \"vf:name\": \"Vocabulary Spec\"\n },\n \"vf:due\": \"2023-05-08T10:30:00-5:00\",\n \"vf:provider\": {\n \"type\": \"Organization\", # vf? as?\n \"vf:name\": \"HUMANs\"\n },\n \"vf:receiver\": {\n \"type\": \"Organization\",\n \"vf:name\": \"SocialHub\"\n }\n }\n }\n }\n}\n\n{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\"\n },\n \"summary\": \"Lynn created a commitment.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Lynn\"\n },\n \"object\": {\n \"type\": \"vf:Commitment\",\n \"vf:inputOf\": {\n \"type\": \"vf:Process\",\n \"vf:name\": \"Draft AP-VF examples\"\n }\n \"vf:action\": \"work\",\n \"vf:conformsTo\": {\n \"type\": \"ResourceSpecification\",\n \"vf:name\": \"Vocabulary Work\"\n },\n \"vf:effortQuantity\": {\n \"type\": \"om:Measure\",\n \"om2:hasNumericalValue\": 10,\n \"om2:hasUnit\": \"hr\"\n }\n \"vf:due\": \"2023-05-01T10:30:00-5:00\",\n \"vf:provider\": {\n \"type\": \"Person\", # vf? as?\n \"vf:name\": \"Lynn\"\n },\n \"vf:receiver\": {\n \"type\": \"Organization\",\n \"vf:name\": \"HUMANs\"\n },\n \"content\": \"First draft of examples, submit for feedback.\" # skos:note?\n }\n}\n</code></pre></p>"},{"location":"fep/d767/#only-stable-parts-of-the-valueflows-vocabulary-will-be-addressed","title":"Only stable parts of the Valueflows vocabulary will be addressed","text":""},{"location":"fep/d767/#implementations","title":"Implementations","text":"<ul> <li>Bonfire</li> <li>Oceco - Communecter (not yet)</li> </ul>"},{"location":"fep/d767/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[ActivityStream] James M Snell, Evan Prodromou, ActivityStream, 2017</li> <li>[Valueflows] Lynn Foster, elf Pavlik, Bob Haugen, Valueflows, 2023</li> </ul>"},{"location":"fep/d767/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/d8c2/","title":"FEP-d8c2: OAuth 2.0 Profile for the ActivityPub API","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evanp@socialwebfoundation.org <code>DRAFT</code> 2023-09-17 #165 Discussions codeberg"},{"location":"fep/d8c2/#summary","title":"Summary","text":"<p>This FEP defines a mechanism for using an ActivityPub object ID as the <code>client_id</code> in the OAuth 2.0 authorization code flow.</p> <p>(An earlier version defined a full profile for using OAuth 2.0 with the ActivityPub API, but this version has been abbreviated to focus only on the client ID mechanism. The title has been retained to accommodate FEP tooling.)</p>"},{"location":"fep/d8c2/#motivation","title":"Motivation","text":"<p>ActivityPub defines the ActivityPub API, a RESTful HTTP API for stream-oriented social software. This API allows client software to read ActivityPub objects, including actors, collections, activities, and content objects. Client software can also create new <code>Activity</code> objects by posting to an actor's <code>outbox</code> collection (also called \"client-to-server\" or \"c2s\").</p> <p>The ActivityPub specification does not define an authorization mechanism for the API, although the ActivityPub Primer Authorization and Authentication recommendations include some suggestions. Although there are many ways to implement client authorization for an API, OAuth 2.0 is a popular and well-understood framework.</p> <p>OAuth 2.0 is broad and encompasses a number of different techniques and use cases. OAuth 2.0 Simplified documents the most common profile of OAuth 2.0: authorization code flow and bearer tokens. Many OAuth 2.0 client libraries implement this profile.</p> <p>The OAuth 2.0 authorization code flow requires two main endpoints for a client to initiate the flow: an authorization endpoint and a token endpoint. These can be discovered using the <code>endpoints</code> property of the ActivityPub actor or the Authorization Server Metadata endpoint from RFC 8414.</p> <p>The OAuth 2.0 flow uses a client identifier to show important information about the client software to the user, and to avoid certain classes of spoofing attacks.</p> <p>A common use case for OAuth 2.0 is an API supplied by a single provider. With a single provider, the client developer can register a client ID out of band using the provider's developer Web site or other tools.</p> <p>With multiple providers, as with the Fediverse, out-of-band registration becomes untenable. With tens of thousands of known ActivityPub servers on the Internet, client developers cannot manually register client IDs with each provider of the ActivityPub API.</p> <p>One option is to use Dynamic Client Registration protocol from RFC 7591. This defines a standard HTTP endpoint used for registering an application with an authorization server and receiving a unique client identifier.</p> <p>Dynamic client registration adds some extra complexity on the client side. In particular, client software has to maintain a record of the correct client ID for each authorization server used.</p> <p>This profile addresses these issues by using a single, well-defined ActivityPub object to identify and describe the client software.</p>"},{"location":"fep/d8c2/#client-identifier","title":"Client identifier","text":"<p>ActivityPub provides a rich vocabulary for describing objects in the social space. Each object in the ActivityPub world has a unique https: URI, which must be dereferenceable to a JSON-LD document describing the object.</p> <p>This allows a distributed description of ActivityPub API clients that doesn't require out-of-band registration.</p> <p>Objects dereferenced at the id SHOULD be of type <code>Application</code> or <code>Service</code>. They MUST have an <code>id</code> property with the same value as the <code>client_id</code> parameter. They MUST have a <code>redirectURI</code> property with the redirect URI for the client (see Context document below).</p> <p>Clients SHOULD provide metadata to help users make authorization decisions, including:</p> <ul> <li><code>nameMap</code> or <code>name</code>: The name of the client software.</li> <li><code>icon</code>: An <code>Image</code> object with the icon for the client software.</li> <li><code>summaryMap</code> or <code>summary</code>: A description of the application or service.</li> <li><code>attributedTo</code>: The <code>name</code>, <code>id</code>, <code>icon</code> and <code>summary</code> properties of the actor responsible for the client software.</li> </ul>"},{"location":"fep/d8c2/#discovery","title":"Discovery","text":"<p>Support for using ActivityPub object IDs as OAuth 2.0 client IDs can be declared in two ways.</p>"},{"location":"fep/d8c2/#actor-discovery","title":"Actor discovery","text":"<p>An ActivityPub actor can include the <code>objectIDAsClientID</code> property. If <code>true</code>, client software can use the client ID format in this specification to identify themselves to authorization servers.</p>"},{"location":"fep/d8c2/#authorization-server-metadata","title":"Authorization Server Metadata","text":"<p>An authorization server can declare its support for ActivityPub object IDs as client IDs by adding the <code>activitypub_object_id_as_client_id</code> flag to its Authorization Server Metadata.</p>"},{"location":"fep/d8c2/#context-document","title":"Context document","text":"<p>The context document for this specification is at <code>https://purl.archive.org/socialweb/oauth/2.0</code>. Its contents are as follows:</p> <pre><code>{\n \"@context\": {\n \"oauth\": \"https://purl.archive.org/socialweb/oauth#\",\n \"redirectURI\": {\n \"@id\": \"oauth:redirectURI\",\n \"@type\": \"xsd:anyURI\"\n },\n \"objectIDAsClientID\": {\n \"@id\": \"oauth:objectIDAsClientID\",\n \"@type\": \"xsd:boolean\"\n }\n }\n}\n</code></pre>"},{"location":"fep/d8c2/#context-url-aliases","title":"Context URL aliases","text":"<p>Aliases are provided for the context URL to allow change over time with backwards compatibility, using a semantic versioning strategy.</p> <ul> <li><code>https://purl.archive.org/socialweb/oauth/2.0.0</code> This URL will be bytewise stable, and can be used for clients that use digital signatures or hashes to validate context URLs.</li> <li><code>https://purl.archive.org/socialweb/oauth/2.0</code> The preferred URL. Backwards-compatible changes, such as whitespace and formatting, may be made, but no new terms will be added and none will be removed or modified. Will be kept up to date with the latest 2.0.x version.</li> <li><code>https://purl.archive.org/socialweb/oauth/2</code> New terms may be added, but none will be removed or modified. Will be kept up to date with the latest 2.x.x version.</li> <li><code>https://purl.archive.org/socialweb/oauth</code> The latest version of the context document; backwards-incompatible changes may be applied, such as removing or modifying terms.</li> </ul> <p>New versions of the context document will increment the major, minor and patch version as needed.</p>"},{"location":"fep/d8c2/#properties","title":"Properties","text":""},{"location":"fep/d8c2/#redirecturi","title":"redirectURI","text":"<p>The <code>redirectURI</code> property is an IRI that the client uses to receive the authorization code after the user authorizes the client. The server MUST verify that the <code>redirect_uri</code> parameter in the authorization request matches the <code>redirectURI</code> property of the client object.</p>"},{"location":"fep/d8c2/#objectidasclientid","title":"objectIDAsClientID","text":"<p>This flag has a boolean value, <code>true</code> or <code>false</code>. If true, the authorization server for the actor with this property supports using ActivityPub object IDs as client IDs, as described in this document.</p>"},{"location":"fep/d8c2/#examples","title":"Examples","text":""},{"location":"fep/d8c2/#actor-flag","title":"Actor flag","text":"<p>The following actor description declares that the actor's authorization server supports ActivityPub object IDs as OAuth 2.0 client IDs using the <code>objectIDAsClientID</code> flag.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https://social.example/user/evan\",\n \"inbox\": \"https://social.example/user/evan/inbox\",\n \"outbox\": \"https://social.example/user/evan/outbox\",\n \"endpoints\": {\n \"oauthAuthorizationEndpoint\": \"https://social.example/authorize\",\n \"oauthTokenEndpoint\": \"https://social.example/token\"\n },\n \"objectIDAsClientID\": true\n}\n</code></pre>"},{"location":"fep/d8c2/#authorization-server-metadata-flag","title":"Authorization Server Metadata flag","text":"<p>An authorization server can declare its support for using ActivityPub object IDs as client IDs with the <code>activitypub_object_id_as_client_id</code> flag.</p> <pre><code>{\n \"issuer\": \"https://social.example\",\n \"authorization_endpoint\": \"https://social.example/authorize\",\n \"token_endpoint\": \"https://social.example/token\",\n \"registration_endpoint\": \"https://social.example/registration\",\n \"scopes_supported\": [\n \"read\",\n \"write\"\n ],\n \"response_types_supported\": [\n \"code\"\n ],\n \"grant_types_supported\": [\n \"authorization_code\",\n \"refresh_token\"\n ],\n \"code_challenge_methods_supported\": [\n \"S256\"\n ],\n \"token_endpoint_auth_methods_supported\": [\n \"none\"\n ],\n \"activitypub_object_id_as_client_id\": true\n}\n</code></pre>"},{"location":"fep/d8c2/#follower-recommender","title":"Follower recommender","text":"<p>A Web service that wants to use the ActivityPub API would define an ActivityPub object at <code>https://followrec.example/client</code>. This object has a <code>redirectURI</code> property with the URI of the Web application's authorization endpoint.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https:/followrec.example/apps/myapp\",\n \"name\": \"Follow Recommender\",\n \"type\": \"Service\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"http://followrec.example/followrec.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Follow Recommender is a service that recommends people to follow based on your existing community.\"\n },\n \"attributedTo\": {\n \"name\": \"Alyssa P. Hacker\",\n \"id\": \"https://hackers.example/alyssa\",\n \"type\": \"Person\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://hackers.example/alyssa/icon.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Alyssa P. Hacker builds cool stuff on the Internet.\"\n }\n },\n \"redirectURI\": \"https://followrec.example/oauth/callback\"\n}\n</code></pre>"},{"location":"fep/d8c2/#mobile-checkin-app","title":"Mobile checkin app","text":"<p>An iOS app uses the ActivityPub API to post location updates for a user. Because the app is a native program, it uses a static site provided by its version control system to host the client object at <code>https://developer.git.example/kfc/client.json</code>.</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https://developer.git.example/kfc/client.json\",\n \"name\": \"Kentucky Fried Checkin\",\n \"type\": \"Application\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://developer.git.example/kfc/icon.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Kentucky Fried Checkin is a mobile app that allows you to post checkins to your ActivityPub timeline.\"\n },\n \"attributedTo\": {\n \"name\": \"MobileCorp\",\n \"id\": \"https://mobilecorp.example/organization\",\n \"type\": \"Organization\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://mobilecorp.example/organization/logo.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"MobileCorp provides cool apps supporting the social web.\"\n }\n },\n \"redirectURI\": \"checkin:oauth/callback\"\n}\n</code></pre> <p>Note that the <code>redirectURI</code> property is a custom URI scheme for the mobile app.</p>"},{"location":"fep/d8c2/#security-considerations","title":"Security considerations","text":"<ul> <li>OAuth 2.0 Security Best Current Practice provides a number of best practices for implementing OAuth 2.0.</li> <li>One risk of implementing OAuth 2.0 is that the user is redirected to the <code>redirect_uri</code> parameter after authorization is complete. This can be used as an attack to treat the authorization server as an open redirector. An app using an OAuth 2.0 authorization endpoint as an open redirector could change the client description document to have a different <code>redirectURI</code> for each request. One mitigation would be to archive the <code>redirectURI</code> value for each client, and cancel the flow if the value has changed too often.</li> <li>As with any protocol that requires fetching a client-provided URI, the server should take care in dereferencing the <code>client_id</code> parameter to avoid attacks such as very large responses, responses that take a long time to generate, or responses with poorly-formatted content.</li> <li>The ActivityPub object used to define the client includes metadata that can be spoofed, like the <code>name</code> or <code>icon</code>. An attacker could use the name, icon, or publisher of a popular application to trick users into authorizing the attacker's application. Tools such as shared blocklists, reputation systems, and user education can mitigate this risk.</li> </ul>"},{"location":"fep/d8c2/#iana-considerations","title":"IANA Considerations","text":""},{"location":"fep/d8c2/#oauth-authorization-server-metadata-registry","title":"OAuth Authorization Server Metadata Registry","text":"<p>The following authorization server metadata value is defined by this specification and registered in the IANA \"OAuth Authorization Server Metadata\" registry established in OAuth 2.0 Authorization Server Metadata RFC8414.</p> <ul> <li>Metadata Name: activitypub_object_id_as_client_id</li> <li>Metadata Description: Boolean value specifying whether the authorization server supports using ActivityPub object IDs as client IDs.</li> <li>Change Controller: W3C Social Web Incubator Community Group</li> <li>Specification Document: https://fediverse.codeberg.page/fep/fep/d8c2/</li> </ul>"},{"location":"fep/d8c2/#references","title":"References","text":"<ul> <li>Dick Hardt, The OAuth 2.0 Authorization Framework, 2012</li> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> </ul>"},{"location":"fep/d8c2/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/d8c8/","title":"FEP-d8c8: BitTorrent <code>Torrent</code> Objects","text":"Authors Status Date received Tracking issue Discussions Repository Jonny Saunders j@nny.fyi <code>DRAFT</code> 2025-11-03 #722 Discussions codeberg"},{"location":"fep/d8c8/#summary","title":"Summary","text":"<p>The BitTorrent protocol is a p2p protocol for distributing data described as a series of hashes and file metadata contained in <code>.torrent</code> files. This FEP describes a JSON-LD representation of <code>.torrent</code> files as an extension of an ActivityStreams <code>Object</code>.</p>"},{"location":"fep/d8c8/#background","title":"Background","text":"<p>A torrent file<sup>1</sup> is an abbreviated, verifiable description of a file or directory that, at minimum, contains </p> <ul> <li>A series of \"piece hashes\" that summarize pieces of the data (see BEP 0003, BEP 0052)</li> <li>A description of how the data is laid out in files and directories</li> <li>Metadata like the <code>piece length</code> that allow downloaded data to be validated against the hashes</li> </ul> <p>A torrent consists of one outer \"metainfo\" dictionary, and an inner \"info\" dictionary (or, informally an \"infodict\"). A SHA1 (in the case of v1 torrents) or SHA256 (for v2 torrents) digest of the bencoded infodict serves as the content address for the data summarized by the torrent, or the \"infohash\".</p> <p>Torrents are an open world specification: the specification sets the minimum required keys and their use, but any additional keys are allowed, ignored if the consuming client doesn't understand them. Torrents are encoded in a bittorrent-specific ASCII-based encoding, \"<code>bencoding</code>,\" whose strings, integers, lists, and dictionaries can be represented as a subset of JSON. Together, this means that with minimal adaptation, torrents can be represented as JSON-LD, with protocol extensions annotated with vocabularies imported by the <code>@context</code>.</p>"},{"location":"fep/d8c8/#spec","title":"Spec","text":"<p>MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS.</p> <p>CURIE prefixes used in this document: - <code>bt</code>: <code>https://w3id.org/fep/d8c8#</code> - the namespace created by this FEP (see FEP-888d), referring to the attached JSON-LD context <code>fep-d8c8.jsonld</code> - <code>as</code>: <code>https://www.w3.org/ns/activitystreams</code> - the ActivityStreams vocabulary</p> <p>A <code>Torrent</code> object is an extension of an ActivityStreams <code>Object</code> whose <code>\"type\"</code> is <code>\"Torrent\"</code> and whose <code>uri</code> is <code>bt:Torrent</code> (or, expanded, <code>https://w3id.org/fep/d8c8#Torrent</code>).</p> <p>The contents of a <code>Torrent</code> object are, at minimum, those specified by BEP 0003 (bittorrent v1) or BEP 0052 (bittorrent v2)<sup>2</sup>.</p>"},{"location":"fep/d8c8/#json-encoding","title":"JSON Encoding","text":"<p><code>Torrent</code> objects MUST be JSON encoded, mapping bencoded strings, integers, lists, and dictionaries to their counterparts in JSON. </p> <ul> <li>Strings that are not explicitly intended to be interpreted as binary data SHOULD be encoded as utf-8. </li> <li>Strings that are to be interpreted as binary data SHOULD be encoded as RFC 4648 base64, EXCEPT</li> <li>Strings that always represent individual hashes SHOULD be encoded as hexadecimal strings.</li> <li>The encoding used for the value of <code>pieces root</code> in v2 <code>file tree</code>s MUST be the same as the encoding used for keys in the <code>piece layers</code> dict.</li> </ul> <p>Examples of strings that should be encoded as base64: - the value of the <code>pieces</code> string in the v1 info dict, even when there is only one piece hash. - the value of of the piece hashes within a v2 <code>piece layers</code> dictionary, even when there is only one piece hash.</p> <p>Examples of strings that should be encoded as hexadecimal: - v1 and v2 infohashes (below) - keys in the v2 <code>piece layers</code> dict, and values of <code>pieces root</code> within <code>file tree</code></p>"},{"location":"fep/d8c8/#bencoding","title":"Bencoding","text":"<p>Additional terms may be added to the metainfo and info dictionaries, and when returning to the <code>bencoded</code> form these terms MUST be kept in their unexpanded form (i.e. not expanded to full URIs if they are terms from the JSON-LD Context) EXCEPT for the terms specified in this FEP, which MUST be removed from the <code>bencoded</code> form.</p> <p>When bencoding a <code>Torrent</code> object, the inverse encoding to that described above MUST be applied to strings: hexadecimal, base64, and unicode strings MUST all be decoded to binary.</p>"},{"location":"fep/d8c8/#additional-keys","title":"Additional Keys","text":"<p><code>Torrent</code> objects MUST contain their infohashes using the following keys, depending on whether they are v1, v2, or hybrid torrents:</p> <ul> <li><code>bt:infohash_v1</code>: v1-only and hybrid torrents MUST have a hexadecimal SHA1 hash of the bencoded <code>info</code> dict</li> <li><code>bt:infohash_v2</code>: v2-only and hybrid torrents MUST have a hexadecimal SHA256 hash of the bencoded <code>info</code> dict</li> </ul> <p>Software that consumes <code>Torrent</code> objects SHOULD verify that the infohashes provided in the <code>Torrent</code> object match those computed by the bencoded form of the <code>Torrent</code>'s <code>info</code> dict, and MAY choose to not process <code>Torrent</code> objects with incorrect infohashes.</p> <p><code>Torrent</code> objects MAY contain the following keys:</p> <ul> <li><code>bt:bencoded</code>: a URI to the bencoded form of the torrent.</li> <li><code>bt:magnet</code>: A magnet URI that MUST contain the same information as the <code>Torrent</code> object, subset to the fields that have corresponding keys in the magnet URI scheme.</li> </ul>"},{"location":"fep/d8c8/#abbreviated-representation","title":"Abbreviated Representation","text":"<p>In Collections or other circumstances where presenting the full, materialized <code>Torrent</code> object might be expensive, implementing software MAY present the object in an abbreviated form consisting of </p> <p>Required: - the object's <code>id</code> uri - its infohash(es): <code>bt:infohash_v1</code>, <code>bt:infohash_v2</code>, if applicable.</p> <p>Optional: - <code>bt:bencoded</code>: a URI to the bencoded form of the torrent. The linked bencoded torrent MUST be equivalent to a bencoded version of the JSON <code>Torrent</code> object (see Bencoding, above) - <code>bt:magnet</code>: a magnet URI for the torrent.</p> <p>In circumstances where other ActivityStreams Objects might be presented as a bare URI, <code>Torrent</code> objects SHOULD be presented as this abbreviated form, allowing the possibility of using the infohash to dereference the torrent contents via DHT or other means.</p>"},{"location":"fep/d8c8/#examples","title":"Examples","text":""},{"location":"fep/d8c8/#torrent-objects","title":"Torrent Objects","text":"<p>For some example torrent with arbitrary contents, we might expect the JSON <code>Torrent</code> to look like this (assuming the <code>@context</code> is supplied in some outer scope):</p>"},{"location":"fep/d8c8/#v1","title":"v1","text":"Expand/collapse v1 Example <pre><code>{\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"creation date\": 1724037213,\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"files\": [\n {\n \"length\": 5261174,\n \"path\": [\n \"tentacoli-15-Tentacles (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 2778997,\n \"path\": [\n \"tentacoli-02-She'll Never Come Back.mp3\"\n ]\n },\n {\n \"length\": 2333412,\n \"path\": [\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\"\n ]\n },\n {\n \"length\": 2562622,\n \"path\": [\n \"tentacoli-04-Summer And Winter.mp3\"\n ]\n },\n {\n \"length\": 2796486,\n \"path\": [\n \"tentacoli-05-San Diego, Yellow Cab.mp3\"\n ]\n },\n {\n \"length\": 3863849,\n \"path\": [\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\"\n ]\n },\n {\n \"length\": 3696387,\n \"path\": [\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\"\n ]\n },\n {\n \"length\": 2583746,\n \"path\": [\n \"tentacoli-08-Sorry, I Have To Go.mp3\"\n ]\n },\n {\n \"length\": 1924024,\n \"path\": [\n \"tentacoli-09-Scotch For Two.mp3\"\n ]\n },\n {\n \"length\": 2463949,\n \"path\": [\n \"tentacoli-10-The Killer Whales' Games.mp3\"\n ]\n },\n {\n \"length\": 1783901,\n \"path\": [\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\"\n ]\n },\n {\n \"length\": 2965602,\n \"path\": [\n \"tentacoli-12-Two Old Kids.mp3\"\n ]\n },\n {\n \"length\": 4319196,\n \"path\": [\n \"tentacoli-13-Tentacles.mp3\"\n ]\n },\n {\n \"length\": 1555839,\n \"path\": [\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 3177585,\n \"path\": [\n \"tentacoli-01-Small Town Pleasures.mp3\"\n ]\n },\n {\n \"length\": 2877921,\n \"path\": [\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 1079679,\n \"path\": [\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\"\n ]\n },\n {\n \"length\": 874993,\n \"path\": [\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 3951350,\n \"path\": [\n \"tentacoli-19-Tentacles (Versione 3).mp3\"\n ]\n },\n {\n \"length\": 4358356,\n \"path\": [\n \"tentacoli-20-Sails.mp3\"\n ]\n },\n {\n \"length\": 2870766,\n \"path\": [\n \"tentacoli-21-Sails (Versione 2).mp3\"\n ]\n }\n ],\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 131072,\n \"pieces\": \"KHWQgYXgbUMnxhVsSiMXnmT8XKpeHwMOnVFCk+yZuDQegJoJy+JuxOxmxOLM3Ah0uGR5OsGgt3UmkZFMCCUNZkfa/MwHc5WE+PMedjx7sbNcNq8aBOEt4+m4qsC4WXfU+21w58CpXdIw59MVvhTpvfWSgE/zfGjEcQpMe7bpWgP5cBIVRo2uF8TyU7GhgHxmJf2dAGeNb5qxsNiEFr/aLmFc9S9+ImE+DFBf2xVfWommHSE+RiLUKoR+cGlFsuRd8nI3TptBqYhI/u+2XWE1NI//egRcAGPewa9rMQ+MYPtcBQdAxndhysx9eL4AB6vPalSaYiFrJoSAJ/tcR8HRNGTu2HVb4Z71gy/+vo8kJDJBXZA+YN/1Al1F0CXucst3Ksh5aCpOnFyJfnfWJh6LZCZtaq42eVmoco6p9U1YYJGPSnwIWxnZXgikExWlC2O/lGUUdTzLBMIzbGxZLzknJ720C5n7zfac7zIoHIY10ga4yjemeg9D/4J+NUIrTVc34J35sgniluu98Lk+VpUKJ+WCKWI47Ttv0rJ3sDobiHSkApRYt/eRmrr4wo8IVFnyWmL1HVT+Lwdl5zKnjv3m/iqmPgytNAlZIagzwAJYFFQBaE+7TcKVY65Qw3TAEEMbALv1/ukB99sClOK3FVY1IxzTJPEnBUHjq7grZUpYb0elaT0Li8fw8fpcJY9Guctw2KcZ0cXnDCZb/8FzxWW/s08uNyzAbphnHi1F5XznhsN4GcieykZD72fI3Bln7YlvtDcHipabxGOX3c+WzHSIK5+YNDKqLLhH9hEJ7orw+ViSyWABEgI4esSogymT/mhKUIhhFhRY8OHWM/4d7n04dvfuGs6NawkMn8mDK5XnqcdVxZgGmF8b6+DDTkqQMpn1t5f8spa1NhHglxqPkfVvqqLs8aEOrGE3VfBmqmaRCngIGQTlAIVo3hcEd7t24896xifWf8AQLiqfKbAJXRAPxQG4dRM49YW5aUeaWbv8vt10WlBsfVBVTL2yo+B9ePWvlj2QW41a84v3VorndZS9x34RVNKqLRAYmXt54u3ZHu4BtaHjZovhbGTTPclAF4N5vQ3ymf7Q0UCyagp9FOwLSmj7d64SvoacPN8Sp4fy8Ylxn69ns+df3wkhwAqHwYeY0ys3JeEER4paBuMyetcX8IRYQMc3zfHYBi+IA6Xp87Ey56I8p9e7WVGyb/kt2krhpaN34A6UgXIepU9iJ0npY/mzKpt7uMv250EnoaFXYscdG4yLqp2Q3sGpJOeuS/csBCiqYPR71lBQACXRpAthXNcxh9Yu36jbfdQOxNgmtVlsx14/TtjvhVJUzPXpjFDgds82QBGw6xPhTSZLm50diF3X055xbvl2aEdqZgO4DmFV0C64lkQ9wYg7Kgb1QpeE18k0SPaYP7D8TONpq6o6rl5f8ug+I6Kx2vlZxEx53dyXTg0BYv8a6j40xdMR3IonlBLlkrx9BvznuR1qmgFJ5bgGRsky8qNx7RP+HYZpzYkPMPe8HykGOHdN/o7I81HlWiPzj0u5lkEIoaY9V6YAXcDNGLAR1X0BF0MtR6sKUe3FK8NbVVStVECcwz9fQO7bO48OuWxNDJZjK6qeZ6CmwZUc2hZkfLAmN8DmrRCbQUuYdXblQvEnIXYjeyB1kL0cigIN0eO7uSVep8Tkr7OPr6ZadZqtHLfp8+743Y6GhCfqq4dlcu30kPUwOqJc4ZnF8dIVPb336lq2RVLARYJyBD0Ut8gQQLmTBYoNVxNhI/ygkDBRjbK6TCkO6lKNuVkJitdiRZO7KNvKmHzV1sXnS6BYOAvfmyaLm5MG4sxKQtLww0mmvh77fpIAFuZDY5nqYHyDYxOoeoL0GCUgYJPw/7lNj6KGMozdW7ewAoyUp3/n5n9WS4sLx8hxj+tUf7jN9oqrgPZJb/IlkLhqa48hJTmefbcSxCNqnZrld01Lua0qmclvPESm3IOn8ZDwcZ3qTL46nt2zCm1ovuWMBPS5RLuVBGYelfgvEnUac53P13/TiN9dZXHlMOsdzkqwi8Q8npZoaZvvjTOPTXYQbH8nA8z5e7yGDAL8PFsLOakKUOMZF3LxiHl3Zqefg1yYgnMtVjvER9T2dQVmxVolRokdZx9un0tQJL2h/fCAIIpAGClsjDOv4rzxEswAXLM265U8g9LDWK0o9+dlhLB/HvDBTqK36eDjQXpqOGU4R+8GEm4SVN0Ws0MCXQoYdkdoFcddwptuyKoywdfw+UlGy4aOXZjuMmHetS457CAQnQTjx3YEvtOJ5VsMlj8UndFnOXtOdTABLe4gUjtPgO83KLOg6Zm7JsIw/Vo2IbgX9R/uxeyqDA56ZV43V1KKdFV6xUgU+pcFQQwBTiV6WtF7jQ472NgSJULEhrl4CltpBJ4m/+tjQ06tom146skdaeL8xkfrjPqm74gYlKOqRIsiWa04Yiytw110DIOnvW/SRCnu99hdCkzIE3quV1boXsmDkiYPBUHUIQeEJU6a+qxXozGY+ETe5udc287NJr1byeKRPjg5DYbpgb7xOsBZzj1D656iQeteICu0Ylrmx4XJbsT/7y0VjCMi6bwGUcnAZ/mNZDPKJPcQBReOCdnXp7716tYw2VxTKQpHWWkxQkCFtgH0z8EHuyjZscZD5O48i9qrhCMs21vVHifLHphVSKRX2PhdL4l943JEg5O9EkGnI+Us1rD2voISp+f3VSFkw4owBM/yuUenUU6IN9sdgpc/HHeIAqEFrBtozkyWQIhS4kUeSLmRsmM3e6wAIztCLSb/u9jSir9qctk/xVbAj4sf9Cdc2LZM/mMzg8x4wTbrZK6B/OByTR/SyV89ZAFWxFsPbm+hWZx82B47Vhp6fU480/C38aaUCItSQAtT08VBF51kXQ1BDZ8cqsmbf4ErbBYi+rPXhSf5nO/PuCg6ZJmfOeDZW8aRw25mdzoKpNVXbDU64BlFlNrrgc4y/jYT467jU2tzxrt1kBplE376xocagNK3HU8SEPCC1wFEOthZiVOM91aCdkt4QznjENfZqxARiKT+EddA/khZ+P79HU9O7Tsd73/z21JXsqAkF/t4ZhTkIxDxGumwuHMuOxiCej0eZN6jZUWsXo9dqueGgjl/odcqMXq5WyXVZHyZUsU0/1vXUPUlsnMoIq53DqXa7jqjHCylFOxAVJHtTarJzvQwCD+cnzBlSlQRd1FOgWBIajwuLvWpx5rWCt497Bp+Rn4z31xMhaD/nSsbGJFIAcV1YtUAG0TtR6DXFVLYNh9ClN+qaACAxLV1yYd2t0XPw1/HflEXwImPeRrJJk6gCE1mib6lMrawqirF4tD0vVbGotRWb4oHIaDS7GTlVFPpDvKopi1TMqBsPF8go4nwMg69saPTm4U+/KSR6myU8RSAc+fyMdqTPy2A3Yv8RMp7UEEWFXEcC62PQnDdKTzhqXTFSjybG7EflrsFbeUmqeW0LShCDiZuvWPX07I/NfM7sKhoX36J4T25Q7fMi9VZ2j11Muf7SG6hqFcgvICtfOzXpU5w9WnIgSfaGLXoaC4NR4dExSSPoFW0UyluTcFPNsAN6Mp+dzwOuhAV1rS2XYLW7wjFutpW2pUaEBW3YVR0AC6zWsDuHCQIeAAPUQcaTLgVReTIiA4dAqo1J/fh1/xofGJlf8Jt8UK0/3Hxs3cfTAguSqIGRbxm+zzty1xeqIkDIXUbwxEdFb8+3YiftPn1zA2BTo+gl6NnGjE1WTMctmeb3AttiH8QvQA8taGESNJAJebioxm3zHpHywG8IGFDMI7r5BcHxDKGNAdGisYdXF2YqpZPATfUvUspU+em6wK1RM7eeo03LnTnI0wzpChk8eMDTAIqHy9nfjgSnxUTuK+8CN+6bYFkLxBf+u+q5BIVr5HYStlXuOAADvuuqcOFsH5myf3C8Ma0l4j3dkP2MuiSGExjOFrwB5Fzo8mlJ9zr/bjVzcH6zXk4tJjzb2onaDZItWEi3NkcICF97EHpgFpiknSL/M+C+qq/i6lXs/KCEOuYhipw36uerG4VAidCZOlGtlr3rOHUhycHDBB6zd+5lu/f7QHKR6H0w1kGTiQ02E4y6o/bElg6pxwpZWD9daKHqBhEO4tOm0ca7R2XlbOqVviUN8tXCj09fQ7Ojrv42ClFjz0utxXmG59IvNUtViNTw879Czh8pKMAbQIzrzdHkjZd4U6jF7vVGHCFyqxWVrN1EzP9kDTM3J3QLsAHDIFs/h0IJsHDEsYvSWwgqonIuw+mF1Xnj0hqgKz55v7cH5FRVBjLTAzKJrh6uQTIf5A44k+KXgB/4hlDSd8o8tkTcOAKQgRWTvdf/uwnoH95FjBhqoJo2nZ7kxZqvuC5XKVFw7pndFClCbnyZ+XbLzitYwHYn/3CDXamfaCUXp4QvdISPRaWrnLd49IqGr5i3mYeW+mQ+YaUvQaNpF4eL2Fe4K9+QojrzjLvmDRmQFlSdNdrCUw8DsZg6DOGLbkW3CX/Ov9XMugrIZPoL2z07yffOgHwOV8+JPrHEBnqmRZb9dyb81/TU9C0ItWMuXbS/4e/AFZHK1LGMTRuJIWY68oxE0Xic4lYE8fsKV4zGKjJbjkt17hqBEBuLAkkKBtrYLrglWmThuJHqLjaQb7VfjedjNFb5Og/UkbSX1G+Oi5B9c0O34rD30p7eVXE6IFj97Kme5BduKCK0QAXtFOkSnjzhr3VMkjgdBW66Cw/SwljLhTYg5mC+e5Ws3vVwH7t1rfGnS/JL4bkgCVNcW0xsIniUxjjoD0Hj9LENMmeXBFMv6KIdD4eLVMCC4MvaEQZtwmt5V9V0/Qia/FVj90cjIYKegBP1fPBtksQzi4LC+/DpPd3cjkvdPFXZ4Votk8Begx//f1L1RVQX/FGvfJTb+LoXFrM031Mab/hNwP7Hr3gRpXZNam7XQ73dOsv5lx9ziAAr/SNufkcgy80b/rTmpBJX4VNZAESGe5IoaduSZ9KXkt7VkhrlD3mRQigOz6g8XiPjFspwB2P6AooBxZMp/5Wbi7JNbXBj+l6ZXQx6ZhPbVCdlgeNkl6+kSrz6KZTZqVnamCteJP4YJwZxpQOALFvUIK/CUA1Hx/oD7rHIuyX7kTEpltdksUhmYRNQ0dy4b64imhC1fL/3HSPpnVH1FopJ7GRVNNeNMnb3h7YJCZtSqXABADSnxYUWaMtQjFPBDw6ks411fAkQnGFeitFLFhmO/+2uwXb2DfXCkciCLK+ZEUadhBKbV4Ptu/pC+rg9hKRNKaF/BlHXR43cj8iYd+HhRz5mMe5vBXt/LOUr9xOREcNFbKb42IzHot4EHq3/VKQs2uKW44+nlJDys0k3foSTso7UA3ZTHieFKqNwgoj/rkZQZsMZF3byF9Wl2CeP5zS50x1oUHRUyqflAOxzyT/R54n1B1ATcE9yxFy1ZAIggnvBjLK4+8QcsZUNpF4DGKaQbpUqY/Ex3URhmyANQysVrtehmCJ1jr0ZkJV7eJ56SaxmYe1VPw2dbX/4LjjBThtDMOQGqPc5ct3MH/t2no+t+IfJXOcbdV8nzrCYh1jo/HADSmJCo/rH3dwTk2HvpB9KnFjfFLdwhfpvOVjqxyj26vYul0ucS9U9Or8LbUSIfi9SoPIV97G2Z4jwCctZmMHDLz7/9N90GSjmYkiiFYxJ9NV7Zy0IK200RT5+ywmnTHqQBqbB0NZcYLHvq6fBVOHuHAaLgYjzPkXyajReJsn/pJcZmXrR+1JypmvEevbPJtYDPoIomhbJOI4q+DDwieXpx35pyT+8BygkCwvZlOUnMNI7DgamGj6zpkqLBICa2ItJMjRnaHCnbXR4bvJ5gyVhOT+9PFNecjAClYyERpDh1ydFMlHNjmhBEqHyz3Uzb/YlrG5OEzLjz1qf95/iMAzfbiOhjzorbtmCmEgl7IopWvqHmEj4b9Z/47jrHn3PeaZPbHihsq4pGnyr51NCPl16OTSTBA4a5u+g3m8rnL+CMLQ72cSTNLLEK3V57KIeaIMpzJg0ZYb0rSzNndGPAKdS5i3vC9hseaynsftvaUz8AyViUtWKVgU8r34cD5/yYynumy5OisB0M7McthNIQfG3wTI9+OgM1wFB3B7+M8Xi7ttziUfGKjqIwo8PF/F5FcR5jCDxZsQYQ+1hMqVeNR2nCUnAupjMvxF7KJwwC4NNbYgphjDBz6o3FITLwp2PTqoQqOJ+lJpUl5vb5EXFNfsAUHYrp7dAwjVYn8y3otaMj2ont854tXK84GWtCe6BkWQ7kmp0K3PCvcMa3ghFmLoLr5Xk3RcxNtPXt/L6rFja3olrH6S8mNbtprc2WK/sBfI0XDKp0U/pqKd9E1Rt0X++DEvSBNucpn98/phsFEo6X2N+L0e+QyncDbA2RaDk7yUiC3Fm5xwJSNSS5L4zjth7CymUHVapZAvRgt447kta/KOc5hfEooL/axRzfcBmf/6APQ8Ot72+DHFyZtpIS7bajEnC3LJRguMoJnS32X1tuVOgeSOys6bvamq8C8EiACnoBDlT+rmIeIAPLRhFtEYYQDy/5ze+BfhH3evAAzkemPMEYD5hgV3Dy/HOrXDEV7mHt+2bJ2yjwrY0EfbEZmUY9T+bFLKMLm0DPRxl2aWsKLF1vUOHsqGctPp7hQqaN9zbKDIjpPf8ZgwAFSN7L3Nam6rCCc38VPogDeLlRB6YNB/anWhvUkMc/aB9nEwf7bc4j2FxCpqjqUP7BIhG/rsk3FBOBTe/uKk/TC6XOoekUmztfLpM64J9q8r71VVmuJ4IA0O/f4lw8tSyKJiwBbgeh87a+9Uo0FO3vJVVzH8bMlxQKcNAG960fXB40WMrEB/YOA6bbhUEjZVc/bqHCYzf4izoPMqJZssT5Zsy6r2aQIAOFRyXtASG3nhD6urIobLUhOER5VveS79qQI69zUMsZfLF2Gv32Me/NZv8pEoHIqQAlf6NAyCvV2WtlIcYde/JNzLgE4NQwJmEgX3b8zuK+WQHxCMIHqSvDPTxdIGrNn/f7G5wBbU6W06yHNAEIehFhUJHE2OxxcEppE3NBJgML+X/DE8PqOY6eQiWBbS4yGvaeF7gvhuidt4djaeV+8STxL+/B9pfv0Sf/BkooAM+igWPvmDGQZYahM0hyyO2Urks5rHC8HeOnAa/qaYnELVY92qWmOvAZNmlYFIbAM1zj2yZVDkcBeNy72c2IAlRGJMmBxuUJTGrnOY4UiRBsiYVM7n8Nm8FUwIxAv/PjICGS/kJ/21CaW4jH3nkycD7MkbgY/b+cu9DxUmE6dp/6JclZ9ziu4e6LgjfAF+qkmRJ8dIkvrUBZpKhHQt48pj/eK8cgBbNObIuKonClHVtcQXTTl9/RFuXS33nGrP+EN8M0qfgYm4ObCKpA47TA9I4xCCBKbyUMbc80JDLuJ46wXEncUVwxoJlRxHzPRybBAp58RT2t6AFFTbQsWguFDE4h6Jz5ZXQxJ0KAU2smWCpVxNuj1cruqPU2Bg9FLK+PUprcAwKKrrZ99N/5LEJc4iM6lLic7s0KJtYnHevJKqRrK6SsNXDTdWKzsOjjWoTMTFcO6CaJHbWgzgbrjEMX5yznG+c6epxIEe4PXDoKc96hlw9Quk8pO+s7bwCuiJOdHULK0a95hnwik9FpOJ20lLPOBcjtUEs6QBbbbJNiggg+N65uP9nIKz2jZvNNkfihuBrq9lmldDM/zrDhS++rGI83Hk8uNd62E42y2oRzvXksYU9zZLfj6r+WgGKXNE2zdSrppM7seqEvCzFrYTh9VF4d0mcP8fUP2NjQaSouzYvcgu4njLNgf9NI5YGAv15n169Qn2j5jDN1GCsUItCW66Z246qOyZadtFWv5OJhI/yfU/6nQ+rBG/YJj2MbXxZtYqUiC8HFdzuIsMjFhL0uNHuVS1JYVkcPkZ3UKMB4uE440tF/TTV5cPdjGDdBXgfzoo7um+hU/EN3Yh0VDUhMDVhmwXuTw5QjKMu8QBRntbY33QsFtqNUWM5JYsTmZOQoonm2gh7gkEc8mPo24DLoRKp1RyBA40d4PncSA7e/usX3LRUE3WvNCF8A03je7PKEHpIPuwBaDt81NsvnX3uoZr9MQBtDBw4/D0x6OwH/lXtnHb/u4HAL1AEuXHqCpguTWXOZdoPxuiZLjRHEHJMBfCBccQfitxQLridrHMr+cKskYzlvbFDToFjQVUGgMZdE9Yl69f3NHBunV4Xzrzn9z5uFa8iqOt5oyqpZSTC6iim7QkmDis56gksc7ebRN5ASr2mNg2/f0zblkJvohbM8Us2m4lJC9MAnryMxanR1xJg6CDD7Jmbd8pqBHc9mQ9DyC3XU2ZvN25+tqdPVy96cmZ9Ii3p7P6Hp7c1gDenDze1QSTI1JuDsZfI2r0OC7Ot+LEnd0GmmRRXhfXjgkdmWqGhwxfBp2FVJ1vfXyxA3+MdXsowpUKOHga7+I7YKtHzQvR8bo2CfbXiriqqxDmoRR8+gVlqVl3JBUIZX2gtSEoUyz6KjlyaUmzv6UOOz27cFc7tiY6zJ7i8hNdbAZRAt3/ge0i+1f0vNXXfW3Ur0xPlurhBWw58jf+S/lCVhqTbdzk1gTCSX0YNGYbXSAOe2rWbbQ9jxXxt/fSHVHYfa94P83yF8+fCd7KdDe94VeeQqg1fwTcDu+6A6udWrrgey1SnwqushIZfXFR66RSmNEijZp4lFIzkvdRpBzRFlLhTXpLURjCE3ndnNbdYhMqupllFjareSAW4hAwgYVbM6PGXweMh9UAuQP7B6z0Hl/hWDExb8ks71aWxRd94F9NOxP7pfDPjBWyTgBREgdJZ7PBFzl5SoXbOYZ1pBuJYEavd+kpYi7vw9scf2CIJQdgWXS+Itq9agLJhwBcoGQ8bpdzK2Ckc4gT6ZCASu5Zvxx9Yp4KyNzY5+aUmMbm8tHysTCsB7958FO/gZNrbpHSfigRNfN8P3aP8JZAOD0M2nk8rIeibC4kVtwRitMAxr/hk2jtSN9MUKEJDcRcljU8nfo9gV8XEg+OgyxF6Q/XTkRn7e8z7Qxj56oH2KKPi7GVik9KCC1O+9KDVA4JvJG8GeI4KqVGekL5as1zHTpH/o9/SgNUhMGch4eujMWI3gnEUnzB0raIJGUBvnL1ChH0SceVN5CV+Tt125pdbWdH8rptKlphXXthSxPt1vUcjydQfIMsWVLvtYaJZagmwpVqdycq2MpiF6aQIkGWeOoJ8gudHiTB2YbzGu3yZEXUV7JTfAW2rTR58ghg6ywruB8O9bjBKAOmHi5vf5sT5sv5pN88NLACYbkyp24DbRT7krw/AP74mhgxgrXIQ4j5Sa8fp4D5IFJcx1EUe/xYTGLtKaJFn3U82MkHGIVvuA7NHa5zockqNiWxf9YTSOKhhvdKa4VmpOS2tEmwfEJk5wqQAO8TKBnzdyR67+8jWmSH6V2WfVXw8PTibu37enPwrYsYwz/SKKOeKaJVWvIznmdBSFVJTyqT7pjDN5a+6LYneVjz3cv+BonlUuktHbcpDcKVXak4eU5nKmJ5CM/hCenf57bMtWLbKl5uGUBwDLqmkmxaNzSC/xA555cqfEbbYe2k89f/mLV8dMYO3GTlxKQohrIzo6kn3yP7wCbxGsomyteBSaXVIGTUECwe7Wq0Ls7BQA9wgX8H8Aj94DtR1rERRyOgLQUXkhiFkIM4bc5jg8ksdHKejnOlgbu7rwzg+A1hXymstoDKPvMcLjm1VHb2jnGp3pFWeNsA/FxwktZwG4cXSKVexIGca8CIcaRU6WCFazbkZ/wKLl03ou2TNfuphrruuoknRTLeH8oTgTgz0pSxm3yyhGTfqcgWO33rOWNsOthuJ3y5mTYdRTM05FqFZJr7ipZq/4FPBTbJeFMWIJjKugm9WWjg6JzBJpeUEh3MA9MMGBDDIakXAQfbgv51a3FrSwAxIu5qeftNgTQYjnKJtXsPt3vDBzgPsPRlNili3FUDNCJC/mnJufpPrgpc2fsqPiJ/2D4taVBrqElCmQjDt7VxRAKwvxZjl0NuIl54UODInCvk2kZvhS7yr5IUq53rYXULB9dA6MZEE/yCByIOpEzZgusV0ehLyllBrk/9XtuFuBKcJSn+sfjVLECd4e39YNlp5KHAfUPaEKMasPs5dH+npmHmW/2YGYQETeLE5cka8Yi+/Xahw+vRzUYSenHnJtuUA8a5swzEKW5EVw2ypk1Y91/NtlyO/Fr8jE8gz/yqdQzggrd4/s5KoP9S1sFgwhE+VfT+GcLUbjB8CLVCJymitZXJgGpOcfeFVxQd1cLG0DyWtcpDu8PsrzHKclY02mEATyFrefUSLHwJwoQ8Vr2gcXA33YmKOkdAC+vhilRW1Red6HBs79YQSfQuOgapQq3ZR84v1Um8beZ6G6Wkk6tPv2EqwyF6fDbefRTW/ntY2mUQaxmPUvTGNZQWTYt/AR2tucFDw7y8vN7oVONUKYeoVPcvl55Lc+Rd613Gdc2GyJUpwdKLjlkET7MmC4sX7qpU83WTrhnllwuvd83nYme5PLwqmLXdgxULkq2hWBTtdK5KHaNkSOkGBaN9rv8GOHsQWfkTcbuNfwg6T2MEdP77i7VVRrTiLr+etu1NEOuNh4x92n1aYwsLOhHaawcE9QwdDDZVF14rTBruI5jlWwSQg8oP0rlBiRTzbmpL1/QDPmOnXCDp6kxlLcAUGx1pk1qwBu60ePwvxSqhBvsDvplbZED3AHicX6CaHvAk0gicMn81CAIo8cSdBX906qM+ro7rKy4JIYmBgx2UMzqx+uO4gw8Ci1NBpVkSvHRCzh0rQy9431RN8rm4xNRsLwyJsh7AgW78divbbhjTZNyzBGJsMbED7mq/8JLx1ygcmi9AJjYKoGo1dFn6K8UF2fibLeZ+pStJFOSTtj3WfUAdpjOQ1pNVZ5XNxr+G+pW8YOohY4WXZbHFVgmPJeJKhPVFTpq7czskpx6TrK2iLeeX+TQlqwlPZDIt6nv3sQomHJqZ6ayDmETEepv6bBEvq4CnhTYj9YIJO22A1///QmUCMLwsa5fwXKtAFqjBUx7xF48R3niNkFFKIDK9g8UeepNUziMqE/iZL8NZSeMUHdHB5T65o9zWZmZv23MS5TR0D9LUdqT0hQYU0n0W3AL4fgKy2eS4AApVzE/B1Xva/g+cASlVvpJbek2/ATNvyo/pVnwurqidIcvvl1/+jp6x5caZ2G0g2a6yy4zAX2lHFox8P0GRTPFqi5uTfOlzUHgQ8HQuW8bUVKaojUv3TfJEAaGHNBimAsi6ZPToaLAPCgs5CJiYEPMdVuJCQj5cpo0WiLgxJ6jyRBqa3RRn6F56bGpatNt6lY25G9PnNrLJvpvm5fdZPTO4P+C/qxfr5OJMQgUWre7RaTIXSrmxUpND+LMgG0MdbTD3PP+JaTbJpnbSop3lLWQKX03LlhnihQHN/aDV/FyfAzocV8IiKoLQ9qUwOZDhqRrSrUGG9rWtJn4Iq5OocKzMkiKiMW4ULKhtF7h10thDuxoxa+j/eLmWPNxVXW59mSn2LE9PsTtNJ4BGU+Bd4f1onHrl3l+RPkV0r3B+Sl7AFcFopY3lOcXBSo+9bCRRLpCMzz7oEM4qk9jOoLkwFNLtcJadgugvOuA5oc0w6ccB9KYtaiuFy9aSeWkvvC53EqUfBTSSdicVbn4fFzdN+MsZL+VuRW+FCg8Tv56b5CtNpbOqqRJZ7vy8MATlfFlRbB2Ob66CP4Okx+I3+Cp2TcQ7vWi5Qfsy6dP+/EVSlWg5blfR7qdhb6WbuhwSn5bUXGp+IlaPQfl/k/Dhix5f4U711APLhYhIchQ6GY+yjyUDnXwrWvNv2KUjlt9bFCtdUoeAzG5N3oCF/IBgINbXuubWJpZxJX0MZnSQ+p0LuW+3rjEx6DxHAPjGMFRV08hRYSfo8x3fmvO95JuOZ9ahTz/btV82nvPIfG2mzhg4uqhSkSwMVLkAu+/LYvNv+xZjln39wYRaCfFjc5Ebj5ST8yDhWJprmVrsqVu+ZSpiTM/o5J9jScYi7+PYGgjf9hQg7qdeL1rWjwRkVwM2E+1XrU0TGdCyTmP5lx8sylD4+yFbOgNaE/Wlo8f6fe+EpnLlb8VZ9SJt1HPZTV8hPQKs7kkX2hxmmhlAh4Eo9QMzPSy3eJ1cP9W2frrHB0McjM+1DiO1/hCjsMBF2aSEhWvLxgvKwRFnn2Q3o6rF0lOWo3zriVwG0eXdUX9vAsTf\"\n },\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"type\": \"Torrent\"\n}\n</code></pre>"},{"location":"fep/d8c8/#v2","title":"v2","text":"Expand/collapse v2 example <pre><code>{\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"file tree\": {\n \"tentacoli-01-Small Town Pleasures.mp3\": {\n \"\": {\n \"length\": 3177585,\n \"pieces root\": \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\"\n }\n },\n \"tentacoli-02-She'll Never Come Back.mp3\": {\n \"\": {\n \"length\": 2778997,\n \"pieces root\": \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\"\n }\n },\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\": {\n \"\": {\n \"length\": 2333412,\n \"pieces root\": \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\"\n }\n },\n \"tentacoli-04-Summer And Winter.mp3\": {\n \"\": {\n \"length\": 2562622,\n \"pieces root\": \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\"\n }\n },\n \"tentacoli-05-San Diego, Yellow Cab.mp3\": {\n \"\": {\n \"length\": 2796486,\n \"pieces root\": \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\"\n }\n },\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\": {\n \"\": {\n \"length\": 3863849,\n \"pieces root\": \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\"\n }\n },\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\": {\n \"\": {\n \"length\": 3696387,\n \"pieces root\": \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\"\n }\n },\n \"tentacoli-08-Sorry, I Have To Go.mp3\": {\n \"\": {\n \"length\": 2583746,\n \"pieces root\": \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\"\n }\n },\n \"tentacoli-09-Scotch For Two.mp3\": {\n \"\": {\n \"length\": 1924024,\n \"pieces root\": \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\"\n }\n },\n \"tentacoli-10-The Killer Whales' Games.mp3\": {\n \"\": {\n \"length\": 2463949,\n \"pieces root\": \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\"\n }\n },\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\": {\n \"\": {\n \"length\": 1783901,\n \"pieces root\": \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\"\n }\n },\n \"tentacoli-12-Two Old Kids.mp3\": {\n \"\": {\n \"length\": 2965602,\n \"pieces root\": \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\"\n }\n },\n \"tentacoli-13-Tentacles.mp3\": {\n \"\": {\n \"length\": 4319196,\n \"pieces root\": \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\"\n }\n },\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\": {\n \"\": {\n \"length\": 1555839,\n \"pieces root\": \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\"\n }\n },\n \"tentacoli-15-Tentacles (Versione 2).mp3\": {\n \"\": {\n \"length\": 5261174,\n \"pieces root\": \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\"\n }\n },\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\": {\n \"\": {\n \"length\": 2877921,\n \"pieces root\": \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\"\n }\n },\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\": {\n \"\": {\n \"length\": 1079679,\n \"pieces root\": \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\"\n }\n },\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\": {\n \"\": {\n \"length\": 874993,\n \"pieces root\": \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\"\n }\n },\n \"tentacoli-19-Tentacles (Versione 3).mp3\": {\n \"\": {\n \"length\": 3951350,\n \"pieces root\": \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\"\n }\n },\n \"tentacoli-20-Sails.mp3\": {\n \"\": {\n \"length\": 4358356,\n \"pieces root\": \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\"\n }\n },\n \"tentacoli-21-Sails (Versione 2).mp3\": {\n \"\": {\n \"length\": 2870766,\n \"pieces root\": \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\"\n }\n }\n },\n \"meta version\": 2,\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 524288,\n \"similar\": []\n },\n \"piece layers\": {\n \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\": \"VPZdDK988MyI5L2JUEvc3RNizYDkWZBhaxZGOqqjJO6N5c5QVL/+NUNQvc6AyZtNFfD2A/APiNK/AVNMjhT2WjgTQeqMBtmnoLKBs3+F+e94T7NQk5uygqd89lOk+eFSW3zIxeoCgjnxq3QlX3i8ABOK3RDVvzFPnegQvpgYoBH+3TlsWxinQ+JtwGFyTMAFwe52fIRscF5UgcqdlgRyrJu6EzKob+25Ngq/Slv0Y/Ij1mSHLRcx6BRy960JjT8J\",\n \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\": \"nIooZfqJg9f3Wa+Kko4aSSlqdLkymMqh+qvpz640fLgJHa+hFhvvQkGCETr/xzUT2YIBwtWr+/8jGUCpE7dWHoSL8ihQXdoNoOXHYcwy/fPygSMJl+r5IgatqWJOAKSX8zyncD6096iCsazYOgp1ya2CkdxNJPpCAyXVXCl7AoPV/ULP7gKn0G3WcqAE+eXPEmLkdzEBI7KWNXsFfhyl5uPDY0RgzfirDwOeC7e34jPxBqxHcfsLQJ1ORDpnhOO1F/Hh3L4hFfiiFifXwqu/tjs7yOUkmKwhpzvr6O5wvA26p/Igw5ztlqj6wlVGVN/ay5anFZB6wmLCmGNt3M7+W5afOyAk1tvRUpNoOMTAabxT0YMPUDiuP6bJHZPsOnG4\",\n \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\": \"hmopzDsPaqQY8kB0fSAE9korQ/EYlhMNNKYh+Stip0nQeIednxVIigWz7SdHjEAxtrISO8Q7oPicQBePe6P5yH70mQLp20KAj82QuAtpEUXP582Ix5A/QTAlv/ZOCCV9bUeY63aSacDLrVzjyx0cu3O0I18d5lMa7GL8TszAeziVxSJDStgC/mSgsEw2lyjyzchw2XHI39o7x+1GlXHhZA==\",\n \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\": \"V8LPZiW6dYEhkVpy50NA8NQobcjCG7BiwqPR0TzNXe+rfk+pF/p7QCzF+tpKReBi5EPurqWGjO4+dbJ73YOO53yCOoLILCo9iji8YyvkB6RQ9nM4Xn71hPiU1+oJwAQb3cgM6dxz5+ida8bDGLpB7CroBXzQniNAbNUungzhJy2T6O5BwEHBoLJmpTrkcLurMqLmHRmGJN2SMqfcMDhg3g==\",\n \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\": \"gAhlHM85lyfo/VNx1txeTrZogY4hwv1y2GQ9PrXIA6zqco3cpn6IIttXkb6GgIkz4PiVmEmC4vlKt/rV+mCr6/hj9o7tmCjolGiDw471NFblaxYcYKu+XfM/coV8GJGtAV+bWjotIAccxZUq+JpcMfVD33C9H0PKjX14NLJwAG02ZRMcu5gU1FEjypDUXi/uGmeb9LquiwDv7c9ZJ+tTqH7/K+w8w45GI3gt8B+ZRa4pPUlDAsS9o3efdLbpwNrwvbIgc/PNpmSwoSkDHtik6bclpq+IKm0oB0eK12ic6/NdOTH7eMZ0+FZ5rtn9ThJ028cHkSAeqHs8v1e+UOjx4w==\",\n \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\": \"AFW7D3V3l7kOP+FoujXugKWb4HH2pXMOc53bQaUj5m3r7xXsXQSTox1chHUlK8ps+O1qxckSydl7e9pPXWN+j84nVqScDN6uF+LJp58SxqOmi/g7nBBJugwxPaf5HrEOAXynbi+CnHFBQPYnZ7/AlSFhpII4j9/Nnv9WT079YgFCc162lViJR860+Rbf3OaATtVAI5oGFJUAVCV636zAsTXoTMj0lnJne+Hb/KgxlROiWKLBDAL1jQ/0Cz3n3D/Vyw4eAQ0AaP0REXemD/wBFDyDE+5ixFS9+cI1uam4bTqGN4UPily/WRC49VpyAwyT6rpMFjTXc7xGTkMEZpNIgbENH8rZxBWFDoNSzJVRL/XjdOZaDFKe5ALRxeQAzbYh7tKeqdi4OXE0p4jzBd8pC+hNM3yDu8LgHDE1qjOM4egkt9gBtsmnSi2Urif1/Q8maaiy3sEHoph4VdA9D8N8nw==\",\n \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\": \"aZRzmN/dmNjg+FsfKWMZl0l2siifCKnrMYFM2PA5lOOB8DclDu0sXUM55se9HRMVjOcnfNXxY3+mRIrtTdaT8sDbpzHXp9iGqsYZBGEAj07ZaeJM3jHvOky1o4x2Qro5JHtefe2GFDgq2lJS0ha+JUsWG9a6MdH3c3faaj/X4IVoJCDtF/V8WpvVY8eMmVoEA5L9Zfpc9eiTlCJNP7HwIw==\",\n \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\": \"sysPGOfDZ8wAr39CdfFK3MzWxiPexd7SkLqWvIySoYD/Gr+n3xJTMyxIZR5LwAjaQwAlCqRpgDx14e0kyWWKQBUPlj3EgcMR2/1vCW6mBHFTqfO2Y/IECE/iSUaFJ3i+zWEQcAklqVfqBloKbLGtfHMAVIRgwY8yoAKB23+gmFk=\",\n \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\": \"QiFoWXevBNl/jPCnBtGEtE4VfW9k31yMrX8MrTtJs+2DpNXJXQIWYPGaXA5S1iOnhQ1wBXlKt+m7M6TChTE/5/q2qPpe4hp5iVl28bkvb2pYgVZSgYiqGqistUewLisrhTygHutf8Rm2ChDp4cfJAJ0hRc2hHQpMo9syRwpVUYI2HpVjsS6t6Ymokojaxfdxk+88mJ1bJZcNNoFmK5EaqsQVRvjTenxaklYKPZROeDeiUb64FGCTvv8hF0T1/cNFCOV3kdUjgM4mrnw+gDfJIm5JdID5bsC4DVF7WrOWaAH251lWksPzDyIFcSxV0C7l+FihtXM4U/BhsxlnnfWEVfWZBP7tFb4vMU3WsMRCCbbXCsKpuNa/NYCwro+h1MO/\",\n \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\": \"VwRUXEzJHC3pz7uEnEowpwFHA/6Q4WYdUmMEoA1Fvog09IALuR6E3bAuo6FP9pTrn2bvxPvJSKHVhGyWwbGaJn5arMAK4ChGGuZyoAQwOOdQPwx/A90v92qo5SuqhxFF\",\n \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\": \"SCiEVcZOjE1GM0aMNWiBTiS7LMV2iIAVX4puj77Dhz+dsqG/hthLs+d6GD3oB29I0wCBYIPUCSDJmcj0ppAvABDDhc0H1bIpQzhqHQmwtK80T122VAQ2MPKCU9vg+k8wkJSzgWKib8AQJEpdpBpEiyO9DPCzSfzLtqPucgTUNvlatKYAh5c8+o0Nf1ewEkZumL58vocsENGianJDe+kSzUJMwJKxT6BvFlE9ujsZ7fSWDV95sVxqOQMFCVwxuNOD\",\n \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\": \"vj2xl+bqofTamS/TSAcwBbsf/E5jBX9C6lgZdswP+/7WNv4Vp15ny5mxi14YqCFG+M1+5NOfUzJw6RiwMLWY9R82q7yrrW4ccnBJMcvhP7f39D6BKMx4mcd/ATr0aEV5KbzkuW6io+4uahSt53fk53UMkGAwS1kGivE17ueD1ng=\",\n \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\": \"TpFZZj49ysDTd5yoplRz0DfoipqsWnG4ZNeNbZA+8M9rTS1ItmqZnol7sDqbMXZ+qrsXiYEr2pWCeaks/cqPI+1ae1ecX8rtsWyA3ej5aGRNt9zUmpYhDazGrqfok2v2\",\n \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\": \"jnrUt1kqrGZ1gPKtCYyDocTIoOxB0RtsWA3xEocJiyxaUaWv8jWumQAjW6ZMboJ10JODGebBV7HQssjlElEVdmR6HnePf6Ov+Fu/ZfplQHILfLi63PQYO3Dc8ohdwEz3o+FgJ+JOhQwbynrSItQOT+azQLJ8o41yWOGY72SUdhtNEjhytqWwGQIHFKMJfQeVlvLpVCryU6p84sgxIiYRJ8cUZbHPhz4Y0m3xPbR1tJMaIlqcfjOw0I9sHYCA4suZ\",\n \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\": \"GQLFRpTn3AVBJS/ojGtr9lhfSI/8ewYR0xLlkqg3Cnqx2AVlwr9RwBjKMajHSgcmVvpQpoZ0AejThvZz2JygF5kkjVy1Pe7zJ65GfmIMU0CCKkiAyCWuQt1PdDTd2xWNReKv7MH7rVRse7H6YrIAZSdEnziGr6eUVt9+fxULQm62g4rihawxEW6dZvMh8QL9D+PUFVjrgRBUKvYWqRXgRY0vObRxqCQj1AJDSwvUdMv33b7o95rNOsVEdBu+n+Lt3JA7CxwAXsQrgZt9AmGXQTvyu+uONt+2ihaqtspuGAkBf6/D8RN/sybcWtWCI0lgSyqu2g8cYBEMaxnjgKGSaw==\",\n \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\": \"snFE/oMWtUu01QzaJnhsMG+D5D97pbKW287lAAMvMSwp0FK1aOBcV19dvQte9qvXmws494090AGQ3xxAOntIFQjzJlYhbp8JQC+2r32oFf7lyaowwreV535RdH6/TNcd39O9UI0dAndQ9uDWP9mRpBFfLksTvYFktKnlwqqCiYRAGbdsO5FC1qlJxi/X1CZHG9gjk1FxdhH0WLUehsdQwwxuoXxdQ12HLuowXNBr1JeAAWNcLVAqSezuUkulc/Or\",\n \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\": \"GaAYwCiYeDqYOop50ZEV4TcybE87ULDDE3Nv1ZG4geIF6cRyFsjN+IAOqiXe6wjRkdReNo3NLKskpp61Ca2rBw==\",\n \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\": \"fi5NJSOcrOVs57ny3dnPOhs/bIbTzJOYgLEGbq+cPwOkBPtHyY+BbAoVmFzgGyjVo+htFC81LKvLtgOMRk/TcrS8wdrRgB4NvawjS5Mk2+0LNGitxFrWZym3KhKNa4oTM6d+UE0wrGWAsOcm/fTxxGRETf4OR+gHpjz/4ED95uAboFk9h317NMudz9etX0IjX25HrBDVRH/cBJ4Kw2G0iwIVXFWQOfyBGIvQ3OUWLVd6pq7cKnYEv9V98EfpVuJquGsoDb08lJZdK72qzsxlIAXMSqhYjvEsXZD5w8wiqUeMs5XuN/DnzgDl8OdPGAUnaf8f5T0NEPvQjNMN4z+aiw==\",\n \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\": \"nZ0lMxZNxcUhIGdtCyi9FXVtv9WF43rLL6S5nCcQD5ufOCkq1YNu0PqtK7c6g89TZlysL0dZ/xqWIeIvsn927cVF+ugd6jvoFFcpmr33scLhCbT2PqN6A1oSYA75CgBrzlJpDzZiV5uwZzBWnlu2/menX26DPBbV67Qz2Fbn71+XwW1aBn9DR1i6MilHdyVMLpZn0C1gpgE1TG41//OZWg2N2QdACzAYK3UMoauKVDnDr4x3+cgPj8cuvjgMV3xk\",\n \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\": \"+mHCqf0cQomJkl6nWynCwp6sCWdbMlZvd78BdV3SkFJcwwolpXPAf2kZZYeUXs8X6HL3xOzFvgJLiCyS2gQ+TrG4iZThREyKwgw5eZVWv6AvxzFU5UntjCP/8R4uVWjaRNf4fupVWANkqYFZmcL0mxGk+b2y0ZH8e6kECzHCT6ZQDXvQMtUtFcg6UfuWNNmf3ha+A/M65Hs/m0q4ccp4Ww==\",\n \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\": \"+aMdZEEaKoLp6RGQeKcp6QbjVdiqEsRghVOLxndG1+abKpoajctleyvXwAk5IszYUYyQoLt6Aj62v4256vygQ/mITuOkN3OQ6l3EaYlUwi79fu5jZvhpzgTLIXWglogaCZEft8aQ12G8m8DO6+GhLI/KqLLGGOayeB4vIq9aMjcnWIEYq5o/vrtTpVGnJFKNgAP9FbiLQka3RvJiEHWw2bMgpKspS8ai+fLNR5NqHEfzQRlxYVcK6ieySks/AcQ4m9X0olJYa0Hh6Cj4Md7pHaYvkMioFshhs+z4HFwnP0Y=\"\n },\n \"url-list\": [],\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"type\": \"Torrent\"\n}\n</code></pre>"},{"location":"fep/d8c8/#hybrid","title":"Hybrid","text":"Expand/collapse Hybrid example <pre><code>{\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"creation date\": 1762216376,\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"file tree\": {\n \"tentacoli-01-Small Town Pleasures.mp3\": {\n \"\": {\n \"length\": 3177585,\n \"pieces root\": \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\"\n }\n },\n \"tentacoli-02-She'll Never Come Back.mp3\": {\n \"\": {\n \"length\": 2778997,\n \"pieces root\": \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\"\n }\n },\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\": {\n \"\": {\n \"length\": 2333412,\n \"pieces root\": \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\"\n }\n },\n \"tentacoli-04-Summer And Winter.mp3\": {\n \"\": {\n \"length\": 2562622,\n \"pieces root\": \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\"\n }\n },\n \"tentacoli-05-San Diego, Yellow Cab.mp3\": {\n \"\": {\n \"length\": 2796486,\n \"pieces root\": \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\"\n }\n },\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\": {\n \"\": {\n \"length\": 3863849,\n \"pieces root\": \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\"\n }\n },\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\": {\n \"\": {\n \"length\": 3696387,\n \"pieces root\": \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\"\n }\n },\n \"tentacoli-08-Sorry, I Have To Go.mp3\": {\n \"\": {\n \"length\": 2583746,\n \"pieces root\": \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\"\n }\n },\n \"tentacoli-09-Scotch For Two.mp3\": {\n \"\": {\n \"length\": 1924024,\n \"pieces root\": \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\"\n }\n },\n \"tentacoli-10-The Killer Whales' Games.mp3\": {\n \"\": {\n \"length\": 2463949,\n \"pieces root\": \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\"\n }\n },\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\": {\n \"\": {\n \"length\": 1783901,\n \"pieces root\": \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\"\n }\n },\n \"tentacoli-12-Two Old Kids.mp3\": {\n \"\": {\n \"length\": 2965602,\n \"pieces root\": \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\"\n }\n },\n \"tentacoli-13-Tentacles.mp3\": {\n \"\": {\n \"length\": 4319196,\n \"pieces root\": \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\"\n }\n },\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\": {\n \"\": {\n \"length\": 1555839,\n \"pieces root\": \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\"\n }\n },\n \"tentacoli-15-Tentacles (Versione 2).mp3\": {\n \"\": {\n \"length\": 5261174,\n \"pieces root\": \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\"\n }\n },\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\": {\n \"\": {\n \"length\": 2877921,\n \"pieces root\": \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\"\n }\n },\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\": {\n \"\": {\n \"length\": 1079679,\n \"pieces root\": \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\"\n }\n },\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\": {\n \"\": {\n \"length\": 874993,\n \"pieces root\": \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\"\n }\n },\n \"tentacoli-19-Tentacles (Versione 3).mp3\": {\n \"\": {\n \"length\": 3951350,\n \"pieces root\": \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\"\n }\n },\n \"tentacoli-20-Sails.mp3\": {\n \"\": {\n \"length\": 4358356,\n \"pieces root\": \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\"\n }\n },\n \"tentacoli-21-Sails (Versione 2).mp3\": {\n \"\": {\n \"length\": 2870766,\n \"pieces root\": \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\"\n }\n }\n },\n \"files\": [\n {\n \"length\": 3177585,\n \"path\": [\n \"tentacoli-01-Small Town Pleasures.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 230287,\n \"path\": [\n \".pad\",\n \"230287\"\n ]\n },\n {\n \"length\": 2778997,\n \"path\": [\n \"tentacoli-02-She'll Never Come Back.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 104587,\n \"path\": [\n \".pad\",\n \"104587\"\n ]\n },\n {\n \"length\": 2333412,\n \"path\": [\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 25884,\n \"path\": [\n \".pad\",\n \"25884\"\n ]\n },\n {\n \"length\": 2562622,\n \"path\": [\n \"tentacoli-04-Summer And Winter.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 58818,\n \"path\": [\n \".pad\",\n \"58818\"\n ]\n },\n {\n \"length\": 2796486,\n \"path\": [\n \"tentacoli-05-San Diego, Yellow Cab.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 87098,\n \"path\": [\n \".pad\",\n \"87098\"\n ]\n },\n {\n \"length\": 3863849,\n \"path\": [\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 68311,\n \"path\": [\n \".pad\",\n \"68311\"\n ]\n },\n {\n \"length\": 3696387,\n \"path\": [\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 235773,\n \"path\": [\n \".pad\",\n \"235773\"\n ]\n },\n {\n \"length\": 2583746,\n \"path\": [\n \"tentacoli-08-Sorry, I Have To Go.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 37694,\n \"path\": [\n \".pad\",\n \"37694\"\n ]\n },\n {\n \"length\": 1924024,\n \"path\": [\n \"tentacoli-09-Scotch For Two.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 173128,\n \"path\": [\n \".pad\",\n \"173128\"\n ]\n },\n {\n \"length\": 2463949,\n \"path\": [\n \"tentacoli-10-The Killer Whales' Games.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 157491,\n \"path\": [\n \".pad\",\n \"157491\"\n ]\n },\n {\n \"length\": 1783901,\n \"path\": [\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 51107,\n \"path\": [\n \".pad\",\n \"51107\"\n ]\n },\n {\n \"length\": 2965602,\n \"path\": [\n \"tentacoli-12-Two Old Kids.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 180126,\n \"path\": [\n \".pad\",\n \"180126\"\n ]\n },\n {\n \"length\": 4319196,\n \"path\": [\n \"tentacoli-13-Tentacles.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 137252,\n \"path\": [\n \".pad\",\n \"137252\"\n ]\n },\n {\n \"length\": 1555839,\n \"path\": [\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 17025,\n \"path\": [\n \".pad\",\n \"17025\"\n ]\n },\n {\n \"length\": 5261174,\n \"path\": [\n \"tentacoli-15-Tentacles (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 243850,\n \"path\": [\n \".pad\",\n \"243850\"\n ]\n },\n {\n \"length\": 2877921,\n \"path\": [\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 5663,\n \"path\": [\n \".pad\",\n \"5663\"\n ]\n },\n {\n \"length\": 1079679,\n \"path\": [\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 231041,\n \"path\": [\n \".pad\",\n \"231041\"\n ]\n },\n {\n \"length\": 874993,\n \"path\": [\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 173583,\n \"path\": [\n \".pad\",\n \"173583\"\n ]\n },\n {\n \"length\": 3951350,\n \"path\": [\n \"tentacoli-19-Tentacles (Versione 3).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 242954,\n \"path\": [\n \".pad\",\n \"242954\"\n ]\n },\n {\n \"length\": 4358356,\n \"path\": [\n \"tentacoli-20-Sails.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 98092,\n \"path\": [\n \".pad\",\n \"98092\"\n ]\n },\n {\n \"length\": 2870766,\n \"path\": [\n \"tentacoli-21-Sails (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 12818,\n \"path\": [\n \".pad\",\n \"12818\"\n ]\n }\n ],\n \"meta version\": 2,\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 262144,\n \"pieces\": \"DplxVW7qZwpqi2b4dkeZeSfQxe00lI+NIAPgqutlOvUJEOiin3DotZcSM6OWGcgmsU7Zi4oQfSLhO4qXdxP3td3x5UL8eHeIpnaJE3Cme9gqaBRdEIfFCVgZanXKMRnzPQ/aoscXWPuiMX0t1Ytjqvag+4yiwxKeeK383m6l/sDQxKceDIf2I/+BiHa/q8LHVvYuAMSGLTXmHzU+bUC5V6cEFfcp/Sd53DeP5gwSPJvNyXX+parYLrqrh4BzHhHRwBluiHyecCUuLcEYmY17+5oYtzyF9og3UtK4MgXUt9MftFAColm6XgHiGTHgy5gn9p3MNL4k2fns1tUAIgRelAMnVF17WwtHj7DQqW3uq7g+9V41bsRYvyNaACsDqxHgB/SXtAqiKGPowh6UdwID9H/BmMrK3kPSuev/gniVE++W7XtZVWecdptUWZQlRMWjaQIXEEUTJlweZ1o4DBVMnyQZU2ZFkTySWCZkpjyUq7IBZ0tLnX4Q1bWW8JOH9wdw9I3TlBRIn05YTO7DGN2+d6xzjjbwBpl2TVkLo/EvyAckO52woS6CLeP7DycsqFmpJySSpbJ4m19RgeC7VZQFFed54Hv/z7mQURMyoPoHEM2anO7UZRun+aHKw2+1ugOKWX5JEDwwEwbFTDhiToHaKnlC0OUJhxTBB9n8BtICTtdDHHLOdg9Nh/E9qZBA7jZHAu9XULXkTyuVABWvA71ieEagDlzRnHjfFmRrnFg6MsQGKVc9QTmw1OZ6eWSqosJVzuIIPsBgwgkPrPXkS0CT3l/DwtkWBRg8TsGz3qOKH5vBWlWiDuNkx5QpfNmpECJRm6XX0n83NXXhXFbAudU7iLvj83LeKRBFNoUJG1asXHGFmd65y6GiX2oAgbOk/3YMHfb60fVEWV3QGOIFwgfcfDFBP8luZiuw1ld95G2W18TrJOSWaaYZnt115wT5x1h3xIi6ide6PCn+AXNqVTRAR0TmQhflW8/YP7EN0RXXTVnBPIUtIWH9xNarDAUHoAKNVZOkAHJSwLv691FyFmD+gS8OBO0OaH7lgC7uFlpSC2P64YYbixnjq2QinqLaPKtkDv4cgCRJQ4ns98qEz9Bewg7f/KbDd8nXJ7Bws0ZSLtPFiwq1+jvlMsEIIOCGldL9TCX39TsRI3uPY9SjW55XA/FVVVkQpvZMn2so+kqhq49UTJCmmxNpCh6lUBSu11x94HyYzTVu9KTaO9s2RGC6RtNpO0hMSjmEOrRQyvA9IUFHAQqqnzjtTQ1BgHOakcUb1k2ziyadfV4DJr1uecQ8GXTTCcQPeQzkUc+HHeezsQs5rwDkQ94j6zqfjgI2HGTlGVdh2QOMJOaRgTzFVIlWZlO/jRo5ACovJunOzES+C2mqhE68DjW81Z91ywbDha/4yE94W1LOlrsQnz6dlE5Ed1g0vedkogdwdyWfRBgLOcAgoQj8iWM6YvDr4orlp/zPtYEt+QLNJY6bGLj1rPspHKs3pKOeyEi7vpNDpY7M+3Mgaqb+NqWj86n9CJGAnXkEOXuG58v5PgJsyBmeQcjLaeYMVhQ7HARFu4ZHy9hi9QnMMztWiiW0M4dSlTJsi5Kcrrfl/yDLIoJ8ZNwcuCJbnKEUT50UDYHcFLscR7ZjJmGBGblGGxs86Q4U4HQguOZ8eBllBCb1gjCDegfFQq+qaMQhHH0tsrbVzpJywCnProjvOJyAyISgMKuquMIUmLUIuHih58qhZqmgsXxdJE2t/eU/asAZmVTBF6fGZhQEAfGg3bdqKhakY0O4uZZz9b405sRaaUaCK88waYQrS038AIxgroCNYzdtR4scDBwqTvVffEqZvJ2hIuBI34DtKkvuwLtWL/S27tlHBMOwmEbCLWMQfrUUhroC0ONNlcElA5g54W7edDZafI6CW34ImyEOIsljk27IRrB+CKIuEqCgLh+HxAnpUp2nNPRWTQHEMvkfO46i6dMBvHPH+Pilt04UfuTjl9PSHO7Ry6XDkssB6ANASH6UFW2IEDiHnRfL3VTN59DTtg4e5uUBxlnIw1b6T8XUDzRjGZ1tYLSCbA+7U/UT9z/Ae/YVuX5YgoTKTP3WwMmQ6LtFLK1PnXM1ICuzJXLijBmkqu28JN9l+HDxXQu+oZOGH403RN0U0Bo9YQWXpgljLFj9jifz6rFOGZLL/gdFHomVRUY1+w1/BcB+MLFKxOzrovhBxV3jzL1dRNiYSQHFFRkt2NtCWlGhga5s0y+xkBM37GBJ8ykYEieAV3LdUlWMIlaGfLvO+938thoV7MK2JC0dNhvpL4GwIBymhGsuUaETQC0vjB1BT4aTo8SRqHK6B86M8kmNNmk8+eqvE1KNYYLofAkf/FGtPwaRQ9dAPxF25qrF1JUw88H8HDZjgmxxYXlU15gn3CM19TNY3c6elH50nuPvBHKiXL8XRVGPRbVYS3lQhJwUOOYqf6kbXasuLpshTHx1IVuZWCzESNHp5To8JvY1WymiYBr2Wx9dh9lpV1qOx25p6Zm/t5oM0VJ/xFK/Q5+bWQzL0rE4Z23WsFwFXsJj3tHE44CRkVfWN3saz/QnkA90QVWwn/ACyK2zm9yaEGR/FXr/LYj3Dzx1U+oYK2I+s1CZfdO3D7VaH6pV5hqQPmW+LSQHUx4Lq0W+FGIT43kumRaxrtuj9fS9n428czT8eb5ok4UZS03GiYCSx9+TL8Hu3tyhhPBKrGmXbLBDP5ZwtGUBir1s9hav0j2OgiIggPMBWKrJSp1nfZFpfN4zG5YL4FAF+ZcJSaqmhoFFJykiQypaMh6eGRoTM0l6Q3ca+SR9cK/QwvcoARlP/gEvRp0wFeb8THOUWyEhYL+WuaMC9Glj5vW//HekRvn6HzDoYepcTFZRnPPupwHPF8VLa9TxUim3PcqZbufhfh4mGxzAVgLCDLxLshINscSMDI0DVkzOJFGEo/0r/8GnuE61CH5FMdaIwbJWC0IamDbCGnoPwSa2AOOKsgU3VkbhgariWoyM5+3Cg5+QJprRL8fhaqiiLg5E1oK4sMgL8C0xbZMOiAj8fIihjxQjwIWx5c+kjgB9rdp4hTUc9GV8uWCyp8fDUNsrIKP34uFqjpwabImsK93OQT9jMYiCQ7QmzCCF/MBoQbH86pEPkeBbtij3u6VOIRgstmz7kdxgKQ1LDSZSvSRibyguLJJaJzZWGYQgwETr95R50Qeb01xLUzar6MV+gI6m3Wv2s9h9nqzvREvtSD4m0pVwGq0ej+gxR/lJquvLfwaidL21U1S115gXko5GXknnMHF9reNTxrmJMRabsugb4U3Q0TRNhofh91BwbOr0nVma2FI5jA8eNSFsQGJYtEoj31qtawjuLwnNDYQ1fDDKJqm5ZiqBQRQTrqUU57/9FsOn6yvSNoNNFI481M3TOIXYfbFUM8wEYR53Wi6A/tRqpidyXvu8TUHsWlwPWAKsOO5qkb/oIQ/NfWeefit2nZUvaSyPcW7jqtjs5IEo7q96hIbseJn60oSNZKtBizhr6aaJKDFZwDxTlbbLg634T1Kof7GjcphPc2ODUaCm4W/M08+9t/USPugMHpiArKKyG5dBBqZkYZK03bmBpUiFO9aJKfD7K0SjcCcSdBQ2T3mDxGgUpUDc5IOQdcxVWvWH8qNIOnl72abNzO0b0kPC+E0CsWmBHhYlV/oiAl6ZwtvrUtGH4aGC2V6M+ycLdHpat0iFxvijC14dXNPZcPWPumyk2zTjUwaT0suhOeU9onB15xrIkUDOqChuNNvMQPXO/cQrKG2+wdSpjZboWuDtj+ElbxXiX/DMMyCLLmXxz2nnWfB3AjwMi1pdb435WG3rV7a5mE2i4cmHNcw+YkXBHyc0pqBxH0FiOtGYWtTAJHpNyvpStC8mIhLq8Gy7rHoijvCdYMcgtRlchc275A/R23wB2HaYbNDV6lj4pa7YWU+vWfP1uTjh8I+csOA4K40IiNLK7/ZwqoWoi0wRGSxIwICx5/t6WmoBO/u6J8jFKiL0pMgVBkZFPY/XANqBS9eku8NfhTSyZ/eiYbKH4WKGkg23UY+Q0H2RC9lAcXQSFv7WjViEqa0H5KbNk6xC3A+IvJCi+0BycWP3gs7qY9hVF7ONA8XP8pTbXI4me/6nZ0GZyam68AXAsheMJOTR80g2jxmsj+PkLZRUFvDlceL9ehSzT0wezNufHgLYHgnMG8H4qvCP4mtWxGUbuNeHSCIZT1gBeOLa3QKT+whRwif803udJdc9OHRAOmCxEl9L0yCOLnwFI3onJ06aFH7A3R7TQTKzPyY7U7tcxDL4nNOs4X5XydPAzwqT9OKjKBfgCk4AdfeaES2dLWU9ztCDFVhShkI8Y9XVI6zqFHEy9laimO5e6h4cNbRuyAqt+Qjo67JDKA96T84OWvwHQCErrW/+Ox3slhe54THp1Rts6fqT1d36+3a/pV+9IQzTK00osQRLHhR/b1XaGIWj37Sfh6LPXb7hVT4hdW0i3yfbc9LZSOhuB06olR/m9e6PKUVJixEuAXbfSSYHlJpu6+WxfDBWkOoAqzU4xZV8TLXocq5BZOOI/6adHTc3Jwfr1fYtrXKz6esT37x4yCy0iD/rklzZN5YzTMA8jmnTv5/wLtKT3Hl4g1ljw/bEv3eZOM+GslMVW59DuLu3n+ez21tDAbl3EY9z5XHiP49D9fQ4PuA9WOZktH9FA3IqS1+QqBH/reUL6ldye6IEYy5wSFt/++zO3MuW+EO5Fh1Gf+Awoi4JlFApscQQDSdC3AUvwg5gqBprnmIRpjlNv7LTJzonTPGr3zploip6YYZD/vdSlKot18wp8yNNP6CRoYBfTtVdYlFO6SnIL9omRB1UK9SNRPM8y3XJWClt+Vcgm0S+9Dg3yC9HHgeY1YxWDG/xZmf7aB1BIEhypEmHgY3VQH+4pm+zIAxTvXBJoR5AJRsAap2XFNux2AxoFJZZ/4yqTRYax1hHV/O92gcpuEA+YmGm2HbAqGDNolznqYNXO1Hty7RdPnAYmMD9gplSTgdbVpDHat9fJVWhHAhjJ12lIbTmDP70eR5hVYS2cU2TW31VZ6yG6Fu20zeAzi/z/4Ls8XRJ6MZ4mTJ8l6rH8+53cksfrbO4mLb9UAmm4evKJ/ZTfOHnQ58OkFGy06OozC5VzWRGbuA4sdJfvs/27W8gd0eYsRFXfbklB3dgn+Bc0+S2j39ldO+9q2bLSwH+ovkbePj7Nb1hR3njzpVyLoYm/foFBmTDaKMb8Aw/eksBXd6ews0RSdZ4ZMIuyOru58/RxNLeOVdX7dYSwpCSRK+H3doP03wHkHSVNPbsxnGZTpaFoJYK4VAvaxqjFEqgZc6laZpFoer3HkBWagkXg0k6rgLUOIoSxwSHJ9E6zdq2e84Vz+yDYngB3n8rTO5lnc6SBPhlS9Z+6JHZeHfjTlppdY3WFCF8RWWw7XyVyrfu+idboEVTrlP1EDc2BYDvk1wu3JQW+XPSvcZ8MSqmsAx9y+nyHigXoMtC9+Vlr9C0Y18qnGZUixeadnsVj8H+Vu/UHd9JzdG7MI3LDDyMgeaKfo1ATzFApCXVAsrGf+LzYBGyWQ66aOx2ruYu6EnRdm/aJ70nYVmmwqpbRJxWWGUmCI+IuxziA2OcHzGnBf4cAvCrqUHUe62UMCpO1Mt9IsmYqgWj0TP9frsAMnNSM2X6CH6UZu+Z5lLpvZimV64zQSCloIgkEsZ93HM5VsGLwXormxrhiEqY6gQ01Tzkn8A65zPfeT1ayO6oIfPLw2w1GqPi2RLdzYg4Xai+GkzuuoDk2sr/mSP/aUaiPNBYbS+tAp2a0gEFCItChaZTc9kYvX/tpLW85KEsqSOZC6XeS1WaNuVOP44YKU1c5aJwSegpxuxOLBPC5kctKqheCopZIM5alNccONgwTS/dttxB79M3QhPcnysSV+7LjMJTSVoQmYy+mEXxkck+bFM3K8Ckti6ROgaWi9goqDeBRMFqU7nFxzpzoj+I4bH48r0SZ7/c5VnEifQ1SuOFePuq4dYoOqScca01Y3JsEpc/OYmZ+WZVqnLYmARI8XxHDeJVhHcOzAItLcoD4dUc0+om3ObiHCp+qqtkq5NH+ErzHG8i78XA8k2daVJqtuDBvnxqFXjxLB0/t9cASFe28OiBoKIIXayXbxGBTYdk+Hjt040SgYSebmVgxImaQMfRqVmM05IjvUGJJuAxPYfoYDPfkS/xegZwgsVzHCXWdcO69nLWO/hXjQjSOOtRYYqixJsWCWmioMsDS8uGgP1NTWb8+eSJJNd69EU2NzeJt9qojUbuT02X2yc5ymzCwC8WyqniXJ1sawgxASLPMpi7nYmRspbf2pJ6g+epVJImm8mk/CEPnSRuCFWNMQ==\"\n },\n \"piece layers\": {\n \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\": \"OrmkL+1wt5DMFYBPxOPA5cH8hsrAtlIhJviOqd41/UovO12SSlbrFchvhvi5DVrS8CkjguZZ3kfha8mRcGGY8RAc/RPjlPTggnO/aWvkFfCtGWsLa8ABT3MQETa+aH2iYfah6XNpDHw5heTQBas94o4noUMRWj2vf4XeSUUYmCdIld3ZUv5EC/lkauDqKi+iYE86H58RLKSoqbGkns+IkwDJQfh9GBY2FRLtitEbukJqsBxwnHY4QWWoGlkas0FrcooYDBSak+2fcYFoipVLbi7fPZHKR+sJLxFjE8Sc7/hVXfb8usqNYn3EPPHB8jlMsL/sC8ZD5YUIj6iXgFWVR0hIvwMLLJ9f7fGRSmO/R6Z15aF+0YLzNyS9DhAxrxJv6VVbT5VKC6l1/QOSSKr2UvW9C78Sidm7/f+4K1yY0XiPWlQyGODUMrvbBlGZTPb0SVi/828r+YrofHkSv5Tz868yv7R2QdvJ7ldV+RrM5604W0MAN1Tip6BNQndW1CWt\",\n \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\": \"93cms33PbZZTwmgqv5mck+k2grLln6vNXYm1D0H9sr626dMzmAdfQif4o5BAAMScWvQhvRYscGWk1tjplEPvLU5U3OMyqu7tVPITvQ1w1lw7kcozYN4t8ePhlcR/nrE75oHF9Qm9xJAMts3GWvYxUROFkFilCLZd82NQ7Q0c6RKBNy5+y67rQ2VD/+AJFUuSBKUZSD70v12TdSRCL6T2xXsf/3G5jRxwaOVPsfnwinyHHz6FShzXytspL6XSAvXOoxH54BB8RmUgqyE+f5ocs1I+jpnx62UGPhIeU9por+UdwIjtjCFBbAYCXnMXBPfsVPR0HcsDgzcKwoWzvap7gJOJWhRRcB15OZiHQL9KJ6GRE1/ugcoEq2EJbw1Nje5Pzm8TXIEsNZyPVa5cN92E19hFFaOM1ZDKgq4lZgMLu1hrfvaeisk2TyI2TF8znoVw9CJCA0XXYI2e2XIH/3zXyqAYBn2h1un8MFpgBDkU+JblBiAp2L8smM7BBb6IQVKQpd86WXtrpWv5rSld2R94H7AUhc8xkaTZAa2vD8sEackjZBC9S02hWDEpGNl0ohQTVikghfqiW+zrOn7n7CIfcTHOp4SVJq7Qpi9ttuyILC9e+QI+MV888XrNcB29b7Gbd3M2/29mflYtJ7wBneZZdtlQGxgFXD+Z/pNktW5znVu7MzTvUhiitUbq42r0+4aXIa7TprFx3vsnO2IHMvKi7A==\",\n \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\": \"nTFIf0LPwCPoNT78+kgBFHGy5XDvp5NA2p61K/z22PXj9kdCi7wPdvqK8jL2fVqwde66RhXHGbT/Wlutw0isomSetuJEWZ9A3F/TKw1rSjKPGz9hOGa+UfxjjUMX84QfVscP7GVGfcegWQTM87L2o/Q9VKMaQyNPUldSCebK5fSoggX+7LT+5d1U+Z1gaEI/GiCm/D0tfLh6ddXQhybKgZTzoP+zvDor7Mqu+4pGKE1eJHU5h0Iw6DdgGup6vfoOcTFY0flI4DUHi5JMR67XXadcpa5sslf8nZ0AyQC+PEe9+jI5yB3Y2Lj5V3Ah9xW3NJXruPOIZjeqCDXnZ+/Il6VuBvzhB/16jX+N1PFWNDfTP5dPGBaZJz15IYLzPUCC\",\n \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\": \"pUy003/UkvEJDvQzN5jXhX2B5truw0pqVsMHdPYMdqepwWcqZEBrtQvOptgzTBmxBmpyVUPoGed4G1ls5CuQOl4xV3fww7zU6tA/jUAZ2ufWbAfeIayiO+LdgX290qihHmni5a1MiGgL7vHrINB+BBnYJ75BSOFJ+Kcb0J1hBgPCleZZSHUe3y4LBo8Ten/ocrGbCwPAKzOr+g3sb3gt8QI9jKvB3e0VKGBTF58QrjoihLIdPiV/F1W55CkQXidVijh9FocJLESmWdf2CFm4mNOxRWSrOXAEOLuNwgs80pg7uL0qnAqr55gJbZLyktaWpP+cVNh5DaW1I6WwjCsEMWLJKvyGyWN74ibh3taHnGD5FFcw8ytSiFBcTeWDZl0pAuZZaxZgq11l8GuiFJjaMHzWhTixWoqOI3lZ8TUx9Yg=\",\n \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\": \"X5RLsarlc9pADwVgp2d7PP3hZN3ZmYlep0pA2FuskDlbKIx3Rm0aNQJO3I0WeALY06cjog7jS9JXMMPGOZjSwfTPnvqPlOrpBmaR0/0k4UsHeV7Ma4D6Bs8TRdGgGIpmL4mhG19GMmNATTaCSTleOdkxLWg8/ikCEbk87hrqjMDTK570+4Z/37L075ZbJTZ9PiBv0d9otlcPwFwcAG0n99SCx7awBNSolAofK4aduCt+p8TffXnfeG72Ng2hZVCDIIKliAoguzDaGly0iyRkD9yxD7LcM6FTtcCpXYEfQBiBcVkQkZ5TclFCE7XPNpMdtu1ft1BPuisUcqDp7OYdGtvnHkjTPwuuYBp6XIbHYeIDIWfatuSmlzP/abATYrkxDSIHJTBSdOnTkY/9kK60QZ6wtbd10RpAdMcFs1QeMaP4fAvN1GrHwfaT+FBuu31S7MICgv6JzSkpRak9DhD9xoyG6H2fTwWPBY4s+1eLYYCwcwf7n6ChO4VHxZI9Yqk+aGjO/PYTlohOGDg3VeHxgGvVEmoDt+VA1nFtqvi4UPg0P7C914o4FzQrZjpqelSMKFCRlW5uWZnNH1eIe0XA7kSr6POILRwTYofx5Pl23MuDjsg+YTWcTukUj1pKqwuo\",\n \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\": \"DyuboUHYom86yA33liC6obKdfKiCuOiyTlNTt5ACMtZF6TRLlaolBIUHNuLUnUPbfTTyz1cKtR/jFWwl563V8JhmXwSx9EvlfbCwIFulQBWcy9/35DHrRoIcwsUvB2Duu2Kfcd4ICOKqVvzOMsIR2+A1C6Ar+UCCADt5hna2ewVAFlQ/TM5rrxNioKAtWgmowt2dVrmng1dOX5cq6b9R1gQOB2byxyBbQf6oAK1royKWOyLMgVjjnGxLbpkgAAQhyhp+ja7MzGYF+mYo/ohSpjok0tfm0cEbG0k4s0Wgqlr3TMLdjyLTHzVGsPywKTwz/kUtrB3JKMWoxIqBjqWjTf+judP0abSv0/R6C8DhLk6x6oKuFeClww1cjoHfaP+OnSMHjZDPTw7gy+9oKVT5J5ByCWrXkUi760FhEQRXeEG2QAZkJTSgnNfVEEU6n7oU2Toslqh3SpMGjgjbZDveCxw6VhEP6mhZe6QyZ0fSHbaTnlIEt53jrUk4UNzEljVqqb08/csCA06T6mmBaAy82VBj8gRLSzfsWc6mQM2K6cG/UGd4NxdFPJgHMReLgU/xYiAiWhurTbjNhW0wwS86+6fbCPehWnvQq5rLDjtt44yohRMlhOIuPNgQeoQ5YUrtB/cQHki6Wg8bc4OyKMt8mrRBqR0ef5SS8aVt8Lt2TzT+AEp1IYwau6BEO59JkwqhF5F1uZO2+jBB449Az/B+O6JG2vjOhfnAw7aJgkJrUZICgzPkIzMfXEGTGmDzBI5acKeGeVt2Husv7794wjbdw5Lnz5k2vTogv34wNhYoSsUiYpFkHw5iiYERu+XfznAs8Zu2iD1oVRv4MWF8fnYJP8nwjOhSwrjBSx9HRaTicrF+sr9wRZfehlk14qN9E1Oq\",\n \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\": \"gATN5ZHot0T6R5YneYJPrP76oSoC9fI49GtMgK2kJq0ya+06/wZZ5+RgBQoqFOv+5uio41M0wZoPyz6NsYrKFu/91+XGXzDoJQMsTj0PJFL/uXRyzfCtfNk7tfAGrEvN1Jstv3MVyPogT9zowv4U3zDpcPY8zjpfu46LkHI1XZyu6Uh2t/vrc55UO0QgSmAwr9z+mcX/up0n0ZaaTGNOESyHATjBon7HMt2ejPQ1MCBEtjLRPbhcdhEyJRW+A85aqFeqUNOLqTspBvqiio5peWQyQd0lIw/M3+Fi4WNJax+fR+DB+VPjKlaWscyje05QtNu42RT2iqB4L11jn0pdvw/Xfk3ooI+RBJ00u1AeSSQqZiY65sP3G6pTXbxDb6EnTgj48g6avUnyKMJCqHEY1/RhvDakG0n41l1NAT7vh7Y=\",\n \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\": \"SVct+bzPEHObJKHC3ZuofpIzlBylCSOFU8qsaGF2y7RrKZrjhlbOZdu8sE4rXUDn1csYAKCKwJ3cV1y3Ylg/el3ZO7d8syTwukJfpMfJE2yybMbQBlT/+AY4KncrT/IOD+3C6t88jOjaJW/VLiKY/RzeGmfyw3vZzVvFArsSLCxAbcrNIeATbLp0MJ57YOpgxiFgPU9+JUdMcN5PZtG86aTC8t+1Ky7MOcb2HzNdteqfVG58Hkl51l/xPFCHYZRTl3SkoOVy1+B8nEj0W8IS+1E8s9RemS4qyN2p2NZE9JBQKGiOTo8zTcyuhZPB9yxhB2xnfyCQenl8FwpfkQLCAA==\",\n \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\": \"slBceVoM9U+QRS9g3QIT+z/C08OGn3aAuwBuSSKGAzfCsDfRGCkY9IhFtQUG9wSjyrUU0Mh5oW6awq6aICOe/KQJCQ+7mM59RUBCkMbuME9Cr0ut2LlqAs1se4NtKgcMFwGnTtmy78Qu/q08tIAFQB60Y/buuHOaCtsxpWhoB3phA3eQRykDaWLmphUlXoYELeJP2T/C/5ZXENVQz79F8uyXTfTZj98iLqOT+8SxOKA7PsZsnIDRLWGWfgOic/nAZh1wJMIBRw90qFUmoYdQKaWRwB1hcQTObEtZAF+n8qxkRfHCbUnoNJTswB87QtG/hxaYclaVRZaCwgoLvoqYx85e+XGZEI83+HHxwto4xN/Bzj/JVtk2HFbx/QLCdV5rOtUKlJhsYIuOK2juRKCaOLSq5NghVf5alfy9qIjWWseQDHdPDiQB1mck2lrpZDJ+Das9f3b+ce4pLwxtQRW1JLOBjgBi7HXelPyu5lMRlqZcwRhNFZ4DxrprmPYmw0KeScndn5LHJXsDC1JA238+wIWP29ZF4QwLWvMPkn4Wfe4QYmBpeNINhPJjCwkhQx90Y20VBaJGKYK8/Le1J3VduaJ3ciYxDYcd7WW0ngaZiVKZ0y7pjfqC+tLT5zq+GQSGNPNXogL+OEaFKYVd7Lz0PfG88uaIkYVq0H7CDAjZwJZSnlCaT85L0Kv44jouiIKmfO9QX8zDS+QxLAh/Xy7VAw==\",\n \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\": \"KyDTfIYZYSJe7qMcrgvE5sUiV+0eus85j2/q76tIC8603rr7bx1er5UDI/7srDKX0/ah0ZdGtzleHKkkAuVwCE73C6iDxwEI5OESBF10mHRzD2ShejpsUNGx2wGKSNBl3m4XFs6Ta84bpxA9FvbbeBPsMvH8k1+fjxee4LqYxghlBHAOyMk0zMmvVGHN59cG7jnDon0o172flEf+UYrOnom8ZDi431XQ7wu5KVpfR8YqCjrAfLfPMhymEv2K17Kf\",\n \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\": \"/5ZiHJz/da3YhNv57jfG+6W5p++ZnjCPsy2QOJlC8m1iN2bmH3ygJtADgZZjbBq9lMg6qbCwEVfHzVNSgh6E62zNiPrHkguznFha+8orialK0xQe9TVnoMAS1fdMtFPkWFa8zKVXlLrmEiXa6MMDt7LmTGDOw4AFmIgTZ9+cQyJh+UvZYdYyO5xl9yVhDIK/HNSkhIe/E/gkMTBm/q+mXfER+uPLq1xc4eqykA+FVvJnBd4rzuDABEFUJP+sVqxqFFY28Ue+SSrcmHjnf8IL6bXem9lUcLoS176lXBOF73OIY9s1E+2vOM02HSXYiPXbsi+QE+cKrvs/PQ1gas5/EqGXDrW2ioYqebIX+Dw5re8xLvP67w3xrQh6N/UwyUb05FoKrgLFxisYn/NELkWiNV66PFccfCh4K0RYZUW8SMsHcwAEfQRst5ICX6H9HI6HUqkk++FQFWw1xmdPbcBKCw==\",\n \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\": \"n2cabuVybK3NWw3P3/XguQcIwT/JKcvTwjfIWqfqHQ8Sd1QeMY4bVjOol1ubEWU7BfhXaI9TS80wWoj7MKXCUdn+XkJXNh09vj1BoksVyiPjPT4powprR0MHolp1jxh+fN5uppVJMEtHYEMy1EcpU9zVAALQ5fwXL2XJ00+VhFsnoL+5NybXXGXB08Ihiy41rTkL/eDf9wS/xnDAooYsO+cC+ZZZJ+v7Pom/3pRU9soMFNJOKct1bsb7uuT9prOnKF4ppNEFZE/GwMezOYK+71WkMqjJHexFaEWiYLGM8mU=\",\n \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\": \"cy1UGgsHtzk1YlCUOzYxU5TTrIeXX7A6KiSlLj0ng+HzCKmJRaDGi0oqbkHJJTxA2UPrluSUJ4+CAhOYw7XZwHPVW8jh2ywDtSABmQvXEulGVJ/knxQCMCbnZEs5Sp+2J/Wk0hp6kJp0lu4Zsqts/pk1n/GiWxfN/1XZA1URfMu0GO4oWPDIPgpqFTKtUR0KL+AICQIo/7iKceDKl4EQLw==\",\n \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\": \"AwIURStzYR9k0OBxqnDmX1TL+6LU6aS9QO3blOLQuVh8MU/2F1KcBDskSVtkrJwfROiHpe9E8WCDMD0qbFRb4AMFBZhF7HeToUj175XtVeLU5WVWK8vB/gumLvQDTZqWb9tBwvcs8dGwzdVJfTNiIyIUl8eAVXtDEoICFgGyt1N85uGsl1BXTsY75p8pcKwAm/3wBVqZHPWUxYyjry1AKeCMCi09CIAn3HSD+65ewi9vwgNuhx3MZJQ6+3YZcHLWonWu2aGTHCPRHy7ERJ3EMgk3l7BDLoXLpTqe+/vRYXm7q7VwHyc0xEo6VeHLtWyeeUGUt23zWfxt9l5hHFCJ2ZvQVbJxYP49QCkvUbOmeYv7HAuWzl5f217lD+tnhOJ6AxBhMVZjWoy+IMg1y2C7mJ25nEd2J2yFfHf+WSy1h4d9IcPbYm64MSWR5TCnHKb1MSt4xdCjjTi2HP/8Zxhb9A==\",\n \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\": \"wfe1c3oZL51D35knRIf54tu+pUgpVC+Pitr0bOoSKXSKeKRvqfGE2aB8VbQLoEB5DjmEYteWfKtJBqN3i01k8aZiKfFLM0jpad/3LAH0ULeyMLRGPT0QTfuCzh46QjiSMa93Fnhf61OzQxqmt1A23pxwXeqwivAsehtrCthNeWncGiRl/8O/pMmyu6h6SF7LtfE7w+MaxB8m1MfQBvauhoTDOdRFAAlKm4wFkAHxZZlAixiUvKOchdvG31viM+OCyibWkTmkSlXVFWCp4ju1ndjS5jjzlPAz4lRd/D8YYoGEbUlK0WF7GNR8B+grTRE7m3CTLQBkLvtxsfPfR+TLEL/iPc7AQ4ZNZ+4ixzkWPISuvu7vZb0xByx5fgoWCPJDiRMprGx+GG8FjcMAI8Q5zpOCBVr5S+tQZpF6gUQJPtLXcHaWfXGy+AWGPWzEQQB4QzutKPTkZT/GteT9I7To+tKSsTv7bnWcfZrtBbOhpBUHBAl02W9vcPCwwgRpDLiWpGJfoziX4bmmqq3PlAdSf6D7pvOd5NJ5gsXFayZJHrwgZ8fO1zO+CvBlVEsTSEfOYxn6k4h6igW5Cjo+hPXRlF6tUi+opqd3oJK+AvER8exB4V3K4pa0eUO7ICA9PViKrX4eeKwRSurxzthIxR1SZyRIu+u80l3Qp0Vjn7Rs9ZY=\",\n \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\": \"mlA7x/hs+LOUdTamHzEJZQZelGfXHa0EIXd8VZDLe1tnCTQgWmwYXfhSeJ6Sfsvz2J0t6jcLHuYM1JenXbNCnWA1sblB+D4aHQs/GWbP15cOKSwp3Vzr4m4VdrbPMRXdHFVn8cE/QRa8akYhme/uel68R1RyhgqvDZOsD1Ml7EigGtsKa0wzu9+kodzCCtCI+qruIUa64BxQDLTcW21fGpLeQZeaDJAIr/DrzSs9s/z0OjI1XP7Eo8VGGLFn7zO8dMT69LLyD5XPRbYIz50+/AV/2+Bn0DaBYy+f6AZJVAiCVEj7++2CuDIyAzNltb4SZSRH6fjyrmqDIo7v9PfIy7iXvTHwuJweyfEuDF/l7iKZsWjMlLT77vseIiaDqCBsXuMPg4cZCF35MXmfmDObco0zvUBxszujKyrGdKxx58bRnTgPPFh4h3lsDwKzJu23F60vsG4V0WNQ9tY2tiePfA==\",\n \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\": \"+yKBmhJqkSsy3at/jXFIFvjZbUPT6ayJHsBtWeIP3mzHHflO6MUtioZiVQ2VnnA/PnR3JelkcwKwMRXmfm7i1zhBM2Awl0WlrqImvM2TBvmfMVPdpyWwejTwa/+qI79szw9waLwWYOdmu+jAwXk2hpE5naPvsm2lRuz1x1ehpyA=\",\n \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\": \"Ngl7rHoMejoI0eGHLHI9mb0ELNrRc7h4EZCiaaiO1TQSgBX//vPdlS6pptsB62ocxGacCH+GrIhH75QNhwxAEWwBWFGDFofCVii4EctiGgBt3CpQgF7Jx0ce5AGxvD2/PKuV5MribIC893ftswL+NQFsT8Vp3N4O+nvWr2Q1/5avfGwkblyHAhWXIV5w3R9uYht94HxH0EjYZrDKCzkVr2ZypdlMVeStnJPBuJG5g+YcZMJBGFJvMxzYs95AeluXmh/dIIEh1WzPzVhaseC6q2z+SiIV5ZJtrH8deVNI4gOd6dOG5VCVjEtjwf1ewCzoYoma14wCm5gf8/4mT7FxNZ5zuB4QjBz0rJ6d/iQ2slugGU0syxnXKBLIFh2+Hx7WRA092URN/32owNkFVaj9OAg7pGKP97gdmKcqChaRoRLjgwJXQ5dr1YbLyG0qzhIUA3Vu1PtPb5qWBOykTxh3lO1ylt4Mlo2M9q7QjqONPfwXXOmttB792hOfERKzgniofrq0kwKEl/Yd/QhktOkrbrKUs1Xco0R3365y4kmuIdes3lws17nmWbu6fRZvjGrC6Zu3ZN7QRgxIpRWd1R7g6MkHnOBfz4Ws4yCheJorn5XgmbamnABpo6t1mwZEk7/6\",\n \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\": \"XldIzkVQ/K0VVG2P0RElCMdWn9S8BZSDOC5aiNVoz6GRXffE8J1looO+ZKsmT2oV8KkcUmbaHnxaWNixFC4Q3LozSkrcAjdnMq0MZxRFk/WVQmsDLTuUFEfKT6s6j9ERT/cTcH7Qw0bzDRzY8H3Q91qdw02RK9C2JWHqWdgxn6G0u8gIPFom7atyZb6eF6wpbPu2y+hYtV+KQ+WKbajiji3RYxBUEmnlsIA7TwaKBNRbL8IbmP0iX8o21yI6BRpPLQ3dfHz1J0FWdnvucbZaN63DLkZVyy0RnJ4jKULGiYXTCkKCFri3kNib799tEsU9JVUq46V5hKdP8SOxarWv7BCYhmvxbH3O53OVEpjmCALaVHtrD/S7R4R7qsPiTk290vk7edzbbj5NyoPsXJPXGDX41OwipCoIL6TMulboWVWU/ZjTSh/bH5WfqPdLJDhKx61AnZSpUPJgJroNdzojjw==\",\n \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\": \"JlcL/yfbL/d+1bjsMMpNkCN9AoNtQSHiecicfzxrpep2Vzthoooyk3zGGyrStrhmxHU1DgfoyW176op+3stFf4InqNCBBU2dciGuJrfEcmj7GuWi7eWVZWOcsZOrbOc07dz4BkaSxjPfCkxShE123IBOdz3t4Lot1CD0BsE8smVFF3qp6x0SFYxfofNSIU5T2gwIRE3HzJPzN1tnsV98lhaAUU/a3YUofqrgVNPAIrqZSH+bAmdGhxPsAJNzW+cq199LRjHVpIY3n41WoujCzZK3bF3hbRtU5hxQK+zqMQnSskDlk/34arHlTPRW+0z8hIIMcZjq+wzxxLBdcIim9hi/HsQjIrQjS4kdFwHuRZ7Mnjv0c2KLCui36pYqjAaMYhhIh5HOeyorUJY0T9ux08f5jE5caofU2GZu9pHZZjk=\",\n \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\": \"O51c+F+qBXvwiodQC7gu1Rr+OfdGN4VI8gsLkqaiiGDyS3znAOXh0/0NF4+CVA98s896hwo9fgRMnLqOh47dkQdpFJVqA8GZS7ye9tf9kQIG46IfkhrKYJNoeilTs0rJC9MI9qK23EtTiDEYPjK2nuEvnCx67Wvj8uyUZvJM152LC9Qy/YdXw0uTP0jD/5hLDjOHWgNGEt4lalejai4yPR4xh4aHAZi8J7KNCdr9WnfQeTv9r0g7XVacs7+lOHc83LzyU2TUdgqlCULob6toS6yWMuoCF1Hpva+OTUyNYIHA1tywiItg4/frDOfdx+wS7SmUfTPxwrDgwfYKRZLT8AnBgScna9JdcDGOF3Vx4mM4H7E2RXM8b9hc+hs0XjsJmtmUueCm96U8Yt9Jj0O9eWVB5Huj47cF4HfJA/gBoOESUMGqURVMjvWL/XiX6+epAdNvySvRTtnOZpEfTvQHBDu1l8JzF7mVqCOOmIqWnS8IDVuQlZhcgtCKn00SWDal1i2RbbqeJbYoEQtCWHkwOWi2Uy37gj8mKZBNxMURazE=\"\n },\n \"infohash_v1\": \"59c586143f057d74f28f0c423b0b6e5d39317838\",\n \"infohash_v2\": \"84c40568b01503d8d4baed762ae8aa3c096ce6bdfdc7ede39fe9c6ef531f2de3\",\n \"type\": \"Torrent\"\n}\n</code></pre>"},{"location":"fep/d8c8/#abbreviated-representations","title":"Abbreviated Representations","text":""},{"location":"fep/d8c8/#v1_1","title":"V1","text":"<p>Minimal</p> <pre><code>{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\"\n}\n</code></pre> <p>Expanded</p> <pre><code>{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btih:1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083&amp;tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n</code></pre>"},{"location":"fep/d8c8/#v2_1","title":"V2","text":"<p>Minimal</p> <pre><code>{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\"\n}\n</code></pre> <p>Expanded</p> <pre><code>{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btmh:d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a&amp;tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n</code></pre>"},{"location":"fep/d8c8/#hybrid_1","title":"Hybrid","text":"<p>Minimal</p> <pre><code>{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\"\n}\n</code></pre> <p>Expanded</p> <pre><code>{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btih:1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083&amp;xt=urn:btmh:d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a&amp;tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n</code></pre>"},{"location":"fep/d8c8/#reference-implementation","title":"Reference Implementation","text":"<p>A python package with PEP 751 lockfile is included with this FEP, see <code>./implementation</code>. The package implements encoding and decoding <code>Torrent</code> objects to and from bencoded torrents. </p> <p>Install the package with <code>python -m pip install .</code> from the FEP directory, and then call <code>fepd8c8 --help</code> to see usage documentation</p> <pre><code>usage: fepd8c8 [-h] [--encode] [--decode] [--with-context] -i INPUT [-o OUTPUT]\n\nEncoding and decoding torrents to ActivityStreams Torrent objects.\n\noptions:\n -h, --help show this help message and exit\n --encode Encode a .torrent file to JSON\n --decode Decode a JSON torrent object to .torrent\n --with-context Include the JSON-LD Context in the json output\n -i, --input INPUT path to input .torrent or JSON file\n -o, --output OUTPUT path to write output to. If not provided, print to stdout\n</code></pre> <p>Additionally, the <code>torrent-models</code> package will implement export to the specified format following this FEP's drafting (and this FEP will be updated with the version of <code>torrent-models</code> that implements it, when it is released)</p>"},{"location":"fep/d8c8/#discussion","title":"Discussion","text":""},{"location":"fep/d8c8/#string-encoding","title":"String Encoding","text":"<p>Two string encodings (base64 and hexadecimal) are specified for binary data. This choice was made to balance space efficiency with matching conventions that are common in bittorrent clients: bittorrent clients and trackers typically represent infohashes and other hashes as hexadecimal strings, however base64 is a more efficient encoding for the much larger concatenated <code>pieces</code> and <code>piece layers</code> strings. </p> <p>The handling of strings in the bittorrent specifications is vague, and that vagueness is matched here.</p>"},{"location":"fep/d8c8/#out-of-scope","title":"Out Of Scope","text":"<p>This FEP only provides a means of representing <code>.torrent</code> files in ActivityPub/ActivityStreams clients. The following is thus out of scope:</p> <ul> <li>How to produce <code>Torrent</code> objects from files and directories</li> <li>How consuming instances should process <code>Torrent</code> objects</li> <li>The rest of the bittorrent specification (e.g. using ActivityPub instances as trackers, which will be the subject of a future FEP)</li> <li>Improvements to the bittorrent specification: this attempts to be a 1:1 representation of existing <code>.torrent</code> files, though the nature of RDF and JSON-LD allowing for future extensions is part of the motivation of this FEP.</li> </ul>"},{"location":"fep/d8c8/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, [ActivityPub], 2018</li> <li>Bram Cohen, BEP 0003: The BitTorrent Protocol Specification</li> <li>Bram Cohen, BEP 0052: The BitTorrent Protocol Specification v2</li> </ul>"},{"location":"fep/d8c8/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/d8c8/#footnotes","title":"Footnotes","text":"<ol> <li> <p>A <code>.torrent</code> file is often just called \"a torrent,\" as it is in this document.\u00a0\u21a9</p> </li> <li> <p>Bittorrent v2 is a backwards compatible extension of bittorrent v1, so torrents may be v1-only, v2-only, or so-called \"hybrid\" torrents that have both v1 and v2 fields.\u00a0\u21a9</p> </li> </ol>"},{"location":"fep/d8c8/implementation/","title":"FEP d8c8 Reference Implementation","text":""},{"location":"fep/d8c8/implementation/#usage","title":"Usage","text":"<p>Install the package with <code>python -m pip install .</code></p> <p>Use the CLI <code>fepd8c8</code>, call <code>fepd8c8 --help</code> for usage instructions</p> <pre><code>usage: fepd8c8 [-h] [--encode] [--decode] [--with-context] -i INPUT [-o OUTPUT]\n\nEncoding and decoding torrents to ActivityStreams Torrent objects.\n\noptions:\n -h, --help show this help message and exit\n --encode Encode a .torrent file to JSON\n --decode Decode a JSON torrent object to .torrent\n --with-context Include the JSON-LD Context in the json output\n -i, --input INPUT path to input .torrent or JSON file\n -o, --output OUTPUT path to write output to. If not provided, print to stdout\n</code></pre>"},{"location":"fep/d8c8/implementation/#testing","title":"Testing","text":"<p>To validate that we can roundtrip a torrent to/from the JSON representation, </p> <ul> <li>install with the optional <code>test</code> dependency group: <code>python -m pip install '.[test]'</code></li> <li>call pytest with a <code>--torrentdir ./some/directory</code> argument that points to a directory with .torrent files to roundtrip</li> <li>Optionally: call with <code>--keep-output</code> to inspect the created json files</li> </ul>"},{"location":"fep/d8c8/implementation/#see-also","title":"See Also","text":"<p><code>torrent-models</code> - a more complete implementation of .torrent files in python</p>"},{"location":"fep/d9ad/","title":"FEP-d9ad: Create Conformance Tests for Fediverse Enhancement Proposals","text":"Authors Status Date received Tracking issue Discussions Repository bengo ben@bengo.co <code>DRAFT</code> 2024-07-02 #350 - codeberg"},{"location":"fep/d9ad/#summary","title":"Summary","text":"<p>This is a proposal to enhance the fediverse by creating test cases for FEPs. The proposal describes a Conformance Test Rule format that FEP authors and testers may find useful when creating tests cases as proposed.</p>"},{"location":"fep/d9ad/#contents","title":"Contents","text":"<ul> <li>Context</li> <li>Proposal</li> <li>Conformance Tests</li> <li>W3C Accessibility Conformance Testing (ACT) Rules Format</li> <li>FEP-d9ad Conformance Test Format</li> <li>Conformance Test Components</li> <li>Applicability</li> <li>Change Log</li> <li>Description</li> <li>Expectations</li> <li>Glossary</li> <li>Identifier</li> <li>Input</li> <li>Issues List</li> <li>Name</li> <li>Outcomes</li> <li>Requirements Mapping</li> <li>Target</li> <li>Test Cases</li> <li>Test Subject</li> <li>Test Suite Memberships</li> <li>Appendix: Conformance</li> </ul>"},{"location":"fep/d9ad/#context","title":"Context","text":"<p>FEP-a4ed: The Fediverse Enhancement Proposal Process describes a process for proposing enhancements to the fediverse (i.e. 'FEPs').</p> <p>However, this process says nothing about testing implementations claiming to conform to those proposals nor ways of making FEPs that are more conducive to consistent conformance testing.</p> <p>However, consistent testing is an important part of an interoperability and accessibility on the web:</p> <p>In order for web specifications to permit full interoperability and access to all, it is important that the quality of implementation be given as much attention as their development. Moreover, as the complexity of W3C specifications and their interdependencies increases, quality assurance becomes even more important to ensuring their acceptance and deployment in the market</p> <p>\u2015NIST.gov - W3C Quality Assurance Activity Initiated</p> <p>The goal of this FEP is to propose a way of proposing FEPs that may lead to more consistent testing of candidate implementations claiming to conform to a FEP.</p> <p>For more context on conformance, see the appendix on conformance for:</p> <ul> <li>What is Conformance?</li> <li>What is a Conformance Model?</li> <li>Why does Conformance Matter?</li> </ul>"},{"location":"fep/d9ad/#proposal","title":"Proposal","text":"<p>FEPs authors MAY include a conformance clause.</p> <p>FEPs authors MAY Indicate which conformance requirements are mandatory, which are recommended, and which are optional.</p> <p>FEP authors MAY write test assertions.</p> <p>FEP authors and testers MAY write tests.</p> <p>FEP testers MAY publish a FEP test as a new FEP.</p> <p>FEP implementors MAY test their implementations.</p> <p>FEP implementors MAY publish an Implementation Conformance Statement explaining how the implementation conforms to a FEP</p> <p>Project and services claiming to implement a FEP SHOULD publish an Implementation Conformance Statement</p> <p>FEP testers MAY write tests using W3C Accessibility Conformance Testing (ACT) Rules Format 1.0</p>"},{"location":"fep/d9ad/#conformance-tests","title":"Conformance Tests","text":"<p>Conformance Tests provide guidance for developers of automated testing tools and manual testing methodologies, to help ensure consistent interpretation of the FEP.</p>"},{"location":"fep/d9ad/#w3c-accessibility-conformance-testing-act-rules-format","title":"W3C Accessibility Conformance Testing (ACT) Rules Format","text":"<p>W3C ACT (spec) is a conformance test format that can be used to provide guidance for testing conformance to W3C's Web Content Accessibility Guidelines that are normatively referenced in other standards and laws like section508.gov, gov.co.uk, and Europe's EN 301 549.</p> <p>While there may be other formats for Conformance Tests, the ACT Format is sufficient for expressing Conformance Tests not only for accessibility, but for FEPs as well. This author was unable to find any other commonly used conformance test formats. At the same time, because the ACT format was designed specifically for accessibility guidelines, the ACT format may be imperfect for FEP Conformance Tests. So this FEP does not specify a strict conformance test format. Instead, it specifies useful components of a Conformance Test, most of which are inspired by similar subcomponents of ACT Rules.</p>"},{"location":"fep/d9ad/#fep-d9ad-conformance-test-format","title":"FEP-d9ad Conformance Test Format","text":"<p>This FEP defines a kind of Conformance Test that may be used. It reuses many good decisions from the ACT Rule Format, while generalizing the format to be useful for expressing tests for things other than accessibility (e.g. FEPs).</p> <p>At a high level, a Conformance Test specifies</p> <ul> <li>input required to run the test</li> <li>applicability, or whether the rest of the test even applies to the input</li> <li>targets, derived from input, that should be tested</li> <li>expectations whose assertions about the targets are tested</li> <li>outcomes the result of testing expectations for each test target</li> <li>requirements mapping how the outcomes affect claims about a test subject's conformance to any specific requirements</li> </ul>"},{"location":"fep/d9ad/#conformance-test-components","title":"Conformance Test Components","text":"<p>Each of these components may be a part of a conformance test.</p> <ul> <li>Applicability</li> <li>Change Log</li> <li>Description</li> <li>Expectations</li> <li>Glossary</li> <li>Identifier</li> <li>Input</li> <li>Issues List</li> <li>Name</li> <li>Outcomes</li> <li>Requirements Mapping</li> <li>Target</li> <li>Test Cases</li> <li>Test Subject</li> <li>Test Suite Memberships</li> </ul>"},{"location":"fep/d9ad/#applicability","title":"Applicability","text":"<p>Applicability describes how to determine whether a test is even able to be applied to a particular Input and produce a meaningful outcome other than <code>inapplicable</code>.</p> <p>A test may have all kinds of outcomes, e.g. <code>inapplicable</code>. If there is a test specified to apply to an ActivityPub Actor Object and check that it is valid JSON, and the test is run with input of a fish, it's not even possible to check the fish for JSON Syntax conformance to derive any targets or apply expectations. It's a fish. The test doesn't apply at all, and in situations like this, the test run MAY have outcome <code>inapplicable</code>. The Applicability section in a Conformance Test specifies how to determine whether the test applies to the test input and, if so, how to derive the test targets.</p> <p>Inspired by</p> <ul> <li>ACT Rule Applicability for Atomic Rules</li> </ul>"},{"location":"fep/d9ad/#example","title":"Example","text":"<p>An example of an Applicability clause for a hypothetical conformance test is:</p> <p>This test applies to an ActivityPub Actor Object. If the value of the <code>actor</code> input's \"type\" property is an array, there should be a test target for each entry in the array. If it is not an array, there should be one test target whose value is the value of the <code>actor</code> input's \"type\" property.</p>"},{"location":"fep/d9ad/#uri","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/applicability</p>"},{"location":"fep/d9ad/#change-log","title":"Change Log","text":"<p>A log with entry for each change to the test over time.</p> <p>For example, if a test if maintained over many years, it is likely that the editors will learn from experience with implementations and usage of the test implementations that there were unexpected inputs the test might be presented with, and then update the test inputs, target, or expectations to better handle the situation. This kind of update is encouraged, but it should be logged in the Conformance Test's Change Log.</p>"},{"location":"fep/d9ad/#uri_1","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/changeLog</p>"},{"location":"fep/d9ad/#description","title":"Description","text":"<p>A plain language overview of what the test does and why.</p> <p>Inspired by</p> <ul> <li>ACT Rule Description</li> </ul>"},{"location":"fep/d9ad/#example_1","title":"Example","text":"<p>This is a conformance test that checks whether an ActivityPub Actor satisfies syntax requirements for the \"type\" property</p>"},{"location":"fep/d9ad/#uri_2","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/description</p>"},{"location":"fep/d9ad/#expectations","title":"Expectations","text":"<p>Assertions about test targets.</p> <p>An Conformance Test MUST contain one or more expectations.</p> <p>The expectations describe what the requirements are for the test targets.</p> <p>An expectation is an assertion about a test target.</p> <p>Expectations determine test target outcomes</p> <ul> <li>When a test target meets all expectations, the test target passed the rule.</li> <li>If the test target does not meet all expectations, the test target failed the rule.</li> <li>If there are no test targets, the outcome for the rule is inapplicable.</li> </ul> <p>Each expectation must be distinct, unambiguous, and be written in plain language.</p> <p>Inspired by</p> <ul> <li>ACT Rule Expectations</li> <li>ACT Rule Expectations for Atomic Rules</li> </ul>"},{"location":"fep/d9ad/#uri_3","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/expectations</p>"},{"location":"fep/d9ad/#glossary","title":"Glossary","text":"<p>Definitions for common terms.</p> <p>The Glossary for a test MUST define each of the test's possible Outcomes.</p> <p>Inspired by</p> <ul> <li>ACT Rule Glossary</li> </ul>"},{"location":"fep/d9ad/#identifier","title":"Identifier","text":"<p>a globally unique identifier that identifies the Conformance Test</p> <p>The Identifier MUST be a URI.</p> <p>Inspired by</p> <ul> <li>ACT Rule Identifier</li> <li>ActivityStreams 2.0 id</li> </ul>"},{"location":"fep/d9ad/#examples","title":"Examples","text":"<ul> <li><code>urn:uuid:93bafe14-b13f-4a9a-9d47-0a9316d50c97</code></li> <li><code>https://bengo.is/fep/d9ad</code></li> </ul>"},{"location":"fep/d9ad/#uri_4","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/id</p>"},{"location":"fep/d9ad/#input","title":"Input","text":"<p>This is what goes in to each run of the conformance test.</p> <p>The test input is the entirety of how each run of the test can be configured.</p> <p>An Input may itself have several components.</p>"},{"location":"fep/d9ad/#example-input-specification","title":"Example Input Specification","text":"<p>Consider a hypothetical Conformance Test that tests conformance with ActivityPub. An Input to the test may have several components, for example:</p> <ul> <li><code>id</code> - URI - An ActivityPub Object ID</li> <li><code>authorization</code> - optional string - A value to pass with each request to fetch <code>id</code></li> </ul> <p>The Input and its components MAY be represented as a JSON Object with a property for each named component:</p>"},{"location":"fep/d9ad/#example-input-as-json","title":"Example Input as JSON","text":"<pre><code>{\n \"object\": \"https://bengo.is/fep/d9ad\",\n \"authorization\": \"mellon\"\n}\n</code></pre>"},{"location":"fep/d9ad/#uri_5","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/input</p>"},{"location":"fep/d9ad/#issues-list","title":"Issues List","text":"<p>A list of unresolved issues with the test.</p> <p>Tests should be published early and often, even and especially before there are no known issues with it. The issues may be considered over time, perhaps waiting for enough information to determine a resolution, and then the test may be updated accordingly. The Issues List makes test readers aware of unresolved issues that may lead to test changes later.</p> <p>Inspired by</p> <ul> <li>ACT Rule Issues List</li> </ul>"},{"location":"fep/d9ad/#uri_6","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/issuesList</p>"},{"location":"fep/d9ad/#name","title":"Name","text":"<p>A short label for the test that can distinguish it in a list of tests.</p> <p>The name MUST NOT include HTML markup.</p> <p>The name MAY have distinct values for distinct languages.</p> <p>The name may be a value that is not unique amongst all other test cases. However, test names should be sufficiently descriptive to distinguish them from other tests in the same test suite.</p> <p>Inspired by</p> <ul> <li>ACT Descriptive Title</li> <li>ActivityStreams 2.0 name &gt; A simple, human-readable, plain-text name for the object. HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values.</li> </ul>"},{"location":"fep/d9ad/#example-name","title":"Example Name","text":"<p>An example name for a hypothetical test:</p> <p>ActivityPub Actors MUST have a valid \"type\" property value according to FEP-003c</p>"},{"location":"fep/d9ad/#uri_7","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/name</p>"},{"location":"fep/d9ad/#outcomes","title":"Outcomes","text":"<p>A test's glossary SHOULD define every possible outcome that the test may assign to test targets as a result of the test expectations.</p>"},{"location":"fep/d9ad/#uri_8","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/glossary</p>"},{"location":"fep/d9ad/#requirements-mapping","title":"Requirements Mapping","text":"<p>When a Conformance Test is designed to test conformance to one or more requirements documents, the test must list all requirements from those documents that are not satisfied when one or more of the outcomes of the test is failed.</p> <p>Each requirement in the mapping must include the following:</p> <ul> <li>the requirement</li> <li>name or summary</li> <li>uri identifier</li> <li>url to documentation</li> </ul> <p>Inspired by</p> <ul> <li>ACT Requirements Mapping</li> </ul>"},{"location":"fep/d9ad/#uri_9","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/requirementsMapping</p>"},{"location":"fep/d9ad/#target","title":"Target","text":"<p>All parts of the test subject that will be checked by the test.</p> <p>Each target MUST have a test outcome. Each possible outcome SHOULD be defined in the glossary.</p> <p>From a given input, the test may derive many targets. For example, from an input that is a JSON Object, a test could derive a test target for each JSON Object nested within the input Object. Then the test, for each target, could check for conformance to a hypothetical requirement that all objects within the input MUST have a certain property.</p> <p>Inspired by</p> <ul> <li>ACT Rule Input</li> <li>ACT Rule Input Aspects</li> </ul>"},{"location":"fep/d9ad/#uri_10","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/input</p>"},{"location":"fep/d9ad/#test-cases","title":"Test Cases","text":"<p>Test Cases (i.e. test cases for the test itself) are a set of example inputs and corresponding results that implementors of the test can check to be confident they have implemented the test has specified in prose. The Test Cases may also be helpful to test readers seeking to understand the intention of the test expectations.</p> <p>Every Conformance Test Case SHOULD include</p> <ul> <li>input</li> <li>targets derived from input</li> <li>outcome for each target</li> <li>how outcomes map to requirements</li> </ul> <p>Inspired by</p> <ul> <li>ACT Rule Test Cases</li> </ul>"},{"location":"fep/d9ad/#uri_11","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/testCases</p>"},{"location":"fep/d9ad/#test-subject","title":"Test Subject","text":"<p>An overview of what kind of thing the Conformance Test tests. For example, a FEP Conformance Test may apply to an ActivityPub Actor, or another ActivityPub Object, or some part of a server that hosts many ActivityPub Objects, or it could test something else entirely. The Test Subject is helpful for contextualizing the test Input</p> <p>Inspired By</p> <ul> <li>https://en.wikipedia.org/wiki/System_under_test</li> </ul>"},{"location":"fep/d9ad/#test-suite-memberships","title":"Test Suite Memberships","text":"<p>The test suites that the test is a part of, if any.</p> <p>For each test suite membership, the test should specify:</p> <ul> <li>name: Plain language name of the Test Suite</li> <li>url: URL to the Test Suite</li> </ul>"},{"location":"fep/d9ad/#uri_12","title":"URI","text":"<p>https://w3id.org/fep/d9ad/ns/test/testSuiteMemberships</p>"},{"location":"fep/d9ad/#conformance-with-this-specification","title":"Conformance with this Specification","text":"Conformance requirements are indicated by sentences containing MUST a la RFC2119. A FEP-d9ad Conformant Conformance Test is a document satisfying all conformance requirements in this document."},{"location":"fep/d9ad/#related-links","title":"Related Links","text":"<p>These links were helpful when researching conformance testing</p> <ul> <li>w3.org - QA Framework: Specification Guidelines</li> <li>ACT Rules Test Cases</li> <li>ISO 17000 - Conformity Assessment</li> </ul>"},{"location":"fep/d9ad/#appendix-conformance","title":"Appendix: Conformance","text":""},{"location":"fep/d9ad/#what-is-conformance","title":"What is Conformance?","text":"<p>Conformance is the fulfillment of specified requirements by a product, process, or service. These requirements are detailed in a specification as part of a conformance clause and in the body of the specification. A conformance clause is the section of a specification that identifies all the criteria that must be satisfied in order to claim conformance to the specification.</p> <p>\u2015w3.org - QA Framework: Specification Guidelines</p>"},{"location":"fep/d9ad/#what-is-a-conformance-model","title":"What is a Conformance Model?","text":"<p>What does it mean?</p> <p>The conformance model is the conceptual framework in which conformance is defined. It consists of and is defined by addressing at least these three topics:</p> <ul> <li>What needs to conform and how \u2014 hereafter designated as class of products.</li> <li>Any special designations or concepts used to distinguish conformance categories, types, etc. (e.g., profile/module/level, well-formed/valid, A/AA/AAA).</li> <li>Ways that conforming implementations can vary from each other (e.g., optionality and extensions).</li> </ul> <p>Why care?</p> <p>The key is to communicate to the reader what conformance to the specification is all about. The model provides a framework for implementers, describes what they need to build in order to conform, and explains the different ways that they could claim conformance. It provides users and customers with a basis on which to express their requirements.</p> <p>\u2015w3.org - QA Framework: Specification Guidelines</p>"},{"location":"fep/d9ad/#why-does-conformance-matter","title":"Why does Conformance Matter?","text":"<p>It is inevitable that people (e.g., vendors, purchasers) will either claim conformance or demand conformance to a technology. In fact, claiming conformance to a technology may be required in certain situations. Thus, it is important to provide a consistent and unambiguous way to make these claims. Identification of the specification version, class of products, and conformance label are some of the items that could be part of such wording.</p> <p>Why care?</p> <p>Having a framework, by which to make conformance claims for a particular usage of the technology, minimizes confusion by people who are interested in such claims. Many contexts use conformance claims, including legal as part of regulations, laws, or policies and commercial when selling or buying a product.</p> <p>\u2015w3.org - QA Framework: Specification Guidelines</p>"},{"location":"fep/d9ad/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/db0e/","title":"Index","text":"<p>hide: - navigation description: \"This proposal addresses the problem of authenticating access to the content of non-public groups. It is mostly intended to supplement FEP-400e. Only the server that hosts the Group actor knows for sure who can and can not access the content in the group. However, due to each object being hosted on the server of the actor that created it, it is not ordinarily possible for those other servers to restrict access to that object only to those actors who have the permission to see it. This FEP defines an authentication mechanism, \"actor tokens\", that allows an actor to issue tokens that serve as a temporary proof of group membership for other servers.\"</p>"},{"location":"fep/db0e/#fep-db0e-authentication-mechanism-for-non-public-groups","title":"FEP-db0e: Authentication mechanism for non-public groups","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me <code>DRAFT</code> 2024-05-03 #313 - codeberg"},{"location":"fep/db0e/#summary","title":"Summary","text":"<p>This proposal addresses the problem of authenticating access to the content of non-public groups. It is mostly intended to supplement FEP-400e.</p> <p>Only the server that hosts the <code>Group</code> actor knows for sure who can and can not access the content in the group. However, due to each object being hosted on the server of the actor that created it, it is not ordinarily possible for those other servers to restrict access to that object only to those actors who have the permission to see it.</p> <p>This FEP defines an authentication mechanism, \"actor tokens\", that allows an actor to issue tokens that serve as a temporary proof of group membership for other servers.</p>"},{"location":"fep/db0e/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].</p>"},{"location":"fep/db0e/#fetching-content-from-the-server-that-hosts-the-group","title":"Fetching content from the server that hosts the group","text":"<p>To fetch an object from the server that hosts the group (including the <code>Group</code> actor itself for private groups), the requesting server MUST sign the GET request with an HTTP signature using the key of any of its actors. It is RECOMMENDED to use a server-wide service actor for this purpose, e.g. <code>/activitypub/serviceActor</code> in Smithereen. The rationale for this is that most ActivityPub servers only fetch and store a single copy of each remote object for all users to whom it may concern, and are responsible themselves for enforcing the visibility rules, if any, either way.</p>"},{"location":"fep/db0e/#using-actor-tokens","title":"Using actor tokens","text":"<p>The process of fetching an object from other server involves an actor token. An actor token is a cryptographically signed temporary proof of membership in a group. Since it would be impractical to provide a revocation mechanism, an actor token has a limited validity time in order to account for cases when someone has left a group or was removed from it.</p>"},{"location":"fep/db0e/#structure-of-the-actor-token","title":"Structure of the actor token","text":"<p>An actor token is a JSON object with the following REQUIRED fields:</p> <ul> <li><code>issuer</code>: ID of the actor that generated this token</li> <li><code>actor</code>: ID of the actor that the token is issued to (and must be presented with a valid HTTP signature of)</li> <li><code>issuedAt</code>: timestamp when the token was generated, ISO-8601 instant (same format as ActivityPub timestamps)</li> <li><code>validUntil</code>: timestamp when the token expires, ISO-8601 instant</li> <li><code>signatures</code>: array of signature objects, currently with only one possible, and REQUIRED, element defined:</li> <li><code>algorithm</code>: must be the string <code>rsa-sha256</code></li> <li><code>keyId</code>: key ID, same as in HTTP signatures (e.g. <code>https://example.com/groups/1#main-key</code>)</li> <li><code>signature</code>: the RSA-SHA256 signature itself encoded as base64, see below for details</li> </ul>"},{"location":"fep/db0e/#the-smactortoken-endpoint","title":"The <code>sm:actorToken</code> endpoint","text":"<p>Actors that are capable of issuing actor tokens have a <code>sm:actorToken</code> endpoint (where <code>sm</code> is an alias to JSON-LD namespace <code>http://smithereen.software/ns#</code>) in their <code>endpoints</code> object. This endpoint accepts signed GET requests and returns actor tokens.</p>"},{"location":"fep/db0e/#making-use-of-the-actor-token","title":"Making use of the actor token","text":"<p>To use an actor token when fetching an object, pass it as <code>Authorization: ActivityPubActorToken {...}</code> HTTP header.</p>"},{"location":"fep/db0e/#generation-of-the-source-string-for-signature","title":"Generation of the source string for signature","text":"<ol> <li>Iterate over the keys in the actor token JSON object, skipping <code>signature</code>, and transform them into the format <code>key: value</code>. Add these strings to an array.</li> <li>Sort the resulting array lexicographically.</li> <li>Join the strings with newline character (<code>\\n</code>, U+000A).</li> <li>Convert the resulting string to a UTF-8 byte array.</li> </ol>"},{"location":"fep/db0e/#generation-of-the-actor-token","title":"Generation of the actor token","text":"<ol> <li>Verify that the requesting actor, as per HTTP signature, has access to the group (there are members with the same domain). If it does not, you MUST return a 403 error and stop.</li> <li>Create a JSON object with the fields above (except <code>signature</code>). It is RECOMMENDED that the validity period is 30 minutes, and it MUST NOT exceed 2 hours.</li> <li>Generate a signature source string as above, sign it, and wrap the signature into an object with <code>signature</code>, <code>algorithm</code>, and <code>keyId</code> fields.</li> <li>Add the object as a single element in the <code>signatures</code> array.</li> <li>Return the resulting JSON object to the client.</li> </ol>"},{"location":"fep/db0e/#verification-of-the-actor-token","title":"Verification of the actor token","text":"<ol> <li>Check that the HTTP signature is valid, and that <code>actor</code> in the token object matches the actor ID from <code>keyId</code> in the HTTP signature. Otherwise, you MUST return a 403 and stop.</li> <li>In the <code>signatures</code> array, find an object that has <code>algorithm</code> set to <code>rsa-sha256</code> to get the <code>signature</code> value. If there isn't any, you MUST return a 403 and stop.</li> <li>Check the validity time: <code>issuedAt</code> MUST be in the past, <code>validUntil</code> MUST be in the future, and the difference between them MUST NOT exceed 2 hours. It is RECOMMENDED to apply some margin to these checks to account for imprecisely set clocks. Smithereen uses 5 minutes.</li> <li>Generate the signature source string as above and verify the signature.</li> <li>Check that the object the requester is accessing is, in fact, part of a collection owned by <code>issuer</code>.</li> <li>If all of the above checks pass, return the requested object. Otherwise, return a 403.</li> </ol>"},{"location":"fep/db0e/#example-of-an-actor-token-object","title":"Example of an actor token object","text":"<pre><code>{\n \"issuer\":\"https://friends.grishka.me/groups/75\",\n \"actor\":\"https://activitypub.academy/actor\",\n \"issuedAt\":\"2024-05-03T14:02:18.680404311Z\",\n \"validUntil\":\"2024-05-03T14:32:18.680404311Z\",\n \"signatures\":[\n {\n \"algorithm\":\"rsa-sha256\",\n \"keyId\":\"https://friends.grishka.me/groups/75#main-key\",\n \"signature\":\"w+W1nNV+XBvXi8sDEUZB7muWSSnv1mEE4tNZJqF5LeoxAstBMiBZi8dtHF+v+vXKVPWBAdZUKLS5CttmgZ4tvnvZAfsBztCjYLyiolVQ71IO2Jxlu00Xo9FDoSTRZ61tXdfWufuzs5lRjG3t+S1t1lLllBFmvPLg6BwmdEPvlZvPYnTJzwNY0ljOjickPqfyvdzIslmdYX6dPC0Ayyi028ZmR2SN1Vooc9vnUQ7GMPrlAZtmXgjCVGw5X/cKlAVvGECxRjJnkKEKiLp3lv/SM1UUhP3VRpBSFhXnRX/1QhTUaFV1MhrfDFgWGPg8ypIf6O/M52+iSpJyIOGepmjmow==\"\n }\n ]\n}\n</code></pre>"},{"location":"fep/db0e/#implementations","title":"Implementations","text":"<ul> <li>Smithereen</li> </ul>"},{"location":"fep/db0e/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Gregory Klyushnikov, Publicly-appendable ActivityPub collections, 2021</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels</li> </ul>"},{"location":"fep/db0e/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/dc88/","title":"FEP-dc88: Formatting Mathematics","text":"Authors Status Date received Tracking issue Discussions Repository Calvin Lee pounce@integraldoma.in <code>DRAFT</code> 2023-09-12 #161 - codeberg"},{"location":"fep/dc88/#summary","title":"Summary","text":"<p>This FEP recommends a method for formatting mathematics in ActivityPub post content in [MathML Core]. Furthermore, this FEP describes how to sanitize and convert such mathematics to plain text, if an implementation does not wish to support mathematical formatting.</p>"},{"location":"fep/dc88/#requirements","title":"Requirements","text":"<p>The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119]. \u201cThe implementation\u201d is to be interpreted as an ActivityPub conformant Client, ActivityPub conformant Server or ActivityPub conformant Federated Server as described in [ActivityPub] which wishes to produce or consume mathematically formatted content.</p>"},{"location":"fep/dc88/#history","title":"History","text":"<p>Written mathematics depends very heavily on a system of notation which has been continuously evolving over thousands of years. Despite its ubiquity, mathematical notation is anything but constant. Mathematicians rely heavily on complicated typesetting systems such as [LaTeX] to layout text in their desired fashion.</p> <p>Several fediverse instances, such as [Mathstodon] have emerged to host discussion related to mathematics. This is to no small part due to the difficulty of conveying and formatting mathematical text, and many provide their own typesetting implementation based on [LaTeX]. However, rendering TeX-like formats is expensive and fraught with issues due to security and implementation-specific quirks. This has resulted in multiple instances with incompatible TeX-like implementations.</p> <p>In recent years, [MathML Core] has become standardized by all major web browsers, and offers an alternative method to communicate mathematics between differing fediverse implementations that is compatible with the ActivityPub standard.</p>"},{"location":"fep/dc88/#formatting-mathematics","title":"Formatting Mathematics","text":"<p>The implementation MAY produce mathematical formatting in the <code>summary</code> or <code>content</code> properties of [ActivityStreams] objects, as defined in [Activity Vocabulary] if the <code>mediaType</code> is <code>text/HTML</code> (the default). This formatting MUST be placed within one or more top-level <code>&lt;math&gt;</code> elements, hereon referred to as 'a math element'.</p> <p>A math element MUST contain one <code>&lt;semantics&gt;</code> child element, and no other children. The <code>&lt;semantics&gt;</code> element MUST contain a [MathML Core] expression as its first child, and at least one <code>&lt;annotation&gt;</code> element. The <code>encoding</code> property of this <code>&lt;annotation&gt;</code> element SHOULD be <code>\"application/x-tex\"</code>, but MAY be <code>\"text/plain\"</code>, and MUST contain a plain-text description of the mathematics\u2014preferably in the authored format. The implementation MAY include additional <code>&lt;annotation&gt;</code> or <code>&lt;annotation-xml&gt;</code> elements with other semantic information.</p> <p>All elements contained within a math element MUST be MathML Core Elements as defined in [MathML Core], excluding those contained within <code>&lt;annotation&gt;</code> elements.</p>"},{"location":"fep/dc88/#sanitizing-mathematically-formatted-text","title":"Sanitizing Mathematically Formatted Text","text":"<p>The implementation SHOULD sanitize incoming mathematical formatting before displaying it to a user. There are two methods by which an implementation may sanitize incoming mathematical formatting.</p>"},{"location":"fep/dc88/#sanitizing-a-math-element","title":"Sanitizing a math element","text":"<p>The implementation MAY sanitize a math element before displaying it to a user. The implementation SHOULD remove all non MathML Core Elements as defined in [MathML Core]. The implementation MAY remove any attribute which does not contain semantic information (see [Semantic Attributes]). The implementation SHALL NOT remove any [Semantic Attributes] or MathML core Elements and instead should replace a math element with text.</p>"},{"location":"fep/dc88/#replacing-a-math-element-with-text","title":"Replacing a math element with text","text":"<p>The implementation MAY remove a math element completely, and replace it with text within the <code>&lt;annotation&gt;</code> element with encoding <code>\"application/x-tex\"</code> as described in [Formatting Mathematics] and SHOULD fall back to a <code>\"text/plain\"</code> annotation. If a math element is not formatted as described in [Formatting Mathematics], then the implementation MUST remove it completely.</p> <p>The implementation MAY surround the text from the <code>&lt;annotation&gt;</code> element with a pair of delimiters. For example, if a math element has the attribute <code>display=\"block\"</code>, it may choose the delimiters <code>$$</code> and <code>$$</code>, and if <code>displaystyle=\"inline\"</code> it may choose <code>$</code> and <code>$</code> to match the TeX typesetting system. </p>"},{"location":"fep/dc88/#examples","title":"Examples","text":"<pre><code>{\"@context\": [\"https://www.w3.org/ns/activitystreams\", {\"@language\": \"en\"}],\n \"type\": \"Note\",\n \"id\": \"http://postparty.example/p/2415\",\n \"content\": \"I have a truly marvelous proof that\n &lt;math&gt;\n &lt;semantics&gt;\n &lt;mrow&gt;\n &lt;msup&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msup&gt;\n &lt;mo&gt;+&lt;/mo&gt;\n &lt;msup&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msup&gt;\n &lt;mo&gt;\u2260&lt;/mo&gt;\n &lt;msup&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msup&gt;\n &lt;/mrow&gt;\n &lt;annotation encoding=\\\"application/x-tex\\\"&gt;x^n+y^n\\\\ne z^n&lt;/annotation&gt;\n &lt;/semantics&gt;\n &lt;/math&gt;\n which this note is too small to contain!\",\n \"source\": {\n \"content\": \"I have a truly marvelous proof that \\\\(x^n+y^n\\\\ne z^n\\\\) which this note is too small to contain!\",\n \"mediaType\": \"text/markdown+math\"}}\n</code></pre> <p>This object's source content represents a valid sanitization of its <code>content</code> field.</p>"},{"location":"fep/dc88/#semantic-attributes","title":"Semantic Attributes","text":"Element Attributes Values all <code>mathvariant</code> <code>normal</code> all <code>displaystyle</code>, <code>scriptlevel</code> all <code>&lt;math&gt;</code> <code>display</code> all <code>&lt;mfrac&gt;</code> <code>linethickness</code> <code>0</code>, <code>1</code> <code>&lt;mspace&gt;</code> <code>width</code>, <code>height</code>, <code>depth</code> all <code>&lt;mo&gt;</code> <code>form</code> <code>stretchy</code>, <code>symmetric</code>, <code>largeop</code>, <code>movablelimits</code>, <code>lspace</code>, <code>rspace</code>, <code>minsize</code>, all"},{"location":"fep/dc88/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[ActivityStreams] James M Snell, Evan Prodromou, ActivityStreams 2.0, 2017</li> <li>[Activity Vocabulary] James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>[LaTeX] LaTeX authors, The LaTeX Project</li> <li>[MathML Core] David Carlisle, Fr\u00e9d\u00e9ric Wang, MathML Core W3C Candidate Reccomendation Snapshot, 2023</li> <li>[Mathstodon] Mathstodon Admins, About Mathstodon, retrieved 2023</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> </ul>"},{"location":"fep/dc88/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/dd4b/","title":"FEP-dd4b: Quote Posts","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org <code>DRAFT</code> 2025-02-21 #511 Discussions codeberg"},{"location":"fep/dd4b/#summary","title":"Summary","text":"<p>This FEP describes the mechanism defined in Activity Streams 2.0 and the Activity Vocabulary for making quote posts, that is, Announce activities with additional commentary.</p>"},{"location":"fep/dd4b/#motivation","title":"Motivation","text":"<p>Redistributing content or activities created by other actors is a key activity on the social web. The Announce activity type is defined in Activity Streams 2.0 (\"AS2\") to represent this activity. <code>Announce</code> is used in ActivityPub to provide sharing functionality; see 7.11 Announce Activity (sharing).</p> <p>The Activity object type in AS2 inherits all the properties of the Object type. This means that all activity types have a rich set of properties for representation as first-class content on the social web. The <code>Announce</code> type is no exception.</p> <p>However, the ActivityPub specification does not describe how to use those properties to provide additional commentary, metadata, and files for the shared content. Called \"quote Tweets\", \"repost with comment\", or \"quote posts\", this kind of extended sharing gives additional context to shared objects.</p> <p>This FEP describes how to use the <code>content</code> property to provide this commentary, as well as other properties that may be useful in this context. It is applicable to uses of Activity Streams 2.0 in general; where it applies to ActivityPub, it is noted.</p>"},{"location":"fep/dd4b/#specification","title":"Specification","text":"<ul> <li>The <code>object</code> property of an <code>Announce</code> activity MUST be a reference to the shared content. It MAY be an AS2 <code>Object</code> or a <code>Link</code>, either as a JSON object or as a URL.</li> <li>The <code>content</code> property of an <code>Announce</code> activity MAY be used to provide additional commentary on the shared content.</li> <li>The <code>attachment</code> property of an <code>Announce</code> activity MAY be used to provide additional media content related to the shared content or the commentary.</li> <li>The <code>tag</code> property of an <code>Announce</code> activity MAY be used to provide additional metadata, such as Mention objects or Hashtag objects, about the shared content or the commentary.</li> <li>The <code>inReplyTo</code> property of an <code>Announce</code> activity MAY be used to connect an <code>Announce</code> activity to another object as a reply. The replied-to object MAY be the shared content, but this is unusual; it MAY also be part of a separate conversation.</li> <li>An <code>Announce</code> activity with an <code>inReplyTo</code> property SHOULD be included in the <code>replies</code> collection of the object being replied to.</li> <li><code>Announce</code> activities SHOULD be counted as part of the <code>shares</code> collection of the shared content, regardless of any additional properties, as defined in 7.11 Announce Activity (sharing).</li> </ul>"},{"location":"fep/dd4b/#examples","title":"Examples","text":""},{"location":"fep/dd4b/#basic-quote-post","title":"Basic Quote Post","text":"<p>This represents a simple quote post, where the actor is sharing a note from another actor with commentary.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/aaabbbccc\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"I think that this is a good point and should be shared.\"\n}\n</code></pre>"},{"location":"fep/dd4b/#quote-post-with-attachment","title":"Quote Post with Attachment","text":"<p>This represents a quote post with an attachment, where the actor is sharing a note from another actor with commentary and an image.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/dddeeefff\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"The author describes the rock formations of Crete; here's an example from my recent visit.\",\n \"attachment\": {\n \"type\": \"Link\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/1234.jpg\"\n }\n}\n</code></pre>"},{"location":"fep/dd4b/#quote-post-with-hashtag","title":"Quote Post with Hashtag","text":"<p>This represents a quote post with a hashtag, where the actor is sharing a note from another actor with commentary and a hashtag.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/ghhiijjkk\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"Great description of Cretan geology; saving it for my next trip. &lt;a href='https://example.com/tags/evanstriptocrete'&gt;#evanstriptocrete&lt;/a&gt;\",\n \"tag\": {\n \"type\": \"Hashtag\",\n \"href\": \"https://example.com/tags/evanstriptocrete\",\n \"name\": \"evanstriptocrete\"\n }\n}\n</code></pre>"},{"location":"fep/dd4b/#quote-post-with-mention","title":"Quote Post with Mention","text":"<p>This represents a quote post with a mention, where the actor is sharing a note from another actor with commentary and a mention.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/lllmmnnoo\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": [\"https://example.com/users/evan/followers\", \"https://example.com/users/jeff\"],\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"&lt;a href='https://example.com/users/jeff'&gt;@jeff&lt;/a&gt; you might like this Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/jeff\",\n \"name\": \"jeff\"\n }\n}\n</code></pre> <p>The author of the quoted material can also be mentioned.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/pppqqqrrr\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": [\"https://example.com/users/evan/followers\", \"https://example.com/users/franklin\"],\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"&lt;a href='https://example.com/users/franklin'&gt;@franklin&lt;/a&gt; wrote this great Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/franklin\",\n \"name\": \"franklin\"\n }\n}\n</code></pre>"},{"location":"fep/dd4b/#quote-post-as-reply","title":"Quote Post as Reply","text":"<p>A quote post can be used as a reply, often to share evidence or information from another source.</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/rrrsssttt\",\n \"type\": \"Note\",\n \"actor\": \"https://example.com/users/jeff\",\n \"to\": \"https://example.com/users/jeff/followers\",\n \"content\": \"Does anyone know where I can find a good article on Cretan geology?\",\n \"replies\": {\n \"id\": \"https://example.com/activities/rrrsssttt/replies\",\n \"type\": \"Collection\",\n \"totalItems\": 1,\n \"items\": [\n {\n \"id\": \"https://example.com/activities/lllmmnnoo\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"&lt;a href='https://example.com/users/jeff'&gt;@jeff&lt;/a&gt; you might like this Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/jeff\",\n \"name\": \"jeff\"\n },\n \"inReplyTo\": \"https://example.com/activities/rrrsssttt\"\n }\n ]\n }\n}\n</code></pre>"},{"location":"fep/dd4b/#user-interface-guidance","title":"User interface guidance","text":"<p>A common representation of quote posts is to include the <code>object</code> of the <code>Announce</code> as an embedded card or other representation, with the <code>content</code> of the <code>Announce</code> object as introductory text.</p> <p></p>"},{"location":"fep/dd4b/#security-considerations","title":"Security considerations","text":"<p>For all <code>Announce</code> activities, it's important to consider how much of the shared content to expose to the recipient of the <code>Announce</code> activity. Using an URL as a reference, or including a limited set of metadata as in the examples in this FEP, allows the publishing server for the shared content to control access to the content. See Inclusion of the shared object in the ActivityPub Primer for more information.</p>"},{"location":"fep/dd4b/#privacy-considerations","title":"Privacy considerations","text":""},{"location":"fep/dd4b/#consent-to-be-shared","title":"Consent to be shared","text":"<p>The creator of the quoted content can indicate their consent to be quoted using the <code>shares</code> collection. If the creator consents to being quoted, they can add the quote post to the <code>shares</code> collection. If they do not consent, they can omit the quote post or remove it.</p> <p>Servers SHOULD provide affordances to allow creators to add to, and remove from, the <code>shares</code> collection. Servers may provide opt-in consent, where <code>Announce</code> activities are not added to the <code>shares</code> collection unless the original author explicitly adds them. The server MAY provide opt-out consent, where <code>Announce</code> activities are added to the <code>shares</code> collection automatically on receipt, but can be removed by the original author.</p> <p>Consumers of quote posts can refer to the <code>shares</code> collection at any time to determine if the publisher has expressed consent. Consent may be extended or withdrawn at any time.</p> <p>Consumers SHOULD indicate if the original author has consented to the quote post, and MAY obscure or hide quote posts that do not have the original author's consent.</p>"},{"location":"fep/dd4b/#references","title":"References","text":"<ul> <li>James Snell, Evan Prodromou, et al. Activity Streams 2.0. W3C Recommendation. 8 May 2018.</li> <li>James Snell, Evan Prodromou, et al. Activity Streams Vocabulary. W3C Recommendation. 8 May 2018.</li> <li>Christine Lemmer-Webber, Jessica Tallon, et al. ActivityPub. W3C Recommendation. 23 January 2018.</li> <li>Evan Prodromou. ActivityPub Miscellaneous Terms. W3C Social Web Community Group Draft Report. 3 October 2024.</li> <li>W3C members and contributors. ActivityPub Primer. W3C Wiki. 2023 and later.</li> </ul>"},{"location":"fep/dd4b/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/e229/","title":"FEP-e229: Best practices for extensibility","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2024-04-02 #293 - codeberg"},{"location":"fep/e229/#summary","title":"Summary","text":"<p>Current popular implementations of ActivityPub do not handle extensibility very well. This FEP seeks to highlight some basic requirements for extensibility, and offer suggested advice to implementers who wish to avoid compatibility issues, particularly for LD-unaware consumers.</p>"},{"location":"fep/e229/#general-recommendations","title":"General recommendations","text":""},{"location":"fep/e229/#ld-unaware-consumers","title":"LD-unaware consumers","text":""},{"location":"fep/e229/#normalize-types-into-type-sets","title":"Normalize types into type-sets","text":"<p>It is an unfortunate and erroneous belief that objects in [AS2-Core] or [AP] can have only one type. This assumption breaks proper extensibility. Wherever a generic ActivityStreams consumer needs to know whether it is dealing with an [AS2-Vocab] type or [AS2-Core] mechanism like Collections, it cannot do so unless that type is present in the <code>type</code> set. However, extension vocabularies may need to declare additional types as interfaces that have been fulfilled by the given object. For this reason, LD-unaware consumers doing type checks need to take care to normalize <code>type</code> into a set, and check that their desired type is contained within that set.</p> <p>For example, <code>\"type\": \"Collection\"</code> would be normalized into <code>\"type\": [\"Collection\"]</code>.</p>"},{"location":"fep/e229/#ignore-json-ld-context-if-you-dont-understand-it","title":"Ignore JSON-LD context if you don't understand it","text":"<p>LD-unaware consumers MUST NOT attempt naive string comparison against the JSON-LD context declaration. There are several possible reasons why a received document might be valid AS2 but not declare a <code>@context</code>. One possibility is that the declared Content-Type is <code>application/activity+json</code> and the producer is LD-unaware. Another possibility is that the producer is LD-aware, but using a different context IRI that defines the same terms. Yet another possibility is that the producer is embedding inline term definitions. Regardless of the reason, either the consumer understands it or does not understand it.</p>"},{"location":"fep/e229/#ld-aware-consumers","title":"LD-aware consumers","text":""},{"location":"fep/e229/#assume-the-as2-context-if-none-is-provided","title":"Assume the AS2 context if none is provided","text":"<p>Given that including the [AS2-Context] is only a SHOULD and not a MUST, it is possible for some LD-unaware producers to serialize a document without a <code>@context</code> declaration. If the Content-Type is <code>application/activity+json</code>, then you MUST assume or inject the [AS2-Context] into the document, per [AS2-Core] section 2.1.</p>"},{"location":"fep/e229/#ld-unaware-producers","title":"LD-unaware producers","text":""},{"location":"fep/e229/#declare-iris-for-terms-that-are-expected-to-be-shared","title":"Declare IRIs for terms that are expected to be shared","text":"<p>By default, the [AS2-Context] document declares <code>@vocab</code> to be <code>_:</code>, meaning that the default vocabulary namespace is the blank namespace. Extension types and properties can be implemented as-is by LD-unaware producers, and the JSON-LD expansion algorithm will expand <code>term</code> to <code>_:term</code>. JSON-LD compaction will not strip these properties, but without the <code>@vocab: _:</code> declaration, they would be stripped. This may be sufficient for experimental or implementation-specific terms that are not expected to be used by anyone else, but it is bad practice for extensibility; the use of the blank namespace for properties is obsolete and may be removed in a future version of JSON-LD.</p>"},{"location":"fep/e229/#ld-aware-producers","title":"LD-aware producers","text":""},{"location":"fep/e229/#avoid-unnecessary-term-prefixes","title":"Avoid unnecessary term prefixes","text":"<p>Compact IRI prefixes can have multiple terms map to the same prefix, depending on which context the producer uses for compaction. For example, say we have a prefix for <code>http://example.com/</code>. You may encounter some documents with <code>example:term</code>, some documents with <code>ex:term</code>, some documents with <code>http://example.com/term</code>, and so on. LD-aware consumers can \"simply\" apply JSON-LD expansion to make all terms unambiguous, and then apply JSON-LD compaction against their local preferred context. LD-unaware consumers instead have to deal with unbounded possible equivalent terms, and will either have to add support for them on a case-by-case basis, or reinvent and reimplement JSON-LD expansion. This issue can be ameliorated by taking care to reuse existing conventional prefixes. An example of this is the [RDFa-Context] \"initial context\".</p>"},{"location":"fep/e229/#consider-producing-documents-compacted-against-only-the-as2-context-document","title":"Consider producing documents compacted against only the AS2 context document","text":"<p>Since JSON-LD expanded form is unambiguous, it may be a good idea to use it wherever possible. This slightly reduces human readability due to the additional verbosity, but it results in exactly one possible representation of your extension data. LD-unaware consumers will possibly have to learn the structure of JSON-LD expanded form. LD-aware consumers can \"simply\" re-compact the document against any additional contexts they understand.</p> <p>For example, consider the current use of \"profile fields\" prior to [FEP-fb2a] \"Actor metadata\". Ignoring that Mastodon currently uses <code>sc</code> as a term prefix for an incorrect definition, such a term prefix would be unnecessary if partially-uncompacted JSON-LD was used:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/~alyssa\",\n \"type\": \"Person\",\n \"name\": \"Alyssa P. Hacker\",\n \"attachment\": [\n {\n \"type\": \"http://schema.org/PropertyValue\",\n \"http://schema.org/name\": \"Pronouns\",\n \"http://schema.org/value\": \"she/her\"\n }\n ]\n}\n</code></pre> <p>In general, try to consider whether your target consumer is expected to understand the context you are declaring. For ActivityStreams-specific consumers, the [AS2-Context] is a requirement, and so it can be generally depended upon. Some specifications (such as [WebAnnotations]) may similarly require their own context to be declared, whereas some specifications may not require this. In general, it may be better to not require a context and to use only the partially-compacted form; this is because multiple context declarations makes it possible for some contexts to conflict, and the most recently-declared context will win out, leading to potentially undefined behavior. This behavior can be avoided by being more judicious about your context declarations and your choice of context documents to compact against.</p>"},{"location":"fep/e229/#declare-the-activitystreams-context-last-if-compacting-against-additional-contexts","title":"Declare the ActivityStreams context last, if compacting against additional contexts","text":"<p>Because [AP] and [AS2-Core] mandate compaction against the [AS2-Context] while also mandating that terms cannot be overridden, it is best to have the [AS2-Context] be the most-recently-declared context. For example:</p> <pre><code>{\n \"@context\": [,\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n // ...\n}\n</code></pre>"},{"location":"fep/e229/#how-to-define-an-extension","title":"How to define an extension","text":"<p>LD-unaware producers need to be aware of at least some of the ways that JSON-LD works; otherwise, their extensions will go into a blank namespace and may be stripped in future versions of JSON-LD. See the recommendations for LD-unaware producers above.</p>"},{"location":"fep/e229/#extension-property","title":"Extension property","text":"<p>Extension properties will primarily be one of two kinds:</p> <ul> <li>Those whose value is a literal value. In uncompacted form, these would use <code>@value</code>.</li> <li>Those whose value is a node on the graph. In uncompacted form, these would use <code>@id</code>.</li> </ul> <p>For LD-unaware producers, it is enough to produce JSON of the following form:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"http://example.com/valueProperty\": \"some string or number or boolean\",\n \"http://example.com/idProperty\": {\n \"@id\": \"https://example.com/some-resource\"\n }\n}\n</code></pre> <p>For LD-aware producers, first note that compacting against any additional context will make parsing harder for LD-unaware consumers, as described in above guidance for LD-aware producers -- any additional context you declare must be shared by the consumer, and the only guaranteed context is the ActivityStreams context. Still, for the benefit of LD-aware consumers, you SHOULD at least make a context document available for download out-of-band. One mechanism for doing so is described in [FEP-888d]. </p> <p>The previous example can be expressed as follows when you compact against additional context:</p> <pre><code>{\n \"@context\": [\n {\n \"valueProperty\": \"http://example.com/valueProperty\",\n \"idProperty\": {\n \"@id\": \"http://example.com/idProperty\",\n \"@type\": \"@id\"\n }\n },\n \"https://www.w3.org/ns/activitystreams\"\n ]\n}\n</code></pre> <p>More complete guidance on term definitions is available within [JSONLD11-TERMS].</p>"},{"location":"fep/e229/#extension-type","title":"Extension type","text":"<p>Extension types follow similar logic to extension properties with literal values.</p> <p>For LD-unaware producers, it is enough to use a full IRI as an extension type, although note that some vocabularies have been declared as term prefixes as part of the [AS2-Context], and thus MUST be serialized as compact IRIs using the given prefix. For example, <code>vcard</code> is declared by the [AS2-Context], but <code>schema</code> is not. Thus:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": [\"Person\", \"http://schema.org/Person\", \"vcard:Individual\"]\n}\n</code></pre> <p>LD-aware producers may wish to declare additional context, keeping in mind above guidance for LD-aware producers:</p> <pre><code>{\n \"@context\": [,\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": [\"Person\", \"schema:Person\", \"vcard:Individual\"]\n}\n</code></pre> <p>Alternatively, to avoid importing the entire Schema.org context:</p> <pre><code>{\n \"@context\": [\n {\n \"schema\": \"http://schema.org/\"\n },\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": [\"Person\", \"schema:Person\", \"vcard:Individual\"]\n}\n</code></pre>"},{"location":"fep/e229/#references","title":"References","text":"<ul> <li>[AP] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[AS2-Context] ActivityStreams 2.0 Terms, 2017</li> <li>[AS2-Core] James M Snell, Evan Prodromou, Activity Streams 2.0, 2017</li> <li>[AS2-Vocab] James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>[FEP-888d] a, FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces, 2023</li> <li>[FEP-fb2a] a, FEP-fb2a: Actor metadata, 2022</li> <li>[JSONLD11-TERMS] Gregg Kellogg, Pierre-Antoine Champin, Dave Longley, JSON-LD 1.1 Section 9.15.1 Expanded Term Definition, 2020</li> <li>[RDFa-Context] Ivan Herman, RDFa Core Initial Context, 2011</li> <li>[WebAnnotations] Robert Sanderson, Paolo Ciccarese, Benjamin Young, Web Annotations Data Model, 2017</li> </ul>"},{"location":"fep/e229/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/e232/","title":"FEP-e232: Object Links","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>FINAL</code> 2022-08-01 2023-12-03 #14 Discussions codeberg"},{"location":"fep/e232/#summary","title":"Summary","text":"<p>This document proposes a way to represent text-based links to ActivityPub objects which are similar to mentions. One example of such link is inline quote within the value of the <code>content</code> property, but this proposal is not limited to any particular use case.</p>"},{"location":"fep/e232/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/e232/#object-links","title":"Object links","text":"<p>It is expected that software will allow users to define object links using some kind of microsyntax, similar to <code>@mention</code> and <code>#hashtag</code> microsyntaxes. The exact way of defining object links may vary depending on the use case and is out of scope of this document.</p> <p>If an object's <code>name</code>, <code>summary</code>, or <code>content</code> has qualified links to other objects, that object SHOULD have the <code>tag</code> property, where each object link is represented as a <code>Link</code> object, as suggested by Activity Vocabulary. The properties of this <code>Link</code> object are:</p> <ul> <li><code>type</code> (REQUIRED): the type MUST be <code>Link</code> or a subtype.</li> <li><code>mediaType</code> (REQUIRED): the media type MUST be <code>application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"</code>. This specification only deals with ActivityPub objects but in practice the media type can be different and servers MAY accept object links which do not comply with the requirement. For example, a media type of <code>application/activity+json</code> SHOULD be treated as equivalent.</li> <li><code>href</code> (REQUIRED): the href property MUST contain the URI of the referenced object.</li> <li><code>name</code> (OPTIONAL): the <code>name</code> SHOULD match the microsyntax used in object's content.</li> <li><code>rel</code> (OPTIONAL): if relevant, the <code>rel</code> SHOULD specify how the link is related to the current resource. Using <code>rel</code> can provide additional purpose to object links by signaling specific intended use-cases.</li> </ul>"},{"location":"fep/e232/#examples","title":"Examples","text":"<p>(This section is non-normative.)</p> <p>A link to an issue in a bug tracker:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"The bug was reported in #1374\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://forge.example/tickets/1374\",\n \"name\": \"#1374\"\n }\n ]\n}\n</code></pre> <p>An inline quote:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a quote:&lt;br&gt;RE: https://server.example/objects/123\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://server.example/objects/123\",\n \"name\": \"RE: https://server.example/objects/123\"\n }\n ]\n}\n</code></pre> <p>Note that the <code>content</code> includes the <code>RE: &lt;url&gt;</code> microsyntax but consuming implementations are not required to parse that in order to make the appropriate associations.</p>"},{"location":"fep/e232/#implementations","title":"Implementations","text":"<ul> <li>(streams)</li> <li>FoundKey</li> <li>Mitra</li> <li>Pleroma (via MRF)</li> <li>Threads (announcement)</li> <li>Friendica</li> <li>Bridgy Fed</li> <li>Hollo</li> <li>Iceshrimp.NET</li> <li>Sharkey</li> <li>dotmakeup</li> <li>Hubzilla</li> </ul>"},{"location":"fep/e232/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> </ul>"},{"location":"fep/e232/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/e3e9/","title":"Index","text":"<p>hide: - navigation description: \" \"All problems in computer science can be solved by another level of indirection\" (the \"fundamental theorem of software engineering\") -- Attributed to Butler Lampson (src) This FEP introduces an ID scheme for ActivityPub objects and collections that has the following properties IDs remains stable across domain migrations. That is, allows the controller of the objects to change object hosting providers without changing the object IDs. IDs are regular HTTP(S) URLs that are resolvable via an HTTP GET request (provided the client allows following 302 redirects). The proposed mechanism identifies objects by adding query parameters to existing Actor profile URLs. ActivityPub clients wishing to fetch the objects make an HTTP GET request to this URL, as usual, carrying whatever authentication mechanism is required currently, and then follow the HTTP 302 status code redirect in the response to the current storage location of the object. Example Actor-Relative URL https //alice-personal-site.example/actor?service=storage&amp;relativeRef=/AP/objects/567 An AP client, encountering an Object ID with this URL makes an HTTP GET request just as it would with any other Object ID http GET /actor?service=storage&amp;relativeRef=/AP/objects/567 HTTP/1.1 Host alice-personal-site.example The server responds with a 302 redirect (which all HTTP clients are able to automatically follow) pointing to the current storage location of the object. For example http HTTP/1.1 302 Found Location https //storage-provider.example/users/1234/AP/objects/567 This redirection mechanism is enabled in all existing HTTP clients by default (see https //developer.mozilla.org/en-US/docs/Web/API/Request/redirect), and requires no additional re-tooling of ActivityPub client code.\"</p>"},{"location":"fep/e3e9/#fep-e3e9-actor-relative-urls","title":"FEP-e3e9: Actor-Relative URLs","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop, bumblefudge bumblefudge@learningproof.xyz <code>DRAFT</code> 2024-06-29 #346 - codeberg"},{"location":"fep/e3e9/#summary","title":"Summary","text":"<p>\"All problems in computer science can be solved by another level of indirection\" (the \"fundamental theorem of software engineering\")</p> <p>-- Attributed to: Butler Lampson (src)</p> <p>This FEP introduces an ID scheme for ActivityPub objects and collections that has the following properties:</p> <ul> <li>IDs remains stable across domain migrations. That is, allows the controller of the objects to change object hosting providers without changing the object IDs.</li> <li>IDs are regular HTTP(S) URLs that are resolvable via an HTTP <code>GET</code> request (provided the client allows following <code>302</code> redirects).</li> </ul> <p>The proposed mechanism identifies objects by adding query parameters to existing Actor profile URLs. ActivityPub clients wishing to fetch the objects make an HTTP <code>GET</code> request to this URL, as usual, carrying whatever authentication mechanism is required currently, and then follow the HTTP <code>302</code> status code redirect in the response to the current storage location of the object.</p> <p>Example Actor-Relative URL:</p> <p><code>https://alice-personal-site.example/actor?service=storage&amp;relativeRef=/AP/objects/567</code></p> <p>An AP client, encountering an Object ID with this URL makes an HTTP <code>GET</code> request just as it would with any other Object ID:</p> <pre><code>GET /actor?service=storage&amp;relativeRef=/AP/objects/567 HTTP/1.1\nHost: alice-personal-site.example\n</code></pre> <p>The server responds with a <code>302</code> redirect (which all HTTP clients are able to automatically follow) pointing to the current storage location of the object. For example:</p> <pre><code>HTTP/1.1 302 Found\nLocation: https://storage-provider.example/users/1234/AP/objects/567\n</code></pre> <p>This redirection mechanism is enabled in all existing HTTP clients by default (see https://developer.mozilla.org/en-US/docs/Web/API/Request/redirect), and requires no additional re-tooling of ActivityPub client code.</p>"},{"location":"fep/e3e9/#actor-relative-urls-for-objects-and-collections","title":"Actor-Relative URLs for Objects and Collections","text":"<p>On the Client side, the main change required is in the author/controller validation procedure (since retrieving the objects at Actor-Relative URLs requires no additional change beyond ensuring that following HTTP redirects is not disabled).</p> <p>On the Server side (specifically, the server hosting the Actor profile), two changes are required:</p> <ul> <li>(Data Model change) Adding a <code>service</code> section to the Actor profile, which is required for author/controller validation.</li> <li>(Protocol change) Enabling http <code>302</code> redirect responses when an Actor profile request is made that has the required query parameters (<code>service</code> and <code>relativeRef</code> params).</li> </ul> <p>In addition:</p> <ul> <li>(Not required but recommended) Implementing FEP-8b32: Object Integrity Proofs is recommended, since it helps with author/controller validation even in the case that the Actor profile host is down or otherwise unavailable.</li> </ul>"},{"location":"fep/e3e9/#validating-an-objects-authorcontroller","title":"Validating an Object's Author/Controller","text":"<p>Given the following example Actor profile:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example\"\n }],\n // Rest of the Actor profile goes here\n}\n</code></pre> <p>When fetching an ActivityPub Object or Collection identified by an Actor-Relative URL (that is, when the Object or Collection ID contains the URL query parameters <code>service</code> and <code>relativeRef</code>), a client MUST validate that the server hosting the Object is authorized by the Actor profile:</p> <ol> <li>The Client performs an HTTP <code>GET</code> request on the Object or Collection, as usual, including any currently required authorization headers.</li> <li>The client performing the <code>GET</code> request MUST be able to support HTTP redirection. For example, if using the WHATWG <code>fetch</code> API, the request's <code>redirect</code> property cannot be set to <code>error</code>.</li> <li>The Client follows the redirect and automatically fetches the object specified in the <code>Location</code> header of the <code>302</code> response (this behavior is the default in most HTTP clients).</li> <li>The Client extracts the current URL of the Object. This is the URL specified in the <code>Location</code> header of the redirect response; for example, if using the WHATWG <code>fetch</code> API, this is the last URL in the response's URL list, retrievable by accessing <code>response.url</code>.</li> <li>The Client retrieves the Actor profile corresponding to this Object's author/ controller (the <code>actor</code> or <code>attributedTo</code> property).</li> <li> <p>The Client extracts the value of the authorized storage endpoint from the profile:</p> <p>a. The Client checks to see if the Actor profile contains the <code>service</code> property. b. If the <code>service</code> property is found, the Client searches through the array of service endpoints until it finds a service endpoint with the relative id ending in <code>#storage</code> (note: this is what the <code>service=storage</code> query parameter refers to, in the Actor-Relative URL). The Client extracts the <code>serviceEndpoint</code> property of this service description object. This is the authorized storage endpoint. c. If no authorized storage endpoint is specified in the Actor profile (that is, if the Actor profile does not contain the <code>service</code> property, or if the <code>service</code> property is <code>null</code> or an empty array, or if the <code>service</code> array does not contain a service endpoint object with a relative <code>id</code> that ends in <code>#storage</code>, or if that service endpoint does not contain a <code>serviceEndpoint</code> property containing a URL), the Client SHOULD indicate to the user that the provenance of this Object cannot be determined, or that the storage location of the Object has not been authorized by the profile of the claimed author/controller.</p> </li> <li> <p>The Client MUST validate that the current URL of the object is authorized by the Actor's profile by checking that:</p> <p>a. The Object's currentURL starts with the value of the authorized storage endpoint. b. The Object's currentURL ends with the value of the <code>relativeRef</code> query parameter. c. For example, in JS pseudocode, using string concatenation: <code>response.url === (authorizedStorageEndpoint + query.relativeRef)</code> d. If these checks fail (if the current URL of the object is not equal to the string concatenation of the authorized storage endpoint and the <code>relativeRef</code> query parameter), the Client SHOULD indicate to the user that the provenance of this Object cannot be determined, or that the storage location of the Object has not been authorized by the profile of the claimed author/controller.</p> </li> </ol> <p>This validation procedure establishes a two-way link: from the Object to its author/controller Actor profile (via the Object's <code>actor</code> or <code>attributedTo</code> property), and from the Actor profile to the authorized storage service provider, at whose domain the Object is currently stored.</p>"},{"location":"fep/e3e9/#client-side-implementation","title":"Client-Side Implementation","text":"<p>An ActivityPub client conforming to this FEP:</p> <ol> <li>When encountering an Actor-Relative URL as an ID of an object, fetch it using the same HTTP <code>GET</code> mechanism that it currently does.</li> <li>Note: An Actor-Relative URL is defined as a URL containing the <code>service</code> and <code>relativeRef</code> query parameters.</li> <li>The client MUST follow the <code>302</code> redirect in the response.</li> <li>The client MUST perform the validation steps outlined in the Validating an Object's Author/Controller section above.</li> </ol>"},{"location":"fep/e3e9/#server-side-implementation","title":"Server-Side Implementation","text":"<p>On the server side (specifically, the server hosting the Actor profile), an ActivityPub server conforming to this FEP:</p> <ol> <li>For every request to the Actor profile object (for example, to <code>https://alice-personal-site.example/actor</code>), examine the HTTP QUERY parameters. If the <code>service</code> and <code>relativeRef</code> query parameters are present in the request, treat this as an Actor-Relative URL Request (by following the steps below).</li> <li> <p>Examine the Actor profile object for this request. If the profile does not contain a valid <code>serviceEndpoint</code> that corresponds to the <code>service</code> query parameter, the server MUST return a <code>422 Unprocessable Entity</code> HTTP status code error. To determine whether the profile contains a valid service endpoint:</p> </li> <li> <p>If the Actor profile does not contain a top level <code>service</code> property: INVALID</p> </li> <li>If the Actor has a <code>service</code> property, but its value is <code>null</code> or <code>[]</code>: INVALID</li> <li> <p>Search through the array of service endpoints (the value of the <code>service</code>) property, until you find a service object with the id that ends in <code>&lt;actor profile url&gt;#&lt;contents of the 'service' query param&gt;</code>. See sample Actor profile and request below. If no valid service endpoint is found: INVALID</p> </li> <li> <p>Assuming that a matching service endpoint is found, compose a current location URL from the <code>serviceEndpoint</code> contained in the profile concatenated with the contents of the <code>relativeRef</code> query parameter (see below for example).</p> </li> <li> <p>Return a <code>302 Found</code> HTTP status code response, and set the <code>Location</code> response header to the value of the current location URL composed in the previous step. Note: Servers SHOULD NOT return a <code>301</code> status response (a 301 response implies a permanent relocation, and the whole point of this FEP is that Actor-Relative URLs are changeable at any point). Similarly, servers SHOULD not return a <code>303 See Other</code> status response.</p> </li> </ol>"},{"location":"fep/e3e9/#example-server-side-request-and-response","title":"Example Server-Side Request and Response","text":"<p>Example request URL:</p> <pre><code>GET https://alice-personal-site.example/actor?service=storage&amp;relativeRef=/AP/objects/567\n</code></pre> <p>The query parameters would be parsed on the server side as something similar to:</p> <pre><code>{ \"service\": \"storage\", \"relativeRef\": \"/AP/objects/567\" }\n</code></pre> <p>Example Actor profile at that URL:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example\"\n }],\n // Rest of the Actor profile goes here\n}\n</code></pre> <p>Example current location URL (from concatenating the <code>serviceEndpoint</code> value with the <code>relativeRef</code> query parameter): <code>https://storage-provider.example/AP/objects/567</code></p> <p>Example response from the server:</p> <pre><code>HTTP/1.1 302 Found\nLocation: https://storage-provider.example/AP/objects/567\n</code></pre>"},{"location":"fep/e3e9/#object-storage-migration-using-actor-relative-urls","title":"Object Storage Migration Using Actor-Relative URLs","text":"<p>Actor-Relative URLs can be used as an option for portable Object and Collection IDs that remain unchanged even through migrating to a different object hosting provider (as long as the Actor ID remains constant).</p>"},{"location":"fep/e3e9/#example-storage-provider-migration","title":"Example Storage Provider Migration","text":"<p>Before migration, Alice uses the <code>https://old-storage-provider.example</code> as a storage provider for her AP objects. She makes sure <code>https://old-storage-provider.example</code> is specified as a service endpoint in her Actor profile.</p> <p><code>GET https://alice-personal-site.example/actor</code></p> <p>returns</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://old-storage-provider.example\"\n }],\n \"assertionMethod\": { /* \u2026 */ },\n // All the other profile properties \u2026\n}\n</code></pre> <p>Alice then creates a Note and stores it with the storage provider (making sure to add an Object Identity Proof). Example request:</p> <pre><code>POST /AP/objects/\nHost: old-storage-provider.example\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"attributedTo\": \"https://alice-personal-site.example/actor\",\n \"id\": \"https://alice-personal-site.example/actor?service=storage&amp;relativeRef=/AP/objects/567\"\n}\n</code></pre> <p>returns</p> <pre><code>HTTP 201 Created\nLocation: https://old-storage-provider.example/AP/objects/567\n</code></pre> <p>Note that this created Object can now be fetched at TWO different URLs:</p> <ol> <li>The direct URL (also called current location URL), <code>https://old-storage-provider.example/AP/objects/567</code></li> <li>The indirect Actor-Relative URL <code>https://alice-personal-site.example/actor?service=storage&amp;relativeRef=/AP/objects/567</code></li> </ol> <p>When it comes time to migrate to a different service provider, the new one being located at <code>https://brand-new-storage.example</code>, Alice performs the following steps.</p> <p>She updates her Actor profile service endpoint, to point to the new provider, so that it looks like this:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://brand-new-storage.example\"\n }],\n \"assertionMethod\": { /* \u2026 */ },\n // All the other profile properties \u2026\n}\n</code></pre> <p>Note that the <code>serviceEndpoint</code> is the only property in the Actor profile that has to change during migration.</p> <p>Alice then transfers her Object to the new provider (for this example, she'll be transferring the object individually, though in future FEPs, we expect specification of APIs to transfer all of the objects in one's storage):</p> <pre><code>POST /AP/objects/\nHost: brand-new-storage.example\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"attributedTo\": \"https://alice-personal-site.example/actor\",\n \"id\": \"https://alice-personal-site.example/actor?service=storage&amp;relativeRef=/AP/objects/567\"\n}\n</code></pre> <p>returns:</p> <pre><code>HTTP 201 Created\nLocation: https://brand-new-storage.example/AP/objects/567\n</code></pre> <p>Notice that the object being stored at the new provider is byte-for-byte identical to the object hosted at the old provider; its indirect <code>id</code> and contents do not change.</p> <p>Throughout this service provider migration, the external indirect <code>id</code> of the object does not change, for the purposes of all other AP mechanisms such as Inbox delivery, Likes and Reposts, and so on.</p>"},{"location":"fep/e3e9/#references","title":"References","text":"<ul> <li> <p>FEP-8b32: Object Integrity Proofs</p> </li> <li> <p>Christine Lemmer Webber, Jessica Tallon, [ActivityPub][AP], 2018</p> </li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> </ul>"},{"location":"fep/e3e9/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/e965/","title":"Index","text":"<p>hide: - navigation description: \"This FEP normatively specifies exactly one narrow step in almost all the migration user-stories defined in FEP-73cd User Migration Stories the updates to an Actor object made after a migration and/or deactivation event, and the Move activity which a source server propagates to inform followers of said Actor object update Our proposal clarifies semantics and behavior of the earlier FEP-7628 on which it strictly relies. It also proposes a simple, additive approach to use the above to express \"deactivated\" Actors by \"tombstoning\" their Actor objects, i.e. adding \"Tombstone\" to their type array (already afforded by the Activity Streams vocabulary). It also accomodates migrations to new forms of Actor object, such as \"Nomadic\"-style Portable Actors as described in FEP-ef61 Portable Objects and \"Independently-hosted\" Actor objects as described in FEP-7952, both for conforming and non-conforming consumers. As such, fully implementing all optional features of this proposal would require implementing FEP-521a Representing actor's public keys, which adds terms to the Actor object for publishing a verification method to verify assertions about the Actor independently of domain.\"</p>"},{"location":"fep/e965/#fep-e965-move-activity-for-migrations-and-announce-activity-for-tombstone-events","title":"FEP-e965: Move Activity for Migrations and Announce Activity for Tombstone Events","text":"Authors Status Date received Tracking issue Discussions Repository bumblefudge bumblefudge@learningproof.xyz, bengo @bengo@social.coop <code>DRAFT</code> 2024-07-05 #352 Discussions codeberg"},{"location":"fep/e965/#summary","title":"Summary","text":"<p>This FEP normatively specifies exactly one narrow step in almost all the migration user-stories defined in FEP-73cd: User Migration Stories:</p> <ul> <li>the updates to an Actor object made after a migration and/or deactivation event, and</li> <li>the Move activity which a source server propagates to inform followers of said Actor object update</li> </ul> <p>Our proposal clarifies semantics and behavior of the earlier FEP-7628 on which it strictly relies. It also proposes a simple, additive approach to use the above to express \"deactivated\" Actors by \"tombstoning\" their Actor objects, i.e. adding \"Tombstone\" to their <code>type</code> array (already afforded by the Activity Streams vocabulary). It also accomodates migrations to new forms of Actor object, such as \"Nomadic\"-style Portable Actors as described in FEP-ef61: Portable Objects and \"Independently-hosted\" Actor objects as described in FEP-7952, both for conforming and non-conforming consumers. As such, fully implementing all optional features of this proposal would require implementing FEP-521a: Representing actor's public keys, which adds terms to the Actor object for publishing a verification method to verify assertions about the Actor independently of domain.</p>"},{"location":"fep/e965/#current-approaches","title":"Current Approaches","text":"<p>Migration is currently supported in a somewhat ad hoc and piecemeal way, without harmonized expectations for how to update, announce, or interpret an Actor object after a migration, or after a deactiviation. Deactivation is sometimes referred to as a \"tombstone\" event, both in general usage in distributed systems and in the Activity Streams sense of the Tombstone object type. Adding the \"Tombstone\" member to the <code>type</code> array of an Actor object marks it as deactivated, as is already possible but more commonly implemented for deleted content or Activities than for Actors. We did not do a review of existing codebases, and the only public prior art that we're aware of is the retrospecification of current practice in FEP-7628: Move Actor.</p> <p>Beyond passively leaving a <code>Tombstone</code> hint for future queries, there have been no public proposals to our knowledge specifying how to actively express a given Actor's controller expressing an \"intent [for that Actor] to be forgotten\" to other servers where interactions with it may be stored, much less how to document that intent for legal purposes (which is explicitly out of scope here).</p>"},{"location":"fep/e965/#specification","title":"Specification","text":""},{"location":"fep/e965/#conformance","title":"Conformance","text":"<p>MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS. Similarly, the references to \"valid\" URIs throughout should be interpreted as conforming to both RFC-3987 and the [Activity Streams guidance on URI usage][Activity Streams URIs].</p> <p>Implementations SHOULD signal their support for this specification by including <code>\"https://w3id.org/fep/7628\"</code> in the <code>@context</code> array of their Actors, as this will clearly signal that the ABSENCE of a <code>movedTo</code> or <code>copiedTo</code> property indicates a currently-active Actor.</p> <p>Implementations MAY prove support for this specification by publishing a Conformance Report referencing the tests run. A specification for possible tests is provided in fep-e965-test-case.</p>"},{"location":"fep/e965/#actor-object-migration-and-deactivation-syntax","title":"Actor Object Migration and Deactivation Syntax","text":"<p>In the section, \"Move Activity\" of FEP-7628, two variations of the Mastodon-style <code>Move</code> Activity are defined, as well as semantics for the <code>movedTo</code> and <code>copiedTo</code> properties that MUST be applied to the Actor object on the source server of the activity:</p> <p>If previous primary actor is deactivated after migration, it MUST have movedTo property containing the ID of the new primary actor. [...] If previous primary actor is not deactivated, copiedTo property MUST be used.</p> <p>We add a few more explicit requirements:</p> <ul> <li><code>movedTo</code> MUST be a string or an array containing 1 string.</li> <li><code>copiedTo</code> MUST be a string or an array of strings.</li> <li>both <code>movedTo</code> and <code>copiedTo</code> MUST NOT be present in the same Actor object.</li> <li>Consuming implementations SHOULD treat an Actor with both properties as malformed.</li> </ul> <p>Many other current and future process and Activities could also be using the same semantics, including new \"styles\" or \"profiles\" of the many possible Actor objects allowed by the [ActivityPub] specification. These include Actors that do not change <code>id</code> after migrating, whether they conform to the Nomadic Actor extension, or to the separately-hosted Actor extension. If an account is moving to one of these configurations, the <code>movedTo</code> or <code>copiedTo</code> value will be the <code>id</code> and location of an <code>ap://</code> URL, or to a URL controlled by the Actor object's data subject, respectively.</p> <p>If the Actor object before the deactivation event included a public key for signing Activities expressed according to Client-Signing, and the same public key will NOT be published at the destination server for verifying post-migration Activities, then the source server MAY add an <code>expires</code> key and current-timestamp value to the key's <code>assertionMethod</code> object as described in section #2.3.1: Verification Methods of the W3C Data Integrity specification (to which FEP-521a normatively refers). Any consumer fetching this <code>assertionMethod</code> object for the purposes of verifying signatures according to the Data Integrity algorithm will thus invalidate signatures newer than the deactivation of that key.</p> <p>If an account has been deleted intentionally and consuming implementations are expected to recognize this, regardless of whether or not a <code>movedTo</code> value has been set, a server MUST include the string \"Tombstone\" in the <code>type</code> array of the deactivated or moved Actor object. Whether any other types are present is out of scope of this specification, to minimize side effects or complications for implementers.</p> <p>If an account whose Actor object containing a valid <code>copiedTo</code> value has been deleted intentionally, this MUST be removed before adding <code>Tombstone</code> to the Actor's <code>type</code>. A server performing this removal MAY move one valid URI from <code>copiedTo</code> to <code>movedTo</code> to aid discovery.</p> <p>If a user account is being deactivated but the source server wants to enable a future migration to be authenticated cryptographically, it MAY add to the Actor object a public key authenticated to the account (if not already present), as per to FEP-521a.</p> <p>An Actor object set to <code>Tombstone</code> SHOULD also set a top-level <code>as:deleted</code> property containing a current XSD <code>dateTime</code> as a courtesy to consumers.</p>"},{"location":"fep/e965/#announcing-a-migration-or-deactivation-event","title":"Announcing a Migration or Deactivation Event","text":"<p>After these changes have been made to the Actor object on the source server, it should be announced according to the type of changes made. If <code>movedTo</code> or <code>copiedTo</code> have been changed, then a FEP-73cd-compatible Move activity with the Actor as its object SHOULD be sent out, at least to all of that Actor's Followers. If the Actor is being retyped to <code>Tombstone</code>, an Announce activity with the Actor as object should be sent out, at least to all of that Actor's followers.</p> <p>If a user account is being deactivated but the source server wants to enable a future migration to be authenticated cryptographically, it is RECOMMENDED that the Announce activity be signed as per FEP-8b32. In this way, a later Move activity signed by the same key could, at least theoretically, be accepted from a new server which authenticates it by FEP-8b32.</p>"},{"location":"fep/e965/#interpreting-a-migrated-or-deactivated-actor-object","title":"Interpreting a Migrated or Deactivated Actor Object","text":"<p>In the section, \"<code>movedTo</code> and <code>copiedTo</code> properties\" of FEP-7628, the following general rule for all Actor objects is proposed:</p> <p>Publishers SHOULD NOT deliver activities to actor's inbox if movedTo property is present.</p> <p>We add the following behavioral expectations:</p> <ul> <li>Publishers SHOULD attempt to resolve the <code>movedTo</code> property to find out if it contains an inbox property.</li> <li>If an inbox is found, publishers SHOULD apply security, privacy, and federation policies on the domain at which it is hosted before taking any further action.</li> <li>If said inbox is permitted, publishers SHOULD attempt to deliver activities to the new inbox.</li> <li>If no <code>movedTo</code> value is set and one or more <code>copiedTo</code> values are set, publishers MAY resolve a <code>copiedTo</code> value to retrieve an <code>inbox</code> value and similarly process it.</li> <li>In the case of a valid <code>copiedTo</code> inbox and allowance by policy, delivery MAY attempt delivery to both Actor inbox and <code>copiedTo</code> inbox(es).</li> <li>Consuming implementations that keep redirect or alias records MAY persist the above-resolved relationship to avoid repeating this resolution in the future.</li> <li>If a <code>movedTo</code> value has been set to a valid URI, but <code>type</code> does not include \"Tombstone\", consuming implementations SHOULD treat it as a deactivated actor per Postel's Law.</li> </ul>"},{"location":"fep/e965/#behavior-for-unfamiliar-actor-uris","title":"Behavior for unfamiliar Actor URIs","text":"<p>There are caveats to interpreting these values if the <code>movedTo</code> or <code>copiedTo</code> properties contain unconventional URLs, such as those generated by an implementation extended by the above-mentioned FEPs:</p> <ul> <li>If the <code>movedTo</code> or <code>copiedTo</code> value is a valid URL beginning with the prefix <code>ap://</code> and the <code>@context</code> value includes the relevant extension properties, the destination server of the migration is likely implementing FEP-ef61 and may require custom resolution logic to return an Actor object.</li> <li>Similarly, if the <code>movedTo</code> or <code>copiedTo</code> value contains an actor-relative URL of the type defined in FEP-7952, it should resolve as usual if the server is live, as long as the querying implementation allows for the HTTP redirect and has no policy against (or hardcoded assumptions incompatible with) <code>inbox</code> values on different domains than <code>id</code> values for a given Actor.</li> <li>If an actor returned contains a non-empty <code>movedTo</code> or a non-empty <code>copiedTo</code> value in turn, this should in turn be dereferenced, barring domain-based policies to the contrary.</li> <li>If a querying implementation cannot resolve a value of these types or further indirections, it SHOULD consider them equivalent to URLs that return 404 and MAY log an error or warning to user or system log as appropriate.</li> <li>It is RECOMMENDED that unresolvable <code>movedTo</code> values be displayed to end-users as corrupted or incomplete moves, rather than as deactivated accounts.</li> </ul>"},{"location":"fep/e965/#interpreting-a-move-activity-or-an-announce-activity-of-a-deactivated-actor","title":"Interpreting a Move Activity or an Announce Activity of a Deactivated Actor","text":"<p>Servers receiving a Move or an Announce activity with an Actor as its object SHOULD NOT increment a <code>shares</code> collection. If a receiving server persists redirects or aliases to more smoothly remain aware of migrating or multi-homed users, or for other reasons, it MAY resolve the new Actor object and perform the above-described checks and MAY record said Actor update.</p>"},{"location":"fep/e965/#open-issues","title":"Open Issues","text":"<ol> <li>Are there others to which an Actor-update Move or Announce should be addressed beyond just the old Actor's Followers, somehow imported? is it worth calling out server-instance Actors, since they might also want to know for... idunno moderation reasons?</li> <li>Announce Activity example</li> <li>Address Actor Equivalence Attestation objects explicitly, or leave up to implementer imagination?</li> </ol>"},{"location":"fep/e965/#references","title":"References","text":"<ul> <li>FEP-521a: Representing actor's public keys</li> <li>FEP-73cd: Migration User Stories</li> <li>FEP-7628: Move Actor</li> <li>FEP-7952: Roadmap for Actor and Object Portability</li> <li>FEP-8b32: Object Integrity Proofs</li> <li>FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases</li> <li> <p>FEP-ef61: Portable Objects</p> </li> <li> <p>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</p> </li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>Dave Longley, Manu Sporny, Verifiable Credential Data Integrity 1.0, 2023</li> <li>Manu Sporny, Dave Longley, Markus Sabadell, Drummond Reed, Orie Steele, Christopher Allen, Decentralized Identifiers (DIDs) v1.0, 2022</li> <li>Dave Longley, Manu Sporny, Data Integrity EdDSA Cryptosuites v1.0, 2023</li> <li>A. Rundgren, B. Jordan, S. Erdtman, JSON Canonicalization Scheme (JCS), 2020</li> </ul>"},{"location":"fep/e965/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/e965/fep-e965-test-case/","title":"Actor Object Migration and Deactivation Syntax","text":""},{"location":"fep/e965/fep-e965-test-case/#background","title":"Background","text":"<p>This proposal extends and combines prior FEPs to define syntax and parsing rules for Actor objects which unambiguously express exactly one of the three following states:</p> <ol> <li>deactivation, OR</li> <li>migration to another URI, OR</li> <li>duplication at another URI.</li> </ol>"},{"location":"fep/e965/fep-e965-test-case/#about-this-test","title":"About this Test","text":"<p>This is a Test Case testing conformance with the two properties of an Actor object specified in FEP-e965.</p>"},{"location":"fep/e965/fep-e965-test-case/#test-subject","title":"Test Subject","text":"<p>The subject of this test is any data claiming to conform to the specification of an ActivityPub Actor Object and to the FEP-e965 extension thereof.</p> <p>This test is not directly applicable to an ActivityPub Server. An ActivityPub Server serves 0 or more Actor Objects. An ActivityPub Server for a big community might serve hundreds of ActivityPub Actor Objects. An ActivityPub Server for a single human may serve only that person's ActivityPub Actor Object.</p> <p>This test applies to Actor Objects, but not all Actor Objects are addressable by an HTTPS URL. The URI that addresses an Actor Object is not the same as the Actor Object. A given URL may resolve to different Actor Objects in different contexts, and a given Actor Object may not be universally addressable across context by any one URL.</p>"},{"location":"fep/e965/fep-e965-test-case/#inputs","title":"Inputs","text":"<p>This test requires the following inputs:</p> <ul> <li><code>actor</code> - the actor object under tested</li> <li>type: binary data</li> <li>constraints<ul> <li>will be interpreted as JSON.</li> <li>If not parseable as JSON, the test result MUST be <code>inapplicable</code>.</li> <li>dereferenced <code>@context</code> array should include both terms defined by FEP-7628 to signal support for this FEP</li> <li>if does not, the test result outcome MUST be <code>inapplicable</code>.</li> </ul> </li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#applicability","title":"Applicability","text":"<p>This test applies directly to the <code>actor</code> input.</p> <ul> <li>If <code>actor</code> is not a JSON object, the outcome MUST be <code>inapplicable</code>.</li> <li>input <code>actor</code> MUST have a <code>@context</code> property whose value is an Array containing the string <code>https://w3id.org/fep/7628</code>. If it does not, the outcome MUST be <code>inapplicable</code>.</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#test-targets","title":"Test Targets","text":"<ul> <li>input <code>actor</code> is the only test target</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#expectations","title":"Expectations","text":"<ol> <li><code>movedTo</code> - MUST be a URI OR an empty string, if present</li> <li><code>copiedTo</code> - MUST be a URI, if present</li> <li><code>movedTo</code> and <code>copiedTo</code> MUST NOT both be present</li> <li><code>actor</code> JSON's <code>@context</code> array SHOULD include <code>\"https://w3id.org/fep/7628\"</code> to signal conformance</li> </ol>"},{"location":"fep/e965/fep-e965-test-case/#assumptions","title":"Assumptions","text":""},{"location":"fep/e965/fep-e965-test-case/#1-how-to-determine-whether-an-actor-object-supports-fep-7628-semantics","title":"1. How to Determine Whether an Actor object supports FEP-7628 Semantics","text":"<p>For the purposes of determining whether the active status and migration history of a given Actor can be tested by this test case:</p> <ul> <li>the input is valid JSON</li> <li>the input, once parsed as JSON</li> <li>has an <code>@context</code> property</li> <li><code>@context</code> array includes the URL <code>\"https://w3id.org/fep/7628\"</code></li> </ul> <p>A warning should be returned if this value is not present.</p>"},{"location":"fep/e965/fep-e965-test-case/#2-property-value-expectations","title":"2. Property value expectations","text":"<p>For the purposes of determining the in/active status and migration history of a the target Actor:</p> <ul> <li>the input is valid JSON</li> <li><code>movedTo</code> and <code>copiedTo</code> MUST NOT both be present</li> <li><code>movedTo</code> can be a valid URI OR not present</li> <li><code>copiedTo</code> can be a valid URI or an array containing one or more valid URIs OR not present</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#property-value-evaluation-logic-pseudocode","title":"Property Value evaluation logic (Pseudocode)","text":"<ul> <li>the input is valid JSON</li> <li><code>movedTo</code> and <code>copiedTo</code> MUST NOT both be present</li> <li>//log (malformed actor - both movedTo and copiedTo present)</li> <li>outcome is <code>FAILED</code></li> <li>else if <code>movedTo</code> is present,</li> <li>value MUST be a valid URI<ul> <li>//log (actor has migrated to $movedTo)</li> <li>if URI is 404 //OPTIONAL CHECK</li> <li>log ($movedTo is not resolvable)</li> <li>outcome is <code>PASSED</code></li> </ul> </li> <li>else<ul> <li>outcome is <code>FAILED</code></li> </ul> </li> <li>else if <code>copiedTo</code> is present,</li> <li><code>type</code> MUST not include <code>\"Tombstone\"</code><ul> <li>outcome is <code>FAILED</code>; log (\"Cannot be tombstoned if copiedTo is set\")</li> </ul> </li> <li>each value MUST be a valid URI<ul> <li>if URI is 404 //OPTIONAL CHECK</li> <li>log ($copiedTo is not resolvable)</li> <li>outcome is <code>PASSED</code></li> </ul> </li> <li>else<ul> <li>outcome is <code>FAILED</code>; log (\"invalid values in <code>copiedTo</code>\")</li> </ul> </li> <li>else</li> <li>//log (actor is currently active and unlinked)</li> <li>outcome is <code>PASSED</code></li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#test-cases","title":"Test Cases","text":"<p>These are test cases for this test case, and can be used to verify that an implementation of this test case specification will be consistent with other implementations.</p>"},{"location":"fep/e965/fep-e965-test-case/#missing-context-values","title":"Missing <code>@context values</code>","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n</code></pre> <p>test result</p> <ul> <li>outcome: <code>inapplicable</code></li> <li>optional: warning (\"value https://w3id.org/fep/7628 not present in \"@context\" to signal conformance\")</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#both-movedto-and-copiedto-present","title":"both <code>movedTo</code> and <code>copiedTo</code> present","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://otherexample.com/newname\",\n \"copiedTo\": \"https://otherexample.com/thirdname\"\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>FAILED</code>, log (<code>movedTo</code> and <code>copiedTo</code> MUST NOT both be present)</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-array","title":"<code>movedTo</code> set to array","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": [\n \"https://example2.com/id\",\n \"https://example3.com/id\"\n ],\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>FAILED</code>, log (<code>movedTo</code> MUST be a functional property)</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#copiedto-contains-invalid-uri","title":"<code>copiedTo</code> contains invalid URI","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": [\n \"https://example2.com/id\",\n \"Tombstone\"\n ],\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>FAILED</code>, log (<code>movedTo</code> MUST be a functional property)</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-invalid-uri-1","title":"<code>movedTo</code> set to invalid URI #1","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"Tombstone\"\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>FAILED</code>, log (<code>movedTo</code> MUST be a URI)</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-invalid-uri-2","title":"<code>movedTo</code> set to invalid URI #2","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"\"\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>FAILED</code>, log (<code>movedTo</code> MUST be a URI)</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#valid-deactivated-actor","title":"Valid Deactivated Actor","text":"<p>input</p> <ul> <li><code>actor</code>:</li> </ul> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\",\"Tombstone\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>PASSED</code></li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#valid-migrated-actor","title":"Valid Migrated Actor","text":"<p>input</p> <ul> <li><code>actor</code>:</li> </ul> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\",\"Tombstone\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://actorname.otherexample.com\"\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>PASSED</code></li> <li>optional: check and log validity of actor referenced by that URI</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#valid-migrated-actor-missing-tombstone","title":"Valid Migrated Actor (Missing Tombstone)","text":"<p>This test vector does NOT conform to [FEP0-f2a] but DOES conform to the older FEP-7628. It is included to assist in testing the consumption of legacy migrated actors.</p> <p>input</p> <ul> <li><code>actor</code>:</li> </ul> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://actorname.otherexample.com\"\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>FAILED</code></li> <li>log (\"Missing Tombstone but backwards-compatible\")</li> <li>optional: check and log validity of actor referenced by that URI</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#valid-multi-homed-actor","title":"Valid Multi-homed Actor","text":"<p>input</p> <ul> <li><code>actor</code>:</li> </ul> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": \"https://personalarchive.otherexample.com\"\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>PASSED</code></li> <li>optional: check and log validity of actor referenced by that URI</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#valid-multi-homed-actor-copiedto-set-to-array","title":"Valid Multi-homed Actor (<code>copiedTo</code> set to array)","text":"<p>input</p> <p>actor:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": [\n \"https://example2.com/id\",\n \"https://example3.com/id\"\n ],\n}\n</code></pre> <p>test return</p> <ul> <li>outcome: <code>PASSED</code>, log (<code>copiedTo</code> contains multiple valid URIs)</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#glossary","title":"Glossary","text":""},{"location":"fep/e965/fep-e965-test-case/#outcome","title":"<code>outcome</code>","text":"<p>An outcome is a conclusion that comes from evaluating a test on a test subject. An outcome can be one of the three following types:</p> <ul> <li><code>inapplicable</code>: No part of the test subject matches the applicability</li> <li><code>passed</code>: A test target meets all expectations</li> <li><code>failed</code>: A test target does not meet all expectations</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#requirements-mapping","title":"Requirements Mapping","text":"<ul> <li>FEP requirement: - Actor Objects must express signing key as assertionMethod Multikey</li> <li>Required for Conformance to FEP-e965</li> <li>Outcome Mapping<ul> <li>when test target <code>assertionMethod</code> has outcome <code>passed</code>, requirement is satisfied</li> <li>when test target <code>assertionMethod</code> has outcome <code>failed</code>, requirement is not satisfied</li> <li>when test target <code>assertionMethod</code> has outcome <code>inapplicable</code>, further testing is needed to determine whether this requirement is satisfied</li> </ul> </li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#references","title":"References","text":""},{"location":"fep/e965/fep-e965-test-case/#change-log","title":"Change Log","text":"<ul> <li>2024-08-28T15:20:36Z - tweak links because FEP slug changed</li> <li>2024-06-28T15:20:36Z - first draft by bumblefudge</li> </ul>"},{"location":"fep/e965/fep-e965-test-case/#issues-list","title":"Issues List","text":"<ul> <li>Add a test vector with signed IdentityProof that verifies using current DI spec and dummy private key</li> </ul>"},{"location":"fep/eb22/","title":"FEP-eb22: Supported ActivityStreams types with NodeInfo","text":"Authors Status Date received Tracking issue Discussions Repository Manton Reece manton@micro.blog <code>DRAFT</code> 2024-07-25 #386 Discussions codeberg"},{"location":"fep/eb22/#summary","title":"Summary","text":"<p>Servers can advertise what features of the API they support, such as creating a poll or boosting a post. Clients can recognize if a server doesn't support a feature and hide it from the UI.</p>"},{"location":"fep/eb22/#background","title":"Background","text":"<p>As the fediverse grows, we'll naturally see a wide range of servers and clients, and not all of them will exactly match the features that popular clients like Mastodon support. To support a diverse mix of clients with different user experiences, clients and servers should communicate about which features should be shown in the client UI.</p> <p>Some services might not include public likes or polls, for example. Some might not include boosting. It would be confusing for a boost icon to show an error message when clicked if that feature was not available. Instead, the client should detect whether boosting is available, and if not simply hide the icon when connected to that server.</p>"},{"location":"fep/eb22/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC 2119.</p>"},{"location":"fep/eb22/#proposal","title":"Proposal","text":"<p>Document in NodeInfo the ActivityStreams types and properties that correspond to features the server supports. This information can be used by both ActivityPub clients as well as Mastodon API clients. We use ActivityStreams types because they are already part of a fediverse standard, rather than inventing new keys to describe existing features.</p> <p>Add a new top-level field <code>types</code> to NodeInfo with three fields, all of which are optional:</p> <ul> <li><code>activities</code>: The types of activities the server broadly supports.</li> <li><code>objects</code>: The types of objects that the server supports in the relevant activities.</li> <li><code>properties</code>: For each activity or object, what properties are allowed in that object.</li> </ul> <p>If no types are present in NodeInfo, a client should assume a server supports all types, just as most clients do today.</p> <p>If no objects are present for an activity, a client should assume a server supports all common objects. If a limited number of objects are present, a client should assume a server only supports those objects.</p> <p>If no properties are present for an object, a client should assume a server supports all common properties. If a limited number of properties are present, a client should assume a server only supports those properties.</p>"},{"location":"fep/eb22/#examples","title":"Examples","text":"<p>The following examples are simplified versions of what most servers should support. Most popular servers will support more activity and object types than this, but these examples keep the list short for readability. Other common activities include <code>Follow</code>, <code>Undo</code>, <code>Accept</code>, <code>Block</code>, and others as listed in the Activity Vocabulary specification.</p>"},{"location":"fep/eb22/#supporting-common-social-features-and-book-reviews-review","title":"Supporting common social features and book reviews (<code>Review</code>)","text":"<p>This example adds <code>Review</code>, an object used by BookWyrm for book reviews. Because only properties for <code>Review</code> are specified, a client can assume that all common objects and properties for the other activities are supported.</p> <pre><code>{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n \"Move\",\n \"Follow\"\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\",\n \"Review\"\n ],\n \"properties\": {\n \"Review\": [ \"title\", \"body\", \"rating\", \"inReplyToBook\" ]\n }\n }\n}\n</code></pre>"},{"location":"fep/eb22/#supporting-common-social-features-but-not-supporting-polls-question","title":"Supporting common social features, but not supporting polls (<code>Question</code>)","text":"<p>This example leaves off the <code>Question</code> type and the <code>oneOf</code> property used for polls.</p> <pre><code>{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\"\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\"\n ],\n \"properties\": {\n \"Note\": [ \"summary\", \"content\", \"published\", \"inReplyTo\" ],\n \"Article\": [ \"name\", \"content\", \"published\" ]\n }\n }\n}\n</code></pre>"},{"location":"fep/eb22/#supporting-common-social-features-including-polls-but-not-supporting-titled-posts-name","title":"Supporting common social features including polls, but not supporting titled posts (<code>name</code>)","text":"<p>This example includes only the <code>content</code> and <code>published</code> properties for <code>Article</code>, meaning the client should not prompt for a title for long-form articles.</p> <pre><code>{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\"\n ],\n \"properties\": {\n \"Question\": [ \"oneOf\" ],\n \"Note\": [ \"summary\", \"content\", \"published\", \"inReplyTo\" ],\n \"Article\": [ \"content\", \"published\" ]\n }\n }\n}\n</code></pre>"},{"location":"fep/eb22/#supporting-common-social-features-but-not-supporting-boosts-announce","title":"Supporting common social features, but not supporting boosts (<code>Announce</code>)","text":"<p>This example lists the bare minimum types, leaving off the explicit list of objects and properties. Clients should assume the server supports whatever properties are common for those types, such as <code>Note</code>, <code>Article</code>, and others.</p> <pre><code>{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Question\",\n \"Move\"\n ]\n }\n}\n</code></pre>"},{"location":"fep/eb22/#supporting-common-social-features-and-account-migration-move","title":"Supporting common social features and account migration (<code>Move</code>)","text":"<p>This example lists <code>object</code> and <code>target</code> fields that are used for account migration with the <code>Move</code> activity.</p> <pre><code>{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n \"Move\"\n ],\n \"properties\": {\n \"Move\": [ \"object\", \"target\" ]\n }\n }\n}\n</code></pre>"},{"location":"fep/eb22/#related-proposals","title":"Related proposals","text":"<p>There have been a few other attempts at addressing how to describe what a server supports:</p> <ul> <li>FEP-9fde: Mechanism for servers to expose supported operations: This extension also uses NodeInfo, but with reverse domain name identifiers and versions to document what operations a server supports. This ties features with the clients that first added support rather than more generally with the common activity types.</li> <li>FEP-6481: Specifying ActivityPub extension support with NodeInfo: This lists \"extensions\" to the common ActivityStreams types. BookWyrm, for example, adds a book \"Review\" type that could be documented in this way.</li> <li>Micropub: Query for Supported Vocabulary: From the IndieWeb community, the Micropub API is an interface for posting to a web site. This Micropub extension returns a list of post types that a server supports.</li> </ul>"},{"location":"fep/eb22/#references","title":"References","text":"<ul> <li>ActivityStreams 2.0</li> <li>Activity Vocabulary</li> <li>BookWyrm ActivityPub</li> </ul>"},{"location":"fep/eb22/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/eb48/","title":"FEP-eb48: Hashtags","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com <code>DRAFT</code> 2024-07-16 #373 Discussions codeberg"},{"location":"fep/eb48/#summary","title":"Summary","text":"<p>This proposal introduces a standardized method for identifying and displaying hashtags in posts across the Fediverse. The rules define what constitutes a hashtag and how it should be parsed and displayed, ensuring consistency and predictability across different platforms and clients.</p>"},{"location":"fep/eb48/#hashtag-rules","title":"Hashtag Rules","text":"<p>A string is considered a hashtag if it meets the following criteria:</p> <ol> <li>It starts with the <code>#</code> symbol.</li> <li>It is followed by one or more alphanumeric characters (letters A-Z, a-z, or digits 0-9).</li> <li>It may include underscores (<code>_</code>) but must not include any other special characters, spaces, or punctuation within the hashtag itself.</li> </ol>"},{"location":"fep/eb48/#examples","title":"Examples","text":"<p>The following examples illustrate how hashtags should be identified and highlighted:</p> <ul> <li><code>#hashtag</code></li> <li>\"<code>#hashtag</code>\"</li> <li>\" <code>#hashtag</code>\"</li> <li>(<code>#hashtag</code>/#hashtag)</li> <li>( <code>#hashtag</code>/#hashtag)</li> <li>( <code>#hashtag</code> /#hashtag)</li> <li>( <code>#hashtag</code> / <code>#hashtag</code>)</li> <li>-<code>#hashtag</code></li> <li>_<code>#hashtag</code></li> <li>!<code>#hashtag</code></li> <li>?<code>#hashtag</code></li> <li>@<code>#hashtag</code></li> <li>;<code>#hashtag</code></li> <li>,<code>#hashtag</code></li> <li>.'<code>#hashtag</code></li> <li>[<code>#hashtag</code></li> <li>&amp;<code>#hashtag</code></li> <li>^<code>#hashtag</code></li> </ul>"},{"location":"fep/eb48/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/ee3a/","title":"FEP-ee3a: Exif metadata support","text":"Authors Status Date received Tracking issue Discussions Repository Marcin Czachurski mczachurski@icloud.com <code>DRAFT</code> 2026-01-13 #752 Discussions codeberg"},{"location":"fep/ee3a/#summary","title":"Summary","text":"<p>The exchangeable image file format ([Exif]) family combines file formats such as JPEG, TIFF and WAV with structured metadata. [Exif] records camera (e.g., lens data, focal length, exposure time) and audio (e.g., channel count, sampling rate) recording parameters. The standard originally focused on photography but was expanded with version 2.1 to cover sound recordings. This proposal defines a Fediverse-wide mechanism for conveying [Exif] metadata using the [<code>exifData</code>] property from the [Schema.org] vocabulary.</p>"},{"location":"fep/ee3a/#motivation","title":"Motivation","text":"<p>To improve interoperability and promote consistent handling of attachment metadata across [ActivityPub] implementations, this FEP defines a vocabulary and processing rules for exposing Exif metadata associated with image (Image) and audio (Audio) attachments.</p>"},{"location":"fep/ee3a/#description","title":"Description","text":"<p>In this FEP, Exif metadata is represented as an array of [<code>PropertyValue</code>] items under the <code>exifData</code> property of an <code>Image</code> or <code>Audio</code> object. Each item MUST include an <code>@type</code> of <code>PropertyValue</code>, a name identifying the Exif tag and a value holding the tag's value. All Exif tags defined in the official Exif specification may be represented, but implementations are encouraged to support a recommended subset for interoperability.</p>"},{"location":"fep/ee3a/#privacy-considerations","title":"Privacy considerations","text":"<p>Exif metadata can reveal sensitive information such as a user's location, device details or recording environment. Geographic coordinates (latitude and longitude) can expose personal addresses or travel patterns. Producers MUST obtain user consent before including these fields and SHOULD clearly inform users about what information will be published. Consumers SHOULD treat geographic and device metadata as sensitive and avoid displaying it publicly without explicit permission.</p>"},{"location":"fep/ee3a/#definitions","title":"Definitions","text":""},{"location":"fep/ee3a/#exifdata-property","title":"<code>exifData</code> property","text":"<p><code>exifData</code> is an optional property attached to media objects of type <code>Image</code> or <code>Audio</code>. When present, its value MUST be an array of objects where each object: - has <code>@type</code> equal to \"PropertyValue\"; - has a <code>name</code> property containing the Exif field name (e.g., \"ExposureTime\", \"SamplesPerSec\"); - has a <code>value</code> property containing the corresponding value.</p> <p>Implementations MUST ignore unknown properties. Schema.org's definition of <code>exifData</code> permits the value to be a string, but this FEP standardizes on the structured array representation for interoperability.</p>"},{"location":"fep/ee3a/#property-names","title":"Property names","text":"<p>Property names MUST be strings corresponding to the Exif specification <code>Field name</code> (e.g., \"FNumber\", \"PhotographicSensitivity\", \"FocalLength\").</p>"},{"location":"fep/ee3a/#recommended-fields","title":"Recommended fields","text":"<p>Implementations MAY expose any Exif tag via <code>exifData</code> when present in the file and permitted by user consent. For interoperability, the following tag names and descriptions are recommended. Fields that apply only to images or only to audio are indicated in the second column.</p> Name (PropertyValue.name) Applies to Description <code>DateTime</code> image, audio Date and time when the media was created. Exif's <code>DateTime</code> tag uses the format \"YYYY:MM:DD HH:MM:SS\". The time is expressed in the photographer's local time zone. <code>ExposureTime</code> image Exposure time (e.g., <code>\"1/100\"</code> or <code>\"4\"</code>). <code>FNumber</code> image Aperture value expressed as an f-number (e.g., <code>\"f/1.8\"</code>). <code>Flash</code> image Description of flash usage (e.g., \"Flash did not fire.\"). <code>FocalLength</code> image Focal length reported by the camera. <code>FocalLengthIn35mmFilm</code> image 35 mm equivalent focal length. <code>GPSLatitude</code> image Exact latitude of the photo location. <code>GPSLatitudeRef</code> image Indicates whether the latitude of shooting location is north or south latitude. 'N' indicates north latitude, and 'S' is south latitude. <code>GPSLongitude</code> image Exact longitude of the photo location (requires user consent). <code>GPSLongitudeRef</code> image Indicates whether the longitude of shooting location is east or west longitude. 'E' indicates east longitude, and 'W' is west longitude. <code>LensMake</code> image Lens manufacturer. <code>LensModel</code> image Lens model name. <code>Make</code> image, audio Device manufacturer. <code>Model</code> image, audio Device model. <code>PhotographicSensitivity</code> image ISO sensitivity. <code>Software</code> image, audio Editing software or firmware used. <code>SamplesPerSec</code> audio Sampling frequency (e.g., <code>\"44100 Hz\"</code>). <code>AvgBytesPerSec</code> audio Bit depth per sample (e.g., <code>\"16 bit\"</code>). <code>Channels</code> audio Channel configuration (e.g., <code>\"mono\"</code>, <code>\"stereo\"</code>). <code>Compression</code> audio Compression scheme (e.g., <code>\"PCM\"</code>, <code>\"\u03bc-Law\"</code>, <code>\"ADPCM\"</code>). <p>Implementations MAY include additional tags and MUST ignore tags they do not understand. Property names SHOULD be consistent across implementations to facilitate display and filtering.</p>"},{"location":"fep/ee3a/#examples","title":"Examples","text":""},{"location":"fep/ee3a/#image-example","title":"Image example","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"schema\": \"https://schema.org/\"\n }\n ],\n \"type\": \"Note\",\n \"content\": \"Sunrise photo.\",\n \"attachment\": [{\n \"type\": \"Image\",\n \"url\": \"https://example.org/photos/123.jpg\",\n \"mediaType\": \"image/jpeg\",\n \"exifData\": [\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"DateTime\",\n \"value\": \"2025:03:30 06:30:00\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"ExposureTime\",\n \"value\": \"1/250\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"FNumber\",\n \"value\": \"f/5.6\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"FocalLength\",\n \"value\": \"70 mm\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"LensModel\",\n \"value\": \"Canon EF 70-200mm\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Make\",\n \"value\": \"Canon\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Model\",\n \"value\": \"EOS R5\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"PhotographicSensitivity\",\n \"value\": \"400\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Software\",\n \"value\": \"Darktable\"\n }\n ]\n }]\n}\n</code></pre>"},{"location":"fep/ee3a/#audio-example","title":"Audio example","text":"<pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"schema\": \"https://schema.org/\"\n }\n ],\n \"type\": \"Note\",\n \"content\": \"Field recording.\",\n \"attachment\": [{\n \"type\": \"Audio\",\n \"url\": \"https://example.org/audio/field.wav\",\n \"mediaType\": \"audio/wav\",\n \"exifData\": [\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"DateTime\",\n \"value\": \"2025-03-02T14:00:00Z\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"SamplesPerSec\",\n \"value\": \"48000 Hz\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"AvgBytesPerSec\",\n \"value\": \"24 bit\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Channels\",\n \"value\": \"stereo\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Compression\",\n \"value\": \"PCM\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Make\",\n \"value\": \"Sony\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Model\",\n \"value\": \"PCM-D10\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Software\",\n \"value\": \"Audacity\"\n }\n ]\n }]\n}\n</code></pre>"},{"location":"fep/ee3a/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[exifDate] Schema.org, exifData, 2025</li> <li>[PropertyValue] Schema.org, PropertyValue, 2025</li> <li>[Exif] Camera &amp; Imaging Products Association (CIPA), Exchangeable image file format for digital still cameras: Exif Version 3.0, 2024.</li> </ul>"},{"location":"fep/ee3a/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/ef61/","title":"FEP-ef61: Portable Objects","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2023-12-06 #209 Discussions codeberg"},{"location":"fep/ef61/#summary","title":"Summary","text":"<p>Portable ActivityPub objects with server-independent IDs.</p>"},{"location":"fep/ef61/#motivation","title":"Motivation","text":"<p>Usage of HTTP(S) URIs as identifiers has a major drawback: when the server disappears, everyone who uses it loses their identity and data.</p> <p>The proposed solution should satisfy the following constraints:</p> <ul> <li>User's identity and data should not be tied to a single server.</li> <li>Users should have a choice between full control over their identity and data, and delegation of control to a trusted party.</li> <li>Implementing the solution in existing software should be as simple as possible. Changes to ActivityPub data model should be kept to a minimum.</li> <li>The solution should be compatible with existing and emerging decentralized identity and storage systems.</li> <li>The solution should be transport-agnostic.</li> </ul>"},{"location":"fep/ef61/#history","title":"History","text":"<p>Nomadic identity mechanism makes identity independent from a server and was originally part of the Zot federation protocol.</p> <p>Streams (2021) made nomadic accounts available via the Nomad protocol, which supported ActivityStreams serialisation.</p> <p>FEP-c390 (2022) introduced a decentralized identity solution compatible with ActivityPub. It enabled permissionless migration of followers between servers, but didn't provide full data portability.</p>"},{"location":"fep/ef61/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/ef61/#identifiers","title":"Identifiers","text":"<p>An ActivityPub object can be made portable by using an identifier that is not tied to a single server. This proposal describes a new identifier type that has this property and is compatible with the ActivityPub specification.</p>"},{"location":"fep/ef61/#ap-uris","title":"'ap' URIs","text":"<p>'ap' URI is constructed according to the RFC-3986 specification, but with a Decentralized Identifier in place of the authority:</p> <pre><code>ap://did:example:abcdef/path/to/object?name=value#fragment-id\n\\_/ \\________________/ \\____________/ \\________/ \\_________/\n | | | | |\nscheme authority path query fragment\n</code></pre> <ul> <li>The URI scheme MUST be <code>ap</code>.</li> <li>The authority component MUST be a valid DID. Colons and other reserved characters MAY be percent-encoded.</li> <li>The path is REQUIRED. It MUST be treated as an opaque string.</li> <li>The query is OPTIONAL. To avoid future conflicts, implementers SHOULD NOT use parameter names that are not defined in this proposal.</li> <li>The fragment is OPTIONAL.</li> </ul> <p>Warning</p> <p>An 'ap' URI is not a valid RFC-3986 URI if reserved characters in the authority component are not percent-encoded. Nevertheless, this form is considered canonical.</p> <p>Note</p> <p>ActivityPub specification requires identifiers to have an authority \"belonging to that of their originating server\". The authority of 'ap' URI is a DID, which does not belong to any particular server.</p> <p>Warning</p> <p>The URI scheme might be changed to <code>ap+ef61</code> in a future version of this document, because these identifiers are not intended to be used for all ActivityPub objects, but only for portable ones.</p>"},{"location":"fep/ef61/#comparing-ap-uris","title":"Comparing 'ap' URIs","text":"<p>Two 'ap' URIs are equivalent when their canonical forms are identical.</p> <p>To produce a canonical 'ap' URI, the following operations MUST be performed:</p> <ul> <li>If the URI is a compatible identifier, convert it into an 'ap' URI.</li> <li>If the authority component is percent-encoded, decode it.</li> <li>Remove query component.</li> </ul>"},{"location":"fep/ef61/#did-methods","title":"DID methods","text":"<p>Implementers MUST support the did:key method. Other DID methods SHOULD NOT be used, as it might hinder interoperability.</p> <p>Note</p> <p>The following additional DID methods are being considered: did:web, did:dns, did:webvh (formerly <code>did:tdw</code>) and did:fedi.</p> <p>To maintain backward compatibility with existing ActivityPub implementations that rely on an origin-based security model and do not canonicalize IDs before comparison, implementers MUST generate DIDs using the base58-btc alphabet, even though the specification allows both base58-btc and base64url. Using both alphabets in practice could prevent such servers from recognizing that a post whose <code>attributedTo</code> value is <code>https://base64url.example/.well-known/apgateway/did:key:u7QGwDY2Tjn93PVFWWq02piP1NE9_XRlg-c8-jhJiDqKBDw/actor</code> belongs to <code>https://base58.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor</code>.</p> <p>DID documents SHOULD contain Ed25519 public keys represented as verification methods with <code>Multikey</code> type (as defined in the Controlled Identifiers specification).</p> <p>Any DID URL capabilities of a DID method MUST be ignored when working with 'ap' URIs.</p>"},{"location":"fep/ef61/#dereferencing-ap-uris","title":"Dereferencing 'ap' URIs","text":"<p>To dereference an 'ap' URI, the client MUST make HTTP GET request to a gateway endpoint at well-known location <code>/.well-known/apgateway</code>. The <code>ap://</code> prefix MUST be removed from the URI and the rest of it appended to a gateway URI. The client MUST specify an <code>Accept</code> header with the <code>application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"</code> media type.</p> <p>Example of a request to a gateway:</p> <pre><code>GET https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n</code></pre> <p>ActivityPub objects identified by 'ap' URIs can be stored on multiple servers simultaneously.</p> <p>If object identified by 'ap' URI is stored on the server, it MUST return a response with status <code>200 OK</code> containing the requested object. The value of a <code>Content-Type</code> header MUST be <code>application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"</code>.</p> <p>If object identified by 'ap' URI is not stored on the server, it MUST return <code>404 Not Found</code>.</p> <p>If an object is not public, the server MUST NOT serve it unless the request is signed by an actor who belongs to object's intended audience.</p> <p>Note</p> <p>This document describes web gateways, which use HTTP transport. However, the data model and authentication mechanism are transport-agnostic and other types of gateways could exist.</p>"},{"location":"fep/ef61/#authentication-and-authorization","title":"Authentication and authorization","text":"<p>Authentication and authorization are performed in accordance with FEP-fe34 origin-based security model, but with two important differences:</p> <ul> <li>Cryptographic origins are used. They are similar to web origins described in RFC-6454 but computed using a different algorithm.</li> <li>Authentication via fetching from an origin is not possible. The main authentication method is verification of a signature.</li> </ul> <p>The origin of an 'ap' URI is identical to the authority component of its canonical form (i.e. it is a DID without percent encoding).</p> <p>The origin of a DID URL is identical to its <code>did</code> component.</p> <p>Actors, activities and objects identified by 'ap' URIs MUST contain FEP-8b32 integrity proofs. Collections identified by 'ap' URIs MAY contain integrity proofs. If collection doesn't contain an integrity proof, another authentication method MUST be used.</p> <p>The value of <code>verificationMethod</code> property of the proof MUST be a DID URL where the DID matches the authority component of the 'ap' URI.</p> <p>Note</p> <p>This document uses terms \"actor\", \"activity\", \"collection\" and \"object\" according to the classification given in FEP-2277.</p>"},{"location":"fep/ef61/#portable-actors","title":"Portable actors","text":"<p>One DID subject can control multiple actors (which are differentiated by the path component of an 'ap' URI).</p> <p>An actor object identified by 'ap' URI MUST have a <code>gateways</code> property containing an ordered list of gateways where the latest version of that actor object can be retrieved. Each item in the list MUST be an HTTP(S) URI with empty path, query and fragment components. The list MUST contain at least one item.</p> <p>Gateways are expected to be the same for all actors under a DID authority and MAY be also specified in the DID document as services.</p> <p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/ef61\"\n ],\n \"type\": \"Person\",\n \"id\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor\",\n \"inbox\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/inbox\",\n \"outbox\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/outbox\",\n \"gateways\": [\n \"https://server1.example\",\n \"https://server2.example\"\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2#z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n}\n</code></pre>"},{"location":"fep/ef61/#location-hints","title":"Location hints","text":"<p>When ActivityPub object containing a reference to another actor is being constructed, implementations SHOULD provide a list of gateways where specified actor object can be retrieved. This list MAY be provided using the <code>gateways</code> query parameter. Each gateway address MUST be URI-endcoded, and if multiple addresses are present they MUST be separated by commas.</p> <p>Example:</p> <pre><code>ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor?gateways=https%3A%2F%2Fserver1.example,https%3A%2F%2Fserver2.example\n</code></pre> <p>This URI indicates that object can be retrieved from two gateways:</p> <ul> <li><code>https://server1.example</code></li> <li><code>https://server2.example</code></li> </ul> <p>Important</p> <p>When comparing 'ap' URIs, query parameters are discarded and canonical URIs are used.</p>"},{"location":"fep/ef61/#inboxes-and-outboxes","title":"Inboxes and outboxes","text":"<p>Portable inboxes and outboxes function as described in the ActivityPub specification. These endpoints are also used to synchronize activities between gateways used by an actor.</p> <p>Servers specified in the <code>gateways</code> property of an actor object MUST accept POST requests targeting its inbox collection.</p> <p>Example:</p> <pre><code>POST https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/inbox\n</code></pre> <p>Activities delivered to an inbox might be not portable. If the server does not accept deliveries on behalf of an actor, it MUST return <code>404 Not Found</code>.</p> <p>Upon receiving an activity in actor's inbox, the server SHOULD forward it to inboxes located on other servers where actor's data is stored. An activity MUST NOT be forwarded from inbox more than once.</p> <p>Servers specified in the <code>gateways</code> property of an actor object MAY accept POST requests targeting its outbox collection. Such servers MUST implement FEP-ae97.</p> <p>Activities delivered to an outbox are performed by a portable actor and therefore MUST be portable too. The server MUST verify them as described in section Authentication and authorization and then process them as described in FEP-ae97. Clients MAY deliver activities to multiple outboxes, located on different servers.</p> <p>Upon receiving an activity in actor's outbox, the server SHOULD forward it to outboxes located on other servers where actor's data is stored. An activity MUST NOT be forwarded from outbox more than once.</p>"},{"location":"fep/ef61/#collections","title":"Collections","text":"<p>Collections identified by 'ap' URIs (including inbox and outbox collections) MAY be served without FEP-8b32 integrity proofs. Consuming implementations MUST NOT process unsecured collections attributed to a portable actor if they were retrieved from a server that is not listed in the <code>gateways</code> array of the actor document.</p> <p>Portable collections can be filtered and paginated in a same way as non-portable collections. A gateway MUST remove the integrity proof when generating a view of a collection created by a FEP-ae97 client.</p>"},{"location":"fep/ef61/#portable-objects","title":"Portable objects","text":"<p>Example:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/ef61\"\n ],\n \"type\": \"Note\",\n \"id\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/objects/dc505858-08ec-4a80-81dd-e6670fd8c55f\",\n \"attributedTo\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor?gateways=https%3A%2F%2Fserver1.example,https%3A%2F%2Fserver2.example\",\n \"inReplyTo\": \"ap://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/objects/f66a006b-fe66-4ca6-9a4c-b292e33712ec\",\n \"content\": \"Hello!\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"url\": \"hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\",\n \"mediaType\": \"image/png\",\n \"digestMultibase\": \"zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\"\n }\n ],\n \"to\": [\n \"ap://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/actor\"\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2#z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n}\n</code></pre>"},{"location":"fep/ef61/#media","title":"Media","text":"<p>Integrity of an external resource is attested with a digest. When a portable object contains a reference to an external resource (such as image), it MUST also contain a <code>digestMultibase</code> property representing the integrity digest of that resource. The digest MUST be computed using the SHA-256 algorithm.</p> <p>The URI of an external resource SHOULD be a hashlink.</p> <p>Example of an <code>Image</code> attachment:</p> <pre><code>{\n \"type\": \"Image\",\n \"url\": \"hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\",\n \"mediaType\": \"image/png\",\n \"digestMultibase\": \"zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\"\n}\n</code></pre> <p>After retrieving a resource, the client MUST verify its integrity by computing its digest and comparing the result with the value encoded in <code>digestMultibase</code> property.</p> <p>Resources attached to portable objects using hashlinks can be stored by gateways. To retrieve a resource from a gateway, the client MUST make an HTTP GET request to the gateway endpoint at well-known location <code>/.well-known/apgateway</code>. The value of a hashlink URI MUST be appended to the gateway base URI.</p> <p>Example of a request:</p> <pre><code>GET https://social.example/.well-known/apgateway/hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\n</code></pre>"},{"location":"fep/ef61/#compatibility","title":"Compatibility","text":""},{"location":"fep/ef61/#identifiers_1","title":"Identifiers","text":"<p>'ap' URIs might not be compatible with existing ActivityPub implementations. To provide backward compatibility, gateway-based HTTP(S) URIs of objects can be used instead of their canonical identifiers:</p> <pre><code>https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n</code></pre> <p>Publishers MUST use the first gateway from actor's <code>gateways</code> list when constructing compatible identifiers. Consuming implementations that support 'ap' URIs MUST remove the part of the URI preceding <code>did:</code> and re-construct the canonical identifier. Objects with the same canonical identifier, but located on different gateways MUST be treated as different instances of the same object.</p> <p>Publishers MUST NOT add the <code>gateways</code> query parameter to object IDs if compatible identifiers are used.</p> <p>When HTTP signatures are necessary for communicating with other servers, each gateway that makes requests on behalf of an actor SHOULD use a separate secret key. The corresponding public keys MUST be added to actor document using the <code>assertionMethod</code> property as described in FEP-521a.</p>"},{"location":"fep/ef61/#webfinger-addresses","title":"WebFinger addresses","text":"<p>WebFinger address of a portable actor can be obtained by the reverse discovery algorithm described in section 2.2 of ActivityPub and WebFinger report, but instead of taking the hostname from the identifier, it MUST be taken from the first gateway in actor's <code>gateways</code> array.</p>"},{"location":"fep/ef61/#discussion","title":"Discussion","text":"<p>(This section is non-normative.)</p>"},{"location":"fep/ef61/#discovering-locations","title":"Discovering locations","text":""},{"location":"fep/ef61/#arbitrary-paths","title":"Arbitrary paths","text":"<p>The <code>gateways</code> array can contain HTTP(S) URIs with a path component, thus enabling discovery based on the \"follow your nose\" principle, as opposed to discovery based on a well-known location.</p> <p>Example of a compatible object ID if the gateway endpoint is <code>https://social.example/ap</code>:</p> <pre><code>https://social.example/ap/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n</code></pre>"},{"location":"fep/ef61/#alternatives-to-gateways-property","title":"Alternatives to <code>gateways</code> property","text":"<p>This proposal makes use of the <code>gateways</code> property, but the following alternatives are being considered:</p> <ul> <li><code>gateways</code> property in actor's <code>endpoints</code> mapping</li> <li><code>aliases</code> and <code>sameAs</code> (containing HTTP(S) URIs of objects)</li> <li><code>alsoKnownAs</code> (used for account migrations, so the usage of this property may cause issues)</li> <li><code>url</code> (with <code>alternate</code> relation type)</li> </ul>"},{"location":"fep/ef61/#did-services","title":"DID services","text":"<p>Instead of specifying gateways in actor document, they can be specified in DID document using DID services. This approach is not compatible with generative DID methods such as <code>did:key</code>, which might be necessary for some types of applications.</p>"},{"location":"fep/ef61/#media-access-control","title":"Media access control","text":"<p>The proposed approach to referencing media with hashlinks does not support access control: anybody who knows the hash can retrieve the file.</p> <p>To work around this limitation, a different kind of identifier can be used where digest is combined with the <code>ap://</code> identifier of its parent document. The gateway will not serve media unless parent document ID is provided, and will check whether request signer has permission to view the document and therefore the attached media.</p>"},{"location":"fep/ef61/#compatibility_1","title":"Compatibility","text":"<p>The following alternatives to gateway-based compatible IDs are being considered:</p> <ol> <li>Use regular HTTP(S) URIs but specify the canonical 'ap' URI using the <code>url</code> property (with <code>canonical</code> relation type, as proposed in FEP-fffd). For pointers to other objects such as <code>inReplyTo</code> property, an embedded object with <code>url</code> property can be used instead of a plain URI.</li> <li>Alter object ID depending on the capabilities of the peer (which can be reported by NodeInfo or some other mechanism).</li> </ol>"},{"location":"fep/ef61/#implementations","title":"Implementations","text":"<ul> <li>Streams</li> <li>Mitra (gateway only)</li> <li>fep-ae97-client (client)</li> <li>Forte</li> <li>tootik</li> </ul>"},{"location":"fep/ef61/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>T. Berners-Lee, R. Fielding, L. Masinter, Uniform Resource Identifier (URI): Generic Syntax, 2005</li> <li>Manu Sporny, Dave Longley, Markus Sabadello, Drummond Reed, Orie Steele, Christopher Allen, Decentralized Identifiers (DIDs) v1.0, 2022</li> <li>Dave Longley, Manu Sporny, Markus Sabadello, Drummond Reed, Orie Steele, Christopher Allen, Controlled Identifiers v1.0, 2025</li> <li>Dave Longley, Dmitri Zagidulin, Manu Sporny, The did:key Method v0.7, 2022</li> <li>M. Nottingham, Well-Known Uniform Resource Identifiers (URIs), 2019</li> <li>silverpill, FEP-8b32: Object Integrity Proofs, 2022</li> <li>silverpill, FEP-ae97: Client-side activity signing, 2023</li> <li>silverpill, FEP-fe34: Origin-based security model, 2024</li> <li>A. Barth, The Web Origin Concept, 2011</li> <li>silverpill, FEP-2277: ActivityPub core types, 2025</li> <li>M. Sporny, L. Rosenthol, Cryptographic Hyperlinks, 2021</li> <li>silverpill, FEP-521a: Representing actor's public keys, 2023</li> <li>a, Evan Prodromou, ActivityPub and WebFinger, 2024</li> <li>Adam R. Nelson, FEP-fffd: Proxy Objects, 2023</li> <li>Jonne Ha\u00df, NodeInfo, 2014</li> </ul>"},{"location":"fep/ef61/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/efda/","title":"FEP-efda: Followable objects","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2025-02-13 #492 Discussions codeberg"},{"location":"fep/efda/#summary","title":"Summary","text":"<p>ActivityStreams Vocabulary defines a <code>Follow</code> activity, and ActivityPub defines its side effects of manipulating a <code>followers</code> collection, but ActivityPub does not specify a full algorithm for how to follow something. This FEP aims to provide guidance on which objects can be followed:</p> <ul> <li>The object MUST have a <code>followers</code> collection present.</li> <li>If the object does not have an <code>inbox</code>, then you MAY recurse upwards through <code>attributedTo</code> until you find a resource with an <code>inbox</code>. The maximum recursion depth SHOULD be 1.</li> </ul> <p>A Follow activity can then be constructed for that <code>object</code> and delivered to the discovered <code>inbox</code>. Additional requirements for the structure of the Follow activity are out-of-scope.</p>"},{"location":"fep/efda/#requirements","title":"Requirements","text":"<p>In order to follow an object, we use a Follow activity. But in order to use this Follow activity, we need to know the following:</p> <p>1) What can be followed? 2) Where do we send the Follow for that <code>object</code>?</p> <p>At the time of writing, current software practices within the fediverse enforce a limit on what can be followed. For example, Mastodon currently limits follows to actors that can be mapped to their internal concept of \"accounts\", and these \"accounts\" are in turn limited to being any of the five \"actor types\" described in AS2-Vocab: <code>Person</code>, <code>Group</code>, <code>Organization</code>, <code>Application</code>, <code>Service</code>.</p> <p>Consequently, other software which does not have such a conceptual limit is forced to declare their actors as one of the five types, or else Mastodon users will not be able to follow their actors. ActivityPub says that there is no specified mapping between \"users\" and \"actors\", but Mastodon's interpretation of \"actors\" as \"users\" de facto disallows following anything but a strict subset of resources.</p> <p>We may therefore express Mastodon's requirements as follows:</p> <ul> <li>The actor MUST have a <code>type</code> of any of <code>Person</code>, <code>Group</code>, <code>Organization</code>, <code>Application</code>, <code>Service</code>.</li> <li>The actor MUST have an <code>inbox</code> where the <code>Follow</code> will be sent.</li> </ul> <p>A proposed modified requirement and algorithm are as follows:</p> <ul> <li>The object SHOULD have a <code>followers</code> collection present. (If it does not, then it is unknown whether it is followable.)</li> <li>If the object does not have an <code>inbox</code>, then you MAY recurse upwards through <code>attributedTo</code> until you find a resource with an <code>inbox</code>. The maximum recursion depth SHOULD be 1.</li> </ul>"},{"location":"fep/efda/#algorithm","title":"Algorithm","text":"<p>Given an object O and a recursion limit L, a general algorithm for following it can be expressed like so:</p> <p>Inputs: - O (Object): an object that may be followable - L (integer): a recursion limit.</p> <p>Outputs: - Error OBJECT_HAS_UNKNOWN_FOLLOWERS_COLLECTION - Error MAX_RECURSION_LIMIT - Success</p> <p>(1) Initialize a variable INBOX. (2) If <code>O.followers</code> is not present, optionally return an error OBJECT_HAS_UNKNOWN_FOLLOWERS_COLLECTION. (3) If <code>O.inbox</code> is present, set INBOX to the referenced IRI. (4) If INBOX is unset, then initialize a variable R whose initial value is O. (5) While INBOX is unset: (5.1) Check that L &gt; 0. If false, return an error MAX_RECURSION_LIMIT. (5.2) Dereference <code>R.attributedTo</code> and set the variable R to this referenced resource. (5.3) If <code>R.inbox</code> is present, set INBOX to the referenced IRI. (5.4) Set the variable L to the value L - 1. (6) Initialize a document ACTIVITY: (6.1) ...whose <code>actor</code> is yourself (6.2) ...whose <code>type</code> is <code>Follow</code> (6.3) ...whose <code>object</code> is O (7) Make an HTTP POST request whose target is INBOX and whose body is ACTIVITY.</p> <p>Additional requirements for the ACTIVITY can be defined at a protocol level, but the exact protocol and those requirements are out-of-scope for this FEP. The scope of this FEP is limited to determining which objects can be followed, and where to send the Follow.</p>"},{"location":"fep/efda/#references","title":"References","text":"<ul> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>trwnh, Unresolved issues surrounding Follow activities, 2019</li> </ul>"},{"location":"fep/efda/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/f06f/","title":"FEP-f06f: Object observers","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2025-02-18 #503 Discussions codeberg"},{"location":"fep/f06f/#summary","title":"Summary","text":"<p>Object observer is an ActivityPub actor that can be followed to receive object updates.</p> <p>This proposal is intended to complement FEP-bad1: Object history collection.</p>"},{"location":"fep/f06f/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/f06f/#observers","title":"Observers","text":"<p>Object observer is a followable actor. It doesn't perform any activities on its own, but activities that affect the observed object are forwarded to its followers. Only objects and collections can be observed.</p> <p>Object observer MUST have an <code>observerOf</code> property specifying the observed object, and it SHOULD have an <code>Application</code> type.</p> <p>Objects can specify their observers using the <code>observer</code> property.</p> <p>Object observer can be created with ActivityPub client by publishing a <code>Create</code> activity with user's actor as its <code>actor</code> and with observer actor as its <code>object</code>.</p> <p>Implementers MAY use a single cryptographic key for signing all HTTP requests made by observers on a server.</p>"},{"location":"fep/f06f/#use-case-subscribing-to-a-conversation","title":"Use case: subscribing to a conversation","text":"<p>When conversation is represented by a collection, a collection observer can be created to support conversation subscriptions.</p> <p>This actor can be attached to a collection via <code>observer</code> property, and can forward <code>Add</code> and <code>Remove</code> activities that modify it.</p>"},{"location":"fep/f06f/#non-forwarding-observers","title":"Non-forwarding observers","text":"<p>If forwarding is not desirable, object observers can use <code>Announce</code> activity to distribute observed activities.</p>"},{"location":"fep/f06f/#examples","title":"Examples","text":"<p>Example of an observer actor:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Application\",\n \"id\": \"https://server.example/objects/123456/observer\",\n \"inbox\": \"https://server.example/objects/123456/observer/inbox\",\n \"outbox\": \"https://server.example/objects/123456/observer/outbox\",\n \"observerOf\": \"https://server.example/objects/123456\"\n}\n</code></pre>"},{"location":"fep/f06f/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>a, FEP-bad1: Object history collection, 2023</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> </ul>"},{"location":"fep/f06f/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/f15d/","title":"FEP-f15d: Context Relocation and Removal","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org, Felix Ableitner me@nutomic.com, Rimu Atkinson rimu@rimu.geek.nz <code>DRAFT</code> 2026-01-12 #751 Discussions codeberg"},{"location":"fep/f15d/#summary","title":"Summary","text":"<p>Threaded applications often have the need to move and remove content between groups/communities for curation purposes (i.e. resolving miscategorization, spam, etc.)</p> <p>This is an extension of the Resolvable Contexts tree of FEPs.</p>"},{"location":"fep/f15d/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119].</p>"},{"location":"fep/f15d/#assumptions","title":"Assumptions","text":""},{"location":"fep/f15d/#nomenclature","title":"Nomenclature","text":"<p>The Forums and Threaded Discussions Task Force (ForumWG) has identified a common nomenclature when referring to organized objects in a threaded discussion model. The two terms used in this FEP are described below:</p>"},{"location":"fep/f15d/#context","title":"Context","text":"<p>A group of related objects (i.e. via reply-association) is referred to as a \"context\". Other terms for this concept would be \"topic\" or \"thread\".</p>"},{"location":"fep/f15d/#audience","title":"Audience","text":"<p>Contexts (see above) can be further grouped together into \"audiences\". Other terms for this concept would be \"community\", \"category\", or \"forum\". In ActivityPub, audiences usually take the form of a <code>Group</code> Actor type.</p>"},{"location":"fep/f15d/#referencing-threaded-objects-as-a-whole","title":"Referencing threaded objects as a whole","text":"<p>ActivityPub implementations differ in how they represent the aggregated collection of threaded objects.</p> <p>Some implementations represent these collections as a distinct abstraction (e.g. a context.)</p> <p>Others make no such distinction and represent them via the root-level object (e.g. link aggregators.)</p> <p>The resolvable contexts tree of FEPs requires that abstraction in order to communicate actions pertaining to it in an explicit manner.</p> <p>For more information, see FEP 7888.</p>"},{"location":"fep/f15d/#relocation","title":"Relocation","text":"<p>When a publisher relocates a context from one audience to another, a <code>Move</code> activity is published to both audiences' followers.</p> <pre><code>{\n @context: \"https://www.w3.org/ns/activitystreams\",\n id: \"https://example.social/context/123#activity/move/&lt;timestamp&gt;\",\n type: \"Move\",\n actor: \"https://example.social/uid/1\",\n to: [\"https://www.w3.org/ns/activitystreams#Public\"],\n cc: [\n \"https://example.social/audience/1/followers\",\n \"https://example.social/audience/2/followers\",\n ],\n object: \"https://example.social/context/1\",\n origin: \"https://example.social/audience/1\",\n target: \"https://example.social/audience/2\",\n}\n</code></pre> <p>Note the following properties:</p> <ul> <li><code>actor</code> is the user actor carrying out the action; typically a moderator (see Security Considerations)</li> <li><code>cc</code> contains the follower collections of both the outgoing and incoming audiences</li> <li><code>object</code> is the resolvable context</li> <li><code>origin</code> is the outgoing audience</li> <li><code>target</code> is the incoming audience</li> <li>Additional recipients can and should be added as necessary (e.g. originating author, announcers, etc.)</li> </ul>"},{"location":"fep/f15d/#audience-wrapper","title":"<code>Audience</code> wrapper","text":"<p>Implementations MAY wrap the <code>Move</code> activity in an <code>Announce</code> activity. Receivers are recommended to support both types.</p>"},{"location":"fep/f15d/#removal","title":"Removal","text":"<p>There are two ways to signal that a context is removed from an audience.</p> <p>Note</p> <p>A removal is functionally different from a deletion. You cannot delete content that is originally from another server, as you are not the owner, but you are able to remove it from yours.</p>"},{"location":"fep/f15d/#resolvable-context-variant","title":"Resolvable context variant","text":"<p>When a publisher removes a context from an audience, a <code>Remove</code> activity is published to the audience's followers.</p> <pre><code>{\n @context: \"https://www.w3.org/ns/activitystreams\",\n id: \"https://example.social/context/123#activity/remove/&lt;timestamp&gt;\",\n type: \"Remove\",\n actor: \"https://example.social/uid/1\",\n to: [\"https://www.w3.org/ns/activitystreams#Public\"],\n cc: [\"https://example.social/audience/1/followers\"],\n object: \"https://example.social/context/1\",\n target: \"https://example.social/audience/1\",\n}\n</code></pre>"},{"location":"fep/f15d/#group-federation-variant","title":"Group federation variant","text":"<p>This sub-section is non-normative</p> <p>Implementations may also handle the federated removal of objects by federating out a <code>Delete</code> referencing the root-level object. This activity is further wrapped in an <code>Announce</code> activity per FEP 1b12.</p> <pre><code>{\n \"actor\": \"http://ds9.lemmy.ml/u/lemmy_alpha\",\n \"to\": [\"https://www.w3.org/ns/activitystreams#Public\"],\n \"object\": \"http://ds9.lemmy.ml/post/1\",\n \"cc\": [\"http://enterprise.lemmy.ml/c/main\"],\n \"audience\": \"http://enterprise.lemmy.ml/u/main\",\n \"type\": \"Delete\",\n \"id\": \"http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12\"\n}\n</code></pre> <p>Example from Join Lemmy: Section 30 \"Federation\" \u2014 \"Delete Post or Comment\" </p>"},{"location":"fep/f15d/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/f15d/#same-origin-check","title":"Same-Origin Check","text":"<p>As per FEP fe34, the <code>Remove</code> and <code>Move</code> events outlined in this FEP are assumed to be authentic if the actor and the audience(s) in question (<code>target</code> and if applicable, <code>origin</code>) are same-origin.</p>"},{"location":"fep/f15d/#moderator-collection-check","title":"Moderator Collection Check","text":"<p>FEP-1b12: Group federation describes the use of an <code>OrderedCollection</code> referenced in an audience's <code>attributedTo</code> to represent an audience's moderators.</p> <p>If the actor of the <code>Move</code> or <code>Remove</code> activities is not same-origin to the audience(s) in question, this collection SHOULD be cross-referenced for authorization.</p>"},{"location":"fep/f15d/#implementors","title":"Implementors","text":"<ul> <li>NodeBB (as of v4.7.0)</li> <li>Piefed (as of v1.5)</li> </ul>"},{"location":"fep/f15d/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>a, FEP 7888: Demystifying the context property, 2023</li> <li>Julian Lam, ForumWG Nomenclature, 2024</li> <li>Felix Ableitner, FEP-1b12: Group federation, 2022</li> <li>Join Lemmy: Section 30 \"Federation\" \u2014 \"Delete Post or Comment\"</li> <li>silverpill, FEP fe34: Origin-based security model</li> </ul>"},{"location":"fep/f15d/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/f1d5/","title":"FEP-f1d5: NodeInfo in Fediverse Software","text":"Authors Status Date received Date final Tracking issue Discussions Repository CJ cjslep@gmail.com, silverpill @silverpill@mitra.social <code>FINAL</code> 2020-12-13 2023-06-02 #50 - codeberg"},{"location":"fep/f1d5/#summary","title":"Summary","text":"<p>NodeInfo is a protocol intended to standardize upon a way to provide server-level metadata to the public. This enables tools and clients to utilize this metadata to assess server health or facilitate end-users choices about servers and software to use on the Fediverse.</p>"},{"location":"fep/f1d5/#history","title":"History","text":"<p>NodeInfo was developed prior to the ActivityPub protocol targeted for use by diaspora, friendica, and redmatrix software. Some of the original protocols it encapsulated include diaspora, pumpio, and gnusocial.</p> <p>The NodeInfo specification is incredibly strict in its schema, often requiring regex-validation and a closed set of enumerated possible values. As an objection to this, the NodeInfo2 fork was created as a form of criticism by removing some validation of fields and with some logical restructuring of the metadata. Building off of NodeInfo and NodeInfo2, ServiceInfo was briefly explored.</p> <p>This FEP does not attempt to document the specific protocol details. For that, see the NodeInfo and NodeInfo2. It attempts to clarify the history and identify shortcomings with the current approaches, to bring context to developers of Fediverse Software.</p>"},{"location":"fep/f1d5/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119.</p> <p>Fediverse software SHOULD implement NodeInfo.</p>"},{"location":"fep/f1d5/#caveats","title":"Caveats","text":"<p>At the time of this FEP's writing, the current objections to the current state of NodeInfo that have been identified by the community are below. Note that any technical alternatives identified are meant to be illustrative and not prescriptive:</p> <ul> <li>The <code>software.name</code> regex is unnecessarily strict. For example, no uppercase letters, no spaces, no non-English-alphabet, and no special characters besides hyphen are permitted.</li> <li>The <code>software.version</code> field is required, which is unnecessarily strict. Forcibly requiring software to divulge version information is potentially a security issue.</li> <li>The <code>inbound</code> and <code>outbound</code> elements are specified as a closed set of enums instead of a simple string. Protocol versioning manifests as renaming, having to add a new enum, which results in unclear version management.</li> <li>The Fediverse software MUST have an <code>openRegistrations</code> concept due to it being required.</li> <li>Lacks an extendable method for identifying and versioning other features, such as HTTP Signatures, webfinger, or OAuth. Whereas the specification is very strict, the <code>metadata</code> is too lax.</li> <li>The <code>usage.users</code> is not denormalized, such that implementations can provide custom pairs of <code>(activity counts, time period in days)</code> that make sense for the software.</li> <li>The <code>usage.users</code> assumes that user identity is tied to a specific instance of running software. It is unclear how to count <code>total</code> users when user identity is: spread across multiple servers, spread across multiple groups, or present within multiple collections of users. Multiple software instances could each have a reasonable claim to counting the user as \"using\" their software, which globally results users being counted more than once.</li> <li>The <code>usage.users</code> activity counts likewise assume that user identity is tied to a specific instance of running software. For the same reasons above, where the <code>total</code> user counts may result in duplicate counts of the same user across all software running, the activity counts <code>activeHalfYear</code> and <code>activeMonth</code> may also result in a globally inflated count.</li> <li>The <code>activeHalfyear</code> and <code>activeMonth</code> are ill-named properties for describing the time periods of 180 days and 30 days, respectively. A \"half of one year\" is 180 days 0% of the time and roughly 182.5 days only 75% of the time. A month is 30 days only 33% of the time.</li> <li>The <code>localPosts</code> and <code>localComments</code> are not denormalized into pairs of <code>(kind, counts)</code> for software that, for example, hosts audio files, hosts videos, or software that does not have comments, or does not have posts.</li> <li>The <code>localPosts</code> and <code>localComments</code> are required, which is problematic for software that does not have comments, or does not have posts.</li> </ul>"},{"location":"fep/f1d5/#implementations","title":"Implementations","text":""},{"location":"fep/f1d5/#servers","title":"Servers","text":"<p>This list is not comprehensive:</p> <ul> <li>Mastodon</li> <li>Matrix</li> <li>Pleroma</li> <li>PeerTube</li> <li>WriteFreely</li> <li>Friendica</li> <li>Diaspora</li> <li>PixelFed</li> <li>Misskey</li> <li>Funkwhale</li> <li>Smithereen</li> <li>Plume</li> <li>GNU Social</li> <li>lemmy</li> <li>zap</li> <li>Socialhome</li> <li>epicyon</li> <li>apcore</li> <li>FIRM</li> </ul>"},{"location":"fep/f1d5/#clients","title":"Clients","text":"<ul> <li>The-Federation.Info</li> <li>Hello Matrix Public Servers</li> </ul>"},{"location":"fep/f1d5/#references","title":"References","text":"<ul> <li>Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>Jonne Ha\u00df, jhass/nodeinfo, 2014</li> <li>Jason Robinson, jaywink/nodeinfo2, 2016</li> <li>Jason Robinson, ServiceInfo - specification for service metadata, 2019</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> </ul>"},{"location":"fep/f1d5/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/f228/","title":"FEP-f228: Backfilling conversations","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2025-02-17 #500 Discussions codeberg"},{"location":"fep/f228/#summary","title":"Summary","text":"<p>The most common conversation backfill method is based on recursive retrieval of posts indicated by <code>inReplyTo</code> property and posts contained in <code>replies</code> collections. This is inefficient and stops working if any node in the reply tree becomes inaccessible.</p> <p>FEP-7888: Demystifying the context property suggests using the <code>context</code> property for grouping related objects (such as posts in a conversation). This property can resolve to a collection, which can be used for efficient backfilling without recursion.</p> <p>Two different implementations of <code>context</code> collection exist: collection of posts and collection of activities.</p>"},{"location":"fep/f228/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/f228/#collection-of-posts","title":"Collection of posts","text":"<p>This collection represents a thread and contains all posts in a conversation, from the perspective of the conversation owner.</p> <p>It is an <code>OrderedCollection</code>, and the order of items is chronological. It MUST contain at least one item, the top-level post. This post MUST have a <code>context</code> property referring to the collection. Other posts might not have this property.</p> <p>When <code>context</code> property is present on a post, it MUST resolve to a collection of posts.</p> <p>There is a difference between contents of this collection and a reply tree defined by <code>inReplyTo</code> and <code>replies</code> relationships, because conversation owner might choose to not include certain replies. When a reply is deleted by its author, the sub-replies MAY remain in the thread collection.</p> <p>Note</p> <p>ActivityPub requires ordered collections to be presented in reverse chronological order. However, an erratum was proposed to relax this requirement.</p>"},{"location":"fep/f228/#collection-of-activities","title":"Collection of activities","text":"<p>This collection contains all activities related to posts in a conversation, including but not limited to:</p> <ul> <li><code>Create</code></li> <li><code>Update</code></li> <li><code>Delete</code></li> <li><code>Like</code></li> </ul> <p>It is an <code>OrderedCollection</code>, and the order of items is chronological. It MUST contain at least one item, the <code>Create</code> activity for the top-level post. This activity MUST have a <code>context</code> property referring to the collection. Other activities might not have this property.</p> <p>When <code>context</code> property is present on an activity, it MUST resolve to a collection of activities.</p>"},{"location":"fep/f228/#compatibility-with-conversation-containers","title":"Compatibility with Conversation Containers","text":"<p>In Conversation Containers this collection would coincide with the conversation container. It will contain <code>Add</code> activities in addition to other activities.</p>"},{"location":"fep/f228/#history-property","title":"<code>history</code> property","text":"<p>Collections described in this document can be implemented separately.</p> <p>If both of them are implemented, the <code>history</code> property can be added to a collection of posts, indicating a corresponding collection of activities. This property is defined in FEP-bad1: Object history collection, although the use case here differs from the one described in that proposal.</p>"},{"location":"fep/f228/#contexthistory-property","title":"<code>contextHistory</code> property","text":"<p><code>contextHistory</code> property can be used to make a reference from a post to a collection of activities.</p>"},{"location":"fep/f228/#reading-collections","title":"Reading collections","text":"<p>After top-level post of a conversation is discovered, the whole conversation can be retrieved using the following algorithm:</p> <ul> <li>If <code>contextHistory</code> property is present, retrieve collection of activities and stop.</li> <li>If <code>context</code> property is present, retrieve collection of posts and stop.</li> <li>If <code>replies</code> property is present, retrieve collection of replies, and repeat this step for every reply.</li> </ul>"},{"location":"fep/f228/#implementations","title":"Implementations","text":"<p>Collection of posts:</p> <ul> <li>NodeBB</li> <li>Iceshrimp.NET</li> <li>WordPress</li> <li>Discourse</li> <li>Mitra</li> <li>Decodon (PR)</li> <li>PieFed (commit)</li> <li>Mastodon</li> </ul> <p>Collection of activities:</p> <ul> <li>Streams</li> <li>Hubzilla</li> </ul>"},{"location":"fep/f228/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>a, FEP-7888: Demystifying the context property, 2023</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>silverpill, FEP-171b: Conversation Containers, 2024</li> <li>a, FEP-bad1: Object history collection, 2023</li> </ul>"},{"location":"fep/f228/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/fb2a/","title":"FEP-fb2a: Actor metadata","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com <code>DRAFT</code> 2022-12-09 #45 - codeberg"},{"location":"fep/fb2a/#summary","title":"Summary","text":"<p>It is useful for actors to publish additional structured information about themselves without necessarily defining an extension property or additional vocabulary. This FEP describes a way for actors to publish generic key-value pairs representing their metadata.</p>"},{"location":"fep/fb2a/#history","title":"History","text":"<p>Mastodon v2.4.0 (March 2018) implemented \"bio fields\" [1], a feature that allows adding structured data to profiles. This feature was federated via the <code>attachment</code> field, filtering for array items that had a type of <code>PropertyValue</code> derived from schema.org's vocabulary. Each item used <code>name</code> from the ActivityStreams Vocabulary, and <code>value</code> from the schema.org context. The schema.org namespace was defined as <code>schema</code> and (erroneously) mapped to <code>http://schema.org#</code> (instead of <code>http://schema.org/</code> or <code>https://schema.org</code>) within the JSON-LD context property. </p> <p>Misskey (December 2018) implemented \"user fields\" [2], following the same federation logic as Mastodon (filtering for a type of <code>PropertyValue</code>, then taking <code>name</code> and <code>value</code>).</p> <p>Pleroma (August 2019) implemented \"custom profile fields\" [3], following the same federation logic as Mastodon (filtering for a type of <code>PropertyValue</code>, then taking <code>name</code> and <code>value</code>).</p>"},{"location":"fep/fb2a/#1-using-activitystreams-note-instead-of-schemaorg-propertyvalue","title":"1. Using ActivityStreams Note instead of schema.org PropertyValue","text":"<p>Rather than depending on an additional (and unnecessary) vocabulary, it makes sense to define a more \"native\" way of expressing the same idea of a key-value pair representing structured metadata about the actor. To this end, this FEP proposes using the existing <code>Note</code> type from the ActivityStreams 2.0 Vocabulary (instead of schema.org's <code>PropertyValue</code>), as well as the existing <code>content</code> property (instead of schema.org's <code>value</code>). Note that the <code>name</code> property exists within both the ActivityStreams 2.0 Vocabulary and the schema.org vocabulary, with largely the same semantic meaning; however, the use of schema.org vocabulary is out of scope of this FEP.</p> <p>Thus, we can define a standard for actor metadata, largely drawing from prior art.</p>"},{"location":"fep/fb2a/#2-defining-generic-actor-metadata-as-key-value-pairs","title":"2. Defining generic actor metadata as key-value pairs","text":"<p>General-purpose actor metadata fields SHOULD be included in the <code>attachment</code> array on the actor. If a more specific property exists and is a better fit for the specific metadata being expressed, then implementations MAY use that instead of or in addition to the more generic actor metadata.</p> <ul> <li>Each generic metadata field MUST have a type of <code>Note</code>.</li> <li>Each metadata field MUST have a property of <code>name</code> representing the name (key) of the field.</li> <li>Each metadata field MUST have a property of <code>content</code> representing the content (value) of the field.</li> </ul>"},{"location":"fep/fb2a/#3-defining-generic-actor-metadata-as-links","title":"3. Defining generic actor metadata as links","text":"<p>Actor metadata fields may also take the form of a link rather than a content value.</p> <ul> <li>Each generic link MUST have a type of <code>Link</code>.</li> <li>Each link field MUST have a property of <code>href</code> representing the value of the link.</li> <li>Each link field SHOULD have a property of <code>name</code> representing a label for the link.</li> <li>Each link field SHOULD use <code>rel</code> values if an appropriate link relation exists, such as <code>\"me\"</code>.</li> </ul>"},{"location":"fep/fb2a/#4-backwards-compatibility-with-legacy-implementations-of-profile-fields-using-incorrect-schemaorg-iris","title":"4. Backwards compatibility with legacy implementations of profile fields using incorrect schema.org IRIs","text":"<p>(This section is non-normative.)</p> <p>Existing implementations currently using the incorrect IRIs <code>http://schema.org#PropertyValue</code> and <code>http://schema.org#value</code> may wish to maintain backwards compatibility during a transitional period by serving both legacy representations as well as representations according to this FEP. The following algorithm may be used to support the legacy implementations while also favoring the implementation within this FEP:</p> <ul> <li>Filter the <code>attachment</code> array for items of type <code>Note</code> or <code>Link</code>. Take <code>name</code> and <code>content</code> from each remaining item if the type is <code>Note</code>, or take <code>name</code> and <code>href</code> if the type is <code>Link</code>. If the type is <code>Link</code> and the <code>rel</code> contains <code>\"me\"</code>, attempt to verify this link using rel-me verification.</li> <li>If none are found (or if items remain in the set of <code>attachment</code>), filter the <code>attachment</code> array for items of type <code>http://schema.org#PropertyValue</code>. Take <code>name</code> and <code>http://schema.org#value</code> from each remaining item. If <code>name</code> is a duplicate of an existing <code>name</code>, ignore the item.</li> </ul> <p>After some transitional period, implementations may wish to simplify their logic by filtering only for items of type <code>Note</code> and drop support for <code>http://schema.org#PropertyValue</code>, <code>http://schema.org#value</code>, and the schema.org context entirely (assuming those implementations do not use any other vocabulary from the schema.org context).</p>"},{"location":"fep/fb2a/#5-examples-of-current-legacy-implementations-compared-to-serialization-according-to-this-fep","title":"5. Examples of current legacy implementations compared to serialization according to this FEP","text":"<p>Consider a profile which has the following profile fields:</p> <pre><code>Pronouns\n: they/them\n\nMy portfolio\n: https://example.com/\n</code></pre> <p>A legacy implementation might currently serialize these as such:</p> <pre><code>{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org#\"\n \"PropertyValue\": \"sc:PropertyValue\",\n \"value\": \"sc:value\"\n }\n ],\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Pronouns\",\n \"value\": \"they/them\"\n },\n {\n \"type\": \"PropertyValue\",\n \"name\": \"My portfolio\",\n \"value\": \"&lt;a href=\"https://example.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"&gt;&lt;span class=\"invisible\"&gt;https://&lt;/span&gt;&lt;span class=\"\"&gt;example.com&lt;/span&gt;&lt;span class=\"invisible\"&gt;&lt;/span&gt;&lt;/a&gt;\"\n }\n ]\n}\n</code></pre> <p>For implementations that do not include the same incorrect IRI term mapping in their contexts, compaction would result in the following:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"http://schema.org#PropertyValue\",\n \"name\": \"Pronouns\",\n \"http://schema.org#PropertyValue\": \"they/them\"\n },\n {\n \"type\": \"http://schema.org#PropertyValue\",\n \"name\": \"My portfolio\",\n \"http://schema.org#PropertyValue\": \"&lt;a href=\"https://example.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"&gt;&lt;span class=\"invisible\"&gt;https://&lt;/span&gt;&lt;span class=\"\"&gt;example.com&lt;/span&gt;&lt;span class=\"invisible\"&gt;&lt;/span&gt;&lt;/a&gt;\"\n }\n ]\n}\n</code></pre> <p>Implementation according to this FEP might result in a simpler and more semantically correct serialization but equivalent representation like so:</p> <pre><code>{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"Note\",\n \"name\": \"Pronouns\",\n \"content\": \"they/them\"\n },\n {\n \"type\": \"Link\",\n \"name\": \"My portfolio\",\n \"href\": \"https://example.com\",\n \"rel\": [\"nofollow\", \"noopener\", \"noreferrer\", \"me\"]\n }\n ]\n}\n</code></pre>"},{"location":"fep/fb2a/#implementations","title":"Implementations","text":"<ul> <li>WordPress</li> <li>Streams</li> <li>NodeBB</li> </ul>"},{"location":"fep/fb2a/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[1] Gargron, Add bio fields (#6645)</li> <li>[2] mei23, Show user fields (#3590)</li> <li>[3] minibikini, Add custom profile fields (!1488)</li> </ul>"},{"location":"fep/fb2a/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication </p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/fc48/","title":"FEP-fc48: Generic ActivityPub server","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2026-02-27 #769 Discussions codeberg"},{"location":"fep/fc48/#summary","title":"Summary","text":"<p>Generic ActivityPub server is a server that implements standard ActivityPub client API or FEP-ae97 client API, and can process any activity (including activities those behavior is not defined in the ActivityPub specification).</p>"},{"location":"fep/fc48/#classification","title":"Classification","text":"<p>A generic server MUST determine the class of an object before processing it. The classification of ActivityPub objects is covered in FEP-2277: ActivityPub core types.</p>"},{"location":"fep/fc48/#authentication-and-authorization","title":"Authentication and authorization","text":"<p>A generic server MUST verify all objects according to the universal authentication and authorization procedures described in FEP-fe34: Origin-based security model.</p>"},{"location":"fep/fc48/#activities-and-side-effects","title":"Activities and side effects","text":"<p>A generic server MUST support the following basic activities:</p> <ul> <li><code>Create</code></li> <li><code>Update</code></li> <li><code>Delete</code></li> <li><code>Add</code></li> <li><code>Remove</code></li> <li><code>Offer</code></li> <li><code>Accept</code></li> <li><code>Reject</code></li> </ul> <p>A generic server SHOULD support other activity types defined in the ActivityPub specification.</p> <p>Other activities MUST NOT have implicit side-effects. Any side-effects, such as collection updates, MUST be specified by clients as additional activities. Clients can embed them into an activity using the <code>result</code> property, or send them as separate activities.</p>"},{"location":"fep/fc48/#managing-collections","title":"Managing collections","text":"<p>A generic server MUST automatically create <code>inbox</code>, <code>outbox</code> and other actor collections defined in the ActivityPub specification after registering an actor.</p> <p>Other collections MUST be created by clients using <code>Create</code> activities where <code>object</code> is an empty collection.</p>"},{"location":"fep/fc48/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>silverpill, FEP-ae97: Client-side activity signing, 2023</li> <li>silverpill, FEP-2277: ActivityPub core types, 2025</li> <li>silverpill, FEP-fe34: Origin-based security model, 2024</li> </ul>"},{"location":"fep/fc48/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/fe34/","title":"FEP-fe34: Origin-based security model","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social <code>DRAFT</code> implementation 2024-11-15 #445 Discussions codeberg"},{"location":"fep/fe34/#summary","title":"Summary","text":"<p>Developing a comprehensive ActivityPub security framework based on the concept of web origin.</p>"},{"location":"fep/fe34/#rationale","title":"Rationale","text":"<p>ActivityPub standard does not specify authentication and authorization mechanisms. However, in some cases it hints at the importance of an object's origin:</p> <p>3. Objects</p> <p>... Servers SHOULD validate the content they receive to avoid content spoofing attacks. (A server should do something at least as robust as checking that the object appears as received at its origin, but mechanisms such as checking signatures would be better if available).</p> <p>7.3 Update Activity</p> <p>... The receiving server MUST take care to be sure that the Update is authorized to modify its object. At minimum, this may be done by ensuring that the Update and its object are of same origin.</p> <p>Implementations often rely on origin and ownership checks for determining the validity of activities and objects, but exact requirements are not documented and can be easily overlooked, leading to vulnerabilities such as GHSA-3fjr-858r-92rw.</p> <p>This proposal attempts to formalize existing practices and provide guidance for implementers.</p>"},{"location":"fep/fe34/#requirements","title":"Requirements","text":"<p>The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.</p>"},{"location":"fep/fe34/#assumptions","title":"Assumptions","text":"<p>The origin-based security model is designed for use in a network where a server is resposible for enforcing security boundaries between the hosted actors. Servers that publish objects without validation are not supported.</p> <p>Object identifiers are assumed to be HTTP(S) URIs. The model can also be used with other kinds of identifiers, but that is not covered in this document.</p>"},{"location":"fep/fe34/#origin","title":"Origin","text":"<p>Object identifiers can be grouped together into protection domains called \"origins\". This concept is similar to the \"web origin\" concept described in RFC-6454, and origins of object IDs are computed by the same algorithm.</p> <p>The same-origin policy determines when a relationship between objects can be trusted. Different origins are considered potentially hostile and are isolated from each other to varying degrees. Actors sharing an origin are assumed to trust each other because all their interactions are mediated by a single piece of software operated by a single person or an organization.</p>"},{"location":"fep/fe34/#comparing-origins","title":"Comparing origins","text":"<ol> <li>Let <code>uri-scheme</code> be the scheme component of the URI, converted to lowercase.</li> <li>Let <code>uri-host</code> be the host component of the URI, converted to lowercase.</li> <li>If there is no port component of the URI, let <code>uri-port</code> be the default port for the protocol given by <code>uri-scheme</code>. Otherwise, let <code>uri-port</code> be the port component of the URI.</li> <li>Return the triple <code>(uri-scheme, uri-host, uri-port)</code>.</li> </ol> <p>Origins are the same if they have identical schemes, hosts, and ports.</p>"},{"location":"fep/fe34/#authentication","title":"Authentication","text":"<p>Authentication is the process of verifying the origin of an ActivityPub object. It is performed in order to protect an application from spoofing attacks.</p> <p>Objects can be authenticated using the following methods:</p> <ul> <li>Fetching from an origin</li> <li>Verification of a signature</li> <li>Authentication through embedding</li> </ul> <p>If an object can't be authenticated, it MUST be discarded.</p> <p>Fetching from an origin is the primary authentication method, and other authentication methods described in this document depend on it. Consumers SHOULD attempt to fetch the object from its origin if other authentication methods are not available.</p>"},{"location":"fep/fe34/#fetching-from-an-origin","title":"Fetching from an origin","text":"<p>Non-anonymous ActivityPub objects can be authenticated by making an HTTP GET request with object's ID as the target.</p> <p>The last URI in the chain of redirects is object's location. The location SHOULD match the ID of the retrieved object. If object's location and ID are different, they MUST have the same origin.</p> <p>If the object is protected, the server MAY require an HTTP signature.</p> <p>Servers MUST validate all objects received from clients. Any activity representing an action that actor is not authorized to perform MUST be rejected. Special attention needs to be paid to media uploads, because malicious actors might attempt to bypass the validation by uploading ActivityPub documents as media. If the server allows clients to upload arbitrary files, it MUST serve media from a different origin (e.g. from a different subdomain).</p> <p>As an additional protection in cases where an attacker was able to bypass the validation, consumers MUST verify that the response to a GET request contains the <code>Content-Type</code> header with the <code>application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"</code> or <code>application/activity+json</code> media type (see GHSA-jhrq-qvrm-qr36 for more information).</p> <p>Servers MUST NOT serve objects until they are validated.</p>"},{"location":"fep/fe34/#signatures","title":"Signatures","text":"<p>Signature-based authentication can be used when:</p> <ul> <li>An object is delivered to inbox and the HTTP request contained a valid HTTP signature.</li> <li>An object contains a valid FEP-8b32 integrity proof.</li> </ul> <p>The ID of the public key (or the verification method) MUST have the same origin as the object's ID.</p> <p>Servers MUST NOT share secret keys with clients.</p> <p>Servers MUST NOT allow clients to create or update objects representing public keys, including such objects embedded within actors and other objects. Public keys can be identified by their properties <code>publicKeyPem</code> and <code>publicKeyMultibase</code>. Embedded public keys with a different origin are permitted.</p> <p>In order to minimize damage in the event of a key compromise or insufficient validation, consumers MUST verify that the signing key has the same owner as the signed object. Consumers MUST also confirm the ownership of the key by verifying a reciprocal claim.</p> <p>Warning</p> <p>JSON-LD consumers might be tricked into processing a specially crafted JSON object without <code>publicKeyPem</code> and <code>publicKeyMultibase</code> properties as a public key. Protections against attacks of that kind are not described in this document.</p>"},{"location":"fep/fe34/#embedding","title":"Embedding","text":"<p>In some cases, an embedded object can be trusted when its wrapping object is trusted:</p> <ul> <li>An embedded object has the same origin and the same owner as the wrapping object.</li> <li>An embedded object is identified as a fragment of the wrapping object.</li> <li>An embedded object is anonymous (doesn't have an ID).</li> </ul>"},{"location":"fep/fe34/#authorization","title":"Authorization","text":"<p>Authorization is the process of verifying permission to create, read, update or delete an object.</p>"},{"location":"fep/fe34/#ownership","title":"Ownership","text":"<p>Ownership is indicated by a property of an ActivityPub object. The name of this property differs depending on the object class:</p> <ul> <li>The owner of an actor is indicated by its <code>id</code> property.</li> <li>Activities have an <code>actor</code> property, which describes the actor that performed the activity. This actor is considered to be the owner of the activity.</li> <li>Public keys and verification methods have <code>owner</code> and <code>controller</code> properties.</li> <li>Other kinds of objects might have an <code>attributedTo</code> property, which describes the actor to which the object is attributed. This actor is considered to be the owner of the object.</li> </ul> <p>In some cases ownership might be implicit. Examples:</p> <ul> <li>Inbox and outbox collections are expected to be owned by the actor to which they are attached.</li> <li>A <code>replies</code> collection is owned by the actor to which the post is attributed.</li> <li>All pages of a collection are expected to be owned by the same actor.</li> </ul> <p>Anonymous objects are not supposed to have an owner.</p> <p>Applications SHOULD use the following algorithm to determine the owner of an object:</p> <ol> <li>Run the duck typing algorithm specified in FEP-2277.</li> <li>If the type is <code>Link</code>, return error.</li> <li>If the type is neither <code>Object</code> nor <code>Collection</code>, and the object has an <code>attributedTo</code> property, return error.</li> <li>If the type is <code>Actor</code>, return the value of the <code>id</code> property.</li> <li>If the type is <code>VerificationMethod</code>, return the value of the <code>controller</code> property.</li> <li>If the type is <code>PublicKey</code>, return the value of the <code>owner</code> property.</li> <li>If the type is <code>Activity</code>, return the value of the <code>actor</code> property.</li> <li>If the type is <code>Object</code> or <code>Collection</code>, return the value of the <code>attributedTo</code> property.</li> </ol> <p>The owner of an object MUST be an actor.</p> <p>Identifier of an object and identifier of its owner MUST have the same origin.</p> <p>Warning</p> <p>According to Activity Vocabulary, <code>actor</code> and <code>attributedTo</code> properties can contain references to multiple actors. These scenarios are not covered by this document and implementers are expected to determine the appropriate authorization procedures on a case-by-case basis.</p>"},{"location":"fep/fe34/#comparing-owners","title":"Comparing owners","text":"<p>Owners are the same if their identifiers are identical after conversion of their schemes and hosts to lowercase.</p>"},{"location":"fep/fe34/#create-update-and-delete","title":"Create, update and delete","text":"<p>The actor that creates an object MUST be its owner.</p> <p>The owner of an object is permitted to modify and delete it. This permission might also be specified with reciprocal claims.</p> <p>If the owner of an activity that modifies or deletes an object doesn't have a permission to perform the operation, the activity MUST be rejected. If such activity is received from another server and the permission can not be verified, the recipient MAY accept the activity if its actor and the owner of the affected object have the same origin.</p> <p>Examples:</p> <ul> <li><code>Update</code> and <code>Delete</code> activities, and objects indicated by their <code>object</code> property are expected to have the same owner.</li> <li><code>Undo</code> activity and object indicated by its <code>object</code> property are expected to have the same owner.</li> <li><code>Add</code> and <code>Remove</code> activities, and objects indicated by their <code>target</code> property are expected to have the same owner.</li> <li><code>Announce</code> and <code>Like</code> activities don't modify objects indicated by their <code>object</code> property, therefore their owners can be different.</li> </ul>"},{"location":"fep/fe34/#access-control","title":"Access control","text":"<p>When a protected object is retrieved, the server MUST verify that the <code>GET</code> request contains an HTTP signature created using a key whose owner belongs to object's intended audience.</p> <p>The server MAY require a signature even if the object is public. In that case, the request can be signed with a key owned by a server actor.</p> <p>Servers that implement proxyUrl endpoint MUST ensure that access to objects is restricted to actors that belong to intended audiences of these objects.</p>"},{"location":"fep/fe34/#ownership-transfer","title":"Ownership transfer","text":"<p>When ownership changes, the new owner ID MUST have the same origin as the old owner ID.</p>"},{"location":"fep/fe34/#reciprocal-claims","title":"Reciprocal claims","text":"<p>Claims are considered reciprocal when one object specifies a claim that is accompanied by a reverse claim specified in another object.</p> <p>Examples:</p> <ul> <li>The ownership of an activity can be asserted by including it in the actor's outbox.</li> <li>The ownership of a public key can be asserted by embedding it within the actor document.</li> </ul>"},{"location":"fep/fe34/#cross-origin-relationships","title":"Cross-origin relationships","text":"<p>Relationships between objects with different origins are possible, but they MUST be confirmed with reciprocal claims made by both origins. In that case, the same-origin policy can be bypassed.</p> <p>Examples:</p> <ul> <li>An activity can be signed with a key of different origin if that key is referenced from the actor document.</li> <li>An object can be deleted by an actor of different origin if that actor is specified as a moderator for the context to which the object belongs.</li> <li>An actor can migrate from one server to another by performing the <code>Move</code> activity if the migrating actor is included in the target actor's <code>alsoKnownAs</code>.</li> </ul>"},{"location":"fep/fe34/#references","title":"References","text":"<ul> <li>Christine Lemmer-Webber, Jessica Tallon, Erin Shepherd, Amy Guy, Evan Prodromou, ActivityPub, 2018</li> <li>James M Snell, Evan Prodromou, Activity Vocabulary, 2017</li> <li>S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>A. Barth, The Web Origin Concept, 2011</li> <li>silverpill, FEP-8b32: Object Integrity Proofs, 2022</li> <li>Ryan Barrett, nightpool, ActivityPub and HTTP Signatures, 2024</li> <li>silverpill, FEP-2277: ActivityPub core types, 2025</li> </ul>"},{"location":"fep/fe34/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"},{"location":"fep/fffd/","title":"FEP-fffd: Proxy Objects","text":"Authors Status Date received Tracking issue Discussions Repository Adam R. Nelson adam@nels.onl, Ryan Barrett (administrative only) <code>DRAFT</code> 2023-04-29 #95 - codeberg"},{"location":"fep/fffd/#summary","title":"Summary","text":"<p>A proxy object is an [ActivityPub] object that is semantically identical to another entity, which may exist on another, non-ActivityPub protocol. For example, an ActivityPub-to-Nostr bridge creates Actors and Notes that are proxies for Nostr users and notes.</p> <p>This document describes a data format to identify proxy objects and to specify the ActivityPub and non-ActivityPub entities they are equivalent to, with the intention that multi-protocol clients will automatically merge objects with their proxies, hiding the implementation details of bridges and cross-protocol publishing from users.</p>"},{"location":"fep/fffd/#1-requirements","title":"1. Requirements","text":"<p>The key words \"MUST\", \"SHOULD\", and \"MAY\" are to be interpreted as described in [RFC2119].</p>"},{"location":"fep/fffd/#2-rationale","title":"2. Rationale","text":"<p>This section is non-normative.</p> <p>Many Fediverse servers speak multiple protocols besides ActivityPub, such as Nostr or Diaspora, and simultaneously publish posts across multiple protocols. Additionally, bridge servers exist to relay posts between ActivityPub and other protocols such as Nostr.</p> <p>A use case also exists for creating a duplicate of an ActivityPub object: moving an account to a new instance. Posts copied to a user's new account should be marked as duplicates of the originals, in a way that preserves the original posts' likes and replies.</p> <p>Despite these use cases, there is currently no standard way to communicate that an ActivityPub activity is a copy (or proxy) of a post on another protocol.</p> <p>Consider this scenario:</p> <ul> <li>Alice's server speaks both ActivityPub and Diaspora protocols. She publishes a post, which is syndicated on both protocols.</li> <li>Bob's server speaks only ActivityPub. He replies to the ActivityPub version of Alice's post.</li> <li>Charlie's server speaks only Diaspora. He replies to the Diaspora version of Alice's post.</li> <li>Dee's server speaks both ActivityPub and Diaspora. She receives Alice's, Bob's, and Charlie's posts. How does Dee's server know that Bob's reply and Charlie's reply belong to the same post?</li> </ul> <p>Proxy objects provide a potential solution to this problem.</p>"},{"location":"fep/fffd/#3-format","title":"3. Format","text":"<p>FEP-fffd does not define any new vocabulary or <code>@context</code> entries. Instead, it further defines the meaning of Links in the <code>url</code> property of an Object when they have a <code>rel</code> property of <code>\"alternate\"</code> or <code>\"canonical\"</code>.</p> <p>Each Link in <code>url</code> with <code>\"rel\": \"alternate\"</code> or <code>\"rel\": \"canonical\"</code> is called a proxy link. Any Object with one or more proxy links is called a proxy object. The referent of a proxy link is called a proxied object, and SHOULD be considered semantically identical to the parent proxy object, modulo the limitations described in section 4.</p> <p>A proxied object is not necessarily an ActivityPub object, or even an object accessible via a network request; its meaning is determined based on its protocol. The protocol and format of the proxied object are determined by the proxy link's URI scheme and <code>mediaType</code>; well-known protocols and defaults are defined in section 5. An application SHOULD ignore proxied objects in protocols or formats that the application does not understand.</p> <p>If a proxy link has <code>\"rel\": \"canonical\"</code>, it indicates that its proxied object is the canonical (original, authoritative) version of the proxy object. A proxy object MUST NOT have more than one proxy link with <code>\"rel\": \"canonical\"</code>. This property SHOULD be used by bridges that relay posts made by third parties, to indicate that the bridged post is not the original. It SHOULD NOT be used by servers that publish to multiple protocols at once; in this case no one version of an object is more authoritative than another.</p>"},{"location":"fep/fffd/#4-merging","title":"4. Merging","text":"<p>When a conforming application encounters a proxy object, it may merge it with its proxy objects under certain circumstances.</p> <p>To merge a proxy object and its proxied object(s) means to display all of these objects as a single entity (such as a user or a post), while combining all collections and metadata belonging to these objects:</p> <ul> <li>The followers, following, liked, and outbox collections of an actor, if present, SHOULD be combined with those of a proxy when merging.</li> <li>The replies, likes, and shares of a non-actor object, if present, SHOULD be combined with those of a proxy object when merging.</li> <li>If one of the merged objects is canonical, its properties SHOULD override any conflicting properties in any other merged object.</li> <li>If none of the merged objects are canonical, conflicting properties MAY be resolved in any way the application chooses, including but not limited to: choosing the representation from the protocol with the most features, displaying a detailed description of the conflict, or refusing to merge objects with conflicting properties.</li> </ul> <p>In some circumstances, an application may encounter malformed or malicious proxy links that could misrepresent objects not owned by the links' author, or it may encounter proxy links whose referents are malformed or missing. These situations sometimes prevent merging.</p> <ul> <li>If a proxy link's referent has been deleted (as indicated by an HTTP 410 Gone status, a Delete activity, or another protocol's equivalent), then:<ul> <li>If the proxied object is canonical, the proxy object SHOULD be deleted</li> <li>If the proxied object is not canonical, the proxy object MAY be deleted; if it is not, all collection entries originally from the deleted object's protocol (if it is not ActivityPub) SHOULD be removed from the proxy object's collections, and their proxy objects, if any, SHOULD be deleted.</li> </ul> </li> <li>If a proxy link is broken, but has not been explicitly deleted, then the proxy object MAY continue to exist, and the application MAY still display cached data or proxy objects for the proxied object's collection entries.</li> <li>If a proxied object is canonical, and that proxied object itself has a link to a different canonical representation (whether through FEP-fffd or another protocol's equivalent), then:<ul> <li>The application SHOULD follow the chain of canonical links up to a fixed, application-defined maximum number of links.</li> <li>If this number of links is exceeded (possibly indicating a cycle), or if any link in the chain has more than one canonical link, the application SHOULD NOT merge any of the objects in the chain.</li> </ul> </li> <li>If a proxied object is not canonical, the application SHOULD verify that the proxied object also considers itself a proxy for the proxy object. If the proxied object is an ActivityPub object, then the application SHOULD NOT merge it with the proxy object if it does not meet at least one of these criteria:<ul> <li>It has a proxy link pointing to the proxy object (that is, both objects are proxies for each other)</li> <li>Both objects are actors, and both are <code>alsoKnownAs</code> each other.</li> <li>Both object are owned by the same actor, or by actors that are <code>alsoKnownAs</code> each other.</li> </ul> </li> <li>If a proxy link points to localhost or any loopback address, the application SHOULD NOT follow the link or attempt to merge the proxied object it represents.</li> </ul>"},{"location":"fep/fffd/#5-protocols","title":"5. Protocols","text":"<p>Several protocols are named in this document, but interaction with these protocols is left intentionally underspecified, as the behavior of non-ActivityPub protocols is outside the scope of this FEP. If a proxy link's URI scheme and/or <code>mediaType</code> match a protocol named in this section, a conforming application SHOULD either use the matching protocol to access the proxied object or ignore the proxy link entirely, but it MUST NOT interpret the link as a proxy link for a different protocol or format.</p>"},{"location":"fep/fffd/#51-well-known-alternate-protocols","title":"5.1. Well-known Alternate Protocols","text":"<ul> <li>Nostr: Identified by the <code>nostr:</code> URI scheme, as defined in [NIP-21]. The identifiers used in these URIs MUST be \"bare\" NIP-19 identifiers starting with <code>npub1</code> or <code>note1</code>. The <code>npub1</code> identifier type MUST be used only in proxy links for Actors.</li> <li>Diaspora: Identified by the <code>diaspora:</code> URI scheme, following the format defined in [<code>diaspora://</code> URI scheme].</li> <li>DID: Identified by the <code>did:</code> URI scheme, as defined in [DID URL Syntax], and MUST be used only in proxy links for Actors.</li> <li>ATProto: Identified by the <code>at</code> URI scheme, as defined in [AT URI Scheme]. AT Repositories, Collections, and Records MUST be used only in proxy links for ActivityPub Actors, Collections, and Objects, respectively. Repository URIs starting with <code>at://did:</code> SHOULD be considered identical to the <code>did:</code> URIs they contain; including both a <code>did:</code> link and an <code>at://did:</code> link for the same DID is redundant.</li> <li>Secure Scuttlebutt: Identified by the <code>ssb:</code> URI scheme.</li> </ul>"},{"location":"fep/fffd/#52-well-known-media-types","title":"5.2. Well-known Media Types","text":"<ul> <li>RSS: <code>application/rss+xml</code>; the <code>href</code> should be the URL of the feed, followed by a URL fragment whose content is the <code>&lt;guid&gt;</code> value of an entry in the feed.</li> <li>Atom: <code>application/atom+xml</code>; the <code>href</code> should be the URL of the feed, followed by a URL fragment whose content is the <code>&lt;id&gt;</code> value of an entry in the feed.</li> <li>ActivityPub: <code>application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"</code> or <code>application/activity+json</code>; the <code>href</code> should point to an ActivityPub Object.</li> </ul> <p>If an application supports general-purpose transport protocols other than HTTP(S), such as Gemini or IPFS, it MAY interpret proxy links to these protocols in the same manner as it would interpret HTTP(S) proxy links, including applying these well-known media types.</p>"},{"location":"fep/fffd/#53-non-federated-web-content","title":"5.3. Non-federated Web Content","text":"<p>By default, if a proxy link uses the <code>http</code> or <code>https</code> protocol, and either does not have a <code>mediaType</code> or has a <code>mediaType</code> of <code>text/html</code>, it is considered a link to some unspecified, application-defined non-federated Web content. An application MAY interpret this link as any kind of content or protocol other than one of the well-known protocols or formats defined in this section. Notably, this kind of proxy object MUST NOT be interpreted as an ActivityPub resource, even if the link responds with valid ActivityStreams data.</p> <p>This default allows an ActivityPub object to proxy any non-federated Web content; for example, a Twitter-to-ActivityPub bridge may use a proxy link to a Twitter URL to identify a Note as a proxy object for a Twitter post.</p>"},{"location":"fep/fffd/#6-examples","title":"6. Examples","text":"<p>This section is non-normative. The JSON-LD <code>@context</code> property is omitted for brevity.</p> <p>A post relayed by a third-party Twitter-to-ActivityPub bridge. Because the <code>canonical</code> proxy link is also the only <code>url</code> entry, it should also be used as a clickable link to the original post.</p> <pre><code>{\n \"id\": \"http://twitter-bridge.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://twitter-bridge.example/@jack\",\n \"content\": \"just setting up my twttr\",\n \"url\": {\n \"type\": \"Link\",\n \"rel\": \"canonical\",\n \"href\": \"https://twitter.com/jack/status/20\"\n }\n}\n</code></pre> <p>A post published to ActivityPub, Diaspora, and Nostr simultaneously. Because there is one non-proxy <code>Link</code> in <code>url</code> with an <code>https</code> protocol, this non-proxy link should be used as a clickable link to the original post.</p> <pre><code>{\n \"id\": \"http://fediverse.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://fediverse.example/@alice\",\n \"content\": \"Hello, world!\",\n \"url\": [{\n \"type\": \"Link\",\n \"href\": \"https://fediverse.example/@alice/1234\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"href\": \"diaspora://alice@fediverse.example/post/deadbeefdeadbeefdeadbeefdeadbeef\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"href\": \"nostr:note1gwdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\"\n }]\n}\n</code></pre> <p>An ActivityPub Note copied from a user's old instance to a new instance when moving to a new account.</p> <pre><code>{\n \"id\": \"http://newinstance.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://newinstance.example/@alice\",\n \"content\": \"Hello, world!\",\n \"url\": [{\n \"type\": \"Link\",\n \"href\": \"https://newinstance.example/@alice/1234\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"canonical\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://oldinstance.example/status/5678\"\n }]\n}\n</code></pre>"},{"location":"fep/fffd/#references","title":"References","text":"<ul> <li>[ActivityPub] Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018</li> <li>[RFC-2119] S. Bradner, Key words for use in RFCs to Indicate Requirement Levels, 1997</li> <li>[NIP-21] fiatjaf, martindsq, mplorentz, NIP-21: <code>nostr:</code> URI scheme, 2023</li> <li>[<code>diaspora://</code> URI scheme] Benjamin Neff, diaspora* federation protocol, 2017</li> <li>[DID URL Syntax] Manu Sporny, Markus Sabadello, Drummond Reed, Orie Steele, Christopher Allen, Decentralized Identifiers (DIDs) v1.0, 2022</li> <li>[AT URI Scheme] Bluesky, ATProto Documentation, 2023</li> </ul>"},{"location":"fep/fffd/#copyright","title":"Copyright","text":"<p>CC0 1.0 Universal (CC0 1.0) Public Domain Dedication</p> <p>To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.</p>"}]}