FEP-9967: Polls

According to the comment in my code, it was Pleroma.

It is a Mastodon idiosyncrasy. As far as I know, no other implementation has this limitation.

Is this not simply https://www.w3.org/ns/activitystreams#closed? Activity Vocabulary

It’s rather awkward due to the range being described as a union of Object, Link, xsd:dateTime, and xsd:boolean
 but it is a property defined by AS2 I guess. And in the context document, the term definition for closed says that it’s specifically a datetime: as:closed is wrongly defined in the normative context as only being xsd:datetime · Issue #542 · w3c/activitystreams · GitHub

So the least problematic usage of it right now is to use closed as “the datetime when the Question was closed”. (It could possibly be applied to more than just Question as well, which Expand the domain of `closed` to be applicable to more than just `Question`? · Issue #594 · w3c/activitystreams · GitHub explores, but in the context of this FEP this shouldn’t be an issue.)

Technically the initial implementation of polls was entirely a Mastodon idiosyncracy, although ostensibly based on a non-normative example from AS2-Vocab regarding how one might use Question: Activity Vocabulary

Note that in the same section, there are multiple ways to use Question, not limited strictly to polls. By default, a Question is open-ended, and you could liken them to StackOverflow posts or Tumblr asks. Mastodon assumes that oneOf/anyOf is what makes it a “poll”, although the matching of a “poll response” to a “poll option” is pretty weird and involves a sort of name-based indexing or possibly an identity-by-description (not using id). The reason Mastodon polls don’t support image attachments is partly because Mastodon was unsure how to deal with poll responses referencing those image attachments, and partly because Mastodon’s UX makes it awkward to stuff images into poll options without affordances like expanding the image or having multiple representations of an image.

Aside from UX concerns, there are protocol concerns with possibly invalidating all previous votes if something about the image attachment(s) is different.


I think the FEP covers a lot of the history and contrast with intended usage, although it doesn’t fully cover the oddities and idiosyncracies when discussing requirements.

  • It mentions Create(Question) vs using Question as an IntransitiveActivity.
  • Voting involves Create(Note) that is inReplyTo the Question, but somehow ends up in the replies of the poll options indirectly? (via replies.totalItems on each option)
    • replies.totalItems is already pretty weird and imprecise too
 in a cleaner re-implementation it would probably make more sense to just have a :voteCount on the option itself.

@silverpill One suggested correction to the FEP:

Question objects MAY have an endTime property indicating the time when the poll ends. Some implementations use closed property instead of endTime or in addition to it (even if the poll is open). Consumers MUST process closed in the same way as endTime.

endTime is when the poll ends, but closed is when the poll closed. They are not the same. A poll with endTime but no closed is by default still open. A poll with closed is closed. I think the logical equivalence you’re looking for is when endTime is in the past, you can maybe infer that the poll closed at that same time?

Also would it make sense to mention Mastodon’s extension properties? http://joinmastodon.org/ns#votersCount in particular is used to disambiguate participation statistics for multiple-choice polls (anyOf). A poll with 3 options that has 5, 7, and 8 votes out of 10 total participants would show percentages of 50%, 70%, and 80% (instead of 25%, 35%, and 40%).

Other feedback:

  • I’m not sure why it requires options to have a type of Note specifically and nothing else. Mastodon serializers will publish this, but it’s not required or used in any way during consumption. Similar for Collection as the type of the replies.
  • “The [Note/vote] object MUST NOT have a content property.” Why not? I assume it’s to differentiate it from a “post”, but there’s no conflict if the same thing is both a vote and a post.
    • “The method of identifying votes described in this document is not reliable because a reply can look exactly like a vote.” This is only a problem if you think votes and replies are somehow disjoint, when they are both a type of response using inReplyTo. It’s just that votes use name and posts use content (although they can also use name).
  • “If the poll is anonymous, this activity MUST NOT be delivered to anyone else.” What does “anonymous” mean here? I would assume “anonymous” is something decided by the poll author and not by the poll participant, since the poll author could choose to expose or not expose responses (modulo authenticity and trust). It would otherwise be the decision of the poll participant if they want to tell others that they voted or how they voted, just like with anything else they might choose to publish.
  • “When a poll allows multiple choices, each vote MUST be sent as a separate activity.” How do you differentiate between someone voting for multiple options, versus someone changing their vote or voting multiple times? I assume you could devise some mechanism using Undo activities, but that’s complicated and yields a confusing concept of “partial votes”.
2 Likes

Do you know any fedi platform that allows closing a poll before the end time? From what I saw, closed is always redundant.

Yes, I probably should mention it, but I don’t understand how it is used. So it affects the displayed percentages in Mastodon? How they are calculated? Why would they want percentages to not add up to 100%?

Other implementations might expect Note, so I don’t recommend changing the type.

I think they should be disjoint. I doubt that any implementation allows votes with comments.

I agree. This should be not a requirement, but a description: “this activity is not delivered to anyone else.”

As far as I know, the implementation of a poll described in this FEP does not allow changing votes. Alternative implementations could be mentioned to the “Future work” section.

@trwnh Do you know if Mastodon puts a limit on the number of poll options in incoming polls? I tried to find it in the code but couldn’t.

Limits on incoming polls

It seems to be 500 poll options, as of v4.5.5 / v4.4.12 / v4.3.18.

As best as I can tell, the initial PR describes local limits of 4 options and 25 characters per option. There was some talk of enforcing remote limits of 10 options, but I don’t think that ever got implemented. I have definitely seen polls with over 20 options from my Mastodon account. The local limits were later raised in https://github.com/mastodon/mastodon/pull/13257 to 50 characters per option. The current local validators are located at https://github.com/mastodon/mastodon/blob/bdad4f78f309af6ac439dac4f7705818550e7c08/app/validators/poll_options_validator.rb and https://github.com/mastodon/mastodon/blob/bdad4f78f309af6ac439dac4f7705818550e7c08/app/validators/poll_expiration_validator.rb.

Incoming polls pass through the Create handler’s process_poll function which validates the poll by attempting to parse it successfully. The poll parser contains all the logic for parsing incoming polls, as far as I can tell. There is a line declaring MAX_ITEMS = 500 which git blame attributes to Merge commit from fork · mastodon/mastodon@028e76f · GitHub and seems to be present in mastodon/FEDERATION.md at bdad4f78f309af6ac439dac4f7705818550e7c08 · mastodon/mastodon · GitHub as well. Looking through release changelogs, I found Denial of Service from a single post (client/server) · Advisory · mastodon/mastodon · GitHub which describes a DoS from trying to process too many poll options.


When a poll is “closed”

My view is that since both are optional, it may be that a poll had no specific endTime but was later closed manually. Or the poll may be temporarily closed before the endTime has been reached. It seems like a good practice to explicitly mark when the poll is closed instead of having people assume it from endTime being in the past. If I am interested in knowing whether the poll is closed or not, I am not necessarily interested in the exact endTime. You could even have a situation where when the poll ends, endTime is replaced by closed.

(This isn’t to say that endTime implies the poll is still open, but that it implies different things depending on whether it is in the past or the future relative to the current time – kind of like the predicates “grue” and “bleen”, if you’re familiar with that problem. TLDR: “grue” is equivalent to green before time t, and equivalent to blue after time t. The perceived color might change, but it is always “grue” independent of time. In much the same way, the poll is “open” before t == endTime and “closed” after t == endTime.)

(And this isn’t even getting into clock skew
)


Voter counts vs vote counts, when voters are allowed to pick more than 1 option

The multiple choice “anyOf” poll inherently allows people to select multiple options. This means that the total number of options selected isn’t always relevant; what’s relevant is how many people selected which option. So to contextualize the raw numbers, you don’t say “25% of the votes went to option A”, you say “50% of the voters approved of option A”. You can’t calculate these percentages without knowing the total number of voters (as distinct from the total number of votes).

Basically, if the poll is multiple choice (uses as:anyOf), then Mastodon will use the toot:votersCount to calculate the percentages. If the poll is single choice (uses as:oneOf), then you could infer the voter count from the vote count, since you assume each voter only voted for 1 option. Or you could just use the vote count directly, under the same assumption. The assumption doesn’t hold when voters are allowed to vote for more than 1 option.


Typing poll options and poll responses

I haven’t surveyed what any particular implementation does, but I would not recommend consumers to attempt to validate that a poll response is a Note. There might not be any particular harm in publishers saying that a poll response is a Note, but it would be very unfortunate if everyone was forced to say this. In reality, if we had a PollVote or Answer or similar class, we would probably use that. Even more compelling, if we were trying to not confuse a Note with a PollVote for whatever reason. (I still see no reason to disallow this, since the comment/content can be ignored when tallying up vote counts. The thing that makes it a vote in this mechanism is inReplyTo + name.)

1 Like

In my view, counting votes is also a reasonable thing to do. You seem to be saying that the only correct presentation is where voters are counted. Why?

What happens if votersCount property is not present?

Counting votes is reasonable, but counting voters is also reasonable if you are trying to poll voters (e.g. “which options do you approve of”) as opposed to polling options (e.g. “which option is most popular”)

In a poll with 3 options, each getting 1 vote from 3 voters, are you interested in knowing that each option got 33% of the votes, or that each option was approved of by 1/3 of the voters? In this case, it’s the same, because 1 voter = 1 vote. But if you allow multiple votes per voter, then you get different numbers per vote vs per voter. Same poll with 3 options, say it gets 3 voters, except option 1 gets approval of all 3 voters, option 2 gets approval of 2 voters, and option 3 gets approval of no voters. So the result is that 3 voters cast 5 votes, and option 1 got 100% of the voters despite only getting 60% of the votes, and option 2 got 66.6% of the voters while getting 40% of the votes. Seeing that option 1 got 60% while option 2 got 40%, how do you interpret that? Compare to seeing that option 1 got 100% of the voters; how do you interpret that?

anyOf polls tend to favor voters more than votes, and oneOf polls nominally don’t make a difference because 1 voter = 1 vote.

Mastodon will always include votersCount, but if it is missing then they fall back to counting vote percentages instead of voter percentages (at the cost of them being potentially less useful, but it’s all they can do).

1 Like

Thanks for the info @trwnh

I am updating the FEP: https://codeberg.org/fediverse/fep/pulls/842. There are new sections about votersCount, closing polls and Mastodon’s limits.

I agree that it is better to implement polls in forward compatible way. However, the FEP doesn’t strictly require type to be Note, so implementers are free to use other heuristics. Maybe something could be said about alternate types in the “Future work” section



P.S.

I believe the documentation of Poll.voters_count is incorrect:

Type: nullable | Integer, or null if multiple is false.

In reality, the value may be present even if multiple is true. It is only null if voters count is not known (in that case Mastodon shows “N votes” instead of “N people”).

Right now most polls are just a question with multiple answers and the sum of the amount of accounts which have broadcast against a one or more answers.

so “Polls” is more of a “vibe check”
 such as “Red VS Blue?” or “parma vs parmi” for the Australians.

as this doesn’t account for (in mastodon at least) being able for

  1. the actor who posted the poll being able to see who voted for what.
  2. the actor being able to contact the people who voted a particular answer one by one.
    or more complex, responding to all of the people who responded to an particular answer as a collection, in a BCC style private post like followers-only but voters-only).

Random example: Who wants to come to my birthday party? What days are you available for a meeting? (monday,wednesday,friday), Who wants a sticker of this dinosaur on a BMX bike?.. these kind of engagement polls don’t work with with closed polls as default.

Question: Is there any interest in open vs closed polls vs mixed?

  • Private poll: Nobody can see who voted for what
  • Public poll: Everybody can see who voted for what
  • Poll owner only: the actor can see who voted for what, the public can not

Side Question: Visibility into polls makes sense if actors were interested in removing people from the poll who were using bots to mess/skew with the poll
 not that polls should be considered “statically reliant”.

2 Likes

Sounds good, I think we can add this. Perhaps to the “Future work” section, if there are no implementers by the time the FEP will have been finalized.

I’m also planning to introduce the voters collection, which is necessary for addressing Update activities. Usually, the poll author sends these activities not only to followers, but also to voters. However, this is not currently reflected in the activity attributes.

So finally. After releasing my WordPress Polls for ActivityPub plugin (link will be working in the next days, Source Code) I find some time.

  • I would like to see a general notice, maybe in the summary, that this FEP is only covering text based polls and is not intending to cover other things, like image polls, or should it?
  • Maybe the history section could add a conclusion sentence what this FEP is aiming to do: writing guides about how to implement polls so that we have a common denominator about the way polls are currently handled widely in the Fediverse due to early implementations by Mastodon? (
 okey, this sentence was bad).
  • Voters count: I would argue that this should be change to a MUST (the current ‘can’ is ambiguous anyway), at least for multiple choice polls, because otherwise remote applications have no way of showing correct percentages of the results.
  • Receiving a vote: What I see missing is a validation/verification that an actor has not voted already. Or is the argument: a remote server that can construct arbitrary id’s could also construct arbitrary actors? Another open question for me is: what is about receiving deletes or updates on votes? I think a short guidance would be helpful for new implementers. Should we ignore, Delete or Updates on “vote replies”? Should we indicate that we do?
  • Updating updated: I am unsure, I would maybe love to have both information: when has either the question/poll-type/option/or start and endTime been last updated, and when has the last vote been received. For me the former is the more important information. What does Mastodon actually do?
  • Publishing results I advice to remote the MUST here. I even ask myself whether we should remove sending updates on incoming votes completely. Cause every remote application that received a vote can define it’s own refresh cycles.
  • Closing a poll: so no boolean MUST be used on closed? Or if endTime is present and closed is true, should the endTime be able to serve as a “closed time”? I think this could be written more precisely, I encourage not to be afraid to just use a MUST here because it’s something easy to implement and I guess will never be a blocking issue for someone to implement it.
  • Editing Options: I would be more precise: Post-Hum ignore of all previous receive vote replies, emptying all replies collection of all anyOf or oneOf options and setting the votersCount to 0.
  • Question object – updated property: Why not make this a MUST? I see no reason not to.
  • Re-opening closed polls: This is something I would like to have guidance. Should a poll object keep track when being closed and re-opened again? Idea: if poll votes are not reset poll MUST keep the information when the poll was open and when not. Or say once a poll has been closed. Re-Opening causes the votes to reset?

Addition 2026-06-26:

  • Re-opening closed polls: When votes are reset should be sent Reject or Undo activities to already accepted votes? Or should we have a specific votesReset action or indicaiton in the updated object (so that remote server know, that their actors might vote again?!).
1 Like

What is the difference between a text-based poll and image poll?

Yes, I will add a sentence.

I am not convinced that the percentages adjusted by votersCount are more correct than non-adjusted, which is why the word “can” is used instead of “MUST”.

Personally, I prefer non-adjusted.

It’s missing. I’ll fix that in the next revision.

To be honest, I don’t know how to handle these activities. It’s probably better to leave it as an open question.

I am not aware of any polls implementation that exposes this information. Maybe startTime could be used to indicate when the poll was reset


We can change it to “When actor that published a poll receives a vote, it MAY publish an Update activity”, but also add “it MUST publish an Update activity when the poll is closed”.

closed is optional. Not all implementations use it, and adding this property has a cost - a background task that updates Question objects might be necessary.

We can add “The value MUST NOT be a boolean”.

Good idea, I will update this section.

If I remember correctly, there was a popular implementation that didn’t add updated. The SHOULD was used because this FEP attempts to be compatible with existing implementations.

I think re-opening should not reset the votes.

Good question. Maybe we should use startTime to signal resets, and require implementations to let users vote again if this value changes.

Regarding Publishing results, I agree with @andre about revising the MUST, since observing even the most prominent implementer, Poll Update activities did not happen/correspond to every vote, but every third vote (on a Poll with hundreds of votes)!

To allow for some implementation flexibility (scheduled vote count updates), while maintaining expected user interactions. I would revise to something like :

When the actor that published a poll receives a vote, it publishes an Update activity containing the Question object with updated results. Each vote activity response MUST be immediately delivered to the actor that voted in the poll. This activity SHOULD promptly be delivered to the audience of the poll and to every actor that voted in the poll.

2 Likes

That’s better, thanks for the suggestion. I’ll change the text.

1 Like

Update: https://codeberg.org/fediverse/fep/pulls/879

I added several changes that we discussed here.

2 Likes