# Decisions Log — QRIS Soundbox Platform Log keputusan arsitektur dan implementasi yang harus dijadikan acuan eksekusi. ## Format Entri - ID: [D-XXX] - Tanggal: - Keputusan: - Alasan: - Dampak / implikasi: - Status: ## D-001 — Basis Implementasi Fase 0 dan 1 - Tanggal: 2026-05-23 - Keputusan: - Menjalankan eksekusi langsung berdasarkan fase roadmap, tanpa pembuatan jadwal rinci. - Alasan: - Tim sudah punya pembagian fase yang jelas dan siap mulai implementasi langsung. - Dampak / implikasi: - Fokus pada deliverable per fase dan DoD, bukan timeline. - Status: Active ## D-002 — Scope Fase 1 Step 1 - Tanggal: 2026-05-23 - Keputusan: - Step 1 Fase 1 dibatasi pada core foundation: auth baseline, schema MVP, merchant/outlet/terminal/device/binding, observability dasar. - Alasan: - Memastikan jalur static payment bisa dipersiapkan stabil sebelum dynamic flow. - Dampak / implikasi: - Fitur lain (settlement, dynamic QR, merchant portal) ditunda sampai Step 1 stabil. - Status: Active ## D-003 — API Contract Error Standard - Tanggal: 2026-05-23 - Keputusan: - Semua response error mengikuti envelope seragam: - `code`, `message`, `details`, `request_id`, `timestamp`. - Alasan: - Memudahkan debug dan tracing lintas service/device. - Dampak / implikasi: - Semua handler API harus mematuhi middleware response formatter yang sama. - Status: Active ## D-004 — Traceability Requirement - Tanggal: 2026-05-23 - Keputusan: - Semua request harus membawa `request_id`; webhook/device callback harus dihubungkan dengan `trace_id` jika multi-step. - Alasan: - Root-cause analysis perlu konteks end-to-end dari callback sampai notifikasi. - Dampak / implikasi: - Framework logging dan parser event wajib men-generate field ini secara konsisten. - Status: Active ## D-005 — Idempotency Rule - Tanggal: 2026-05-23 - Keputusan: - Setiap path sensitif terhadap duplicate (create merchant/create device/binding/webhook request/dynamic QR nanti) wajib men-support idempotency key/table. - Alasan: - Menghindari double create, double binding, dan state drift akibat retry. - Dampak / implikasi: - Menambah kebutuhan service/DB untuk `idempotency_keys` sejak awal Fase 1. - Status: Active ## D-006 — Device Binding - Tanggal: 2026-05-23 - Keputusan: - Notifikasi pembayaran hanya boleh dipush ke device dari binding aktif yang valid (active_flag=true). - Alasan: - Mencegah notifikasi salah kirim jika device pernah dipindahkan antar outlet/terminal. - Dampak / implikasi: - Query notification selalu harus resolve binding aktif secara explicit. - Status: Active ## D-007 — Fallback Strategy untuk Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Retry MQTT pada Fase 1 memakai retry sederhana (fixed/backoff pendek), tanpa DLQ kompleks. - Alasan: - Menghemat effort awal sambil menjaga fitur core berjalan. - Dampak / implikasi: - Fase 4 mengelola policy DLQ dan retry matang. - Status: Active ## D-008 — Minimal RBAC untuk Mulai Eksekusi - Tanggal: 2026-05-23 - Keputusan: - Implement RBAC baseline minimum di Fase 1 (admin-only) sambil menjaga token device terpisah. - Alasan: - Mengurangi risiko akses silang awal tanpa memperlambat pengembangan. - Dampak / implikasi: - Permission matrix akan disempurnakan di Fase 4. - Status: Active ## D-009 — Dokumentasi Eksekusi Step 1 - Tanggal: 2026-05-23 - Keputusan: - Spesifikasi detail Step 1 dituangkan di `12-fase1-step1-core-foundation-spec.md`. - Alasan: - Menghindari ketidakpastian saat tim mulai coding. - Dampak / implikasi: - Semua implementer wajib merujuk dokumen ini saat menyiapkan branch dan PR. - Status: Active ## D-010 — Webhook Signature dan Parsing - Tanggal: 2026-05-23 - Keputusan: - Semua callback harus divalidasi signature-nya terlebih dahulu; jika tidak valid, respons harus `401` dan tidak boleh melakukan perubahan state apapun. - Alasan: - Payment callback adalah sumber trust utama dan harus dijaga dari spoofing/replay. - Dampak / implikasi: - Service callback wajib mengimplementasikan validator HMAC (atau skema cryptographic sesuai partner) sebelum mapping transaksi. - Status: Active ## D-011 — Callback Idempotency di Level Transaksi - Tanggal: 2026-05-23 - Keputusan: - Callback duplikat harus diidentifikasi deterministik dari `partner_reference + payment_status + partner_event_id` dan diperlakukan idempotent. - Alasan: - Callback retry sangat umum dari partner dan dapat memicu double state update. - Dampak / implikasi: - state transition dilakukan hanya jika perubahan state valid sesuai mesin state. - Status: Active ## D-012 — Evented Transaction State Change - Tanggal: 2026-05-23 - Keputusan: - Setiap perubahan state transaksi harus menghasilkan `transaction_events` agar Step 3 dan observability bisa berjalan. - Alasan: - Auditability dan troubleshooting menuntut timeline per transaksi. - Dampak / implikasi: - update transaksi tanpa menulis event dianggap bug di Step 1. - Status: Active ## D-013 — Terminal Event `paid` sebagai Trigger Notification - Tanggal: 2026-05-23 - Keputusan: - Notifikasi pembayaran di Step 3 harus dipicu dari event internal status `paid`, bukan dari polling callback. - Alasan: - Menghindari race dan duplikasi notifikasi. - Dampak / implikasi: - Implementasi Step 3 harus subscribe dan consume event transaction paid. - Status: Active ## D-014 — Retry Notifikasi Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Step 3 menggunakan retry dasar maksimum 3 kali dengan jadwal 15/30/60 detik. - Alasan: - Menyeimbangkan reliability dan kecepatan operasional tanpa kompleksitas DLQ penuh pada fase awal. - Dampak / implikasi: - `notifications.retry_count` wajib ditulis dan dibatasi maksimal 3. - Status: Active ## D-015 — Notifikasi Tanpa Ack Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Pada fase 1, absence of MQTT ack tidak dianggap error akhir; status sukses ditentukan dari publish outcome, bukan ack dari device. - Alasan: - Device ecosystem belum konsisten untuk ack schema, dan goal fase 1 adalah stabilitas flow utama. - Dampak / implikasi: - `ack_status` bisa `not_supported`, dan operasi tetap lanjut dengan monitoring via retry/publish status. - Status: Active ## D-016 — Heartbeat Status Threshold Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Definisi status device ditetapkan: online (<90s), degraded (signal/battery buruk), stale (<15 menit), offline (>15 menit) berdasarkan `last_seen_at`. - Alasan: - Konsistensi status dibutuhkan untuk ops triage cepat. - Dampak / implikasi: - Setiap list/detail device menampilkan status yang dihitung dari rule yang sama. - Status: Active ## D-017 — Dashboard KPI Fase 1 - Tanggal: 2026-05-23 - Keputusan: - Dashboard ops minimum menampilkan: transaksi hari ini, success rate hari ini, active devices, pending notifications, stale/offline counts. - Alasan: - Tim operasi perlu indikator cepat tanpa menunggu custom analytics. - Dampak / implikasi: - Endpoint summary dashboard wajib dihitung dari data transaksi/device/notification inti. - Status: Active ## D-018 — Pencairan Dana Non-Tersentral per Merchant - Tanggal: 2026-05-24 - Keputusan: - Pencairan dana tidak dilakukan di rekening perusahaan; setiap merchant memakai rekening tujuan sendiri (atau reference payout miliknya) untuk settlement. - Alasan: - Menghindari ketergantungan izin/operasional PJP di tahap awal dan mempercepat launch MVP. - Dampak / implikasi: - Schema dan model onboarding merchant memakai `payout_account_reference`/rekening merchant, bukan rekening vault pusat. - Modul settlement platform difokuskan ke rekonsiliasi, status payout, dan visibility, bukan pengelolaan rekening pusat. - Callback payout dan payout execution dianggap partner-oriented (tergantung integrasi penyedia), bukan core di fase awal. - Status: Active ## D-019 — Fase 1 Audit Log dan Ledger Placeholder - Tanggal: 2026-05-26 - Keputusan: - Fase 1 mencatat audit log untuk aksi admin/webhook penting dan membuat ledger placeholder `gross_income` saat transaksi berubah ke `paid`. - Alasan: - Acceptance Fase 1 membutuhkan audit aksi CRUD penting, callback state changes, dan placeholder ledger untuk transaksi sukses tanpa menunggu modul finance penuh. - Dampak / implikasi: - `audit_logs` menjadi sumber trace operasional awal untuk entity penting. - `ledger_entries` Fase 1 hanya placeholder gross income; fee/platform payable detail tetap masuk Fase 3. - Duplicate paid callback tetap idempotent dan tidak membuat ledger duplikat karena unique key per `transaction_id + entry_type`. - Status: Active ## D-020 — Awal Fase 2 Dynamic QR API-Direct - Tanggal: 2026-05-26 - Keputusan: - Fase 2 dimulai dari capability resolver dan endpoint `POST /device/transactions/dynamic-qr` untuk device `communication_mode=api`. - Dynamic QR API-direct memakai mock QRIS payload lokal sampai integrasi partner QRIS tersedia. - Alasan: - Capability/routing harus stabil sebelum MQTT dynamic dan config push dibangun. - Mock partner memungkinkan transaksi dynamic tersimpan dan callback Fase 1 tetap diuji sebagai source of truth. - Dampak / implikasi: - Device static/MQTT yang tidak memiliki capability `dynamic_qr.api_direct` ditolak dengan `DEVICE_CAPABILITY_NOT_SUPPORTED`. - Device wajib punya binding aktif ke terminal `qr_mode=dynamic_api`; jika tidak, API mengembalikan `DEVICE_NOT_BOUND`. - Response dynamic QR idempotent memakai `Idempotency-Key` atau `request_id`, dan transaksi dibuat `awaiting_payment`. - Callback paid tetap memakai endpoint webhook yang sama untuk mengubah transaksi menjadi `paid` dan memicu notifikasi. - Status: Active ## D-021 — MQTT Dynamic QR dan Device Config Fase 2 - Tanggal: 2026-05-26 - Keputusan: - MQTT dynamic QR Fase 2 diimplementasikan sebagai HTTP simulator endpoint `POST /device/mqtt/uplink/dynamic-qr/request` yang mencatat uplink/downlink ke `mqtt_messages`. - Config push memakai `PATCH /admin/devices/{deviceId}/config`, disimpan di `device_configs`, dipublish ke MQTT outbox, lalu device mengirim `POST /device/config/ack`. - Alasan: - Belum ada broker MQTT sungguhan di stack lokal, tapi kontrak topic/payload dan idempotency perlu bisa diuji end-to-end. - Outbox membuat downlink response/config push observable lewat admin sebelum integrasi broker asli. - Dampak / implikasi: - Saat broker MQTT dipasang, `mqtt_messages` bisa menjadi outbox/trace awal untuk adapter broker. - Dynamic MQTT request memakai `request_id` sebagai `correlation_id` dan idempotency key. - Device config selalu versioned; ACK dicatat terpisah di `device_config_acks`. - Status: Active ## D-022 — Config Drift dan Retry Push Fase 2 - Tanggal: 2026-05-26 - Keputusan: - Fase 2 menambahkan status drift config device melalui `GET /admin/devices/{deviceId}/config/status`. - Retry config dilakukan via `POST /admin/devices/{deviceId}/config/retry-push` tanpa menaikkan `config_version`. - ACK config dari device juga dicatat sebagai uplink trace di `mqtt_messages` dengan `message_type=config_ack`. - Alasan: - Operasi perlu membedakan config `applied`, `pending_ack`, `failed_ack`, `stale_ack`, dan `never_pushed` sebelum broker MQTT sungguhan dipasang. - Retry harus mengirim ulang desired config yang sama agar idempotent dan tidak membuat drift versi buatan. - Dampak / implikasi: - Config yang sudah `applied` tidak boleh di-retry kecuali admin mengirim `force=true`. - `mqtt_messages` menjadi timeline awal untuk config push dan ACK device. - Saat broker MQTT asli masuk, endpoint retry tetap menjadi trigger adapter/outbox, bukan tempat menyimpan logic konfigurasi baru. - Status: Active ## D-023 — Health Summary Device Fase 2 - Tanggal: 2026-05-26 - Keputusan: - Endpoint admin device menambahkan `health_summary` berisi `status`, `score`, `age_seconds`, dan `reasons`. - `derived_status` tetap dipertahankan untuk kompatibilitas UI, namun nilainya berasal dari rule health summary yang sama. - Alasan: - Operasi membutuhkan konteks kenapa device online/degraded/stale/offline, bukan hanya label status. - Skor 0-100 memudahkan sorting/filter dashboard tanpa mengubah threshold status Fase 1. - Dampak / implikasi: - Status masih mengikuti threshold Fase 1: online <90 detik, stale >90 detik, offline >15 menit, degraded untuk sinyal/baterai buruk. - UI dapat memakai `health_summary.reasons` untuk badge/tooltip ops. - Status: Active ## D-024 — UI Ops Device Fase 2 - Tanggal: 2026-05-26 - Keputusan: - UI device registry dan device technical detail menampilkan `health_summary` dan config delivery status. - Retry config push tersedia dari drawer device registry dan halaman detail device. - Alasan: - Operator perlu melihat status Fase 2 tanpa membuka raw payload/API response. - Retry config adalah tindakan operasional langsung, sehingga harus dekat dengan status drift config. - Dampak / implikasi: - UI memakai endpoint `GET /admin/devices/{deviceId}/config/status` dan `POST /admin/devices/{deviceId}/config/retry-push`. - `derived_status` tetap dipakai sebagai fallback/compatibility, sementara health score/reasons menjadi konteks tambahan. - Status: Active ## D-025 — Dynamic QR Expiry Sweep Fase 2 - Tanggal: 2026-05-26 - Keputusan: - Dynamic QR `awaiting_payment` yang melewati `expired_at` dapat ditutup oleh sweep internal `POST /admin/transactions/expire-due`. - Sweep hanya berlaku untuk transaksi `qr_mode=dynamic` dan status `awaiting_payment`. - Alasan: - Fase 2 tidak boleh bergantung penuh pada callback partner untuk menutup QR yang kadaluarsa. - Expiry internal menjaga daftar transaksi pending tetap akurat untuk ops dan device flow. - Dampak / implikasi: - Sweep menulis `STATE_CHANGED` event dengan reason `dynamic_qr_expired`. - Callback paid yang datang setelah transaksi sudah `expired` tetap ditolak oleh state transition guard. - Endpoint admin ini bisa menjadi dasar scheduler/background worker saat fase operasional berikutnya. - Status: Active ## D-026 — Penundaan Broker MQTT Sungguhan - Tanggal: 2026-05-28 - Keputusan: - Integrasi broker MQTT sungguhan ditunda sampai infrastruktur broker, credential, network access, dan topic policy siap. - Selama broker belum siap, Fase 2 tetap memakai HTTP simulator dan `mqtt_messages` sebagai outbox/trace untuk dynamic QR MQTT, config push, dan config ACK. - Prioritas lanjutan sebelum broker siap adalah hardening UI ops, scheduler dynamic QR expiry, dan observability outbox. - Alasan: - Broker MQTT membutuhkan persiapan eksternal di luar kode aplikasi agar implementasi adapter tidak menebak-nebak detail koneksi dan policy topic. - Simulator/outbox saat ini sudah cukup untuk menguji kontrak payload, idempotency, correlation ID, dan visibilitas admin. - Dampak / implikasi: - Tidak menambahkan dependency broker/client MQTT sungguhan sampai detail infrastruktur tersedia. - Adapter broker nanti harus memakai `mqtt_messages` sebagai sumber trace/outbox awal, bukan mengganti kontrak Fase 2 yang sudah berjalan. - Pekerjaan berikutnya dapat lanjut ke scheduler expiry dan peningkatan UI/filter ops tanpa blocking pada broker. - Status: Active ## D-027 — Broker MQTT Awal Menggunakan Mosquitto - Tanggal: 2026-05-28 - Keputusan: - Broker MQTT sungguhan pertama akan memakai Mosquitto karena ringan, sederhana dioperasikan, dan cukup untuk kebutuhan MVP/early production. - Migrasi ke EMQX atau managed MQTT tetap dibuka jika skala, high availability, dashboard broker, rule engine, atau auth/authorization kompleks mulai dibutuhkan. - Kontrak topic, credential model, dan adapter backend harus dibuat broker-agnostic agar migrasi tidak mengubah flow device. - Alasan: - Kebutuhan saat ini masih single broker dengan TLS, username/password, ACL per device, dan publish/subscribe dasar. - Mosquitto memberi jalur deploy paling cepat sambil menjaga biaya dan kompleksitas operasional rendah. - Target skala besar seperti 100.000 device masih memungkinkan dievaluasi lewat benchmark bertahap sebelum memutuskan migrasi. - Dampak / implikasi: - Hindari penggunaan fitur broker-spesifik yang sulit dipindahkan. - Device topic tetap mengikuti kontrak `devices/{deviceId}/...`. - Credential device tetap dikelola sebagai bagian dari domain platform agar tidak terkunci pada format internal broker tertentu. - Benchmark kapasitas wajib dilakukan sebelum milestone skala besar, misalnya 20k, 50k, dan 100k connected device. - Status: Active ## D-028 — Finance Light dan Credential Device Operasional - Tanggal: 2026-05-28 - Keputusan: - Ledger Fase 1 dinaikkan dari placeholder gross-only menjadi finance light: `gross_income`, `platform_fee`, dan `merchant_payable`. - Platform fee awal memakai konfigurasi `FINANCE_PLATFORM_FEE_BPS` agar mudah diubah tanpa migrasi schema. - Credential MQTT device dikelola di domain device melalui status credential, username MQTT, timestamp issue/rotate/revoke, dan fingerprint secret. - Password credential hanya dikembalikan satu kali oleh endpoint rotate dan tidak disimpan sebagai plaintext. - Username MQTT device harus sama dengan `device_id` karena ACL Mosquitto memakai pattern `devices/%u/...`. - Alasan: - Admin settlement dan reconciliation butuh angka net payable awal sebelum modul settlement penuh. - Credential device perlu siap sebelum ACL/per-device broker MQTT dibuat agar provisioning tidak bergantung pada catatan manual. - Dampak / implikasi: - Callback paid membuat ledger idempotent per `transaction_id + entry_type`. - Model fee saat ini masih global/default; fee profile merchant tetap bisa ditambahkan kemudian tanpa mengubah kontrak ledger entry. - Endpoint `POST /admin/devices/{deviceId}/credentials/rotate` menjadi jalur provisioning credential MQTT awal. - Script `npm run mqtt:provision-device` menjadi jalur ringan untuk rotate credential dan menghasilkan command `mosquitto_passwd`. - UI device technical detail menyediakan rotate credential dan modal one-time secret untuk operator. - ACL Mosquitto per device masih perlu disinkronkan dari credential platform saat tahap broker provisioning berikutnya. - Status: Active ## D-029 — Device Auth Per-Credential dengan Fallback Dev Token - Tanggal: 2026-05-28 - Keputusan: - Endpoint `/device/*` menerima autentikasi per-device melalui header `X-Device-Id` dan `X-Device-Secret`. - Secret device diverifikasi memakai fingerprint credential yang tersimpan di database; plaintext secret tidak disimpan. - Credential device hanya boleh mengakses resource untuk `device_id` yang sama dengan `X-Device-Id`. - `Authorization: Bearer DEVICE_TOKEN` tetap diterima sebagai fallback/dev compatibility selama transisi. - Alasan: - Credential MQTT yang sudah diprovision perlu menjadi identitas device yang sama untuk API platform. - Token global device terlalu luas untuk production karena satu token bocor bisa mengakses semua device. - Fallback dev token menjaga smoke test, simulator, dan tooling lama tetap berjalan saat migrasi bertahap. - Dampak / implikasi: - Device production sebaiknya mulai mengirim `X-Device-Id` dan `X-Device-Secret`. - API menolak credential valid yang mencoba mengirim payload untuk device lain dengan `403 FORBIDDEN`. - Production dapat set `DEVICE_AUTH_ALLOW_LEGACY_TOKEN=false` untuk mematikan fallback `DEVICE_TOKEN`. - Status: Active ## D-030 — Settlement Batch Light dari Merchant Payable - Tanggal: 2026-05-28 - Keputusan: - Settlement awal dibuat sebagai batch dari ledger entry `merchant_payable` yang belum pernah masuk batch. - Batch dikelompokkan per merchant dan menyimpan `gross_amount`, `platform_fee_amount`, `net_payable_amount`, `entry_count`, `cutoff_at`, dan status. - Status awal batch adalah `created`; admin dapat menandai batch menjadi `paid`. - Settlement light tidak memindahkan dana dan tidak mengelola rekening escrow; batch adalah rekonsiliasi/payable visibility. - Alasan: - Finance light sudah menghasilkan merchant payable, sehingga admin membutuhkan unit batch untuk proses settlement manual/merchant-direct. - Model ini tetap cocok dengan keputusan payout merchant-direct, karena batch adalah daftar kewajiban bayar, bukan instruksi transfer otomatis. - Dampak / implikasi: - Ledger entry hanya boleh masuk satu batch lewat unique key `settlement_batch_entries.ledger_entry_id`. - Batch berikutnya hanya mengambil payable yang belum dibatch. - Endpoint baru: `POST /admin/settlement-batches`, `GET /admin/settlement-batches`, `GET /admin/settlement-batches/{batchId}`, `GET /admin/settlement-batches/{batchId}/export.csv`, dan `POST /admin/settlement-batches/{batchId}/mark-paid`. - UI settlement batch management membaca batch real, menampilkan KPI, membuka detail drawer, generate batch, mark paid, dan download CSV payout report. - Tahap berikutnya bisa menambahkan format bank-specific payout file jika dibutuhkan. - Status: Active ## D-031 — Dashboard Settlement / Finance Summary - Tanggal: 2026-05-28 - Keputusan: - Admin dashboard overview menampilkan ringkasan settlement dan finance dari data batch real. - Summary backend `/admin/dashboard/summary` memuat pending payout, paid payout, total platform fee, jumlah batch open/paid/total. - Dashboard menampilkan panel "Settlement Finance" dengan batch terbaru dan tombol download CSV payout report per batch. - Alasan: - Setelah settlement batch light aktif, operator perlu melihat exposure payable dan fee tanpa harus masuk dulu ke halaman settlement detail. - Summary di dashboard membantu sanity check finance harian sebelum payout manual/merchant-direct dijalankan. - Dampak / implikasi: - Angka dashboard saat ini bersumber dari maksimal 500 batch terbaru, cukup untuk fase awal dan smoke/manual ops ringan. - Untuk volume besar, summary finance perlu dipindah ke query agregasi khusus atau materialized summary agar tidak bergantung pada limit list batch. - Status: Active ## D-032 — Settlement Mark Paid Membutuhkan Bukti Payout - Tanggal: 2026-05-28 - Keputusan: - UI settlement tidak lagi menutup batch dengan satu klik langsung; operator harus mengisi `paid_at`, reference payout, dan note rekonsiliasi. - Endpoint `POST /admin/settlement-batches/{batchId}/mark-paid` menerima `paid_reference` dan `paid_note`, lalu menyimpannya di `metadata_json`. - Audit log tetap menyimpan before/after batch agar perubahan status dan bukti payout dapat ditelusuri. - Alasan: - Mark paid adalah aksi finansial operasional, sehingga perlu bukti transfer/reference sebelum status batch ditutup. - Menyimpan bukti di metadata menjaga schema tetap ringan pada fase awal sambil mempertahankan traceability. - Dampak / implikasi: - UI mewajibkan reference payout, tetapi API masih kompatibel dengan payload lama untuk tooling internal. - Jika workflow payout makin formal, field bukti payout dapat dipromosikan menjadi kolom dedicated atau tabel payout events. - Status: Active ## D-033 — Dashboard Finance Summary Menggunakan Agregasi DB - Tanggal: 2026-05-28 - Keputusan: - Summary settlement/finance untuk admin dashboard dihitung lewat query agregasi langsung ke `settlement_batches`. - Endpoint `/admin/dashboard/summary` tidak lagi menghitung total finance dari list batch yang dibatasi pagination. - Alasan: - Angka payable, paid payout, fee, dan jumlah batch harus mewakili seluruh data, bukan hanya batch terbaru. - Jalur agregasi khusus lebih siap untuk volume data yang bertambah tanpa mengubah kontrak UI dashboard. - Dampak / implikasi: - UI dashboard tetap memakai kontrak field yang sama. - Jika volume settlement makin besar, agregasi ini bisa ditingkatkan menjadi materialized summary tanpa mengubah respons API. - Status: Active ## D-034 — Merchant Portal Settlement View - Tanggal: 2026-05-28 - Keputusan: - Merchant portal memiliki endpoint settlement merchant-scoped untuk profile, summary payout, daftar batch, detail batch, dan export CSV. - Merchant login fase awal memakai token dev `MERCHANT_TOKEN` dan password portal sederhana `MERCHANT_PORTAL_PASSWORD`. - UI `merchant-settlement-history` membaca data settlement real sesuai merchant yang login. - Alasan: - Settlement tidak hanya perlu visible untuk admin; merchant juga perlu transparansi pending payout, paid payout, detail batch, dan report. - Merchant-scoped endpoint mencegah UI merchant memakai API admin atau melihat batch merchant lain. - Dampak / implikasi: - Auth merchant saat ini masih ringan untuk fase development; production perlu diganti ke user/session auth merchant yang sebenarnya. - Kontrak endpoint sudah dipisah dari admin sehingga hardening auth bisa dilakukan tanpa mengubah UI settlement merchant secara besar. - Status: Active ## D-035 — Payout Event History - Tanggal: 2026-05-28 - Keputusan: - Settlement batch memiliki event history formal di tabel `settlement_batch_events`. - Event awal yang dicatat: `created`, `csv_exported`, dan `marked_paid`. - Admin dan merchant detail settlement membaca event history yang sama. - Alasan: - Metadata dan audit log cukup untuk jejak internal, tetapi operator dan merchant membutuhkan timeline payout yang mudah dibaca. - Event history menjadi fondasi untuk workflow berikutnya seperti failed/cancelled payout, reference update, dan dispute. - Dampak / implikasi: - Export CSV dari admin maupun merchant menambah event `csv_exported`. - Event history tidak menggantikan audit log; audit log tetap dipakai untuk compliance internal admin. - Status: Active ## D-036 — Settlement Failed dan Cancel Workflow - Tanggal: 2026-05-28 - Keputusan: - Batch settlement berstatus `created` dapat ditandai `failed` atau `cancelled` oleh admin dengan reason wajib. - Endpoint baru: `POST /admin/settlement-batches/{batchId}/mark-failed` dan `POST /admin/settlement-batches/{batchId}/cancel`. - Reason disimpan ke `failure_reason`, detail resolusi disimpan ke `metadata_json`, dan event `failed`/`cancelled` dicatat di payout event history. - Alasan: - Workflow payout operasional perlu menangani transfer gagal dan batch yang dibatalkan sebelum dibayar. - Reason wajib menjaga keputusan finansial tetap dapat ditelusuri oleh operator dan merchant. - Dampak / implikasi: - Batch failed/cancelled menjadi final state, tetapi dapat direprocess lewat aturan D-037. - Status: Active ## D-037 — Reprocess Settlement Failed/Cancelled - Tanggal: 2026-05-28 - Keputusan: - Batch settlement berstatus `failed` atau `cancelled` dapat direprocess menjadi batch baru berstatus `created`. - Reprocess memindahkan `settlement_batch_entries` dari batch lama ke batch baru, karena ledger entry hanya boleh aktif pada satu batch. - Batch lama tetap menjadi arsip final dan menyimpan `reprocessed_to_batch_id` di `metadata_json`. - Endpoint baru: `POST /admin/settlement-batches/{batchId}/reprocess`. - Alasan: - Payable dari payout gagal/cancel perlu dapat diproses ulang tanpa membuat ledger entry duplikat. - Memindahkan entry menjaga constraint unik `settlement_batch_entries.ledger_entry_id` sekaligus membuat batch baru siap mengikuti workflow normal. - Dampak / implikasi: - Detail report batch lama setelah reprocess tidak lagi menampilkan entry karena entry dipindah ke batch baru; angka aggregate batch lama tetap tersimpan sebagai arsip. - Reprocess hanya boleh satu kali per batch lama untuk menghindari batch duplikat. - Event history mencatat `reprocessed` pada batch lama dan `created` pada batch baru. - Status: Active ## D-038 — Settlement Reconciliation Mismatch Report - Tanggal: 2026-05-28 - Keputusan: - Admin memiliki report reconciliation untuk membandingkan aggregate settlement batch dengan ledger dan transaksi aktual. - Endpoint baru: `GET /admin/reconciliation/settlement-batches`. - UI `admin-reconciliation-management` membaca data real dari endpoint tersebut untuk KPI matched, discrepancy, issue count, raw payload, dan tabel batch. - Batch arsip yang sudah direprocess tidak dihitung sebagai mismatch palsu karena entry settlement-nya memang dipindah ke batch baru. - Alasan: - Operator finance butuh cara cepat melihat batch yang angka gross, fee, net payable, entry count, atau status transaksinya tidak sinkron. - Report ini menjadi sanity layer sebelum workflow koreksi payout/reference yang lebih detail. - Dampak / implikasi: - Report masih internal-system reconciliation, belum mencocokkan bank statement eksternal. - Smoke test memverifikasi endpoint report mengembalikan aggregate dan rows. - Status: Active ## D-039 — Settlement Payout Reference Update - Tanggal: 2026-05-28 - Keputusan: - Admin dapat memperbarui `paid_reference` dan `paid_note` untuk settlement batch yang sudah berstatus `paid`. - Endpoint baru: `PATCH /admin/settlement-batches/{batchId}/reference`. - Update reference menyimpan nilai terbaru di `metadata_json`, mencatat event `reference_updated`, dan membuat audit log before/after. - UI settlement batch management menampilkan form update reference hanya untuk batch `paid`. - Alasan: - Bukti payout manual bisa salah input atau perlu dilengkapi setelah rekonsiliasi. - Perubahan bukti payout harus traceable tanpa membuka ulang status finansial batch. - Dampak / implikasi: - Reference update tidak mengubah status, nominal, entry, atau paid_at batch. - Batch yang belum paid, failed, atau cancelled tidak dapat mengubah payout reference lewat endpoint ini. - Status: Active ## D-040 — Bank Generic Payout Export - Tanggal: 2026-05-28 - Keputusan: - Endpoint export settlement mendukung format tambahan `bank_generic` lewat query `?format=bank_generic`. - Format default `standard` tetap menjadi payout report detail per transaksi agar kompatibel dengan UI dan tooling yang sudah ada. - Format `bank_generic` menghasilkan satu baris payout per batch berisi beneficiary account, account type, beneficiary name, amount, currency, remark, batch code, dan transaction count. - Admin dan merchant export memakai pilihan format yang sama; UI admin settlement menyediakan pilihan General CSV atau Bank Upload. - Alasan: - Operasional finance membutuhkan file yang lebih dekat ke pola upload bank/payment rail, bukan hanya report detail transaksi. - Format generik memberi baseline sebelum membuat template bank spesifik seperti BCA, Mandiri, BRI, atau payment gateway tertentu. - Dampak / implikasi: - `settlement_account_reference` dan `settlement_account_type` merchant menjadi sumber destination account pada file bank generic. - Event `csv_exported` menyimpan format dan filename untuk jejak audit. - Format bank spesifik berikutnya dapat ditambahkan sebagai nilai `format` baru tanpa mengubah endpoint. - Status: Active ## D-041 — Settlement Dispute/Adjustment Record - Tanggal: 2026-05-28 - Keputusan: - Admin dapat mencatat adjustment/dispute ringan pada settlement batch lewat endpoint `POST /admin/settlement-batches/{batchId}/adjustments`. - Adjustment disimpan di `metadata_json.adjustments` dengan tipe `credit` atau `debit`, nominal absolut, signed amount, reason, note, actor, dan timestamp. - Total signed adjustment disimpan di `metadata_json.total_adjustment_amount`. - Setiap adjustment mencatat event `adjustment_recorded` dan audit log before/after. - UI settlement batch management menyediakan form Record Adjustment dan menampilkan total adjustment. - Alasan: - Setelah reconciliation, operator perlu mencatat koreksi/selisih payout tanpa langsung mengubah ledger historis. - Event dan metadata cukup untuk fase awal dispute tracking sebelum dibuat ledger adjustment formal. - Dampak / implikasi: - Adjustment tidak mengubah `gross_amount`, `platform_fee_amount`, `net_payable_amount`, status batch, atau ledger entries. - Batch arsip yang sudah direprocess tidak dapat menerima adjustment baru agar arsip tetap final. - Tahap berikutnya dapat mempromosikan adjustment menjadi tabel/ledger entry khusus jika proses finance membutuhkannya. - Status: Active ## D-042 — Settlement Adjustment Summary di Finance Dashboard - Tanggal: 2026-05-28 - Keputusan: - Summary finance settlement menghitung `adjustment_amount` dari `metadata_json.total_adjustment_amount` setiap batch. - Endpoint `/admin/dashboard/summary` menambahkan `settlement_adjustment_amount` dan `settlement_adjusted_paid_amount`. - UI admin dashboard menampilkan paid payout sebagai adjusted paid amount dan menampilkan adjustment sebagai angka terpisah. - UI settlement batch management menampilkan total adjustment pada KPI finance. - Alasan: - Setelah adjustment/dispute dicatat, operator perlu melihat dampaknya di ringkasan finance tanpa membuka batch satu per satu. - Paid payout asli tetap dibutuhkan sebagai angka batch historis, sementara adjusted paid memberi pandangan operasional setelah koreksi. - Dampak / implikasi: - Adjustment summary masih berbasis metadata, belum menjadi ledger formal. - `settlement_paid_amount` tetap nominal paid batch asli; `settlement_adjusted_paid_amount` adalah paid amount ditambah signed adjustment. - Status: Active ## D-043 — Merchant Settlement Adjustment Visibility - Tanggal: 2026-05-28 - Keputusan: - Merchant settlement summary memakai `adjusted_paid_amount` untuk total settled yang ditampilkan di portal merchant. - Portal merchant menampilkan `adjustment_amount` sebagai angka terpisah agar koreksi payout tetap transparan. - Drawer settlement merchant menampilkan adjustment/refund dan total disbursed setelah adjustment. - Alasan: - Merchant perlu melihat dampak koreksi payout yang sama dengan admin finance, bukan hanya nominal batch asli. - Menampilkan adjustment terpisah menjaga transparansi tanpa mencampurkan koreksi dengan gross/net historis. - Dampak / implikasi: - Backend merchant sudah memakai summary yang sama dari `getSettlementFinanceSummary`. - Smoke test memverifikasi `adjustment_amount` dan `adjusted_paid_amount` tersedia untuk merchant. - Status: Active ## D-044 — Settlement Adjustment Formal Table - Tanggal: 2026-05-28 - Keputusan: - Adjustment settlement dipromosikan dari metadata-only menjadi tabel formal `settlement_batch_adjustments`. - Endpoint `POST /admin/settlement-batches/{batchId}/adjustments` menulis row adjustment formal, lalu mensinkronkan ringkasan kompatibilitas ke `settlement_batches.metadata_json`. - Detail batch admin dan merchant mengembalikan array `adjustments`. - Summary finance menghitung adjustment dari tabel formal, bukan dari metadata. - Alasan: - Adjustment/dispute adalah data finance auditable dan perlu dapat di-query tanpa parsing metadata batch. - Metadata tetap dipertahankan sebagai ringkasan agar UI dan payload lama tidak langsung patah. - Dampak / implikasi: - `metadata_json.adjustments` bukan sumber utama lagi; sumber utama adalah `settlement_batch_adjustments`. - UI admin dan merchant membaca adjustment formal dari detail response saat tersedia, dengan fallback metadata. - Jika ada data metadata-only lama di environment yang sudah berjalan, perlu migrasi backfill sebelum summary formal dianggap lengkap. - Status: Active ## D-045 — Backfill Adjustment Metadata ke Tabel Formal - Tanggal: 2026-05-28 - Keputusan: - Migrasi schema melakukan backfill idempotent dari `settlement_batches.metadata_json.adjustments` ke `settlement_batch_adjustments`. - Backfill memakai ID adjustment lama jika tersedia; jika tidak tersedia, ID dibuat deterministik dari batch dan urutan array. - Insert backfill memakai `ON CONFLICT (id) DO NOTHING` supaya aman dijalankan berulang. - Setelah backfill, `metadata_json.total_adjustment_amount` disinkronkan ulang dari tabel formal. - Alasan: - Ada kemungkinan environment yang sudah berjalan memiliki adjustment metadata-only dari D-041 sebelum tabel formal D-044. - Summary finance sekarang menghitung dari tabel formal, sehingga data lama perlu ikut masuk agar laporan tidak turun nilainya. - Dampak / implikasi: - Backfill menjaga kompatibilitas metadata, tetapi sumber utama tetap `settlement_batch_adjustments`. - Metadata lama yang tidak memiliki reason akan diberi reason default `Backfilled settlement adjustment`. - Status: Active ## D-046 — Settlement Adjustment Report - Tanggal: 2026-05-28 - Keputusan: - Admin memiliki endpoint `GET /admin/settlement-adjustments` untuk membaca adjustment settlement lintas batch. - Report mendukung filter `merchant_id`, `adjustment_type`, `from`, `to`, dan `limit`. - Payload report menyertakan total count, total credit, total debit, net signed adjustment, serta row dengan konteks batch. - UI `admin-reconciliation-management` menampilkan recent adjustment activity dari report formal. - Alasan: - Setelah adjustment menjadi tabel formal, finance perlu audit trail lintas batch tanpa membuka detail batch satu per satu. - Reconciliation dashboard adalah tempat natural untuk melihat koreksi payout terbaru. - Dampak / implikasi: - Summary report dihitung dari semua row yang sesuai filter, sedangkan daftar row tetap dibatasi limit untuk UI. - Report ini belum berupa export CSV khusus; export bisa ditambahkan jika finance membutuhkan lampiran audit. - Status: Active ## D-047 — Settlement Adjustment CSV Export - Tanggal: 2026-05-28 - Keputusan: - Admin memiliki endpoint `GET /admin/settlement-adjustments/export.csv`. - Export CSV memakai filter yang sama dengan report JSON: `merchant_id`, `adjustment_type`, `from`, `to`, dan `limit`. - CSV menyertakan blok summary, lalu daftar adjustment dengan konteks batch, actor, reason, note, dan nominal signed. - UI `admin-reconciliation-management` menyediakan tombol download CSV pada panel Recent Adjustment Activity. - Alasan: - Finance perlu lampiran audit adjustment lintas batch yang bisa disimpan atau dikirim di luar dashboard. - CSV lebih cepat dipakai untuk proses operasional awal dibanding PDF/reporting engine. - Dampak / implikasi: - Export dibatasi maksimal 500 row mengikuti batas report saat ini. - Jika volume adjustment besar, perlu pagination export atau async report job. - Status: Active ## D-048 — Settlement Adjustment Report Filter UI - Tanggal: 2026-05-28 - Keputusan: - UI `admin-reconciliation-management` menambahkan filter adjustment berdasarkan merchant ID, tipe `credit/debit`, tanggal awal, dan tanggal akhir. - Filter yang aktif dipakai untuk recent adjustment activity dan download CSV. - Tombol Clear mengembalikan report ke default terbaru. - Alasan: - Finance perlu menelusuri adjustment lintas batch berdasarkan merchant atau periode audit tertentu. - Export CSV harus konsisten dengan data yang sedang dilihat operator di dashboard. - Dampak / implikasi: - Filter merchant masih berupa input ID agar tidak menambah dependency daftar merchant pada panel reconciliation. - Jika kebutuhan operasional meningkat, filter merchant dapat dinaikkan menjadi searchable select. - Status: Active ## D-049 — Searchable Merchant Filter untuk Adjustment Report - Tanggal: 2026-05-28 - Keputusan: - Filter merchant pada adjustment report di UI reconciliation memakai daftar merchant dari endpoint admin. - Control memakai native datalist agar operator bisa mencari merchant tanpa dependency UI baru. - Input dapat menerima ID merchant langsung atau nilai yang cocok dengan kode, brand, atau legal name merchant. - Alasan: - Operator finance tidak seharusnya perlu copy-paste merchant ID manual untuk audit adjustment. - Native datalist cukup ringan untuk fase sekarang dan tetap kompatibel dengan filter API yang menerima `merchant_id`. - Dampak / implikasi: - Untuk ribuan merchant, datalist perlu diganti menjadi remote search/pagination. - Query backend tetap memakai merchant ID sehingga kontrak API tidak berubah. - Status: Active ## D-050 — Production Admin Session dan RBAC Baseline - Tanggal: 2026-05-29 - Keputusan: - Admin auth mendukung signed session token berbasis user/role selain legacy dev token. - Role awal disiapkan: admin, finance, ops, support, viewer. - Endpoint settlement sensitif dipagari permission granular seperti `settlement:pay`, `settlement:adjust`, `settlement:export`, dan `reconciliation:read`. - Legacy admin token dan login dev tetap default aktif untuk smoke/local, tetapi bisa dimatikan lewat `ADMIN_AUTH_ALLOW_LEGACY_TOKEN=false` dan `ADMIN_DEV_LOGIN_ENABLED=false`. - Alasan: - Production tidak boleh bergantung pada satu token admin global. - Finance/ops/support/viewer membutuhkan batas akses yang berbeda saat pilot makin dekat. - Dampak / implikasi: - Production wajib mengatur `ADMIN_SESSION_SECRET` kuat dan menonaktifkan login dev. - Smoke lama tetap kompatibel selama mode dev masih aktif. - Status: Active ## D-051 — MQTT Broker Observability dan Uplink Subscriber Opsional - Tanggal: 2026-05-29 - Keputusan: - MQTT publisher menyimpan status runtime: koneksi terakhir, disconnect terakhir, error terakhir, dan counter publish success/failure. - Service subscriber broker opsional dapat diaktifkan dengan `MQTT_SUBSCRIBE_ENABLED=true` untuk merekam uplink topic `devices/+/uplink/#` ke `mqtt_messages`. - Endpoint `/admin/mqtt/status` mengembalikan status publisher, subscriber, dan trace pesan terakhir. - Alasan: - Operator perlu melihat health broker dan jejak message tanpa masuk ke broker langsung. - Subscriber dibuat opsional agar local smoke tetap stabil tanpa dependency broker. - Dampak / implikasi: - Uplink dari broker saat ini direkam untuk observability; pemrosesan business flow device tetap memakai endpoint device yang sudah ada. - Untuk production broker, ACL Mosquitto tetap harus disiapkan sesuai panduan. - Status: Active ## D-052 — Approval Workflow untuk Settlement Adjustment - Tanggal: 2026-05-29 - Keputusan: - `settlement_batch_adjustments` memiliki `approval_status` (`pending`, `approved`, `rejected`) dan audit field approval/rejection. - Summary finance, metadata total adjustment, dan UI detail hanya menghitung adjustment `approved`. - Production dapat mewajibkan approval dengan `SETTLEMENT_ADJUSTMENT_REQUIRE_APPROVAL=true`. - Endpoint admin ditambahkan untuk approve/reject adjustment pending. - Alasan: - Adjustment payout adalah kontrol finance dan tidak boleh selalu final saat dicatat. - Local smoke perlu tetap kompatibel, sehingga default dev masih auto-approved. - Dampak / implikasi: - Merchant hanya melihat nominal adjustment final/approved dalam summary dan detail. - Finance dapat memfilter report adjustment berdasarkan `approval_status`. - Status: Active ## D-053 — Deployment Readiness Preflight - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan `npm run deploy:check-env` untuk memvalidasi env production kritikal. - Checklist production dituangkan di `DEPLOYMENT_READINESS.md`. - Alasan: - Banyak flag dev yang sengaja aktif untuk smoke/local dan harus eksplisit dimatikan sebelum production. - Dampak / implikasi: - Deploy candidate harus menjalankan typecheck, smoke e2e, dan env preflight. - Preflight sengaja gagal jika secret/default dev masih dipakai. - Status: Active ## D-054 — Admin User Bootstrap Script dan Finance Approval UI - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan script `npm run admin:create-user` untuk create/update user admin production dengan hash `scrypt`. - Script mendukung role `admin`, `finance`, `ops`, `support`, `viewer`, status inactive, dan password rotation eksplisit. - UI reconciliation menambahkan filter `approval_status` serta tombol approve/reject untuk adjustment pending. - Alasan: - RBAC production perlu cara operasional membuat user nyata tanpa edit database manual. - Approval workflow adjustment harus bisa dipakai finance dari UI, bukan hanya lewat API. - Dampak / implikasi: - Production bootstrap admin user bisa dilakukan sebelum `ADMIN_DEV_LOGIN_ENABLED=false`. - Finance dapat memproses pending adjustment dari panel Recent Adjustment Activity. - Status: Active ## D-055 — Merchant Session Auth dan Merchant User Bootstrap - Tanggal: 2026-05-29 - Keputusan: - Merchant portal mendukung signed session token scoped ke `merchant_id`. - Ditambahkan tabel `merchant_users` untuk login production berbasis email/password hash `scrypt`. - Ditambahkan script `npm run merchant:create-user` untuk create/update merchant portal user. - Login dev lama berbasis `MERCHANT_TOKEN` dan `MERCHANT_PORTAL_PASSWORD` tetap default aktif untuk local smoke, tetapi bisa dimatikan via `MERCHANT_AUTH_ALLOW_LEGACY_TOKEN=false` dan `MERCHANT_DEV_LOGIN_ENABLED=false`. - Alasan: - Merchant portal tidak boleh bergantung pada satu password/token global saat production. - Setiap merchant membutuhkan user yang jelas dan token yang tidak bisa dipakai lintas merchant. - Dampak / implikasi: - Production wajib membuat merchant users sebelum mematikan dev login. - UI merchant tetap kompatibel dengan response profile lama dan baru. - Status: Active ## D-056 — Versioned Migration Runner - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan command `npm run db:migrate`. - Migration runner memakai tabel `schema_migrations` dan advisory lock Postgres untuk mencegah dua proses migrasi bersamaan. - Migration file dibaca dari folder `migrations/` dengan format urut `NNN_description.sql` atau `NNN_description.mts`. - Migration awal `001_current_schema_bootstrap.mts` menjalankan schema bootstrap existing sebagai baseline. - Alasan: - Schema sudah besar dan production deploy membutuhkan riwayat migration yang eksplisit. - Baseline menjaga kompatibilitas dengan schema bootstrap yang sudah ada tanpa menggandakan SQL besar. - Dampak / implikasi: - Deploy candidate harus menjalankan `npm run db:migrate` sebelum start service. - Migration berikutnya sebaiknya dibuat sebagai file terpisah, bukan menambah perubahan besar langsung ke bootstrap. - Status: Active ## D-057 — Production Structured Logging dan Observability Summary - Tanggal: 2026-05-29 - Keputusan: - Request logging memakai middleware internal dengan structured fields: `request_id`, `trace_id`, method, path, status, latency, user-agent, dan IP. - `LOG_FORMAT=json` tersedia untuk production stdout/stderr log collector; default local tetap readable. - Error middleware menulis structured API/unhandled error log. - Health endpoint ditambah: `GET /health/deep` dan `GET /admin/health/deep`. - Admin endpoint `GET /admin/observability/summary` merangkum DB, MQTT, notification failure/pending, dan settlement reconciliation mismatch. - Alasan: - Pilot production membutuhkan log yang bisa dicari berdasarkan request/trace dan indikator operasional cepat. - Mengurangi dependency pada log format dev dan console manual. - Dampak / implikasi: - `morgan` dihapus karena digantikan request logging internal. - Production sebaiknya set `LOG_FORMAT=json` dan mengirim stdout/stderr ke log collector. - Status: Active ## D-058 — Initial Load Test Harness - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan script `npm run load:test`. - Harness menguji transaction create, QRIS paid callback, device heartbeat, dynamic QR API, dan observability summary dengan concurrency configurable. - Output berupa JSON summary berisi total success/error, durasi total, throughput perkiraan, serta p50/p95/max per label. - Data merchant/transaksi load test dibersihkan setelah run. - Alasan: - Setelah structured logging dan observability tersedia, perlu baseline performa awal sebelum pilot. - Script ringan cukup untuk menemukan bottleneck awal tanpa memasang load testing framework eksternal. - Dampak / implikasi: - Angka baseline lokal pertama: 190 request, 0 error, durasi 1202.49 ms, throughput perkiraan 158.01 req/s. - p95 lokal: transaction create 119.54 ms, callback paid 119.30 ms, heartbeat 45.93 ms, dynamic QR 41.75 ms, observability summary 69.63 ms. - Angka ini bukan kapasitas production final; perlu run ulang di environment target dan dengan data lebih besar. - Status: Active ## D-059 — Admin UI RBAC Awareness - Tanggal: 2026-05-29 - Keputusan: - Admin UI shared helper menyimpan profile admin, membaca `GET /admin/me`, dan menyediakan helper permission untuk elemen `data-admin-permission`. - Settlement batch UI dan reconciliation UI menyembunyikan/disable aksi finance sesuai permission seperti `settlement:write`, `settlement:pay`, `settlement:adjust`, dan `settlement:export`. - Alasan: - RBAC backend sudah aktif, tetapi UI perlu memberi sinyal operasional yang jelas sebelum user menekan aksi yang akan ditolak API. - Dampak / implikasi: - UI menjadi role-aware tanpa mengganti enforcement backend. - API tetap menjadi sumber otorisasi final. - Status: Active ## D-060 — Async Settlement Adjustment Export Jobs - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan tabel `export_jobs`, migration `002_export_jobs.sql`, dan store `exportJobStore`. - Ditambahkan endpoint `POST /admin/exports/settlement-adjustments`, `GET /admin/exports/:jobId`, dan `GET /admin/exports/:jobId/download`. - Job export saat ini dieksekusi segera di request lifecycle sebagai skeleton async, tetapi contract status/result/download sudah siap dipisah ke worker. - Alasan: - Export report adjustment bisa membesar dan perlu contract job sebelum dipindah ke background worker production. - Dampak / implikasi: - UI/API consumer bisa mulai memakai pola job polling. - Tahap berikutnya adalah worker queue dan storage object jika volume export besar. - Status: Active ## D-061 — Load Test Level 2 dan Audit Cleanup - Tanggal: 2026-05-29 - Keputusan: - Load test lokal level 2 dijalankan dengan 300 callback, 600 heartbeat, 300 dynamic QR, 100 observability read, concurrency 25. - Dependency `uuid` dihapus karena tidak dipakai source dan menjadi sumber audit moderate. - `npm audit` sekarang menghasilkan 0 vulnerability. - Alasan: - Pilot membutuhkan baseline lebih besar dari smoke awal dan dependency audit bersih. - Dampak / implikasi: - Hasil lokal level 2: 1610 request, 0 error, durasi 4955.45 ms, throughput perkiraan 324.9 req/s. - p95 lokal: transaction create 183.56 ms, callback paid 129.03 ms, heartbeat 90.4 ms, dynamic QR 71.57 ms, observability summary 230.96 ms. - Angka ini tetap baseline lokal, bukan kapasitas production final. - Status: Active ## D-062 — Real MQTT Broker Smoke Validation - Tanggal: 2026-05-29 - Keputusan: - `npm run smoke:mqtt-real` dijalankan terhadap broker `mqtts://broker.bizone.id:8883`. - Test memverifikasi broker connect, subscribe `devices/+/downlink/#`, payment success downlink, config push, dan dynamic QR response. - Alasan: - MQTT broker production-like harus divalidasi terpisah dari simulator sebelum pilot device real. - Dampak / implikasi: - Smoke broker real terakhir lulus dengan 3 message diterima dan cleanup data smoke berhasil. - Smoke e2e utama tetap memakai simulator agar CI/local tidak bergantung broker eksternal. - Status: Active ## D-063 — Merchant Portal Session Polish - Tanggal: 2026-05-29 - Keputusan: - Merchant API helper menyimpan user session dan auth mode. - Merchant settlement UI menampilkan nama/session role user serta menyediakan tombol logout yang membersihkan session lokal. - Alasan: - Setelah merchant session auth production tersedia, portal perlu feedback login yang lebih jelas dan alur keluar yang eksplisit. - Dampak / implikasi: - Merchant operator bisa melihat identitas session aktif dan logout tanpa menghapus storage manual. - Status: Active ## D-064 — Export Job Worker Productionization - Tanggal: 2026-05-29 - Keputusan: - Export adjustment settlement diproses oleh worker internal `exportJobWorker`, bukan lagi langsung di request lifecycle. - Endpoint create job mengembalikan job `pending`; worker melakukan atomic claim ke status `running`, lalu menyelesaikan ke `completed` atau `failed`. - Worker memiliki konfigurasi `EXPORT_WORKER_ENABLED`, `EXPORT_WORKER_INTERVAL_MS`, `EXPORT_WORKER_BATCH_SIZE`, `EXPORT_JOB_STALE_RUNNING_MS`, dan `EXPORT_SETTLEMENT_ADJUSTMENT_MAX_ROWS`. - `/admin/observability/summary` menampilkan status worker dan count job per status. - Alasan: - Export besar tidak boleh menahan request admin dan perlu pola polling yang production-friendly. - Stale running job perlu bisa di-reset setelah restart/crash worker. - Dampak / implikasi: - Client harus polling `GET /admin/exports/:jobId` sampai status `completed` sebelum download. - Hasil export saat ini masih disimpan di DB `result_body`; tahap berikutnya adalah storage eksternal/object storage jika ukuran file membesar. - Status: Active ## D-065 — Reconciliation UI Async Export Polling - Tanggal: 2026-05-29 - Keputusan: - UI `admin-reconciliation-management` mengganti download adjustment CSV sinkron menjadi alur async export job. - Tombol export membuat job via `POST /admin/exports/settlement-adjustments`, menampilkan status job, polling `GET /admin/exports/:jobId`, lalu download dari endpoint job saat `completed`. - Shared admin API helper menambahkan method create/get/download export job. - Alasan: - Finance user perlu UX yang selaras dengan worker export agar report besar tidak bergantung request CSV sinkron. - Dampak / implikasi: - Export lama `GET /admin/settlement-adjustments/export.csv` masih ada untuk kompatibilitas, tetapi UI reconciliation memakai job async. - Tahap berikutnya adalah tampilan riwayat job export jika finance perlu mengambil ulang file lama. - Status: Active ## D-066 — Export File Storage, Retention, dan Job History - Tanggal: 2026-05-29 - Keputusan: - Hasil export baru disimpan sebagai file di `EXPORT_STORAGE_DIR`, bukan `result_body` database. - Metadata `result_storage_path`, `result_size_bytes`, dan `expires_at` ditambahkan melalui migration `003_export_job_storage.sql`. - Worker membersihkan hasil export expired berdasarkan `EXPORT_RETENTION_DAYS`. - Endpoint `GET /admin/exports` ditambahkan untuk history job, dan UI reconciliation menampilkan lima job export adjustment terbaru. - Alasan: - File CSV besar tidak ideal disimpan sebagai body di database. - Finance membutuhkan visibility status/download ulang job export terbaru. - Dampak / implikasi: - Directory `EXPORT_STORAGE_DIR` harus writable dan masuk strategi backup/retention. - Export lama yang masih punya `result_body` tetap bisa didownload sebagai fallback. - Status: Active ## D-067 — MQTT ACL Hardening Helper - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan script `npm run mqtt:check-acl` untuk print template dan validasi file ACL Mosquitto. - Provisioning device sekarang menampilkan topic scope per device dan command validasi ACL. - Alasan: - Pilot hardware real butuh guard agar device hanya publish/subscribe topic miliknya sendiri. - Dampak / implikasi: - Production preflight harus menjalankan validasi ACL setelah perubahan file broker. - Username MQTT device tetap harus sama dengan `device_id`. - Status: Active ## D-068 — Backup/Restore Readiness Scripts - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan `npm run backup:production` untuk dump Postgres dan opsional copy Mosquitto passwd/ACL. - Ditambahkan `npm run restore:plan` yang default hanya mencetak command restore dan hanya execute jika diberi `--execute`. - Alasan: - Pilot production membutuhkan backup yang bisa dijalankan dan restore drill yang tidak mudah terpanggil destruktif tanpa sadar. - Dampak / implikasi: - Restore harus diuji pada database disposable sebelum pilot live. - Backup directory dan file Mosquitto perlu masuk retention/secure storage operational. - Status: Active ## D-069 — Staging Load Test Profile - Tanggal: 2026-05-29 - Keputusan: - Load test mendapat skenario async export adjustment via `LOAD_EXPORTS`. - Ditambahkan script `npm run load:test:staging` dengan profile lebih besar: callback, heartbeat, dynamic QR, observability read, dan export job. - Alasan: - Baseline lokal belum mencakup worker export dan belum cukup mewakili staging/production-like environment. - Dampak / implikasi: - Jalankan profile ini terhadap staging dengan `BASE_URL` dan secret/token staging. - Angka hasil staging harus dicatat terpisah dari baseline lokal. - Status: Active ## D-070 — MQTT ACL Smoke Test untuk Pilot Device - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan `npm run smoke:mqtt-acl` untuk validasi credential device terhadap ACL broker. - Smoke memastikan device A bisa akses topic miliknya dan ditolak saat subscribe ke downlink device B. - Alasan: - File ACL yang benar secara template belum cukup; perlu validasi runtime sebelum device fisik pilot. - Dampak / implikasi: - Smoke membutuhkan dua username device test yang sudah ada di broker. - Jalankan setelah provisioning credential device dan reload Mosquitto. - Status: Active ## D-071 — Restore Drill Validation - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan `npm run restore:validate`. - Validator menjalankan migration idempotent, cek `/health`, `/admin/health/deep`, dan memastikan tabel kunci tersedia. - Alasan: - Restore drill harus berakhir dengan bukti service bisa start dan schema masih konsisten. - Dampak / implikasi: - Jalankan terhadap service/database restore disposable, bukan production live. - `RESTORE_DRILL_RUN_MIGRATE=false` tersedia jika migration ingin dijalankan manual. - Status: Active ## D-072 — UI QA Lightweight Gate - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan `npm run ui:qa`. - Checker memvalidasi inline script halaman operasional, async export reconciliation, dan permission-aware settlement actions. - Placeholder navigation di halaman operasional utama dibersihkan dan kini menjadi failure jika muncul lagi. - Alasan: - Perubahan UI HTML inline rawan regresi sintaks yang tidak tertangkap TypeScript. - Dampak / implikasi: - UI QA ringan menjadi gate untuk mencegah placeholder nav kembali di halaman operasional utama. - Status: Active ## D-073 — Staging Load Report Artifact - Tanggal: 2026-05-29 - Keputusan: - `scripts/load-test.mjs` dapat menulis JSON summary ke `LOAD_REPORT_FILE`. - `npm run load:test:staging` sekarang memakai wrapper yang otomatis menyimpan report ke `reports/`. - Alasan: - Staging baseline perlu artifact yang bisa disimpan di handoff/release note, bukan hanya terminal output. - Dampak / implikasi: - Folder `reports/` berisi hasil run staging dan dapat diarsipkan oleh operator. - Status: Active ## D-074 — Strict Production Security Preflight - Tanggal: 2026-05-29 - Keputusan: - `npm run deploy:check-env` sekarang gagal jika legacy admin/merchant/device auth masih aktif. - Production preflight mewajibkan finance approval, export worker, export storage dir, retention positif, dan secret minimal 24 karakter. - Alasan: - Pilot live tidak boleh berjalan dengan fallback dev auth atau secret pendek. - Dampak / implikasi: - `.env` lokal dev memang akan gagal preflight production; validasi ini ditujukan untuk environment candidate. - Status: Active ## D-075 — Rate Limiting dan Request Security Polish - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan middleware rate limit in-memory dengan header `RateLimit-*` dan error `RATE_LIMITED`. - Rate limit dipasang untuk admin/merchant login, admin write routes, device routes, dan integration callback routes. - App menambahkan `JSON_BODY_LIMIT`, optional `TRUST_PROXY`, referrer policy, dan HSTS saat `NODE_ENV=production`. - Alasan: - Endpoint login, device, admin write, dan webhook/callback perlu guard dasar sebelum pilot publik. - Dampak / implikasi: - Default local dibuat cukup longgar agar smoke/load lokal tetap berjalan. - Production wajib `RATE_LIMIT_ENABLED=true` dan tuning limit sesuai trafik pilot. - Status: Active ## D-076 — Placeholder Navigation Cleanup - Tanggal: 2026-05-29 - Keputusan: - Placeholder `href="#"` dibersihkan dari halaman operasional utama: reconciliation, settlement batch, merchant settlement history, dan device technical detail. - Link diarahkan ke halaman UI nyata seperti dashboard, settlement, reconciliation, device detail, transaction history, hub, atau login. - Alasan: - Placeholder nav mengganggu manual UI QA dan bisa membingungkan operator pilot. - Dampak / implikasi: - `npm run ui:qa` sekarang hijau tanpa warning placeholder. - Beberapa target masih berupa halaman representatif sampai navigasi produk final dirapikan. - Status: Active ## D-077 — Login Audit dan Bootstrap Password Policy - Tanggal: 2026-05-29 - Keputusan: - Admin login success/failure dicatat ke audit log dengan action `admin.login.success` dan `admin.login.failed`. - Merchant login success/failure dicatat ke audit log dengan action `merchant.login.success` dan `merchant.login.failed`. - Script `admin:create-user` dan `merchant:create-user` memperketat password policy: minimal 14 karakter, lowercase, uppercase, angka, simbol, dan tanpa kata default/produk obvious. - Alasan: - Pilot production membutuhkan visibility login dan guard sederhana sebelum user bootstrap. - Dampak / implikasi: - Audit log dapat dipakai untuk review brute force/credential misuse. - Password bootstrap lama yang terlalu sederhana akan ditolak. - Status: Active ## D-078 — Operational Runbook dan Pilot Checklist - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan `OPERATIONAL_RUNBOOK.md` untuk SOP pre-deploy, deploy, smoke, backup, restore, rollback, dan incident response. - Ditambahkan `PILOT_EXECUTION_CHECKLIST.md` untuk go/no-go pilot. - Alasan: - Artefak operasional perlu eksplisit agar handover tidak bergantung pada chat atau ingatan developer. - Dampak / implikasi: - Operator punya satu dokumen SOP dan satu checklist eksekusi pilot yang bisa dicentang. - Status: Active ## D-079 — Export Storage Deployment Readiness - Tanggal: 2026-05-29 - Keputusan: - Ditambahkan `EXPORT_STORAGE_READINESS.md`. - Production preflight mewajibkan `EXPORT_STORAGE_DIR` absolute path. - Dokumen menjelaskan single-node pilot, multi-node shared storage, dan batasan sebelum object storage adapter tersedia. - Alasan: - Export sudah file-based, sehingga deployment multi-node perlu aturan storage yang jelas. - Dampak / implikasi: - Untuk multi-node, export directory harus shared atau download bisa gagal di node berbeda. - S3/object storage tetap menjadi peningkatan future jika skala membutuhkan. - Status: Active ## D-080 — Admin Audit Log UI Wiring - Tanggal: 2026-05-29 - Keputusan: - Halaman `admin-system-audit-logs` membaca data real dari `GET /admin/audit-logs`. - Backend audit log mendukung filter `action_contains` untuk preset login events lintas admin/merchant. - UI audit menambahkan filter action/entity/date, search client-side, KPI total/login failed/login success, dan drawer JSON payload. - Alasan: - Login audit sudah dicatat, sehingga operator membutuhkan view cepat untuk investigasi login failure/success. - Dampak / implikasi: - `npm run ui:qa` sekarang memasukkan halaman audit logs ke gate. - Audit UI bisa dipakai untuk review brute force dan credential misuse selama pilot. - Status: Active