You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by sb...@apache.org on 2026/07/24 13:37:38 UTC

(tooling-trusted-releases) branch sbp updated: Disallow zero hour vote durations in production mode

This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new f5a200ef Disallow zero hour vote durations in production mode
f5a200ef is described below

commit f5a200efb434d0dc7610475db573b30dc651e20d
Author: Sean B. Palmer <se...@miscoranda.com>
AuthorDate: Fri Jul 24 14:36:59 2026 +0100

    Disallow zero hour vote durations in production mode
---
 atr/get/voting.py                     |  2 ++
 atr/shared/projects.py                |  6 ++--
 atr/shared/voting.py                  |  3 ++
 atr/util.py                           |  4 +--
 tests/unit/test_vote_duration_zero.py | 67 +++++++++++++++++++++++++++++++++++
 5 files changed, 78 insertions(+), 4 deletions(-)

diff --git a/atr/get/voting.py b/atr/get/voting.py
index a6696a8e..da8c3ff8 100644
--- a/atr/get/voting.py
+++ b/atr/get/voting.py
@@ -86,6 +86,8 @@ async def selected(
         release_policy = release.project.release_policy
         if release_policy and (release_policy.min_hours is not None):
             min_hours = release_policy.min_hours
+        if (min_hours == 0) and config.is_production_mode():
+            min_hours = 72
 
         vote_mode = release.effective_vote_mode
         default_subject_template = await construct.start_vote_subject_default(project_key)
diff --git a/atr/shared/projects.py b/atr/shared/projects.py
index 427edf70..147c3ed3 100644
--- a/atr/shared/projects.py
+++ b/atr/shared/projects.py
@@ -23,6 +23,7 @@ from typing import Annotated, Literal
 import pydantic
 
 import atr.calver as calver
+import atr.config as config
 import atr.construct as construct
 import atr.form as form
 import atr.models.safe as safe
@@ -192,8 +193,7 @@ class VotePolicyForm(form.Form):
     )
     min_hours: form.Int = form.label(
         "Minimum voting period",
-        "The minimum time to run the vote, in hours. Must be 0 or between 72 and 144 inclusive. "
-        "If 0, then wait until 3 +1 votes and more +1 than -1.",
+        "The minimum time to run the vote, in hours. Must be between 72 and 144 inclusive.",
         default=72,
     )
     release_checklist: str = form.label(
@@ -242,6 +242,8 @@ class VotePolicyForm(form.Form):
     @pydantic.model_validator(mode="after")
     def validate_vote_fields(self) -> VotePolicyForm:
         validation.validate_policy_min_hours(self.min_hours)
+        if (self.min_hours == 0) and config.is_production_mode():
+            raise ValueError("Minimum voting period must be between 72 and 144 hours inclusive.")
         return self
 
 
diff --git a/atr/shared/voting.py b/atr/shared/voting.py
index fa488423..ff8af251 100644
--- a/atr/shared/voting.py
+++ b/atr/shared/voting.py
@@ -19,6 +19,7 @@ from __future__ import annotations
 
 import pydantic
 
+import atr.config as config
 import atr.form as form
 import atr.models.safe as safe
 import atr.models.sql as sql
@@ -72,6 +73,8 @@ class StartVotingForm(form.Form):
     @classmethod
     def validate_vote_duration(cls, field):
         util.validate_vote_duration(field)
+        if (field == 0) and config.is_production_mode():
+            raise ValueError("Minimum vote duration must be between 72 and 144 hours inclusive.")
         return field
 
     @pydantic.model_validator(mode="after")
diff --git a/atr/util.py b/atr/util.py
index bce76206..64113ac2 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -1639,8 +1639,8 @@ def validate_vote_duration(duration: int):
     if duration == 0:
         return
     if duration > 0:
-        if duration < 72 or duration > 168:
-            raise ValueError("Vote duration must be between 0 and 168")
+        if (duration < 72) or (duration > 144):
+            raise ValueError("Vote duration must be 0 or between 72 and 144 hours inclusive.")
 
 
 # TODO: AM put these rules into safe.versionkey
diff --git a/tests/unit/test_vote_duration_zero.py b/tests/unit/test_vote_duration_zero.py
new file mode 100644
index 00000000..00121dc2
--- /dev/null
+++ b/tests/unit/test_vote_duration_zero.py
@@ -0,0 +1,67 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pydantic
+import pytest
+
+import atr.config as config
+import atr.shared.projects as projects
+import atr.shared.voting as voting
+
+_POLICY_BASE = {
+    "csrf_token": "x",
+    "variant": "vote",
+    "project_key": "test",
+    "email_cc": [],
+    "email_bcc": [],
+    "vote_mode": "email",
+    "min_hours": 0,
+    "release_checklist": "",
+    "vote_comment_template": "",
+    "start_vote_subject": "",
+    "start_vote_template": "",
+    "finish_vote_template": "",
+}
+
+_VOTING_BASE = {
+    "csrf_token": "x",
+    "email_to": "dev@test.apache.org",
+    "vote_duration": 0,
+    "subject": "[VOTE] Release Apache Test 1.0",
+    "subject_template_hash": "abc",
+    "body": "Please vote.",
+    "vote_mode": "email",
+    "rendered_revision": "00001",
+}
+
+
+def test_policy_form_rejects_zero_min_hours_in_production(monkeypatch) -> None:
+    monkeypatch.setattr(config, "is_production_mode", lambda: True)
+    with pytest.raises(pydantic.ValidationError, match="between 72 and 144"):
+        projects.VotePolicyForm.model_validate(_POLICY_BASE)
+
+    monkeypatch.setattr(config, "is_production_mode", lambda: False)
+    projects.VotePolicyForm.model_validate(_POLICY_BASE)
+
+
+def test_voting_form_rejects_zero_vote_duration_in_production(monkeypatch) -> None:
+    monkeypatch.setattr(config, "is_production_mode", lambda: True)
+    with pytest.raises(pydantic.ValidationError, match="between 72 and 144"):
+        voting.StartVotingForm.model_validate(_VOTING_BASE)
+
+    monkeypatch.setattr(config, "is_production_mode", lambda: False)
+    voting.StartVotingForm.model_validate(_VOTING_BASE)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org