Repository navigation
feat(purchase): persist no_submission proof on failed executions (#118 PR-C1) - #828
Conversation
…PR-C1) Add purchase_executions.no_submission (migration 000113, additive, default false). It is set only where the code proves no cloud purchase call was made: saveTerminalExecution on a beforeSubmissionError failure (now also for direct executes) and the bare plan step failure in notifications. The reaper and every outcome-unknown failure leave it false. The upsert is monotone (no_submission OR new value) so a stale in-memory copy cannot clear the proof. All twelve SELECT/RETURNING column lists and the scanner read the new column. The ladder reconciler (C2) will treat a failed execution as "nothing bought" only when this proof is set. Refs #118 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 13 billable files and costs up to $3.25.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 40 minutes for your next included review. View limit details
Comment |
Gate review (Opus, claude-opus-5-5): CHANGES REQUESTED at 15ab64bBlockingB1. Reachable trace at this head:
The row ends Reproduced on real PostgreSQL 17.11 with a store-level replay of steps 1-4 (gate-only test, not pushed): With Required:
Residual, non-blocking for C1 (follow-up issue; must be settled before C2 trusts the flag)The guard does not close the other ordering of the same race: if step 3 lands before step 2, the final failed+false write still ORs onto the true from step 2. The root cause predates this PR. The approve path executes an Checked and OK
Local evidence at this head (git archive 15ab64b, GOTOOLCHAIN=go1.26.9, throwaway PostgreSQL 17.11 on 127.0.0.1:54419, temporary local test-helper override, not committed)
Verdict: CHANGES REQUESTED (B1). Not merged. CI at this head: Unit Tests failed in "Download dependencies" (proxy.golang.org stream INTERNAL_ERROR for klauspost/compress, an infra flake, not code). Not rerun, since the head has to change anyway. |
…118 PR-C1) The monotone OR let a stale non-failed upsert keep the proof: approve CAS -> claimAndExecute fails pre-submission (proof true) -> the approve path's stale audit upsert sets the row back to approved (proof kept) -> the provider is called and the row ends failed with the proof still true. Guard the upsert: keep the proof only when the written status is failed, clear it otherwise. Tests on real PostgreSQL: a completed or approved write over failed+true clears the proof; the approve-path sequence replay ends failed with no proof; the readers test now seeds the proof on a failed row. Refs #118 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Gate verdict (Opus, claude-opus-5-5): APPROVED at 3bc2dfcRe-gated after the B1 fix (2fc96f8), then
|
Refs #118
PR-C1 of the ladder tranche reconciler. Persisted proof only: C2 (the reconciler, tranche status_reason, chain walk) is not included and follows as a separate stacked PR. Nothing fires yet: no executor exists, ladder flags stay off, no tranche can reach
fired, so this changes no purchase behavior.Migration number is claimed at merge.
000113is a placeholder (current max on main is 000112); renumber to max+1 right before merge.What
purchase_executions.no_submission BOOLEAN NOT NULL DEFAULT FALSE(ADD COLUMN IF NOT EXISTS, additive; old code ignores it). Existing rows read false (no proof, fail closed).PurchaseExecution.NoSubmission; the upsert keeps the proof only for a failed write:no_submission = CASE WHEN $3 = 'failed' THEN purchase_executions.no_submission OR $28 ELSE false END. Among failed writes it is monotone (a stale failed copy cannot clear it); any non-failed write clears it, so a stale approved/completed upsert cannot leave the proof on a row that reaches the provider.scanExecutionRowsread the column.saveTerminalExecutionon abeforeSubmissionErrorfailure with no rec purchased (now also for direct executes, which previously carried no persisted trace), andstampFailedBarePlanStep(pending/notified bare step failed before the executor). NOT stamped: the reaper,safeFail, outcome-unknown failures,errAllAccountsFailed.000100_..._test.gonow migrates to head before using the store, since the store selects columns added after 000100.Verification
Real PostgreSQL 17.11 (throwaway local instance; Docker unavailable here; temporary helper override reverted before commit).
go test -tags integrationpasses for: new migration test (pre-existing row = false, up statement idempotent, down drops the column),TestNoSubmission_UpsertIsMonotone(false->true persists, stale false upsert does not clear),TestNoSubmission_ReadersReturnTheFlag(GetExecutionByID, GetExecutionByPlanAndDate, ListStuckExecutions, TransitionExecutionStatus RETURNING), and the fullinternal/config,internal/purchase,internal/api,internal/schedulerand migrations integration suites.internal/serverintegration was not completed locally (timed out at 600s); CI covers it.ORkept the proof through the approve-path sequence (approvals.go:392 CAS -> claimAndExecute pre-submission failure -> stale approved upsert at :412 -> provider call at :421). Reproduced on real PG17 with the old SQL (3 new tests fail), pass with the CASE guard. Tests:TestNoSubmission_NonFailedWriteClearsProof(completed and approved),TestNoSubmission_StaleApproveUpsertDoesNotKeepProofOnSubmittedRow; the readers test now seeds the proof on a failed row.no_submission = $28) fails the monotone test ("a stale false upsert must not clear the proof").TestSaveTerminalExecution_ReleasesSuppressionsOnlyBeforeSubmissionextended with the proof expectation per case;TestStampFailedBarePlanStep_RecordsNoSubmissionProof; pgxmock column lists updated.go test ./internal/...7390 passed;make lint0 issues;git secrets --scanwith--register-awsrun under BSD and GNU grep.Known residual race (do not trust the flag yet)
The other ordering of the same race is NOT closed here: if the stale approved save lands before the pre-submission failure, the final failed write still ORs onto a true proof. Root cause predates this PR: the approve path runs
executeAndFinalizeon anapprovedrow without claimingrunning(approvals.go:421) whileclaimAndExecuteacceptsapproved(manager.go:180), so two executors can run one row. C2 and B2 must not treatno_submissionas proof until that race is closed (e.g. stamp the proof only through a CAS fromrunningthe executor owns). Follow-up issue to be filed.🤖 Generated with Claude Code