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:
- 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.
- 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.
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
againstcount. Our governance requires the opposite: a plurality of the votes actually cast, ignoring everyone who abstained or stayed silent. We currently cannot express this withpass_threshold.Current behavior
From
src/results.rs, the result is computed as:This means:
allowed_voters. Abstentions (eyes) and non-voters stay in the denominator and drag the percentage down — there is no way to exclude them.againstcount is tracked (against_percentage) but never used in thepasseddecision. 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 anypass_threshold >= 17. Loweringpass_thresholddoesn't help either, because a low threshold would also pass a vote with more-1than+1(e.g. 2 in favor, 10 against), sinceagainstis 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):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:
Behavior of
simple_majority:passed = in_favor > against(binding votes only).+1(i.e. an empty vote with0in favor and0against does not pass).Naming is of course up to you (
pass_mode, or aquorum/majoritysub-section). The key point is a mode that (a) comparesin_favoragainstagainst, and (b) uses cast votes rather than the fullallowed_votersset as the basis.Alternatives considered
pass_threshold: rejected — it ignoresagainstand would pass votes with more-1than+1.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
+1that remaining votes can't flip it, or enough-1that+1 > -1is impossible).Happy to help implement, test or review a PR for this issue.