Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC3847: Ignoring invites with policy rooms #3847

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions proposals/3847-ignoring-invites-with-policy-rooms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# MSC3847: Ignoring invites using individual policy rooms

Receiving unwanted invites is something that users currently need to live
with on the Matrix network. To alleviate this, Matrix needs a mechanism to
let users specify that they are not interested in invites from specific
users or towards specific rooms.

In this proposal, we introduce the necessary extensions to let users do
this and to let clients perform filtering of unwanted invites.

We proceed as follows:

1. We build upon the mechanism of policy rooms, defined in MSC2313, and define
Yoric marked this conversation as resolved.
Show resolved Hide resolved
a user's individual policy room, which may be created on behalf of the user by
a Matrix client, and which is shared across all devices and clients of the user.
2. We build upon the mechanism of recommendations, also defined in MSC2313,
and create a new recommendation for ignoring invites from a specific user, from
a specific server or towards a specific room.


## Proposal

### Background

MSC2313 defines policy rooms. A policy room is a room in which rules such
Yoric marked this conversation as resolved.
Show resolved Hide resolved
as the following may be published:

```jsonc
{
"type": "m.policy.rule.user", // Or `m.policy.rule.server` or `m.policy.rule.room`.
"state_key": "rule_1", // Arbitrary.
"content": {
"entity": "@alice:example.org",
"recommendation": "m.ban",
"reason": "undesirable behaviour"
}
}
```

Policy rooms are designed to be associated with entire servers, communities,
individual rooms or individual users, but there is no specification in MSC2313
clarifying how to associate an issuer with a policy room.

### Associating a policy room with a user

For individual users, we introduce a new event `m.policies`, designed
to be used as part of account data. This event has content:

| Content | Type | Description |
|---------|------|-------------|
| `room` | Room ID or Alias | The main room in which a user may publish policies to be applied on their behalf. |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better to make this an array, for decentralized reputation reasons.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the idea was to add the array as a followup MSC, for decentralized reputation reasons, and keep this MSC simpler by focusing on a single source.

Regardless of whether it's a single source or several, we need one of them to be primary. Otherwise, the client that is used to issue policy events won't know where to send these events. I suppose that I could arbitrarily pick the first from the array, but that feels really fragile.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client can make decisions about which one to post to based on how the room is structured. If someone subscribes to the code of conduct list on matrix.org then they won't have write access, but if the client sees a room that the user created then it can publish there.

Arrays are ordered in JSON, so would heavily rely on that as well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client can make decisions about which one to post to based on how the room is structured. If someone subscribes to the code of conduct list on matrix.org then they won't have write access, but if the client sees a room that the user created then it can publish there.

Arrays are ordered in JSON, so would heavily rely on that as well.

Sure, we could make the information implicit (or even use a heuristic such as "first room in the array in which we can write state events"), but I feel that this is much more brittle. For instance, if a user suddenly becomes homeserver moderator and gains rights to the homeserver's coc room, we don't want this to accidentally change into which room they store their individual policies.

So, if we introduce an array, I strongly believe that we need to make an explicit difference between the target room and the source rooms.


We expect that future MSCs will expand upon this event `m.policies` and add
other rooms where policies are published by other users or communities but
that the current user also wish to apply, e.g. for distributing trust. This
is, however, beyond the scope of the current proposal.

The expected behavior is that if a user Alice has a `m.policies` with `room` R,
then:

- whenever Alice issues a new policy from their client or another trusted agent,
this policy will be stored in room R;
- any client or trusted agent acting on behalf of Alice will monitor room R for
new policies and apply the recommendations on behalf of Alice.

### A recommendation for ignoring invites

We expand the **`enum`** `recommendation` with the following value

| Value | Description |
|---------|-------------|
| `m.invites.ignore` | The user's client should not display any invite from/to the entity specified in `entity`. |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're introducing a new recommendation, let's shorten it to m.ignore to retain the generic nature of recommendations.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the objective is specifically to ignore invites. What would we gain by making it more vague?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do we gain by being overly restrictive? :P

The original design of policy rooms and the recommendation is that we would only ever need ~5 to cover everything, because we deliberately and intentionally do not assign behaviour to the recommendation - we assign ideas and suggestions. m.ignore suggests that the implementation ignore the described entity, but doesn't explain how or why that might be done. Context clues, like "here are the policy lists I want you to ignore invites from", help inform a better understanding of what to do with that policy rule/room. Making it specific just means we need more MSCs to introduce more recommendations.

Copy link
Author

@Yoric Yoric Jul 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the rationale.

My objective is to let users click on "ignore invites" in the UI of the client and have invites ignored. If the user clicks on "ignore invite", why would they want the client to do anything other than ignoring invites?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the UX level we wouldn't be changing anything. At the technical (spec) level, the context of "ignore these invites" comes from how the lists are used, not the explicit recommendation. We could still have a m.ban recommendation on all the rules and end up ignoring the invites: the client will have told the server that all rules in the listed policy rooms are for ignoring invites. Alternatively phrased: "please ban these entities from my invite list", "ban this room from showing up as an invite", etc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it has been useful in the past, yea. It's how the original Mjolnir bot, synapse module, and Element Web implementation all did different things with exactly the same information.

Copy link
Author

@Yoric Yoric Jul 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the latest version of my understanding, based on our conversation, is that "policy lists" are not about policies at all, and could just have well be named "entity lists". Also, that recommendation is actually kind of useless?

Am I correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're still policy lists containing rules. Eventually we want to put non-entity rules in there as well, which is where the recommendation also comes in.

The original design for policy lists included future proofing for decentralized reputation, access control, and more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see what I can do.
Note that we still need the ability to have a "main" room in which to issue these rules.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've given it a few days trying to work things out on paper and I believe that, while context + rule is one way to express semantics, this is the wrong direction to head.

Option 1: One recommendation per semantics

In this case, this means adding a new recommendation m.invite.ignore or m.ignore.invite, rather than one new context of policy room.

Adding one recommendation per semantics has essentially a linear cost in terms of both specification size and cognitive load: if we want one new behavior, we add one recommendation and one paragraph in the spec.

It might make it harder to reuse lists in contexts for which they were not intended. For the time being, I don't see any real benefit to this.

Option 2: Reuse recommendations, introduce new meta-policies

By meta-policies, I mean ToS, CoC, specifying m.ignore.invites as a key in m.policies, etc.

Essentially, adding a new behavior to solve a problem means:

  • either introduce a new meta-policy; or
  • when a meta-policy is not sufficient to represent the expected behavior (e.g. numeric opinions, allow lists), introduce a new recommendation.

This has an explosive cost, both in terms of specification size and in terms of cognitive load:

  • harder to determine whether we need to introduce a new meta-policy or a new recommendation;
  • whenever we introduce either a meta-policy or a new recommendation, we need to specify all the semantics for each (meta-policy, recommendation) pair and one paragraph in the spec for each pair — or worse, not specify it at all, leaving it undocumented territory;
  • similarly, when implementing a tool that works with policy lists, this means more unexpected combinations to handle, which makes it harder to write, test and trust.

Costs for both options are in addition to whatever specifications we're going to need for composition if/when policies start becoming non-trivial (e.g. allow lists and block lists, numeric opinions, etc.)


In particular, if Alice has a policy with `recommendation` `m.invites.ignore`:
Yoric marked this conversation as resolved.
Show resolved Hide resolved

- if `type` is `m.policy.rule.user` and `entity` is Bob's User ID, Alice's clients will not display any invite issued by Bob;
- if `type` is `m.policy.rule.room` and `entity` is the Room ID or alias of room Bobroom, Alice's clients will not display any invite issued to Bobroom;
- if `type` is `m.policy.rule.server` and `entity` is the server name of server Bobverse, Alice's clients will not display any invite issued from any account from Bobverse or towards a room alias on the Bobverse.

### Client behaviour

If a new policy `m.invites.ignore` appears in Alice's individual policy room:

- any pending invite currently displayed that matches the `entity` is removed from display;
- any incoming invite that matches the `entity` is not displayed among invites.

However, clients are expected to offer the ability to look at any ignored invite,
in a manner similar to a trashcan/recycle bin/undelete mechanism for desktop file
systems.

Similarly, if a policy `m.invites.ignore` is redacted/amended, clients should show any
invite that doesn't match any `m.invites.ignore` entity anymore.

### Server behavior

As recommended in MSC2313, if a policy `m.ban` appears in Alice's individual policy room:

- if `type` is `m.policy.rule.user`, ignore any message or invite from the user `entity`, as per `m.ignored_users`;
- if `type` is `m.policy.rule.room`, ignore any message in the room or invite from the room `entity`;
- if `type` is `m.policy.rule.server`, ignore any message in any room on server `entity`, any message from any user on server `entity`, any invite towards any room on server `entity`, any invite from any user on server `entity`.

## Potential issues

### Number of events

There is a risk that the list of ignored invites of some users may grow a lot, which might have
performance impact, both during initial sync and during filtering. We believe that this risk is
limited. If necessary, clients may decide to cleanup ignored invites after some time.

### Sync size

With this proposal, any invite ignored with `m.invites.ignore` will still show up at each `/sync`.
In time, this can grow expensive.

If necessary, clients may decide to convert ignored invites into rejected invites or `m.ban`
after a while.

## Alternatives

### Server-side filtering

Just as `m.ignored_users_list` is handled mostly on the server, we could handle `m.invites.ignore`
largely on the server. However, this would make it much harder to undo erroneous ignores (i.e.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to mention #3659 here which does the filtering on the server.

Copy link

@joshqou joshqou Jun 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Johennes Both specs aim to fix the same problem but #3659 drops invites as opposed to ignoring them. imo dropping invites is better than a client-side ignore since it wouldn't require all a user's clients to support the spec, even if such behaviour is more "destructive".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I merely meant to say that mentioning #3659 here would be nice for context on prior art, not necessarily that I'd prefer one over the other. On that matter, filtering invites on the server sounds more efficient but it won't allow you to review ignored invites (and possibly even unignore them). So both seem to have pros and cons.

implementing some sort of recovery from trashcan) on the client.

So we prefer handling things on the client, even if this may require more traffic.

## Security considerations

Can't think of any right now.
Yoric marked this conversation as resolved.
Show resolved Hide resolved

## Unstable prefix

During testing:

- `m.invites.ignore` should be prefixed `org.matrix.msc3847.invites.ignore`;
- `m.policies` should be prefixed `org.matrix.msc3847.policies`.