# Auction Socket.IO Contract

Branch: `auction-lifecycle-delivery`
Scope: canonical Auction realtime (bidding + room sync).
Agora / Haraj live streaming is an **optional overlay** and is **not** required for core Auction readiness.

## Authentication

Handshake query (existing project pattern):

- `userId` (Mongo ObjectId)
- `userType` (`client` | `provider` / store | …)
- `lang` (`ar` | `en`)
- `deviceType`, `deviceId`

Mutation handlers **always** use `socket.userId` / `socket.userType`.
Client-supplied `userId`, `clientId`, `seller`, `provider`, `bidderId` in event payloads are **ignored**.

## Server-authoritative time

Clients must not trust local timers as source of truth.
After `auction:enter` or reconnect, use the snapshot fields:

- `serverNow`
- `endAt`
- `remainingSeconds`
- `currentPrice`
- `minimumNextBid`
- `status` / `settlementStatus`
- `extensionCount`
- `totalBids`

## Events

### `auction:enter` (command)

**Inbound:** `{ auctionId }`
**Auth:** handshake user
**Service:** Auction lookup + room join
**Success emit (to joining socket):** `auction:entered` (authoritative snapshot)
**Ack (optional callback):** `{ key: 'success', data: { snapshot } }`
**Errors:** `auction:error` + ack `{ key: 'fail', code, message }`

### `auction:bid` (command) — canonical

**Inbound (preferred):**
```json
{
  "auctionId": "665f1c2a9b4e1d0012ab34ff",
  "amount": 1500,
  "idempotencyKey": "client-bid:550e8400-e29b-41d4-a716-446655440000"
}
```

- `amount` is the absolute bid value.
- `price` is accepted as an absolute alias for compatibility with older Socket clients.
- Increment-style socket bidding is **no longer accepted**.
- `idempotencyKey` is **required** for Socket (max 128; `[A-Za-z0-9_:-]` only).
  There is **no** server-generated fallback and `socket.id` is never used as the key.
  Socket rejects omission.

**Domain service:** `placeBidAtomic`.
**Persistence before emit:** yes.
**Room broadcast (new Bid only):**

| Event | Role |
|---|---|
| `auction:bid-updated` | **Canonical public** bid update |
| `auction.bidAccepted` | Legacy alias |
| `auction.priceUpdated` | Legacy alias |
| `auction.extended` | Only when anti-sniping extended `endAt` |

**Idempotent replay:** stable ack with `data.replayed: true` — **no** second Bid, **no** price/extension mutation, **no** accepted-bid broadcast.

**Ack success:**
```json
{
  "key": "success",
  "message": "...",
  "data": {
    "bid": { "id": "...", "price": 1500, "createdAt": "..." },
    "auction": {
      "id": "...",
      "currentPrice": 1500,
      "totalBids": 3,
      "endAt": "...",
      "extensionCount": 1,
      "minimumNextBid": 1510
    },
    "replayed": false,
    "extended": true
  }
}
```

**Ack / `auction:error` failure (aligned):**
```json
{
  "key": "fail",
  "code": "STALE_BID",
  "message": "...",
  "minimumNextBid": 1510,
  "currentPrice": 1500,
  "endAt": "...",
  "serverNow": "..."
}
```

Error codes include: `VALIDATION_ERROR`, `UNAUTHORIZED_ROLE`, `OWNER_CANNOT_BID`, `DEPOSIT_REQUIRED`, `BID_TOO_LOW`, `STALE_BID`, `NOT_STARTED`, `ENDED`, `NOT_LIVE`, `IDEMPOTENCY_KEY_REQUIRED`, `IDEMPOTENCY_KEY_INVALID`, `IDEMPOTENCY_CONFLICT`, `INTERNAL`.

Payloads never include `reservePrice`, wallet balances, or raw Mongo documents.

### `auction:ended` (read-only observe)

**Not a finalization command.**
Clients cannot declare an Auction ended.

**Behavior:**

1. Load Auction from DB.
2. If `status` is not `finished` / `cancelled` → fail `NOT_FINALIZED` (no mutation).
3. If already finalized → emit `auction:finished` + `auction.ended` with safe summary and leave the room.

Canonical finalization remains: **cron / worker → `finalizeAuction`**.

### Live overlay (optional — not core)

`start-live`, `enter-live`, `add-comment`, `exit-live`, `live:*`, viewer counts, Agora token helpers.

- Classified as **optional Haraj/Agora overlay**.
- Agora token generation is currently **disabled/partial** (commented paths).
- Overlay failures must not change Auction settlement or bidding.
- **Not a merge blocker** for the core Auction module.

### Disconnect

Cleans live viewer tracking / may set live broadcast `NOT_LIVE` for a publisher socket.
Does **not** finalize Auctions, refund deposits, or create Orders.

## Reconnect

1. Re-handshake.
2. `auction:enter` again.
3. Prefer HTTP `GET` auction details / bids if the client needs a full refetch.
4. Replace local timer with `serverNow` + `endAt` from snapshot.

## Legacy notes

- `seller` dual-read remains for older Auction documents and live overlay host checks.
- Canonical ownership for bidding/settlement is **provider + product**.
- Legacy live paths must not create independent Bids or finalize Auctions.
