docs/plans/2025-12-31-grant-request-refactor-design.md
Grant request completion logic (grant privilege + close issue + create comment) is duplicated in two places:
backend/runner/approval/runner.go:171-200): When no approval template is found during issue creationbackend/api/v1/issue_service.go:619-652): When approval flow completesAdditionally, there's partial duplication at issue_service.go:596-601 for granting privilege.
completeGrantRequestIssue on IssueServiceCreateIssue (when no approval needed) and ApproveIssue (when approval completes)Location: backend/api/v1/issue_service.go
// completeGrantRequestIssue grants privilege and closes a grant request issue.
// Called when:
// 1. Issue created without approval template (auto-approved)
// 2. Issue approval flow completes
//
// Returns the updated issue with DONE status.
func (s *IssueService) completeGrantRequestIssue(
ctx context.Context,
issue *store.IssueMessage,
grantRequest *storepb.GrantRequest,
) (*store.IssueMessage, error) {
// 1. Grant the privilege
if err := utils.UpdateProjectPolicyFromGrantIssue(ctx, s.store, issue, grantRequest); err != nil {
return nil, err
}
// 2. Update issue status to DONE
newStatus := storepb.Issue_DONE
updatedIssue, err := s.store.UpdateIssue(ctx, issue.UID, &store.UpdateIssueMessage{
Status: &newStatus,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to update issue %q's status", issue.Title)
}
// 3. Create issue comment documenting the status change
if _, err := s.store.CreateIssueComments(ctx, common.SystemBotEmail, &store.IssueCommentMessage{
IssueUID: issue.UID,
Payload: &storepb.IssueCommentPayload{
Event: &storepb.IssueCommentPayload_IssueUpdate_{
IssueUpdate: &storepb.IssueCommentPayload_IssueUpdate{
FromStatus: &issue.Status,
ToStatus: &updatedIssue.Status,
},
},
},
}); err != nil {
// Non-fatal: log warning but continue
slog.Warn("failed to create issue comment after changing the issue status", log.BBError(err))
}
return updatedIssue, nil
}
FindAndApplyApprovalTemplateUpdate signature to return whether approval template was found:
func FindAndApplyApprovalTemplate(
ctx context.Context,
stores *store.Store,
webhookManager *webhook.Manager,
licenseService *enterprise.LicenseService,
issue *store.IssueMessage,
) (approvalTemplateFound bool, error)
Changes in backend/runner/approval/runner.go:
approvalTemplate != nil, nil at the endCreateIssueLocation: backend/api/v1/issue_service.go:395-415
// Trigger approval finding based on issue type
switch issue.Type {
case storepb.Issue_GRANT_REQUEST, storepb.Issue_DATABASE_EXPORT:
approvalTemplateFound, err := approval.FindAndApplyApprovalTemplate(
ctx, s.store, s.webhookManager, s.licenseService, issue)
if err != nil {
slog.Error("failed to find approval template",
slog.Int("issue_uid", issue.UID),
slog.String("issue_title", issue.Title),
log.BBError(err))
// Continue anyway - non-fatal error
}
// For GRANT_REQUEST without approval template, auto-complete it
if issue.Type == storepb.Issue_GRANT_REQUEST && !approvalTemplateFound {
issue, err = s.completeGrantRequestIssue(ctx, issue, issue.Payload.GrantRequest)
if err != nil {
return nil, connect.NewError(connect.CodeInternal,
errors.Wrapf(err, "failed to complete grant request"))
}
}
case storepb.Issue_DATABASE_CHANGE:
// DATABASE_CHANGE needs to wait for plan check to complete
s.bus.ApprovalCheckChan <- int64(issue.UID)
default:
// For other issue types, no approval finding needed
}
ApproveIssueLocation: backend/api/v1/issue_service.go:595-652
Replace existing logic:
Before (lines 595-601):
// Grant the privilege if the issue is approved.
if approved && issue.Type == storepb.Issue_GRANT_REQUEST {
if err := utils.UpdateProjectPolicyFromGrantIssue(ctx, s.store, issue, payload.GrantRequest); err != nil {
return nil, err
}
// TODO(p0ny): Post project IAM policy update activity.
}
After:
// Grant the privilege if the issue is approved (will be completed below)
// TODO(p0ny): Post project IAM policy update activity.
Replace lines 619-652 with:
// If the issue is a grant request and approved, complete it
if issue.Type == storepb.Issue_GRANT_REQUEST && approved {
var err error
issue, err = s.completeGrantRequestIssue(ctx, issue, payload.GrantRequest)
if err != nil {
slog.Debug("failed to complete grant request issue", log.BBError(err))
}
}
Location: backend/runner/approval/runner.go
Remove lines 170-200 (entire grant+close block)
Update return at line 224:
return approvalTemplate != nil, nil
Update line 62 (signature):
func FindAndApplyApprovalTemplate(...) (bool, error)
Update line 95 (processIssue call):
if _, err := findApprovalTemplateForIssue(...); err != nil {
In completeGrantRequestIssue:
UpdateProjectPolicyFromGrantIssue fails → return error (don't close issue)In CreateIssue:
completeGrantRequestIssue fails → return error to userIn ApproveIssue:
completeGrantRequestIssue fails → log debug, continue (matches current behavior)backend/api/v1/issue_service.go - Add helper method, update CreateIssue and ApproveIssuebackend/runner/approval/runner.go - Remove grant+close logic, update return typecompleteGrantRequestIssue with various scenarios