Ketika Guard Status Dilepas: Satu Aturan Keras Pengganti Larangan
Pengalaman melepas guard forward-only pada state machine booking, dan mengapa satu aturan keras lebih efektif daripada larangan total.
Ringkasan
Gara-gara guard status cuma boleh maju, admin kesulitan mundurin tiket yang salah input, malah bikin tiket sampah buat akal-akalan. Solusinya simpel: guard dibuang, admin bebas ubah status ke mana aja lewat API yang sama. Tapi satu aturan keras tetap: kalau jadi ditolak, wajib isi catatan keputusan dan pemohon otomatis dikirim surel, jadi semua kegiatan tetap ke-audit.
Saya sedang memantau dashboard administrator sistem booking fasilitas di KotaPortal. Seorang operator perlu memundurkan status tiket dari "diproses" kembali ke "menunggu verifikasi" karena ada kesalahan entri data pada dokumen pendukung. Begitu dia coba ubah, sistem menolak dengan pesan error. State machine yang saya bangun sendiri hanya mengizinkan pergerakan maju.
Awalnya saya berasumsi guard ini harus tetap ada. Logikanya tampak masuk akal: guard status dirancang melindungi integritas data. Kalau pergerakan mundur diizinkan, khawatirnya riwayat bisa dimanipulasi atau data jadi inkonsisten. Saya mengira kekakuan ini harga yang wajar untuk keamanan, dan dengan membatasi pergerakan saya sudah menutup celah penyalahgunaan wewenang.
Ilusi Keamanan pada State Machine Kaku
Ternyata asumsi itu keliru. State machine yang terlalu ketat hanya memodelkan proses yang ideal. Realitas operasional penuh koreksi: keputusan yang dibuat mundur, dokumen hilang lalu diajukan ulang, tiket yang harus dibenahi karena salah input. Guard yang saya buat justru menghalangi pekerjaan sah para admin. Alih-alih menjaga keamanan, guard ini mendorong mereka mencari jalan pintas yang tidak rapi, misalnya membuat tiket baru hanya untuk "membatalkan" tiket lama, dan database pun penuh entri sampah.
Prinsip fail-safe defaults bilang kondisi default adalah ketiadaan akses, dan perlindungan hanya mengidentifikasi kondisi di mana akses diizinkan [1]. Pada dunia yang sama, jalur pengecualian (break-glass) tetap disediakan, tapi dengan syarat: harus terdokumentasi, memicu peringatan, dan menghasilkan catatan audit yang ditinjau setelah kejadian, "ideally a louder one" [1][4]. Prosedur break-glass di sistem klinis bekerja persis begitu; admin mencatat setiap pemakaian darurat, dan kontrol standar justru didesain untuk meminimalkan seberapa sering jalur itu dipakai [2].
Detail teknisnya sederhana tapi menentukan. Kontrak admin tidak berubah sama sekali: endpoint dan payload yang sama, kolom status yang sama. Yang pindah hanya letak keputusan: dari guard yang memblokir di awal, jadi validasi dan efek samping di akhir. Tiga kolom catatan sudah tersedia sejak lama di skema, satu untuk keputusan akhir dan dua untuk catatan proses, jadi tidak ada migrasi database. Pekerjaannya kemudian beres jadi tiga hal: hapus guard, tambahkan validasi wajib-isi, pastikan surel terkirim, lalu perbarui tes transisi yang dulu mengharuskan urutan maju.
Detail teknisnya sederhana tapi menentukan. Kontrak admin tidak berubah sama sekali: endpoint dan payload yang sama, kolom status yang sama. Yang pindah hanya letak keputusan: dari guard yang memblokir di awal, jadi validasi dan efek samping di akhir. Tiga kolom catatan sudah tersedia sejak lama di skema, satu untuk keputusan akhir dan dua untuk catatan proses, jadi tidak ada migrasi database. Pekerjaannya kemudian beres jadi tiga hal: hapus guard, tambahkan validasi wajib-isi, pastikan surel terkirim, lalu perbarui tes transisi yang dulu mengharuskan urutan maju.
Waktu saya lepas guard status dari lapisan layanan, sistem tidak kacau. Alur enam status yang ada tetap dipertahankan sebagai jalur yang direkomendasikan, tapi saya berhenti menegakkannya secara keras. Urutan dari SOP klien jadi petunjuk arah, bukan paksaan. Admin boleh menetapkan status mana pun kapan pun lewat antarmuka yang sama.
Satu Aturan Keras Menggantikan Larangan Total
Perubahan ini diterapkan di lapisan layanan tanpa mengubah kontrak API. Guard yang dulu memblokir pergerakan mundur dihapus. Tapi ada satu aturan keras yang tetap: kalau status diubah menjadi ditolak, sistem mewajibkan pengisian kolom decision_note dan otomatis mengirim surel notifikasi ke pemohon. Kolom catatan ini sudah ada di skema, jadi tidak perlu migrasi struktur. Validasi wajib-isi, pengiriman surel, dan revisi tes transisi status menyusul di pekerjaan yang sama.
Bentuk ini sejalan dengan standar fungsional HL7 EHR-S: sistem wajib menangkap metadata akses luar biasa, siapa-apa-kapan-di mana-kenapa, dan secara eksplisit mewajibkan sistem merekam alasan (rationale) di balik akses tersebut [3]. Alasan tertulis itulah yang menggantikan larangan.
Guard yang bertentangan dengan alur kerja nyata operator pada akhirnya diakali. Override yang mewajibkan alasan tertulis jauh lebih jujur daripada guard yang semua orang alih-alihkan. Keamanan tidak hilang; ia bergeser dari pencegahan buta menjadi akuntabilitas yang terukur.
Ada satu efek samping yang saya jadikan fitur: karena alur SOP tetap jadi jalur default di antarmuka, admin yang tidak punya alasan khusus tetap bergerak maju seperti biasa. Jalur penuh pagar itu tidak hilang; ia tinggal berhenti jadi penjaga dan mulai jadi penunjuk arah. Persis pesan dari sumber [2]: kontrol standar didesain supaya jalur darurat jarang terpakai, bukan supaya jalur darurat tidak ada.
Ada satu efek samping yang saya jadikan fitur: karena alur SOP tetap jadi jalur default di antarmuka, admin yang tidak punya alasan khusus tetap bergerak maju seperti biasa. Jalur penuh pagar itu tidak hilang; ia tinggal berhenti jadi penjaga dan mulai jadi penunjuk arah. Persis pesan dari sumber [2]: kontrol standar didesain supaya jalur darurat jarang terpakai, bukan supaya jalur darurat tidak ada.
Keputusan akhirnya: penolakan wajib beralasan tertulis, dan pemohon selalu dapat notifikasi. Satu aturan ini memikul seluruh beban keamanan. Setiap penyimpangan dari alur normal punya jejak yang bisa diaudit, tanpa mengorbankan kelancaran operasional harian di KotaPortal.
Sumber