Groups implementation

I’ve implemented groups in Tavern and would like feedback and comments. All of the group specific actor and activity behavior is documented in https://gitlab.com/ngerakines/tavern/-/blob/issue-21-groups/FEDERATION.md as well.

Groups share the same namespace as users and it is up to instance operators to moderate how that namespace is managed. For example, both @sneakerheads@groups.tavern.town, @sneakerheads-group@tavern.town, and @sneakerheads@tavern.town are all valid names. I’m considering adding name filtering via allow-list or regex to give operators more control.

Groups have 3 roles: viewer, member, and owner. Viewers will only receive activities published by the group (Announce/Note). Members will receive published activities and can also submit activities to the group inbox. Owners receive activities, can publish activities, and can also manage the group through the website. Groups can also be configured to auto-accept new followers.

To join a group, an actor must send a Follow/Group activity to the group inbox. If auto-accept is enabled by the group, the actor will receive an Accept/Follow/Group activity and be recorded as a follower of the group with the default membership role.

To leave a group, an actor must send the Undo/Follow/Group activity to the group inbox.

To publish to the group, include the group inbox in the to/cc/bto/bcc fields and ensure the activity is sent to the group’s inbox. Activities received from actors that are not “member” or “owner” will get an authorized error on submission. The group will create an Announce/Object activity and publish it to all followers for the received Create/Note or Announce/Note activity.

Person actors may be invited to a groups.

Side Effects

Privacy

When a group sends the Announce/Object activity to members, the “to” attribute is set to the group’s followers collection. The activity is not intended to be public and only the object id is set. The expectation is that privacy mechanics between actors is meant to be respected and only minimal meta-data shared between group members.

Group Membership Changes

When a member joins or leaves a group, an Update/Collection activity is created for the group follower’s collection and sent to all members of the group. This activity may be throttled to occur less frequently by instance operators (no more than once every 30 minutes to allow batching).

Group Conversation Viewing

The specifics of how group messages and threads are viewed is left to the actors receiving the announcements.

Use Cases

Public Group

A “public group” is a group that is configured to auto-accept new followers with the “member” role.

Broadcast Group

A “broadcast group” is a group that has one central creator of activities, and many followers that are only viewers. New followers may be auto-accepted with the “viewer” role.

This is useful for:

  • Providing “top down” updates for projects, teams, or organizations

Working Group

A “working group” (or committee) is a group has a mix of both viewers and contributors. New followers may be auto-accepted with the “viewer” role.

This is useful for:

  • Public project groups where discussions are visible, but contributions limited to a select number of individuals.

Private Group

A “private group” is a group that is limited to contributors who are invited to the group. New followers are kept as “pending” until accepted, or the group may be invitation only.

TODO

  • How can roles be conveyed in invitations?
  • Can group actors or follower collections be limited to members of the group?
  • Should Join and Leave activities be sent to group members instead of Update/Followers activities?
1 Like

Sending an Update{Collection} is never a good idea. Imagine there’s a group with 50k members. Do you really expect all other instances to refetch the entire collection and diff it with their local copy every time someone joins or leaves? It’s much better to avoid being vague (“something changed, go figure out”) but instead forward the Follow and Undo{Follow} activities.

Yeah, that’s a really good point. I don’t, however, like the idea of forwarding the Follow or Undo/Follow activities because those are the intent of the user, but don’t signal the end result of that intent. I’d rather publish Join or Leave activities.

Just to say out loud what I’m getting from you: Sending the granular activity is a net reduction in overall network activity because I’m “pushing” information that doesn’t require additional action on their part. They aren’t receiving an update and then having to iterate through something like a collection.

Why would a diff be necessary? You defer to remote authority and just replace your local cache entirely.

Yeah sure, deleting 50000 rows from the database and then inserting 50001 new ones because someone joined a remote group feels like a totally sane thing to do. /s

Why not? This is a fundamental freedom of federating applications. You’re not responsible for the behaviors of Federated software. As long as everyone agrees to the meaning behind an Undo/Follow (“remove from collection”, which is a sensible assumption given we haven’t yet had incompatibility arising from misinterpreting ActivityStreams Activities), your app can do the necessary things required to make that an accurate reflection of it’s reality, and someone else in their software can do the same thing. If your software cares about groups for meetups, it can let an organizer know someone decided not to show up. If someone else’s microblog software just treats groups as a discussion, it can use that same interaction to notify its users that someone left the group chat.

Different “intents” between your software and the federated peer’s software. But each taken alone are entirely self-consistent, and taken together are still interoperable despite very different “views” of the Undo/Follow, even if the underlying ActivityStreams manipulation is the same.

You’re never going to be able to force federated software to do what you want, which is both the curse and blessing of such systems. So I wouldn’t optimize for attempting to do that.

[2020-04-02 07:47:39+0000] CJ via SocialHub:

Why not? This is a fundamental freedom of federating applications. You’re not responsible for the behaviors of Federated software. As long as everyone agrees to the meaning behind an Undo/Follow (“remove from collection”, which is a sensible assumption given we haven’t yet had incompatibility arising from misinterpreting ActivityStreams Activities), your app can do the necessary things required to make that an accurate reflection of it’s reality, and someone else in their software can do the same thing. If your software cares about groups for meetups, it can let an organizer know someone decided not to show up. If someone else’s microblog software just treats groups as a discussion, it can use that same interaction to notify its users that someone left the group chat.

Different “intents” between your software and the federated peer’s software. But each taken alone are entirely self-consistent, and taken together are still interoperable despite very different “views” of the Undo/Follow, even if the underlying ActivityStreams manipulation is the same.

You’re never going to be able to force federated software to do what you want, which is both the curse and blessing of such systems. So I wouldn’t optimize for attempting to do that.

True, but cooperating with each others should be a goal in fediverse implementations, otherwise it’s not likely to federate well.

Anyway, back on topic: One thing which could be neat is to be able to somewhat address to a group’s collection (for example if you’re part of it or joining it), thus allowing to pick whether you want stuff like Join/Leave or Follow/Undo being forwarded as a choice to the original actor.

Right, you’re demonstrating that this criticism is one of the whole ecosystem, and not a specific criticism for Follow and Undo/Follow.

There are Add and Remove Activities which might be better, but now we’re talking about what semantics are being captured. And trying to “capture the semantic outcome” was the original intent I was critiquing: the most likely thing everyone will agree on is at user intent, people are less and less likely to agree on outcome, so why make it more difficult on one’s self? Trying to shove nuance into activities is asking for gigantic ontologies as everyone creates their particular flavor, and therefore we arrive at interoperability problems because some splinter group of people want Join{Collection} to mean one thing while the main group of people wanted Add{Collection} to mean the generalized thing.

This is a great discussion. I really appreciate the feedback in this.

I’m really leaning towards using Leave and Join events because it is granular and leaves no ambiguity as to what happened.

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "summary": "",
  "type": "Join",
  "actor": "https://tavern.town/users/nick",
  "object": "https://tavern.town/groups/sneakerheads"
}

When that event is published by the group and sent to group followers (members), then there is no question that the given actor has either left or joined the group. I don’t want to forward the original Follow or Undo/Follow activities because I think the group actor should be the source of truth and these specific activities reflect that more clearly.

It also makes clear that the group entity had a membership change, and reduces the ambiguity about how to represent that action. If the receiving actor wants to do stuff based on the Join or Leave event, then they can. It also avoids the question of how membership is reflected via collections entirely, which is probably the best thing to do to keep it simple for now.

3 Likes

Well, now I think you’re right that Join/Leave from the group actor are better than forwarding I was suggesting. This also opens the way for VK-style “closed” groups: group admins approve new members and have the ability to expel the existing ones (the member list is still public). Can’t do the last part with forwarding because it’s an activity that is, in fact, initiated by the admins acting on behalf of the group itself.

1 Like

there’s potentially many ways to handle it

1 - Follow semantics

Follow Group = member;
Accept Follow Group = approved to be in the group

2 - Join semantics

Join Group = join
Accept Join Group = approved to be in group


both of these options can work for “closed” groups.

for signaling on the network when new members join/leave, you can either forward the Join/Leave activities from the Group actor, or the Group admin can send Add/Remove. the latter potentially works better when someone is kicked from a group, as a Leave activity would potentially be a forgery if attributedTo the Actor who is leaving the Group.

I’ve updated the documentation for how Tavern will implement it: https://gitlab.com/ngerakines/tavern/-/blob/master/FEDERATION.md

Here is the tl;dr:

  • Groups are actors that share the same namespace as users.
  • To join a group, an actor sends a Follow to the group actor. The group can be configured to auto-accept follows, or a group owner/manager can manually accept or reject the follow. Either way, an Accept{Follow} or Reject{Follow} will be returned to the the actor requesting membership in the group, with the caveat that it may not be immediate if members are manually approved.
  • When a member is added to the group, either automatically or manually, a Join activity is published to all members.
  • To leave a group, a member sends an Undo{Follow} activity to the group.
  • When a member leaves (or is otherwise removed from) the group a Leave activity is published to all members.
  • A group member may publish an Invite{Actor} activity to the group and invited actor. When a group a group has received that activity and the invited actor sends a Follow activity to the group, the actor is immediately accepted (triggering the returning Accept{Follow} activity and group-broadcast Join activity to members).
  • To cancel an invitation, an Undo{Invite} activity can be sent to the group.
5 Likes

@ngerakines For me there is a huge difference between “Following” and “Joining”. And unfortunately, for me it looks like you chose the wrong word to describe Joining/Leaving a group :frowning:

Following an Actor means an intent to receive all activities by that Actor (Group being a type of Actor as per Activity vocabulary https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)

Joining a Group means an intent to receive all activities sent to that Group (not by that group). Actors - members of the group may post a lot more than will be sent to the Group. So you will not receive all activities by the Group members.

PS: My related and long thread on groups/lists in different social networks is here: https://github.com/andstatus/andstatus/issues/248

1 Like

I think that understand where you’re coming from, but respectfully I disagree.

Following an actor means you want to receive everything that the actor is publishing. Because the group is the source of the published Announce activity, the mechanic is pretty direct and uncomplicated. Either a group actor allows you to follow it (implying membership), or it does not, and if it does not, you won’t receive it’s announced activities.

Membership is a byproduct of being accepted as a follower or not. In the future, it would be interesting to split read vs. write permissions, but for the time being, the goal is to keep it blindly simple. When the Join or Leave activities are published, they come from the group actor which is the source of truth of membership.

Who told you that creation of Announce activities is the only or even the best way to implement delivery of activities, sent by individual Actors to the members of the Group? :slight_smile:
I think that these announce wrappers are unnecessary. The group may be just a recipient of the activity. The activity that may be forwarded to the Group members transparently.

Yes, I’m coming from a User’s perspective, as I develop a Client app for the last 10 years :slight_smile: )
And I hear that users care more about who and what writes, and not about some abstract group’s activities (“announcements”)…

@yvolk, I’m really not sure how to help you. Good luck.

@ngerakines I thought that you asked for help :slight_smile:

Curious. I’m getting ready to start implementing groups but had a very different approach. Since a group is an Actor, I was thinking they’d work much more like a Person, but with a delegated ability to manage things that Users/Persons can do. So to publish a message to the group, a member would simply post to the group’s outbox, rather than addressing it to the group’s members. The group would then send a Create activity to the members, attributed to the originating actor.

To use an email analogy, groups to me are more like a listserv, whereas relationships are more like the user’s private groups kept in their email client. Am I thinking about this incorrectly?

For c2s functionality, that makes a lot of sense. For what it’s worth, I don’t think that having groups publish announce activities and the group outbox referencing group member’s “activity” conflict with each other. They seem like logical, incremental group actor behaviors.

Although it complicates the initial implementation of groups, I think it is an interesting idea to have the object (Note, Article, Event, etc.) originate from the same server that the group actor originates on, but attribute the content to the (possibly remote) actor. Doing so makes it easier to handle public/private group content and actors have less “work” to do to figure out if they can or can’t see content. Granted, that also means that group actors must implement c2s. I think groups can and should still announce their relevant activities though.