Skip to main content

Legal Vote


Terminology

  • User - A user is an account inside OpenTalk, a single user may be inside a room as multiple participants.
  • Participant - A unique connection to a room. Participants may be a guest or logged-in users.

Overview

The legal vote module allows users to participate in a legally sound vote. When a vote is started, a selection of allowed participants can cast the vote options Yes, No and Abstain on a defined voting topic. An allowed participant cannot be a guest, as each participant needs to have an underlying user id to cast a vote.

While a vote is active, the occurring vote events are saved in a vote protocol which is moved to the database once the vote is complete. This provides a foundation to backtrack results, votes and errors after a vote has ended. The result and protocol of a vote can be accessed in redis for the lifetime of the room where the vote had taken place in.

The state of a vote is held in a redis instance. There can only be one active vote at a time, every completed vote gets moved to the vote history for later access. Any operation that accesses more than one redis key is done with a Lua script in order to make those operations atomic.

Vote kind

The voting procedure may follow different patterns depending on their kind. In order to choose the vote kind, the corresponding identifier needs to be set in the Start message.

Live roll call

Identifier: "live_roll_call"

In a live roll call, every vote that is handed in gets published to all room participants immediately. This allows everybody to follow the current state of the voting procedure live, immediately revealing who voted what. The report will contain a list of user ids and the vote options they chose.

Roll call

Identifier: "roll_call"

A roll call is private while it is running, while the details get published once it is finished. No updates will be sent out during the voting procedure, but votes are counted by the server. The report will contain a list of users and the vote options they chose.

Pseudonymous

Identifier: "pseudonymous"

A pseudonymous vote allows the users to keep their vote option private. Each user gets a single use token which is consumed when the the vote option is counted. The token is revealed to the user, who should keep it private. The report will contain a list of tokens and the vote options that were handed in with them. This allows every user to verify that their vote has been counted correctly. Because the tokens are published with the association of the chosen vote option, this is only pseudonymous, not anonymous.

Joining the room

JoinSuccess

When joining a room, the join_success control event contains the module-specific fields described below.

Fields

FieldTypeAlwaysDescription
votesVoteSummary[]yesA list of current and past votes.

VoteSummary fields:

FieldTypeRequiredDescription
kindenumyesThe exhaustive list of kind can be found in section Vote Kind.
initiator_idstringyesId of the participant which started the vote.
legal_vote_idstringyesId of the vote.
start_timestringyesRFC 3339 timestamp when the vote started.
max_votesintyesThe maximum number of possible votes.
namestringyesGeneral name of the vote.
subtitlestringnoA subtitle for the vote
topicstringnoDetailed topic that will be voted on.
allowed_participantsstring[]yesAn array of participant ids, where each contained participant is allowed to cast a vote.
enable_abstainboolyesEnable/Disable the 'Abstain' option on this vote.
auto_closeboolyesWhen set, the vote will automatically close when every allowed participant casted a vote.
create_pdfboolyesAutomatically create a protocol PDF when the vote ends.
durationintnoDuration of the vote in seconds, counting from the start_time.
tokenstringnoOptional. Only users who participate in the voting procedure receive a token.
statestringyesThe state of the vote. Valid values are "started", "finished", "canceled", "invalid".
end_timestringnoRFC 3339 timestamp when the vote ended.

When state is "finished":

FieldTypeRequiredDescription
stop_kindenumyesDescribes how the vote finished. Valid values are "by_user", "auto", "expired".
stopped_bystringnoThe id of the participant who stopped the vote. Only present if stop_kind is "by_user".
yesintyesNumber of "yes" votes
nointyesNumber of "no" votes
abstainintwhen enable_abstain is trueNumber of "abstain" votes
voting_recordmapyesMapping of participant or token including their vote option

When state is "canceled":

FieldTypeRequiredDescription
issuerstringyesThe id of the participant who canceled the vote
reasonstringyesEither "room_destroyed", "initiator_left" or "custom"
customstringnoA custom cancel reason, only present when reason is "custom"

When state is "invalid":

FieldTypeRequiredDescription
reasonenumyesEither "abstain_disabled" or "vote_count_inconsistent"
Example

Stopped with valid results on a vote which reveals the users:

[
{
"legal_vote_id": "00000000-0000-0000-0000-000000000123",
"kind": "live_roll_call",
"initiator_id": "00000000-0000-0000-0000-000000000001",
"start_time": "1970-01-01T00:00:00Z",
"max_votes": 5,
"name": "Yes or no",
"subtitle": "Choose either yes or no",
"allowed_participants": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003",
"00000000-0000-0000-0000-000000000004",
"00000000-0000-0000-0000-000000000005"
],
"enable_abstain": true,
"auto_close": true,
"create_pdf": true,
"duration": 300,
"state": "finished",
"stop_kind": "auto",
"yes": 1,
"no": 2,
"abstain": 2,
"voting_record": {
"00000000-0000-0000-0000-000000000001": "yes",
"00000000-0000-0000-0000-000000000002": "no",
"00000000-0000-0000-0000-000000000003": "abstain",
"00000000-0000-0000-0000-000000000004": "abstain",
"00000000-0000-0000-0000-000000000005": "no"
},
"end_time": "1970-01-01T00:05:00Z"
},
{
"legal_vote_id": "00000000-0000-0000-0000-000000000456",
"kind": "roll_call",
"initiator_id": "00000000-0000-0000-0000-000000000001",
"start_time": "1970-01-01T01:00:00Z",
"max_votes": 3,
"name": "Vote Test",
"subtitle": "Yes or No?",
"allowed_participants": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"enable_abstain": false,
"auto_close": false,
"create_pdf": true,
"timezone": "CET",
"duration": 60,
"state": "canceled",
"issued_by": "00000000-0000-0000-0000-000000000001",
"reason": "initiator_left"
}
]

Joined

When joining a room, the joined control event sent to all other participants contains the module-specific fields described below.

Commands

Commands are issued by a participant to start or interact with a vote.

Start

The Start message can be sent by a moderator to start a new legal vote.

Fields

FieldTypeRequiredValidationDescription
actionenumyesMust be "start".
kindenumyesThe exhaustive list of kind can be found in section Vote Kind.
namestringyesmax 150 charsGeneral name of the vote
subtitlestringnomax 255 charsA subtitle for the vote
topicstringnomax 500 charsDetailed topic that will be voted on.
allowed_participantsstring[]yesmin 1 participantAn array of participant ids, where each contained participant is allowed to cast a vote.
enable_abstainboolyesEnable/Disable the 'Abstain' option on this vote
auto_closeboolyesWhen set, the vote will automatically close when every allowed participant casted a vote.
create_pdfboolyesAutomatically create a protocol PDF when the vote ends.
timezonestringnomax 150 charsTimezone used in the protocol, defaults to UTC, IANA format, e.g."CET" or "Europe/Vienna".
durationintnomin 5 secondsDuration of the vote in seconds.
Example
{
"action": "start",
"kind": "roll_call",
"name": "Vote Test",
"topic": "Yes or No?",
"allowed_participants": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"enable_abstain": false,
"auto_close": false,
"create_pdf": true,
"timezone": "CET",
"duration": 60
}

Response

A Started message is sent to all participants that are currently in the room.


Stop

Stop the currently active vote. Will only succeed when the issuer is the vote initiator.

Fields

FieldTypeRequiredDescription
actionenumyesMust be "stop".
legal_vote_idstringyesThe vote that shall be stopped
Example
{
"action": "stop",
"legal_vote_id": "00000000-0000-0000-0000-000000000000"
}

Response

A Stopped message is sent to all participants that are currently in the room.


Cancel

Cancel the currently active vote when the provided legal_vote_id matches. This command may only be issued by a moderator. The vote protocol will still be saved in the database and in the room-vote-history, but the vote results should be handled as invalid.

This command may be triggered by the controller itself when an invalid state or error was detected. See Canceled for more details on which server events may cause this.

Fields

FieldTypeRequiredValidationDescription
actionenumyesMust be "cancel".
legal_vote_idstringyesThe vote that shall be canceled.
reasonstringyesmax 255 charsThe reason for the cancel.
Example
{
"action": "cancel",
"vote_id": "00000000-0000-0000-0000-000000000000",
"reason": "A very descriptive reason",
}

Response

A Canceled message is sent to all participants that are currently in the room.


Vote

Cast a vote on the specified legal_vote_id. Each user is allowed to only vote once.

Fields

FieldTypeRequiredDescription
actionenumyesMust be "vote".
legal_vote_idstringyesThe vote that shall be voted on.
optionenumyesThe chosen vote option, may be "yes", "no" or "abstain" when the abstain option is enabled.
tokenstringyesThe token that was handed to the user with the Started message.
Example
{
"action": "vote",
"legal_vote_id": "00000000-0000-0000-0000-000000000000",
"option": "yes",
"token": "2QNav7b3FJw"
}

Response

When the vote is successful, a Voted message is sent to each participant that is logged in under the same user id.

When the vote failed, a Voted response is sent to the issuer.


ReportIssue

Report an issue to the vote creator while the vote is active.

Can be sent by any vote participant during the vote. These events will be saved and displayed in the vote protocol.

Fields

FieldTypeRequiredDescription
actionenumyesMust be "report_issue".
legal_vote_idstringyesThe ID of the related legal vote
kindenumnoEither "audio", "video" or "screenshare".
descriptionstringnoAn optional message to the vote creator. Is mandatory when no "kind" is provided

Example

{
"action": "report_issue",
"kind": "audio",
"description": "Hello, my audio is not working :("
}
{
"action": "report_issue",
"description": "Hello, something else is not working :("
}

Response

No response is sent to the issuer. The moderator will receive a ReportedIssue message.


GeneratePdf

Generate a PDF of the protocol of the specified vote_id. Only passed votes can be generated as a PDF document.

Fields

FieldTypeRequiredDescription
actionenumyesMust be "generate_pdf".
vote_idstringyesThe selected vote.
timezonestringnoTimezone used in the protocol, defaults to UTC, IANA format, e.g."CET" or "Europe/Vienna".
Example
{
"action": "generate_pdf",
"vote_id": "00000000-0000-0000-0000-000000000000",
"timezone": "CET"
}

Response

When the PDF got created, a PdfAsset response is sent to the issuer.


Events

Events are received by participants when the vote state is changed. Events may be a 'direct' response to an issued command or unrelated to the actions of the receiving participant.

Started

A vote has been started by a moderator.

This message will also be received when joining a room that has an active vote going.

Fields

FieldTypeRequiredDescription
messageenumyesIs "started".
kindenumyesThe exhaustive list of kind can be found in section Vote Kind.
initiator_idstringyesId of the participant which started the vote.
legal_vote_idstringyesId of the vote.
start_timestringyesRFC 3339 timestamp when the vote started.
max_votesintyesThe maximum number of possible votes.
namestringyesGeneral name of the vote.
subtitlestringnoA subtitle for the vote
topicstringnoDetailed topic that will be voted on.
allowed_participantsstring[]yesAn array of participant ids, where each contained participant is allowed to cast a vote.
enable_abstainboolyesEnable/Disable the 'Abstain' option on this vote.
auto_closeboolyesWhen set, the vote will automatically close when every allowed participant casted a vote.
create_pdfboolyesAutomatically create a protocol PDF when the vote ends.
durationintnoDuration of the vote in seconds, counting from the start_time.
tokenstringnoOptional. Only users who participate in the voting procedure receive a token.
Example
{
"message": "started",
"kind": "roll_call",
"initiator_id": "00000000-0000-0000-0000-000000000004",
"legal_vote_id": "00000000-0000-0000-0000-000000000123",
"start_time": "1970-01-01T00:00:00Z",
"max_votes": 3,
"name": "Vote Test",
"topic": "Yes or No?",
"allowed_participants": [
"00000000-0000-0000-0000-000000000001",
"00000000-0000-0000-0000-000000000002",
"00000000-0000-0000-0000-000000000003"
],
"enable_abstain": false,
"auto_close": false,
"duration": 60,
"token": "2QNav7b3FJw"
}

Voted

Event received by a user whenever they voted. Usually understood as a response to the Vote command. Since every user may only vote once, each participant logged in as that user will receive this message after any of them successfully cast their vote.

Fields

FieldTypeRequiredDescription
messageenumyesIs "voted".
legal_vote_idstringyesId of the vote.
responseenumyesEither "success" or "failed"
vote_optionenumwhen response is successThe option the issuer voted for
issuerstringwhen response is successId of the participant which voted.
consumed_tokenstringwhen response is successThe token that is consumed once a user has voted
reasonenumwhen response is failedReason why the vote failed, see table below

Failure reason:

ReasonDescription
invalid_vote_idthe field legal_vote_id contained an invalid or unknown id
ineligiblethe user is not eligible to vote
invalid_optionthe given vote_option field contained an unknown option
Examples

Successful vote:

{
"message": "voted",
"legal_vote_id": "00000000-0000-0000-0000-000000000123",
"response": "success",
"vote_option": "yes",
"issuer": "00000000-0000-0000-0000-000000000001",
"consumed_token": "2QNav7b3FJw"
}

Failed vote:

{
"message": "voted",
"legal_vote_id": "00000000-0000-0000-0000-000000000123",
"response": "failed",
"reason": "ineligible"
}

Updated

Update to an ongoing vote which supports live updates, signaling the newest results. Used to visualize the UI.

Fields

FieldTypeRequiredDescription
messageenumyesIs "updated".
legal_vote_idstringyesId of the vote.
yesintyesNumber of "yes" votes
nointyesNumber of "no" votes
abstainintwhen enable_abstain is trueNumber of "abstain" votes
voting_recordmapyesMapping of participant which voted to their vote option
Example
{
"message": "updated",
"legal_vote_id": "00000000-0000-0000-0000-000000000123",
"yes": 1,
"no": 2,
"abstain": 2,
"voting_record": {
"00000000-0000-0000-0000-000000000001": "yes",
"00000000-0000-0000-0000-000000000002": "no",
"00000000-0000-0000-0000-000000000003": "abstain",
"00000000-0000-0000-0000-000000000004": "abstain",
"00000000-0000-0000-0000-000000000005": "no"
}
}

Stopped

An ongoing vote has been finished and the results are being distributed with this event. The vote results may be invalid, this happens when the final vote results are not consistent or altered unexpectedly.

Fields

FieldTypeRequiredDescription
messageenumyesIs "stopped".
legal_vote_idstringyesId of the vote.
kindenumyesEither "by_participant", "auto" or "expired"
issuerstringwhen kind is by_participantId of the participant which issued the stop command
resultsenumyesIs either valid or invalid. This field changes the rest of the fields to one of the following tables.
end_timestringyesRFC 3339 timestamp when the vote ended.

When results is valid:

FieldTypeRequiredDescription
yesintyesNumber of "yes" votes
nointyesNumber of "no" votes
abstainintwhen enable_abstain is trueNumber of "abstain" votes
voting_recordmapyesMapping of participant or token including their vote option

When invalid:

FieldTypeRequiredDescription
reasonenumyesEither "abstain_disabled" or "vote_count_inconsistent"
Example

Stopped with valid results on a vote which reveals the users:

{
"message": "stopped",
"legal_vote_id": "00000000-0000-0000-0000-000000000123",
"kind": "by_participant",
"issuer": "00000000-0000-0000-0000-000000000001",
"results": "valid",
"yes": 1,
"no": 2,
"abstain": 2,
"voting_record": {
"00000000-0000-0000-0000-000000000001": "yes",
"00000000-0000-0000-0000-000000000002": "no",
"00000000-0000-0000-0000-000000000003": "abstain",
"00000000-0000-0000-0000-000000000004": "abstain",
"00000000-0000-0000-0000-000000000005": "no"
},
"end_time": "1970-01-01T00:00:00Z"
}

Stopped with valid results on a vote which reveals the tokens:

{
"message": "stopped",
"legal_vote_id": "00000000-0000-0000-0000-000000000123",
"kind": "by_participant",
"issuer": "00000000-0000-0000-0000-000000000001",
"results": "valid",
"yes": 1,
"no": 2,
"abstain": 2,
"voting_record": {
"9AMndyeorvB": "yes",
"G9rLx7vkeMD": "no",
"Mypgay5rhRj": "abstain",
"TjR94viayBf": "abstain",
"UuLLU1sxgPw": "no"
},
"end_time": "1970-01-01T00:00:00Z"
}

Stopped with invalid results:

{
"message": "stopped",
"legal_vote_id": "00000000-0000-0000-0000-000000000123",
"kind": "by_participant",
"issuer": "00000000-0000-0000-0000-000000000001",
"results": "invalid",
"reason": "vote_count_inconsistent",
"end_time": "1970-01-01T00:00:00Z"
}

Canceled

An ongoing vote with legal_vote_id has been canceled. This may either be issued by a moderator or by the server when some error or inconsistency occurred.

Fields

FieldTypeRequiredDescription
messageenumyesIs "canceled".
legal_vote_idstringyesId of the vote.
reasonenumyesEither "custom", "room_destroyed" or "initiator_left"
customstringwhen reason is customThe reason for the cancel, set by the moderator.
Examples
{
"message": "canceled",
"legal_vote_id": "00000000-0000-0000-0000-000000000000",
"reason": "initiator_left"
}

With custom reason:

{
"message": "canceled",
"legal_vote_id": "00000000-0000-0000-0000-000000000000",
"reason": "custom",
"custom": "Some important voters left"
}

ReportedIssue

Received by the vote creator when a participant reports an issue via the ReportIssue action.

The reported issues are saved and displayed in the vote protocol.

Fields

FieldTypeRequiredDescription
messageenumyesIs "reported_issue".
legal_vote_idstringyesThe ID of the related legal vote
participant_idstringnoThe participant ID of the user that issued the report. Omitted when the vote is pseudonymous
kindenumnoEither "audio", "video" or "screenshare".
descriptionstringnoAn optional message to the vote creator. Is mandatory when no kind is provided

Example

{
"message": "reported_issue",
"participant_id": "00000000-0000-0000-0000-000000000000",
"kind": "video",
"description": "Hello, my video is not working"
}
{
"message": "reported_issue",
"participant_id": "00000000-0000-0000-0000-000000000000",
"description": "Something else is not working"
}

PdfAsset

A PDF document has been created for the specified vote.

This message is sent to the issuing moderator when the vote ends and create_pdf field in the Start message is set. In case of an automatic stop, the message is sent to the vote initiator.

Fields

FieldTypeRequiredDescription
messageenumyesIs "pdf_asset".
legal_vote_idstringyesId of the vote.
asset_idstringyesId of the created asset.
Examples
{
"message": "pdf_asset",
"legal_vote_id": "00000000-0000-0000-0000-000000000000",
"asset_id": "00000000-0000-0000-0000-000000000000"
}

Error

The error event is a message that may be triggered by syntactically correct but invalid commands inside the legal-vote namespace and therefore could be considered a kind of response. Errors must be handled outside of any context as they are considered events that can happen at any time. (e.g. an internal error may occur at any time to signal an internal problem)

FieldTypeRequiredDescription
messageenumyesIs "error".
errorenumyesExhaustive list of error strings, see table below
guestsstring[]when error is "allowlist_contains_guests"A list of participants that where found to be quests
fieldsstring[]when error is "bad_request"A list of fields that ignored validation constraints
ErrorDescription
vote_already_activeStart command sent while a vote was already active
no_vote_activeA vote related related request was sent while no vote is active
invalid_vote_idAn invalid vote id was references inside a command
ineligibleThe requesting user is ineligible for the issued command
allowlist_contains_guestsThe allow_list of a Start provided start message contained guests
bad_requestThe input validation failed for one or more of the provided fields
permission_errorFailed to set permissions when creating backend resources
insufficient_permissionsThe requesting user has insufficient permissions (E.g. a command requires the moderator role
internalBackend services encountered an internal error and any active vote should be considered invalid

Example

{
"message": "error",
"error": "vote_already_active"
}

Error for an invalid Start request, where the allowlist contains guests:

{
"message": "error",
"error": "allowlist_contains_guest",
"guests": ["00000000-0000-0000-0000-000000000123", "00000000-0000-0000-0000-000000011311"]
}

Error for an invalid Start request, where topic and duration constraints where ignored:

{
"message": "error",
"error": "bad_request",
"guests": ["topic", "duration"]
}