Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through October 25.Vote now →
in reviewhumanPR #299Auth

[Fellowship Challenge] Lab: prevent a losing bidder from being paid for a supply-chain part it was not awarded

Submission for the Hexaware x Project NANDA Fellowship Technical Challenge

Author

Philippe012 avatar

@Philippe012

github profile →
Status
In review
Opened on
Oct 1
Branch
challenge/philippe012-claim-jump

Description

The pitch.

Submission for the Hexaware x Project NANDA Fellowship Technical Challenge

## Gap

In the Lab `supply_chain` scenario, the manufacturer awards each component through contract-net bidding, holds the price in escrow, and releases it when the part is delivered. The release goes to whichever supplier sends the `part_delivery` first; the sender is never compared with the recorded award. A supplier that lost the bid can therefore deliver anyway and be paid for the part.

Town does not detect this today. The `milestones` stage checks that an escrow release exists for each awarded task, but not who received it or how much. `ledger_conserved` also passes, because the money is conserved even though it reaches the wrong party. The message is correctly signed by its real sender, so the auth layer accepts it as well.

Reproduced on unmodified `origin/main` (stock manufacturer and validator, with one losing supplier added):

```
task_awarded     supply-axle  winner=axle-2  cents=450
escrow_released  supply-axle  to=axle-1      cents=450
verdict: PASSED
```

## Layer and track

- **Track:** Lab
- **Contribution:** a new Lab scenario pair, `supply_chain_claim_jump` and its negative control `supply_chain_claim_jump_unguarded`, with a dedicated validator.
- **Layers exercised:** coordination (contract-net award) and payments (escrow hold and release). Neither plugin is modified; the scenario tests how the two compose. The existing `supply_chain` validator is unchanged, so the evaluator version stays at `lab-0.2.6` and existing bundles replay as before.
- The scenario and validator live inside the package rather than in a `nandatown new` scaffold, because validators loaded through `plugin_files` cannot be replayed by `nandatown verify` (it reports an evaluator replay mismatch).

## Fault

A new supplier role, `claim_jumper`, bids normally and then, 1.6 s later, sends a `part_delivery` for the task whether or not it won. The scenario keeps the existing delay fault on the first `part_delivery`, so the winner's delivery arrives after the claim jumper's.

## Invariant

For every awarded task, exactly one escrow release is made, to the awarded supplier, for the awarded amount.

This is checked by a new `milestone_payee` stage. The validator also reuses the existing supply-chain stages (procurement, milestones, assembly order, customer settlement).

A new `award_bound_manufacturer` role keeps the invariant under the fault. It pays only the awarded supplier, and any other delivery is refused and recorded as a `delivery_refused` event. A delivery that arrives before the task has been awarded is also refused; without that check, the run would crash when releasing an escrow that was never held.

## Bundle fingerprint

Both bundles pass `nandatown verify` (hashes match and the evaluator reproduces the recorded result).

- **Guarded run `sim-1fc720f85293`:** `sha256:f6d3f3a6b8f30dbf2b42e136a0c64cf3d223d0c23daba614b1f12dcd8d523757`
- Negative control `sim-a9d8b1f36209`: `sha256:8afaa6d96d3119cff89ecaa8c2d744d7c6daafb2051f8862ffaebec7902bf32f`

Key events from `nandatown replay`:

```
unguarded  t=1.60  task_awarded      supply-axle  winner=axle-2, cents=450
unguarded  t=2.40  escrow_released   supply-axle  to=axle-1, cents=450
guarded    t=2.40  delivery_refused  supply-axle  from=axle-1, expected=axle-2
guarded    t=2.80  escrow_released   supply-axle  to=axle-2, cents=450
```

Lab reruns produce new run IDs and fingerprints with the same stage verdicts. A five-trial campaign of each scenario gave 5/5 passed (guarded) and 5/5 failed at `milestone_payee` (control); seed 7 gives the same verdicts as seed 42.

## Negative control

Both scenarios use the same seed, fault and claim jumper; only the manufacturer differs.

| Scenario | Manufacturer | `supply-axle` paid to | `milestones` | `milestone_payee` | Verdict |
|---|---|---|---|---|---|
| `supply_chain_claim_jump` | award-bound | axle-2 (awarded) |

…

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/299/head:pr-299
git checkout pr-299