Repository navigation
fix(purchase): route AutoPurchase steps to approval when 4-eyes is on - #1793
Conversation
With RequireDifferentApprover on, the scheduler's auto path (the cron sweep and the SQS execute_purchase handler, both through executableByScheduler) no longer executes a recommendation-bearing step of an AutoPurchase plan. The step stays notified and waits for a human approval in the approval queue, like a manual plan's step. The approval then runs enforceFourEyesPolicy, which accepts any identified approver for a system-created row. Empty steps route as before. A missing or unreadable global config still fails closed. A step root pre-created through the admin API key has no recorded creator and is not system-created, so nobody can approve it under 4-eyes. It waits, then expires. This follows decision (a) on #1771 and is documented on RequireDifferentApprover. Tests: - real-Postgres: the sweep leaves the row notified with no purchase, and user B's approval then buys once; - a root pre-created by A: A is denied and B succeeds; - with the flag off, the sweep executes as before; - unit tests for the sweep and the SQS gate. Dropping the gate fails them. Part of #1771 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The upcoming-purchase email told every recipient the step "will be purchased on X unless canceled". That isn't true for a manual plan, or for an AutoPurchase plan while 4-eyes is on. Those steps are bought only once someone approves them, and the email had no approve link. sendPlanNotification now takes needsApproval, computed per tick from the global config the notifier already reads: !AutoPurchase || no config || RequireDifferentApprover. When it's set, the template keeps the totals and cap sentence and says: - the step is due on X and is bought once approved; - to approve it in the approval queue, through the existing Review & Edit link; - that it does not run automatically. When it isn't set, the text is byte-identical to before. Tests: - the helper's modes; - the rendered wording in both modes; - the call-site wiring in the SendUpcomingPurchaseNotifications tests. Forcing the value at the call site fails them. Closes #1771 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 12 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
…ting #1771 changes intended behavior: with RequireDifferentApprover on, an AutoPurchase plan's due step waits for a human approval instead of auto-executing. #1761's TestCombinedPlanPurchase_AutomaticOnScheduledExecution turned on 4-eyes for every combined-plan fixture and asserted auto-execution, so it pinned the old behavior and failed in CI. It is updated for the intended change: - The fixture turns 4-eyes on only for the manual (approval) flows, which still exercise the different-approver path. - TestCombinedPlanPurchase_AutomaticOnScheduledExecution now runs with 4-eyes off and still asserts auto-execution. - New TestCombinedPlanPurchase_AutomaticWithFourEyesWaitsForApproval is the end-to-end proof at the API layer. With 4-eyes on, the due sweep executes nothing, fails nothing and buys nothing, and the step stays notified. A different user's approval then buys it once. Part of #1771 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
New head 97f5325: one test-only commit on 658a922, fixing the red CI run. #1761's
Local: |
|
APPROVED 97f5325 by claude-ui-fix-1 Cold review of #1793 (Closes #1771), base main The approval requirement covers every path that can buyThe only provider purchase call is
Toggling the setting mid-run: the gate reads the global config per row ( The test delta at 97f5325
Verification (local, macOS, heavy-review-2 lock, throttled
|
|
APPROVED 97f5325 by claude-qa-ui-4 Cold review of the code at 658a922 plus the test-only delta at 97f5325.
Non-blocking, predates this PR:
|
|
Merge gate at 97f5325:
Main moved to da8c78e (#1790, #1795) with no overlap with this PR's files. The merge is clean (git merge-tree). |
Closes #1771
With 4-eyes approval mode on (
RequireDifferentApprover), an AutoPurchase plan's step no longer buys without a second person. It waits for a human approval in the approval queue, the same as a manual plan's step. This implements the A05-003 decision recorded on #348 and #1771.What changes
Commit 1: the gate (
internal/purchase/manager.go).executableByScheduleris the single gate for both unattended executors, the cron sweep and the SQSexecute_purchasehandler. It now returns "not executable" for a recommendation-bearing step while 4-eyes is on.notified. Approving it through the queue (session approve) runsApproveAndExecute, thenenforceFourEyesPolicy. That accepts any identified approver for a system-created row, and refuses the creator of a root pre-created with the plan.autoPurchaseConfig.Commit 2: the email (
internal/email,internal/purchase/notifications.go). The upcoming-purchase email used to say every step "will be purchased on X unless canceled", with no approve link. That isn't true for a manual plan, or for an AutoPurchase plan under 4-eyes. When a step needs approval:When the step runs on its own, the text is byte-identical to before.
Decisions and known limits
These are recorded on #1771, and the owner can redirect them.
RequireDifferentApprover.Tests
four_eyes_auto_plan_integration_test.go):notifiedwith no purchase. Then user B's approval buys once, and the row completes.ErrFourEyesDenied, and B succeeds.planNeedsApprovalin each mode;SendUpcomingPurchaseNotificationstests.planNeedsApproval;needsApprovalto false at the call site.Local verification (macOS)
At the committed tree, using the touched packages:
go vet -tags integrationpasses for purchase, email and config.go test -tags integration ./internal/purchase/passes, including the 4-eyes, system-execution and plan-step tests.Rebased onto current main after the local gate, with no conflicts. CI checks the rebased head.
A non-author adversarial review of the staged tree ran 2 passes and ended clean. Its findings are fixed in this branch.
🤖 Generated with Claude Code