Epic 6 — Approval Workflow

Covers the awaiting_approval inbox, approving and rejecting stages with mandatory comments, the 7-day pause countdown that terminates unanswered gates, and the BranchProtectionViolation gate banner that blocks approval when branch-protection rules would reject the PR.

Personas: AP (primary approver) OP BU (view-only on approval gate)

Shared modules: OptimisticMutationHelper CorrelationChip LastSyncedBadge EnvProvenance

Story 6.1 — Awaiting-Approval Inbox

As an
AP
I want
to see all stages currently awaiting my approval in one place
So that
I never miss an approval gate that is blocking a run
Scenario: Inbox loads with pending approvals
Givenat least one stage has stages.status = 'awaiting_approval' and the user has the AP role Whenthe user navigates to /approvals and GET /approvals resolves Thena paginated inbox is rendered showing: stage name, project name, run_id (truncated, copyable), time waiting, artifact preview link, and a countdown to the 7-day auto-expiry
Scenario: Empty inbox
Givenno stages are awaiting approval Thenthe inbox renders an empty state reading All clear — no pending approvals
Endpoint / DBPurpose
GET /approvalsPaginated list of awaiting_approval stages
DB stages.status = awaiting_approvalFilter source
DB approvals.expires_at7-day countdown source

Story 6.2 — Approve a Stage

As an
AP
I want
to approve a stage gate with an optional comment
So that
the pipeline run continues to the next stage
Scenario: Approval submitted successfully
Giventhe stage is in awaiting_approval state and no BranchProtectionViolation is active Whenthe AP clicks Approve (optionally adds a comment) and confirms ThenPOST /approvals/{approval_id}/approve is issued with optional { "comment": "..." }; the stage card status transitions to Approved; the item is removed from the inbox
Mutation addendum — Optimistic approve + rollback:

UI clones the approvals store slice into prior_state, removes the inbox item optimistically and marks the stage card Approved, then issues POST /approvals/{approval_id}/approve. On server error, the store slice is restored from prior_state exactly; the inbox item reappears; the CorrelationChip surfaces the failure. Regression: zero phantom approvals remain in the stage timeline.

Endpoint / DBPurpose
POST /approvals/{approval_id}/approveRecord approval decision
DB approvals.decision = approvedWritten on success

Story 6.3 — Reject a Stage

As an
AP
I want
to reject a stage gate with a mandatory comment
So that
the pipeline run is terminated and the reason is recorded for audit
Scenario: Rejection with mandatory comment
Giventhe stage is in awaiting_approval state Whenthe AP clicks Reject, enters a mandatory comment (blank comment blocks submission), and confirms ThenPOST /approvals/{approval_id}/reject is issued with { "comment": "..." }; the run transitions to failed; the stage card shows a Rejected badge with the comment; the inbox item is removed
Mutation addendum — Optimistic reject + rollback:

Same pattern as Story 6.2 but writes rejected decision. On rollback, the inbox item reappears with the original awaiting_approval state (regression assertion).

Endpoint / DBPurpose
POST /approvals/{approval_id}/rejectRecord rejection with mandatory comment
DB approvals.decision = rejectedWritten on success

Story 6.4 — 7-Day Pause Countdown

As an
AP
I want
to see how long remains before an unanswered approval gate auto-expires
So that
I can prioritise my review queue before a run is automatically terminated
Scenario: Countdown displayed on inbox item and stage card
Givenan approval has been pending for more than 12 hours Thena live countdown timer is shown: Expires in <days>d <hours>h <minutes>m; it updates each minute; the colour transitions from neutral to amber (< 24 h) to red (< 6 h)
Scenario: Approval auto-expired — run failed
Giventhe SSE stream (or polling) notifies the UI that approvals.expires_at has passed Thenthe inbox item transitions to an Expired state; the stage card shows Approval timed out; the run status shows failed

Story 6.5 — BranchProtectionViolation Gate Banner

As an
AP
I want
to see a clear blocking banner when the target branch's protection rules would reject the PR
So that
I know to fix the branch-protection configuration before approving rather than approving a run that will immediately fail at PR delivery
Scenario: BranchProtectionViolation banner blocks the Approve button
Giventhe approval gate API returns blocked_by: "BranchProtectionViolation" Whenthe AP views the approval decision form Thena red banner is shown: Branch protection violation — the target branch requires status checks that the current pipeline cannot satisfy.; the Approve button is disabled; a View branch rules link (external, opens in new tab) is provided; CorrelationChip is shown
Scenario: Rejection is still available during BranchProtectionViolation
Giventhe BranchProtectionViolation banner is active Thenthe Reject button remains enabled so the AP can terminate the run with an explanatory comment
Endpoint / DBPurpose
GET /approvals/{approval_id}Returns blocked_by field when gate is blocked
DB approvals.blocked_byStores blocking failure class