fix(station): reject empty-approver quorums and zero-requirement policies - #644
Merged
Merged
Conversation
This was referenced Jul 23, 2026
…cies Two related approval-quorum integrity fixes: - Quorum/QuorumPercentage evaluation now fails closed when a rule resolves to zero eligible approvers. Previously cmp::min(min_approved, 0) collapsed the threshold to 0, so approved (0) >= 0 auto-approved the request with zero votes. Keyed on total_possible_approvers == 0 so it also covers QuorumPercentage, whose threshold computes to 0 over an empty set. - Policy validation rejects Quorum(_, 0) and QuorumPercentage(_, 0%), guiding users to AutoApproved for no-approval rules, via a dedicated RequestPolicyRuleValidationError. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MRmarioruci
force-pushed
the
fix/quorum-empty-approver-floor
branch
from
July 23, 2026 07:48
86fe45c to
1e2e6b6
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens Station request-approval policy evaluation and validation to prevent accidental or malicious “no-vote” approvals caused by empty approver sets or zero-requirement quorum rules.
Changes:
- Fail-closed in
RequestApprovalSummary::evaluatewhen there are zero eligible approvers to avoidcmp::min(..., 0)collapsing approval thresholds to0. - Reject creation of
Quorum(_, 0)andQuorumPercentage(_, 0%)rules duringRequestPolicyRule::validate, guiding users toAutoApprovedfor no-approval workflows. - Add regression tests covering both the direct evaluation guard and end-to-end evaluation via
RequestEvaluator.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| core/station/impl/src/models/request_policy_rule.rs | Adds validation against zero-requirement quorum rules, adds fail-closed evaluation guard for empty approver sets, and adds unit tests. |
| core/station/impl/src/errors/validation.rs | Introduces RequestPolicyRuleValidationError and wires it into ValidationError display/details/conversions. |
| core/station/impl/src/errors/request.rs | Propagates the new validation error variant through RequestError conversions. |
| core/station/impl/src/errors/request_policy.rs | Propagates the new validation error variant through RequestPolicyError conversions. |
| core/station/impl/src/core/request.rs | Adds end-to-end evaluator tests for empty approver sets in quorum/quorum-percentage rules. |
Comments suppressed due to low confidence (1)
core/station/impl/src/models/request_policy_rule.rs:349
RequestApprovalSummary::evaluatestill approves unconditionally whenmin_approved == 0(becauseapproved >= 0), which means stations with pre-upgrade persistedQuorum(_, 0)/QuorumPercentage(_, 0%)rules would remain auto-approving even after this change (since evaluation doesn’t re-run validation). Adding amin_approved == 0fail-closed guard here provides defense-in-depth and closes that backwards-compatibility hole.
if self.total_possible_approvers == 0 {
return EvaluationStatus::Rejected;
}
let min_approved = cmp::min(min_approved, self.total_possible_approvers);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Addresses review feedback on the approval-quorum validation: - QuorumPercentage validation now also rejects percentages > 100. The DTO carries a raw u16 and the mapper builds Percentage without the TryFrom bound, so values > 100 could previously reach calculate_minimum_threshold and violate its documented 0..=100 assumption. - RequestApprovalSummary::evaluate now fails closed when min_approved == 0 as well as when there are no possible approvers. Evaluation does not re-run validation, so a Quorum(_, 0) / QuorumPercentage(_, 0%) rule persisted before the validation guard existed would otherwise still auto-approve with a non-empty approver set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
✅ No security or compliance issues detected. Reviewed everything up to 50f3ed5. Security Overview
Detected Code Changes
|
aterga
approved these changes
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Approval quorums can be evaluated or created in ways that bypass the intended approval requirement. Two related issues:
Zero-vote auto-approval on empty-approver quorums.
RequestApprovalSummary::evaluateclamps the required approvals withcmp::min(min_approved, total_possible_approvers). When aQuorum/QuorumPercentagerule resolves to zero eligible approvers (e.g. the target user group is empty or all members went inactive), the clamp collapses the threshold to0, andapproved (0) >= 0returnsApproved— the request passes with no votes.QuorumPercentageis affected directly too:calculate_minimum_threshold(100%, 0) == 0.Zero-requirement policies are creatable.
Quorum(_, 0)andQuorumPercentage(_, 0%)pass validation today, producing a rule that approves with zero votes regardless of the approver set.Changes
RequestApprovalSummary::evaluatefails closed (Rejected) whentotal_possible_approvers == 0, before thecmp::minclamp. This covers bothQuorumandQuorumPercentage.evaluateis only reached from those two arms, both of which now always require a positive amount (see below), so an empty approver set can never legitimately be satisfied.RequestPolicyRule::validaterejectsQuorum(_, 0)andQuorumPercentage(_, 0%)with a new dedicatedRequestPolicyRuleValidationError::InvalidRule, guiding users toAutoApprovedfor no-approval rules.Note on the fix for (1)
An obvious guard of
min_approved > 0 && total_possible_approvers == 0does not coverQuorumPercentage, whosemin_approvedcomputes to0over an empty set, so the guard would never fire for it. Keying ontotal_possible_approvers == 0alone closes both variants and is safe becauseevaluate(min_approved)is only ever called from theQuorumandQuorumPercentageevaluation arms.Tests
New unit tests (full
stationlib suite passes;cargo fmt/clippyclean):positive_quorum_with_no_possible_approvers_is_rejected— directevaluateguard, including themin_approved == 0percentage case.quorum_with_no_possible_approvers_is_rejected/quorum_percentage_with_no_possible_approvers_is_rejected— end-to-end evaluation viaRequestEvaluatorwith an empty group; both returnRejected.quorum_and_percentage_reject_zero_requirement_on_validation—Quorum(_, 0)/QuorumPercentage(_, 0%)rejected; positive variants andAutoApprovedaccepted.The existing
misconfigured_min_approved_when_not_enough_approvers_should_still_approvetest (one active approver,total == 1) is unaffected.