> ## Documentation Index
> Fetch the complete documentation index at: https://docs.laportenard.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Leasing

> Single-writer order ownership, heartbeats, expiry, and manager override.

Leases enforce **single-writer ownership** on orders. Only the terminal holding the lease for an order can submit snapshots. This prevents two terminals from concurrently editing the same order and creating divergent state.

## How it works

### Acquiring a lease

1. Terminal opens an order and sends `LEASE_ACQUIRE { posReference, baseVersion }`
2. Hub checks if the order is already leased:
   * **Not leased** — Grant immediately. Returns `LEASE_GRANTED { version, expiresAt }`
   * **Leased by same terminal** — Grant (re-entrant). Returns current version
   * **Leased by another terminal** — Deny. Returns `LEASE_DENIED { holder, reason }`
3. Terminal must hold a lease before sending `ORDER_SNAPSHOT`

### Maintaining a lease

* Leases expire after **60 seconds** (`LEASE_TTL_MS`) without renewal
* Terminals send `LEASE_HEARTBEAT` every **20 seconds** (`HEARTBEAT_MS`)
* Each heartbeat resets the expiry to `now + 60s`
* If a terminal crashes or loses connection, the lease auto-expires

### Releasing a lease

* Terminal sends `LEASE_RELEASE { posReference, finalSnapshot? }`
* If `finalSnapshot` is provided, the hub saves it before releasing
* This happens when closing an order, navigating away, or paying

### Lease expiry (tick)

The hub runs a periodic tick every 20 seconds:

1. Query all leases where `expires_at <= now`
2. For each expired lease:
   * Load the last known order snapshot
   * Send `LEASE_REVOKED { posReference, reason: "Lease expired", snapshot }` to the terminal
   * Delete the lease from storage
   * The order is now available for other terminals

### Disconnect grace period

When a terminal's WebSocket disconnects (network drop, browser close):

1. Hub does **not** immediately revoke leases
2. Instead, sets lease expiry to `now + 10s` (grace period)
3. If the terminal reconnects within 10 seconds, it can resume editing
4. If grace period expires, the normal tick process revokes the lease

```
  Terminal                              Hub
     │                                   │
     ├──── WS Disconnect ───────────────►│  (unexpected close)
     │                                   │
     │                                   │  lease.expires_at = now + 10s
     │                                   │
     │      (reconnects within 10s)      │
     ├──── WS Connect ──────────────────►│
     ├──── AUTH { token } ──────────────►│
     │◄─── AUTH_OK ──────────────────────┤
     │                                   │  lease still valid → resume editing
     │                                   │
  ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ OR ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
     │                                   │
     │      (10s passes, no reconnect)   │
     │                                   │  tick(): lease expired
     │                                   ├──► LEASE_REVOKED sent to terminal
     │                                   │    lease deleted from storage
```

## Manager override

Managers can forcibly take over an order held by another terminal:

<Steps>
  <Step title="Request">
    Manager terminal sends `LEASE_ACQUIRE { posReference, force: true }`
  </Step>

  <Step title="Validate">
    Hub checks that `force` is set and the requesting user has appropriate role
  </Step>

  <Step title="Revoke">
    Hub sends `LEASE_REVOKED { reason: "Manager override", snapshot }` to the current holder
  </Step>

  <Step title="Grant">
    Hub grants the lease to the manager. Manager receives `LEASE_GRANTED` with the latest version
  </Step>
</Steps>

Non-managers who attempt to access a held order see a `LEASE_DENIED` response. Managers see a confirmation dialog before sending the force-acquire.

## Lease storage

Leases are stored in SQLite (`nu_pos_hub/src/storage/leaseStore.ts`):

```sql theme={null}
CREATE TABLE leases (
  pos_reference TEXT PRIMARY KEY,
  terminal_id   TEXT NOT NULL,
  uid           INTEGER,
  role          TEXT,
  version       INTEGER NOT NULL DEFAULT 0,
  granted_at    TEXT NOT NULL DEFAULT (datetime('now')),
  expires_at    TEXT NOT NULL
);
```

Key operations: `grant()`, `renew()`, `release()`, `getExpired(now)`, `getByTerminal(id)`.

## Frontend lease handling

In hub mode, `hubTransport.ts` manages lease state:

* `acquireLease()` sends `LEASE_ACQUIRE` and returns a `Promise<LeaseResult>` that resolves on `LEASE_GRANTED` or `LEASE_DENIED` (or times out after 10 seconds)
* `releaseLease()` sends `LEASE_RELEASE` and removes from internal `heldLeases` set
* `isLeaseHeld()` checks the internal set
* `onLeaseRevoked()` fires when hub sends `LEASE_REVOKED`

In direct mode, `directTransport.ts` uses local tab-level locks (`orderLocks.ts`) with a 45-second TTL. No network coordination exists.

## Timing summary

| Parameter                  | Value | Configurable via            |
| -------------------------- | ----- | --------------------------- |
| Lease TTL                  | 60s   | `HUB_LEASE_TTL_MS` env var  |
| Heartbeat interval         | 20s   | `HUB_HEARTBEAT_MS` env var  |
| Disconnect grace           | 10s   | `HUB_GRACE_MS` env var      |
| Acquire timeout (frontend) | 10s   | `LEASE_TIMEOUT_MS` constant |
| Tick interval (hub)        | 20s   | Same as heartbeat interval  |
