asf-tooling commented on issue #1216:
URL:
https://github.com/apache/tooling-trusted-releases/issues/1216#issuecomment-4409663686
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@2da7807a`
**Type:** `new_feature` • **Classification:** `actionable` •
**Confidence:** `medium`
**Application domain(s):** `voting`, `web_api_infrastructure`
### Summary
Issue #1216 is a tracking issue for four distinct enhancements to Trusted
Vote mode: (1) API endpoints for vote operations, (2) sending reminders to
binding voters who vote on the mailing list instead of through ATR, (3)
carrying IPMC member votes from podling round 1 to round 2, and (4)
automatically resolving hybrid votes. Some infrastructure already exists
(auto_resolve in tasks/vote.py, automatic_resolve_when_finished parameter in
writers/vote.py), but the specific features requested are largely new. The
issue was created 4 days ago with no discussion.
### Where this lives in the code today
#### `atr/storage/writers/vote.py` — `FoundationCommitter.cast_trusted`
(lines 65-74)
_currently does this_
Existing trusted vote casting logic that would be reused by a new API
endpoint for casting votes programmatically.
```python
async def cast_trusted( # noqa: C901
self,
project_key: safe.ProjectKey,
version_key: safe.VersionKey,
choice: sql.VoteChoice,
comment: str,
fullname: str,
expected_vote_seq: int | None = None,
expected_vote_mode: sql.VoteMode | None = None,
) -> tuple[list[str], str]:
```
#### `atr/tasks/vote.py` — `auto_resolve` (lines 40-50)
_currently does this_
Existing auto_resolve task handles automatic resolution for Trusted Vote
non-podling votes. Would need extension for 'hybrid votes' mentioned in the
issue.
```python
@checks.with_model(args.VoteAutoResolve)
async def auto_resolve(task_args: args.VoteAutoResolve) -> results.Results |
None: # noqa: C901
"""Automatically resolve a non-podling Trusted Vote after its scheduled
end."""
async with db.session() as data:
release = await data.release(
key=task_args.release_key,
_project=True,
_committee=True,
_release_policy=True,
_project_release_policy=True,
).get()
```
#### `atr/storage/writers/vote.py` — `CommitteeParticipant.start` (lines
292-311)
_currently does this_
Vote initiation already accepts automatic_resolve_when_finished; this is the
infrastructure for item 4 (auto-resolve hybrid votes).
```python
async def start( # noqa: C901
self,
email_to: str,
project_key: safe.ProjectKey,
version_key: safe.VersionKey,
selected_revision_number: safe.RevisionNumber,
vote_duration_choice: int,
subject: str,
body_data: str,
asf_fullname: str,
release: sql.Release | None = None,
promote: bool = True,
permitted_recipients: list[str] | None = None,
email_cc: list[str] | None = None,
email_bcc: list[str] | None = None,
second_round_email_to: str | None = None,
expected_vote_mode: sql.VoteMode | None = None,
notify_when_finished: bool = False,
automatic_resolve_when_finished: bool = False,
) -> sql.Task:
```
#### `atr/storage/writers/vote.py` — `CommitteeMember.resolve_release`
(lines 620-629)
_needs modification_
When resolving a podling round 1 vote (voting_round == 1 and passed), this
is where IPMC member ballot carry-through from round 1 to round 2 would be
implemented.
```python
async def resolve_release( # noqa: C901
self,
project_key: safe.ProjectKey,
release: sql.Release,
voting_round: int | None,
vote_result: Literal["passed", "failed", "cancelled"],
latest_vote_task: sql.Task,
asf_fullname: str,
resolution_body: str,
) -> tuple[sql.Release, int | None, str, str | None]:
```
#### `atr/tasks/vote.py` — `end_notify` (lines 139-146)
_currently does this_
Existing end_notify sends reminders to the vote initiator. The new feature
(item 2) would need a similar mechanism but targeting binding voters who voted
via mailing list.
```python
@checks.with_model(args.VoteEndNotify)
async def end_notify(task_args: args.VoteEndNotify) -> results.Results |
None:
"""Send a self addressed reminder when a vote ends, if it has not been
resolved."""
async with db.session() as data:
release = await data.release(key=task_args.release_key,
_project=True, _committee=True).get()
if release is None:
log.info(f"Vote end notify skipped: release
{task_args.release_key} not found")
return _end_notify_skipped("release_not_found")
```
#### `atr/tabulate.py` — `votes` (lines 201-210)
_currently does this_
Vote tabulation from mailing list threads. This would be the detection point
for identifying binding voters who voted via email (item 2 - send them a
reminder).
```python
async def votes( # noqa: C901
committee: sql.Committee | None,
thread_id: str,
*,
excluded_message_ids: set[str] | None = None,
) -> tuple[int | None, dict[str, models.tabulate.VoteEmail]]:
"""Tabulate votes."""
excluded_message_ids_normalized = {
_message_id_normalize(message_id) for message_id in
(excluded_message_ids or set()) if message_id
}
```
### Where new code would go
- `atr/api/__init__.py` — after symbol checks_ongoing
New API endpoints for vote operations (cast trusted vote, resolve vote,
list ballots) would be added alongside existing API endpoints.
- `atr/models/api.py` — new models section
Request/response models for new vote API endpoints (VoteCastArgs,
VoteResolveArgs, VoteBallotListResults, etc.).
- `atr/tasks/vote.py` — after symbol end_notify
A new task for detecting and notifying binding voters who voted on the
mailing list instead of through ATR.
- `atr/storage/writers/vote.py` — in CommitteeMember.resolve_release where
voting_round == 1 and vote_result == passed
Logic to copy IPMC member ballots from round 1 to round 2 during podling
vote transition.
### Proposed approach
This tracking issue contains four distinct features that should likely be
implemented as separate PRs:
1. **API endpoints**: Add API endpoints in `atr/api/__init__.py` mirroring
the web POST handlers in `atr/post/vote.py` and `atr/post/resolve.py`. These
would use JWT authentication (via `@jwtoken.require`), call the same storage
layer methods (`cast_trusted`, `resolve`), and return JSON responses. Models
would be added to `atr/models/api.py`.
2. **Binding voter reminders**: When tabulating votes from the mailing list
thread (in `atr/tabulate.py` or during periodic monitoring), detect if a
binding voter has voted via email. If so, schedule a task (similar to
`end_notify`) that sends them a direct email reminder that in Trusted Vote
mode, their vote must be recorded through ATR. This likely requires a new task
type and integration with the vote monitoring loop.
3. **IPMC ballot carry-through**: In `CommitteeMember.resolve_release`
(atr/storage/writers/vote.py), when a podling round 1 vote passes and
transitions to round 2, query the round 1 BallotPaper records for voters who
are IPMC members (binding in round 2). Create corresponding round 2 BallotPaper
entries for those voters, preserving their original choice.
4. **Auto-resolve hybrid votes**: The infrastructure for auto-resolve
already exists (`auto_resolve` task, `automatic_resolve_when_finished`
parameter). This item likely means extending it to work with votes that have
both email and trusted ballots (hybrid mode), or making auto-resolve the
default option in the UI when starting a Trusted Vote.
### Suggested patches
#### `atr/api/__init__.py`
Add a vote/cast API endpoint that allows programmatic trusted vote casting
with JWT authentication, reusing existing storage layer logic.
````diff
--- a/atr/api/__init__.py
+++ b/atr/api/__init__.py
@@ -56,6 +56,48 @@ ROUTES_MODULE: Final[Literal[True]] = True
@api.typed
[email protected]
+@quart_schema.security_scheme([{"BearerAuth": []}])
+@quart_schema.validate_response(models.api.VoteCastResults, 200)
+async def vote_cast(
+ _vote_cast: Literal["vote/cast"],
+ data: models.api.VoteCastArgs,
+) -> DictResponse:
+ """
+ URL: POST /vote/cast
+
+ Cast a trusted vote on a release candidate.
+
+ The release must be in Trusted Vote mode and in the RELEASE_CANDIDATE
phase.
+ The caller must be authenticated via JWT (obtained from a PAT).
+ """
+ asf_uid = _jwt_asf_uid()
+ fullname = await _jwt_fullname(asf_uid)
+ async with storage.write(asf_uid) as write:
+ wafc = write.as_foundation_committer()
+ email_to, error_message = await wafc.vote.cast_trusted(
+ data.project_key,
+ data.version_key,
+ sql.VoteChoice(data.choice),
+ data.comment,
+ fullname,
+ expected_vote_seq=data.vote_seq,
+ expected_vote_mode=sql.VoteMode.TRUSTED,
+ )
+ if error_message:
+ raise exceptions.Conflict(error_message)
+ return models.api.VoteCastResults(
+ endpoint="/vote/cast",
+ success=True,
+ email_to=email_to,
+ ).model_dump(mode="json"), 200
+
+
+async def _jwt_fullname(asf_uid: str) -> str:
+ """Look up the full name for a JWT-authenticated user."""
+ # TODO: confirm how to resolve fullname from asf_uid (likely via LDAP)
+ person = await ldap.person_by_uid(asf_uid)
+ if person is None:
+ raise exceptions.Forbidden("User not found")
+ return person.fullname
+
+
[email protected]
@quart_schema.validate_response(models.api.ChecksListResults, 200)
async def checks_list(
_checks_list: Literal["checks/list"],
````
### Open questions
- What does 'hybrid votes' mean in item 4? Is it a specific VoteMode, or
does it refer to votes where both email and trusted ballots coexist?
- For item 2 (binding voter reminders), should the detection happen during
periodic vote monitoring, or at vote resolution time when tabulating mailing
list votes?
- For item 3 (IPMC ballot carry-through), should the carried ballots be
exact copies, or should they be marked differently (e.g., with a flag
indicating they originated from round 1)?
- What is issue #1204 that is referenced? Its scope would affect this
issue's implementation.
- The _jwt_fullname helper function proposed in the diff needs verification
- how does the codebase currently resolve fullnames for API-authenticated users?
- Should the API endpoints support all vote operations (cast, resolve, list,
start) or just a subset initially?
- For the VoteCastArgs and VoteCastResults models that would go in
atr/models/api.py - what exact fields are needed beyond project_key,
version_key, choice, comment, and vote_seq?
### Files examined
- `atr/storage/writers/vote.py`
- `atr/blueprints/api.py`
- `atr/post/resolve.py`
- `atr/post/vote.py`
- `atr/post/voting.py`
- `atr/tabulate.py`
- `atr/tasks/vote.py`
- `atr/api/__init__.py`
### Related issues
This issue appears related to: #1226.
_Both address improvements to Trusted Vote mode, with #1226 being a specific
sub-task of #1216_
---
*Draft from a triage agent. A human reviewer should validate before merging
any change. The agent did not run tests or verify diffs apply.*
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]