Skip to content

Add multi-approver quorum, vote audit trail and on_timeout resolution to workflow approvals#579

Open
fernandorocagonzalez wants to merge 2 commits into
ctrliq:mainfrom
fernandorocagonzalez:feat-approval-quorum
Open

Add multi-approver quorum, vote audit trail and on_timeout resolution to workflow approvals#579
fernandorocagonzalez wants to merge 2 commits into
ctrliq:mainfrom
fernandorocagonzalez:feat-approval-quorum

Conversation

@fernandorocagonzalez

Copy link
Copy Markdown
Contributor

What this adds

Three improvements to workflow approval nodes, aimed at closing the gap with the manual gates in Jenkins (input step) and GitHub Actions (environment required reviewers):

Multi approver quorum

Approval nodes get a required_approvals field (default 1, so existing nodes behave exactly as before). When it is higher than 1, each user with the approver role casts one vote and the node only moves to successful once that many distinct users have approved. A single deny always denies the node immediately, same as GitHub Actions reviewers. Users cannot vote twice, the approve/deny endpoints return a 400 if they try, and the UI disables the buttons once you have voted while the node waits for the rest of the quorum.

The approval detail endpoint and UI now show approvals_received next to required_approvals so approvers can see how far along the node is.

Vote audit trail

Every approve/deny action is recorded as a WorkflowApprovalVote row: who voted, what they voted, when, and an optional free text comment (POST /approve/ {"comment": "..."}). Votes are exposed at:

  • /api/v2/workflow_approvals/N/votes/
  • /api/v2/workflow_approval_votes/ (flat, filterable by user, vote, date and so on)

Votes are immutable through the API and survive job cleanup and user deletion: the FKs are severed with SET_NULL and the vote keeps denormalized copies of the approval name, workflow job id/name and username, so the audit trail stays useful after the approval itself is purged. Visibility follows the workflow: you can list votes for approvals you can see, superusers and system auditors see everything including orphaned votes.

The approved_or_denied_by field keeps working as before, pointing at the user whose vote resolved the node.

Timeout resolution

Approval nodes with a timeout get an on_timeout choice (deny, the current behavior and the default, or approve). With approve, when the timeout expires the task manager marks the node successful instead of failed, still flagging it timed_out and recording an explanation saying it was automatically approved. This mirrors what Jenkins pipelines commonly do with timeout wrapped input steps.

Notes

  • Migration 0206 adds the two fields on template and job plus the vote table.
  • With required_approvals = 1 and on_timeout = 'deny' nothing changes for existing deployments; those are the defaults applied by the migration.
  • Comments on votes are API only for now, the UI approve/deny buttons stay single click. Showing a comment box there is a possible follow up.
  • New functional tests cover quorum progression, the deny veto, double vote rejection, both timeout resolutions, vote visibility rules and the audit fields surviving deletion. UI has jest coverage for the new node form fields and the votes display in the approval detail.

… to workflow approvals

Approval nodes get a required_approvals count (default 1, existing behavior
unchanged): each user with the approver role casts a single vote and the
node completes once enough distinct users approved. One deny always denies
the node immediately. Users cannot vote twice; the approve/deny endpoints
reject repeat votes and the UI disables the buttons via user_has_voted
while the node waits for the rest of the quorum.

Every vote is stored as a WorkflowApprovalVote with user, action, timestamp
and an optional comment (POST {"comment": ...} on approve/deny), browsable
at /api/v2/workflow_approvals/N/votes/ and filterable across workflows at
/api/v2/workflow_approval_votes/. Votes are immutable through the API and
keep denormalized approval/job/user names so the audit trail survives job
cleanup and user deletion.

Approval nodes with a timeout can now choose what the timeout means with
on_timeout: deny (default, current behavior) or approve, in which case the
task manager marks the expired node successful, still flagged timed_out
and with a job explanation saying it was automatically approved.
@cigamit cigamit self-assigned this Jul 17, 2026
@cigamit cigamit added the enhancement New feature or request label Jul 17, 2026
@cigamit
cigamit requested a review from Copilot July 17, 2026 15:11

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 enhances workflow approval nodes across the API, scheduler, and UI by adding (1) multi-approver quorum support, (2) an immutable vote audit trail, and (3) configurable timeout resolution (auto-approve vs auto-deny), aligning approvals with “required reviewers” style gates.

Changes:

  • Adds required_approvals + on_timeout fields to workflow approval templates and jobs, with UI support for configuring and displaying these values.
  • Introduces WorkflowApprovalVote as an audit log for approve/deny actions, plus new vote list/detail API endpoints and related UI display.
  • Updates TaskManager timeout handling so timed-out approvals can optionally resolve as successful (“approve”) instead of failed (“deny”).

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
awx/ui/src/screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.test.js Extends detail view tests to cover quorum progress, votes rendering, and on-timeout display.
awx/ui/src/screens/WorkflowApproval/WorkflowApprovalDetail/WorkflowApprovalDetail.js Fetches and displays vote audit trail; shows quorum progress and on-timeout behavior.
awx/ui/src/screens/WorkflowApproval/shared/WorkflowDenyButton.js Disables deny when the user has already voted.
awx/ui/src/screens/WorkflowApproval/shared/WorkflowApprovalButton.js Disables approve when the user has already voted.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Visualizer.js Propagates required_approvals and on_timeout into visualizer node state.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js Populates edit defaults for approval node quorum/timeout settings.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.js Adds initial form values for new approval node fields.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.test.js Adds coverage for the new approval node form inputs.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js Adds UI inputs for “Required approvals” and “On timeout” for approval nodes.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.test.js Updates modal tests to include new approval node form values.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js Ensures non-approval nodes strip the new approval-only fields; sets defaults.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js Sends required_approvals / on_timeout to API when editing approval nodes.
awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js Sends required_approvals / on_timeout to API when adding approval nodes.
awx/ui/src/api/models/WorkflowApprovals.js Adds client method for GET /workflow_approvals/:id/votes/.
awx/main/tests/functional/api/test_workflow_approval_quorum.py Functional tests for quorum progression, veto deny, double-vote rejection, timeout behavior, and vote audit durability/visibility.
awx/main/scheduler/task_manager.py Implements on_timeout=approve behavior when expiring approvals.
awx/main/models/workflow.py Adds fields and vote model; records votes and applies quorum logic when approving/denying.
awx/main/models/init.py Exposes WorkflowApprovalVote in the models package.
awx/main/migrations/0206_approval_quorum_votes.py Migration adding quorum fields and creating the vote table.
awx/main/access.py Adds access model for vote objects and prefetches votes for approvals.
awx/api/views/root.py Exposes top-level API route for workflow approval votes.
awx/api/views/init.py Adds comment parsing for votes; prevents double votes; adds vote list/detail views.
awx/api/urls/workflow_approval.py Adds nested votes endpoint for a workflow approval.
awx/api/urls/workflow_approval_vote.py Adds top-level votes list/detail endpoints.
awx/api/urls/urls.py Wires vote URL module into the API router.
awx/api/serializers.py Adds new serializer fields for quorum/voting state and a serializer for vote records.

Comment thread awx/api/serializers.py Outdated
Comment thread awx/main/models/workflow.py Outdated
Comment thread awx/api/views/__init__.py
…p quorum stable when an approver is deleted, accept vote comments from any dict-like request body
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Development

Successfully merging this pull request may close these issues.

3 participants