Skip to content

fix: gitvote passes a vote on an exact tie #80

Description

@gbartolini

Fix: gitvote passes a vote on an exact tie

Problem

gitvote decides a vote with in_favor_percentage >= pass_threshold
(src/results.rs),
where the denominator is the whole electorate rather than the votes cast:

in_favor_percentage = in_favor as f64 / allowed_voters.len() as f64 * 100.0;
...
passed: in_favor_percentage >= vote.cfg.pass_threshold,

The >= is the problem. At a flat 50, an even-sized electorate splitting
exactly down the middle clears the bar, so a 2-2 tie on a four-owner
repository, or a 3-3 split on a six-member committee, passed the motion.

Electorate In favor % >= 50 Correct?
7 4 57.1 pass yes
7 3 42.9 fail yes
10 5 50.0 pass no, that is a 5-5 tie
10 6 60.0 pass yes

Odd-sized electorates were never affected: the percentage cannot land on
exactly 50, so >= 50 and > 50 agree.

A simple majority is strictly more than half. A percentage cannot express
"half plus one vote", so the bar has to sit just above 50 instead.

Fix

Value Was Why
50.01 50 The smallest possible majority of N voters is 50 + 100/N percent, which stays above 50.01 for any N up to 10000. Rejects an exact tie, accepts every real majority.
66.66 66 Two-thirds is 66.666…, so a flat 66 sat under its own bar (33 of 50 is 66.0 and would have passed). Deliberately not 66.67: for an electorate divisible by three the exact two-thirds computes to 66.6666…, which falls under 66.67 and would break a legitimate 2-of-3.

pass_threshold is an f64 in gitvote's config
(src/cfg_repo.rs)
with no integer constraint, so fractional values parse fine.

The thresholds GOVERNANCE.md describes in prose, "simple majority" and
"two-thirds", are unchanged. Only the encoding of them in the tooling was
wrong.

Scope

The threshold lives in two places, so both are changed in one pass:

  1. cnpg-infra's scripts/render-gitvote-config.sh, which renders
    .gitvote.yml into every managed repository. Pushed direct to main as
    cloudnative-pg/cnpg-infra@8ed4bd9
    per that repo's documented bypass exception.
  2. governance's hand-maintained .gitvote.yml (marked excluded: true in
    gitvote-policy.yaml, since the federated model gave it a profile per
    subproject committee plus a -removal variant of each).

35 repositories in total: 1 direct push, 34 pull requests.

Pull requests

Added to the existing dev/sync-generated-files PRs (17)

These repositories still had their generated-files sweep PR open, so the fix
went on as an extra commit rather than a separate PR.

New dev/gitvote-majority-threshold PRs (17)

These repositories had already merged their sweep PR, so the fix needed a
fresh branch. governance is here because its file is hand-maintained and
never had a sweep PR at all.

Direct push (1)

Notes

  • The governance PR touches only .gitvote.yml, not GOVERNANCE.md, so the
    amendment automation does not fire on it. Confirmed: no vote was opened.
  • cnpg-infra is the source of truth. Since the renderer changed first, a
    later sync-ownership-files.sh run will not revert any of these.
  • Verified across all 35 repositories: every profile parses as a float,
    50.01 or 66.66, and every branch matches its remote.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions