# Codex Handoff - QRIS Soundbox Platform

Tanggal update: 2026-06-03, Asia/Jakarta.

Dokumen ini adalah snapshot kerja terakhir untuk melanjutkan project tanpa perlu membaca ulang seluruh chat.

## Status Terakhir

- Fokus hari ini bergeser dari production readiness umum ke integrasi device soundbox QF100.
- Folder SDK lokal `QF100-60s-l511-SecondApp-260107/` ditemukan dan dianalisis. Folder ini sengaja tidak dimasukkan git.
- Backend sekarang sudah punya adapter awal untuk firmware QF100 sample:
  - config server vendor-compatible;
  - payload MQTT payment success format QF100;
  - smoke script backend untuk static + dynamic QF100.
- Target milestone berikutnya: dua soundbox real, satu static dan satu dynamic, bisa ambil config dari backend, connect MQTT, lalu static payment test bunyi dan tercatat di dashboard.
- Worktree aktif/dirty karena perubahan backend QF100 dan update handoff. Jangan revert perubahan yang tidak eksplisit diminta.

## Verifikasi Terakhir

- `npm run typecheck`: pass setelah perubahan QF100.
- `npm install`: sudah dijalankan untuk memasang dependency lokal; perubahan lockfile accidental sudah dibalik.
- `npm run smoke:qf100`: script sudah dibuat, belum dijalankan end-to-end di DB/server lokal pada turn ini.
- Verifikasi lama yang masih relevan:
  - `npm run db:migrate`: sebelumnya pass dan idempotent sampai migration `003_export_job_storage.sql`.
  - `npm audit --json`: sebelumnya pass, 0 vulnerability.
  - `npm run ui:qa`: sebelumnya pass.
  - `npm run smoke:e2e`: sebelumnya pass.
  - Real MQTT smoke sebelumnya pernah pass terhadap `mqtts://broker.bizone.id:8883`.

## SDK QF100 Yang Ditemukan

- Folder lokal: `QF100-60s-l511-SecondApp-260107/`.
- Sudah ditambahkan ke `.gitignore`:
  - `QF100-60s-l511-SecondApp-260107/`
- Struktur penting:
  - `docs/Config Server.docx`: kontrak config server vendor.
  - `docs/Cloud Speaker API Spec V2.8.7.pdf`: API spec cloud speaker.
  - `app/source/MainApp/globalDefine.h`: device model, demo mode, `CONFIG_ADDR`.
  - `app/source/MainApp/demo.c`: alur config server, MQTT connect/subscribe, parse payment payload.
  - `app/source/MainApp/demo.h`: MQTT TLS/QoS/clean session/cert config.
  - `app/source/MainApp/main.c`: boot flow dan monitor network/MQTT.
  - `app/inc/MercuryMqtt.h`: header MQTT SDK.
  - `app/release/user_app.bin`: firmware build existing.

## Kesimpulan SDK QF100

- SDK ini memungkinkan develop/patch aplikasi firmware sendiri, tetapi bentuknya embedded C firmware app, bukan SDK backend.
- Strategi dipilih: firmware hanya hardcode URL config server backend kita, bukan hardcode broker MQTT.
- Firmware sample boot lalu call `CONFIG_ADDR` ke endpoint vendor `/speaker/dev-config`.
- Request config berisi field seperti:
  - `dev-model`
  - `item-number`
  - `dev-sn`
  - `fw-version`
  - `fw-build`
  - `app-config-version`
  - `imei`
  - `imsi`
  - `iccid`
- Response yang firmware cari memiliki blok:
  - `mqtt.broker-ip`
  - `mqtt.broker-port`
  - `mqtt.client-id`
  - `mqtt.user-name`
  - `mqtt.password`
  - `mqtt.subscribe-topic`
  - `mqtt.keep-alive`
- Payment success payload yang firmware sample bisa bunyikan:
  ```json
  {
    "header": {
      "category": 1
    },
    "data": {
      "pay-amount": 15000
    }
  }
  ```
- `MQTT_TLS_ENABLE` di SDK sample masih `0`. Jika broker production memakai `mqtts://...:8883`, firmware perlu patch TLS atau pilot perlu listener non-TLS terbatas.

## Implementasi QF100 Backend Hari Ini

### 1. Config Server Vendor-Compatible

- Route baru:
  - `GET /speaker/dev-config`
- File:
  - `src/routes/speaker.ts`
  - mounted di `src/app.ts`
- Perilaku:
  - menerima query/body vendor;
  - lookup device memakai `dev-sn` -> `devices.serial_number`;
  - jika device aktif, update:
    - `vendor`
    - `model`
    - `communication_mode`
    - `last_seen_at`
    - `firmware_version`
  - mencatat heartbeat `state: config_pull`;
  - balas JSON vendor top-level, bukan wrapper internal `successResponse`.
- Error vendor style:
  - `1001`: `dev-sn` kosong;
  - `1002`: device tidak terdaftar atau inactive;
  - `1003`: broker MQTT belum dikonfigurasi.

### 2. Env QF100

- Env baru di `src/config/env.ts` dan `.env.example`:
  - `QF100_MQTT_BROKER_HOST`
  - `QF100_MQTT_BROKER_PORT`
  - `QF100_MQTT_USERNAME`
  - `QF100_MQTT_PASSWORD`
  - `QF100_MQTT_KEEP_ALIVE_SECONDS`
- Jika host/port QF100 kosong, endpoint mencoba parse dari `MQTT_BROKER_URL`.
- Catatan penting: password MQTT yang dikirim ke QF100 diambil dari `QF100_MQTT_PASSWORD` atau fallback `MQTT_PASSWORD`. Sistem saat ini menyimpan credential device sebagai fingerprint, jadi plaintext per-device tidak bisa dibaca ulang dari DB.

### 3. Payload MQTT QF100

- File:
  - `src/shared/services/mqttPublisher.ts`
  - `src/shared/orchestrators/notificationOrchestrator.ts`
- Topic QF100:
  - `devices/{deviceId}/downlink/qf100`
- Adapter memilih format QF100 jika:
  - `device.model` mengandung `QF100`; atau
  - `capability_profile_json.mqtt_payload_profile`, `protocol_profile`, atau `vendor_protocol` bernilai `qf100`.
- Downlink payment sekarang juga dicatat ke `mqtt_messages`, sehingga dashboard device detail bisa melihat topic/payload yang dikirim.

### 4. Device Store dan Schema

- Lookup baru:
  - `getDeviceBySerialNumber(serialNumber)` di `src/shared/store/deviceStore.ts`.
- Schema bootstrap menambahkan index:
  - `idx_devices_serial_number`.

### 5. Smoke QF100

- Script baru:
  - `scripts/smoke-qf100-adapter.mjs`
- Package script baru:
  - `npm run smoke:qf100`
- Script ini:
  - create merchant/outlet/terminal/device static;
  - create merchant/outlet/terminal/device dynamic MQTT;
  - set `model: QF100` dan `capability_profile_json.mqtt_payload_profile: qf100`;
  - hit `/speaker/dev-config` untuk dua SN;
  - validasi MQTT config vendor;
  - trigger static QRIS paid callback;
  - validasi `mqtt_messages` downlink QF100 payload `category: 1`;
  - trigger backend dynamic QR MQTT flow untuk device dynamic.
- Untuk memakai SN real:
  ```bash
  QF100_STATIC_SN=<sn-static> \
  QF100_DYNAMIC_SN=<sn-dynamic> \
  BASE_URL=http://127.0.0.1:3000 \
  npm run smoke:qf100
  ```

## Device Flow Yang Disepakati

### Static Soundbox

1. Device boot.
2. Firmware call backend `/speaker/dev-config`.
3. Backend balas MQTT config.
4. Device connect MQTT dan subscribe `devices/{deviceId}/downlink/qf100`.
5. QRIS callback paid masuk backend.
6. Backend publish payload QF100 `category: 1`.
7. Device bunyi nominal.
8. Dashboard melihat transaction, notification, mqtt message, last seen/config pull.

### Dynamic Soundbox

- Prinsip: trigger tetap via MQTT, bukan polling terus-menerus.
- HTTP polling dynamic QR dianggap tidak cocok untuk skala 20 ribu device karena `gap=30s` berarti sekitar 40 ribu request/menit.
- Desain lanjutan:
  1. Backend/admin/merchant membuat dynamic QR request.
  2. Backend publish MQTT command ke device.
  3. Device menampilkan QR langsung atau HTTP fetch QR detail dari URL.
  4. Payment callback tetap jadi source of truth.
  5. Payment success tetap via MQTT `category: 1`.
- Firmware dynamic handler belum dipatch. Perlu tambah handler misalnya `category == 10` di `demo.c`.

## Dashboard Yang Perlu Disiapkan Berikutnya

Prioritas UI/dashboard untuk test dua soundbox real:

1. Device detail QF100 panel:
   - SN;
   - model/profile;
   - config pull terakhir;
   - broker host/port yang dikirim;
   - client-id;
   - subscribe-topic;
   - keepalive.
2. Test payment button:
   - nominal quick actions, misalnya Rp1.000 dan Rp15.000;
   - create dummy tx/callback internal;
   - tampilkan notification dan MQTT payload result.
3. MQTT timeline:
   - direction;
   - topic;
   - message_type;
   - payload JSON;
   - publish_status/reason;
   - timestamp.
4. Dynamic QR panel:
   - input nominal;
   - create dynamic QR;
   - tampilkan QR payload/status;
   - nanti `Send to Soundbox` via MQTT command setelah firmware handler siap.
5. Ops summary:
   - total soundbox;
   - online/stale/offline;
   - config pull terbaru;
   - payment notification sent/failed;
   - dynamic QR active/paid/expired.

## Endpoint Penting Saat Ini

- Health:
  - `GET /health`
  - `GET /health/deep`
- QF100 vendor config:
  - `GET /speaker/dev-config`
- Admin auth/session:
  - `POST /admin/login`
  - `POST /admin/logout`
  - `GET /admin/me`
- Admin device:
  - `POST /admin/devices`
  - `GET /admin/devices`
  - `GET /admin/devices/{id}`
  - `POST /admin/devices/{id}/credentials/rotate`
  - `POST /admin/devices/{id}/bind`
  - `POST /admin/devices/{id}/unbind`
  - `GET /admin/devices/{id}/mqtt-messages`
  - `GET /admin/devices/{id}/notifications`
- Device API:
  - `POST /device/heartbeat`
  - `POST /device/transactions/dynamic-qr`
  - `POST /device/mqtt/uplink/dynamic-qr/request`
  - `GET /device/config`
  - `POST /device/config/ack`
- Integrations:
  - `POST /integrations/qris/callback`
- Observability:
  - `GET /admin/observability/summary`
  - `GET /admin/observability/mqtt-status`

## Package Scripts Penting

- `npm run typecheck`
- `npm run db:migrate`
- `npm run smoke:qf100`
- `npm run smoke:e2e`
- `npm run smoke:mqtt-real`
- `npm run smoke:mqtt-acl`
- `npm run ui:qa`
- `npm run deploy:check-env`
- `npm run load:test`
- `npm run load:test:staging`
- `npm run mqtt:provision-device`
- `npm run mqtt:check-acl`
- `npm run admin:create-user`
- `npm run merchant:create-user`

## File Kunci Yang Sering Disentuh

- App bootstrap: `src/app.ts`
- Env config: `src/config/env.ts`
- QF100 config route: `src/routes/speaker.ts`
- Device route: `src/routes/device.ts`
- Admin route: `src/routes/admin.ts`
- MQTT publisher: `src/shared/services/mqttPublisher.ts`
- MQTT subscriber: `src/shared/services/mqttSubscriber.ts`
- Notification orchestrator: `src/shared/orchestrators/notificationOrchestrator.ts`
- Device store: `src/shared/store/deviceStore.ts`
- MQTT message store: `src/shared/store/mqttMessageStore.ts`
- Heartbeat store: `src/shared/store/heartbeatStore.ts`
- Schema bootstrap: `src/shared/db/pool.ts`
- QF100 smoke: `scripts/smoke-qf100-adapter.mjs`
- Env sample: `.env.example`

## Sisa Gap Utama

1. Jalankan `npm run smoke:qf100` terhadap backend + DB lokal/staging.
2. Register dua device real:
   - static SN -> device static;
   - dynamic SN -> device dynamic.
3. Patch firmware QF100:
   - `CONFIG_ADDR` ke backend kita;
   - TLS setting sesuai broker.
4. Test static device real:
   - config pull;
   - MQTT connect;
   - payment success bunyi;
   - dashboard melihat downlink.
5. Patch firmware dynamic:
   - tambah handler command dynamic QR, kemungkinan `category == 10`;
   - pilih direct QR payload vs HTTP fetch detail.
6. Siapkan dashboard QF100 detail/test panel.
7. Putuskan credential strategy production:
   - shared pilot password saat ini cukup untuk lab;
   - production sebaiknya credential per-device yang bisa diprovisioning aman.
8. Tetap perlu staging rehearsal, restore drill, export storage strategy, dan manual visual QA dari handoff lama.

## Catatan Penting

- Folder SDK QF100 adalah artefak lokal dan jangan dimasukkan git.
- Jangan hardcode broker credential production di firmware.
- Firmware cukup hardcode config server URL; broker/topic/credential dikirim dari backend.
- Jangan gunakan wildcard MQTT subscribe di production kecuali maintenance terkontrol.
- Jika memakai MQTT TLS (`mqtts://...:8883`), firmware QF100 perlu `MQTT_TLS_ENABLE=1` dan sertifikat yang sesuai.
- Rate limiting aktif default; `/speaker` memakai limiter device.
- `CODEX_HANDOFF.md` ini adalah snapshot operasional terbaru; untuk detail historis keputusan, baca `DECISIONS_LOG.md`.
