Partial-failure across the purchase flow is not handled cleanly (two layers)
When some recs in a multi-rec / multi-account / fan-out purchase succeed and others fail, the flow leaves inconsistent state.
(a) Backend: whole execution marked failed + success notification skipped despite real commitments
internal/purchase/execution.go:74-80 (single-account) returns early when len(purchaseErrors) > 0, so sendPurchaseNotification is skipped even though the successful recs already fired and were written to purchase_history with Purchased=true.
finalizeExecution (internal/purchase/manager.go:128-137) is binary (completed XOR failed), so the row is marked failed even though real commitments were created.
executeForAccount (execution.go:129-148, multi-account) has the same shape.
Result: the user gets no confirmation email for commitments that actually purchased, and History/status shows failed for a partially-successful run (misleading on a financial action, and invites a re-attempt that would double-buy on the already-purchased recs).
(b) Frontend: fan-out partial failure leaves orphaned pending executions
handleFanOutExecute (frontend/src/app.ts:445) uses Promise.allSettled over N per-bucket executePurchase POSTs. If bucket 3 of 5 fails, buckets 1-2 are already-created pending executions that approvers can act on, with no compensating cleanup — only a toast reporting counts.
Fix
- Backend: introduce a
partially_completed outcome (or keep completed but record per-rec failures) so the row reflects reality, and send the success notification for the recs that did purchase. Never mark a row failed when commitments were created (re-purchase / double-spend hazard).
- Frontend: on fan-out partial failure, surface which buckets created pending executions and offer to cancel the orphaned ones (or document that they remain actionable).
- Regression tests for both layers.
Dedup
Distinct from #632 (approved-strand) and #621 (vanish). No existing issue covers partial-success status/notification or fan-out orphan cleanup. New. Surfaced in the full purchase-workflow trace.
Partial-failure across the purchase flow is not handled cleanly (two layers)
When some recs in a multi-rec / multi-account / fan-out purchase succeed and others fail, the flow leaves inconsistent state.
(a) Backend: whole execution marked
failed+ success notification skipped despite real commitmentsinternal/purchase/execution.go:74-80(single-account) returns early whenlen(purchaseErrors) > 0, sosendPurchaseNotificationis skipped even though the successful recs already fired and were written topurchase_historywithPurchased=true.finalizeExecution(internal/purchase/manager.go:128-137) is binary (completed XOR failed), so the row is markedfailedeven though real commitments were created.executeForAccount(execution.go:129-148, multi-account) has the same shape.Result: the user gets no confirmation email for commitments that actually purchased, and History/status shows
failedfor a partially-successful run (misleading on a financial action, and invites a re-attempt that would double-buy on the already-purchased recs).(b) Frontend: fan-out partial failure leaves orphaned pending executions
handleFanOutExecute(frontend/src/app.ts:445) usesPromise.allSettledover N per-bucketexecutePurchasePOSTs. If bucket 3 of 5 fails, buckets 1-2 are already-created pending executions that approvers can act on, with no compensating cleanup — only a toast reporting counts.Fix
partially_completedoutcome (or keepcompletedbut record per-rec failures) so the row reflects reality, and send the success notification for the recs that did purchase. Never mark a rowfailedwhen commitments were created (re-purchase / double-spend hazard).Dedup
Distinct from #632 (approved-strand) and #621 (vanish). No existing issue covers partial-success status/notification or fan-out orphan cleanup. New. Surfaced in the full purchase-workflow trace.