# Devices Handling

# JikoXpress Pro Device & Staff Authentication Specificationge

```
Device-first. PIN-based staff auth. Server is always the authority.
One device, one kitchen. Trust earned through physical registration.

```

---

## Philosophy

- **Device identity first** — no device, no access. Period.
- **Server is always authority** — device only reacts, never decides
- **Minimal local storage** — only what is necessary lives on device
- **Progressive trust** — setup token → device token → staff token
- **Config as source of truth** — everything including kitchenId lives in one payload
- **Revocation is the kill switch** — no rotation complexity, owner revokes when needed
- **Device type drives auth requirements** — KIOSK needs less, POS needs more

---

## Actor Overview

<table id="bkmrk-actor-who-they-are-h"><thead><tr><th>Actor</th><th>Who They Are</th><th>How They Auth</th></tr></thead><tbody><tr><td>Platform Super Admin</td><td>QBIT SPARK team</td><td>Separate system — out of scope here</td></tr><tr><td>Kitchen Owner / Admin</td><td>Restaurant owner</td><td>Email + Password → Owner JWT</td></tr><tr><td>Device</td><td>Physical tablet / POS / kiosk</td><td>Setup flow → DEVICE\_TOKEN</td></tr><tr><td>Staff</td><td>Kitchen employee</td><td>PIN on registered device → STAFF\_TOKEN</td></tr><tr><td>Customer (Kiosk)</td><td>Walk-in customer</td><td>No auth — device auth is enough</td></tr></tbody></table>

---

## Token Types

<table id="bkmrk-token-lifespan-purpo"><thead><tr><th>Token</th><th>Lifespan</th><th>Purpose</th><th>Issued At</th></tr></thead><tbody><tr><td>`setupToken`</td><td>5 mins, single use</td><td>Binds physical device to claim attempt</td><td>`GET /devices/setup/token`</td></tr><tr><td>`DEVICE_TOKEN`</td><td>Permanent until revoked</td><td>Device identity on every request</td><td>After owner configures device</td></tr><tr><td>`STAFF_TOKEN`</td><td>8 hrs (one shift)</td><td>Staff session, device-bound</td><td>`POST /auth/staff/login`</td></tr><tr><td>`OWNER_TOKEN`</td><td>Standard session</td><td>Owner management access</td><td>`POST /auth/owner/login`</td></tr></tbody></table>

---

## Device Types &amp; Auth Requirements

<table id="bkmrk-device-type-device_t"><thead><tr><th>Device Type</th><th>DEVICE\_TOKEN</th><th>STAFF\_TOKEN</th><th>Notes</th></tr></thead><tbody><tr><td>`POS`</td><td>✅ Required</td><td>✅ Required</td><td>Full staff auth</td></tr><tr><td>`STORE_TABLET`</td><td>✅ Required</td><td>✅ Required</td><td>Full staff auth</td></tr><tr><td>`KIOSK`</td><td>✅ Required</td><td>❌ Not needed</td><td>Customer self-service</td></tr><tr><td>`KITCHEN_DISPLAY`</td><td>✅ Required</td><td>❌ Not needed</td><td>Passive display only</td></tr></tbody></table>

<table id="bkmrk-device-type-device_t-1"><thead><tr><th>Device Type</th><th>DEVICE\_TOKEN</th><th>STAFF\_TOKEN</th><th>Notes</th></tr></thead><tbody><tr><td>`POS`</td><td>✅ Required</td><td>✅ Required</td><td>Full staff auth</td></tr><tr><td>`STORE_TABLET`</td><td>✅ Required</td><td>✅ Required</td><td>Full staff auth</td></tr><tr><td>`KITCHEN_DISPLAY`</td><td>✅ Required</td><td>✅ Required</td><td>Station-bound staff auth</td></tr><tr><td>`KIOSK`</td><td>✅ Required</td><td>❌ Not needed</td><td>Customer self-service only</td></tr></tbody></table>

KIOSK is the only device type with no staff auth. Kitchen can have multiple KDS devices — one per station.

---

## Config Payload — Single Source of Truth

Everything the device needs to know lives in one JSON payload. `kitchenId` is not stored separately — it is a field inside this payload.

```json
{
  "deviceId": "dv_uuid",
  "deviceName": "Front Kiosk",
  "deviceType": "KIOSK",
  "kitchenId": "kt_uuid",
  "kitchenName": "Mama Pima Kitchen",
  "deviceStatus": "ACTIVE",
  "permissions": {
    "allowDineIn": true,
    "allowPickup": true,
    "allowDelivery": false,
    "allowPOS": false,
    "allowReports": false,
    "allowKitchenDisplay": true,
    "allowStoreAccess": false
  }
}

```

### Config Hash

Hash is **always derived on the fly** from the full config payload. It is never stored as a static field.

```
hash(configPayload) → compare with hash in response
→ match   → config still fresh, proceed
→ mismatch → pull fresh config, overwrite local payload, re-derive

```

### What Device Stores Locally

```
LOCAL DEVICE STORE
├── deviceToken      → permanent identity
└── configPayload    → full JSON blob above (kitchenId lives here)

```

Nothing else. No separate kitchenId field. No separate hash field.

---

## Device Status Lifecycle

```
UNCONFIGURED   → claimed by owner, awaiting kitchen assignment
ACTIVE         → fully configured, operating normally
SUSPENDED      → kitchen subscription lapsed / unpaid
REVOKED        → decommissioned or compromised

```

### Device Reaction Per Status

<table id="bkmrk-status-device-behavi"><thead><tr><th>Status</th><th>Device Behaviour</th></tr></thead><tbody><tr><td>`ACTIVE`</td><td>Check configHash → proceed normally</td></tr><tr><td>`SUSPENDED`</td><td>Lock screen → show "Kitchen subscription inactive. Contact admin."</td></tr><tr><td>`REVOKED`</td><td>Wipe all local storage → reset to setup/QR screen</td></tr></tbody></table>

Device does **not** need to know *why* it is suspended or revoked. It just reacts. The reason lives server-side.

---

## Every API Response Envelope

Every response to a device — from boot config pull to mid-session requests — carries:

```json
{
  "deviceStatus": "ACTIVE",
  "configHash": "a3f9c2d1...",
  "data": { }
}

```

Device reads `deviceStatus` and `configHash` on **every** response before processing `data`.

---

## Device Fingerprint

Generated on the device from hardware/software attributes:

```
fingerprint = hash(
  deviceModel +
  osVersion +
  screenResolution +
  installationId     ← generated once on first app install, stored permanently
)

```

`installationId` is the anchor — generated once, never changes unless app is reinstalled.

### When Fingerprint Is Used

```
GET /devices/setup/token     → sent in header (binding moment)
Polling /setup/status        → sent while waiting for CLAIMED only
                               polling stops the moment CLAIMED detected

Everything after CLAIMED → fingerprint NOT needed
Everything after DEVICE_TOKEN is issued → fingerprint NOT needed

```

Fingerprint is **never** included in the QR code. It travels only in request headers, never exposed visually.

---

## Flow 1 — Device Setup &amp; Registration

```
  ┌──────────────────────────────────────────────────────┐
  │  Device boots. No DEVICE_TOKEN in local storage.     │
  └─────────────────────┬────────────────────────────────┘
                        │
                        ▼
            Show "Authenticate Device" screen
            [Single button: Set Up Device]
                        │
                        ▼ (user taps button)
            QR page opens
            Device sends fingerprint to server
                        │
                        ▼
  ┌──────────────────────────────────────────────────────┐
  │  GET /devices/setup/token                            │
  │  Header: X-Device-Fingerprint: fp_xxxx               │
  │                                                      │
  │  Server:                                             │
  │  → generates cryptographically random setupToken     │
  │  → stores { setupToken, fingerprint, TTL: 5mins }    │
  │  → returns setupToken                                │
  └─────────────────────┬────────────────────────────────┘
                        │
                        ▼
            Device renders QR containing:
            { "setupToken": "abc123xyz..." }
            (fingerprint NOT in QR)
                        │
                        ▼
            Device polls every 5 seconds for CLAIMED only:
  ┌──────────────────────────────────────────────────────┐
  │  GET /devices/setup/status                           │
  │  Header: X-Device-Fingerprint: fp_xxxx               │
  │  Header: X-Setup-Token: abc123xyz                    │
  │                                                      │
  │  Possible responses:                                 │
  │  { "status": "PENDING" }  → keep showing QR          │
  │  { "status": "CLAIMED" }  → admin scanned → STOP     │
  │  { "status": "EXPIRED" }  → refresh QR               │
  └─────────────────────┬────────────────────────────────┘
                        │
              ┌─────────┴─────────┐
              │                   │
           EXPIRED             CLAIMED
              │                   │
              ▼                   ▼
     Fetch new setupToken   STOP polling completely
     Re-render QR           Show "Complete Setup" screen
                            (admin is now configuring
                            on their phone)
                                   │
                                   ▼ (user taps button)
                            GET /devices/setup/complete
                            Header: X-Device-Fingerprint
                            Header: X-Setup-Token
                                   │
                              ┌────┴────┐
                              │         │
                         CONFIGURED  NOT YET
                              │         │
                              ▼         ▼
                       Server returns  Show:
                       DEVICE_TOKEN    "Admin hasn't finished.
                       + configPayload  Try again in a moment."
                              │         [Try Again]
                              ▼
                       Device stores:
                       - DEVICE_TOKEN
                       - configPayload
                       Derives configHash
                       setupToken destroyed server-side
                              │
                              ▼
                       Navigate to PIN screen ✓

```

### Incomplete Registration — Admin Abandons Mid-Flow

Three scenarios handled gracefully:

```
Scenario A — Admin never scanned (setupToken expired)
→ Device still on QR screen
→ Poll detects EXPIRED
→ Device auto-fetches new setupToken, re-renders QR
→ Admin can scan fresh anytime ✓

Scenario B — Admin scanned but never finished configuring, setupToken expired
→ Device on "Complete Setup" screen
→ User taps button next day
→ Server: setupToken expired → error returned
→ Device shows: "Setup session expired. Ask your admin to scan again."
             [Generate New QR]
→ Device returns to QR screen with fresh setupToken
→ Admin scans again, configures, done ✓

Scenario C — Admin fully configured, device never tapped "Complete Setup"
→ Device on "Complete Setup" screen
→ User taps button the next day
→ Server: device is CONFIGURED → returns DEVICE_TOKEN + configPayload
→ Works perfectly ✓
→ CONFIGURED state has no expiry — safe to complete anytime

```

---

## Flow 2 — Owner Claims &amp; Configures Device (Admin App)

```
  Owner opens admin app on phone
  Navigates to Devices → Add Device
                        │
                        ▼
            Owner scans QR on device screen
            Admin app reads: { setupToken }
                        │
                        ▼
  ┌──────────────────────────────────────────────────────┐
  │  POST /devices/claim                                 │
  │  Header: Authorization: Bearer <OWNER_TOKEN>         │
  │  Body: { "setupToken": "abc123xyz" }                 │
  │                                                      │
  │  Server validates:                                   │
  │  → setupToken exists and not expired?                │
  │  → setupToken not already used?                      │
  │  → fingerprint on file matches device polling?       │
  │  → requester is a valid kitchen owner?               │
  │  → all yes → device status: UNCONFIGURED             │
  └─────────────────────┬────────────────────────────────┘
                        │
                        ▼
            Admin app shows device configuration screen:

  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        Device Claimed ✓
        Now configure it

        KIOSK · Mama Pima Kitchen    ← read-only, from device + session

        Device Name
        ┌─────────────────────────┐
        │  Front Kiosk            │
        └─────────────────────────┘

        Permissions
        ─────────────────────────
        Dine In          [ ON  ]
        Pickup           [ ON  ]
        Delivery         [ OFF ]
        POS Access       [ OFF ]
        Reports          [ OFF ]
        Kitchen Display  [ ON  ]
        Store Access     [ OFF ]

        ┌─────────────────────┐
        │    Save & Activate  │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
                        │
                        ▼
  ┌──────────────────────────────────────────────────────┐
  │  PUT /devices/{deviceId}/configure                   │
  │  Header: Authorization: Bearer <OWNER_TOKEN>         │
  │  Body: {                                             │
  │    "name": "Front Kiosk",                            │
  │    "permissions": {                                  │
  │      "allowDineIn": true,                            │
  │      "allowPickup": true,                            │
  │      "allowDelivery": false,                         │
  │      "allowPOS": false,                              │
  │      "allowReports": false,                          │
  │      "allowKitchenDisplay": true                     │
  │    }                                                 │
  │  }                                                   │
  │                                                      │
  │  Server:                                             │
  │  → kitchenId taken from owner session                │
  │  → deviceType taken from setupToken record           │
  │  → generates DEVICE_TOKEN                            │
  │  → builds configPayload                              │
  │  → status → ACTIVE                                   │
  │  → signals CONFIGURED on next poll                   │
  └──────────────────────────────────────────────────────┘
                        │
                        ▼
            Device polling detects CONFIGURED
            Fetches DEVICE_TOKEN + configPayload
            Stores locally, navigates to PIN screen ✓

```

---

## Flow 3 — Device Boot (Already Registered)

```
  Device boots
      │
      ▼
  Check local storage
      │
  ┌───┴───┐
  │       │
NO TOKEN  TOKEN FOUND
  │       │
  ▼       ▼
Setup  GET /devices/{deviceId}/config
Flow   Header: X-Device-Token: <DEVICE_TOKEN>
           │
           ▼
       Response:
       {
         "deviceStatus": "ACTIVE",
         "configHash": "a3f9c2...",
         "config": { ...full configPayload... }
       }
           │
           ▼
       Device overwrites local configPayload
       Derives hash from fresh payload
           │
    ┌──────┴──────┐
    │             │
 ACTIVE       SUSPENDED / REVOKED
    │             │
    ▼             ▼
 Show PIN     React accordingly
 screen ✓     (lock / wipe)

```

---

## Flow 4 — Staff Login (POS / STORE\_TABLET)

```
  Device on PIN screen
  Staff enters PIN
      │
      ▼
  POST /auth/staff/login
  Header: X-Device-Token: <DEVICE_TOKEN>
  Body: { "pin": "1234" }
      │
      ▼
  Server:
  → identifies kitchen from DEVICE_TOKEN
  → checks deviceType — is staff auth required?
  → finds staff with matching PIN hash in that kitchen
  → validates PIN
  → issues STAFF_TOKEN (8hrs, device-bound)
  → fetches permissionsHash for this staff member
      │
      ▼
  Response:
  {
    "deviceStatus": "ACTIVE",
    "configHash": "a3f9c2...",
    "staffToken": "eyJ...",
    "permissionsHash": "b7d1e4..."
  }
      │
      ▼
  Device fetches staff permissions once:
  GET /staff/me/permissions
  Header: X-Device-Token + X-Staff-Token
      │
      ▼
  Device caches permissions locally for session
  Effective permissions = staff permissions ∩ device permissions
      │
      ▼
  Staff sees their dashboard ✓

```

### PIN Security

```
Wrong PIN attempt → increment counter on device record
5 wrong attempts  → device locked for 15 mins (server-enforced)
Lockout is device-scoped, not global
Correct PIN → counter resets

```

### Staff Session Rules

```
One active STAFF_TOKEN per device at any time
New PIN login → previous STAFF_TOKEN immediately invalidated
Staff logs out → STAFF_TOKEN invalidated → back to PIN screen
Token expires after 8hrs → back to PIN screen

```

---

## Flow 5 — Mid-Session Config &amp; Permissions Sync

```
  Every API response carries:
  {
    "deviceStatus": "ACTIVE",
    "configHash": "a3f9c2...",
    "permissionsHash": "b7d1e4..."   ← only when staff session active
  }

  Device on every response:

  Step 1 — Check deviceStatus
  → ACTIVE     → continue
  → SUSPENDED  → lock screen immediately
  → REVOKED    → wipe local storage → setup screen

  Step 2 — Check configHash
  → matches local derived hash  → config still fresh
  → mismatch                    → pull fresh config
                                  GET /devices/{deviceId}/config

  Step 3 — Check permissionsHash (if staff session active)
  → matches cached hash  → permissions still fresh
  → mismatch             → pull fresh permissions
                           GET /staff/me/permissions
                           recalculate effective permissions

```

---

## Flow 6 — Manual Refresh (Refresh Button on Device)

```
  Staff or admin taps Refresh button on device
      │
      ▼
  GET /devices/{deviceId}/config
  Header: X-Device-Token: <DEVICE_TOKEN>
      │
      ▼
  Same as boot config pull
  Device reacts to whatever server returns
  (ACTIVE / SUSPENDED / REVOKED)
      │
      ▼
  Config updated if hash changed ✓

```

Useful when owner changes permissions on dashboard and staff need it reflected immediately without waiting for next request.

---

## Flow 7 — Device Revocation (Owner Dashboard)

```
  Owner opens dashboard → Devices → [Device Name] → Revoke

  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        Revoke Device?

        ┌─────────────────────┐
        │ [icon] Front Kiosk  │
        │ KIOSK · Last seen   │
        │ 5 mins ago          │
        └─────────────────────┘

        This device will be immediately
        locked and all local data wiped
        on its next request.

        ┌─────────────────────┐
        │    Revoke Device    │  ← destructive, red
        └─────────────────────┘
        ┌─────────────────────┐
        │       Cancel        │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
      │
      ▼
  PATCH /devices/{deviceId}/revoke
  Header: Authorization: Bearer <OWNER_TOKEN>
      │
      ▼
  Server: device status → REVOKED
  DEVICE_TOKEN invalidated immediately
  All active STAFF_TOKENs on this device killed
      │
      ▼
  Device makes next request (any request or refresh)
  Response carries: { "deviceStatus": "REVOKED" }
      │
      ▼
  Device:
  → deletes DEVICE_TOKEN from local storage
  → deletes configPayload from local storage
  → clears all active staff sessions locally
  → clears any cached order/cart data
  → resets to setup/QR screen ✓

  Device owner can now re-register the device
  fresh through the QR setup flow

```

### Two Revocation Paths

<table id="bkmrk-source-endpoint-conf"><thead><tr><th>Source</th><th>Endpoint</th><th>Confirmation Required</th></tr></thead><tbody><tr><td>Owner dashboard</td><td>`PATCH /devices/{id}/revoke`</td><td>Dashboard UI confirmation</td></tr><tr><td>Device itself</td><td>`POST /devices/self-revoke`</td><td>Are you sure + kitchen name typed</td></tr></tbody></table>

Both lead to the same outcome — token dead, device wiped, setup screen shown.

---

## Flow 7b — Self-Revoke From Device

Device has a revoke option buried in settings — not on the main PIN screen.

```
Staff/admin navigates to Settings → Revoke Device
      │
      ▼
  Step 1 — Are You Sure? dialog

  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        ⚠️  Revoke This Device?

        This will immediately wipe
        all data from this device.
        It must be re-registered
        to be used again.

        ┌─────────────────────┐
        │   Yes, Continue     │
        └─────────────────────┘
        ┌─────────────────────┐
        │       Cancel        │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
      │
      ▼ (taps Yes, Continue)
  Step 2 — Type Kitchen Name to Confirm

  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        Confirm Revocation

        Type your kitchen name
        to confirm:

        ┌─────────────────────────┐
        │  Mama Pima Kitchen      │
        └─────────────────────────┘

        ┌─────────────────────┐
        │   Revoke Device     │  ← red, disabled until name matches
        └─────────────────────┘
        ┌─────────────────────┐
        │       Cancel        │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
      │
      ▼
  Revoke button enabled only when typed name matches
  kitchenName from local configPayload exactly
  (local string comparison — no server call needed for the check)
      │
      ▼ (taps Revoke Device)
  POST /devices/self-revoke
  Header: X-Device-Token: <DEVICE_TOKEN>
  Body: { "kitchenName": "Mama Pima Kitchen" }
      │
      ▼
  Server validates:
  → DEVICE_TOKEN valid?
  → kitchenName matches kitchen on record for this device?
  → yes → marks device REVOKED
         → invalidates DEVICE_TOKEN
         → kills all active STAFF_TOKENs
      │
      ▼
  Device:
  → wipes all local storage
  → resets to setup/QR screen ✓

```

---

## Flow 8 — Suspension (Subscription Lapsed)

```
  Kitchen subscription expires
  Platform marks kitchen as SUSPENDED server-side
  All devices belonging to this kitchen → status SUSPENDED
      │
      ▼
  Device makes next request
  Response: { "deviceStatus": "SUSPENDED" }
      │
      ▼
  Device:
  → does NOT wipe local storage
  → locks screen
  → shows: "Kitchen subscription inactive. Contact your admin."

  When kitchen pays and subscription restored:
  → Platform marks kitchen ACTIVE
  → All devices → ACTIVE
  → Next device request returns ACTIVE
  → Device unlocks automatically ✓

```

DEVICE\_TOKEN and configPayload are preserved during suspension. Device resumes instantly when restored — no re-registration needed.

---

## Flow 9 — Endpoint Access Control by Device Type

Server enforces device type restrictions on every request:

```
Request arrives
→ extract X-Device-Token
→ look up device record → read deviceType
→ check: is this deviceType permitted on this endpoint?
→ yes → proceed
→ no  → 403 Forbidden

```

### Endpoint Permission Matrix

<table id="bkmrk-endpoint-pos-store_t"><thead><tr><th>Endpoint</th><th>POS</th><th>STORE\_TABLET</th><th>KIOSK</th><th>KITCHEN\_DISPLAY</th></tr></thead><tbody><tr><td>`GET /menu/public`</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>`POST /orders`</td><td>✅</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>`GET /kitchen/display`</td><td>✅</td><td>❌</td><td>❌</td><td>✅</td></tr><tr><td>`POST /pos/cash-drawer`</td><td>✅</td><td>❌</td><td>❌</td><td>❌</td></tr><tr><td>`GET /reports`</td><td>✅</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>`POST /kiosk/self-checkout`</td><td>❌</td><td>❌</td><td>✅</td><td>❌</td></tr><tr><td>`POST /auth/staff/login`</td><td>✅</td><td>✅</td><td>❌</td><td>✅</td></tr></tbody></table>

---

## Request Structure Reference

### Device-Only Request (KIOSK / KITCHEN\_DISPLAY)

```
Headers:
  X-Device-Token: <DEVICE_TOKEN>

```

### Device + Staff Request (POS / STORE\_TABLET)

```
Headers:
  X-Device-Token: <DEVICE_TOKEN>
  X-Staff-Token:  <STAFF_TOKEN>

Server validates:
→ DEVICE_TOKEN valid and ACTIVE?
→ STAFF_TOKEN valid and not expired?
→ STAFF_TOKEN.deviceId === DEVICE_TOKEN.deviceId?
→ deviceType requires staff auth?
→ all yes → process request

```

---

## Payload Shapes

### DEVICE\_TOKEN Payload (JWT)

```json
{
  "deviceId": "dv_uuid",
  "kitchenId": "kt_uuid",
  "deviceType": "KIOSK"
}

```

### STAFF\_TOKEN Payload (JWT)

```json
{
  "staffId": "st_uuid",
  "kitchenId": "kt_uuid",
  "deviceId": "dv_uuid",
  "expiresAt": "2026-05-05T18:00:00Z"
}

```

Permissions are **not** inside the token. They are fetched once per session and cached on device.

### Staff Permissions Payload

```json
{
  "permissionsHash": "b7d1e4...",
  "permissions": {
    "canViewOrders": true,
    "canManageOrders": true,
    "canViewReports": false,
    "canManageMenu": false,
    "canManageStaff": false,
    "canProcessRefunds": false
  }
}

```

### Effective Permissions (Device ∩ Staff)

```
Device allowReports: false
Staff  canViewReports: true
Effective: CANNOT view reports on this device

Device allowPOS: true
Staff  canProcessRefunds: true
Effective: CAN process refunds on this device

```

Staff can never exceed what the device allows, regardless of their role.

---

## Owner Dashboard — Device Management Screens

### Screen 1 — Devices List

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        Devices                [+ Add Device]

        ┌─────────────────────────────┐
        │ 🟢 Front Kiosk              │
        │    KIOSK · Mama Pima        │
        │    Last seen: 2 mins ago   →│
        ├─────────────────────────────┤
        │ 🟢 Counter POS              │
        │    POS · Mama Pima          │
        │    Last seen: 5 mins ago   →│
        ├─────────────────────────────┤
        │ 🔴 Back Office Tablet       │
        │    STORE_TABLET · Mama Pima │
        │    REVOKED                 →│
        ├─────────────────────────────┤
        │ 🟡 Kitchen Display          │
        │    KITCHEN_DISPLAY          │
        │    SUSPENDED               →│
        └─────────────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen 2 — Add Device (QR Scanner)

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        Add New Device

        Point your camera at the
        QR code shown on the device

        ┌─────────────────────────┐
        │                         │
        │    [ Camera Viewfinder ]│
        │                         │
        │   ┌───┐           ┌───┐ │
        │   └───┘           └───┘ │
        │                         │
        │   ┌───┐           ┌───┐ │
        │   └───┘           └───┘ │
        └─────────────────────────┘

        Make sure the device is showing
        its setup QR code

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen 3 — Configure Device (After Scan)

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        Device Claimed ✓
        Now configure it

        KIOSK · Mama Pima Kitchen    ← read-only

        Device Name
        ┌─────────────────────────┐
        │  Front Kiosk            │
        └─────────────────────────┘

        Permissions
        ─────────────────────────
        Dine In          [ ON  ]
        Pickup           [ ON  ]
        Delivery         [ OFF ]
        POS Access       [ OFF ]
        Reports          [ OFF ]
        Kitchen Display  [ ON  ]
        Store Access     [ OFF ]

        ┌─────────────────────┐
        │   Save & Activate   │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen 4 — Device Detail

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        ← Front Kiosk

        Status      🟢 ACTIVE
        Type        KIOSK
        Kitchen     Mama Pima Kitchen
        Last Seen   2 minutes ago
        Registered  1 May 2026

        Permissions
        ─────────────────────────
        Dine In          [ ON  ]
        Pickup           [ ON  ]
        Delivery         [ OFF ]
        POS Access       [ OFF ]
        Reports          [ OFF ]
        Kitchen Display  [ ON  ]

        ┌─────────────────────┐
        │   Edit Permissions  │
        └─────────────────────┘

        ┌─────────────────────┐
        │   Revoke Device     │  ← red, destructive
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen 5 — Revoke Confirmation

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        Revoke Device?

        ┌─────────────────────────┐
        │ [icon]  Front Kiosk     │
        │ KIOSK · Last seen       │
        │ 2 mins ago              │
        └─────────────────────────┘

        This device will be locked
        immediately. All local data
        will be wiped on its next
        connection.

        To use it again it must be
        re-registered from scratch.

        ┌─────────────────────┐
        │    Revoke Device    │  ← red
        └─────────────────────┘
        ┌─────────────────────┐
        │       Cancel        │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

---

## Device Screens (Dotted)

### Screen A — Authenticate Device (Unregistered)

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        [JikoXpress Logo]

        This device is not registered

        Ask your kitchen admin to
        scan the setup code

        ┌─────────────────────┐
        │   Set Up Device     │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen B — QR Code Display

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        [JikoXpress Logo]

        Scan this code with the
        JikoXpress admin app

        ┌─────────────────────────┐
        │                         │
        │    ██████   ██  ██████  │
        │    ██  ██  ████ ██  ██  │
        │    ██████  ██   ██████  │
        │       ██   ██  ██       │
        │    ██████  ████ ██████  │
        │                         │
        └─────────────────────────┘

        Refreshes in  03:47

        Waiting for admin to scan...

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

QR refreshes every 5 minutes. Old setupToken is burned on each refresh. Polling runs every 5 seconds **for CLAIMED status only** — stops the moment admin scans.

### Screen C — Admin Scanned (Complete Setup)

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        [JikoXpress Logo]

        Code scanned ✓

        Your admin is configuring
        this device on their phone.

        Tap below once they are done:

        ┌─────────────────────┐
        │   Complete Setup    │
        └─────────────────────┘

        ← Back (generate new QR)

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

No polling on this screen. User taps "Complete Setup" manually when admin signals done.

### Screen C2 — Complete Setup — Session Expired

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        [JikoXpress Logo]

        ⚠️  Setup session expired

        Ask your admin to scan
        the device again.

        ┌─────────────────────┐
        │   Generate New QR   │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen C3 — Complete Setup — Admin Not Done Yet

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        [JikoXpress Logo]

        Admin hasn't finished yet

        Ask your admin to complete
        the configuration on their
        phone then try again.

        ┌─────────────────────┐
        │      Try Again      │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen D — PIN Login (Registered Device)

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        Mama Pima Kitchen
        Front Kiosk

        Enter your PIN

        ┌───┐ ┌───┐ ┌───┐ ┌───┐
        │ ● │ │ ● │ │   │ │   │
        └───┘ └───┘ └───┘ └───┘

        ┌───┐ ┌───┐ ┌───┐
        │ 1 │ │ 2 │ │ 3 │
        ├───┤ ├───┤ ├───┤
        │ 4 │ │ 5 │ │ 6 │
        ├───┤ ├───┤ ├───┤
        │ 7 │ │ 8 │ │ 9 │
        ├───┤ ├───┤ ├───┤
        │ ⌫ │ │ 0 │ │ ✓ │
        └───┘ └───┘ └───┘

        [↺ Refresh]

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen E — Suspended

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        [JikoXpress Logo]

        ⚠️

        Kitchen subscription inactive

        This device has been
        suspended. Please contact
        your kitchen admin.

        ┌─────────────────────┐
        │      Refresh        │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

### Screen F — Revoked

```
  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

        [JikoXpress Logo]

        🔒

        Device access revoked

        This device has been
        deactivated by your admin.

        Contact your admin to
        re-register this device.

        ┌─────────────────────┐
        │   Set Up Device     │
        └─────────────────────┘

  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

```

---

## Endpoint Reference

### Device Setup — No Auth

<table id="bkmrk-method-endpoint-head"><thead><tr><th>Method</th><th>Endpoint</th><th>Headers</th><th>Body</th><th>Returns</th></tr></thead><tbody><tr><td>GET</td><td>`/devices/setup/token`</td><td>`X-Device-Fingerprint`</td><td>—</td><td>`setupToken`</td></tr><tr><td>GET</td><td>`/devices/setup/status`</td><td>`X-Device-Fingerprint`, `X-Setup-Token`</td><td>—</td><td>`{ status }`</td></tr><tr><td>GET</td><td>`/devices/setup/complete`</td><td>`X-Device-Fingerprint`, `X-Setup-Token`</td><td>—</td><td>`DEVICE_TOKEN + configPayload`</td></tr></tbody></table>

### Device Claim &amp; Config — Owner Auth

<table id="bkmrk-method-endpoint-head-1"><thead><tr><th>Method</th><th>Endpoint</th><th>Headers</th><th>Body</th><th>Returns</th></tr></thead><tbody><tr><td>POST</td><td>`/devices/claim`</td><td>`Authorization: Bearer <OWNER_TOKEN>`</td><td>`{ setupToken }`</td><td>`{ deviceId, status: UNCONFIGURED }`</td></tr><tr><td>PUT</td><td>`/devices/{id}/configure`</td><td>`Authorization: Bearer <OWNER_TOKEN>`</td><td>name, permissions</td><td>`{ success }`</td></tr><tr><td>GET</td><td>`/devices`</td><td>`Authorization: Bearer <OWNER_TOKEN>`</td><td>—</td><td>All devices for owner</td></tr><tr><td>PATCH</td><td>`/devices/{id}/revoke`</td><td>`Authorization: Bearer <OWNER_TOKEN>`</td><td>—</td><td>`{ success }`</td></tr><tr><td>PATCH</td><td>`/devices/{id}/reactivate`</td><td>`Authorization: Bearer <OWNER_TOKEN>`</td><td>—</td><td>`{ success }`</td></tr><tr><td>PUT</td><td>`/devices/{id}/permissions`</td><td>`Authorization: Bearer <OWNER_TOKEN>`</td><td>permissions object</td><td>`{ success }`</td></tr></tbody></table>

### Device Runtime — Device Auth

<table id="bkmrk-method-endpoint-head-2"><thead><tr><th>Method</th><th>Endpoint</th><th>Headers</th><th>Body</th><th>Returns</th></tr></thead><tbody><tr><td>GET</td><td>`/devices/{id}/config`</td><td>`X-Device-Token`</td><td>—</td><td>`deviceStatus + configHash + configPayload`</td></tr><tr><td>POST</td><td>`/devices/self-revoke`</td><td>`X-Device-Token`</td><td>`{ kitchenName }`</td><td>`{ status: REVOKED }`</td></tr></tbody></table>

### Staff Auth — Device Auth

<table id="bkmrk-method-endpoint-head-3"><thead><tr><th>Method</th><th>Endpoint</th><th>Headers</th><th>Body</th><th>Returns</th></tr></thead><tbody><tr><td>POST</td><td>`/auth/staff/login`</td><td>`X-Device-Token`</td><td>`{ pin }`</td><td>`STAFF_TOKEN + permissionsHash`</td></tr><tr><td>GET</td><td>`/staff/me/permissions`</td><td>`X-Device-Token`, `X-Staff-Token`</td><td>—</td><td>`permissionsHash + permissions`</td></tr><tr><td>POST</td><td>`/auth/staff/logout`</td><td>`X-Device-Token`, `X-Staff-Token`</td><td>—</td><td>`{ success }`</td></tr></tbody></table>

---

## Security Summary

<table id="bkmrk-threat-mitigation-st"><thead><tr><th>Threat</th><th>Mitigation</th></tr></thead><tbody><tr><td>Stranger scans QR</td><td>Claim requires valid OWNER\_TOKEN — scan alone is useless</td></tr><tr><td>QR replay attack</td><td>setupToken expires in 5 mins, single use, burns on claim</td></tr><tr><td>Wrong device claims token</td><td>Fingerprint registered at token fetch — mismatch on claim → rejected</td></tr><tr><td>Stolen DEVICE\_TOKEN</td><td>Owner revokes from dashboard → dead on next request</td></tr><tr><td>Accidental self-revoke on device</td><td>Two-step confirmation + kitchen name typed to confirm</td></tr><tr><td>Malicious self-revoke attempt</td><td>Server re-validates kitchenName against device record server-side</td></tr><tr><td>Staff PIN brute force</td><td>5 wrong attempts → 15 min lockout, device-scoped</td></tr><tr><td>Stolen STAFF\_TOKEN</td><td>Device-bound — useless on any other device</td></tr><tr><td>Device deactivated mid-session</td><td>deviceStatus in every response → device reacts immediately</td></tr><tr><td>Subscription lapse</td><td>All kitchen devices suspended automatically — no data lost</td></tr><tr><td>Wrong device type hits endpoint</td><td>Server reads deviceType from record — 403 if mismatch</td></tr><tr><td>Config changed while device offline</td><td>Boot always pulls fresh config — guaranteed fresh start</td></tr><tr><td>Rooted / jailbroken device</td><td>App detects on boot and refuses to run</td></tr><tr><td>Token extracted from storage</td><td>OS keychain hardware-backed — not extractable even on rooted devices (with detection)</td></tr></tbody></table>

---

## Secure Token Storage — Per Platform

DEVICE\_TOKEN must never be stored in plaintext, localStorage, or unencrypted files. Every platform has a secure OS-managed storage mechanism. JikoXpress follows the same pattern used by Spotify, Slack, Discord, and 1Password.

### Storage Mechanism Per Platform

<table id="bkmrk-platform-storage-mec"><thead><tr><th>Platform</th><th>Storage Mechanism</th><th>Security Level</th></tr></thead><tbody><tr><td>Android</td><td>Android Keystore System</td><td>⭐⭐⭐⭐⭐ hardware-backed</td></tr><tr><td>iOS</td><td>Keychain + Secure Enclave</td><td>⭐⭐⭐⭐⭐ hardware-backed</td></tr><tr><td>Windows</td><td>DPAPI / Credential Manager</td><td>⭐⭐⭐⭐ strong</td></tr><tr><td>Mac</td><td>macOS Keychain</td><td>⭐⭐⭐⭐⭐ hardware on Apple Silicon</td></tr><tr><td>Linux (with keychain)</td><td>libsecret → GNOME Keyring / KWallet</td><td>⭐⭐⭐⭐ strong</td></tr><tr><td>Linux (headless / no keychain)</td><td>Encrypted file, machine-derived key</td><td>⭐⭐⭐ acceptable</td></tr><tr><td>Web</td><td>httpOnly cookie only</td><td>⭐⭐⭐ acceptable — never localStorage</td></tr></tbody></table>

### Cross-Platform Desktop (Electron)

For any Electron-based desktop client, use `keytar` — the same library used by Spotify, Slack, and VS Code:

```javascript
// Store
await keytar.setPassword('JikoXpress', 'deviceToken', token)

// Retrieve
await keytar.getPassword('JikoXpress', 'deviceToken')

// Delete (on revoke/wipe)
await keytar.deletePassword('JikoXpress', 'deviceToken')

```

`keytar` wraps the OS keychain on all three platforms — one API, three platforms handled transparently.

### Linux Fallback (No Keychain Available)

```
Derive encryption key from:
  hash(machineId + username + appSalt)
→ encrypt DEVICE_TOKEN with derived key
→ store encrypted blob in ~/.config/jikoxpress/
→ not hardware-backed but significantly better than plaintext

```

This is the same approach VS Code uses on headless Linux.

### What Is Never Acceptable

```
❌ Plain text files on filesystem
❌ localStorage or sessionStorage (web)
❌ Unencrypted local SQLite
❌ Hardcoded in config files
❌ Environment variables on disk
❌ Shared storage accessible by other apps

```

### Root / Jailbreak Detection

On app boot, before loading any stored token:

```
Android → RootBeer library → detect root
iOS     → DTTJailbreakDetection → detect jailbreak
→ detected → refuse to run
→ show: "This device does not meet security requirements.
         Contact your admin."

```

Compromised OS → secure storage guarantees are void → app must not run.

### Managed Device Enforcement

Since JikoXpress devices are business-owned and managed:

```
App enforces on boot:
→ screen lock must be enabled
→ if no screen lock → refuse to run
→ show: "Please enable screen lock to use JikoXpress"

```

Physical access without screen lock is the simplest attack vector. Eliminating it costs nothing.

---

## Real-Time Architecture — WebSocket + STOMP

### What Needs Real-Time

```
Kitchen devices (KDS, POS, STORE_TABLET):
→ New order arrived (from any channel)
→ Order status changed
→ Order ready for pickup/delivery

Customer app:
→ Order confirmed
→ Order being prepared
→ Driver assigned
→ Driver location (live map)
→ Order delivered

Driver app:
→ New delivery assigned
→ Route updates

Platform dashboard (super admin):
→ Live orders across all kitchens
→ Live driver positions
→ Alerts on stuck orders

```

### Protocol Decision

<table id="bkmrk-protocol-order-event"><thead><tr><th>Protocol</th><th>Order Events</th><th>Driver Location</th><th>Verdict</th></tr></thead><tbody><tr><td>STOMP over WebSocket</td><td>✅ perfect fit</td><td>⚠️ works but heavy</td><td>Use for events</td></tr><tr><td>Raw WebSocket</td><td>✅ works</td><td>✅ perfect fit</td><td>Use for location</td></tr><tr><td>MQTT</td><td>✅ overkill</td><td>✅ strong but needs broker + separate auth per client</td><td>Not used</td></tr></tbody></table>

**MQTT rejected** — every mobile client (driver app, customer app) would need separate MQTT broker credentials, creating a second auth system alongside JikoXpress auth. Not worth it.

```
Final stack:
→ STOMP over WebSocket  → order events, notifications, status updates
→ Raw WebSocket         → driver location streaming (high frequency)
→ Auth for both         → existing DEVICE_TOKEN / user JWT in headers
→ No MQTT              → credential complexity not justified

```

### Topic Design (STOMP)

```
/topic/kitchen/{kitchenId}/orders       → KDS, POS, STORE_TABLET
/topic/kitchen/{kitchenId}/order/{id}   → specific order status updates
/topic/order/{orderId}/tracking         → customer tracks their order
/topic/platform/dashboard               → super admin live view

```

Server assigns topic subscriptions based on `deviceType` — device cannot self-subscribe to unauthorized topics.

### Raw WebSocket (Driver Location)

```
Driver app → /ws/location (raw WebSocket)
→ sends GPS coordinates every 3 seconds
→ socket server broadcasts to:
   /topic/order/{orderId}/tracking  → customer
   kitchen dashboard                → kitchen
   platform dashboard               → super admin

```

### Scaling Architecture (Phased)

```
Phase 1 — v1 (now):
→ STOMP embedded directly in main Spring Boot
→ kitchen order events only
→ zero extra infrastructure

Phase 2 — delivery launches:
→ extract dedicated Socket Server (separate Spring Boot service)
→ RabbitMQ between main backend and socket server
→ main backend publishes events → RabbitMQ → socket server → devices
→ raw WebSocket for driver location on same socket server
→ STOMP for all other events

Phase 3 — scale:
→ socket server scales horizontally
→ RabbitMQ fans out across all socket server instances
→ device clients unchanged — same STOMP protocol

```

```
Phase 2+ architecture:

┌─────────────────┐         ┌──────────────┐        ┌──────────────────────┐
│   Main Backend  │─publish─▶   RabbitMQ   │─consume─▶   Socket Server     │
│  (Spring Boot)  │         │              │        │  STOMP → /ws/stomp   │
└─────────────────┘         └──────────────┘        │  Raw WS → /ws/location│
                                                     └──────────┬───────────┘
                                                                │
                                          ┌─────────────────────┼─────────────────────┐
                                          ▼                     ▼                     ▼
                                    Kitchen Devices        Customer App          Driver App
                                    KDS / POS / Tablet     Order tracking        Location push

```

### Missed Message Recovery

STOMP does not retain messages. On reconnect each device fetches missed events:

```
KDS reconnects
→ GET /kitchen/orders?since={lastReceivedAt}&station=GRILL

POS reconnects
→ GET /kitchen/pos/orders?since={lastReceivedAt}

Customer app reconnects
→ GET /orders/{orderId}/status

```

No messages lost. Gap filled on reconnect via REST fallback.

---

## File Structure — `kitchen_service/devices/`

Base package: `org.qbitspark.jikoxpresspro`

```
kitchen_service/
└── devices/
    │
    ├── device_mng/
    │   ├── entity/
    │   │   └── DeviceEntity.java
    │   ├── enums/
    │   │   ├── DeviceType.java
    │   │   └── DeviceStatus.java
    │   ├── payload/
    │   │   ├── DeviceSetupTokenResponse.java
    │   │   ├── DeviceSetupStatusResponse.java
    │   │   ├── DeviceClaimRequest.java
    │   │   ├── DeviceConfigureRequest.java
    │   │   ├── DeviceConfigPayload.java
    │   │   ├── DevicePermissionsPayload.java
    │   │   ├── DeviceResponse.java
    │   │   └── DeviceSummaryResponse.java
    │   ├── repo/
    │   │   └── DeviceRepo.java
    │   ├── service/
    │   │   ├── DeviceService.java
    │   │   └── impl/
    │   │       └── DeviceServiceImpl.java
    │   ├── controller/
    │   │   ├── DeviceSetupController.java       ← setup/claim/complete (no auth)
    │   │   └── DeviceManagementController.java  ← owner dashboard endpoints
    │   └── mapper/
    │       └── DeviceMapper.java
    │
    ├── device_auth/
    │   ├── entity/
    │   │   └── StaffSessionEntity.java
    │   ├── enums/
    │   │   └── StaffTokenStatus.java
    │   ├── payload/
    │   │   ├── StaffLoginRequest.java
    │   │   ├── StaffLoginResponse.java
    │   │   ├── StaffPermissionsPayload.java
    │   │   └── DeviceAuthResponse.java          ← wrapper with deviceStatus + configHash
    │   ├── repo/
    │   │   └── StaffSessionRepo.java
    │   ├── service/
    │   │   ├── DeviceAuthService.java
    │   │   └── impl/
    │   │       └── DeviceAuthServiceImpl.java
    │   └── controller/
    │       └── DeviceAuthController.java
    │
    └── device_config/
        ├── payload/
        │   └── DeviceRuntimeResponse.java       ← envelope every response carries
        └── service/
            ├── DeviceConfigService.java
            └── impl/
                └── DeviceConfigServiceImpl.java

```

### Package Responsibilities

```
device_mng    → device lifecycle — setup, claim, configure, revoke, list
device_auth   → staff PIN login, session management, permissions
device_config → shared runtime envelope (deviceStatus + configHash)
               also handles boot config pull

```

`DeviceAuthResponse` is the shared wrapper every device endpoint returns — not just auth endpoints. It wraps the endpoint `data` alongside `deviceStatus` and `configHash`.

---

## Future Scope — KDS Advanced Configuration

> ⚠️ This section is documented for memory and future planning. Not part of v1 build scope.

Multiple KDS devices are fully supported by the current auth design — each KDS is a separate registered device with its own `DEVICE_TOKEN` and staff auth. What changes in future scope is the **configuration depth** of what each KDS shows and how it behaves.

### KDS Station Assignment (v1 — Basic)

Currently `deviceName` (e.g. "Grill Station") is the only station identifier. Enough for v1.

### KDS Advanced Config (Future)

Each KDS will have a `kdsConfig` block inside its configPayload covering:

**What to show:**

```
station          → GRILL / FRYER / COLD / DRINKS / BAKERY / EXPO / PASS
showByCategories → ["MAINS", "STARTERS"] — filter by menu category
showByFulfillment→ DINE_IN / DELIVERY / PICKUP / ALL
showByChannel    → ALL or specific channels (App, POS, Kiosk, WhatsApp)

```

**Display algorithm:**

```
sortAlgorithm    → FIFO / PRIORITY / BY_TABLE / FULFILLMENT_FIRST
gridColumns      → 4 / 6 / 8 (how many orders visible at once)
cardDetail       → COMPACT / FULL (item name only vs full modifiers + notes)
fontSize         → SMALL / MEDIUM / LARGE

```

**Timing &amp; alerts:**

```
alertYellowAfterMins → order turns yellow after X mins
alertRedAfterMins    → order turns red after Y mins
soundOnNewOrder      → true / false
soundOnCritical      → true / false

```

**Station coordination:**

```
expoMode                  → this KDS sees ALL stations, coordinates completion
showSiblingStationStatus  → show mini progress of other stations on same order
autoBumpOnAllStationsDone → auto-move order to READY when all stations done

```

### Future Config Payload Shape (KDS)

```json
{
  "deviceType": "KITCHEN_DISPLAY",
  "deviceName": "Grill Station",
  "station": "GRILL",
  "kdsConfig": {
    "showByCategories": ["MAINS", "STARTERS"],
    "showByFulfillment": ["DINE_IN", "PICKUP"],
    "showByChannel": ["ALL"],
    "sortAlgorithm": "FIFO",
    "gridColumns": 4,
    "cardDetail": "FULL",
    "fontSize": "MEDIUM",
    "alertYellowAfterMins": 8,
    "alertRedAfterMins": 15,
    "soundOnNewOrder": true,
    "soundOnCritical": true,
    "expoMode": false,
    "showSiblingStationStatus": true,
    "autoBumpOnAllStationsDone": false
  }
}

```

All of this will live inside configPayload — hash-monitored, server-controlled, no app update needed when owner changes KDS settings from dashboard.

### Real World KDS Setups

```
Small kitchen (1 KDS)
→ One screen, shows everything, chef manages all

Medium kitchen (2-3 KDS)
→ Grill Station  → grill items only
→ Fryer Station  → fryer items only
→ Cold Station   → salads, desserts, drinks

Large kitchen (4+ KDS)
→ All of above plus:
→ Expo Screen    → head chef sees ALL orders, coordinates
→ Pass Screen    → confirms order complete before handoff to runner

```

---

*JikoXpress Pro — Device Auth Spec v1.6 — QBIT SPARK*