# Arsitektur, akses, dan integritas

## Sumber data

`DB` menyediakan D1/SQLite persisten. `BUCKET` menyediakan R2 untuk file tetap. UI mengambil data dari server. Tidak ada total donasi atau konten yang memakai LocalStorage sebagai sumber utama.

CMS memakai `records` dengan payload draft, payload publik (`live`), revisi, status, waktu, dan pelaku. Jenis site memuat halaman profil/hero/kontak/ketentuan. Jenis program, progres, FAQ, dan laporan memiliki versi sendiri. Media dirujuk dengan ID stabil. Draft yang belum pernah diterbitkan tidak memiliki payload publik.

Data awal hanya memuat data yang diberikan: lima program dengan target Rp615.230.000. Jumlah donasi awal nol berarti belum ada transaksi terverifikasi dalam aplikasi, bukan klaim saldo rekening bank yayasan. Pengurus, visi, legalitas, akreditasi, siswa, peta, foto, proposal, progres, dan tanggal penyelesaian tidak dikarang.

## Hak akses server

| Fitur | Publik / donatur | Editor | Keuangan | Pemilik |
|---|---|---|---|---|
| Konten yang diterbitkan | Baca | Baca | Baca | Baca |
| Status donasi | Token acak miliknya | Tidak ada akses bukti | Baca privat | Baca privat |
| Bukti / kontak donatur | Tidak ada akses publik | Ditolak | Diizinkan | Diizinkan |
| Program, progres, site, FAQ | Tidak dapat menulis | Kelola | Tidak dapat menulis | Kelola |
| Verifikasi, ledger, mutasi, laporan | Ditolak | Ditolak | Kelola | Kelola |
| Rekening, akun, backup, restore | Ditolak | Ditolak | Ditolak | Kelola dengan autentikasi ulang |
| Keamanan akun | Bukan akun admin | Miliknya | Miliknya, wajib 2FA | Miliknya, wajib 2FA |

Identitas awal diverifikasi oleh dispatcher Sites. Header identitas bukan bukti yang aman jika Worker diterbitkan langsung tanpa dispatcher. Allowlist role berada di DB. Semua mutasi memakai pemeriksaan sesi, role, Origin, dan header aplikasi untuk melindungi CSRF. Token sesi dan status memakai 256 bit acak, dan hash di server. Form/login/faktor dibatasi per jendela waktu pada DB.

Secret authenticator dienkripsi AES-GCM. Kode pemulihan memiliki 128 bit acak, hash terikat ID admin, dan `used_at` yang diubah atomik. TOTP menolak penggunaan ulang time step. Pending sesi pemilik/keuangan tidak membuka API admin. Reauth berlaku lima menit dan tidak otomatis diberikan setelah login biasa atau login pemulihan.

## Definisi uang

Target = jumlah target program kampanye utama pada konten publik. Subtotal bangunan sudah termasuk aluminium/kaca dan granit. Target tidak ikut dalam perhitungan saldo.

Penerimaan bruto = receipt terverifikasi, termasuk offline yang terverifikasi, dikurangi pembalikan penerimaan. Biaya, refund, dan pengeluaran dicatat terpisah. Koreksi biaya/pengeluaran merujuk catatan awal. Saldo periode = saldo awal + penerimaan − pengeluaran − biaya − refund + alokasi masuk − alokasi keluar.

Dana prioritas memakai `program_id=NULL`. Alokasi membuat pasangan `allocation_out` dari prioritas dan `allocation_in` ke program. Pasangan ini bukan penerimaan baru. Pengeluaran tidak boleh melampaui saldo kelompok dana. Dana program khusus tidak memiliki endpoint pemindahan antarprogram otomatis.

Receipt memakai `source_key=receipt:<donation_id>` yang unik. Verifikasi membuat receipt dan perubahan status dalam satu transaksi D1. Trigger menolak receipt tanpa pencocokan penuh, alokasi berlebih, perubahan/deletion ledger, serta pengubahan/deletion donasi yang pernah terverifikasi. Koreksi menambahkan catatan baru dengan alasan, pelaku, dan waktu.

## Bank dan rekonsiliasi

Import dan mutasi bersifat privat. Referensi unik menurut rekening mencegah impor ganda. Fingerprint tanpa referensi hanya menandai kandidat duplikat; tidak otomatis membuang transaksi sah. Pencocokan unik per bank/donasi dan kapasitas bank/donasi ditegakkan dalam trigger. Mutasi di luar kampanye tidak dapat digunakan untuk pencocokan donasi tanpa pemeriksaan cakupan.

Rekonsiliasi membandingkan receipt pada tanggal verifikasi dengan kredit yang dialokasikan pada tanggal mutasi. Selisih dapat menunjukkan kebutuhan pemeriksaan timing, refund/koreksi, atau transaksi belum terverifikasi. Laporan tidak menyamakan saldo dana kampanye dengan seluruh saldo bank. Debit rekening nonkampanye tidak otomatis menjadi pengeluaran kampanye.

## File dan privasi

JPG/PNG/PDF maksimal 5 MB dan MP4 maksimal 25 MB, dengan pemeriksaan tanda tangan/MIME server. Bucket tidak memiliki akses publik langsung. Endpoint media privat menolak publik/editor. File publik dipilih secara eksplisit oleh admin yang berwenang. Caption dan rich content diperlakukan sebagai teks, di-escape React; tidak ada HTML mentah yang dijalankan.

API publik memilih field aman dan tidak membaca kontak/bukti mentah ke output. Status token tidak mengembalikan kontak/pesan privat. Nama publik hanya dengan persetujuan. Pesan publik hanya bila ada persetujuan dan moderasi. Laporan PDF hanya dari media publik yang dipilih pengelola.

File lama dipertahankan untuk riwayat, bukan dihapus ketika dilepas dari konten. Kebijakan retensi dan penghapusan data donatur harus diatur pemilik sesuai praktik organisasi; UI tidak memberikan hapus permanen receipt. Export CSV admin tidak memuat kontak atau bukti donor.

## Versi dan backup

Optimistic concurrency memeriksa revisi. Versi menyimpan snapshot, referensi media, actor, waktu, ringkasan, dan state. Restore konten melalui jalur CMS biasa menjadi revisi baru dan draft, tanpa menulis ledger/donasi/rekening.

Backup memakai snapshot read D1 konsisten dan objek tetap, enkripsi terpisah, checksum, log status, serta jadwal yang dikendalikan server. Pemulihan dashboard bersifat merge data hilang agar penerimaan baru, penggunaan recovery code, dan keuangan aktif tidak kembali ke waktu lampau. Pemulihan penuh memerlukan resource terpisah, uji operator, reauth pemilik, checkpoint sekarang, audit, dan rekonsiliasi sesudah snapshot.
