# AI-/Lovable-prompter

Kör prompterna i ordning. Tillsammans producerar de mönstret på [Golden path](/docs/golden-path) — inte inloggning vid varje öppning.

> **Byt bara ut platshållarna** — Prompterna innehåller inga riktiga uppgifter. Sätt bas-URL för rätt miljö och låt `{{phoneNumber}}`/`{{password}}` komma från miljövariabler.

## 1. KiiOn Auth

Inloggning och säker server-side-lagring av tokenparet.

```text
Build a KiiOn API client in <language/framework>.

Base URL: https://staging.zesec.com/api
Login: POST /auth/login-by-phone with body {"phoneNumber": "...", "password": "..."}
Response: {"AccessToken": "...", "RefreshToken": "..."}

Requirements:
- Credentials come from environment variables, never from code or the repository.
- The token pair is stored SERVER-SIDE in shared storage (database or cache), one record per
  API account. It must never reach a browser, a mobile app or a log line.
- Expose exactly one entry point, getValidAccessToken(). No other code may call login().
- The account allows ONE active session, so the client must be a singleton per account.

Do not implement refresh yet. Just login, storage and the singleton boundary.
```

## 2. Token-förnyelse

Proaktiv förnyelse, 401-fallback och roterande engångs-refresh-token.

```text
Extend the client with token renewal.

Refresh: POST /auth/refresh with body {"refreshToken": "<RefreshToken>"}
Response: {"AccessToken": "...", "RefreshToken": "..."} — BOTH values are new.

Requirements:
- The refresh token is single-use. The one you sent is dead the instant the response arrives.
  Persist the new access token AND the new refresh token in ONE atomic write.
- Refresh proactively every 36 hours, or earlier if the JWT "exp" claim says so.
  Do not wait for a 401.
- Guard renewal with a single-flight lock: concurrent callers must await one refresh, never
  start two, or the second burns a token the first already consumed.
- On 400/401 from /auth/refresh the token is spent — fall back to a full login. Never retry the
  same refresh token.
- If the login fallback also fails, stop and surface the error. Do not loop.
- Wrap API calls so a 401 triggers one refresh and one replay of the original request, then gives up.
```

## 3. Enhetssynk + mappning

Hämta enhetslistan och cachelagra mappningen mot era egna dörrar.

```text
Add device synchronisation.

Device list: GET /user/device with header "Authorization: Bearer <AccessToken>"
Response: array of {"id": <int>, "name": "...", "locationId": <int>, "locationName": "...", "online": <bool>}

Requirements:
- The "id" field is the {deviceId} used by the unlock endpoint. It is an integer.
- Run syncDevices() on a schedule (roughly daily) and after any 404 from an unlock, never on the
  unlock path itself.
- Persist a mapping from OUR internal door identifier to the KiiOn id, keeping name, location and
  a syncedAt timestamp for debugging.
- Never hard-code a device id anywhere in the codebase.
```

## 4. Unlock-funktion

Ett anrop i öppningsögonblicket, med typade fel och audit.

```text
Add the unlock function.

Unlock: PUT /device/{deviceId}/unlock with header "Authorization: Bearer <AccessToken>"
A 200 means the command was accepted and forwarded to the device.

Requirements:
- The signature takes OUR internal door identifier, not a KiiOn id.
- Check our own authorization rules first. KiiOn does not decide who may open.
- Resolve {deviceId} from the cached mapping. Exactly one HTTP call happens here.
- Never retry an unlock automatically — a repeated call can open the door a second time. On
  failure, return a typed error: 401 handled by the token layer, 403 = no key for this device,
  404 = stale mapping (trigger a re-sync), 5xx = device or gateway unreachable.
- Write every attempt to our own audit log with actor, door, deviceId and outcome.
```

## 5. UI med öppna-knapp per dörr

Ett gränssnitt som aldrig ser en token.

```text
Build a UI that lists doors and gives each one an Open button.

Requirements:
- The UI talks ONLY to our own backend. It never sees a KiiOn token, base URL or device id.
- List the doors the signed-in user is allowed to open, using our own authorization rules.
- The Open button calls our backend endpoint, disables itself while the request is in flight and
  shows a clear result: opened, not permitted, door unreachable, or try again later.
- No automatic retry on failure — the user decides whether to press again.
- Show when a door is offline so the user is not left guessing.
```

**Vidare**
- [AI-integrationsdirektiv — hela underlaget i ett svep](/docs/ai-instructions)
- [/llms-full.txt — mata in hela dokumentationen i agenten](/llms-full.txt)
