Skip to content

test(seller): pin the award-dedup contract on two distinct award events (#279) - #282

Merged
orveth merged 1 commit into
devfrom
test/award-dedup-two-legs
Jul 30, 2026
Merged

test(seller): pin the award-dedup contract on two distinct award events (#279)#282
orveth merged 1 commit into
devfrom
test/award-dedup-two-legs

Conversation

@orveth

@orveth orveth commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Tests only — no production code changes. Pins the seller's award-dedup contract and files the hole in it as #279.

What the two legs are for

The seller dedups awards on two different keys, and the shapes they catch are not the same shape:

  • award record keys on event id (awards.award_id PK) ⇒ catches a re-seen award id (relay redelivery).
  • execute guard keys on job state (Delivered/Paid/Failed ⇒ skip) ⇒ catches a redundant award for a terminal job.

A buyer retry produces neither: a fresh event id for a job we already hold. So covering redelivery says nothing about retries, and the two legs test that boundary from both sides. Both outcomes were declared before either test was written, so neither could be retrofitted.

Leg 1 — a_fresh_award_id_for_a_delivered_job_is_recorded_but_never_re_executed (green)

Two distinct award ids, one job id, job Delivered. Asserts the composition, not the predicate alone:

  1. re-seen id ⇒ Awarded::Duplicate (the contrast case, so both keys are distinguished in one place)
  2. fresh id ⇒ Awarded::Newthe record layer does not catch a retry
  3. the job row still reads Delivered — the re-attempt does not clobber terminal state
  4. therefore the state guard is what refuses the second run

Step 2 is the point. Asserting only the predicate leaves the record layer's answer unstated, and that answer is what decides whether the guard is load-bearing at all.

This is not already covered. resume_selects_awarded_or_executing_not_delivered_or_terminal tests the pure predicate. delivered_or_paid_job_is_not_re_executed asserts "a delivered job must not re-execute on a duplicate award" — but it never records a second award; it drives one award to Delivered and checks the predicate. That message named a property the test did not construct, so this PR corrects it to say what it actually asserts and leg 1 carries the duplicate-award claim.

Red-proven. Changing the award path's INSERT OR IGNORE INTO jobs to an upsert that writes state = 'awarded' fails leg 1 on exactly the intended line:

assertion `left == right` failed: a second award must never clobber a terminal job state back to awarded
  left: Some(Awarded)
 right: Some(Delivered)

Leg 2 stayed green under that break, correctly — it does not depend on terminal state.

Leg 2 — mid_flight_second_award_is_admitted_for_execution_no_per_job_in_flight_lock (#279)

Same shape, but the job is awarded/executing (both are exercised). The record layer says New, the guard says proceed, and nothing between them refuses — so a second execute_job is admitted for a job already running.

This test passing is the bad news. It is a tripwire: it asserts the unguarded behaviour and goes red the moment the per-job in-flight lock lands, at which point the fixer inverts one assertion and closes #279. The doc comment says so in place.

Why a tripwire and not #[ignore]: an ignored test reports nothing whether the hole is open or closed — the failure mode is silence, which is the class of defect this lane has spent the day removing. The hazard is real and worth naming: CI now enforces a known-wrong behaviour, so anyone who fixes the hole sees a red test. That is intentional and the comment routes them, but reviewers should agree it is the right trade before merging.

What #279 costs — and does not

Not double payment; that is blocked further down (reservation PK, single job-keyed watcher, outbox dedup) — verified by the mobee lead, not by this PR. The costs are a duplicate agent process, a slot-accounting bypass (the first award already removed the parked permit, so the second execution holds no permit and the node exceeds its configured concurrency invisibly), and a second push that can move the branch tip off the journaled commit — which the buyer refuses on tip-match, failing toward not paying a seller that did the work.

⚠ There is no observed field instance. This is a code fact from reading both dedup paths; an earlier "second award event" in this fleet turned out to be an accept event and that attribution was retracted.

Verification

  • Both legs green: 3 passed including the neighbour, run under the money-path feature set (--no-default-features --features gateway,git-delivery,wallet).
  • Count prediction, so the money-path row's jump is expected rather than anomalous: dev is currently 639 passed; 2 ignored (641). This PR adds 2 ⇒ the row should read 641 passed; 2 ignored (643 total). cargo test --lib -- --list reports 643 on this branch and both new tests appear by name in it, so they are genuinely in the suite CI runs — not compiled out.
  • Branched off dev after ci: run the money-path suite in its own job (#271) #276, so these run inside the new money-path job.

…ts (#279)

The seller dedups awards on two different keys, and neither covers a retry
arriving mid-flight. Two legs, outcomes declared before either was written so
neither could be retrofitted to whatever the code happened to do.

Leg 1 — post-terminal, expected GREEN. A fresh award id for a Delivered job is
`Awarded::New` at the record layer, so the event-id key does NOT catch the retry
shape; the job row survives the re-attempt; and the job-state guard is what
refuses the second run. It asserts that composition rather than the predicate
alone, because the record layer's answer is what decides whether the guard is
load-bearing. It also asserts the contrast case — a re-seen id is `Duplicate` —
so both shapes and both keys are distinguished in one place.

Leg 2 — mid-flight, expected RED as a finding (#279). Same shape while the job
is `awarded`/`executing`: the record layer says New, the guard says proceed, and
nothing between them refuses, so a second execution is admitted. It is written
as a PASSING tripwire that goes red when the per-job in-flight lock lands,
rather than an `#[ignore]`d failing test — an ignored test reports nothing
whether the hole is open or closed.

Leg 1 is red-proven: making the award path clobber the job row's state back to
`awarded` fails it on that exact assertion.

Also corrects a neighbouring assertion message. It claimed a delivered job
"must not re-execute on a duplicate award", but that test never records a second
award — it checks the state predicate. It now says what it asserts, and leg 1
carries the duplicate-award claim it was standing in for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mobee Ready Ready Preview Jul 30, 2026 12:17am

Request Review

@orveth
orveth merged commit 1d51078 into dev Jul 30, 2026
5 of 6 checks passed
orveth added a commit that referenced this pull request Jul 30, 2026
…230, #287)

Pre-existing test-only flake, not a #282 regression and not a production defect. The overspend race test re-awarded a hardcoded presumed winner (format!({:064x},0)); when scheduling left agent 0 among the two refused, that call was a fresh 30-sat reserve against 10 available and reserve() correctly refused it — read as an idempotency failure when the guard did the right thing. The aggregate asserts above the panic (winners.len()==3, reserved==90) were passing throughout. Fix: capture the actual race winners and re-award winners[0], making the idempotent-re-award leg deterministic. Test-only +17/-8, all in mod tests. Gated: money-path row green 641/0/2 by structural max-N+presence rule on the live head; seller-orch red-proved the panic byte-for-byte (requested:30/available:10/bound:Wallet) by pointing the re-award at a never-awarded id. Landed by seller-orch per buyer-orch(#230 owner)+lead ruling, buyer-orch reviews post-hoc.
@orveth orveth added this to the v1 milestone Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant