Skip to content

feat: deployment approval UI and backend handlers#5256

Merged
Flo4604 merged 21 commits intofeat/external-contributor-protectionfrom
feat/deployment-approval-ui
Mar 16, 2026
Merged

feat: deployment approval UI and backend handlers#5256
Flo4604 merged 21 commits intofeat/external-contributor-protectionfrom
feat/deployment-approval-ui

Conversation

@Flo4604
Copy link
Member

@Flo4604 Flo4604 commented Mar 9, 2026

What does this PR do?

Adds GitHub Check Runs support for deployment authorization workflow. When external contributors push to protected branches, the system now creates visible check runs in GitHub PRs that require project member authorization before deployments can proceed.

The implementation includes:

  • GitHub Check Run API integration with create, update, and list operations
  • Automatic check run creation for unauthorized deployments from external contributors
  • Check run status updates to "success" when deployments are authorized by project members
  • New web UI page for authorizing deployments with approve/dismiss actions
  • Configuration support for GitHub App ID and private key credentials
  • Audit logging for deployment authorization events

CleanShot 2026-03-12 at 09.53.21.png

CleanShot 2026-03-12 at 10 31 45

When testing:::

Please ensure your app has the following permissions(this is important for prod too)!!

CleanShot 2026-03-12 at 09.56.41.png

CleanShot 2026-03-12 at 09.10.36.png

CleanShot 2026-03-12 at 09.10.47.png

then go into https://github.com/settings/installations and find your app and re-approve the perms.

For testing the happy path: FORCE_DEPLOYMENT_APPROVAL should be set to false in the yaml file.

Fixes # (issue)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Create a PR from an external contributor to a protected branch
  • Verify a GitHub check run appears with "action_required" status
  • Navigate to the authorization page and approve the deployment
  • Confirm the check run updates to "success" status
  • Test the dismiss functionality and verify check run remains failed
  • Verify audit logs are created for authorization events

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Ran make fmt on /go directory
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

@vercel
Copy link

vercel bot commented Mar 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard Ready Ready Preview, Comment Mar 16, 2026 11:55am

Request Review

Copy link
Member Author

Flo4604 commented Mar 9, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d8a374f0-b90e-4d63-84f1-6b8af96651e3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/deployment-approval-ui
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from d315940 to cc70395 Compare March 9, 2026 13:42
@Flo4604 Flo4604 force-pushed the feat/external-contributor-protection branch from 36216f3 to 66dad62 Compare March 9, 2026 13:42
@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from cc70395 to 5eeb2b0 Compare March 10, 2026 08:34
@Flo4604 Flo4604 force-pushed the feat/external-contributor-protection branch from 66dad62 to 4f7c6e8 Compare March 10, 2026 08:34
@Flo4604 Flo4604 force-pushed the feat/external-contributor-protection branch from 4f7c6e8 to 24ccc92 Compare March 10, 2026 09:11
@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from 5eeb2b0 to 990e123 Compare March 10, 2026 09:11
@Flo4604 Flo4604 force-pushed the feat/external-contributor-protection branch from 24ccc92 to fc52cb0 Compare March 10, 2026 14:13
@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from 990e123 to 9edfbae Compare March 10, 2026 14:13
@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from 9edfbae to d034877 Compare March 10, 2026 17:58
@Flo4604 Flo4604 force-pushed the feat/external-contributor-protection branch from fc52cb0 to 4fea8a6 Compare March 10, 2026 17:58
@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from d034877 to 60772bf Compare March 11, 2026 16:14
@Flo4604 Flo4604 force-pushed the feat/external-contributor-protection branch from 4fea8a6 to 0edf2ec Compare March 11, 2026 16:14
@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from 60772bf to 6a191d7 Compare March 11, 2026 18:02
@Flo4604 Flo4604 force-pushed the feat/external-contributor-protection branch from 0edf2ec to be84e80 Compare March 11, 2026 18:02
@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from 6a191d7 to a808725 Compare March 11, 2026 18:21
@Flo4604 Flo4604 force-pushed the feat/deployment-approval-ui branch from a808725 to adf6636 Compare March 12, 2026 07:32
Flo4604 and others added 21 commits March 16, 2026 12:52
Add the complete approval/rejection flow for gated deployments:

Backend (ctrl service):
- Add ApproveDeployment and RejectDeployment RPCs to DeployService proto
- Implement approve handler: validates status, updates to pending, records
  approval, triggers deploy workflow via Restate
- Implement reject handler: validates status, updates to failed
- Add FindAppBuildSettingByAppEnv SQL query for approval flow

Dashboard:
- Add awaiting_approval to deployment status badges, filters, and collection
- Add DeploymentApprovalBanner component with approve/reject buttons
- Show banner on deployment detail page when awaiting approval
- Add tRPC mutations for approve/reject calling ctrl service
- Add deployment.approve and deployment.reject audit log events
- Add 'rejected' to deployment status badges, filters, and collections
- Add DeploymentRejectedBanner with red error styling
- Show rejected banner on deployment detail page
- Add rejected to grouped status filter with error color
Remove deployment approval/rejection banners, status badge configs,
filter options, and tRPC routes for awaiting_approval/rejected statuses.

Add /projects/[projectId]/authorize page that reads branch from URL
params and calls AuthorizeDeployment RPC. Add authorize tRPC route.
The ctrl API needs GitHub App credentials to fetch branch HEAD
when authorizing deployments from external contributors.
- Remove awaiting_approval and rejected from deployment statuses, filters,
  and status badge configs (no longer used — authorization is handled via
  GitHub Check Runs, not deployment status)
- Remove deployment.approve and deployment.reject audit log events
- Delete unused duplicate deployment-status-badge.tsx in table components
- Fix broken page.tsx referencing undefined awaitingApproval/rejected vars
- Extract createCtrlClient helper to deduplicate identical ctrl client
  creation boilerplate across 9 trpc router files
Centered layout with GitHub + shield icons, commit details card showing
branch/SHA/message/sender from URL query params, proper Button components,
success/error states, and non-member handling hint.
Frontend now validates SHA format (40-char hex) before allowing authorization
and passes it to the backend which verifies it matches the branch HEAD.
Instead of a generic error, show a clear message explaining the branch
has new commits and direct the user to GitHub to find the latest
authorization link.
Instead of telling the user to check GitHub, parse the new HEAD SHA
from the backend error and offer a direct "View Latest Commit" button
that navigates to the updated authorize page.
…om DB

- Rewrite authorize page to take single deploymentId search param
- Fetch deployment details from DB via getById instead of URL params
- Handle awaiting_approval, already authorized, and failed states
- Simplify authorize mutation input to just deploymentId
- Add gitCommitMessage, gitCommitAuthorHandle, gitCommitAuthorAvatarUrl, projectId to getById response
- Register getById in tRPC router
Replace the separate /authorize page with an inline DeploymentApproval
component shown when deployment status is awaiting_approval. Delete the
standalone authorize page. Add awaiting_approval to deriveStatusFromSteps
valid statuses so it doesn't fall back to pending.
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