Skip to content

Pengaduan Lahir Tanpa Jejak: Timeline yang Kosong Sejak Awal

Adityo Guni Waluyo

Timeline pengaduan kosong bukan bug tampilan. Baris history pertamanya memang tidak pernah di-INSERT, karena fungsi kembar CreateRegistration dan CreateReport

Ringkasan

Buka timeline pengaduan di dashboard admin kosong padahal laporan udah masuk, kirain query-nya error. Ternyata pas dicek DB emang baris awal history dari NULL ke pengajuan gak pernah dibikin, beda sama fitur pendaftaran yang udah bener. Akhirnya diperbaiki pakai transaksi biar laporan dan history kecreate barengan plus ditambahin test biar gak kejadian lagi.

Layar Kosong yang Arahannya Nggak Sederhana

Buka halaman detail pengaduan di dashboard admin, bagian timeline-nya kosong. Padahal laporannya udah masuk sistem. Tebakan pertama saya justru ke arah pembacaan data: join ke tabel history yang gagal, filter yang salah, atau semacamnya. Sampai-sampai saya buka network tab dulu buat mastiin responsnya memang array kosong, bukan error.

Responsnya beneran array kosong. Jadi saya buka database dan cek tabel application_status_history pakai ID laporan itu. Hasilnya nihil. Baris pertamanya memang tidak pernah ada. Data yang harusnya ada sejak detik pertama itu memang nggak pernah ditulis.

Di sini saya keliru satu langkah. Kebiasaan debugging nyeret saya ke sisi baca dulu (query, join, filter), padahal dilihat dari luar, pengaduan itu baru submit, belum ada aksi admin apa pun. Kalau timeline kosong untuk entitas yang baru lahir, patokan pertama justru cek sisi tulisnya. Baris hari-nol itu objek yang harusnya eksis.

Drift di Fungsi Kembar

Sumber masalahnya ada di repository layer layanan: ada dua fungsi yang seharusnya berperilaku mirip. CreateRegistration udah lama men-seed baris awal dari status NULL ke pengajuan. CreateReport nggak. Setiap submit pengaduan cuma INSERT baris laporan, tanpa jejak status awal.

Yang bikin ini nggak bisa dianggap keputusan desain: pesan commit perbaikannya menyebut "idiom sama dengan CreateRegistration". Artinya perilaku CreateRegistration-lah yang jadi acuan resmi, dan CreateReport luput. Ini wajah klasik drift diam-diam. Dua fungsi kembar kelihatan sejajar, padahal satu di antaranya ketinggalan perubahan yang lama dan lupa diterapkan ke kembarannya. Kalo dua fungsi kayak gini nggak dijaga test yang membandingkan, drift-nya cuma soal waktu.

OWASP menuliskan audit trail sebagai catatan kronologis yang memungkinkan rekonstruksi urutan kejadian sampai ke transaksi yang bisa diverifikasi [2]. Baris awal NULL ke pengajuan itu persis itu: tanpa baris itu, rekonstruksinya pincang sejak detik pertama. Laporan di status pengajuan, timeline kosong, dan siapa pun yang mengecek belakangan nggak bisa membedakan "belum ada aksi" dari "jejaknya nggak pernah ditulis".

Yang bikin kasus ini hidup selama ini: test penjaganya cuma nempel di satu sisi. File submit_history_test.go udah ada dan isinya ngurus history submit pendaftaran, termasuk mastiin bidang tak valid nggak meninggalkan baris. Jadi sisi pendaftaran dijaga, sisi pengaduan nggak punya satu pun assertion. Pegangan sederhana yang saya pegat sekarang: tiap kali nulis fungsi kedua yang kembar dengan yang udah ada, test kembarannya ikut disalin di file yang sama, diberi nama bersebelahan. Kalau nanti salah satu berubah, diff-nya kelihatan mana kembaran yang belum ikut.

Insert dalam Satu Transaksi

Fix-nya memindahkan CreateReport ke pola transaksional yang sama: buka transaksi, INSERT laporan, ambil LastInsertId, INSERT baris history awal dengan from_status NULL dan to_status pengajuan, baru commit. Dokumentasi Go merumuskan prinsipnya ringkas: semua operasi dalam transaksi harus sukses semua atau gagal semua, dan Tx.Commit yang sukses mengonfirmasi semua update sebagai satu perubahan atomik [3].

Ini yang bikin fix-nya beda dari sekadar nambah satu INSERT: baris history dijamin lahir bareng laporannya, atau nggak sama sekali. MySQL menyebut COMMIT membuat perubahan jadi permanen [1], dan defer Tx.Rollback di awal fungsi menutup transaksi rapi bila ada error sebelum commit. Tanpa transaksi, kombinasi gagal paling licik itu INSERT history yang gagal sementara laporan berhasil. Skenarionya balik ke kondisi semula, tapi sekarang terjadi diam-diam di setiap submit baru, dan kamu baru sadar berbulan-bulan kemudian pas audit.

Soal bukti, ada test regresi baru yang men-submit laporan lalu mastiin tepat satu baris history NULL ke pengajuan tercipta, dan halaman status publik lewat nomor tiket memuat riwayat yang sama. Developer yang kelak nggak sengaja hapus seeding-nya bakal digagalkan CI, bukan sekadar diingatkan di review. Semangatnya sama kayak artikel transisi status dan outbox dulu: perubahan penting ditulis dalam satu kesatuan atomik, bukan disebar di beberapa INSERT yang doakan berhasil.

Baris hari-nol nggak kelihatan di fitur mana pun, sampai kamu butuh dia. Sistem layanan publik hidup dari jejak yang bisa direkonstruksi, dan jejak yang baik dimulai sebelum aksi pertama: sejak baris pertama ditulis.

Sumber

Artikel terkait