fep/fep/76ea/fep-76ea.md
silverpill eae0493642
All checks were successful
ci/woodpecker/push/test Pipeline was successful
Register FEP-76ea
2024-10-04 20:31:03 +00:00

12 KiB

slug authors status dateReceived discussionsTo trackingIssue
76ea Evan Prodromou <evan@socialwebfoundation.org> DRAFT 2024-10-04 https://codeberg.org/evanp/fep/issues #407

FEP-76ea: Conversation Threads

Summary

This FEP defines a way to identify the conversation thread of an object with Activity Streams 2.0.

Motivation

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.

Some social software restricts the depth of the thread, while others allow for unlimited depth.

Identifying the thread that an AS2 object is part of allows for the construction of a conversation view of the thread.

It is possible with Activity Streams 2.0 to construct a conversation thread by following the inReplyTo property of an object until the original post is found, and then expanding the replies 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.

This FEP defines an extension property, thread, that can be used to identify the conversation thread of an object.

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.

Context

The context URL for this FEP is https://purl.archive.org/socialweb/thread.

The context is as follows:

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "thr": "https://purl.archive.org/socialweb/thread#",
  "thread": {
    "@id": "thr:thread",
    "@type": "@id"
  },
  "root": {
    "@id": "thr:root",
    "@type": "@id"
  }
}

Terms

The context defines two properties.

thread

The thread property is an OrderedCollection that contains all of the objects in the conversation thread. The collection is ordered in reverse chronological order, with the most recent object first.

The thread 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 inReplyTo and/or replies properties of each object in the collection.

The thread property extends the context property from the Activity Vocabulary.

The thread property does not replace the replies property of an object. replies contains the possibly curated collection of direct replies to the object; thread contains the full conversation tree, up- and down-thread.

root

The root property is an Object that is the original post of the conversation thread. The root property is usually the last (earliest) object in the thread collection.

This property gives an easy way for a consumer to find the root post of the thread without having to search the orderedItems collection, navigate through multiple OrderedCollectionPage pages, or traverse the inReplyTo properties of the objects in the collection.

Note that thread and root are partially inverse properties. The thread property of the root property of a collection SHOULD contain the id of the thread collection. However, the root property of the thread property of an object MAY not contain the object's id, because the object is in the thread, but is not the root.

Thread maintenance

As with the replies property, the processor implementing the original post of a thread SHOULD maintain the thread collection by adding new objects to the collection as they are received.

In ActivityPub, this could be done when the processor receives an object with an inReplyTo property that matches an object in the thread collection.

To facilitate collection synchronization, the processor SHOULD distribute an Add activity to the audience of the original object with the new object as the object property and the thread as the target property.

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.

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.

In ActivityPub, if an object is removed from the thread, he processor SHOULD distribute a Remove activity to the audience of the original object with the new object as the object property and the thread as the target property.

The tree structure of the thread should be maintained; every object in the thread collection, except the root, should have an inReplyTo property that matches the id 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.

The replies property of objects in the thread collection MAY be maintained by other processors. Curation of the replies collections or of the thread collection may mean that objects may be omitted from one collection or the other. However, the replies collection of the original post SHOULD be a subset of the thread collection.

Examples

Example 1

An example of a Note object with a thread property:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://purl.archive.org/socialweb/thread"
  ],
  "id": "https://example.com/note/123",
  "type": "Note",
  "attributedTo": "https://example.com/user/1",
  "to": [
    "https://remote.example/user/17",
    "https://remote.example/user/17/followers"
  ],
  "content": "I concur!",
  "thread": "https://remote.example/thread/117",
  "inReplyTo": "https://remote.example/note/117"
}

Example 2

An example of an Image object with a thread property. The Image is a root or original post with no inReplyTo property:

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://purl.archive.org/socialweb/thread"
  ],
  "id": "https://example.com/image/123",
  "type": "Image",
  "name": "A photo of a cat",
  "attributedTo": "https://example.com/user/1",
  "to": "https://example.com/user/1/followers",
  "url": {
    "type": "Link",
    "mediaType": "image/jpeg",
    "href": "https://example.com/image/123.jpg"
  },
  "replies": "https://example.com/replies/123",
  "thread": {
    "id": "https://example.com/thread/123",
    "to": "https://example.com/user/1/followers",
    "type": "OrderedCollection",
    "totalItems": 4,
    "orderedItems": [
      {
        "id": "https://fourth.example/note/721",
        "attributedTo": "https://fourth.example/user/4",
        "to": [
          "https://example.com/user/1",
          "https://example.com/user/1/followers",
          "https://other.example/user/2"
        ],
        "inReplyTo": "https://other.example/note/338"
      },
      {
        "id": "https://third.example/note/992",
        "attributedTo": "https://third.example/user/3",
        "to": "https://example.com/user/1",
        "inReplyTo": "https://example.com/image/123"
      },
      {
        "id": "https://other.example/note/338",
        "attributedTo": "https://other.example/user/2",
        "to": [
          "https://example.com/user/1",
          "https://example.com/user/1/followers"
        ],
        "inReplyTo": "https://example.com/image/123"
      },
      "https://example.com/image/123"
    ]
  }
}

Note that not all objects in the thread collection need to be addressed to the same audience. The audience of the thread collection is the audience of the original post.

Example 3

This is a Note object that is a reply to two different objects, and thus is part of two different threads.

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://purl.archive.org/socialweb/thread"
  ],
  "id": "https://example.com/note/789",
  "attributedTo": "https://example.com/user/1",
  "to": "as:Public",
  "content": "These are both good points.",
  "inReplyTo": [
    "https://remote.example/note/57",
    "https://other.example/note/456"
  ],
  "thread": [
    "https://remote.example/thread/57",
    "https://other.example/thread/456"
  ]
}

Example 4

Objects in a thread that have been deleted by their author can be represented in the thread collection with a Tombstone object.

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://purl.archive.org/socialweb/thread"
  ],
  "id": "https://example.com/note/345",
  "attributedTo": "https://example.com/user/1",
  "to": "as:Public",
  "content": "Activity Streams 2.0 is awesome!",
  "replies": "https://example.com/replies/345",
  "thread": {
    "id": "https://example.com/thread/345",
    "to": "as:Public",
    "type": "OrderedCollection",
    "orderedItems": [
      {
        "id": "https://third.example/note/567",
      },
      {
        "type": "Tombstone",
        "id": "https://remote.example/note/456",
        "inReplyTo": "https://example.com/note/345",
        "deleted": "2024-10-03T00:00:00Z"
      },
      "https://example.com/note/345"
    ]
  }
}

Example 5

The thread collection can be paged, as with other collections.

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://purl.archive.org/socialweb/thread"
  ],
  "id": "https://example.com/note/678",
  "attributedTo": "https://example.com/user/1",
  "to": "as:Public",
  "content": "Is Wario A Libertarian?",
  "replies": "https://example.com/replies/678",
  "thread": {
    "id": "https://example.com/thread/678",
    "to": "as:Public",
    "type": "OrderedCollection",
    "totalItems": 244780,
    "first": "https://example.com/thread/678/page/12239",
    "last": "https://example.com/thread/678/page/1"
  }
}

Example 6

The root property can be used to identify the original post of a thread.

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://purl.archive.org/socialweb/thread"
  ],
  "id": "https://example.com/thread/654",
  "type": "OrderedCollection",
  "totalItems": 457,
  "first": "https://example.com/thread/654/page/23",
  "last": "https://example.com/thread/654/page/1",
  "root": "https://example.com/note/654"
}

Security Considerations

Not all objects in the thread collection may be addressed to the same audience. Representations of the collection SHOULD NOT include the content property or other sensitive information from objects in the collection that are not addressed to the recipient of the representation.

In ActivityPub, the orderedItems property of the thread collection MAY be filtered for the recipient of the representation.

Previous work

The ostatus:conversation property is used in Mastodon and elsewhere to identify the thread of an object, but it is not necessarily dereferenceable.

Some implementations of ActivityPub use the context property to represent the thread of an object. This FEP provides a more specific property, which frees up the "intentionally vague" context property for other uses. It also avoids the confusing clash with the @context property of JSON-LD.

References

  • Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018

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.