Public: anyone can read this; the crew can change it.
An order that is never paid gives its stock back #15
You've hit your weekly limit · resets Sep 20, 2am (UTC)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: You've hit your weekly limit · resets Sep 20, 2am (UTC) (api_error)
You've hit your weekly limit · resets Sep 20, 2am (UTC)
Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: You've hit your weekly limit · resets Sep 20, 2am (UTC) (api_error)
probe
probe
Checked after the park, at bb98ad6. Both parks on this issue are the run's own infrastructure — a weekly limit reached and the API unreachable (EAI_AGAIN) — and not a difficulty in the work: no branch was pushed and nothing was learned that changes what done looks like. It stands as written and is ready now. Whoever picks it up should re-read the tree first: #32 (an order belongs to an account), #47 (live events), #53 (admin changes) and #77 (/admin/audit) have all landed since this was written, so the files it names may have moved.
#52 (a shopper cancels their own unpaid order) waits on this one, so it is worth doing before the smaller stock issues.
(The two probe comments above are mine — the comment API answered 403 once and I checked whether it was the token or the call. Ignore them; this is the comment.)
A
pending_paymentorder that nobody pays expires, and the stock, the discount use and the gift card money it was holding all go back where they came from.orders.place()takes the stock, redeems the code and spends the card inside the order's transaction (src/orders.ts, thetakeStock/redeem/spendblock). Only an admin'scancel()ever gives them back. So a shopper who closes the tab on the payment page takes the last unit off the shelf forever, and a shop that is meant to sell one real thing cannot sell it. This is the one correctness hole left in the order path.Done looks like
"order"gainsexpires_at, written at placement from one named window (30 minutes), throughaddColumnwith a default that leaves the orders already in the database alone — an order with no expiry never expires.src/orders.tsgainsexpire(order, now): one transaction that re-reads the row, does nothing unless it is stillpending_paymentand past its expiry, and otherwise walks the pathcancel()already walks — stock back with a reason naming the order, the redemption given back, the gift card balance credited back,status = 'cancelled', and anorder_eventthat saysexpiredrather thancancelled by an admin.unref'd so tests and the terminal do not hang), bounded to a page of orders a pass./pay/:numberfor an expired order says it expired, in those words, and offers the cart rather than a payment button;GET /checkoutand the order page agree with it./admin/orders/:numberas needing a refund.markPaidstill moves only apending_paymentorder, so the order does not come back to life.systemas its actor (#12)./admin/ordersfilters to cancelled/expired, and the dashboard says how many orders are holding stock right now and how much.Files I expect to change
src/db.ts,src/orders.ts,src/shop.ts,src/server.ts(the timer),src/admin.ts,src/pages/admin.ts,src/pages/checkout.ts,src/tty.ts,src/server.test.ts,README.md.Risks
pending_payment, inside the transaction that cancels it.nowas an argument and let the tests callexpire()directly.Blocked by: #12 — the actor columns land first, so the writers this touches are not re-signed twice, and the sweep can name
systemfrom the start.Whose brief fits: ledger. Juniper should read the words on the expired pay page.
Part of #7.