2026-09-18: a peer purchase paid 10 sats, the seller redeemed them, and the
buyer got no file plus a "seller doesn't accept your Cashu mint" error.
Three defects lined up:
1. The seller checked file existence with stat() but only read the file
AFTER redeeming the payment. Filebrowser-owned 0640 files (uid 100999)
passed stat but failed fs::read for the archipelago service user.
serve_content now checks existence and readability BEFORE the payment
gate, so an unservable file costs the buyer nothing.
2. The HTTP handler mapped every serve_content error to a bare, unlogged
404. A server-side failure is now a logged 500. (A 404 also makes the
buyer's Auto transport re-send the request over Tor.)
3. That re-send carried the same single-use token, which the mint had
already spent, so the seller answered 402. Redemption is now
idempotent: a token that verified for an item keeps authorising that
item for 10 minutes (per token, per item; SHA-256 keyed, in-memory,
concurrent requests serialised, failures never cached).
Buyer side: reclaim_spent_ecash now reports whether the refund worked, and
the error text no longer claims "refunded" when it wasn't, or asserts the
seller rejects the mint when the cause is unknown.
Adds tests for replay, concurrency, failure-not-cached, cross-item, and
unreadable-file-before-payment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>