Skip to content

fix(station): reject empty-approver quorums and zero-requirement policies - #644

Merged
MRmarioruci merged 2 commits into
mainfrom
fix/quorum-empty-approver-floor
Jul 27, 2026
Merged

MRmarioruci merged 2 commits into
mainfrom
fix/quorum-empty-approver-floor

Conversation

@MRmarioruci

@MRmarioruci MRmarioruci commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Problem

Approval quorums can be evaluated or created in ways that bypass the intended approval requirement. Two related issues:

  1. Zero-vote auto-approval on empty-approver quorums. RequestApprovalSummary::evaluate clamps the required approvals with cmp::min(min_approved, total_possible_approvers). When a Quorum/QuorumPercentage rule resolves to zero eligible approvers (e.g. the target user group is empty or all members went inactive), the clamp collapses the threshold to 0, and approved (0) >= 0 returns Approved — the request passes with no votes. QuorumPercentage is affected directly too: calculate_minimum_threshold(100%, 0) == 0.

  2. Zero-requirement policies are creatable. Quorum(_, 0) and QuorumPercentage(_, 0%) pass validation today, producing a rule that approves with zero votes regardless of the approver set.

Changes

  • RequestApprovalSummary::evaluate fails closed (Rejected) when total_possible_approvers == 0, before the cmp::min clamp. This covers both Quorum and QuorumPercentage. evaluate is 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::validate rejects Quorum(_, 0) and QuorumPercentage(_, 0%) with a new dedicated RequestPolicyRuleValidationError::InvalidRule, guiding users to AutoApproved for no-approval rules.

Note on the fix for (1)

An obvious guard of min_approved > 0 && total_possible_approvers == 0 does not cover QuorumPercentage, whose min_approved computes to 0 over an empty set, so the guard would never fire for it. Keying on total_possible_approvers == 0 alone closes both variants and is safe because evaluate(min_approved) is only ever called from the Quorum and QuorumPercentage evaluation arms.

Tests

New unit tests (full station lib suite passes; cargo fmt/clippy clean):

  • positive_quorum_with_no_possible_approvers_is_rejected — direct evaluate guard, including the min_approved == 0 percentage case.
  • quorum_with_no_possible_approvers_is_rejected / quorum_percentage_with_no_possible_approvers_is_rejected — end-to-end evaluation via RequestEvaluator with an empty group; both return Rejected.
  • quorum_and_percentage_reject_zero_requirement_on_validationQuorum(_, 0) / QuorumPercentage(_, 0%) rejected; positive variants and AutoApproved accepted.

The existing misconfigured_min_approved_when_not_enough_approvers_should_still_approve test (one active approver, total == 1) is unaffected.

…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
MRmarioruci force-pushed the fix/quorum-empty-approver-floor branch from 86fe45c to 1e2e6b6 Compare July 23, 2026 07:48
@MRmarioruci MRmarioruci changed the title fix(station,upgrader): harden approval quorum and DR committee validation fix(station): reject empty-approver quorums and zero-requirement policies Jul 23, 2026
@MRmarioruci
MRmarioruci requested a review from Copilot July 23, 2026 08:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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::evaluate when there are zero eligible approvers to avoid cmp::min(..., 0) collapsing approval thresholds to 0.
  • Reject creation of Quorum(_, 0) and QuorumPercentage(_, 0%) rules during RequestPolicyRule::validate, guiding users to AutoApproved for 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::evaluate still approves unconditionally when min_approved == 0 (because approved >= 0), which means stations with pre-upgrade persisted Quorum(_, 0) / QuorumPercentage(_, 0%) rules would remain auto-approving even after this change (since evaluation doesn’t re-run validation). Adding a min_approved == 0 fail-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.

Comment thread core/station/impl/src/models/request_policy_rule.rs
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>
@MRmarioruci
MRmarioruci requested a review from aterga July 23, 2026 09:05
@MRmarioruci
MRmarioruci marked this pull request as ready for review July 23, 2026 09:05
@MRmarioruci
MRmarioruci requested a review from a team as a code owner July 23, 2026 09:05
@zeropath-ai

zeropath-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 50f3ed5.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► core/station/impl/src/core/request.rs
    Add tests for quorum with no possible approvers and quorum percentage with no possible approvers
Bug Fix ► core/station/impl/src/errors/request.rs
    Add From for RequestError     and propagate into error handling
Bug Fix ► core/station/impl/src/errors/request_policy.rs
    Add From for RequestPolicyError     and propagate into error handling
Enhancement ► core/station/impl/src/errors/validation.rs
    Include RequestPolicyRuleValidationError in ValidationError enum and conversions     Display/DetailableError support     Add From for ValidationError
Enhancement ► core/station/impl/src/models/request_policy_rule.rs
    Import RequestPolicyRuleValidationError and ValidationError; extend validation for Quorum and QuorumPercentage to use RequestPolicyRuleValidationError
Enhancement ► core/station/impl/src/test/mod.rs
    Add regression and evaluation tests for zero/invalid quorum scenarios

@MRmarioruci
MRmarioruci merged commit 591bc10 into main Jul 27, 2026
29 checks passed
@MRmarioruci
MRmarioruci deleted the fix/quorum-empty-approver-floor branch July 27, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants