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:
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.
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.
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:
The
>=is the problem. At a flat50, an even-sized electorate splittingexactly 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.
>= 50Odd-sized electorates were never affected: the percentage cannot land on
exactly 50, so
>= 50and> 50agree.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
50.015050 + 100/Npercent, which stays above 50.01 for any N up to 10000. Rejects an exact tie, accepts every real majority.66.666666sat 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_thresholdis anf64in gitvote's config(
src/cfg_repo.rs)with no integer constraint, so fractional values parse fine.
The thresholds
GOVERNANCE.mddescribes 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:
cnpg-infra'sscripts/render-gitvote-config.sh, which renders.gitvote.ymlinto every managed repository. Pushed direct tomainascloudnative-pg/cnpg-infra@8ed4bd9
per that repo's documented bypass exception.
governance's hand-maintained.gitvote.yml(markedexcluded: trueingitvote-policy.yaml, since the federated model gave it a profile persubproject committee plus a
-removalvariant of each).35 repositories in total: 1 direct push, 34 pull requests.
Pull requests
Added to the existing
dev/sync-generated-filesPRs (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-thresholdPRs (17)These repositories had already merged their sweep PR, so the fix needed a
fresh branch.
governanceis here because its file is hand-maintained andnever had a sweep PR at all.
Direct push (1)
mainNotes
governancePR touches only.gitvote.yml, notGOVERNANCE.md, so theamendment automation does not fire on it. Confirmed: no vote was opened.
cnpg-infrais the source of truth. Since the renderer changed first, alater
sync-ownership-files.shrun will not revert any of these.50.01or66.66, and every branch matches its remote.