Skip to content

fix(api/purchases): reject approval of non-AWS execution with deleted account (closes #609)#613

Merged
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/issue-609-approve-orphan-guard
May 20, 2026
Merged

fix(api/purchases): reject approval of non-AWS execution with deleted account (closes #609)#613
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/issue-609-approve-orphan-guard

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 20, 2026

Summary

Fix approach

Without this guard, approving an orphan Azure/GCP execution surfaces an opaque chained-credential error from the cloud SDK (IMDS endpoint, missing env vars, CLIs absent from Lambda runtime). The 409 lets the frontend show a useful toast directing the operator to cancel the purchase instead.

Test plan

  • TestHandler_approvePurchase_AzureOrphanRejects409 - Azure orphan returns 409 with descriptive message
  • TestHandler_approvePurchase_GCPOrphanRejects409 - GCP orphan returns same 409
  • TestHandler_approvePurchase_AWSOrphanFallsThrough - AWS orphan does NOT trigger the guard
  • TestHandler_approvePurchase_NonOrphanUnchanged - execution with valid account proceeds normally
  • TestManager_ApproveExecution_NonAWSOrphanReturnsError - token path (SQS worker / email link) Azure orphan returns error
  • TestManager_ApproveExecution_AWSOrphanFallsThrough - token path AWS orphan falls through

Summary by CodeRabbit

  • Bug Fixes

    • Purchase approval now rejects with a clear error when attempting to execute for unavailable Azure and GCP cloud accounts.
  • Tests

    • Added test coverage for purchase approval validation with unavailable accounts.

Review Change Stack

… account (closes #609)

Add a preflight guard in both approve entry points — the HTTP handler
(approvePurchaseViaSession and the token path via approvePurchase) and
the token-authenticated ApproveExecution in the purchase package — that
rejects an approval request with a clear 409 when the execution's
CloudAccountID is nil and the provider is non-AWS. AWS executions with a
nil CloudAccountID are left through because the ambient-host-account
fallback from PR #607/#604 handles them correctly.

Without this guard the failure surfaces deep in the cloud SDK as an
opaque chained-credential error (IMDS endpoint, missing env vars, CLIs
absent from the Lambda runtime) that gives the operator no indication
that the underlying account was deleted.

Empty provider is treated as AWS (legacy rows pre-dating multi-cloud
support) to preserve backward compatibility.

Regression tests: Azure orphan 409, GCP orphan 409, AWS orphan
falls through, non-orphan with valid account proceeds normally;
plus ApproveExecution (token path) Azure orphan and AWS orphan cases.
@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/all-users Affects every user effort/s Hours type/bug Defect labels May 20, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 20, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Warning

Rate limit exceeded

@cristim has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 22 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d70f5f35-a519-41fd-912b-b1a9719e20c0

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2e907 and 9f02a6b.

📒 Files selected for processing (3)
  • internal/api/handler_purchases.go
  • internal/purchase/approvals.go
  • internal/purchase/approvals_test.go
📝 Walkthrough

Walkthrough

The PR implements an orphan account guard that prevents approval of non-AWS executions lacking a CloudAccountID. The service layer validates the condition and returns a descriptive 409 error; the handler layer integrates this check via a new helper. Regression tests cover Azure/GCP rejection, AWS fallthrough, and non-orphan passthrough at both layers.

Changes

Orphan Account Guard for Purchase Approvals

Layer / File(s) Summary
Service-layer orphan guard validation
internal/purchase/approvals.go
ApproveExecution adds a preflight orphanExecutionError check that rejects executions with nil CloudAccountID and non-AWS provider recommendations before calling ApproveAndExecute.
Handler-layer guard integration
internal/api/handler_purchases.go
approvePurchase refactors to call loadApproveExecution helper, which fetches the execution, maps missing rows to 404, and delegates the orphan guard check to the service layer, propagating any 409 rejection.
Service-layer regression tests
internal/purchase/approvals_test.go
Two new tests verify that non-AWS executions with nil CloudAccountID are rejected with an error containing "no longer exists" and the provider name, while AWS executions with nil CloudAccountID bypass the guard and complete successfully.
Handler-layer regression tests
internal/api/handler_purchases_test.go
Four new tests confirm Azure and GCP orphans are rejected with 409 (purchase manager not called), AWS orphans are not blocked, and non-orphan executions (populated CloudAccountID) proceed unaffected.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • LeanerCloud/CUDly#609 — This PR directly implements the issue's solution: the orphan guard that rejects non-AWS nil-CloudAccountID executions at approval time.
  • LeanerCloud/CUDly#602 — Both address handling of nil CloudAccountID with non-AWS providers (this PR guards rejection, issue #602 addresses credential setup), making them related in scope.

Possibly related PRs

  • LeanerCloud/CUDly#373 — Modifies the same approvePurchase/ApproveExecution approval flow, introducing synchronous ApproveAndExecute execution that this PR's orphan guard now guards.
  • LeanerCloud/CUDly#299 — Changes the purchase approval dispatch in the same handler and approval service layers that this PR extends with the orphan guard logic.

Poem

🐰 A guard at the gate, wise and kind,
Orphans of Azure and GCP find
No passage to execute without home,
While AWS clouds are forever free to roam.
Issue 609 is now fixed with care!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a guard to reject approval of non-AWS executions with deleted (orphan) accounts, and directly references the issue being fixed (#609).
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-609-approve-orphan-guard

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/api/handler_purchases.go`:
- Around line 266-290: The orphan-check logic in loadApproveExecution duplicates
purchase.orphanExecutionError; remove the duplicated predicate/error
construction and call the centralized function instead (or extract the predicate
into a shared helper used by both). Specifically, replace the manual block in
loadApproveExecution that inspects execution.CloudAccountID and
execution.Recommendations with a call to purchase.orphanExecutionError (or the
new shared helper) to get the ClientError (or nil), return it if non-nil, and
otherwise return execution; ensure the function you call is exported or
accessible from internal/api, and preserve the same error semantics (HTTP 409
ClientError) and use execution.ExecutionID and provider info from the execution
when building the error via the shared routine.

In `@internal/purchase/approvals.go`:
- Around line 67-79: The orphanExecutionError currently treats executions with
nil execution.CloudAccountID as orphaned for non-AWS providers even when
recommendations carry account IDs; update orphanExecutionError to, after
obtaining provider := execution.Recommendations[0].Provider and confirming
provider is non-empty and not "aws", iterate execution.Recommendations and if
any recommendation has a non-nil CloudAccountID (e.g., rec.CloudAccountID)
return nil (not orphan); only return the fmt.Errorf referencing
execution.ExecutionID and provider if no recommendation-level CloudAccountID is
found.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f23bdb42-589b-4336-af6e-41b9d74a6d12

📥 Commits

Reviewing files that changed from the base of the PR and between bc81d15 and 1c2e907.

📒 Files selected for processing (4)
  • internal/api/handler_purchases.go
  • internal/api/handler_purchases_test.go
  • internal/purchase/approvals.go
  • internal/purchase/approvals_test.go

Comment thread internal/api/handler_purchases.go
Comment thread internal/purchase/approvals.go Outdated
…level account id (CR pass-1)

Export OrphanExecutionError from internal/purchase and call it from
internal/api/handler_purchases.go, removing the duplicated predicate
in loadApproveExecution (CR actionable 1).

Update OrphanExecutionError to treat an execution as NOT orphaned when
any individual recommendation carries a non-nil CloudAccountID, which
covers the multi-rec fan-out path where the execution-level field is
intentionally nil while recs still name their target accounts (CR
actionable 2).

Add TestOrphanExecutionError_RecLevelAccountIDPreventsOrphan as a
regression guard for the new rec-level check.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 20, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim cristim merged commit f6772c7 into feat/multicloud-web-frontend May 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant