cncf / cncf/gitvote

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

Open
#707 1 comment 1 reaction 2 assignees Claimed by @cynthia-sg View on GitHub
Dominant language
Rust
Stars
141
Forks
18
PR merge metrics
No merged PRs in 30d

Description

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`](https://github.com/cncf/gitvote/blob/main/src/results.rs), the result is computed as:

```rust
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](https://github.com/hiero-ledger/governance/blob/main/roles/roles-and-groups.md#voting)):

> 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:

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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.