Melacak Jejak Panen Data dengan Log Audit SQLite
Dua penjaga lokal untuk pipeline panen data: rulebook AGENTS.md untuk agen koding dan log audit SQLite yang idempoten lewat UPSERT.
Ringkasan
Pas panen data di DemandScope error terus, aku bingung commit mana yang ngasilin datanya karena Git cuma nyatet berkas berubah doang. Solusinya gampang: bikin AGENTS.md buat ngatur agen koding biar nggak sembarangan nulis data atau commit kredensial. Terus bikin log audit SQLite per target pakai UPSERT biar idempoten, jadi tiap data bisa dilacak asalnya.
Sesi panen data di DemandScope berhenti di tengah jalan. Terminal menampilkan pesan error yang tidak dikenal, dan saya menghentikan proses secara paksa. Ketika sesi dilanjutkan, satu pertanyaan mendasar muncul dan tidak bisa saya jawab: commit mana yang membawa data untuk perkara ini? Dugaan pertama saya, riwayat Git saja sudah cukup untuk melacak perubahan berkas data mentah. Asumsi itu keliru.
Riwayat Git hanya mencatat bahwa sebuah berkas berubah, bukan konteks eksekusi di balik perubahan itu. Tanpa pencatatan terstruktur, setiap baris data kehilangan jejak asalnya. Solusinya ternyata bukan menambah arsitektur baru, melainkan dua penjaga sederhana yang bekerja lokal: sebuah rulebook untuk agen koding, dan satu log audit berbasis SQLite.
Rulebook Batas untuk Agen Koding
Langkah pertama menetapkan batasan. Saya menambahkan berkas AGENTS.md di akar repositori sebagai buku aturan yang wajib dipatuhi agen koding. Formatnya terbuka dan dipakai lebih dari 60 ribu proyek sumber terbuka [3].
Analisis atas lebih dari 2.500 berkas serupa di repositori publik menunjukkan pola yang bekerja: persona yang spesifik, perintah eksekusi diletakkan di awal, contoh kode mengalahkan penjelasan panjang, dan batasan eksplisit tentang apa yang tidak boleh disentuh [5]. Untuk pipeline panen data, aturan yang paling menentukan ada dua: agen tidak boleh menulis ke direktori data hasil panen, dan tidak boleh membawa kredensial ke dalam commit.
Disiplin commit juga hidup di berkas yang sama. Setiap commit hasil panen wajib tercatat di log audit. Agen koding seperti Codex membaca berkas ini sebelum mulai bekerja, dengan lapisan global ke proyek hingga subdirektori, berkas yang lebih dekat menimpa yang di atasnya, berkas kosong dilewati, dan totalnya dibatasi 32 KiB secara default [6]. Aturan yang terlalu panjang bukan cuma membengkak konteks; bagian ekornya bisa diam-diam tidak pernah termuat.
Log Audit Lokal dengan SQLite
Untuk penyimpanan log, basis data klien-server terasa berlebihan. SQLite memang dirancang untuk ceruk ini: bersaing dengan fopen(), bukan dengan sistem basis data terpusat, dan dokumentasinya menyebut situs dengan lalu lintas di bawah 100 ribu hit per hari masih nyaman memakainya [1]. Saya membuat satu berkas basis data per target panen di dalam direktori data target. Pemisahan per target membuat kegagalan skema atau kunci di satu target tidak menyeret target lain.
Setiap baris log mewakili satu unit panen: kombinasi commit, perkara, dan tab. Bentuk ini versi miniatur dari prinsip provenance, informasi tentang entitas dan aktivitas yang menghasilkan sebuah data, yang dipakai untuk menilai kualitas dan kepercayaan [4]. Standar pelacakan run seperti OpenLineage mengharuskan setiap run punya ID unik dan pasangan event mulai-selesai [7]; log kecil ini mengadopsi kontrak yang sama dalam skala satu berkas.
Kuncinya satu: idempotensi. Log yang ditulis ulang saat sesi dijalankan kembali akan menggandakan baris kalau tidak ada mekanisme pencegahan. Di sinilah UPSERT masuk.
Idempotensi lewat UPSERT
UPSERT bukan trik kueri. Ia adalah INSERT yang berubah menjadi UPDATE atau no-op saat melanggar constraint keunikan, dan hanya bekerja untuk constraint keunikan [2]. Tanpa kunci UNIQUE di skema, jaminan ini tidak pernah aktif. Skema lognya:
CREATE TABLE harvest_log (
commit_hash TEXT NOT NULL,
case_no TEXT NOT NULL,
tab TEXT NOT NULL,
status TEXT NOT NULL,
ts TEXT DEFAULT (datetime('now')),
UNIQUE(commit_hash, case_no, tab)
);
INSERT INTO harvest_log (commit_hash, case_no, tab, status)
VALUES ('a1b2c3d', '2026/001', 'putusan', 'ok')
ON CONFLICT(commit_hash, case_no, tab)
DO UPDATE SET status = excluded.status, ts = excluded.ts;
Sesi yang diulang dengan commit yang sama kini memperbarui baris, bukan menduplikasi. Saat pertanyaan asal-usul muncul lagi, jawabannya satu kueri:
SELECT commit_hash, status, ts
FROM harvest_log
WHERE case_no = '2026/001'
ORDER BY ts DESC
LIMIT 1;
Saya sempat menimbang lapisan pelacakan yang lebih tebal: service lineage terpisah, atau log terstruktur di platform lain. Semuanya menambah komponen yang harus hidup dan dijaga. Dua penjaga lokal ini sudah menjawab pertanyaan yang dulu tidak terjawab. Setiap baris data kini punya identitas, terhubung ke commit yang menghasilkannya, dan sesi yang terputus tidak lagi meninggalkan kekosongan yang tidak bisa direkonstruksi.
## Sumber [1] Appropriate Uses For SQLite, sqlite.org [2] UPSERT, sqlite.org [3] AGENTS.md, agents.md [4] PROV-Overview, W3C [5] How to write a great agents.md: Lessons from over 2,500 repositories, GitHub Blog [6] Custom instructions with AGENTS.md, OpenAI Developers [7] OpenLineage Spec, GitHub