That’s the problem. Or not, depending on some implementer’s desire to federate reliably with Mastodon and similar microblogging platforms. It’s spec-compliant AFAICT. The spec allows an Announce of anything, People, Places, Events, even other Announce activities. However, no implementation I know of completely supports the full flexibility of the specification and that’s fine. Again, this is only an issue if interop is a goal.
In the interest of keeping this on-topic, I’ll refrain from litigating this chain of replies and instead summarize my points of contention, so as to be clear:
- The “goal” of ActivityStreams is to describe objects and collections, with an emphasis on social objects and activity streams.
- The “goal” of ActivityPub is to define standardized side effects for activities, as well as provide a common interface for actors to communicate via inbox/outbox according to these side effects.
Put simply, a collection of activities is an “outbox”. You may separately have a collection of objects representing a thread, intended to be fetched as a generic linked data representation – this is where you would serve your object
, replies
, context
, and so on, without requiring them to be wrapped in activities.
The core proposed behavior is as follows:
- A “thread” or “topic” can be represented by a Collection of posts. This Collection can be referenced via
context
. It can be fetched directly. The posts areattributedTo
whoever wrote them. The collection isattributedTo
whoever is managing the thread. - A “thread” or “topic” can also be represented by an actor. This actor distributes activities related to the “thread” or “topic”, like how a mailing list works. You can Follow it. You can send it an activity and it will relay that action accordingly, e.g. via FEB-1b12, via inbox forwarding and/or Add activities, via a “boost bot”… however you wish.
Additional behavior can be implemented as follows:
- A category actor can Create/Announce/Add/etc any topic actors, if you wish to replicate this information via ActivityPub federation. Your followers will have to understand such activities targeting a “thread” or “topic”, however that may be represented. Adding a topic to the category seems natural to me, but I believe FEP-1b12 expects you to Announce a Page object.
- Following the category actor alerts you to new topics in that category.
- Following the topic actor alerts you to new posts in that topic.
- Following the user actor alerts you to new posts by that user in any topic.
This should broadly support all kinds of implementations, including ones that make different decisions. Say you want to be able to split a topic. It seems natural to me under an Add/Remove system, your Client could Remove the posts from the old topic and Add them to the new topic. Under current systems, you wouldn’t have any mechanism for this, as long as there is no explicit representation of a “topic” as separate from the reply-tree.
Aside from this, I will weigh in on at least one other thing: Mastodon triggering the ProcessCollectionService as a result of activities arriving in the outbox is a quirk of Mastodon, not something natively supported by ActivityPub. Even in Mastodon’s case, it mostly expects a Collection of Notes, such as a pinned posts collection being fetched via featured
on the profile. Spec-wise, it’s ambiguous because there is no expectation to have multiple object
in an activity, so the behavior for such cases is not clearly defined. You could maybe expect being able to map the action to all objects in a functional way, but implementations would have to support this explicitly, and I think most if not all current implementations expect only a single object. There would also probably be issues with doing multiple objects as well – what happens if you Add 10 objects to a collection, but only 7 of those succeed, and 3 of them fail? Was the activity overall a success or a failure? Do the failed objects get retried? These kinds of questions should be answered before attempting such a distribution.
Oh I think I get it now. You want to send a collection of activities to inboxes of other instances instead of sending them one by one, is that right? I dont think its a good idea, because Im not aware of any platform which would support this. Besides it sounds like premature optimization, Lemmy sends countless activities (including for each individual vote) and it works without problems, the main server load is from local users not federation. Whats the purpose of the 5 minute publishing delay anyway?
I’m on board with this and the Discourse plugin is doing it already.
I don’t really have an issue with this. I’m not sure if it’s supported anywhere, but I guess you have to start somewhere. Can you give me a very specific, very simple example to work from (please not a big wall of text ).
I’ll address this in the context of your next point.
I agree with this new phrasing, i.e. can also (the emphasis is mine). To use an analogy you can “Watch” a topic in standard Discourse (bell next to the timeline on the right) and you’ll get notifications about that topic. However that’s just one way of discovering content. I don’t think it makes sense to give the topic Actor primacy in the way you were suggesting before. The Discourse plugin may add topic actors at some point to support this behaviour (indeed it’s already arisen as a possibility in internal discussions).
Yes, I agree with most of this. I would phrase some of it slightly differently, but yes, it follows from the above. The main thing I would say is I don’t see the need to make this the only way threaded content in ActivityPub works. We’ll add support for your desired use of context
for topic collections and perhaps add topic actors, but not to the exclusion of the other methods of discovery of content.
- @nutomic Mastodon supports it. @trwnh I appreciate what you’re saying about the provenance of this however the fact is that this is what Mastodon actually does, i.e. it processes collections, and you have to take things as you find them to an extent. If you send an OrderedCollection of Activities to the Mastodon inbox it will process it in order. However that functionality came to be, whatever its original purpose, that’s what it does currently.
- @nutomic The purpose is essentially to give the OP breathing space before they publish to the fediverse (i.e. time to delete, edit etc). This was also requested from a few different interested folks (see the plugin’s topic on meta.discourse.org).
- @nutomic @trwnh Thank you for your thoughts on this as I can see we’ll need to change the default behaviour here for wider support.
That said, I don’t really see why processing a Collection of Activities shouldn’t be more widely “supported” (caveat @trwnh’s point) than just Mastodon. A Collection is just another Object. And in some scenarios it seems to be more efficient to send a Collection in one POST. Indeed there seems to be some overlap between this way of thinking about a topic/thread and @trwnh’s preferred approach. But I’m not going to die on this hill. It’s relatively straightforward for the Discourse plugin to just send individual Activities to inboxes instead.
Mike Macgirvin responded with:
There are good reasons why not everybody is willing to adopt the Lemmy solution. And that is Private Groups and comment permissions. I’ve been telling people this for a long time and probably sound like a broken record, but when one does private groups, and especially when one adds comment permissions to that situation – everything changes. Really, everything. So start with that, or one finds they have painted themself into a corner - as nearly every project supporting groups is currently doing.
Mike isn’t on this forum so it’d be great if any Group devs on here could engage with him via the linked fedi thread.
FEP-1b12, “The Announce activity” section:
In case the incoming activity is deemed valid, the group MUST wrap it in an
Announce
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].
How recipients of the Announce
activity should verify the authenticity of the wrapped activity?
If wrapped activity contains an integrity proof, we can verify the proof and see that it was created by the actor
. But, as far as I know, existing FEP-1b12 implementations don’t use integrity proofs. Do they re-fetch all wrapped activities from the server of origin?
This information seems to be missing from the FEP.
Lemmy doesnt do any such verification, because we already trust the community by following it. Even with authentication, a malicious community could do numerous bad things like banning innocent users or not announcing certain activities. So authentication would only solve part of the problem. And in practice, users would notice such manipulation themselves and migrate elsewhere.
This is quite different from the microblogging world, where any user can make a controversial post which gets shared across the whole network. So verification is more important there.
The thing I’m worrying about is impersonation. A malicious community could announce an authentically looking post attributed to, for example, a Lemmy developer, which directs people to some phishing page. It is hard to tell in advance if community can be trusted or not.
I’d argue that this is more serious than banning / censoring.
That would show up as a federated post coming from lemmy.ml, but clicking the fedilink icon would bring you either to an error page or to an unrelated post, so it would be easy to tell that something is wrong. Also the only one who could do such an attack is an instance admin in a locally hosted community, so it would be easy to tell who did it. I agree that signature verification would be helpful to prevent this, but there are many issues which are more important.
This FEP obviously describes a very particular way that a group federates, but other FEPs may design different kinds of groups.
With that in mind, would it not make sense to include in the FEP a requirement that Group actors that work according to this FEP should have a special type
field, equal to (for example) ["https://w3id.org/fep/1b12/group", "Group"]
? Or something to that effect.
Right now the groups following this FEP are, as far as I can tell, indistinguishable from any other kind of ActivityPub Group actor, aside from the special fields that this FEP defines. But I don’t think you should use the presence of those fields to decide that a Group actor follows this FEP - an explicit signal in the type would be preferable.
Does this make sense? Or is there perhaps already a mechanism for discovering that a Group
actor follows this FEP that I don’t know about yet? Or is there a better approach to signal this?
that’s one way to signal protocols, yes. either we can use types or we can use properties on the actor/inbox/outbox…
Negotiating protocols between actors or clients is related, the general gist of which is to declare profiles or constraints on what any actor (or their inbox or outbox) might receive or produce, and how to interpret certain payloads.
prior art is to declare an LDN inbox
is constrainedBy
some constraints profile but there isn’t much guidance on what this should look like. you could theoretically apply this to actors instead of just inboxes…
something like this might work?
GET /c/example HTTP/1.1
Host: lemmy.example
Link: <https://w3id.org/fep/1b12>; rel="http://www.w3.org/ns/ldp#constrainedBy"
HTTP/1.1 200 OK
Content-Type: application/activity+json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://lemmy.example/c/example",
"type": "Group",
"inbox": {
"id": "https://lemmy.example/c/example/inbox",
"type": "OrderedCollection"
"http://www.w3.org/ns/ldp#constrainedBy": "https://w3id.org/fep/1b12"
}
"http://www.w3.org/ns/ldp#constrainedBy": "https://w3id.org/fep/1b12"
}
note that this isn’t really specified anywhere, this is just spitballing for now. Negotiating protocols between actors or clients is where further discussion should be.
Shouldn’t this appear in the context? It seems to me this would follow JSON LD context spec closer, and provide for a generic way of adding terms from FEPs in the ActivityPub context. Something like:
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/fep/1b12"
]
...
Yea, probably. I’m not super well-versed in JSON-LD but if you go with my custom type approach, I guess you could have the actor look like this:
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"fep-1b12": "https://w3id.org/fep/1b12/"
}
],
"type": ["fep-1b12:group", "Group"]
}
Would that be correct? That would maybe be preferable so that both JSON-LD-aware processors can understand it and JSON-LD-unaware processors can look for fep-1b12:group
which should be specific and unique enough to be useful as an indication.
For this FEP I’m not too fond of this approach, it seems needlessly complicated for the use case here, which is very focused around the group itself. I feel a custom type on the group should be plenty of a signal.
@nutomic do you think adding the above extra type and context to Lemmy’s community Group
actors would make sense? Also just curious, how does Lemmy react when it sees a Group
actor that isn’t actually a FEP-1b12 group? Would it (probably erroneously) interpret it as a Lemmy community?
Context is only a mapping of terms to IRIs. It is inappropriate for anything else.
See fep/fep/888d/fep-888d.md at main - fediverse/fep - Codeberg.org for defining terms associated with FEPs but also see fep/fep/e229/fep-e229.md at main - fediverse/fep - Codeberg.org for extensibility considerations. In particular, only full IRIs would be unambiguous. It is possible to define infinitely many prefixes that all map to the same thing. It’s too late to define https://w3id.org/fep/1b12/Group
but it is still possible for some other FEP to define https://w3id.org/fep/xxxx/1b12Group
or similar.
But the “problem” with using types to declare protocol is that it’s all-or-nothing and doesn’t decompose well. The advantage of a property like constrainedBy
is that it can have multiple values, as granular or as broad as you think is best. It also avoids “polluting” the type array.
Hmm so how would the Group
actor be written then? Not sure I understand and honestly I don’t have the time to read through what you linked right now (it is quite long).
I don’t really care specifically how the type is called, just that it is fairly unambiguous and easily located for both JSON-LD-aware and JSON-LD-unaware consumers and producers.
But can’t the type
field also have multiple values in the same way? I don’t see the difference.
This is not ideal because existing implementations expect a single value. A boolean flag can be used instead:
{
"type": "Group",
"isFEP1b12Group": true
}
Also, this proposal is FINAL, and FEP process doesn’t permit significant changes at that stage. A new proposal can be created, though.
That’s unfortunate - the spec is quite clear that multiple types are permitted. But sure, I guess a boolean could work as well.
Hm I was not aware that the FEP process was that rigid. That seems undesirable? If we go with the boolean approach, the change would not even be breaking. Why aren’t FEPs versioned with semantic versioning, for instance? Then we could use 1.0.0 instead of “FINAL” and this extra enhancement on top could be version 1.1.0.
Depends on how this requirement will be specified. If it is something like “…MAY add a property to Group actor”, it can be added:
A proposal with status
FINAL
can not be changed or updated in a way that would lead to adjustments to implementations. Minor corrections are allowed.
But I think it would be better to publish a revised version of this FEP, because it is not up to date. For example, it describes audience
field, which is not used by Lemmy anymore.
FINAL is supposed to be a signal that FEP is safe to implement. A new FEP can easily be published if changes are necessary, so there is no need for 2.0 versions.
the inbox doesn’t have to be partially embedded, that’s just for the convenience of writing one example instead of two
type
represents the “is a” relation, and is in some ways a statement of class or category. using a property like constrainedBy
is more precise. imagine saying in plain english <this> <is a> <1b12 group>
. now we need to know more about what <1b12 group>
means. how would you define that class? does that definition grant any useful logical properties? what happens if you want to break up 1b12 into specific behaviors?