FEP-1b12: Group federation

The Lemmy implementation is compatible with Mastodon. Thats because it works in a very similar way by using Announce, with the main difference that the actor has type Group and not Person. In Lemmy posts are also owned by the user and announced by the group, which works just fine with Mastodon.

I dont know which problems Hubzilla had with Mastodon compatibility. Maybe their forum implementation works in a different way from Lemmy, or there were changes on the Mastodon side before Lemmy started federating.

1 Like

That is entirely possible. Hubzilla forums have been around for a long time now. The federation with Mastodon was written a long time ago, and Mastodon has gone through many major upgrades since then.

1 Like

Congrats @nutomic and everyone who contributed. The FEP is now finalized!

Feel free to boost (David Sterry: "đŸ„ł I'm pleased to announce that "FEP-1b12: Group f
" - Ecko / c4.social) or share other announcements.

3 Likes

@nutomic I’m implementing a version of this in the Discourse ActivityPub Plugin. See here

Currently the only Activity type I’m announcing is Create, as it’s relevant to content discovery, which seems to be the primary utility of Announce. I’m currently federating Update, Delete etc activities concerning the same content directly from the Actor associated with that content, i.e.

  1. A Discourse topic is created.
  2. Various activities happen prior to initial publication (e.g. a few posts, likes etc)
  3. All initial activities (activities that occur prior to initial publication) are wrapped in Announcements and published as a Collection (an OrderedCollection of Announcements of Activities) by the Discourse category Actor to its Followers.
  4. All activities subsequent to initial publication are published by the relevant Discourse user’s Actor to the Followers of the Category Actor.

The above works (as far as I can tell) with Mastodon. I was initially publishing all Activities (before and after initial publication) as announcements of the Category Actor but that didn’t seem to play so nicely with Mastodon, and also feels a bit strange, i.e. the content discovery has already happened once the Create Activity is Announced, why keep Announcing further Activities?

But maybe I’m missing something?

(@trwnh I thought of you when I decided to use a Collection for a topic :wink: Interested in your thoughts too).

2 Likes

@angus Why a Collection of Announced Activities and not a Collection of objects? That feels like two layers of unnecessary wrapping.

Also, if it were just an OrderedCollection of Activities, you may as well make it an actor and just use outbox. Yes, it would be unexpected by current projects that assume an actor that is always one of Person/Group/Organization/Application/Service, but that was never a valid assumption, and it should be broken sooner rather than later. Anything can be an actor as long as it has an inbox and outbox.

Stepping back a bit: the argument for a collection of objects rather than a collection of activities (create/announce or otherwise) is that it is both simpler and more broadly compatible to rely on ActivityStreams for the data model rather than ActivityPub. There are projects that understand AS2 and deal with AS2 objects, but not necessarily strictly ActivityPub. The expectation to only deal with activities is more of a consequence of those activities having defined side effects for content management.

Basically I am proposing the following:

id: <some-topic>
type: [Collection or OrderedCollection, plus some extension type maybe if you want to disambiguate... Topic? Conversation? etc]
name: Some Topic
summary: This is a topic.
orderedItems: [<the-first-post>, <the-second-post>, ...]
totalItems: ...
inbox: <some-inbox>
outbox: <some-outbox>
attributedTo: <topic-owner>

id: <some-outbox>
type: OrderedCollection
name: Some Topic's Outbox
summary: This is the outbox for Some Topic | <some-topic>
orderedItems: [ <some-activity> or <some-wrapping-announce> or <some-announce>, ... ]
totalItems: ...

id: <the-first-post>
type: ...
content: hello world, this is the first post in Some Topic
attributedTo: <some-author>

id: <the-second-post>
type: ...
content: hello this is the second post
attributedTo: <some-author-2>

# Pick a distribution method

id: <some-activity>
actor: <some-author>
type: Create
object: <the-first-post>
to: <topic-owner>, <some-topic
summary: This creates the first post in the topic, and makes the topic owner aware of this so they can forward it. It can be forwarded directly as-is.

id: <some-wrapping-announce>
actor: <topic-owner>
type: Announce
object: <some-activity>
to: <some topic's audience or followers or etc>
summary: Or, wrap the activity in an Announce for compatibility with FEP-1b12.

id: <some-announce>
actor: <topic-owner>
type: Announce
object: <the-first-post>
to: <some topic's audience or followers or etc>
summary: Or, directly announce the object/post for compatibility with Mastodon "boosts".

Because that wouldn’t work. The actor on the announcement is the Group, i.e. the category. The actor on the activity is the Person, i.e. the user. The Person owns the object. Whereas the Group is telling its followers about it. But more broadly, a simple collection of objects is just less descriptive than a collection of activities. The activity tells you something about the object, which may not just be “Create”. It could also be a Like or some other Activity. An initial publication of a Collection may include, for example:

Announce by Group
Create by Person 1
Note for Post 1

Announce by Group
Like by Person 2
Note for Post 1

To put it another way, I see what you would lose in your proposal, besides compatibility, and I don’t see what you would gain.

How? Remember, an Activity is still an Object. You can have 5 Notes and a Like in the same Collection. You can also have 5 Creates and a Like. You could also have 6 Announces. I’m basically saying that Announce{Create{Note}} is less direct than just putting the Note in there.

The exception is for outbox in ActivityPub, which contains Activities only, typically as a side effect of those activities being POSTed by the client and then processed and delivered.

So there are three ways for the “Group” to make its followers aware of the “Person”'s object, as described at the end of the code block:

  • Inbox forwarding. Just POST the activity directly as you received it, as ActivityPub expects
  • Announce the Create, as FEP-1b12 expects
  • Announce the object, as a normal “reshare” action

Yes, and in this case, you could either inbox forward it, or wrap it in an Announce as FEP-1b12 expects. But the Like is also available via the likes collection on the object. If you have a collection that is an actor that represents the topic, you can get an object’s likes or you can get the Like activity from the topic actor’s outbox.

What you gain is a more generic data representation that works outside of ActivityPub. The Collection holds all the posts, for which the Create/Add/Announce activities are irrelevant. Put another way: the Collection’s items might hold [Note, Note, Note] while the actor’s outbox might hold [Announce.Create, Announce.Create, Announce.Like, Announce.Create] following FEP-1b12, or it might hold [Create, Create, Like, Create] in an alternative system. This is not a hypothetical, either – see the following example:

$ apget https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8

{
   "@context":[
     // ...
   ],
   "type":"Note",
   "id":"https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
   // ...
   "context":"https://macgirvin.com/conversation/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
   "conversation":"https://macgirvin.com/conversation/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
   "content":"Did you know? <br><br>When using software from the streams repository, instances are actors. You can follow instances. And instances can follow other instances. <br><br>Thank you for your time. G'day.",
   // ...
}
$ apget https://macgirvin.com/conversation/4c93eb1b-8634-4bbd-8873-de595cdd8bd8

{
   "@context":[
      // ...
   ],
   "id":"https://macgirvin.com/conversation/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
   "type":"OrderedCollection",
   "totalItems":35,
   "orderedItems":[
      {
         "type":"Create",
         "id":"https://macgirvin.com/activity/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
         // ...
         "object":{
            "type":"Note",
            "id":"https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
            // ...
            "content":"Did you know? <br><br>When using software from the streams repository, instances are actors. You can follow instances. And instances can follow other instances. <br><br>Thank you for your time. G'day.",
            // ...
      },
      {
         "@context":[
            // ...
         ],
         "type":"Like",
         "id":"https://diversispiritus.net.br/activity/2296c320-b6ed-45ea-be79-c127dd0aed05",
         // ...
         "inReplyTo":"https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
         "context":"https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
         "conversation":"https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
         "actor":"https://diversispiritus.net.br/channel/andre",
         //...
         "object":{
            "type":"Note",
            "id":"https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
            "published":"2023-07-27T23:11:20Z",
            "location":{
               "type":"Place",
               "name":"Bugger All, Australia",
               "latitude":-34.4452,
               "longitude":150.3
            },
            "commentPolicy":"specific",
            "attributedTo":"https://macgirvin.com/channel/mike",
            "isContainedConversation":true,
            "canReply":"https://diversispiritus.net.br/followers/mike",
            "context":"https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
            "conversation":"https://macgirvin.com/item/4c93eb1b-8634-4bbd-8873-de595cdd8bd8",
            "content":"Did you know? <br><br>When using software from the streams repository, instances are actors. You can follow instances. And instances can follow other instances. <br><br>Thank you for your time. G'day.",
            // ...
         },
        // ...
         "signature":{
            "type":"RsaSignature2017",
            "nonce":"f7932584dadab8a47b3997c767356ab8703ce916a674d9a7d678f4609cf12fba",
            "creator":"https://diversispiritus.net.br/channel/andre",
            "created":"2023-07-27T23:14:14Z",
            "signatureValue":"EtS+..."
         }
      },
      // ...
}

With respect, you’ve just re-written ActivityPub. In order for this to work all of the implications you’re trying to spell out here would also have to be described in a specification. They’re not inherent in ActivityStreams. It’s not more generic, it’s just a different version of ActivityPub.

I appreciate the effort, but what you’re describing is not a convincing alternative to ActivityPub, and would not be feasible for anyone to adopt. Indeed adopting it would run counter to the efforts to promote standardisation in ActivityPub.

I appreciate that it makes sense to you as a direct implication of ActivityStreams, but as you’ve said elsewhere the specification is generic enough to support many interpretations. Indeed, this is why ActivityPub is necessary.

But I’d also like to say that if you really want to do this, the Discourse ActivityPub plugin will let you actually. You could quite easily configure it to support the kind of use you’re envisaging. So perhaps this is your chance to get a real-world example. If you want to fork it and try out your theory, I’d be happy to give you tips on the code.

I’m very confused by this statement:

What is this supposed to mean? It doesn’t feel like it follows from any of what I described, or the example of in-the-wild code that I pointed to. I’m not even sure what you mean by “ActivityPub” in this statement. Are we talking about two different meanings of it? Because I’m talking about the actual spec as published. It defines an inbox and outbox, and it defines side effects for activities, and it specifies inbox forwarding as the intended mechanism for redistributing activities. ActivityPub, put simply, is:

  • Actors have an inbox and outbox (section 4, Actors)
  • POST to outbox (Section 6, C2S) and certain local side effects (Sections 6.2 - 6.10) will take place
  • If the activity is addressed to anyone (Section 6.1) it will be delivered to their inbox (6.11, 7.1)
    • If there are collections owned by the recipient, you may dereference them and forward to them (7.1.2)
    • Certain distributed side effects (7.2 - 7.12) will take place

Everything else, including the data model, comes from ActivityStreams 2.0, which is normatively referenced by ActivityPub. The “implications” are spelled out in these specs, ActivityPub and ActivityStreams 2.0 Core; specific quotations as follows:

ActivityPub actors are generally one of the ActivityStreams Actor Types, but they don’t have to be. For example, a Profile object might be used as an actor, or a type from an ActivityStreams extension.

ActivityPub does not dictate a specific relationship between “users” and Actors; many configurations are possible. There may be multiple human users or organizations controlling an Actor, or likewise one human or organization may control multiple Actors. Similarly, an Actor may represent a piece of software, like a bot, or an automated process. More detailed “user” modelling, for example linking together of Actors which are controlled by the same entity, or allowing one Actor to be presented through multiple alternate profiles or aspects, are at the discretion of the implementation.

The Activity Vocabulary defines a range of Object types that are common to many social Web applications.

Collection objects are a specialization of the base Object that serve as a container for other Objects or Links.

To reiterate, you can actually support both a plain AS2 parser and also an AP-aware implementation by having a collection be an actor with an outbox. Objects (“posts”) go in the items and activities go in the outbox. If you want people to discover what the actor did, you put it in outbox. If you want people to discover the actual posts themselves, they can browse the objects directly
 and you might bundle these objects in a collection for convenience, in the same way that an HTML page can contain multiple sections, or a directory can contain multiple documents. These don’t have to be separate things. They both represent the “topic”. One mechanism is just more directly focused on the actual data (the topic as a collection of objects, defined by ActivityStreams), while the other is focused on the actions required to construct that data (the topic as an actor with an outbox, defined by ActivityPub).

So in summary, if you’re going to bundle a bunch of Announce activities, just use the outbox. Make the topic an actor. There’s no reason to force everything to go through the Discourse user actor or through the category actor. The correct abstraction layer is to have topic activities flow through a topic actor. Otherwise, what even is a topic?

The average person reading the ActivityPub standard does not come away from it thinking that the goal is to do away with Activities aside from exceptional cases. To be honest this feels like the kind of debate I used to have in law school (which is not a good thing). Yes, there are specific phrases you can point to to try and build your interpretive case. But pretty much everyone else who’s read the standard has not come to the same conclusion as you. It’s the difference between textual and purposive interpretation.

And gain what exactly by doing this? I only see downside in this:

  1. Users cannot follow the actor until the topic is created.
  2. The content is shown as authored on other platforms by
the topic? That would be strange.

A Collection of Notes by different Actors on a specific topic (summarised in the summary, aka the topic title). That makes much more sense to me than making the topic itself an Actor.

For step 3 it would make more sense to add a replies collection which contains post objects instead of activities. This is supported by Mastodon (but not yet by Lemmy).

Also it would be good if you could send me examples of the Activitypub json which your project sends. Then I can add it to the Lemmy tests to ensure that things are compatible. See here for existing test files.

1 Like

Am I understanding correctly that you intend to publish an OrderedCollection of Activities instead of a single Activity to an inbox? If so, that may work with Mastodon because of a historical (and, arguably, unintentional) quirk in the implementation but it’s not going to work with most other servers, if any.

I’m also surprised that announcing an activity works with Mastodon. When I look at the Announce handler in the source code, it appears to expect an object that can be converted into a Mastodon Status. Although a Create activity is an Object, it’s not one of the types of Objects that Mastodon can convert to a Status.

Could you explain your thinking here a bit more? What do you gain by getting rid of the Activities? What’s the benefit of moving in this direction?

The PR adding this functionality is not yet merged, so I’ll give you a final version when it is, but it’ll look something like this

Example Collection JSON
{
  "id": "https://angus.eu.ngrok.io/ap/collection/07530bbc3e0e87b2766f82132b010c10",
  "type": "OrderedCollection",
  "to": "https://www.w3.org/ns/activitystreams#Public",
  "published": "2023-08-08T08:38:51Z",
  "updated": "2023-08-08T08:38:51Z",
  "url": "https://localhost/t/this-is-a-new-topic/11",
  "totalItems": 3,
  "summary": "This is a new topic",
  "orderedItems": [{
      "id": "https://angus.eu.ngrok.io/ap/activity/767fb780785bd91b608733801ee25817",
      "type": "Announce",
      "to": "https://www.w3.org/ns/activitystreams#Public",
      "updated": "2023-08-08T08:38:51Z",
      "actor": {
        "id": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe",
        "type": "Group",
        "updated": "2023-08-07T14:42:47Z",
        "url": "https://localhost/c/general/4",
        "inbox": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/inbox",
        "outbox": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/outbox",
        "followers": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/followers",
        "preferredUsername": "general",
        "publicKey": {
          "id": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe#main-key",
          "owner": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe",
          "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4D/UcTsgVdAo6lWu/8Y\nhjLIaisqADAZaY0ALd0LV6nlo8vMBw+sqsC5iB//Gl+reH1GiXwJIGV2L04dW1wt\nvw0Z8Y+lZKjP0uYxaR28hNvYnTegt3u18U/DyuopGRm16O+pO+nKGKm/sirfxELK\nvPzFfipWMpPyWeZfjvll7HpScMkmMQi+JXdA2sbeNUzUiMvVYKVfIGZCGASNOImp\nbnNce8Y+xmrbA2C04MB4pULeHffIEdq5NlugA56Gg7/p9whG96jC28Zwh3K9EJvp\nAeU4kYO5etBSTI6185ONHxcmaFtoqYB00c6HMSaXhv9AMVnC0EjvpuJKnt34tIWJ\nLQIDAQAB\n-----END PUBLIC KEY-----\n"
        },
        "icon": {
          "type": "Image",
          "mediaType": "image/png",
          "url": "http://localhost:3000/images/discourse-logo-sketch-small.png"
        },
        "name": "General",
        "@context": "https://www.w3.org/ns/activitystreams"
      },
      "@context": "https://www.w3.org/ns/activitystreams",
      "object": {
        "id": "https://angus.eu.ngrok.io/ap/activity/6c33b55799e5d819bb25c44c4eb6acda",
        "type": "Create",
        "to": "https://www.w3.org/ns/activitystreams#Public",
        "published": "2023-08-08T08:38:51Z",
        "updated": "2023-08-08T08:38:51Z",
        "actor": {
          "id": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199",
          "type": "Person",
          "updated": "2023-08-07T14:43:49Z",
          "url": "http://localhost:3000/u/angus",
          "inbox": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/inbox",
          "outbox": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/outbox",
          "followers": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/followers",
          "preferredUsername": "angus",
          "publicKey": {
            "id": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199#main-key",
            "owner": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199",
            "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNyL3+e/Bjl/jUS6F4KG\nB3dLIbxIEczNzSC2mjYi6HEfdptFvEI7SoSZAM7SKSK8eB5THhpQbEvYZQxRxzJQ\ncANZ2kEHe7qsFLHJeUOX9y7cHf20nnEkCiqw7sKI901d28vrzoe9QMw19I6EWGIQ\nOyIlXOF0EPTM8wDkH9NmdK7IbjY+6F7wLNEUYSVZPhKryoMzf7FHkzzOQ5Zoe0LE\nUzjCIbxMFVLLo4osKiNfOuBEGswyIkg/Fos6opQJ4Pp8QJ9opWh6zmjgJW7wHVan\nd3SiiQkOZNsHWVxPgQf4npU5pr/AxYGMwmJU5NTVk05iRq6XVuhc/K6oATxoYCrY\neQIDAQAB\n-----END PUBLIC KEY-----\n"
          },
          "icon": {
            "type": "Image",
            "mediaType": "image/png",
            "url": "//localhost:3000/user_avatar/localhost/angus/96/3_2.png"
          },
          "@context": "https://www.w3.org/ns/activitystreams"
        },
        "@context": "https://www.w3.org/ns/activitystreams",
        "object": {
          "id": "https://angus.eu.ngrok.io/ap/object/0fdbe2f67c115951ae2d55236b5ff409",
          "type": "Note",
          "to": "https://www.w3.org/ns/activitystreams#Public",
          "published": "2023-08-08T08:38:51Z",
          "updated": "2023-08-08T08:38:51Z",
          "url": "https://localhost/t/this-is-a-new-topic/11/3",
          "content": "This is the third post.<br><br><a href=\"https://localhost/t/this-is-a-new-topic/11/3\">Discuss this on our forum.</a>",
          "inReplyTo": "https://angus.eu.ngrok.io/ap/object/17aaf4a5e50be8a7d2f181b0490e6502",
          "@context": "https://www.w3.org/ns/activitystreams"
        }
      }
    },
    {
      "id": "https://angus.eu.ngrok.io/ap/activity/b5594c545d8b6418fbcedf5a65a5dc32",
      "type": "Announce",
      "to": "https://www.w3.org/ns/activitystreams#Public",
      "updated": "2023-08-08T08:38:51Z",
      "actor": {
        "id": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe",
        "type": "Group",
        "updated": "2023-08-07T14:42:47Z",
        "url": "https://localhost/c/general/4",
        "inbox": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/inbox",
        "outbox": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/outbox",
        "followers": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/followers",
        "preferredUsername": "general",
        "publicKey": {
          "id": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe#main-key",
          "owner": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe",
          "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4D/UcTsgVdAo6lWu/8Y\nhjLIaisqADAZaY0ALd0LV6nlo8vMBw+sqsC5iB//Gl+reH1GiXwJIGV2L04dW1wt\nvw0Z8Y+lZKjP0uYxaR28hNvYnTegt3u18U/DyuopGRm16O+pO+nKGKm/sirfxELK\nvPzFfipWMpPyWeZfjvll7HpScMkmMQi+JXdA2sbeNUzUiMvVYKVfIGZCGASNOImp\nbnNce8Y+xmrbA2C04MB4pULeHffIEdq5NlugA56Gg7/p9whG96jC28Zwh3K9EJvp\nAeU4kYO5etBSTI6185ONHxcmaFtoqYB00c6HMSaXhv9AMVnC0EjvpuJKnt34tIWJ\nLQIDAQAB\n-----END PUBLIC KEY-----\n"
        },
        "icon": {
          "type": "Image",
          "mediaType": "image/png",
          "url": "http://localhost:3000/images/discourse-logo-sketch-small.png"
        },
        "name": "General",
        "@context": "https://www.w3.org/ns/activitystreams"
      },
      "@context": "https://www.w3.org/ns/activitystreams",
      "object": {
        "id": "https://angus.eu.ngrok.io/ap/activity/ddf3611f5a0c23fda0693f77897631b2",
        "type": "Create",
        "to": "https://www.w3.org/ns/activitystreams#Public",
        "published": "2023-08-08T08:38:51Z",
        "updated": "2023-08-08T08:38:51Z",
        "actor": {
          "id": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199",
          "type": "Person",
          "updated": "2023-08-07T14:43:49Z",
          "url": "http://localhost:3000/u/angus",
          "inbox": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/inbox",
          "outbox": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/outbox",
          "followers": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/followers",
          "preferredUsername": "angus",
          "publicKey": {
            "id": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199#main-key",
            "owner": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199",
            "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNyL3+e/Bjl/jUS6F4KG\nB3dLIbxIEczNzSC2mjYi6HEfdptFvEI7SoSZAM7SKSK8eB5THhpQbEvYZQxRxzJQ\ncANZ2kEHe7qsFLHJeUOX9y7cHf20nnEkCiqw7sKI901d28vrzoe9QMw19I6EWGIQ\nOyIlXOF0EPTM8wDkH9NmdK7IbjY+6F7wLNEUYSVZPhKryoMzf7FHkzzOQ5Zoe0LE\nUzjCIbxMFVLLo4osKiNfOuBEGswyIkg/Fos6opQJ4Pp8QJ9opWh6zmjgJW7wHVan\nd3SiiQkOZNsHWVxPgQf4npU5pr/AxYGMwmJU5NTVk05iRq6XVuhc/K6oATxoYCrY\neQIDAQAB\n-----END PUBLIC KEY-----\n"
          },
          "icon": {
            "type": "Image",
            "mediaType": "image/png",
            "url": "//localhost:3000/user_avatar/localhost/angus/96/3_2.png"
          },
          "@context": "https://www.w3.org/ns/activitystreams"
        },
        "@context": "https://www.w3.org/ns/activitystreams",
        "object": {
          "id": "https://angus.eu.ngrok.io/ap/object/1f2d7bfb76b711103b38338ada94b7f0",
          "type": "Note",
          "to": "https://www.w3.org/ns/activitystreams#Public",
          "published": "2023-08-08T08:38:51Z",
          "updated": "2023-08-08T08:38:51Z",
          "url": "https://localhost/t/this-is-a-new-topic/11/2",
          "content": "This is the second post.<br><br><a href=\"https://localhost/t/this-is-a-new-topic/11/2\">Discuss this on our forum.</a>",
          "inReplyTo": "https://angus.eu.ngrok.io/ap/object/17aaf4a5e50be8a7d2f181b0490e6502",
          "@context": "https://www.w3.org/ns/activitystreams"
        }
      }
    },
    {
      "id": "https://angus.eu.ngrok.io/ap/activity/0fe5ae348ca9747c01a5639b5af28884",
      "type": "Announce",
      "to": "https://www.w3.org/ns/activitystreams#Public",
      "updated": "2023-08-08T08:38:51Z",
      "actor": {
        "id": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe",
        "type": "Group",
        "updated": "2023-08-07T14:42:47Z",
        "url": "https://localhost/c/general/4",
        "inbox": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/inbox",
        "outbox": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/outbox",
        "followers": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe/followers",
        "preferredUsername": "general",
        "publicKey": {
          "id": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe#main-key",
          "owner": "https://angus.eu.ngrok.io/ap/actor/f1647a8c356030d99861bf8910bb6dbe",
          "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4D/UcTsgVdAo6lWu/8Y\nhjLIaisqADAZaY0ALd0LV6nlo8vMBw+sqsC5iB//Gl+reH1GiXwJIGV2L04dW1wt\nvw0Z8Y+lZKjP0uYxaR28hNvYnTegt3u18U/DyuopGRm16O+pO+nKGKm/sirfxELK\nvPzFfipWMpPyWeZfjvll7HpScMkmMQi+JXdA2sbeNUzUiMvVYKVfIGZCGASNOImp\nbnNce8Y+xmrbA2C04MB4pULeHffIEdq5NlugA56Gg7/p9whG96jC28Zwh3K9EJvp\nAeU4kYO5etBSTI6185ONHxcmaFtoqYB00c6HMSaXhv9AMVnC0EjvpuJKnt34tIWJ\nLQIDAQAB\n-----END PUBLIC KEY-----\n"
        },
        "icon": {
          "type": "Image",
          "mediaType": "image/png",
          "url": "http://localhost:3000/images/discourse-logo-sketch-small.png"
        },
        "name": "General",
        "@context": "https://www.w3.org/ns/activitystreams"
      },
      "@context": "https://www.w3.org/ns/activitystreams",
      "object": {
        "id": "https://angus.eu.ngrok.io/ap/activity/a41210d107cc00c54f5f613c248cdc9d",
        "type": "Create",
        "to": "https://www.w3.org/ns/activitystreams#Public",
        "published": "2023-08-08T08:38:51Z",
        "updated": "2023-08-08T08:38:51Z",
        "actor": {
          "id": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199",
          "type": "Person",
          "updated": "2023-08-07T14:43:49Z",
          "url": "http://localhost:3000/u/angus",
          "inbox": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/inbox",
          "outbox": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/outbox",
          "followers": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199/followers",
          "preferredUsername": "angus",
          "publicKey": {
            "id": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199#main-key",
            "owner": "https://angus.eu.ngrok.io/ap/actor/429132f80a0cef25d1864c07fc5b7199",
            "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNyL3+e/Bjl/jUS6F4KG\nB3dLIbxIEczNzSC2mjYi6HEfdptFvEI7SoSZAM7SKSK8eB5THhpQbEvYZQxRxzJQ\ncANZ2kEHe7qsFLHJeUOX9y7cHf20nnEkCiqw7sKI901d28vrzoe9QMw19I6EWGIQ\nOyIlXOF0EPTM8wDkH9NmdK7IbjY+6F7wLNEUYSVZPhKryoMzf7FHkzzOQ5Zoe0LE\nUzjCIbxMFVLLo4osKiNfOuBEGswyIkg/Fos6opQJ4Pp8QJ9opWh6zmjgJW7wHVan\nd3SiiQkOZNsHWVxPgQf4npU5pr/AxYGMwmJU5NTVk05iRq6XVuhc/K6oATxoYCrY\neQIDAQAB\n-----END PUBLIC KEY-----\n"
          },
          "icon": {
            "type": "Image",
            "mediaType": "image/png",
            "url": "//localhost:3000/user_avatar/localhost/angus/96/3_2.png"
          },
          "@context": "https://www.w3.org/ns/activitystreams"
        },
        "@context": "https://www.w3.org/ns/activitystreams",
        "object": {
          "id": "https://angus.eu.ngrok.io/ap/object/17aaf4a5e50be8a7d2f181b0490e6502",
          "type": "Note",
          "to": "https://www.w3.org/ns/activitystreams#Public",
          "published": "2023-08-08T08:38:51Z",
          "updated": "2023-08-08T08:38:51Z",
          "url": "https://localhost/t/this-is-a-new-topic/11/1",
          "content": "This is the first post.<br><br><a href=\"https://localhost/t/this-is-a-new-topic/11/1\">Discuss this on our forum.</a>",
          "@context": "https://www.w3.org/ns/activitystreams"
        }
      }
    }
  ],
  "@context": "https://www.w3.org/ns/activitystreams"
}

We are currently publishing an OrderedCollection when a topic is first created, yes. This is partly because we allow a few minutes delay between when a topic is first created and the first activities are published. A few posts can be added to the topic in that time window. Rather than schedule multiple publications, we publish all the activities performed in the initial window at once.

Publishing a collection has the added benefit of publishing topic metadata the same time (e.g. a topic tile is a summary on the collection), so it’s quite conceivable you could, say, federate between two Discourse categories (that won’t work in the first version of this, but it’ll be possible to do with this approach).

Could you explain your thinking a bit more? it seems quite intentional. The service processing incoming activities in the inbox is the ProcessCollectionService which handles various types of collections.

Announcing an activity works quite well with Mastodon. Indeed FEP-1b12 would probably need another approach if it didn’t.

1 Like

That’s what I thought, but it’s not clear. In any case, it’s not a typical AP interpretation.

(See the related quote from @Gargron.)

Announcing an activity works quite well with Mastodon. Indeed FEP-1b12 would probably need another approach if it didn’t.

I did some more digging. I see why it works now. This feels like another Mastodon-specific implementation detail/quirk and not something a typical AP server would expect.

It works because when Mastodon sees the Announce embedded object is an unsupported type it tries to fetch the remote “status” (just in case it’s different?). To fetch the remote status it uses the id of the Announce embedded object if the object is a supported type. Otherwise, it uses the id of the object of the Announce object (the Create object, if I haven’t gotten lost somewhere). It fetches that and tries to create a Status from it. It looks like this convoluted behavior is possibly related to “bearcap” support (based on the function naming), but I’m not very familiar with bearcaps so I’m not sure.

I also think this might not work for anything other than an Announce of a Create (since the “create” behavior is implicit in the code logic). The algo I outlined above doesn’t seem like it would work for an Announce of a Like or a Delete, for example.

2 Likes

Using the same approach as an existing implementation is always preferable because it results in better compatibility. Not only with Mastodon, but also other projects which copied the approach. Maybe your way with activities seems to make more sense, but it wont be compatible with any other platforms. So you will need to make a breaking change in the future, better to avoid that.

I’m confused. Currently my approach works with Mastodon and follows FEP-1b12. Indeed, before you said

Are you saying you’ll be changing both Lemmy and FEB-1b12 to this new activity-less approach in the future too?

@stevebate If what you say is true FEP-1b12 will need to be re-written because it assumes the opposite. Indeed there seems to be a tension between what you’re saying and the claim that FEB-1b12 is based on how existing services work.

I agree that announcing anything other than a Create is a bit strange. Indeed

That was slightly incorrect in so far as a “Like” prior to initial publication would also be announced, however after this discussion I’m thinking of ensuring pre-initial-publication likes are just directly federated by their post Actors too as it seems there is significant confusion around the approach here.

The Announce in FEP-1b12 is used to send activities to inboxes. What we are talking about here is fetching objects on demand which is a different mechanism. Its not necessary that both work in the same way, and I dont see why activities would be necessary for fetching. The replies collection is something that already exists, so for compatibility its better to use the same solution rather than reinvent the wheel (eg https://mastodon.social/users/randahl/statuses/110896788294006292/replies?only_other_accounts=true&page=true).

Lemmy doesnt currently have any functionality to fetch missing comments automatically. It expects that all of them are sent via inbox activity. You could work around this by announcing preexisting comments once the post is getting federated.

Regarding Announce for different activities, I dont see what the problem is. Lemmy wraps all kinds of activities in Announce such as Create, Update, Vote, Delete, Block, Add, Lock etc. Maybe Mastodon doesnt support all of them but that doesnt matter.

2 Likes

Ah, I see our confusion.

I was just talking about sending activities to inboxes.

When I said “Published” here, I meant sending to inboxes. I didn’t mean when the Activity is retrieved by a remote instance. In that case there is no Collection or Announce involved, the Activity is simply returned as normal. Sorry, I should have been more specific. I think we’re on the same page here.

That makes sense for retrieval, no argument there. We will implement a replies collection along these lines in the future. But what about the initial posting of the relevant Activities to inboxes? To be clear the main reasons I’ve introduced a collection into the mix at the initial posting stage are:

  • There may be multiple activities prior to the first POST as there’s a POST delay (a setting defaulted to 5 minutes), so it’s a single POST per follower instead of multiple at once.

  • The Collection used to POST activities can also serve the purpose of containing thread (topic) data, e.g. summary = title.

I don’t have a big problem with that per se. It just feels a bit unnecessary that’s all. But I can see why you’ve decided to take that approach.

1 Like