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 10 commits
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
346 changes: 346 additions & 0 deletions proposals/3381-polls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
# 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.start` room event, similar to the following:

```json5
{
"type": "m.poll.start",
"sender": "@alice:example.org",
"content": {
"m.poll.start": {
"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.poll.disclosed",
"max_selections": 1,
"answers": [
{ "id": "pizza", "m.text": "Pizza 🍕" },
{ "id": "poutine", "m.text": "Poutine 🍟" },
{ "id": "italian", "m.text": "Italian 🍝" },
{ "id": "wings", "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
}
turt2live marked this conversation as resolved.
Show resolved Hide resolved
```

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's votes are disclosed while the poll is still open. `m.poll.undisclosed`
means the results are revealed once the poll is closed. `m.poll.disclosed` is the opposite: the votes are
visible up until and including when the poll is closed. Custom values are permitted using the standardized
naming convention are supported. Unknown values are to be treated as `m.poll.undisclosed` for maximum
compatibility with theoretical values. More specific detail as to the difference between two polls come up
later in this MSC.

`answers` must be an array with at least 1 option and no more than 20. Lengths outside this range are invalid
and must not be rendered by clients. Most polls are expected to have 2-8 options. The answer `id` is an
arbitrary string used within the poll events. Clients should not attempt to parse or understand the `id`.

turt2live marked this conversation as resolved.
Show resolved Hide resolved
`max_selections` is optional and denotes the maximum number of responses a user is able to select. Users
can select fewer options, but not more. This defaults to `1`. Cannot be less than 1.

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": {
"answers": [
"poutine",
]
}
},
// other fields that aren't relevant here
}
```
turt2live marked this conversation as resolved.
Show resolved Hide resolved

Like `m.poll.start`, 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 `answers` array in the response is the user's selection(s) for the poll. Clients should only consider
the first `max_selections` worth of entries as valid: anything beyond that is simply ignored. The entries
are the `id` of each answer from the original poll start event. If *any* of the supplied answers is unknown,
or the field is otherwise invalid, then the user's vote is spoiled. Spoiled votes are also how users can
"un-vote" from a poll - redacting, or setting `answers` to an empty array, will spoil that user's vote.

Only the poll creator or anyone with a suitable power level for redactions can close the poll. The rationale
for using the redaction power level is to help aid moderation efforts: while moderators can just redact the
original poll and invalidate it entirely, they might prefer to just close it and leave it on the historical
record.

Closing a poll is done as follows:

```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.

### Disclosed versus undisclosed polls
turt2live marked this conversation as resolved.
Show resolved Hide resolved

Disclosed polls 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.

Undisclosed polls do track who voted for what, though don't reveal the results until the poll has been
closed, even after a user has voted themselves. This is enforced visually and not by the protocol given
the votes are sent to the room for local tallying - this is considered more of a social trust issue than
a technical one. This MSC expects that rooms (and clients) won't spoil the results of an undisclosed poll
before it is closed.

turt2live marked this conversation as resolved.
Show resolved Hide resolved
In either case, once the poll ends the results are shown regardless of kind. Clients might wish to avoid
disclosing who voted for what in an undisclosed poll, though this MSC leaves that at just a suggestion.
Comment on lines +243 to +244
Copy link
Member Author

Choose a reason for hiding this comment

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

There is a desire to have clients conditionally show who voted for what along the way, described by a potential show_participants field on the poll object here. Currently, the MSC deliberately leaves it as an implementation detail for whether the client shows who voted for what. Element has clearly decided to implement it as not showing this information, though given the PR suggestion I do wonder if it should be conditional, changed in this MSC, or still left as an implementation detail.

@arnfriedgriesert did you have suggestions on this as the original PR author?

Copy link

@gnunicorn gnunicorn Jun 1, 2023

Choose a reason for hiding this comment

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

What makes show_participants inherently different from e.g. randomize_order, show_results or allow_change that this should be included, why the others should not as cited earlier?

Copy link
Member Author

Choose a reason for hiding this comment

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

The other flags were already linked in a thread whereas this new one wasn't. The scope difference is subtle: I'm generally leaning towards not adding new flags and instead codifying the behaviour where we can.

Additional flags to control behavior are certainly possible. The further those flags are from the core of the MSC though, the more ideal it is to have them as dedicated MSCs. show_participants is challenging a core component of the MSC.

Copy link
Member Author

Choose a reason for hiding this comment

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

ftr - this doesn't appear to have prevented FCP, so I'm assuming this is safely future MSC territory.


## 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 undisclosed 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 should consider
warning the user that their vote is not anonymous. For example, saying "22 total responses, including
from TravisR, Matthew, and Alice" before the user votes.

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.

Users should be wary of polls changing their question after they have voted. Considering polls can be
edited, responses might no longer be relevant. For example, if a poll was opened for "do you like
cupcakes?" and you select "yes", the question may very well become "should we outlaw cupcakes?" where
your "yes" no longer applies. This MSC considers this problem more of a social issue than a technical
one, and reminds the reader that polls should not be used for anything important/serious at the moment.
turt2live marked this conversation as resolved.
Show resolved Hide resolved

## Future considerations

Some aspects of polls are explicitly not covered by this MSC, and are intended for another future MSC
to solve:

* Allowing voters/room members to add their own freeform options. The edits system doesn't prevent other
members from editing messages, though clients tend to reject edits which are not made by the original
author. Altering this rule to allow it on some polls could be useful in a future MSC.

* Verifiable or cryptographically secret polls. There is interest in a truly enforceable undisclosed poll
where even if the client wanted to it could not reveal the results before the poll is closed. Approaches
like [MSC3184](https://github.com/matrix-org/matrix-doc/pull/3184) or Public Key Infrastructure (PKI)
might be worthwhile to investigate in a future MSC.
turt2live marked this conversation as resolved.
Show resolved Hide resolved

## 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.