test(buyer): re-award an agent that actually won, not a presumed one (#230) - #287
Merged
Merged
Conversation
…230) `n_equal_agents_cannot_overspend_the_shared_budget` spawns five agents that race for the money lock. The first three to acquire it win 30 sats each of a shared 100; the other two are correctly refused. Which three win is decided by scheduling order, and nothing pins it. The idempotency leg then re-awarded `format!("{:064x}", 0)`, assuming agent 0 was among the winners. When scheduling leaves agent 0 among the two refused it has no `reservations.job_id` row, so that call is not a re-award at all -- it is a fresh reserve of 30 against the 10 the winners left, which `reserve` correctly refuses: idempotent re-award: Reserve(InsufficientAvailable { requested: 30, available: 10, bound: Wallet }) So the test read a correct overspend refusal as an idempotency failure. It was green by scheduling luck since it was written, and #282 adding two tests to the binary perturbed the schedule enough to surface it. Each task now reports the job id it won, and the leg re-awards `winners[0]`, so it asserts idempotency against a job that was genuinely awarded -- which is what it was always trying to assert. Worth stating because a red row on a money guard reads as a broken guard: the production path was never at fault. The aggregate asserts three lines above the panic (`winners.len() == 3`, `reserved == 90`) had already proven the invariant held. The overspend guard was refusing an overspend under adversarial scheduling. Red-proven: pointing the re-award at a never-awarded id reproduces the CI panic byte for byte. Reported-by: keeper:mobee-market-orch Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unblocks the dev→main cut. The money-path row went red on #283 (
640 passed; 1 failed) after #282 landed. It is a pre-existing test bug, not a #282 regression and not a production defect.What was wrong
n_equal_agents_cannot_overspend_the_shared_budgetspawns five agents that race for the money lock. The first three to acquire it win 30 sats each of a shared 100; the other two are correctly refused. Which three win is decided by scheduling order — nothing pins it.The idempotency leg then re-awarded
format!("{:064x}", 0), assuming agent 0 won. When scheduling leaves agent 0 among the two refused, it has noreservations.job_idrow — so that call is a fresh reserve of 30 against the 10 the winners left, whichreservecorrectly refuses:available: 10= min(cap, balance) 100 − the winners' 90. Those numbers only occur if agent 0 was not a winner, which is what pins the diagnosis.The production path was never at fault
Worth stating outright, because a red row on a money guard reads as a broken guard until someone says otherwise. The two aggregate asserts three lines above the panic —
winners.len() == 3andreserved == 90— were passing. The invariant held. What happened is the overspend guard refusing an overspend under adversarial scheduling.The fix
Each task now returns the job id it won; the leg re-awards
winners[0]. It therefore asserts idempotency against a job that was genuinely awarded, which is what it was always trying to assert. Strictly stronger than before, and no longer luck-dependent.Verification
requested: 30, available: 10, bound: Wallet), so the mechanism is demonstrated on demand rather than argued.641 passed; 0 failed; 2 ignored— meets the structural acceptance rule (maxN passed≥ 641, 0 failed).Why it surfaced now, and why that is good news
#282 added two tests to the binary, which perturbed the parallel schedule. The bug has been latent since #230 — any scheduling change (a new test, a faster runner, a different core count) would do it.
It was structurally invisible on
main, where the money suite still runs inside the acp job and gets skipped when that job is reaped. #276's decouple is the reason anyone can see it: the instrument caught a latent defect in an overspend guard on its first run with real counts.Notes for reviewers
award_with_reservation's return type to an enum and touches every call site in this file, including the line this PR edits. This goes first (it gates the tag) and they rebase onto it — the cheap direction. Visible only from the file list, not from either PR alone.