fix(rip302): disputed jobs are a dead end — the worker can never re-deliver and the escrow only goes back to the poster - #8086
Open
Vyacheslav-Tomashevskiy wants to merge 1 commit into
Conversation
/dispute answers the client with "Worker can re-deliver or admin can refund", but /deliver accepted only 'claimed' jobs and no route ever moved a job back out of 'disputed'. A disputed job is also outside the TTL expiry sweep, so its escrow never released on its own. The only reachable exit was the poster cancelling and taking the full escrow back - after the deliverable was already readable on GET /agent/jobs/<id>. - /deliver now accepts 'disputed' from the assigned worker (re-delivery), clears the stale rejection_reason and logs the action as 'redelivered'. - The TTL gate applies to a first delivery only: a disputed job is deliberately outside the expiry sweep, so gating it there would fail re-delivery with a misleading STATE_RACE once the original TTL passed. - The /dispute response now names only routes that actually exist (/deliver for the worker, /cancel for the poster); there is no admin refund endpoint. Tests: tests/test_agent_dispute_redelivery.py - 8 cases, 6 fail on main and pass with the fix, 2 are regression guards (open/completed jobs are still undeliverable; a claimed job past TTL still expires and refunds). Agent-economy suite 84 passed, BCOS SPDX check OK.
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.
The bug
POST /agent/jobs/<id>/disputeanswers the client with:Neither half was reachable:
/deliveraccepted onlyclaimedjobs (if j["status"] != STATUS_CLAIMED: 409), and no route moves a job back out ofdisputed— so the worker could never act on the rejection reason./agent/jobs,/agent/jobs/<id>,/claim,/deliver,/accept,/dispute,/cancel,/agent/reputation/<w>,/agent/stats._expire_refundable_jobreturns early unless the status isopen/claimed), so the escrow never released on its own either.The only reachable exit was the poster calling
/cancel, which refunds 100% of the escrow to the poster — after the deliverable is already stored on the job and readable by anyone viaGET /agent/jobs/<id>. So on a money path, the documented recourse for the worker did not exist.Reproduced against
main(Flask test client, real endpoints, the same harness style astests/test_agent_dispute_accept_race.py):The fix
Make the promise the API already prints actually true, without changing any other transition:
/deliveracceptsdisputedfrom the assigned worker as a re-delivery; the atomicWHERE ... AND status = ?guard now expects the status it read, so the concurrency protection from Security: Hardened P2P Sync Module (Replay Protection + Deterministic JSON) #2867 is preserved.rejection_reason(the reason stays inagent_job_log) and is logged asredelivered, so the audit trail distinguishes the retry from the first delivery.STATE_RACEonce the original deadline passed./disputemessage now names only routes that exist:/deliverfor the worker,/cancelfor the poster. No admin endpoint is invented.Deliberately not changed: the poster can still cancel a disputed job and be refunded. Whether a poster should be able to unilaterally reclaim escrow after receiving a deliverable is a protocol decision, not something to slip into a bugfix — this PR only removes the dead end that left the worker with no move at all.
Tests
tests/test_agent_dispute_redelivery.py— 8 cases:worker == 1_000_000,agent_escrow == 0);rejection_reason;redelivered, exactly onedelivered);403, job staysdisputed;openandcompletedjobs are still undeliverable (409);/deliver(410, escrow 0, poster whole);Mutation check (revert
rip302_agent_economy.pyonly): 6 of 8 fail onmain, all 8 pass with the fix; the 2 that pass either way are the regression guards, which is the point of them.Verification:
python3 -m pytest tests/test_agent_dispute_redelivery.py→ 8 passedtest_agent_reputation,test_agent_economy_bounties,test_agent_economy_cli,test_agent_economy_dashboard_security,test_agent_economy_v2_dashboard_security,test_agent_economy_error_redaction,test_agent_jobs_query_validation,test_agent_economy_sdk,test_rustchain_agent_cli,test_agent_dispute_accept_race, plus the new file) → 84 passedgit diff --check→ cleanpython3 tools/bcos_spdx_check.py --base-ref upstream/main→ BCOS SPDX check: OKClaiming under rustchain-bounties#71 · RTC wallet
RTCd1554f0f35576faf01d386a6be1c947f560dd0b7