Spec-Driven Development untuk Requirement ERP dengan AI Agent

Foto oleh Internet Archive Book Images via Wikimedia Commons (No restrictions)
Artinya menuliskan requirement sebagai aturan yang bisa diuji sebelum AI agent menulis kode, bukan menjelaskan requirement di dalam prompt. Untuk ERP, spec itu punya tiga bagian: satu acceptance criteria per aturan bisnis, satu tabel contoh berisi input nyata dan hasil yang diharapkan, serta catatan pertanyaan yang belum terjawab. Agent membangun mengikuti spec, dan spec itulah yang direview dan disimpan.
Tulis sedemikian rupa sehingga sebuah test bisa gagal hari ini dan lulus setelah aturannya diimplementasikan. Sebut nama orang, bukan nama jabatan, satu assertion per baris, dan sertakan jendela delegasi lengkap dengan tanggal mulai dan berakhir. Kalau assertion-nya tidak bisa dinyatakan dalam satu klausa, berarti itu dua aturan dan harus dipecah.
Karena paragraf tentang limit approval biasanya cuma diangguki saat review, sedangkan satu baris konkret pasti diperdebatkan, dan debat itulah tujuannya. Isi baris boundary lebih dulu: nominal yang persis menyentuh limit, approver yang ternyata pemohonnya sendiri, invoice bertanggal sama dengan hari period dikunci. Tabel berisi contoh tengah yang aman selesai direview cepat dan tidak membuktikan apa pun.
Tiap entri memuat pertanyaannya, nama orang yang bisa menjawab, tanggal pertanyaan diajukan, dan perilaku default yang akan dirilis kode kalau jawabannya tidak datang. Menyebut nama departemen alih-alih nama orang berarti tidak ada yang memilikinya. Bagian default paling penting, karena mengubah pertanyaan terbuka menjadi perilaku yang bisa dirilis plus risiko yang tercatat, dan test menegaskan default itu sehingga perubahannya jelas saat jawaban sebenarnya tiba.
Review spec-nya. Prompt dibuang begitu session berakhir, sedangkan spec dibaca berbulan-bulan kemudian oleh orang yang sedang mengaudit angka yang salah. Simpan file spec di dalam repository bersebelahan dengan modul yang diaturnya, supaya perubahan aturan bisnis muncul sebagai diff yang bisa Anda sodorkan ke orang finance.

Foto oleh Internet Archive Book Images via Wikimedia Commons (No restrictions)
Ringkasan Utama
Requirement ERP datang sebagai kumpulan edge case, bukan spesifikasi. Sebelum menulis prompt untuk agent, tulis satu acceptance criteria yang bisa diuji untuk tiap aturan, satu tabel contoh berisi baris boundary yang paling diperdebatkan finance, dan satu ambiguity log yang menyebut siapa pemilik tiap pertanyaan yang belum terjawab. Spec hidup lebih lama daripada prompt, jadi spec itulah yang direview.
Requirement itu datang sebagai tiga hal dalam satu sore: satu spreadsheet berisi purchase request yang salah rute approval, satu thread WhatsApp tempat branch manager menjelaskan mana yang sebenarnya tidak masalah, dan satu meeting saat finance bilang limitnya lima juta rupiah — kecuali di akhir bulan. Tidak satu pun dari itu adalah spesifikasi, dan prompt sebagus apa pun tidak akan mengubahnya menjadi spesifikasi.
Tulisan ini soal bagian dari spec-driven development yang terjadi sebelum agent dilibatkan sama sekali: mengubah bahan tadi menjadi acceptance criteria yang bisa membuat test gagal, tabel contoh yang mau ditandatangani finance, dan catatan pertanyaan yang tidak bisa dijawab oleh spec itu sendiri. Contohnya adalah aturan ERP yang paling sering jadi bahan debat saat review — hierarki approval dengan jalur delegasi, penguncian posting period, pembulatan pajak, dan alokasi landed cost.
Requirement ERP jarang tertulis, karena bisnisnya merasa sudah tahu. Aturannya hidup di dalam pengecualian, dan pengecualian baru muncul ke permukaan ketika ada dokumen yang salah rute. Yang sampai ke meja Anda hampir selalu salah satu dari empat bentuk berikut, dan tidak ada satu pun yang berupa aturan:
Keempatnya punya cacat yang sama: menjelaskan perilaku lewat contoh tanpa menyebut contoh mana yang jadi aturan dan mana yang pengecualian. Serahkan salah satunya ke agent, hasilnya akan terdengar masuk akal, karena masuk akal adalah satu-satunya hal yang bisa dihasilkan dari input seperti itu. Pekerjaannya bukan menulis prompt yang lebih baik, melainkan memutuskan secara tertulis, sebelum session dimulai, baris mana yang berlaku sebagai hukum.
Hanya ini filter yang saya pakai untuk setiap baris spec ERP: bisakah saya menulis test yang hari ini gagal dan nanti lulus ketika aturannya diimplementasikan? Kalau tidak bisa, baris itu adalah preferensi, harapan, atau ringkasan. Simpan di bagian background dan berhenti menyebutnya requirement. ISO/IEC/IEEE 29148, standar yang menggantikan IEEE 830 pada 2011, meminta hal yang sama dengan kosakatanya sendiri — tiap requirement harus necessary, appropriate, dan unambiguous.
# specs/purchasing/pr-approval.feature
# Wrong: nothing in this line can fail, so it is not a rule yet.
# Then high-value requests should be approved faster
# Right: one rule, one assertion per line, real people, a dated delegation.
Rule: A purchase request above the branch limit needs finance approval
Background:
Given branch JKT-01 has an approval limit of 5000000 IDR
And Rina is the finance head for JKT-01
And Rina has delegated approval to Budi from 2026-09-14 to 2026-09-21
Scenario Outline: Routing by amount, delegation and self-approval
Given a purchase request for <amount> IDR raised by <requester>
When the department manager approves it
Then the next approver is <next_approver>
And the audit trail records <trail>
Examples:
| amount | requester | next_approver | trail |
| 4500000 | Sari | none | manager only |
| 5000000 | Sari | none | manager only, inclusive |
| 40000000 | Sari | Budi | delegated from Rina |
| 40000000 | Budi | Finance Director | delegate raised it |Gherkin bukan intinya di sini; bentuknya yang penting. Satu aturan per blok, satu assertion per baris, dan nama orang, bukan nama jabatan. Menulis bahwa Rina mendelegasikan approval ke Budi dari 14 sampai 21 September memaksa jendela delegasi masuk ke dalam spec, dan justru jendela itulah yang paling sering dilupakan implementasi: delegasi tanpa tanggal berakhir berarti satu approver tambahan yang permanen dan tidak pernah diaudit sampai ada auditor bertanya siapa yang menandatangani.
Tulis assertion-nya dulu, baru teks aturannya. Kalau assertion itu tidak bisa dinyatakan dalam satu klausa, berarti aturannya ada dua — pecah. Limit approval dan pembekuan akhir bulan terdengar seperti satu kalimat di ruang meeting, padahal di dalam spec keduanya aturan terpisah dengan dua pemilik keputusan yang berbeda.
Yang saya bawa ke review adalah tabelnya, bukan prosanya. Paragraf tentang limit approval cuma diangguki; satu baris yang menyatakan request senilai persis 5.000.000 disetujui manager sendirian pasti diperdebatkan, dan debat itulah hasil kerjanya. Example Mapping dari Cucumber menyebut baris seperti ini green card, contoh konkret yang duduk di bawah tiap blue card berisi aturan, dan klaimnya cocok dengan yang terjadi di review ERP: contoh memunculkan ketidaksepakatan yang justru disembunyikan oleh kalimat aturan.
| Input | Hasil yang diharapkan | Alasan baris ini ada di tabel |
|---|---|---|
| PR senilai 4.500.000 IDR, limit approval cabang 5.000.000 | Manager departemen approve, tanpa langkah finance | Jalur normal, supaya regresi routing langsung terlihat |
| PR senilai persis 5.000.000 IDR | Manager approve sendirian — limitnya bersifat inklusif | Finance bilang sampai lima juta; implementasi pertama membacanya sebagai di bawah lima juta |
| PR senilai 40.000.000 IDR, finance head mendelegasikan ke controller selama satu minggu | Controller approve; audit trail mencatat kedua nama beserta jendela waktunya | Delegasi tanpa tanggal berakhir berubah jadi approver kedua yang permanen |
| PR senilai 40.000.000 IDR yang diajukan oleh delegasi itu sendiri | Naik satu level di atasnya, tidak pernah kembali ke delegasi | Larangan self-approval adalah kontrol, bukan preferensi, jadi butuh barisnya sendiri |
| Dua baris invoice senilai 1.000.005 IDR dengan tarif 11 persen di item master | Pajak 220.002 IDR, dibulatkan half up di tiap baris | Membulatkan total invoice sekali saja menghasilkan 220.001, dan selisihnya tidak pernah cocok |
| Invoice supplier bertanggal 30 September, period sudah terkunci | Posting ditolak; masuk ke Oktober dengan tanggal dokumen tetap dipertahankan | Boundary yang selalu ditanyakan finance paling awal, di setiap review |
Setiap baris di tabel itu ada karena pernah ada yang salah menanganinya. Baris boundary-lah yang bekerja: nominal yang persis menyentuh limit, approver yang ternyata pemohonnya sendiri, invoice bertanggal sama dengan hari period ditutup. Tabel berisi baris tengah yang aman tidak membuktikan apa pun dan selesai direview dalam tiga puluh detik, dan justru itu sebabnya membuatnya terasa produktif.

Setiap spec modul ERP yang saya tulis menghasilkan lebih banyak pertanyaan daripada aturan pada putaran pertama, dan pertanyaannya ternyata bagian yang paling berharga. Example Mapping melakukan hal serupa lewat red card — seperti ditulis Cucumber, Anda baru saja mengubah unknown unknown menjadi known unknown. Penyesuaian khas ERP-nya adalah tiap pertanyaan diberi pemilik, tanggal, dan default.
# specs/purchasing/pr-approval.ambiguities.yaml
# Every question the worked-example table could not settle. Read in review.
- id: AMB-01
question: 'Does up to 5,000,000 include exactly 5,000,000?'
owner: Rina, Finance Manager # a person, never a department
asked: 2026-09-02
due: 2026-09-05
default: 'inclusive - the highest amount a manager may approve alone'
blocks: false # the default ships, and the test asserts it
- id: AMB-02
question: 'When the named delegate raises the request, who approves it?'
owner: Rina, Finance Manager
asked: 2026-09-02
due: 2026-09-05
default: none # no safe default exists for this one
blocks: true # self-approval is a control, not a preference
- id: AMB-03
question: 'Does the month-end freeze stop approvals, or only posting?'
owner: Andri, Controller
asked: 2026-09-02
due: 2026-09-08
default: 'posting only - approvals continue through close'
blocks: falseDua field membuat dokumen ini hidup, bukan sekadar daftar keluhan. Field owner menyebut nama orang, karena pertanyaan yang dimiliki finance sebenarnya tidak dimiliki siapa-siapa. Field default menyatakan apa yang akan dilakukan kode kalau jawabannya tidak pernah datang, sehingga pertanyaan terbuka berubah jadi perilaku yang bisa dirilis plus risiko yang tercatat — dan test menegaskan default itu, jadi begitu jawaban sebenarnya tiba, test yang gagal menunjukkan persis apa yang harus diubah.
Kegagalan paling umum adalah spec yang ditulis setelah kodenya jadi. Bacanya seperti spesifikasi, lolos review, padahal isinya dokumentasi dari apa pun yang terlanjur dibangun, termasuk bagian yang tidak pernah diputuskan siapa pun. Cirinya adalah baris boundary yang hilang: spec yang ditulis belakangan tidak punya baris itu, karena implementasinya sudah menjawab semua pertanyaan boundary secara diam-diam.
Dua kelas aturan menyumbang sebagian besar perdebatan setelah go-live, dan keduanya tidak bisa ditebak dari kode di sekitarnya. Yang pertama adalah batas posting period. Odoo, sebagai contoh yang terdokumentasi, punya dua: Lock Everything date yang mencegah perubahan entry yang sudah diposting sekaligus mencegah posting baru pada tanggal itu atau sebelumnya, dan Hard Lock date yang bersifat irreversible dan, dalam kalimat Odoo sendiri, tidak bisa diubah atau ditimpa terlepas dari hak akses. Aturan Anda mengacu ke yang mana adalah keputusan bisnis, dan kalimat periodenya sudah ditutup tidak menjawabnya.
Yang kedua adalah pembulatan, tempat sebuah spesifikasi benar-benar membayar dirinya sendiri, karena hasil aritmetikanya ditentukan oleh di mana Anda membulatkan, bukan bagaimana. Dua baris invoice senilai Rp 1.000.005 dengan tarif 11 persen yang dikonfigurasi di item master menghasilkan pajak 220.002 rupiah bila tiap baris dibulatkan half up, dan 220.001 bila total invoice yang dibulatkan sekali. Satu rupiah, di setiap invoice, dan satu task rekonsiliasi yang tidak pernah selesai. Alokasi landed cost punya pola yang sama dengan arah selisih terbalik.
// specs/purchasing/landed-cost.spec.ts - written before the module existed.
//
// Rule LC-03: import freight is apportioned across receipt lines by line
// value, rounded half up to the rupiah, and the rounding residual lands on
// the highest-value line so the apportioned total equals the freight exactly.
const halfUp = (n: number) => Math.floor(n + 0.5);
function apportion(freight: number, lineValues: number[]): number[] {
const total = lineValues.reduce((a, b) => a + b, 0);
const shares = lineValues.map((v) => halfUp((freight * v) / total));
// Naive rounding invents money: three equal lines of a 2,000,000 freight
// each round to 666,667, which sums to 2,000,001. The extra rupiah has to
// be taken back from one named line, decided once, in the spec.
const drift = freight - shares.reduce((a, b) => a + b, 0);
shares[lineValues.indexOf(Math.max(...lineValues))] += drift;
return shares;
}
test("LC-03 puts the residual on one named line, not on every line", () => {
const shares = apportion(2_000_000, [1_000_000, 1_000_000, 1_000_000]);
expect(shares).toEqual([666_666, 666_667, 666_667]);
expect(shares.reduce((a, b) => a + b, 0)).toBe(2_000_000);
});Komentar di dalam test itulah spesifikasinya. Pembulatan naif menciptakan satu rupiah ongkos angkut yang tidak ada, jadi sisa pembulatan harus mendarat di suatu tempat secara sengaja. Baris mana yang menerimanya memang arbitrer — nilai tertinggi, baris pertama, atau baris terakhir sama-sama bisa dibela — tetapi harus tertulis, karena kalau tidak, tiga developer akan memilih tiga jawaban arbitrer yang berbeda dan valuasi inventory berhenti cocok dengan invoice supplier.
Prompt dibuang begitu session berakhir. Spec dibaca orang berikutnya, dan di modul ERP orang berikutnya biasanya sedang mengaudit angka yang salah pada bulan yang sudah lama Anda lupakan. Spec Kit dari GitHub membawa gagasan ini ke ujungnya, menggambarkan proses saat spesifikasi menjadi executable dan langsung menghasilkan implementasi yang berjalan, bukan sekadar memandu, dengan specify, plan, tasks, dan implement sebagai command terpisah. Yang saya ambil lebih sempit: unit yang direview adalah file spec di dalam repository, bersebelahan dengan kode yang diaturnya.
Praktiknya, spec tinggal di folder specs bersama modulnya, ambiguity log duduk di sebelahnya, dan perubahan aturan menjadi diff yang bisa disodorkan ke orang finance. Yang saya review adalah diff dari spec, bukan transkrip session yang mengimplementasikannya. Transkrip memberi tahu apa yang disampaikan ke agent; spec memberi tahu apa yang diputuskan bisnis, dan hanya salah satunya yang masih benar kuartal depan.

Urutannya lebih penting daripada formatnya. Ditulis dengan urutan ini, spec tetap jadi spesifikasi; ditulis dengan urutan lain, pelan-pelan ia berubah jadi deskripsi kode.
Langkah yang paling sering dilewati saat dikejar deadline adalah langkah keempat, dan melewatinya adalah seluruh perbedaan antara modul dengan tiga keputusan terbuka yang tercatat dan modul dengan tiga keputusan terbuka yang tidak tercatat. Keduanya tetap rilis di hari yang sama. Hanya satu yang bisa diaudit setelahnya.
Spec-driven development di ERP bukan teknik menulis prompt. Ini disiplin requirement lama — satu criteria yang bisa diuji per aturan, contoh mengalahkan prosa, pertanyaan dicatat atas nama pemilik tertentu — dikerjakan sebelum agent boleh menulis apa pun, karena agent dengan senang hati mengimplementasikan ambiguitas alih-alih melaporkannya. Tulis spec sampai failing test-nya lahir duluan, dan implementasi berhenti menjadi bagian yang sulit.
Sumber dan bacaan lanjutan