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

MSC3381: Polls (mk II) #3381

Merged
merged 38 commits into from
Dec 5, 2023
Merged
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
32ab69a
Polls (mk II)
turt2live Sep 7, 2021
9dd1990
Merge branch 'main' into travis/msc/polls
turt2live Oct 12, 2021
a2baf26
m.poll.start
turt2live Oct 12, 2021
b9f1a72
m.poll.* types
turt2live Oct 12, 2021
211f591
Allow multiple selections
turt2live Oct 12, 2021
851e7e4
Give moderators access to closing polls
turt2live Oct 12, 2021
b350e4e
Mention freeform edits
turt2live Oct 12, 2021
17b1059
Mention how edits are a security issue
turt2live Oct 12, 2021
b8ceeb9
Enforce minimum and maximum length
turt2live Oct 12, 2021
b455266
Rename open and secret
turt2live Oct 12, 2021
b5347dd
Mention message pinning
turt2live Oct 12, 2021
d2a356b
Update unstable definition for polls
turt2live Oct 12, 2021
4b54d84
Merge branch 'main' into travis/msc/polls
turt2live Nov 23, 2021
9149428
Rework responses into aggregations/annotations
turt2live Nov 23, 2021
73de215
Note how answer IDs should be unique
turt2live Nov 23, 2021
c46929e
Mention MSC3523
turt2live Nov 23, 2021
56f131a
Revert change to annotations
turt2live Nov 24, 2021
7f5f111
Mention how to handle invalid closure events
turt2live Nov 24, 2021
a0b9cf2
Only count the first closure
turt2live Nov 24, 2021
513cd2e
Add push rules
turt2live Jan 3, 2022
c4a1e9c
spelling
turt2live Jan 3, 2022
162f76b
Clarify the extensible events structure
turt2live Jan 3, 2022
22f1c27
Fix wording of answer handling in start event
turt2live Jan 14, 2022
95fdc44
Clarify how the push rules work, and add one for ending polls
turt2live Jan 14, 2022
078fd3c
WIP rewrite of polls to consider content blocks system
turt2live Nov 13, 2022
13ad17a
Merge branch 'main' into travis/msc/polls
turt2live Nov 14, 2022
1af9365
Finish the polls rewrite
turt2live Nov 14, 2022
169afe0
Remove notifications
turt2live Nov 14, 2022
4891ca5
Merge branch 'main' into travis/msc/polls
turt2live Apr 18, 2023
cb0a9fe
Update links and MSC1767 behaviour
turt2live Apr 18, 2023
cd4cb41
Remove excess artifact
turt2live Apr 18, 2023
df1926c
Note UISI problems in potential issues section
turt2live Jun 1, 2023
e0d9065
Note why MSC3523 is a bad idea
turt2live Jun 1, 2023
35ffaf9
Update proposals/3381-polls.md
turt2live Jun 25, 2023
e1084e6
Update MSC
turt2live Jul 4, 2023
a707fc1
Update proposals/3381-polls.md
turt2live Jul 4, 2023
2312620
Typo: event layering
turt2live Dec 5, 2023
2ff9870
Minor clarifications from non-blocking review feedback
turt2live Dec 5, 2023
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
316 changes: 316 additions & 0 deletions proposals/3381-polls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
# MSC3381: Chat Polls

Polls are additive functionality in a room, allowing someone to pose a question and others to answer
until the poll is closed. In chat, these are typically used for quick questionares such as what to
turt2live marked this conversation as resolved.
Show resolved Hide resolved
turt2live marked this conversation as resolved.
Show resolved Hide resolved
have for lunch or when the next office party should be, not elections or anything needing truly
secret ballot.

[MSC2192](https://github.com/matrix-org/matrix-doc/pull/2192) does introduce a different way of doing
polls (originally related to inline widgets, but diverged into `m.room.message`-based design). That
MSC's approach is discussed at length in the alternatives section for why it is inferior.

## Proposal

Polls are to be handled completely client-side and encrypted when possible in a given room. They are
simply started by sending an appropriate event, responded to with more events, and closed (eventually)
by the sender. Servers have no added requirements in order to support this MSC: they simply need to
be able to send arbitrary event types, which they already should be capable of.

The events in this MSC make heavy use of [MSC1767: Extensible Events](https://github.com/matrix-org/matrix-doc/pull/1767).

A poll can be started by sending an `m.poll` room event, similar to the following:
turt2live marked this conversation as resolved.
Show resolved Hide resolved
turt2live marked this conversation as resolved.
Show resolved Hide resolved

```json5
{
"type": "m.poll",
"sender": "@alice:example.org",
"content": {
"m.poll": {
"question": {
"m.text": "What should we order for the party?"
turt2live marked this conversation as resolved.
Show resolved Hide resolved
turt2live marked this conversation as resolved.
Show resolved Hide resolved
},
"kind": "m.open",
turt2live marked this conversation as resolved.
Show resolved Hide resolved
"answers": [
{ "m.text": "Pizza 🍕" },
{ "m.text": "Poutine 🍟" },
{ "m.text": "Italian 🍝" },
{ "m.text": "Wings 🔥" }
]
},
"m.message": [
{
"mimetype": "text/plain",
"body": "What should we order for the party?\n1. Pizza 🍕\n2. Poutine 🍟\n3. Italian 🍝\n4. Wings 🔥"
},
{
"mimetype": "text/html",
"body": "<b>What should we order for the party?</b><ol><li>1. Pizza 🍕</li><li>2. Poutine 🍟</li><li>3. Italian 🍝</li><li>4. Wings 🔥</li></ol>"
}
]
},
// other fields that aren't relevant here
}
```

As mentioned above, this is already making use of Extensible Events: The fallback for clients which don't
know how to render polls is to just post the message to the chat. Some of the properties also make use of
extensible events within them, such as the `question` and the elements of `answers`: these are effectively
`m.message` events (under the Extensible Events structure), which means they're required to have a plain
text component to them. HTML is allowed, though clients are generally encouraged to rely on the plain text
representation for an unbiased rendering. Meme value of HTML might be desirable to some clients, however.

The `kind` refers to whether the poll is "secret" or "open". Secret polls reveal the results after the poll
has closed while open polls show the results at any time (or, if the client prefers, immediately after the
user has voted). These translate to `m.secret` and `m.open` under this MSC, though custom values using the
standardized naming convention are supported. Unknown values are to be treated as `m.secret` for maximum
compatibility with theoretical values. More specific detail as to the difference between open and secret
polls comes up later in this MSC.

There is no limit to the number of `answers`, though more than 20 is considered bad form. Clients should
truncate the list at no less than 20. Similarly, there is no minimum though a poll of zero or one options
turt2live marked this conversation as resolved.
Show resolved Hide resolved
is fairly useless - clients should render polls with less than 2 options as invalid or otherwise unvotable.
Most polls are expected to have 2-8 options.

The `m.message` fallback should be representative of the poll, but is not required and has no mandatory
format. Clients are encouraged to be inspired by the example above when sending poll events.

To respond to a poll, the following event is sent:

```json5
{
"type": "m.poll.response",
"sender": "@bob:example.org",
"content": {
"m.relates_to": {
"rel_type": "m.reference", // from MSC2675: https://github.com/matrix-org/matrix-doc/pull/2675
"event_id": "$poll"
},
"m.poll.response": {
"answer": 2 // index of the answers array selected (zero-indexed)
turt2live marked this conversation as resolved.
Show resolved Hide resolved
turt2live marked this conversation as resolved.
Show resolved Hide resolved
}
},
// other fields that aren't relevant here
}
```
turt2live marked this conversation as resolved.
Show resolved Hide resolved

Like `m.poll`, this `m.poll.response` event supports Extensible Events. However, it is strongly discouraged
for clients to include renderable types like `m.text` and `m.message` which could impact the usability of
the room (particularly for large rooms with lots of responses). The relationship is a normal MSC2675 reference
relationship, avoiding conflicts with message reactions described by [MSC2677](https://github.com/matrix-org/matrix-doc/pull/2677).

**XXX**: It is almost certainly ideal if the server can aggregate the poll responses for us, but MSC2677
crushes the source event type out of the equation, considering only the `key`. If MSC2677 were to consider
aggregating/grouping by event type and then by `key`, we could maintain the deliberate feature of being able
to react to polls while also aggregating poll responses.
turt2live marked this conversation as resolved.
Show resolved Hide resolved

Users can vote multiple times, however only the user's most recent vote (by timestamp) shall be considered
by the client when calculating results. Votes are accepted until the poll is closed (again, by timestamp).

The `answer` field is the zero-indexed position from the original `answers` array. Out of range or otherwise
invalid values must be considered a spoiled vote by a client. Spoiled votes are also how a user can "un-vote"
from a poll - redacting the vote event would cause the vote to become spoiled.

Only the poll creator can close a poll. It is done as follows:
turt2live marked this conversation as resolved.
Show resolved Hide resolved
```json5
{
"type": "m.poll.end",
"sender": "@bob:example.org",
"content": {
"m.relates_to": {
"rel_type": "m.reference", // from MSC2675: https://github.com/matrix-org/matrix-doc/pull/2675
"event_id": "$poll"
},
"m.poll.end": {},
"m.text": "The poll has ended. Top answer: Poutine 🍟"
turt2live marked this conversation as resolved.
Show resolved Hide resolved
},
// other fields that aren't relevant here
}
```

Once again, Extensible Events make an appearance here. There's nothing in particular metadata wise that
needs to appear in the `m.poll.end` property of `content`, though it is included for future capability. The
backup `m.text` representation is for fallback purposes and is completely optional with no strict format
requirements: the example above is just that, an example of what a client *could* do. Clients should be
careful to include a "top answer" in the end event as server lag might allow a few more responses to get
through while the closure is sent. Votes sent on or before the end event's timestamp are valid votes - all
others must be disregarded by clients.

**Rationale**: Although clock drift is possible, as is clock manipulation, it is not anticipated that
polls will be closed while they are still receiving high traffic. There are some cases where clients might
apply local timers to auto-close polls, though these are typically used in extremely high traffic cases
such as Twitch-style audience polls - rejecting even 100 responses is unlikely to significantly affect
the results. Further, if a server were to manipulate its clock so that poll responses are sent after the
poll was closed, but timestamped for when it was open, the server is violating a social contract and likely
turt2live marked this conversation as resolved.
Show resolved Hide resolved
will be facing a ban from the room. This MSC does not propose a mitigation strategy beyond telling people
not to ruin the fun. Also, don't use polls for things that are important.

Clients should disable voting interactions with polls once they are closed. Events which claim to close
the poll from senders other than the creator are to be treated as invalid and thus ignored.

### Open polls

These are most similar to what is seen on Twitch and often Twitter: members of the room are able to see
the results and vote accordingly. Clients are welcome to hide the poll results until after the user has
voted to avoid biasing the user.

Once the poll ends, the results are shown regardless.

### Secret polls
turt2live marked this conversation as resolved.
Show resolved Hide resolved

turt2live marked this conversation as resolved.
Show resolved Hide resolved
With these polls, members of the room cannot see the results of a poll until the poll ends, regardless
of whether or not they've voted. This is enforced visually and not by the protocol given the votes
turt2live marked this conversation as resolved.
Show resolved Hide resolved
are sent to the room for local tallying - this is considered a social issue rather than a technical one.
Don't go spoiling the results if the sender didn't intend for you to see them.

The poll results should additionally be hidden from the poll creator until the poll is closed by that
creator.

## Potential issues

As mentioned, poll responses are sent to the room regardless of the kind of poll. For open polls this
isn't a huge deal, but it can be considered an issue with secret polls. This MSC strongly considers the
problem a social one: users who are looking to "cheat" at the results are unlikely to engage with the
poll in a productive way in the first place. And, of course, polls should never be used for something
important like electing a new leader for a country.

Poll responses are also de-anonymized by nature of having the sender attached to a response. Clients
are strongly encouraged to demonstrate anonymization by not showing who voted for who, but might want
to warn/hint at the user that their vote is not anonymous. For example, saying "22 total responses,
including from TravisR, Matthew, and Alice" before the user votes.
turt2live marked this conversation as resolved.
Show resolved Hide resolved

Limiting polls to client-side enforcement could be problematic if the MSC was interested in reliable
or provable votes, however as a chat feature this should reasonably be able to achieve user expectations.
Bolt-on support for signing, verification, validity, etc can be accomplished as well in the future.

The fallback support relies on clients already knowing about extensible events, which might not be
the case. Bridges (as of writing) do not have support for extensible events, for example, which can
mean that polls are lost in transit. This is perceived to be a similar amount of data loss when a Matrix
user reacts to an IRC user's message: the IRC user has no idea what happened on Matrix. Bridges, and
other clients, can trivially add message parsing support as described by extensible events to work
around this. The recommendations of this MSC specifically avoid the vote spam from being bridged, but
the start of poll and end of poll (results) would be bridged. There's an argument to be made for
surrounding conversation context being enough to communicate the results without extensible events,
though this is slightly less reliable.

Though more important for Extensible Events, clients might get confused about what they should do
with the `m.message` parts of the events. For absolute clarity: if a client has support for polls,
it can outright ignore any irrelevant data from the events such as the message fallback or other
representations that senders stick onto the event (like thumbnails, captions, attachments, etc).

turt2live marked this conversation as resolved.
Show resolved Hide resolved
## Alternatives
turt2live marked this conversation as resolved.
Show resolved Hide resolved

The primary competition to this MSC is the author's own [MSC2192](https://github.com/matrix-org/matrix-doc/pull/2192)
which describes not only polls but also inline widgets. The poll implementation in MSC2192 is primarily
based around `m.room.message` events, using `msgtype` to differentiate between the different states. As
[a thread](https://github.com/matrix-org/matrix-doc/pull/2192/files#r514497274) notes on the MSC, this
is an awful experience on clients which do not support polls properly, leaving an irritating amount of
contextless messages in the timeline. Though not directly mentioned on that thread, polls also cannot be
closed under that MSC which leads to people picking options hours or even days after the poll has "ended".
This MSC instead proposed to only supply fallback on the start and end of a poll, leading to enough context
for unsupporting clients without flooding the room with messages.

Originally, MSC2192 was intended to propose polls as a sort of widget with access to timeline events
and other important information, however the widget infrastructure is just not ready for this sort of
thing to exist. First, we'd need to be able to send events to the widget which reference itself (for
counting votes), and allow the widget to self-close if needed. This is surprisingly difficult when widgets
can be "popped out" or have a link clicked in lieu of rendering (for desktop clients): there's no
communication channel back to the client to get the information back and forth. Some of this can be solved
with scoped access tokens for widgets, though at the time of writing those are a long ways out. In the
end, it's simply more effective to use Extensible Events and Matrix directly rather than building out
the widgets infrastructure to cope - MSC2192 is a demonstration of this, considering it ended up taking
out all the widget aspects and replacing them with fields in the content.

Finally, MSC2192 is simply inferior due to not being able to restrict who can post a poll. Responses
and closures can also be limited arbitrarily by room admins, so clients might want to check to make
sure that the sender has a good chance of being able to close the poll they're about to create just
to avoid future issues.

## Security considerations

As mentioned a multitude of times throughout this proposal, this MSC's approach is prone to disclosure
of votes and has a couple abuse vectors which make it not suitable for important or truly secret votes.
Do not use this functionality to vote for presidents.

Clients should apply a large amount of validation to each field when interacting with polls. Event
bodies are already declared as completely untrusted, though not all clients apply a layer of validation.
In general, this MSC aims to try and show something of use to users so they can at least figure out
what the sender intended, though clients are also welcome to just hide invalid events/responses (with
the exception of spoiled votes: those are treated as "unvoting" or chosing nothing). Clients are
encouraged to try and fall back to something sensible, even if just an error message saying the poll
is invalid.

## Unstable prefix

While this MSC is not eligible for stable usage, the `org.matrix.msc3381.` prefix can be used in place
of `m.`. Note that extensible events has a different unstable prefix for those fields.

The 3 examples above can be rewritten as:

```json5
{
"type": "org.matrix.msc3381.poll",
"sender": "@alice:example.org",
"content": {
"org.matrix.msc3381.poll": {
"question": {
"org.matrix.msc1767.text": "What should we order for the party?"
},
"kind": "m.open",
"answers": [
{ "org.matrix.msc1767.text": "Pizza 🍕" },
{ "org.matrix.msc1767.text": "Poutine 🍟" },
{ "org.matrix.msc1767.text": "Italian 🍝" },
{ "org.matrix.msc1767.text": "Wings 🔥" }
]
},
"org.matrix.msc1767.message": [
turt2live marked this conversation as resolved.
Show resolved Hide resolved
{
"mimetype": "text/plain",
"body": "What should we order for the party?\n1. Pizza 🍕\n2. Poutine 🍟\n3. Italian 🍝\n4. Wings 🔥"
},
{
"mimetype": "text/html",
"body": "<b>What should we order for the party?</b><ol><li>1. Pizza 🍕</li><li>2. Poutine 🍟</li><li>3. Italian 🍝</li><li>4. Wings 🔥</li></ol>"
}
]
},
// other fields that aren't relevant here
}
```

```json5
{
"type": "org.matrix.msc3381.poll.response",
"sender": "@bob:example.org",
"content": {
"m.relates_to": {
"rel_type": "m.reference", // from MSC2675: https://github.com/matrix-org/matrix-doc/pull/2675
"event_id": "$poll"
},
"org.matrix.msc3381.poll.response": {
"answer": 2 // index of the answers array selected (zero-indexed)
}
},
// other fields that aren't relevant here
}
```

```json5
{
"type": "org.matrix.msc3381.poll.end",
"sender": "@bob:example.org",
"content": {
"m.relates_to": {
"rel_type": "m.reference", // from MSC2675: https://github.com/matrix-org/matrix-doc/pull/2675
"event_id": "$poll"
},
"org.matrix.msc3381.poll.end": {},
"org.matrix.msc1767.text": "The poll has ended. Top answer: Poutine 🍟"
},
// other fields that aren't relevant here
}
```
turt2live marked this conversation as resolved.
Show resolved Hide resolved

Note that the extensible event fallbacks did not fall back to `m.room.message` in this MSC: this
is deliberate to ensure polls are treated as first-class citizens. Client authors not willing/able
to support polls are encouraged to instead support Extensible Events for better fallbacks.