Symptom
User approved an AWS purchase from the dashboard and it immediately disappeared from Purchase History. Not "failed", not "completed" — gone.
Root cause (structural)
The History view (internal/api/handler_history.go) builds its list from two sources:
- Completed purchases from the
purchase_history table (rendered as completed).
- Non-completed
purchase_executions rows whose status is in:
var historyExecutionStatuses = []string{"pending", "notified", "failed", "expired", "cancelled"}
This list excludes approved, running, and paused. The exclusion was justified by #372's assumption that approval executes synchronously: an Approve click drives the row approved -> completed (lands in purchase_history) or approved -> failed (stays, shown) all inside one HTTP request, so approved is "only ever transient".
That assumption breaks under interruption. Manager.ApproveAndExecute (internal/purchase/approvals.go) persists status approved before calling executeAndFinalize, and only finalizeExecution (internal/purchase/manager.go) flips it to completed/failed after executePurchase returns. If the synchronous execution does not finish inside the request, the row stays persisted as approved:
- Lambda timeout — AWS RI/SP purchase calls are slow; a multi-rec or multi-account approval can exceed the function timeout.
- Panic / crash mid-execution.
A row left in approved (or running/paused) is in neither purchase_history nor historyExecutionStatuses, so it silently vanishes from the UI. For a financial action this is the worst failure mode: the user can't tell whether the purchase went through, and may re-approve / re-purchase.
Secondary contributing path
Even on the happy path, savePurchaseHistory (internal/purchase/execution.go:449) logs and swallows a failed SavePurchaseHistory insert, while the execution is still marked completed. A failed history write (constraint/type mismatch — recall the recent #255 MonthlyCost-nullable and #453 ServiceDetails churn) also yields a completed-but-invisible purchase.
Fix
- Stop hiding in-flight/stuck states: add
approved, running, paused to historyExecutionStatuses so an approval that hasn't finalized stays visible (rendered with its real status, e.g. "approving..." / "in progress"). The render path already has case "approved", "completed": handling in annotateApproved, so only the query filter needs widening.
- Dedup against
purchase_history so a completed execution row and its purchase_history row don't both show (key on execution ID / commitment ID).
- Don't let a completed purchase be invisible: either surface a
completed execution row that has no matching purchase_history row, or make the SavePurchaseHistory failure at execution.go:449 transition the execution to a visible state (not silently completed).
- Add a regression test: an execution persisted as
approved/running appears in the History list; a completed execution whose history write failed is not silently dropped.
Diagnostic to confirm in prod
For the user's vanished purchase, check either:
- DB:
SELECT status FROM purchase_executions WHERE execution_id = '<id>' — if it's approved or running, this is the timeout/crash path.
- Lambda logs around the approval for
Failed to save history / AUDIT LOSS / a timeout, which would indicate the secondary path.
Symptom
User approved an AWS purchase from the dashboard and it immediately disappeared from Purchase History. Not "failed", not "completed" — gone.
Root cause (structural)
The History view (
internal/api/handler_history.go) builds its list from two sources:purchase_historytable (rendered ascompleted).purchase_executionsrows whose status is in:This list excludes
approved,running, andpaused. The exclusion was justified by #372's assumption that approval executes synchronously: an Approve click drives the rowapproved -> completed(lands inpurchase_history) orapproved -> failed(stays, shown) all inside one HTTP request, soapprovedis "only ever transient".That assumption breaks under interruption.
Manager.ApproveAndExecute(internal/purchase/approvals.go) persists statusapprovedbefore callingexecuteAndFinalize, and onlyfinalizeExecution(internal/purchase/manager.go) flips it tocompleted/failedafterexecutePurchasereturns. If the synchronous execution does not finish inside the request, the row stays persisted asapproved:A row left in
approved(orrunning/paused) is in neitherpurchase_historynorhistoryExecutionStatuses, so it silently vanishes from the UI. For a financial action this is the worst failure mode: the user can't tell whether the purchase went through, and may re-approve / re-purchase.Secondary contributing path
Even on the happy path,
savePurchaseHistory(internal/purchase/execution.go:449) logs and swallows a failedSavePurchaseHistoryinsert, while the execution is still markedcompleted. A failed history write (constraint/type mismatch — recall the recent #255 MonthlyCost-nullable and #453 ServiceDetails churn) also yields a completed-but-invisible purchase.Fix
approved,running,pausedtohistoryExecutionStatusesso an approval that hasn't finalized stays visible (rendered with its real status, e.g. "approving..." / "in progress"). The render path already hascase "approved", "completed":handling inannotateApproved, so only the query filter needs widening.purchase_historyso acompletedexecution row and itspurchase_historyrow don't both show (key on execution ID / commitment ID).completedexecution row that has no matchingpurchase_historyrow, or make theSavePurchaseHistoryfailure at execution.go:449 transition the execution to a visible state (not silently completed).approved/runningappears in the History list; acompletedexecution whose history write failed is not silently dropped.Diagnostic to confirm in prod
For the user's vanished purchase, check either:
SELECT status FROM purchase_executions WHERE execution_id = '<id>'— if it'sapprovedorrunning, this is the timeout/crash path.Failed to save history/AUDIT LOSS/ a timeout, which would indicate the secondary path.