# Auction State Transition Matrix

Approved decisions (2026-07-30). Maps prompt concepts onto repository enums.

## Status axes

| Axis | Field | Values |
|---|---|---|
| Moderation | `approvalStatus` | `wait`, `changes_requested`, `accept`, `reject`, `cancelled` |
| Lifecycle | `status` | `upcoming`, `current`, `finished`, `cancelled` |
| Settlement | `settlementStatus` | `none`, `no_bids`, `reserve_not_met`, `awaiting_winner_payment`, `paid`, `sold`, `winner_defaulted`, `refund_pending`, `settlement_failed`, `cancelled` |

Legacy dual-read: `seller` / free-text product fields remain readable; **canonical write path** is `provider` + `product` + `productSnapshot`.

## Transition table

| Actor | Action | Moderation | Lifecycle | Settlement | Result | Preconditions | Side effects |
|---|---|---|---|---|---|---|---|
| Provider | submit create | — | — | — | `wait` / `upcoming` / `none` | Owned approved Product; no conflicting active auction | Snapshot; admin notify; audit |
| Provider | edit (pending) | `wait` | `upcoming` | `none` | stays `wait` | Ownership; allowlisted fields | Effective-change only; no duplicate notify |
| Provider | edit (changes_requested) | `changes_requested` | `upcoming` | `none` | → `wait` on material change | Ownership | Admin notify; clear stale review meta |
| Provider | edit material (approved) | `accept` | `upcoming` | `none` | → `wait`; clear approval meta | Not started | Cancel start cron until re-approve; notify admin |
| Provider | cancel | `wait`/`changes_requested`/`accept` | `upcoming` | `none` | lifecycle `cancelled`; settlement `cancelled` | No bids/payments or policy allows | Soft preserve; notify |
| Admin | approve | `wait` | `upcoming` | `none` | → `accept` / `upcoming` | Product still eligible; schedule valid | Snapshot extension/policy; schedule start/end crons; notify provider |
| Admin | reject | `wait`/`changes_requested` | `upcoming` | `none` | → `reject` | Reason required | Notify provider; audit |
| Admin | request changes | `wait` | `upcoming` | `none` | → `changes_requested` | Reason required | Notify provider; audit |
| Admin | configure platform fields | `accept` | `upcoming` | `none` | same | Permission | Snapshot into auction; audit |
| Cron/lazy | start | `accept` | `upcoming` | `none` | → `current`; `activatedAt` | `startAt <= now` | Realtime `auction.started`; notify |
| Customer | deposit | any public | `upcoming`/`current` | — | subscription `PAID` | Not provider; idempotent | Wallet debit; eligibility |
| Customer | bid | `accept` | `current` | `none` | price/highest updated | Deposit PAID; amount ≥ minNext; CAS | Bid row; optional extension; sockets after commit |
| System | anti-snipe | — | `current` | — | `endAt` += duration; `extensionCount++` | In window; under max; same bid CAS | Reschedule end cron; emit extended |
| Cron/lazy | finalize no bids | `accept` | `current` | `none` | `finished` / `no_bids` | `endAt < now`; CAS once | Refund deposits; notify |
| Cron/lazy | finalize reserve not met | `accept` | `current` | `none` | `finished` / `reserve_not_met` | Highest < reserve | Refund all deposits; no sale |
| Cron/lazy | finalize reserve met/off | `accept` | `current` | `none` | `finished` / `awaiting_winner_payment` | Highest wins | Deadline; refund losers; notify winner |
| Customer | winner pay | — | `finished` | `awaiting_winner_payment` | → `paid` then `sold` | Winner only; amount server-side | One Order; FT/Profit; notify |
| Cron | payment deadline | — | `finished` | `awaiting_winner_payment` | → `winner_defaulted` | Unpaid past deadline | **Hold** winner deposit (no auto forfeit/refund); admin review; **no** 2nd winner |
| Admin | resolve held deposit | — | `finished` | `winner_defaulted` | refund or forfeit audited | Permission; idempotent | Wallet credit or forfeit ledger |
| Admin | cancel live/scheduled | — | `upcoming`/`current` | — | `cancelled` / `cancelled` | Permission; reason | Stop bids; refund orchestration; notify |
| Job | loser refunds | — | `finished` | various | subscription `REFUNDED` | Idempotent | Wallet credit; retryable |

Invalid transitions are rejected at the service layer.
