Skip to content

Feature request: "simple majority of cast votes" pass mode (more +1 than -1, excluding abstentions and non-voters) #707

Description

@hendrikebbers

Add a pass mode where a vote passes when there are more binding votes in favor (+1) than against (-1), and where abstentions (eyes) and maintainers who did not vote are not counted at all.

Today the pass decision is a percentage of all allowed voters and never looks at the against count. Our governance requires the opposite: a plurality of the votes actually cast, ignoring everyone who abstained or stayed silent. We currently cannot express this with pass_threshold.

Current behavior

From src/results.rs, the result is computed as:

in_favor_percentage = in_favor / allowed_voters.len() * 100
passed = in_favor_percentage >= pass_threshold

This means:

  1. The denominator is always the full set of allowed_voters. Abstentions (eyes) and non-voters stay in the denominator and drag the percentage down — there is no way to exclude them.
  2. The against count is tracked (against_percentage) but never used in the passed decision. There is no "more in favor than against" comparison.
    So a vote with 2 in favor, 0 against, 10 abstentions in a 12-maintainer team yields 2 / 12 = 16.7 %, which fails at any pass_threshold >= 17. Lowering pass_threshold doesn't help either, because a low threshold would also pass a vote with more -1 than +1 (e.g. 2 in favor, 10 against), since against is ignored.

Use case

The Hiero project (LF Decentralized Trust) uses GitVote for all asynchronous maintainer votes. Our governance defines the passing rule as follows (see roles/roles-and-groups.md, "Voting" section):

A vote passes when more maintainers have voted in favor (+1 / thumbs up in GitVote) than against (-1 / thumbs down in GitVote). Maintainers who abstain or do not vote are not counted. For example, 2 votes in favor and 0 against with 10 abstentions is a passing vote.

Abstaining is an explicit, first-class outcome in our process: maintainers are required to participate in a nomination but may effectively abstain by only posting a comment. Non-participation is a separate governance concern (inactivity), not a "no" vote. So neither abstentions nor non-voters may count toward or against the result.

We suspect this "plurality of participants" model is common in maintainer/promotion votes and would be useful beyond our project.

Proposed solution

Introduce an alternative pass mode, selectable per profile. For example:

profiles:
  default:
    duration: 5m
    # Instead of pass_threshold, allow a mode that compares cast votes:
    pass_mode: simple_majority   # passes when in_favor > against; abstain & not_voted ignored
    allowed_voters:
      teams:
        - project-maintainers

Behavior of simple_majority:

  • passed = in_favor > against (binding votes only).
  • Abstentions and non-voters are excluded from the decision entirely.
  • Optionally require at least one +1 (i.e. an empty vote with 0 in favor and 0 against does not pass).
    Naming is of course up to you (pass_mode, or a quorum/majority sub-section). The key point is a mode that (a) compares in_favor against against, and (b) uses cast votes rather than the full allowed_voters set as the basis.

Alternatives considered

  • Low pass_threshold: rejected — it ignores against and would pass votes with more -1 than +1.
  • Threshold as a % of all maintainers: changes the semantics (a fixed quorum of the whole team), and depends on team size; it does not match "more for than against, ignore abstentions".
  • Custom GitHub Action counting reactions: works, but we'd rather keep using GitVote as the single, consistent voting tool across all Hiero projects.

Additional context

This would also make the existing "close on passing" / early-close logic more expressive for us, since our governance already allows closing a vote early once the outcome can no longer change (enough +1 that remaining votes can't flip it, or enough -1 that +1 > -1 is impossible).

Happy to help implement, test or review a PR for this issue.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions