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 #291Payments

[Fellowship Challenge] payments: leased escrow, a stranded hold refunds itself when its lease ends

Submission for the Hexaware x Project NANDA Fellowship Technical Challenge

Author

Status
In review
Opened on
Sep 30
Branch
challenge/ojuschugh1-leased-escrow

Description

The pitch.

Submission for the Hexaware x Project NANDA Fellowship Technical Challenge

Track: Lab. Layer: payments. One new plugin, `leased.v1`, two bundled scenarios and one validator. `ledger.v1`, the reference agents and the schemas are unchanged.

**Gap.** `ledger.v1.hold()` moves the buyer's cents into escrow with no expiry. After that the money moves only when an agent calls `release` or `refund`: the reference buyer releases when its delivery arrives, and no reference agent ever calls `escrow_refund`. Drop the seller's `delivery` and the buyer never releases, so the money sits in escrow until the run ends, while `ledger_conserved` still passes because held escrow counts as conserved. It does not even need a network fault: the reference buyer has no handler for `order_rejected`, so an out-of-stock reply strands a hold too. The Track already puts custody of work under a lease; the Lab has nothing like that for money. None of the merged payments plugins (#21, #38, #41, #90) expires a hold, and #252 left timelocks as a separate design decision.

**Layer.** `leased.v1` is a subclass of `ledger.v1` that sits next to it in `layers/payments.py`, the way `plain.v1` sits next to `hmac.v1`. `hold()` records `escrow_leased {from, cents, expires_at}` and schedules an expiry on the logical clock. A hold still held at `expires_at` emits `escrow_expired` and then the normal `escrow_refunded`, so the standing ledger check is unchanged. A release after that, including one landing at exactly `expires_at`, is refused and recorded as `escrow_release_rejected` instead of paying out. `LEASE = 5.0` logical seconds; the slowest honest hold-to-release in the bundled scenarios is 2.9 (supply_chain). A scenario that needs another lease registers a subclass in its own plugin file, the route a `nandatown new plugin` scaffold takes, and a test runs the scenario that way through `--plugin` and `--layer`. `leased.v1` itself was not scaffolded with `nandatown new`; it lives in the module of the ledger it extends.

**Fault.** `lost_delivery`: two buyers, one seller, `drop delivery nth 1`. Both buyers haggle to 1795 and hold 3590 for two widgets. buyer-1's delivery is dropped; buyer-2's arrives and buyer-2 releases. At t=6.7 the town expires buyer-1's hold and refunds it. buyer-2's lease ends at the same moment and nothing happens, because that hold was already released.

**Invariant.** Money held for a trade that never completed goes back to its payer when the lease recorded on the hold ends, and the trade that did complete pays its seller once. The stages read only `events.jsonl`, and the money records, the drop and the delivery they rely on must be recorded by the town, not by an agent:

| stage | passes when |
|---|---|
| delivery_dropped | the fault fired; with no fault the stage is inconclusive |
| hold_leased | the run holds escrow, and every hold has one lease recorded at the same time, for the same payer and cents, with a finite `expires_at` after the hold |
| refund_on_schedule | every expiry matches its lease, lands exactly at `expires_at`, was never released, and is followed by one refund of the leased cents to the payer |
| no_hold_outlives_run | every hold was released or refunded before `run_finished` |
| payer_made_whole | the hold behind the dropped delivery was refunded in full to its payer and never released |
| completed_trade_untouched | the hold behind the delivered order paid that seller once, with no expiry, refund or refused release |

**Negative control.** `lost_delivery_no_lease` is the same YAML on `ledger.v1`, like `capability_spoofing_weak_auth`. Predicted before running:

| stage | leased.v1 | ledger.v1 |
|---|---|---|
| hold_leased | passed | failed |
| refund_on_schedule | passed | not_enough_evidence |
| no_hold_outlives_run | passed | failed |
| payer_made_whole | passed | failed |
| ledger_conserved | passed | passed |
| verdict | passed | failed |

delivery_dropped, completed_trade_untouched and privacy pass in both. On `ledger.v

…

Try it

Open PR on GitHubView diff

Checkout locally

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