Sikathree Master PRD
Dokumentasi lengkap untuk prototipe interaktif, MVP Full System, dan persyaratan Production Go-Live.
Ringkasan Eksekutif
Tujuan, lingkup, keputusan yang dibekukan, serta batas kesiapan production.
0.1 Pernyataan produk
Sikathree — Sistem Informasi K3 adalah aplikasi web multi-organisasi untuk mendukung tiga domain yang berdiri sendiri tetapi memakai fondasi bersama: HIRADC, Laporan Bahaya, dan Learning Management System (LMS). Sikathree membantu Operator/Petugas mencatat bahaya dan menjalankan tugas, Pengawas K3 melakukan review dan verifikasi sesuai izin, pengelola organisasi mengatur keanggotaan, dan Admin K3 mengelola materi pembelajaran global.
Pengalaman pengguna menggunakan Modern Operational Dashboard (A), Light Mode, responsif sepenuhnya pada desktop, tablet, dan smartphone. Alur lapangan (Laporan Bahaya dan Control Task) didesain mobile-first. Satu akun dapat mempunyai membership di beberapa organisasi; data tenant tidak otomatis terlihat oleh role global.
0.2 Tujuan dokumen
Master PRD ini adalah spesifikasi end-to-end untuk dua tahap implementasi: (1) Interactive Prototype frontend dengan mock data yang saling terhubung dan simulator workflow; (2) MVP Full System dengan frontend, backend, database, file storage, worker, deployment, serta security controls nyata. Dokumen juga memisahkan go-live gates sebelum pemakaian data operasional sungguhan.
0.3 Keputusan paling utama
- Frontend: Next.js App Router, TypeScript, Tailwind CSS, shadcn/ui, React Hook Form + Zod, TanStack Query, dan tRPC BFF.
- Backend: NestJS modular monolith, REST
/api/v1, worker terpisah, PostgreSQL + pgvector; transactional outbox untuk email; Garage S3 untuk berkas. - Tenant isolation: membership dan Dynamic RBAC diperiksa di backend; PostgreSQL Row Level Security (RLS) dengan konteks transaksi; immutable business versions dan append-only audit.
- Infrastruktur: 1 VPS per environment (Production 1 dan Staging 1), masing-masing menggunakan 5 Compose project modular. Hanya Nginx mempublikasikan port 80/443; layanan lain memakai Docker network internal. Tidak menggunakan WireGuard antarservice.
- Capacity assumption: 4.000 akun terdaftar, estimasi 3% aktif bersamaan = 120 peak concurrent users; 8 vCPU/32 GB merupakan kandidat pengujian, bukan kapasitas yang telah dibuktikan.
- Keamanan produksi: pengujian RLS, otorisasi, konsistensi transaksi, object storage, dan backup/recovery harus selesai sebelum mengelola data nyata.
0.4 Status spesifikasi
Dokumen memakai label [FINAL] untuk keputusan percakapan yang sudah dikunci, [DESIGN] untuk rancangan detail yang didelegasikan pengguna dan direkomendasikan sebagai baseline implementasi, [PROVISIONAL] untuk kebutuhan yang masih perlu disahkan sebelum menjadi konfigurasi production, dan [GATE] untuk kriteria wajib sebelum go-live. Penetapan [DESIGN] bukan berarti klaim sudah terimplementasi atau lolos pengujian.
0.5 Prioritas keberhasilan
- Proses bisnis dan histori yang benar, terutama approval, versi, residual risk, dan aturan attempt LMS.
- Tidak ada kebocoran data antarorganisasi atau peningkatan hak akses tanpa izin.
- Pengalaman mobile yang nyaman untuk pekerjaan lapangan tanpa menghilangkan kemampuan desktop.
- Dapat dioperasikan/diobservasi pada staging, lalu dipromosikan ke production melalui release terotorisasi.
- Perilaku UI, API, database, audit, dan email saling konsisten; tidak ada hanya-perubahan-badge.
Status Keputusan, Scope dan Prioritas
Membedakan keputusan final, rancangan delegasi, hal provisional, scope MVP, dan go-live gate.
1.1 Penetapan status dan pengendalian perubahan
- [FINAL] Keputusan yang telah disepakati dalam diskusi: stack inti, tiga domain, aturan bisnis spesifik, satu VPS per environment, Dynamic RBAC, session, mobile responsive, dan mode penyimpanan draft manual.
- [DESIGN] Rekomendasi pengisi celah yang pengguna delegasikan: field-level data, validation details, permission/action mapping, state guards, idempotency, edge cases, acceptance test.
- [PROVISIONAL] Nilai atau batas yang memerlukan kebijakan operator: retention angka final, limit file, durasi grant, provider VPS, domain nyata, detail recovery jawaban tes, dan jalur REST publik.
- [GATE] Ketentuan yang harus diverifikasi sebelum data nyata: backup/recovery, storage durability, tests security/RLS, secrets, alerting, release process, load testing.
Setiap perubahan keputusan [FINAL] memerlukan catatan Decision Record yang mencantumkan keputusan lama, usulan baru, alasan, dampak pada entitas/API/UX, dan persetujuan pemilik produk. Developer atau AI Agent tidak boleh mengubah rule hanya agar implementasi lebih mudah.
1.2 Tahap deliverable
| Tahap | Tujuan | Data & security | Bukti selesai |
|---|---|---|---|
| P0 — Prototipe interaktif | Uji seluruh layar dan workflow | Fixtures JSON, state lokal, auth/RBAC simulasi | 12 scenario utama + negative cases, mobile QA |
| P1 — MVP fungsional | Jalankan operasi tenant dengan persistence | NestJS, PostgreSQL/RLS, sessions, Garage, worker, email | Integration, e2e, contract, RLS & concurrency tests |
| P2 — Production go-live | Menggunakan data operasional nyata | Seluruh go-live gate tuntas | Go/no-go checklist dan sign-off |
1.3 Termasuk MVP
- Multi-org users, invitation-only account, organization selection, role/permission dinamis, guarded support access.
- HIRADC proposal, risk assessment per record dan versi, K×P, review, kontrol, verifikasi, residual risk, audit dan official PDF snapshot.
- Hazard reporting dari mobile, attachment, approval, penugasan satu PIC aktif, tindakan, verifikasi, revisi substansial, histori dan email penting.
- LMS course global terbit, pre-test, learn manual complete, post-test, attempt quota, fingerprint/versioning, results dan tenant progress monitoring.
- Administration tenant/platform/K3, file lifecycle, notification outbox, staging/prod deployment dan basic observability.
1.4 Tidak termasuk MVP
- AI Learning Companion dan pencarian semantik meskipun
pgvectortersedia. - Simulasi 3D, native mobile apps, offline/PWA, JSA/LOTO workflows, e-certificate, cross-module task orchestration.
- Notification Center in-app, video streaming/transcoding platform khusus, advanced BI, microservices/Kubernetes.
- Multi-node Garage/high availability tidak otomatis masuk scope implementasi MVP awal; tetap menjadi risiko durabilitas yang harus diselesaikan sebelum go-live nyata.
1.5 Asumsi lingkungan
- Organisasi merupakan tenant dengan empat area K3 global yang sama.
- Nama domain dan identitas organisasi contoh dalam spesifikasi adalah placeholder, bukan alamat produksi yang telah diputuskan.
- Semua string locale
id-ID, tanggal/jam dipresentasikan WIB (Asia/Jakarta) di UI; event disimpan sebagai UTC timestamp di backend [DESIGN]. - Status
organizationIddi browser hanya pilihan konteks, bukan sumber kebenaran authorization.
Aktor, Organisasi dan Use Cases
Aktor dan batas kewenangannya, katalog empat area, serta alur penggunaan sistem.
2.1 Model identitas dan membership
Satu User merupakan identitas global. Satu user dapat memiliki nol atau lebih Membership organisasi; setiap membership memiliki satu role lokal aktif dan personnel category terpisah. Role Platform Super Admin dan Admin K3 adalah role platform, bukan membership tenant. Organization Admin tidak otomatis memperoleh izin review HIRADC atau laporan. Platform role tidak otomatis membaca tenant data.
2.2 Aktor utama
| Aktor | Tujuan | Lingkup akses default |
|---|---|---|
| Operator/Petugas | Melaporkan bahaya, mengajukan identifikasi, menjalankan tugas, belajar | Tenant tempat menjadi anggota, sesuai permission |
| Pengawas K3 | Review assessment/laporan, verifikasi kontrol, monitoring | Tenant dan permission efektif; berlaku guard per transaksi |
| Organization Admin | Administrasi membership, undangan, role, support approval | Administrasi tenant, bukan otomatis seluruh proses operasional |
| Admin K3 | Membuat dan menerbitkan konten pembelajaran | Course/assessment global, bukan progress individu tenant tanpa grant |
| Platform Super Admin | Mengelola tenant dan platform, mengajukan support grant | Metadata platform; data bisnis tenant hanya melalui grant disetujui |
| Multi-membership User | Berpindah antara beberapa tenant | Hanya tenant yang aktif dan masih mempunyai membership sah |
2.3 Area/lokasi K3 global [FINAL]
| Kode stabil [DESIGN] | Label tampilan |
|---|---|
AREA_DISINFECTION |
Penyimpanan dan Pembubuhan Desinfektan |
AREA_PROCESS_CHEMICAL |
Penerimaan, Penyimpanan, Penyiapan dan Pembubuhan Bahan Kimia Proses |
AREA_MECH_ELECTRIC |
Pompa, Panel Listrik dan Genset |
AREA_RESERVOIR |
Reservoir Air Bersih |
Katalog area berlaku pada HIRADC, Laporan Bahaya, filter, laporan, dan course tagging. Field lokasi spesifik berbentuk teks untuk lokasi lebih rinci. User tidak dapat membuat area global baru dari form tenant.
2.4 Use case end-to-end
- Operator menemukan kondisi bahaya dan membuat laporan; status, severity, evidence, email dan audit mengikuti rule; Pengawas/penanggung jawab menindaklanjuti hingga verifikasi.
- Operator menyampaikan usulan identifikasi; pihak berizin melakukan assessment satu risiko per record; Pengawas berbeda dari pembuat risiko/versi menyetujui; kontrol dikerjakan dan diverifikasi.
- Peserta mengerjakan Pre-Test diagnostik bila aktif, membaca lesson dan menandai selesai manual, kemudian lulus Post-Test sesuai threshold/kuota.
- Admin K3 menerbitkan lesson/assessment versi baru; identitas lesson dan fingerprint mempertahankan atau mereset progress sesuai perubahan nyata.
- Organization Admin mengundang anggota, mengubah role dalam delegation ceiling, dan menyetujui/menolak permintaan Support Access.
- Platform Super Admin mengelola metadata tenant, bukan membaca isi data tenant tanpa izin spesifik.
2.5 Sasaran operasional terukur [DESIGN]
- Pengguna dapat menemukan tindakan utama (buat laporan, tugas, belajar) dari Dashboard atau mobile navigation.
- Semua perubahan bisnis signifikan mempunyai actor, timestamp, reason jika diwajibkan, version ID, dan audit event.
- Seluruh route sensitif mempunyai pemeriksaan permission server-side; tidak bergantung pada hidden UI button.
- Cross-tenant isolation dan no-self-approval HIRADC diverifikasi oleh automated tests.
Information Architecture & Screen Inventory
Seluruh route dan pembagian workspace, termasuk perbedaan desktop/mobile.
3.1 Navigasi dan application shells
Aplikasi memiliki satu desain terpusat dengan lima variasi shell: Authentication, Organization, Platform, K3 Content Studio, dan Focused Learning/Assessment. Pada desktop Organization Shell memakai sidebar + topbar; di smartphone memakai topbar, bottom navigation izin-terfilter, dan drawer menu lengkap. Tidak ada fitur berizin yang boleh hilang dari mobile.
3.2 Authentication dan akun
| Route | Screen | Catatan |
|---|---|---|
| /login | Login | Email/password, generic errors, no public sign-up |
| /invite/[token] | Aktivasi undangan | Sekali pakai, expired handling |
| /forgot-password | Lupa password | Permintaan kode OTP tanpa account enumeration |
| /reset-password | Reset password | 6 digit OTP 5 menit [FINAL] |
| /workspace | Pemilihan workspace | Membership dan platform role |
| /account/profile | Profil | Nama dan informasi akun |
| /account/security | Keamanan | Change password, sesi dan logout |
3.3 Organization Workspace
| Route | Screen | Tujuan |
|---|---|---|
| /app | Dashboard | Action-oriented dan permission-scoped |
| /app/my-activity | Aktivitas Saya | Agregasi baca-only atas pekerjaan dari domain asal |
| /app/organization | Profil Organisasi | Metadata tenant |
| /app/hiradc | Risk Register | Daftar risiko, filter, skor |
| /app/hiradc/proposals | Daftar Usulan | Antrean/riwayat usulan |
| /app/hiradc/proposals/new | Buat Usulan | Field bahaya & lokasi |
| /app/hiradc/proposals/[id] | Detail Usulan | Review usulan |
| /app/hiradc/risks/new | Penilaian Baru | Form assessment bertahap |
| /app/hiradc/risks/[id] | Detail Risiko | Versi, kontrol, residual, dokumentasi |
| /app/hiradc/risks/[id]/versions/new | Revisi Risiko | Buat kandidat assessment baru |
| /app/hiradc/risks/[id]/versions/[versionId] | Detail Versi | Snapshot immutable |
| /app/hiradc/reviews | Review Assessment | Antrean approval |
| /app/hiradc/controls | Control Tasks | Tugas PIC/verifikasi |
| /app/hiradc/exports | Dokumen Resmi | Preview dan immutable PDF |
| /app/reports | Daftar Laporan | Case list dan filter |
| /app/reports/new | Buat Laporan | Mobile-first 3 langkah |
| /app/reports/drafts/[id] | Edit Draft | Manual Save Draft |
| /app/reports/[id] | Detail Laporan | Evidence, PIC, timeline |
| /app/reports/reviews | Review Laporan | Approval/revision |
| /app/reports/assignments | Penugasan Saya | Tugas PIC aktif |
| /app/reports/[id]/revisions/new | Ajukan Revisi | Perubahan substansial |
| /app/reports/[id]/revisions/[revisionId] | Riwayat Revisi | Snapshot dan diff |
| /app/learning | Katalog | Course global terbit |
| /app/learning/courses/[id] | Course Detail | Stage/progress |
| /app/learning/courses/[id]/lessons/[lessonKey] | Lesson | Focused viewer |
| /app/learning/courses/[id]/assessments/[type] | Mulai Assessment | Pre/Post-Test introduction |
| /app/learning/attempts/[id] | Assessment Player | Pertanyaan tanpa kunci jawaban |
| /app/learning/attempts/[id]/result | Result | Nilai, kuota, status |
| /app/learning/history | Riwayat Saya | Historis vs current completion |
| /app/learning/monitoring | Monitoring Peserta | Tenant-scoped, permission-protected |
3.4 Administrasi organisasi dan platform
| Route | Screen | Catatan |
|---|---|---|
| /app/settings/organization | Organization Settings | Metadata tenant sesuai permission |
| /app/settings/members | Members | Daftar anggota |
| /app/settings/members/[id] | Member Detail | Membership/role history |
| /app/settings/invitations | Invitations | Undang, revoke, resend |
| /app/settings/roles | Roles | Custom org role dalam ceiling |
| /app/settings/roles/[id] | Role Editor | Permission matrix |
| /app/settings/support-access | Support Approvals | Organization Admin approves |
| /platform | Platform Overview | Metadata, bukan business dashboard tenant |
| /platform/organizations | Organizations | Create/update/activate |
| /platform/organizations/[id] | Organization Detail | Admin metadata |
| /platform/accounts | Platform Accounts | Global platform role management |
| /platform/rbac-templates | RBAC Templates | Versioned default templates |
| /platform/support-access | Support Requests | Request/revoke/history |
| /platform/support-access/[grantId] | Support Session | Read-only resource/time scoped |
| /platform/k3 | Content Dashboard | Global course metadata |
| /platform/k3/courses | Course Management | Draft/published |
| /platform/k3/courses/[id] | Course Editor | Content structure |
| /platform/k3/courses/[id]/lessons/[lessonId] | Lesson Editor | Rich text/media |
| /platform/k3/assessments | Assessment Management | Question bank/config |
| /platform/k3/assessments/[id] | Assessment Editor | Answer key only privileged |
| /platform/k3/publications | Publication History | Version impact |
3.5 Route behavior
Semua route mendukung direct link, browser refresh, broken URL, loading/empty/error states, session expiration, dan 403/404. Jangan mengandalkan permission check pada layout saja: page data loader dan action tetap memeriksa izin. URL tenant tidak menyertakan organizationId; konteks dipilih dengan key lokal tepat organizationId dan diteruskan per operasi. Deep link dari email harus mendeteksi tenant mismatch dan menawarkan peralihan hanya jika membership sah.
UI/UX Design System dan Responsive Behavior
Tokens visual, komponen bersama, interaksi, dan perilaku smartphone yang wajib didukung.
4.1 Identitas visual [FINAL]
- Modern Operational Dashboard, Light Mode Only, warna primary teal
#176B63, accent#249B83, muted#E8F5F0, background#F5F7FA, surface putih#FFFFFF, text#1E293B. - Semantic success
#15803D, warning#B45309, critical#B91C1C, info#2563EB; kontras harus memenuhi WCAG 2.2 AA. - Font Inter direkomendasikan [DESIGN], body 14–16px, form mobile 16px, heading page desktop 24–28px, mobile 20–24px; 4px spacing scale; radius 8–12px.
- Risiko HIRADC: Low hijau, Moderate biru, High amber, Very High merah. Label selalu tertulis; status workflow tidak disamakan dengan tingkat risiko.
4.2 Breakpoints dan layout [DESIGN]
| Viewport | Navigasi | Perilaku layout |
|---|---|---|
| 320–639px | Topbar + bottom nav + drawer | 1 kolom, filter via sheet, fullscreen complex dialog |
| 640–767px | Mobile navigation | 1 kolom lega |
| 768–1023px | Sidebar collapsible | 1–2 kolom tergantung isi |
| 1024–1279px | Compact sidebar | Multi-column form/record |
| >=1280px | Expanded sidebar | Table, details, compare side-by-side |
4.3 Component specification
| Komponen | Kontrak visual | Kontrak interaksi |
|---|---|---|
| AppShell | Consistent chrome, workspace indicator | Responsive nav, scoped menu |
| WorkspaceSwitcher | Organization/platform/K3 choices | Membership verified; protects dirty forms |
| ResponsiveDataList | Desktop table, mobile cards | Search, sort/filter, pagination, empty/error |
| PageHeader | Breadcrumb, title, action | Mobile action repositioning |
| PermissionGate | Hide/disable unauthorized options | Backend remains authoritative |
| StatusBadge | Domain registry mapping | Text + color, tooltip only explanatory |
| FormActions | Distinct Save Draft / Submit | Loading, success, error, double submit guard |
| UnsavedChangesGuard | Warning modal | Back, route, org change |
| FileUploader | Picker/camera, preview, progress | Retry, delete temp, attached state |
| RiskMatrix5x5 | K,P picker and grid | Preview only; server calculates official |
| VersionCompare | Before/after and changed fields | Vertical mobile, immutable snapshots |
| ActivityTimeline | Chronological events | Audit source, pagination |
| AssessmentPlayer | One question/group per viewport | No answer keys, stable attempt |
| EmptyState | Contextual headline + CTA | Permission-scoped CTA |
| ConfirmationDialog | Consequence summary, optional reason | Full-screen complex mobile |
| CourseEditor | Outline + editor | Drawer outline on mobile, draft version only |
4.4 UX behavioral rules
- Laporan Bahaya dan HIRADC: Simpan Draft manual, tidak autosave. Tindakan Submit terpisah dan konfirmasi final. Saat belum tersimpan, back/navigation/org switch menampilkan dirty form warning.
- Dashboard: KPI hanya yang berizin; Perlu Tindakan merupakan read-only agregasi dari status modul, bukan Notification Center.
- Detail records: tampilkan owner, organization context, status terpisah, versi efektif, kandidat revisi, histori, evidence.
- Permission: menu berdasarkan effective permission; direct URL dan server mutation tetap diperiksa; denied page tidak membocorkan data record.
- Mobile field operations: form laporan tiga langkah (informasi, evidence/penerima, review); kontrol tugas dan approval mempunyai area sentuh memadai, tidak hanya hover.
- Assessment: Focused Learning Shell menghilangkan navigasi operasional yang mengganggu; belum ada timer wajib yang ditetapkan.
- Feedback: setiap mutation menampilkan pending, success, error atau conflict; jangan mengklaim tersimpan ketika jaringan gagal.
4.5 States universal
Loading/skeleton, empty, error/retry, forbidden, not-found, session expired, organization inactive, unsaved changes, version conflict, concurrent modification, upload failed, timeout/unknown submission outcome, offline/network unavailable, assessment changed. Untuk hasil request ambigu, lakukan read-after-write atau status lookup berbasis idempotency key sebelum menampilkan hasil atau mengulangi mutation.
4.6 Accessibility targets
Keyboard navigation, visible focus ring, descriptive field errors, aria labels, semantic headings, screen-reader status feedback, no color-only indicator, text zoom, touch target 44×44 px sebagai target UX; jangan memblokir browser zoom. Uji Chrome Android, Safari iOS, keyboard mobile, landscape/portrait, viewport 320px dan desktop besar.
Arsitektur Aplikasi dan Boundary Layanan
Boundary browser/BFF/NestJS, database, storage, worker, dan sinkronisasi kontrak API.
5.1 Arsitektur logical [FINAL]
Browser (desktop/mobile)
| HTTPS + host-only session cookie
v
Nginx Public Edge :80/:443
|----> Next.js App Router + tRPC BFF ----> NestJS REST /api/v1 (internal)
| |--- PostgreSQL + pgvector
| |--- Garage S3 (internal)
| `--- Transactional Outbox
|----> Garage S3 via storage hostname (signed upload/download)
`----> No other ports exposed
NestJS Worker ----> PostgreSQL outbox, Garage cleanup, SMTP provider
Prometheus/Grafana/Alertmanager/Exporters ----> health metrics + external uptime monitor
5.2 Boundary API [PROVISIONAL DESIGN]
Rekomendasi eksekusi: browser memakai tRPC BFF untuk semua business operations; NestJS REST tetap internal di Docker network; Nginx hanya mempublikasikan web app dan storage signed gateway. Ini mengurangi jalur keamanan ganda. Jika integrasi pihak ketiga membutuhkan public REST kelak, buat keputusan arsitektur terpisah dengan autentikasi dan rate limit sendiri. /api-docs/Scalar harus diproteksi, tidak boleh dipublikasikan secara terbuka.
5.3 Frontend [FINAL]
Next.js App Router, React Hook Form + Zod, TanStack Query untuk query cache, Zustand hanya untuk state lintas-komponen yang tidak lebih tepat dikelola React. Folder fitur: auth, organizations, permissions, dashboard, hazard-reports, hiradc, learning, administration, support-access. dto/handler/service/repository pada frontend bila digunakan merupakan adapter REST ke NestJS, bukan SQL repository. tRPC mengirim organizationId terverifikasi dan cookie session ke backend.
5.4 Backend [FINAL]
NestJS modular monolith; REST controllers -> DTO validation -> guards/policies -> domain service -> database repository dalam transaksi. Worker proses asinkron terpisah; bukan microservice HTTP baru. Gunakan standard error envelope, observability trace correlation ID, idempotency and optimistic concurrency.
5.5 API contract synchronization [DESIGN]
NestJS menghasilkan OpenAPI versioned artifact pada CI; frontend REST adapter dibuat/divalidasi dari kontrak tersebut, disertai contract tests. Perubahan breaking mengikuti /api/v1 policy dan backward-compatible deployment window. Jalur tRPC menyesuaikan schema frontend; browser tidak mengenal kredensial NestJS internal.
5.6 Service responsibility map
| Lapisan | Berhak memutuskan | Tidak boleh diasumsikan |
|---|---|---|
| Browser React | Presentasi, local validation, preview, optimistic UX terkendali | Final permission, official risk score, immutable approval |
| Next.js tRPC BFF | Input normalization, session forwarding, adapter, UX mapping | Akses DB tenant langsung, melewati NestJS guards |
| NestJS API | Authorization, workflow, calculations, transaction, outbox | Memercayai role/tenant dari body client |
| PostgreSQL | Durable state, FK, unique, RLS, ACID | Session context dari koneksi tanpa transaksi |
| Garage | Object storage, signed access | Append-only business audit |
| Worker | Outbox, retry, temp file cleanup | Menghapus evidence historis atau bypass policy tanpa service account |
5.7 Time, error dan replay
Simpan timestamp UTC, presentasi WIB; semua mutation kritis memakai requestId/idempotencyKey yang stabil antar retry. REST/Next tidak boleh mengasumsikan koneksi timeout berarti server gagal: status mutation perlu dicek ulang. Pertahankan HTTP status 400, 401, 403, 404, 409, 422, 429, 5xx sesuai semantik; rahasiakan detail stack trace.
Field-Level Specification — Seluruh Form dan Payload
Spesifikasi validasi dan default untuk 175 field dalam 10 grup form.
6.1 Aturan field-level global [DESIGN]
Tabel di bawah merupakan rancangan detail yang didelegasikan oleh pemilik produk. Kolom Draft menentukan apakah field harus terisi saat pengguna menekan Simpan Draft; Submit menentukan apakah field wajib pada aksi final. Auto berarti dihitung/diset oleh server dan tidak dapat diedit klien. Kond. berarti wajib apabila kondisi tertentu terpenuhi. Teks panjang dipangkas whitespace, kosong tidak disamakan dengan 0, datetime disimpan UTC, identifier disimpan sebagai UUID/ULID atau key stabil yang tidak mengandung data pribadi. Semua pilihan katalog berasal dari server dan dipastikan valid saat submit.
- Identitas/role/organization context diambil dari session atau membership; jangan beri field
createdByyang bebas ditentukan frontend. - Semua mutation perubahan dokumen/submission harus membawa
expectedLockVersiondanidempotencyKeypada transport, walaupun UI tidak memperlihatkannya sebagai form field. - Validasi frontend hanya bantuan UX; NestJS mengulangi validasi dan mengecek rule transaksi.
- Bila angka batas di bawah memerlukan penyesuaian operasional (misalnya ukuran file), simpan sebagai konfigurasi kebijakan, bukan asumsi yang tersebar di UI.
- Untuk form Draft HIRADC/Laporan: penyimpanan manual menerima partial fields yang valid; tidak boleh mengeluarkan status Submitted sebelum seluruh persyaratan final terpenuhi. Draft kosong total ditolak sebagai
EMPTY_DRAFT[DESIGN].
6.2 Autentikasi dan akun
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Login | N/A | Wajib | Trim, lowercase canonical; max 254; generic error | ||
| Login | password | secret | N/A | Wajib | Tidak dilog; rate limit; no credential exposure |
| Invitation | inviteToken | opaque token | N/A | Wajib | Once-only, hashed at rest, expiry [DESIGN: 72h configurable] |
| Invitation | passwordNew | secret | N/A | Kond. | Wajib untuk user baru; password policy centrally configured |
| Password reset request | N/A | Wajib | Generic success message independent of existence | ||
| Password reset confirm | otp | string(6) | N/A | Wajib | 6-digit numeric; 5m validity; limited attempts [FINAL TTL] |
| Password reset confirm | newPassword | secret | N/A | Wajib | Policy strong; invalidates sessions as configured |
| Profile | displayName | string(2..120) | N/A | Wajib | No unsupported HTML; trim; UTF-8 |
| Session | sessionId | opaque | Auto | Auto | HttpOnly Secure host-only cookie; never localStorage |
| Workspace | organizationId | UUID | Auto | Kond. | Required for tenant operations, verified membership |
6.3 Organisasi, membership dan undangan
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Organization | legalName | string(3..200) | N/A | Wajib | Server detects duplicates within policy |
| Organization | shortName | string(2..80) | N/A | Wajib | Shown in switcher; non-unique allowed |
| Organization | status | enum | Auto | Auto | ACTIVE/INACTIVE; guarded deactivate |
| Organization | contactEmail | N/A | Opsional | Canonical email | |
| Membership | userId | UUID | Auto | Auto | Existing user or invite resolution |
| Membership | organizationId | UUID | Auto | Auto | Tenant scope |
| Membership | roleId | UUID | N/A | Wajib | Assignable within delegation ceiling |
| Membership | personnelCategory | enum | Auto | Auto | OPERATOR/SUPERVISOR/OTHER via role/membership policy |
| Membership | status | enum | Auto | Auto | ACTIVE/INACTIVE |
| Invitation | N/A | Wajib | Max 254; reject duplicate active membership | ||
| Invitation | roleId | UUID | N/A | Wajib | Active assignable org role |
| Invitation | expiresAt | timestamp | Auto | Auto | [DESIGN] 72h invitation default, configurable |
| Role | name | string(3..80) | N/A | Wajib | Unique per org; not a source of privilege |
| Role | description | string(0..500) | N/A | Opsional | Plain text |
| Role | personnelCategory | enum | N/A | Wajib | Controls recipient class independent from role label |
| Role | permissionKeys | string[] | N/A | Wajib | Subset of delegation ceiling, never privilege escalation |
| Role | isSystemTemplate | boolean | Auto | Auto | Global template flag, cannot be edited by tenant |
6.4 HIRADC — Usulan Bahaya
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Risk Proposal | areaId | enum | Opsional | Wajib | One of four global area keys |
| Risk Proposal | specificLocation | string(0..200) | Opsional | Opsional | Free text; trim |
| Risk Proposal | activity | string(0..240) | Opsional | Wajib | Nama aktivitas/pekerjaan |
| Risk Proposal | hazardDescription | text(0..4000) | Opsional | Wajib | Deskripsi bahaya yang teridentifikasi |
| Risk Proposal | potentialConsequence | text(0..3000) | Opsional | Wajib | Dampak yang mungkin timbul |
| Risk Proposal | existingControlNotes | text(0..3000) | Opsional | Opsional | Pengendalian eksisting dari pengusul |
| Risk Proposal | evidenceIds | UUID[] | Opsional | Opsional | Only UPLOADED/ATTACHED tenant files |
| Risk Proposal | submitNote | text(0..1000) | Opsional | Opsional | Catatan pengajuan |
| Risk Proposal | reporterId | UUID | Auto | Auto | Taken from session, never editable |
| Risk Proposal | state | enum | Auto | Auto | DRAFT/SUBMITTED/ACCEPTED/REJECTED/NEEDS_INFO |
6.5 HIRADC — Assessment Version
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Risk Assessment | riskId | UUID | Auto | Auto | Risk record identity, one risk per record |
| Risk Assessment | areaId | enum | Opsional | Wajib | One of four global areas |
| Risk Assessment | specificLocation | string(0..200) | Opsional | Opsional | Free text |
| Risk Assessment | workActivity | string(0..240) | Opsional | Wajib | Aktivitas spesifik |
| Risk Assessment | hazardSource | text(0..3000) | Opsional | Wajib | Sumber/kondisi bahaya |
| Risk Assessment | riskEvent | text(0..3000) | Opsional | Wajib | Peristiwa risiko |
| Risk Assessment | potentialImpact | text(0..3000) | Opsional | Wajib | Konsekuensi operasional/K3 |
| Risk Assessment | existingControls | text(0..3000) | Opsional | Wajib | Existing risk controls |
| Risk Assessment | initialLikelihoodK | int 1..5 | Opsional | Wajib | Kali kemungkinan; kriteria global |
| Risk Assessment | initialSeverityP | int 1..5 | Opsional | Wajib | Keparahan; kriteria global |
| Risk Assessment | initialScore | computed int | Auto | Auto | K×P; level 1–4/5–9/10–14/15–25 |
| Risk Assessment | plannedResidualK | int 1..5 | Opsional | Wajib | Prospektif setelah kontrol |
| Risk Assessment | plannedResidualP | int 1..5 | Opsional | Wajib | Prospektif setelah kontrol |
| Risk Assessment | plannedResidualScore | computed int | Auto | Auto | K×P; never treated as verified actual |
| Risk Assessment | assessmentRationale | text(0..3000) | Opsional | Wajib | Alasan/justifikasi penilaian |
| Risk Assessment | controlIds | UUID[] | Opsional | Kond. | Minimal 1 rencana pengendalian jika diperlukan; at least 1 required control in MVP [DESIGN] |
| Risk Assessment | evidenceIds | UUID[] | Opsional | Opsional | Evidence safely scoped |
| Risk Assessment | submitReviewNote | text(0..1500) | Opsional | Opsional | Catatan pemohon |
| Risk Assessment | versionNumber | int | Auto | Auto | Monotonic per risk; immutable after approval |
| Risk Assessment | assessmentStatus | enum | Auto | Auto | DRAFT/PENDING_REVIEW/NEEDS_REVISION/APPROVED/REJECTED |
| Risk Assessment | matrixVersionId | UUID | Auto | Auto | Global version pinned to assessment |
| Risk Assessment | verifiedResidualK | int 1..5 | N/A | Kond. | Only when all required controls verified |
| Risk Assessment | verifiedResidualP | int 1..5 | N/A | Kond. | Only when all required controls verified |
| Risk Assessment | verifiedResidualScore | computed int | Auto | Auto | Never auto-copy planned residual |
6.6 HIRADC — Control Task dan Review
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Control Plan | controlDescription | text(10..3000) | Opsional | Wajib | Specific actionable mitigation |
| Control Plan | hierarchyLevel | enum | Opsional | Wajib | Elimination/substitution/engineering/administrative/PPE [DESIGN catalog] |
| Control Plan | isRequired | boolean | Opsional | Wajib | Default true [DESIGN] |
| Control Plan | picMembershipId | UUID | Opsional | Wajib | Active membership same org |
| Control Plan | targetDate | date | Opsional | Opsional | Cannot be nonsensical; overdue tolerated with reason |
| Control Task | actionNote | text(10..3000) | N/A | Kond. | Required to request verification |
| Control Task | executionEvidenceIds | UUID[] | N/A | Kond. | Required if control policy demands evidence |
| Control Verification | decision | enum | N/A | Wajib | VERIFY / RETURN_FOR_REWORK |
| Control Verification | reviewNote | text(0..3000) | N/A | Kond. | Required when returning for rework |
| HIRADC Review | decision | enum | N/A | Wajib | APPROVE / REQUEST_REVISION / REJECT |
| HIRADC Review | reason | text(0..2000) | N/A | Kond. | Required for reject/request revision |
| HIRADC Review | assessmentVersionId | UUID | Auto | Auto | Target immutable candidate |
| HIRADC Review | expectedLockVersion | int | Auto | Auto | Optimistic lock |
| Official Export | riskVersionIds | UUID[] | N/A | Wajib | Only eligible approved immutable assessment versions |
| Official Export | title | string(3..200) | N/A | Wajib | Snapshot manifest title |
| Official Export | purposeNote | string(0..500) | N/A | Opsional | Reason for issuance |
6.7 Laporan Bahaya — Draft dan Submit
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Hazard Report | reportTypeId | catalog id | Opsional | Wajib | [DESIGN] UNSAFE_CONDITION/UNSAFE_ACTION/NEAR_MISS/INCIDENT, configurable |
| Hazard Report | severityId | catalog id | Opsional | Wajib | [DESIGN] LOW/MEDIUM/HIGH/CRITICAL; CRITICAL special acceptance |
| Hazard Report | areaId | enum | Opsional | Wajib | Four global K3 areas |
| Hazard Report | specificLocation | string(0..240) | Opsional | Opsional | Free text |
| Hazard Report | occurredAt | datetime | Opsional | Wajib | Default now editable; reject future > allowed clock skew [DESIGN] |
| Hazard Report | title | string(0..180) | Opsional | Wajib | Trim; readable summary |
| Hazard Report | description | text(0..5000) | Opsional | Wajib | What was observed; safety language |
| Hazard Report | immediateAction | text(0..3000) | Opsional | Opsional | Steps already taken, if any |
| Hazard Report | evidenceIds | UUID[] | Opsional | Opsional | Not mandatory for initial report [DESIGN] |
| Hazard Report | extraRecipientMembershipIds | UUID[] | Opsional | Opsional | Active same-org members, dedup |
| Hazard Report | reporterId | UUID | Auto | Auto | Session actor |
| Hazard Report | reportNumber | string | Auto | Auto | Generated on successful submit; not during draft |
| Hazard Report | acceptanceStatus | enum | Auto | Auto | PENDING_APPROVAL/ACCEPTED/REJECTED/NEEDS_CORRECTION |
| Hazard Report | handlingStatus | enum | Auto | Auto | UNASSIGNED/ASSIGNED/IN_PROGRESS/PENDING_VERIFICATION/COMPLETED/REOPENED |
6.8 Laporan Bahaya — Penanganan, Verifikasi, Revisi
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Report Review | decision | enum | N/A | Wajib | APPROVE/REQUEST_CORRECTION/REJECT |
| Report Review | reviewReason | text(0..2000) | N/A | Kond. | Required for correction/reject |
| PIC Assignment | picMembershipId | UUID | N/A | Wajib | Active membership and suitable permission |
| PIC Assignment | assignmentNote | text(0..2000) | N/A | Opsional | Audit event |
| PIC Assignment | expectedLockVersion | int | N/A | Wajib | Prevents concurrent active PIC conflict |
| Report Action | actionDescription | text(10..5000) | N/A | Wajib | Action performed |
| Report Action | evidenceIds | UUID[] | N/A | Opsional | Optional during process, see completion rule |
| Report Action | performedAt | datetime | Auto | Auto | Server timestamp; if actual time editable require reason [DESIGN] |
| Completion Request | completionNote | text(10..5000) | N/A | Wajib | Required completion statement |
| Completion Request | completionEvidenceIds | UUID[] | N/A | Kond. | At least one OR documented evidence exception |
| Completion Request | evidenceExceptionReason | text(20..1500) | N/A | Kond. | Required when no completion evidence |
| Completion Verification | decision | enum | N/A | Wajib | VERIFY/RETURN_FOR_REWORK |
| Completion Verification | verificationNote | text(0..3000) | N/A | Kond. | Required on return |
| Report Revision | revisionReason | text(10..2000) | N/A | Wajib | Must explain material change |
| Report Revision | changeSet | object | Opsional | Wajib | Field diff against current effective revision |
| Report Revision | newEvidenceIds | UUID[] | Opsional | Opsional | Version-scoped evidence |
| Report Note | body | text(1..3000) | N/A | Wajib | Append-only, never edit/delete once submitted |
| Reopen Report | reason | text(20..2000) | N/A | Wajib | Audited reopening |
6.9 LMS — Course, Lesson, Assessment Authoring
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Course | title | string(3..200) | Opsional | Wajib | Unique enough for catalog; global |
| Course | summary | text(0..3000) | Opsional | Wajib | Training description |
| Course | targetAreaIds | enum[] | Opsional | Opsional | Zero or more of four global areas |
| Course | preTestEnabled | boolean | Auto | Wajib | Default true [DESIGN] |
| Course | learnEnabled | boolean | Auto | Wajib | Default true [DESIGN] |
| Course | postTestEnabled | boolean | Auto | Wajib | Default true [DESIGN] |
| Course | stageOrder | array | Auto | Auto | Pre-Test, Learn, Post-Test, skip disabled |
| Course | courseVersion | int | Auto | Auto | Immutable published version |
| Lesson | stableLessonKey | string | Auto | Wajib | Stable across unchanged lessons; version linked |
| Lesson | title | string(3..180) | Opsional | Wajib | Trim |
| Lesson | content | rich text JSON | Opsional | Wajib | Sanitize HTML, media whitelist, min meaningful content |
| Lesson | mediaEvidenceIds | UUID[] | Opsional | Opsional | Global asset bucket logical scope |
| Lesson | isRequired | boolean | Auto | Wajib | Default true [DESIGN] |
| Lesson | sortOrder | int | Auto | Wajib | Unique within published course version |
| Lesson | fingerprint | hash | Auto | Auto | Deterministic canonical content + meaningful settings |
| Assessment | type | enum | Auto | Wajib | PRE_TEST or POST_TEST |
| Assessment | passingThreshold | int 0..100 | Opsional | Kond. | Default 80 for Post-Test; Pre-Test no passing gate |
| Assessment | attemptLimit | int 1..20 | Opsional | Wajib | Default 3 [PROVISIONAL], versioned; configurable |
| Assessment | shuffleQuestions | boolean | Auto | Wajib | Default false [DESIGN] |
| Assessment | shuffleOptions | boolean | Auto | Wajib | Default false [DESIGN] |
| Assessment | questionIdList | UUID[] | Opsional | Wajib | At least 1 active question |
| Assessment | fingerprint | hash | Auto | Auto | Includes questions, options, correct answers, weights, threshold, rules |
| Question | stem | text(5..5000) | Opsional | Wajib | Rich content sanitized |
| Question | type | enum | Auto | Wajib | MVP SINGLE_CHOICE [DESIGN] |
| Question | options | array(2..8) | Opsional | Wajib | At least two options and distinct IDs |
| Question | correctOptionId | UUID | Opsional | Wajib | Privileged authoring only; never participant API |
| Question | weight | positive number | Auto | Wajib | Default 1 [DESIGN]; normalised to percent |
| Publication | releaseNote | text(0..2000) | N/A | Opsional | Recommended for change trace |
| Publication | expectedDraftVersion | int | N/A | Wajib | Prevents publish race |
6.10 LMS — Participant dan Attempt
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Learning Progress | courseId | UUID | Auto | Auto | Global published course identity |
| Learning Progress | organizationId | UUID | Auto | Auto | Tenant progress |
| Lesson Completion | lessonKey | string | Auto | Wajib | Exact stable key and fingerprint published |
| Lesson Completion | fingerprint | hash | Auto | Auto | Server validates against current lesson |
| Assessment Start | assessmentVersionId | UUID | Auto | Wajib | Current published version, quota guarded |
| Assessment Start | idempotencyKey | UUID/string | Auto | Wajib | No duplicate start on retry |
| Assessment Answer | questionId | UUID | N/A | Wajib | Belongs to attempt snapshot |
| Assessment Answer | selectedOptionId | UUID | N/A | Wajib | One valid option of question |
| Assessment Answer | answerRevision | int | N/A | Wajib | Sequential update; prevents stale overwrite |
| Assessment Submit | attemptId | UUID | N/A | Wajib | One active attempt per config [DESIGN] |
| Assessment Submit | expectedLockVersion | int | N/A | Wajib | Prevent double submit |
| Assessment Result | score | 0..100 | Auto | Auto | Computed server-side |
| Assessment Result | passed | boolean | Auto | Auto | Post-test threshold; Pre-test diagnostic |
| Assessment Result | status | enum | Auto | Auto | IN_PROGRESS/SUBMITTED/ABANDONED/INTERRUPTED_BY_UPDATE |
6.11 Evidence, Audit, Email dan Support Grant
| Objek | Field | Tipe | Draft | Final/Submit | Validasi, default, dan ketentuan |
|---|---|---|---|---|---|
| Upload Init | filename | string(1..255) | N/A | Wajib | Strip path separators; escaped display |
| Upload Init | mimeType | string | N/A | Wajib | Allowlisted mime, server sniff after upload |
| Upload Init | sizeBytes | int | N/A | Wajib | [DESIGN] images <=10MB, PDF <=20MB; configurable |
| Upload Init | purpose | enum | N/A | Wajib | REPORT/RISK/CONTROL/LESSON; bucket/path policy |
| Evidence | objectKey | opaque | Auto | Auto | No user PII in object key; tenant scoped |
| Evidence | state | enum | Auto | Auto | PENDING/UPLOADED/ATTACHED |
| Evidence | sha256 | hex(64) | Auto | Auto | Integrity/hash validation on finalize [DESIGN] |
| AuditEvent | actorId | UUID | Auto | Auto | Authenticated actor |
| AuditEvent | organizationId | UUID? | Auto | Auto | Tenant or null global |
| AuditEvent | entityRef | string | Auto | Auto | Stable entity ID/version |
| AuditEvent | eventType | enum | Auto | Auto | Append-only typed event |
| Outbox | eventType | enum | Auto | Auto | Business event trigger |
| Outbox | recipients | email[] | Auto | Auto | Snapshot + dedup, enforce policy |
| Outbox | status | enum | Auto | Auto | PENDING/PROCESSING/SENT/FAILED |
| Support Request | organizationId | UUID | N/A | Wajib | Target tenant |
| Support Request | purpose | text(20..2000) | N/A | Wajib | Why support access needed |
| Support Request | resourceScope | resource ID[] | N/A | Wajib | Explicit resource list, not wildcard [DESIGN] |
| Support Request | requestedMinutes | int | N/A | Wajib | [PROVISIONAL] max 120, default 60 |
| Support Review | decision | enum | N/A | Wajib | APPROVE/REJECT |
| Support Review | reviewReason | text(0..2000) | N/A | Kond. | Mandatory on reject |
| Support Grant | expiresAt | timestamp | Auto | Auto | Time bound; server-now check each request |
| Support Grant | mode | enum | Auto | Auto | READ_ONLY only |
| Support Revocation | reason | text(10..1000) | N/A | Wajib | Audited revoke |
6.15 Field dependency dan conditional behavior
| Condition | UI effect | Server rule |
|---|---|---|
| Severity == CRITICAL | Warning jelas, email urgent flag, confirmation | Auto-ACCEPTED; handling belum selesai |
| Completion tanpa evidence | Field evidenceExceptionReason wajib |
Action ditolak jika kedua evidence dan exception kosong |
| Review == REJECT/REQUEST_REVISION | Kolom alasan wajib | Approval event mencatat reason |
| Semua kontrol wajib VERIFIED | Verified residual input dapat dibuka bagi Pengawas berizin | Server menghitung required-control set pada assessment version efektif |
postTestEnabled=false |
Stage Post-Test disembunyikan | Course completion tidak mengharuskan skor tes |
lessonFingerprint berubah |
Materi tampil perlu dibaca kembali | Completion lama disimpan sebagai histori, bukan completion current |
assessmentFingerprint berubah |
Informasi versi baru dan kuota assessment baru | Attempt aktif lama interrupted_by_update dan tidak mengurangi quota |
| Perubahan organisasi saat form dirty | Dialog batalkan perubahan atau tinggalkan form | Submit dengan konteks lama/wrong tenant ditolak |
| Support grant expired | Keluar dari mode support | Semua read, file signed URL issuance, data access ditolak |
6.16 Batasan data dan kontrak input
ID pada daftar harus referentially valid; organizationId diperiksa sesudah autentikasi; sensitive strings tidak masuk audit detail/log; HTML dari editor harus disanitasi dan link/file dikendalikan. Tanggal occurredAt dari pengguna memiliki sumber dan timezone eksplisit. Perubahan versi/approval memerlukan expectedLockVersion. Semua field submission yang optional benar-benar diterima sebagai null/absent oleh backend; jangan mengirim string "undefined" atau menyamakan angka nol dengan tidak terisi.
Permission Registry, Action Matrix & Delegation Rules
53 aksi dengan permission key, domain, scope, dan guard bisnis.
7.1 Aturan inti otorisasi [FINAL]
- Akses ditentukan dari effective permission + tenant context + record scope + business guard, bukan hanya string nama role. Contoh default role pada tabel ditandai
*sebagai pemetaan rekomendasi, bukan hak yang tidak dapat diubah oleh admin. - Satu user dapat mempunyai beberapa organisasi, tetapi satu membership hanya satu role aktif; kategori personel disimpan terpisah. Organization Admin tidak otomatis Pengawas; Admin K3 tidak otomatis tenant reader; Super Admin tidak otomatis tenant reader.
- Semua endpoint read dan mutation wajib menegakkan policy; frontend PermissionGate hanya aspek UX. Support Access tidak menambah role tenant dan selalu read-only.
- Untuk setiap perubahan izin, event audit merekam actor, role sebelum/sesudah, organisasi, reason bila relevan, waktu dan lock version.
7.2 Permission registry dan action matrix [DESIGN]
| Domain | Permission key | UI/aksi yang dilindungi | Guard tambahan wajib | Default contoh / kategori |
|---|---|---|---|---|
| Navigasi | organization.workspace.enter | Melihat workspace organisasi | Membership aktif pada tenant aktif | Operator/Pengawas/Org Admin* |
| Profil | account.profile.read | Membaca profil sendiri | Subject = actor | Semua akun |
| Profil | account.profile.update | Mengubah profil sendiri | Whitelist field non-privileged | Semua akun |
| HIRADC | hiradc.risk.read | Melihat Risk Register dan detail risiko | Tenant scope, status/visibility | Operator/Pengawas* |
| HIRADC | hiradc.proposal.create | Membuat usulan bahaya | Membership aktif; pembuat dari session | Operator/Pengawas* |
| HIRADC | hiradc.proposal.read | Melihat usulan | Own atau permission org-wide | Operator/Pengawas* |
| HIRADC | hiradc.proposal.review | Menilai usulan | Proposal valid, transaction guard | Pengawas* |
| HIRADC | hiradc.assessment.create | Membuat draft penilaian formal | Domain capability, record scoped | Pengawas* |
| HIRADC | hiradc.assessment.update-draft | Mengubah assessment draft | Owner/authorized editor; lockVersion | Pengawas* |
| HIRADC | hiradc.assessment.submit | Mengajukan assessment | Field lengkap, K×P valid | Pengawas* |
| HIRADC | hiradc.assessment.review | Setuju/tolak/minta revisi | No self-approval terhadap pembuat risiko maupun versi | Pengawas* |
| HIRADC | hiradc.control.assign | Menugaskan kontrol | Satu PIC aktif, target org membership | Pengawas* |
| HIRADC | hiradc.control.execute | Catat tindakan pengendalian | PIC atau delegasi valid | Operator/Pengawas* |
| HIRADC | hiradc.control.verify | Verifikasi kontrol | Pengawas berizin; self-verification PIC diperbolehkan | Pengawas* |
| HIRADC | hiradc.residual.verify | Tetapkan verified residual | Semua kontrol wajib VERIFIED; K/P baru diisi | Pengawas* |
| HIRADC | hiradc.version.read | Lihat versi assessment | Tenant, record visibility | Operator/Pengawas* |
| HIRADC | hiradc.export.issue | Terbitkan PDF resmi | Hanya approved version; snapshot immutable | Pengawas* |
| Laporan Bahaya | hazard.report.read | Membaca daftar/detail | Tenant scope plus record visibility | Operator/Pengawas* |
| Laporan Bahaya | hazard.report.create | Membuat draft laporan | Active membership | Operator/Pengawas* |
| Laporan Bahaya | hazard.report.submit | Submit draft laporan | Validasi final, atomic numbering | Operator/Pengawas* |
| Laporan Bahaya | hazard.report.review | Menerima, koreksi, menolak | Pengawas berizin; self-approval diperbolehkan | Pengawas* |
| Laporan Bahaya | hazard.report.assign | Menetapkan/mengganti PIC | Satu PIC aktif, valid membership | Pengawas* |
| Laporan Bahaya | hazard.report.action | Catat tindakan | PIC berwenang; current effective revision | Operator/Pengawas* |
| Laporan Bahaya | hazard.report.request-verification | Ajukan hasil penanganan | Catatan + evidence atau exception | PIC/authorized* |
| Laporan Bahaya | hazard.report.verify | Verifikasi penyelesaian | Pengawas berizin, guard state | Pengawas* |
| Laporan Bahaya | hazard.report.revise | Ajukan revisi substantif | Accepted report; current effective unchanged | Reporter/authorized* |
| Laporan Bahaya | hazard.revision.review | Approve/reject revisi | Review submission version candidate | Pengawas* |
| Laporan Bahaya | hazard.report.note.append | Catatan tambahan | Append-only termasuk completed | Authorized member* |
| Laporan Bahaya | hazard.report.reopen | Buka kembali | Alasan wajib, history event | Pengawas* |
| LMS Peserta | learning.course.read | Buka katalog/lesson | Global published course; enrolled tenant context | Operator/Pengawas/Org Admin* |
| LMS Peserta | learning.lesson.complete | Tandai lesson selesai | Current course/lesson fingerprint | Peserta* |
| LMS Peserta | learning.attempt.start | Mulai Pre/Post-Test | Prerequisites + quota + version check | Peserta* |
| LMS Peserta | learning.attempt.answer | Kirim/perbarui jawaban attempt | Owner attempt, version, question membership | Peserta* |
| LMS Peserta | learning.attempt.submit | Submit assessment | Owner, quota, idempotent scoring | Peserta* |
| LMS Peserta | learning.history.self | Lihat histori pribadi | Actor = participant | Peserta* |
| LMS Monitoring | learning.participant.monitor | Monitor progres tenant | Only org active + permitted participant set | Pengawas/Org Admin* |
| Org Admin | organization.read | Lihat profil organisasi | Membership current tenant | Org Admin* |
| Org Admin | organization.members.manage | Kelola membership | Delegation ceiling, last org admin guard | Org Admin* |
| Org Admin | organization.invitation.manage | Undang/cabut/kirim ulang | Cannot grant role above ceiling | Org Admin* |
| Org Admin | organization.role.read | Lihat role lokal | Tenant scoped | Org Admin* |
| Org Admin | organization.role.manage | Ubah permission lokal | Cannot escalate own ceiling or manage global template | Org Admin* |
| Support Access | support.request.create | Request tenant scoped access | Global platform role; explicit resources | Super Admin |
| Support Access | support.request.review | Approve/reject support request | Org Admin target tenant; not requester | Org Admin* |
| Support Access | support.grant.read | Read granted records | Approved grant + resource + time + read-only | Super Admin via grant |
| Support Access | support.grant.revoke | Revoke grant | Approving org authority or authorized platform abort | Org Admin*/Platform* |
| Platform | platform.organization.manage | Kelola metadata tenant | No business data read by default | Super Admin |
| Platform | platform.account.manage | Manage platform role/account | No tenant membership escalation | Super Admin |
| Platform | platform.role-template.manage | Kelola template RBAC global | Versioned; org template copy policy | Super Admin |
| K3 Content | content.course.manage | Create/edit course global draft | Global-only resource | Admin K3 |
| K3 Content | content.lesson.manage | Create/edit lesson | Stable lesson key/fingerprint | Admin K3 |
| K3 Content | content.assessment.manage | Question/answer authoring | Answer keys only privileged endpoints | Admin K3 |
| K3 Content | content.course.publish | Publish course version | Compare fingerprints, atomic update | Admin K3 |
| Audit | audit.business.read | Baca event bisnis yang diizinkan | Tenant visibility or platform global metadata | Authorized auditor* |
7.3 Delegation ceiling & non-escalation
Setiap role lokal memiliki permission efektif subset dari role delegation ceiling milik administrator yang menugaskan. Administrator tidak boleh: memberikan hak yang ia tidak boleh delegasikan, mengubah role dirinya untuk memperoleh hak yang melebihi ceiling, menghapus/menonaktifkan Organization Admin aktif terakhir, memberi platform role melalui tenant role editor, atau mengaktifkan grant Support Access tanpa keputusan target Org Admin.
7.4 UI matrix dan navigation
- Menu ditampilkan apabila setidaknya satu screen/action dalam kategori dapat diakses; tindakan dikontrol terpisah oleh izin spesifik. Jangan tampilkan tombol approval hanya karena user dapat membaca record.
- Dashboard KPI menghitung hanya item yang boleh diakses oleh aktor; jangan menyimpulkan permission dari angka agregat. Filter tenant wajib ikut dalam cache key
['org',organizationId,'user',userId,...]. - Personel yang menerima email berdasarkan klasifikasi Operator/Pengawas tidak otomatis memperoleh hak mutation; read permission harus konsisten agar deep link tidak dead end. Saat perlu, buat scoped read permission dengan tujuan dan audit yang benar, bukan bypass.
7.5 Support Access action limitations
Grant memuat tenant, requester, approving admin, enumerasi resource yang disetujui, valid-from/expiry/revocation, dan READ_ONLY. Tidak boleh wildcard secara default, tidak berlaku ke tenant lain, dan tidak boleh mengedit/export luas kecuali download/preview spesifik tercakup eksplisit. Setiap operasi read menilai grant yang masih aktif pada saat request, sekalipun browser masih menyimpan halaman sebelumnya.
State Transition Matrix & Business Invariants
40 perubahan status dan guard untuk HIRADC, Laporan Bahaya, LMS, Support Access.
8.1 Sumber kebenaran state
State disimpan di backend untuk MVP. UI membaca kode yang stabil, menerjemahkan via status registry code/label/color/description/allowedActions, dan tidak menentukan perubahan state dengan mutasi lokal. Transition = satu transaksi DB untuk record + version pointer/assignment + audit/outbox yang terkait. Mock prototype menirukan transaksi tersebut dalam state update tunggal.
8.2 State transition matrix [DESIGN]
| Object | Status awal | Event/aksi | Status hasil | Precondition/guard | Side effect |
|---|---|---|---|---|---|
| Hazard Report | DRAFT | SAVE_DRAFT | DRAFT | owner; partial validation | Tidak buat nomor/email |
| Hazard Report | DRAFT | SUBMIT noncritical | PENDING_APPROVAL | submit complete, idempotent | Nomor, versi efektif, audit, outbox |
| Hazard Report | DRAFT | SUBMIT critical | ACCEPTED | severity=CRITICAL | Accepted tanpa selesai; urgent outbox |
| Acceptance | PENDING_APPROVAL | APPROVE | ACCEPTED | hazard.report.review | Buka assignment queue |
| Acceptance | PENDING_APPROVAL | REQUEST_CORRECTION | NEEDS_CORRECTION | reason wajib | Tetap belum accepted |
| Acceptance | PENDING_APPROVAL | REJECT | REJECTED | reason wajib | Immutable event, stop handling |
| Acceptance | NEEDS_CORRECTION | RESUBMIT | PENDING_APPROVAL | complete and scoped | new submission event |
| Handling | UNASSIGNED | ASSIGN_PIC | ASSIGNED | one active PIC; accepted | assignment start + audit |
| Handling | ASSIGNED | START_WORK | IN_PROGRESS | current PIC/authorized | timestamp |
| Handling | IN_PROGRESS | REQUEST_VERIFICATION | PENDING_VERIFICATION | note + evidence or exception | freeze submission content |
| Handling | PENDING_VERIFICATION | VERIFY | COMPLETED | hazard.report.verify | history + completedAt |
| Handling | PENDING_VERIFICATION | RETURN | IN_PROGRESS | reason wajib | rework event |
| Handling | COMPLETED | REOPEN | REOPENED | authorized; reason wajib | history persists |
| Handling | REOPENED | ASSIGN_OR_CONTINUE | IN_PROGRESS | PIC valid | continuation event |
| Report Revision | none | CREATE_CANDIDATE | DRAFT | accepted report | does not replace effective |
| Report Revision | DRAFT | SUBMIT | PENDING_REVIEW | revision reason, valid diff | effective unchanged |
| Report Revision | PENDING_REVIEW | APPROVE | APPROVED | hazard.revision.review | atomic effective pointer swap |
| Report Revision | PENDING_REVIEW | REQUEST_CORRECTION | NEEDS_CORRECTION | reason | effective unchanged |
| Report Revision | PENDING_REVIEW | REJECT | REJECTED | reason | effective unchanged |
| Risk Proposal | DRAFT | SUBMIT | SUBMITTED | core hazard details valid | proposal event |
| Risk Proposal | SUBMITTED | ACCEPT | ACCEPTED | hiradc.proposal.review | eligible formal assessment |
| Risk Proposal | SUBMITTED | REJECT | REJECTED | reason wajib | event immutable |
| HIRADC Assessment | DRAFT | SUBMIT_REVIEW | PENDING_REVIEW | K/P/controls valid | version snapshot frozen for review |
| HIRADC Assessment | PENDING_REVIEW | APPROVE | APPROVED | no self approve, permission | effective pointer and audit |
| HIRADC Assessment | PENDING_REVIEW | REQUEST_REVISION | NEEDS_REVISION | reason wajib | effective previous unchanged |
| HIRADC Assessment | PENDING_REVIEW | REJECT | REJECTED | reason wajib | effective previous unchanged |
| Control Task | TO_DO/ASSIGNED | START | IN_PROGRESS | PIC/authorized | task event |
| Control Task | IN_PROGRESS | REQUEST_VERIFICATION | PENDING_VERIFICATION | action note and policy evidence | submitted action snapshot |
| Control Task | PENDING_VERIFICATION | VERIFY | VERIFIED | supervisor permission | verification event |
| Control Task | PENDING_VERIFICATION | RETURN | NEEDS_REWORK | reason wajib | rework event |
| Control Task | NEEDS_REWORK | RESUME | IN_PROGRESS | PIC/authorized | new action revision |
| Verified Residual | NOT_VERIFIED | VERIFY_RESIDUAL | VERIFIED | ALL required controls VERIFIED | distinct measured K,P and audit |
| LMS Attempt | NONE | START | IN_PROGRESS | prerequisites + quota | quota reservation counted |
| LMS Attempt | IN_PROGRESS | SUBMIT | SUBMITTED | version current, answers valid | atomic score/result |
| LMS Attempt | IN_PROGRESS | ABANDON | ABANDONED | explicit exit/expiry policy | quota still counts |
| LMS Attempt | IN_PROGRESS | PUBLISH_CHANGED | INTERRUPTED_BY_UPDATE | assessment fingerprint changed | quota refunded/excluded |
| Course Draft | DRAFT | PUBLISH | PUBLISHED VERSION +1 | valid lessons/assessments | atomic global pointer change |
| Support Request | REQUESTED | APPROVE | GRANTED | tenant Org Admin reviewer | read-only, scoped, expiry |
| Support Request | REQUESTED | REJECT | REJECTED | reason wajib | no grant |
| Support Grant | GRANTED | EXPIRE/REVOKE | EXPIRED/REVOKED | time or authorization | block further read and signed URL creation |
8.3 Irregular transitions
- HARUS TOLAK: double approval pada version yang sudah diputuskan; publish dua draft yang sama; duplikasi PIC aktif; submit attempt dua kali; self-approve HIRADC; submit atas organisasi lain; verified residual jika masih ada kontrol wajib non-VERIFIED.
- RETRY AMAN: timeout setelah server berhasil, memakai idempotency key yang sama; API mengembalikan hasil operasi pertama atau status final saat lookup. Jangan menaikkan version atau mengirim email berulang.
- KONFLIK:
expectedLockVersiontidak cocok menghasilkan 409VERSION_CONFLICTdengan metadata aman (currentVersion,updatedAt,retryHint); halaman harus reload/compare sebelum retry. - VERSI EFEKTIF: hanya versi APPROVED/ACCEPTED yang dipakai operasional. Candidate draft/pending tidak menggantikan data efektif. Historic notes/attachments pada versi lama tetap dapat dilihat sesuai izin.
8.4 Status presentation registry (contoh desain)
| Domain code | Label UI (Indonesia) | Semantic color | Catatan |
|---|---|---|---|
| DRAFT | Draft | Neutral | Belum final |
| PENDING_APPROVAL / PENDING_REVIEW | Menunggu Persetujuan | Warning | Menunggu keputusan |
| ACCEPTED / APPROVED | Disetujui/Diterima | Success | Belum tentu penanganan completed |
| NEEDS_CORRECTION / NEEDS_REVISION | Perlu Perbaikan | Warning | Alasan terlihat |
| REJECTED | Ditolak | Critical | Record tetap histori |
| IN_PROGRESS | Dalam Penanganan | Information | PIC/tindakan aktif |
| PENDING_VERIFICATION | Menunggu Verifikasi | Warning | Submission menunggu review |
| COMPLETED / VERIFIED | Selesai / Terverifikasi | Success | Event tersimpan |
| INTERRUPTED_BY_UPDATE | Terhenti karena Pembaruan | Neutral | Attempt tidak dihitung kuota |
8.5 Event audit minimum
Setiap event final mempunyai eventId, actorId, organizationId?, entityType, entityId, versionId?, action, prevState, nextState, occurredAt, requestId, reason?. Event audit append-only; catatan baru menambah baris, bukan update lama. Simpan diff field non-sensitif bila perlu tanpa menyimpan kredensial, OTP, atau raw answer key.
API, DTO, Error Envelope & Payload Examples
Inventaris 35 endpoint representatif dan contoh payload lintas modul.
9.1 API style and routing policy
- NestJS REST base
/api/v1; browser memakai Next.js tRPC BFF sebagai pola pilihan [PROVISIONAL DESIGN]. Backend tetap memverifikasi session,X-Organization-Idyang dinormalisasi BFF, membership, permission, record ownership, RLS. Header dari client publik tidak dipercaya. - OpenAPI versioned artifact menjadi kontrak antarrepository; request/response dan error typed; jangan menyalin DTO manual tanpa contract test.
- Default pagination cursor/limit untuk daftar besar; limit
1..100[DESIGN], sort key whitelist; semua filter server-side juga scoped pada tenant.
9.2 Endpoint inventory (representatif dan normatif secara fungsi, path exact [DESIGN])
| Method | NestJS endpoint | Input/output inti | Guard |
|---|---|---|---|
| GET | /api/v1/session | current session | Authenticated |
| POST | /api/v1/auth/login | email + password | CSRF, rate-limit |
| POST | /api/v1/auth/logout | none | session revoke |
| GET | /api/v1/organizations/mine | memberships | No cross org data |
| GET | /api/v1/hiradc/risks | filters + page | hiradc.risk.read |
| POST | /api/v1/hiradc/proposals | proposal DTO | hiradc.proposal.create |
| POST | /api/v1/hiradc/assessments | assessment DTO | assessment.create |
| POST | /api/v1/hiradc/assessments/{id}/submit | version + idempotency | assessment.submit |
| POST | /api/v1/hiradc/assessments/{id}/review | decision/reason/version | No self approval |
| POST | /api/v1/hiradc/controls/{id}/verify | decision, evidence, version | Permission and status |
| POST | /api/v1/hiradc/risks/{id}/verify-residual | K,P/version | All required controls verified |
| POST | /api/v1/hiradc/exports | approved version set | Immutable snapshot |
| GET | /api/v1/hazard-reports | filter + page | hazard.report.read |
| POST | /api/v1/hazard-reports/drafts | partial DTO | createDraft |
| PATCH | /api/v1/hazard-reports/drafts/{id} | partial DTO + version | saveDraft |
| POST | /api/v1/hazard-reports/drafts/{id}/submit | idempotency/version | report submit |
| POST | /api/v1/hazard-reports/{id}/review | decision/reason | review |
| POST | /api/v1/hazard-reports/{id}/assignment | PIC/version | one active PIC |
| POST | /api/v1/hazard-reports/{id}/actions | note/evidence | PIC |
| POST | /api/v1/hazard-reports/{id}/verify | decision/reason | verification |
| POST | /api/v1/hazard-reports/{id}/revisions | candidate diff | effective unchanged |
| GET | /api/v1/learning/courses | published only | tenant learner |
| POST | /api/v1/learning/lessons/{id}/complete | fingerprint | manual complete |
| POST | /api/v1/learning/attempts | version + idempotency | quota locked |
| PATCH | /api/v1/learning/attempts/{id}/answers | question and answer revision | owner + current version |
| POST | /api/v1/learning/attempts/{id}/submit | version/idempotency | single score |
| GET | /api/v1/learning/monitoring | course and participant filters | tenant scoped |
| POST | /api/v1/content/courses/{id}/publish | draft/version | global Admin K3 |
| POST | /api/v1/admin/members/invite | email/role/org | delegation ceiling |
| PATCH | /api/v1/admin/members/{id}/role | role/version | last-admin guard |
| POST | /api/v1/support/requests | purpose/scope/duration | platform requester |
| POST | /api/v1/support/requests/{id}/review | decision/reason | target tenant Org Admin |
| GET | /api/v1/support/grants/{id}/resources/{resourceId} | grant ID | strict read only |
| POST | /api/v1/files/uploads | filename/mime/size/purpose | presigned URL issuance |
| POST | /api/v1/files/{id}/finalize | file checksum + version | PENDING→UPLOADED |
9.3 Standard successful result envelope
{
"data": { "id": "record-123", "status": "PENDING_REVIEW", "lockVersion": 3 },
"meta": { "requestId": "req-abc", "serverTime": "2026-11-05T09:00:00Z" }
}
9.4 Standard domain error envelope
{
"error": {
"code": "VERSION_CONFLICT",
"message": "Data telah diperbarui oleh pengguna lain.",
"details": { "currentLockVersion": 4 },
"requestId": "req-abc"
}
}
Jangan masukkan stack trace, email/OTP/raw cookie, maupun existence detail untuk record tenant yang tidak berizin ke error response.
9.5 Payload Laporan Bahaya - submit
{
"organizationId": "org-a",
"draftId": "draft-118",
"expectedLockVersion": 1,
"idempotencyKey": "03d6cf5e-9d18-4a19-9ed5-fdeec3ad9d6a",
"report": {
"reportTypeId": "UNSAFE_CONDITION",
"severityId": "CRITICAL",
"areaId": "AREA_DISINFECTION",
"specificLocation": "Ruang klorinasi",
"occurredAt": "2026-11-05T09:00:00Z",
"title": "Kondisi berbahaya pada sistem desinfektan",
"description": "Deskripsi faktual hasil pengamatan petugas.",
"immediateAction": "Area dibatasi dan pengawas dihubungi.",
"evidenceIds": ["ev-001"],
"extraRecipientMembershipIds": ["mem-009"]
}
}
Expected: acceptanceStatus=ACCEPTED, handlingStatus=UNASSIGNED, generated report number, new effective revision, audit event, urgent outbox; tidak langsung COMPLETED.
9.6 Payload HIRADC - submit version
{
"organizationId": "org-a",
"riskId": "risk-001",
"assessmentVersionId": "assess-003",
"expectedLockVersion": 2,
"idempotencyKey": "d48d7139-0eb6-402e-82db-82e9d111c040",
"assessment": {
"areaId": "AREA_PROCESS_CHEMICAL",
"workActivity": "Penyiapan bahan kimia proses",
"hazardSource": "Paparan bahan kimia dalam penyiapan",
"riskEvent": "Paparan tidak disengaja",
"potentialImpact": "Gangguan keselamatan operator",
"existingControls": "Instruksi kerja dan APD sesuai prosedur",
"initialLikelihoodK": 4,
"initialSeverityP": 4,
"plannedResidualK": 2,
"plannedResidualP": 3,
"assessmentRationale": "Berdasarkan kondisi pekerjaan dan kontrol yang direncanakan",
"controlIds": ["control-01", "control-02"]
}
}
Expected: skor awal 16/Very High; planned residual 6/Moderate; verified residual tetap belum ada. Assessment version menjadi pending review; keputusan harus oleh reviewer yang bukan pencipta record/versi.
9.7 Payload approval dan concurrency
{
"organizationId": "org-a",
"assessmentVersionId": "assess-003",
"decision": "APPROVE",
"reason": null,
"expectedLockVersion": 3,
"idempotencyKey": "f4e22221-40c8-4ad2-962d-fb67cdd937a3"
}
Guard: hiradc.assessment.review, reviewer bukan creator risiko/versi, still pending, version lock cocok; update pointer effective + audit dalam satu transaction.
9.8 Payload LMS attempt start & submit
{
"organizationId": "org-a",
"courseId": "course-gas-klor",
"assessmentVersionId": "post-v2",
"idempotencyKey": "04d6d12e-63b3-48bb-9b0a-e0cd2c4a03f0"
}
{
"organizationId": "org-a",
"attemptId": "attempt-003",
"expectedLockVersion": 5,
"idempotencyKey": "b42c7212-a183-4370-b57a-8d810b35ed49",
"answers": [
{ "questionId": "q1", "selectedOptionId": "opt-b" },
{ "questionId": "q2", "selectedOptionId": "opt-c" }
]
}
Backend menghitung score dan menyimpan answer snapshot yang berkaitan dengan assessment version. Answer key tidak pernah dimasukkan ke participant-facing payload.
9.9 Upload presign response (illustrative)
{
"data": {
"evidenceId": "ev-001",
"method": "PUT",
"uploadUrl": "https://storage.example.invalid/signed/object?...",
"requiredHeaders": { "Content-Type": "image/jpeg" },
"expiresInSeconds": 300,
"state": "PENDING"
}
}
Signed URL memakai host yang sesuai dengan signature dan dilayani Nginx langsung ke Garage; query string, method dan signed headers tidak boleh dimodifikasi client/proxy. example.invalid adalah placeholder, bukan URL produksi.
9.10 BFF session mapping
Next.js membaca host-only HttpOnly session cookie, meneruskan credential ke NestJS dengan mekanisme internal aman, mengambil organizationId eksplisit dari tRPC operation, melakukan input validation, lalu NestJS menegakkan scope. BFF tidak boleh membuat header X-Organization-Id dari state sembarang tanpa validasi business session; NestJS tetap menolak header palsu. All unsafe mutations CSRF protected.
Database Architecture, Schema & PostgreSQL RLS
Peta 39 tabel logis, composite FK, transaction isolation, migration, dan kontrol RLS.
10.1 Design principles
Relational PostgreSQL adalah single source of truth untuk sesi, organisasi, transaksi domain, LMS, outbox dan audit. pgvector tersedia tetapi fitur AI di luar MVP. Gunakan migration versioned, foreign key, NOT NULL dan CHECK/UNIQUE untuk invariants yang dapat ditegakkan DB. Public IDs sebaiknya UUID/ULID; official_no urutan bisnis yang diterbitkan di dalam transaksi dan unik per tenant.
10.2 Logical schema dan tabel utama [DESIGN]
| Domain | Table | Kolom/indeks penting | Aturan integritas |
|---|---|---|---|
| Identity | users | id PK, email UNIQUE CITEXT, password_hash, status, created_at | Global identity, no tenant scope |
| Identity | sessions | id PK, user_id FK, token_hash UNIQUE, idle_expires_at, absolute_expires_at | Opaque session token hashed at rest |
| Identity | password_reset_otps | user_id, code_hash, expires_at, used_at, attempt_count | 5 min, 6 digits; one-time use |
| Tenant | organizations | id PK, slug UNIQUE, name, status, created_at | Global metadata |
| Tenant | memberships | id PK, user_id, organization_id, role_id, status | UNIQUE(user_id,organization_id), composite tenant FKs |
| RBAC | permissions | key PK, domain, description, delegable | Global registry |
| RBAC | role_templates | id, version, category, content_hash | Global, immutable template versions |
| RBAC | organization_roles | id, organization_id, name, personnel_category, ceiling | Tenant role definitions |
| RBAC | organization_role_permissions | organization_id, role_id, permission_key | Composite FK, unique role+permission |
| HIRADC | risk_proposals | id, organization_id, area_id, creator, status, data | Owner/scope RLS |
| HIRADC | risk_records | id, organization_id, current_approved_version_id, created_by | One risk per record |
| HIRADC | assessment_versions | id, organization_id, risk_id, version_no, status, score fields, lock_version | UNIQUE(risk_id,version_no); approved immutable |
| HIRADC | risk_controls | id, organization_id, risk_id, stable_control_key, required | Plan/control identity across versions |
| HIRADC | assessment_version_controls | organization_id, version_id, control_id, controls snapshot | Version references |
| HIRADC | control_tasks | id, organization_id, control_id, pic_membership_id, status, version | Execution continuity |
| HIRADC | control_events | id, organization_id, control_task_id, action, actor, payload | Append-only |
| HIRADC | risk_exports | id, organization_id, manifest_json, object_id, issued_by | Immutable approved snapshot |
| Hazard | hazard_reports | id, organization_id, official_no, reporter_id, acceptance, handling, effective_revision_id | Unique tenant report no; lock version |
| Hazard | hazard_revisions | id, organization_id, report_id, version_no, status, data | Unique report/version; immutable accepted snapshot |
| Hazard | report_assignments | id, organization_id, report_id, pic_id, started_at, ended_at | Partial UNIQUE(report_id) WHERE ended_at IS NULL |
| Hazard | report_actions | id, organization_id, report_id, author, body, evidence refs | Append-only |
| Hazard | report_verifications | id, organization_id, report_id, decision, note | Append-only |
| Hazard | report_notes | id, organization_id, report_id, body, author | Append-only even completed |
| LMS Global | courses | id PK, slug, title, current_published_version_id | No tenant ownership |
| LMS Global | course_versions | id, course_id, version_no, status, config, fingerprint | Immutable published |
| LMS Global | lesson_versions | id, stable_key, course_version_id, content, fingerprint | Global immutable published version |
| LMS Global | assessment_versions | id, course_version_id, type, config, fingerprint | No participant answer key access |
| LMS Global | question_versions | id, assessment_version_id, stem, options, correct_answer, weight | Authoring security boundary |
| LMS Tenant | course_progress | id, organization_id, membership_id, course_id, current_version | Tenant scope; no forced mass reset |
| LMS Tenant | lesson_completions | id, organization_id, user_id, course_id, lesson_key, fingerprint | Unique completion identity by fingerprint |
| LMS Tenant | assessment_attempts | id, organization_id, user_id, assessment_version_id, status, lock_version | Atomic quota reservation; version pinned |
| LMS Tenant | attempt_answers | id, organization_id, attempt_id, question_id, option_id, answer_revision | Attempt owner, encrypted/privacy controls if needed |
| Storage | file_objects | id, organization_id?, purpose, object_key, mime, size, state, sha256 | PENDING/UPLOADED/ATTACHED, global or tenant scoped |
| Storage | file_attachments | id, organization_id?, file_id, entity_type, entity_id, version_id | File and target same scope |
| Audit | business_audit_events | id, organization_id?, entity_ref, actor, action, payload, occurred_at | Append-only, least-data |
| outbox_messages | id, event_id, event_type, payload, state, next_attempt_at | Enqueued in business transaction | |
| outbox_delivery_attempts | id, outbox_id, recipient_hash, attempt, response | Troubleshooting without sensitive content | |
| Support | support_requests | id, organization_id, requester_id, scope, purpose, status | Target tenant approves |
| Support | support_grants | id, request_id, approver_id, scopes, expires_at, revoked_at | Read-only and expiring |
10.3 Tenant keys and RLS
- Pada setiap tabel tenant, gunakan
organization_idNOT NULL, indeks(organization_id, status, created_at)atau indeks komposit sesuai pola query, dan composite FK pada semua relasi tenant agar join antar-tenant tidak mungkin secara struktural. - Application DB role bukan owner tabel dan tidak memiliki
BYPASSRLS; migration menggunakan role terpisah. TerapkanENABLE ROW LEVEL SECURITYdanFORCE ROW LEVEL SECURITYjika owner path masih mungkin, beserta explicit USING/WITH CHECK untuk SELECT/INSERT/UPDATE/DELETE yang perlu. - Saat request tenant: ambil DB connection dalam transaction; setelah server memvalidasi session & membership, jalankan
SELECT set_config('app.organization_id', $1, true)atauSET LOCALdi transaction yang sama, serta context user/grant bila perlu. Query dan commit dijalankan dalam transaction tersebut; pool connection tidak boleh mewarisi context request sebelumnya. - RLS bukan satu-satunya authorization: NestJS policy memeriksa effective permission + ownership + workflow state dan support grant scopes. Super Admin tidak diberi bypass tenant RLS untuk support access. Jika support read menuntut mekanisme berbeda, rancang narrowly-scoped database function/service with audit and deny-write, lalu uji secara khusus.
- Background worker menggunakan service account dengan kebijakan spesifik dan audit; jangan memakainya untuk melakukan operasi lintas tenant generik tanpa scoping.
10.4 Transaksi dan locking
submitHazardReport: validasi draft, row lock, nomor resmi unik, accepted if critical, current revision, audit, outbox satu transaksi.reviewHIRADC: lock assessment version + risk pointer, enforce reviewer not creator of risk/version, conditional status update, append audit, commit.reassignPIC: close existing active assignment + insert new active within transaction; partial unique index rejects races.verifyResidual: evaluate all required controls in consistent transaction snapshot, lock relevant version/task, then store verified K/P and audit.startAttempt: lock participant+assessment quota row or equivalent serialized key, count in-progress/submitted/abandoned, exclude interrupted-by-update, insert once by idempotency.publishCourse: validate draft, lock course, snapshot lessons+assessment fingerprint, atomically switch published version pointer, queue optional event; avoid mass-update all participants synchronously.
10.5 Immutable history
Status workflow changes may update mutable head/pointer rows, but approved assessment versions, accepted report revisions, published lessons/questions, issued PDF manifests, and audit/note/event rows are immutable. Do not soft-delete or hard-delete business history through generic cleanup task. Revocation and expiry append new event or update narrow revocation status without rewriting original approval facts.
10.6 Migration safety
One-shot migration Compose job using separate privileged DB credential; take backup first for production; schema changes expand -> deploy -> contract, keep rollback window. Explicit migration lock and do not allow two concurrent migration jobs. API readiness blocks until schema compatible; never silently run destructive migration at every container startup.
Authentication, Authorization, Security & Privacy
Session cookie, OTP, RBAC policy chain, CSRF/XSS, storage access, dan security go-live gates.
11.1 Session model [FINAL]
- Akun invitation-only; tidak ada registrasi publik.
- Email/password; hash password dengan Argon2id atau alternatif modern yang ditinjau [DESIGN].
- Session opaque token dalam cookie
HttpOnly,Securepada HTTPS,SameSiteyang sesuai threat model, host-only cookie (tanpaDomainuntuk wildcard tenant), masa idle 60 menit dan absolute 7 hari. - Sesi disimpan PostgreSQL dengan hash token dan metadata security minimum; idle expiry digeser melalui aturan throttled refresh yang aman; logout/revoke mengakhiri akses di server.
- Reset password memakai OTP 6 digit berlaku 5 menit, hash OTP, batas percobaan, single-use, anti-enumeration dan throttle permintaan. Bukan jawaban rahasia.
11.2 Authentication sequence
Login page -> tRPC BFF -> NestJS /auth/login -> PostgreSQL session
-> response Set-Cookie host-only; BFF forwards approved cookie flags
-> workspace resolver -> active membership/global role -> dashboard
Auth middleware menangani routing untuk UX, tetapi server policy dan PostgreSQL RLS tetap sumber enforcement. Session yang sudah kedaluwarsa harus menolak request walaupun UI masih terbuka. Cookie tidak boleh dicatat di request logs.
11.3 CSRF, XSS dan web security
Mutasi cookie-auth wajib menggunakan perlindungan CSRF (token synchronizer/double-submit dengan desain yang sesuai atau strict origin checks + defense in depth) dan validasi Origin/Host, bukan hanya SameSite. Terapkan CSP yang sesuai Next.js dan media editor, sanitasi konten rich text Tiptap, safe HTML rendering, output escaping, dan anti-clickjacking. Rate-limit login, OTP, review, file sign, attempt start, dan operasi sensitif. CORS untuk presigned Garage hanya pada origin Sikathree yang diizinkan dan required methods/headers.
11.4 Authorization model
Evaluasi session -> platform role or tenant membership -> effective permissions -> record scope/ownership -> workflow guard -> RLS. Apabila langkah mana pun gagal, fail closed. Audit setiap grant, review, role change, approval, publish, export dan support-read yang diwajibkan. Semua resource endpoints memakai opaque IDs; invalid cross tenant ID menghasilkan 404/403 yang tidak membocorkan eksistensi resource.
11.5 Security and privacy handling
- Jangan masukkan password, OTP, session, full presigned URL, answer key, PII sensitif, atau raw rich text berbahaya pada log JSON.
- Link evidence harus signed, short TTL, scope-aware, dan diregenerasi setelah grant/konteks berubah. Download endpoint menerapkan authorization saat menerbitkan URL; expired grant tidak boleh menerbitkan URL baru. Catat bahwa URL yang telah diterbitkan mungkin valid hingga TTL selesai; gunakan TTL pendek dan gateway controls untuk risiko revoke instan.
- Global Admin K3 melihat content, bukan nilai peserta; Platform Super Admin melihat metadata, bukan konten tenant. Khusus support access, tenant admin menyetujui resource, waktu, dan tujuan spesifik.
- File types diverifikasi oleh backend setelah upload; antivirus/malware scanning bila risiko dan kebijakan operasional menuntutnya [GATE evaluasi].
11.6 Security testing [GATE]
Uji IDOR lintas organisasi, role escalation, missing RLS context, SQL policy bypass via table owner, forced browsing, CSRF, XSS rich text, session fixation/revocation, invite/OTP replay, duplicate operations, signed URL scope, and support grant expiration. Tidak cukup hanya berasumsi aman karena tombol UI tersembunyi.
File Evidence, Garage S3, Outbox & Cleanup
Presigned upload/direct gateway, email delivery semantics dan pemisahan temporary cleanup dengan histori permanen.
12.1 Garage S3 file lifecycle [FINAL]
Browser -> BFF/API: upload initialization (filename, size, MIME, purpose)
API -> DB: FileObject PENDING + scoped object key + short TTL presign
API -> Browser: signed PUT URL + required headers
Browser -> Nginx storage hostname -> Garage (no NestJS binary proxy)
Browser -> API: finalize evidence with id + integrity metadata
API -> Garage: head/check; DB FileObject UPLOADED
API domain mutation -> DB transaction: FileAttachment ATTACHED to record/version
Worker cleanup: old PENDING or UPLOADED orphan, never valid ATTACHED history
Pada awal prototype, file hanya metadata/local preview; real storage harus mengikuti presigned URL S3, DNS yang valid, CORS dan signed host preservation. storage.sikathree.web.id pernah dipakai sebagai contoh hostname; nama final perlu konfirmasi DNS/ownership [PROVISIONAL]. Nginx mesti meneruskan method, signed Host/headers dan query tanpa transformasi yang membatalkan signature.
12.2 Storage policy [DESIGN]
- MIME allowlist awal: JPEG, PNG, WebP, PDF; format lainnya hanya setelah kebutuhan diverifikasi. Batas contoh JPEG/PNG/WebP <=10MB/file dan PDF <=20MB/file; angka dapat dikonfigurasi serta memerlukan uji provider/kapasitas.
- Object keys opaque dan tidak memasukkan email, nama atau secret. Metadata DB menyimpan tenant/purpose/version/scope.
- Unduh/view melalui short-lived signed GET yang hanya diberikan setelah record access check; file global authoring punya scope tersendiri.
- Worker masuk Docker network storage karena harus dapat membersihkan object orphan; gunakan credential worker yang dibatasi untuk prefix sementara.
PENDING/UPLOADED/ATTACHEDdibedakan jelas pada UI. Kegagalan upload/finalize/attach tidak boleh menampilkan file sebagai evidence final.
12.3 Single-node durability risk [GATE]
Garage single-node dengan replication factor 1 tidak menyediakan redundansi antar-node. Boleh untuk staging dan demo, tetapi tidak boleh dianggap cukup tahan kegagalan untuk evidence nyata tanpa strategi pemulihan dan keputusan risiko yang disahkan. Backup & Disaster Recovery detail sengaja tetap backlog sesuai diskusi, namun keberadaannya tidak menghapus status wajib sebelum go-live real data.
12.4 Transactional outbox [FINAL]
- Setiap tindakan penting (submit laporan, laporan kritis, review/assignment/completion/reopen, dan event penting lain yang disepakati) membuat outbox record dalam transaksi DB yang sama.
- Worker polling
FOR UPDATE SKIP LOCKED, claim job terbatas, retry dengan exponential backoff + jitter, dedup event/recipient, delivery attempt logging, dead-letter/final failure handling. Delivery semantics at-least-once attempt, bukan exactly-once guarantee. - Penerima laporan baru: seluruh anggota aktif kategori Operator/Pengawas organisasi, pelapor, penerima tambahan yang dipilih; dedup berdasarkan identitas/email. Reviewer/admin lain mendapat email sesuai event policy, bukan broadcast semua event LMS rutin.
- Laporan
CRITICAL:ACCEPTEDotomatis, priority email urgent, tidak otomatis COMPLETED. Jika email gagal, status laporan tetap valid dan worker retry; masalah email terlihat di monitoring. - Staging menggunakan sink email test (misalnya Mailpit), tidak mengirim email ke pengguna production.
12.5 File/audit cleanup
Cleanup registry menggunakan whitelist eksplisit dan retention TTL bernomor dari konfigurasi; hanya temporary file, expired token, expired session atau cache tepercaya yang boleh dibersihkan. Tabel business history, revisions, audit log dan attachment valid tidak boleh masuk daftar generic cleanup. Jalankan dry-run dan uji foreign key dependencies sebelum mengaktifkan scheduled job.
Infrastructure Topology, Docker Compose & VPS Sizing
Satu VPS per environment, lima Compose project, Docker networks, secrets, sizing kandidat, dan risiko go-live.
13.1 Topology [FINAL]
Terdapat dua VPS total: 1 Production VPS dan 1 Staging VPS, bukan dua VPS per environment. Seluruh service tiap VPS berjalan lewat lima project Docker Compose terpisah dan external Docker networks internal. Tidak membutuhkan WireGuard atau inter-VPS private IP untuk komunikasi service pada satu environment.
13.2 Five Compose projects
| Project | Containers/jobs | Networks | Eksposur |
|---|---|---|---|
| sikathree-infra | nginx, postgres | edge, backend, storage | Nginx 80/443 saja |
| sikathree-web | Next.js | edge | Internal web runtime |
| sikathree-api | NestJS API, worker, one-off migration job | edge, backend, storage, outbound | Worker API-independent process |
| sikathree-storage | Garage S3 | storage | Single node, private S3 port |
| sikathree-monitoring | Prometheus, Grafana, Alertmanager, Node/Postgres exporters | metrics, backend limited | Private management access only |
13.3 Docker networks dan trust boundaries
sikathree_edge: reverse proxy Nginx, Next.js, NestJS API. Internal API tidak publish host port.sikathree_backend: API, worker, Postgres.internal: truesesuai kemampuan Compose dan kebutuhan worker; tidak buka DB ke internet.sikathree_storage: Nginx storage gateway, NestJS API, worker cleanup, Garage. Internal service port, bukan host-published.sikathree_metrics: monitoring dan exporter; akses Grafana dibatasi via admin/private auth, tidak diekspos tanpa proteksi.sikathree_outbound: worker membutuhkan outbound SMTP/API sesuai konfigurasi (network egress policy dan internal network split harus diuji agar DNS berfungsi). Hanya Nginx mempublikasikan host ports 80/443. Gunakan Docker name stability dan external networks untuk menghubungkan Compose projects; health checks dan startup dependency tidak diasumsikan lintas project.
13.4 Kandidat kapasitas [DESIGN, memerlukan load test]
| Resource | Production candidate | Staging comparable test | Pertimbangan |
|---|---|---|---|
| CPU | 8 vCPU | 8 vCPU | 4 vCPU/16GB hanya bila lulus load test |
| RAM | 32 GB | 32 GB | Postgres, Next, Nest, worker, Garage, monitoring berbagi host |
| Storage | 400-500 GB NVMe | 200-300 GB NVMe | Evidence dan LMS media growth harus dimodelkan |
| Peak concurrency | 120 active users | 120 VUs simulated | 4.000 akun × 3%, bukan 120 concurrent requests tetap |
13.5 Data growth and capacity model
4.000 akun bukan ukuran disk utama; volume file berasal dari laporan, foto evidence, materi LMS, retensi, dan audit. Sebelum go-live, lakukan survei jumlah laporan/berkas, proyeksi pertumbuhan minimal 12 bulan, IOPS, throughput HTTP, kapasitas email worker, dan disk headroom. Hindari menempatkan service monitoring pada disk penuh tanpa retensi.
13.6 Environment separation
Production dan Staging memiliki environment secret, PostgreSQL, Garage, session namespace, Mailpit/test SMTP, DNS/HTTPS, metrics dan data berbeda. Tidak ada copy data production ke staging tanpa sanitasi dan approval. Persisted Docker volumes tidak dibagikan antar-environment.
13.7 Secrets and runtime config
Credential database, session, S3, SMTP dan container registry runtime disimpan di VPS masing-masing, diinjeksikan via Compose secrets atau mekanisme secret file yang aman (/run/secrets), bukan variabel NEXT_PUBLIC atau GitHub Actions secret untuk runtime app. GitHub Actions hanya memegang credential deploy yang minimal dan audit-approved. Images dipromosikan berdasarkan immutable digest, staging -> production.
13.8 Availability and disaster recovery [GATE]
Single VPS adalah single point of failure; single-node Garage tidak redundan. Pemulihan data PostgreSQL, objek Garage, config, deployment artifacts, serta tes restore belum dibahas tuntas dan tetap ditempatkan pada backlog wajib. Production real-data launch diblok sampai RPO/RTO, backup off-host/restore rehearsal, key escrow, dan ownership disepakati serta diverifikasi.
CI/CD, Image Promotion, Migrations & Release
Dua repository, GitHub Actions/GHCR, digest promotion, approval, schema compatibility dan release gates.
14.1 Source control architecture
Dua repository produk: sikathree-web (Next.js frontend + tRPC BFF) dan sikathree-api (NestJS API/worker/migrations). Penempatan sumber konfigurasi infra, Nginx, Garage dan monitoring perlu ditetapkan sebagai ownership eksplisit; jangan diam-diam menciptakan repository ketiga jika belum disetujui.
14.2 CI pipeline setiap repo [DESIGN]
- Pull request: lint, typecheck, formatting, unit tests, security scanning, dependency audit, OpenAPI contract comparison, build and smoke test.
- Build multi-stage Docker image yang berjalan non-root, menghasilkan SBOM/attestation jika tool tersedia, tag commit SHA dan immutable digest.
- Push image ke GitHub Container Registry (GHCR) hanya setelah CI lulus.
- Deploy staging otomatis setelah syarat branch/CI yang ditetapkan; tetap dengan environment terisolasi.
- Jalankan one-off migration bila kompatibel, health check, E2E smoke, responsive smoke di staging.
- Production release hanya setelah approval/manual authorized release; promote digest yang sama dari staging, bukan rebuild source yang berbeda.
- Post-release health, rollback to previous digest jika schema backward-compatible; jangan rollback sembarangan setelah destructive migration.
14.3 Deployment security
GitHub workflow memakai scoped credentials dan pinned actions bila mungkin; development runtime secrets tidak masuk repository, build logs atau image layers. Audit siapa menyetujui/release. Manual workflow_dispatch saja tidak selalu sama dengan two-person approval; pilih mekanisme policy yang benar-benar ditegakkan dan sesuai GitHub plan/repo visibility.
14.4 Next.js build-time config
NEXT_PUBLIC_* dapat di-inline pada build. Gunakan konfigurasi yang tidak berubah antar-staging/production pada artifact promosi atau pola runtime config yang diuji; jangan menyematkan URL/kredensial staging ke image yang dipromosikan production.
14.5 Release readiness checklist
- [ ] Staging build SHA/digest exact cocok dengan target production.
- [ ] OpenAPI contract kompatibel.
- [ ] Schema migration rehearsal dan backup valid.
- [ ] RLS/authorization tests lulus.
- [ ] E2E critical paths mobile & desktop lulus.
- [ ] Alerting, metrics, logging dan external uptime aktif.
- [ ] Secrets, DNS, TLS, Nginx CORS/signed URL diverifikasi.
- [ ] Load testing kapasitas target terdokumentasi.
- [ ] Backup/restore dan storage durability go-live gate terpenuhi.
- [ ] Approval production tercatat dan incident owner siap.
Monitoring, Logging, Alerting & Operations
Prometheus/Grafana/Alertmanager, metrics domain, privacy-safe log, external uptime dan runbook.
15.1 Practical Observability [FINAL]
Pada setiap VPS: Prometheus, Grafana, Alertmanager, Node Exporter, Postgres exporter, Garage metrics, NestJS API metrics dan worker/outbox metrics, Nginx access/error counts; external uptime check dari luar VPS. Aplikasi menulis structured JSON logs dengan Docker local logging driver + rotation. Loki dan Tempo belum termasuk MVP awal.
15.2 Golden signals [DESIGN]
| Komponen | Metrik wajib | Indikasi masalah |
|---|---|---|
| Nginx | RPS, 4xx/5xx, latency, upstream failure | Overload / bad routing |
| NestJS | p50/p95/p99 latency, error counts, DB pool wait, request duration | Slow business endpoints |
| PostgreSQL | CPU/mem, connections, locks, deadlocks, slow queries, disk/replication if any | RLS/perf/locking issues |
| Garage | S3 request failure/latency, disk utilization, object error | Evidence operations degraded |
| Worker/Outbox | pending, oldest age, retry count, failure, send rate | Email stuck, retries |
| System | node CPU, RAM, disk, load, IO wait | All-in-one VPS saturation |
| User journey | Hazard submit fail, assessment start/submit errors, HIRADC review 409 | Critical workflow availability |
15.3 Labels, logs and privacy
Gunakan label metrik rendah cardinality seperti route template/status class/operation, jangan menambahkan raw userId, reportId, email, answerId, file keys, atau signed URL ke Prometheus labels. Structured logs memakai requestId/traceId; audit bisnis berada PostgreSQL append-only terpisah dari log runtime yang dirotasi. Redact secrets dan konten pribadi dari logs.
15.4 Alert examples [DESIGN]
- API 5xx error rate meningkat selama 5–10 menit; p95 above agreed SLO (alert thresholds perlu tuning dari load test).
- Postgres connections at high watermark, deadlock spike, disk usage >80%, outbox oldest pending > defined operational limit.
- Garage failed requests or object read checksum errors.
- External uptime detects HTTPS unavailable independent of local VPS monitoring.
- Critical alerts have owner, severity, runbook link dan non-email single point of notification risk evaluated.
15.5 Operational docs
Dokumentasikan dashboard utama, query untuk domain errors, alert severity, siapa on-call, deploy/rollback, rotate credentials dan runbook incident. Test alerts in staging before go-live. Monitoring stack sendiri perlu memory/disk budget dan protected access.
Edge Cases, Error Handling & Acceptance Matrix
55 acceptance/negative scenarios lengkap dengan ID dan metode pengujian.
16.1 Normal / abnormal / adverse path policy
Edge case diuji pada tiga lapis: domain service (unit/integration), API/database authorization dan transaksi (integration/security), lalu UI E2E desktop/mobile. Implementasi dianggap benar hanya bila result, audit/history dan state setelah refresh konsisten; toast sukses saja tidak cukup.
16.2 Edge case & acceptance matrix [DESIGN]
| Domain | Skenario | Expected behavior / acceptance | Test ID | Metode |
|---|---|---|---|---|
| Auth | Email unknown vs wrong password | Same generic error, rate limit, no enumeration | AUTH-01 | Unit+E2E |
| Auth | OTP wrong/expired/reused | Reject safely; attempts counted; never log OTP | AUTH-02 | Integration |
| Auth | Session idle expired during form | Mutation denied; no saved claim; login retry/unsaved warning | AUTH-03 | E2E desktop/mobile |
| Auth | Absolute 7-day expiration | Session invalid regardless of idle activity | AUTH-04 | Integration |
| Org | Switch tenant with dirty form | Confirmation; cancel retains context; confirm resets tenant caches | ORG-01 | E2E mobile |
| Org | Membership revoked while viewing | Next read/mutation denied; invalidate data | ORG-02 | Integration |
| Org | Deep link record from other org | Offer eligible switch after membership check; no leaked summary | ORG-03 | E2E |
| Org | Cross-tenant foreign key attempted | DB composite FK and RLS fail | ORG-04 | DB integration |
| Org | Organization inactive | Block mutation; permit read only when explicitly allowed | ORG-05 | Integration |
| RBAC | Permission removed in another tab | Next action fails, UI refreshes effective permissions | RBAC-01 | E2E |
| RBAC | Organization Admin grants own higher rights | Delegation ceiling rejects | RBAC-02 | Security |
| RBAC | Last org admin disabled | Reject and show explanatory error | RBAC-03 | Integration |
| RBAC | Platform Admin accesses tenant without grant | 403/404; no data disclosure | RBAC-04 | Security |
| RBAC | Support resource not in grant | Deny; audit attempt | RBAC-05 | Security |
| RBAC | Support grant expires during view | Block next read/download sign; warn UI | RBAC-06 | E2E |
| Hazard | Incomplete draft saved | Save partial but submit blocked with field errors | HAZ-01 | E2E mobile |
| Hazard | Critical report submitted | Auto ACCEPTED, handling unassigned, urgent outbox, not completed | HAZ-02 | Integration |
| Hazard | Submit request timeout after commit | Read-after-write/idempotency returns single report | HAZ-03 | Integration |
| Hazard | Two supervisors assign PIC simultaneously | At most 1 active PIC; 409 conflict for loser | HAZ-04 | Concurrency test |
| Hazard | Completion missing evidence and exception | Reject finalization | HAZ-05 | Integration |
| Hazard | Notes added to completed report | Allowed append-only; historic notes unchanged | HAZ-06 | Integration |
| Hazard | Revision awaiting approval | Operational actions use previous effective revision | HAZ-07 | E2E |
| Hazard | Supervisor approves own report | Allowed if permission; audit | HAZ-08 | Integration |
| Hazard | Reopen without reason | Reject; completed history preserved | HAZ-09 | Integration |
| HIRADC | K/P values outside 1..5 | Reject; server computes score | HIR-01 | Unit+Integration |
| HIRADC | Creator tries to approve own record/version | Reject even if review permission | HIR-02 | Security+E2E |
| HIRADC | Two reviewers approve same version | Single effective transaction; other 409 | HIR-03 | Concurrency test |
| HIRADC | Residual actual before controls verified | Reject, planned residual stays distinct | HIR-04 | Integration |
| HIRADC | PIC who is supervisor verifies own control | Permit under permission; audit | HIR-05 | Integration |
| HIRADC | Control rejected for rework | History retained; status NEEDS_REWORK | HIR-06 | E2E |
| HIRADC | New assessment proposed while V2 effective | V2 still governs until V3 approved | HIR-07 | E2E |
| HIRADC | Official PDF snapshot after newer version | Earlier issued PDF remains unchanged | HIR-08 | Integration |
| LMS | Pre-Test score low | Stage completes after submit; no passing requirement | LMS-01 | E2E |
| LMS | Lesson opened but not clicked complete | No completion persisted | LMS-02 | E2E |
| LMS | Two tabs start final allowed attempt | Only one counted/created under quota guard | LMS-03 | Concurrency test |
| LMS | Duplicate submit attempt | One immutable result; no double score | LMS-04 | Integration |
| LMS | Abandoned attempt | Quota counts, historical attempt preserved | LMS-05 | Integration |
| LMS | Assessment changed while attempt active | Interrupt old attempt; exclude quota; show version message | LMS-06 | E2E+Integration |
| LMS | Lesson fingerprint unchanged on publish | Retain previous completion | LMS-07 | Integration |
| LMS | Lesson fingerprint changed on publish | Current completion resets, history remains | LMS-08 | Integration |
| LMS | Answer key exposed in participant response | Fail security check; answer only server-side | LMS-09 | Security |
| LMS | Production answer recovery after browser refresh | Only claim persistence if recovery policy deployed; no fake autosave | LMS-10 | E2E/provisional |
| File | Camera permission denied | Fallback file picker | FILE-01 | Mobile E2E |
| File | Upload succeeded but finalize failed | Keep UPLOADED/unattached, retry finalize or cleanup | FILE-02 | Integration |
| File | ATTACHED historic evidence targeted by cleanup | Delete prohibited | FILE-03 | Integration |
| File | Wrong signed host/header | Upload fails clearly; do not show attached | FILE-04 | Integration |
| File | Malicious filename/path | Sanitize display; opaque object key | FILE-05 | Security |
| SMTP unavailable after report commit | Report valid; outbox retries, monitoring alerts | MAIL-01 | Integration | |
| Same recipient from multiple groups | One deduplicated intended recipient per event | MAIL-02 | Unit | |
| Deploy | Staging credential points production | Deployment blocked by env guard | OPS-01 | CI test |
| Deploy | Migration incompatible with previous API | Release blocked, no unsafe rollback | OPS-02 | Release rehearsal |
| Deploy | Single VPS restart | Health recovers; external uptime observes downtime | OPS-03 | Resilience test |
| Capacity | 120 active users mixed workload | Latency/errors meet agreed thresholds, integrity 100% | PERF-01 | k6 |
| Mobile | Viewport 320 and keyboard open | No clipped content/action; navigation usable | UX-01 | Playwright/real device |
| Mobile | Screen reader/keyboard approval dialog | Focus trapped/restored; full context available | UX-02 | A11y |
16.3 Response time dan reliability target [PROVISIONAL baseline]
- Operasi API umum p95 <1 detik; mutasi kritis p95 <2 detik di luar upload/video transfer, pada workload staging target; threshold final ditetapkan setelah benchmark.
- HTTP 5xx <1% pada uji peak dan 0 data integrity violations pada transaksi kritis; no extra accepted reports, duplicate attempt results, double approval, maupun cross-tenant leak.
- Uji beban bertahap 30, 60, 120 concurrent virtual users, lalu stress 180 dan 240. Virtual users mengikuti skenario think-time dan mix aktivitas; 120 VUs bukan 120 request/detik.
- Mobile UX target 320–430px dan tablet/desktop; WCAG 2.2 AA target audit; test on Chrome Android and Safari iOS using actual devices or representative emulation.
16.4 Definition of acceptance
Setiap use case diterima jika (1) prasyarat & izin tepat, (2) UI form/feedback benar, (3) backend mengubah state yang benar, (4) tenant row security lolos, (5) event/versi tercatat, (6) email/file side effect konsisten, (7) refresh dan navigasi mobile menampilkan hasil benar, (8) negative and concurrency case ditolak secara aman.
Testing Strategy, E2E, Security & Capacity QA
34 test suite, 12 journeys, mobile matrices, dan k6 capacity plan.
17.1 Test pyramid
TypeScript unit tests untuk pure rules, NestJS integration tests dengan PostgreSQL RLS nyata, contract tests OpenAPI dan frontend adapters, Playwright E2E desktop/mobile, security/adversarial tests, dan k6 capacity tests. Mock prototype berguna untuk UX tetapi tidak menggantikan integration test pada backend real.
17.2 Test plan matrix
| Layer | Test suite | Related ID | Pass criteria |
|---|---|---|---|
| UNIT | RiskScore: 1..25 classification | HIR-01 | 1–4 low, 5–9 moderate, 10–14 high, 15–25 very high |
| UNIT | Permission resolver | RBAC-01/02 | Denies non-delegable, no arbitrary platform role |
| UNIT | Form conditional validation | HAZ-01/05 | Draft vs submit, evidence exception rules |
| UNIT | Lesson and assessment fingerprint | LMS-07/08 | Deterministic meaningful content changes |
| UNIT | Outbox recipient de-dup | MAIL-02 | No duplicated email recipient |
| INTEGRATION | RLS org A vs B read/write | ORG-04 | Cross tenant GET/INSERT/UPDATE/delete fail |
| INTEGRATION | RLS missing context/pool reuse | ORG-04 | Fail closed after transaction boundary |
| INTEGRATION | Self-approval HIRADC | HIR-02 | Forbidden at service and DB scope |
| INTEGRATION | Concurrent HIRADC review | HIR-03 | One approved transition only |
| INTEGRATION | Concurrent PIC reassignment | HAZ-04 | Unique active PIC invariant preserved |
| INTEGRATION | Critical hazard + outbox | HAZ-02/MAIL-01 | Accepted immediate, urgent outbox atomically |
| INTEGRATION | Course publish while attempt active | LMS-06 | Interrupted/returned quota |
| INTEGRATION | Attempt start quota race | LMS-03 | No over-allocation |
| INTEGRATION | Idempotent submit retry | HAZ-03/LMS-04 | Single final state and event |
| INTEGRATION | Evidence lifecycle | FILE-02/03 | PENDING->UPLOADED->ATTACHED; safe orphan cleanup |
| SECURITY | CSRF with session cookie | AUTH-03 | Unsafe origins rejected |
| SECURITY | Tenant IDOR via guessed IDs | ORG-04 | No data, no info leak |
| SECURITY | RLS bypass via owner role | ORG-04 | App runtime cannot bypass RLS |
| SECURITY | Support grant expired/revoked | RBAC-06 | No further read/sign |
| SECURITY | Stored XSS via Tiptap and notes | FILE-05 | Sanitized rendering |
| SECURITY | No answer key at participant boundary | LMS-09 | No correct options hidden in response |
| E2E Desktop | Login + org switch + dashboard | T-01 | Correct menu, scoped data |
| E2E Mobile | Hazard draft and camera evidence | T-02 | Capture/picker works with 320–430px viewport |
| E2E Mobile | Critical hazard and reviewer workflow | T-03/T-04 | Automatic acceptance vs handling correct |
| E2E Both | Effective revision preserved | T-05 | Case handling still uses old until review |
| E2E Both | HIRADC assessment + control | T-06/T-08 | Score and verification state correct |
| E2E Mobile | LMS Pre-Learn-Post | T-09 | Can fully complete on smartphone |
| E2E Desktop | Content authoring & publication | T-10 | Global version released with impact preview |
| E2E Both | Members and delegation ceiling | T-11 | Role changes restricted appropriately |
| E2E Both | Support access approval and expiry | T-12 | Read only with expiry |
| LOAD | 30/60/120 mixed concurrent users | PERF-01 | Thresholds, integrity, DB pool, CPU/RAM measured |
| STRESS | 180/240 concurrent users | PERF-01 | Saturation point and safe failure measured |
| DEPLOY | Staging promotion by digest | OPS-01/02 | Same artifact, approved prod release |
| RECOVERY | Off-host backup restore drill | GATE-DR | Restoration documented and validated before real data |
17.3 Twelve end-to-end business journeys
T-01: login/membership/org switch; T-02: hazard draft/save/submit; T-03: critical hazard auto acceptance; T-04: reviewer->PIC->action->verification; T-05: accepted report revision; T-06: HIRADC proposal; T-07: risk assessment review with no self approval; T-08: required controls->verified residual; T-09: LMS pre-test->learn->post-test; T-10: content publication/versioning; T-11: member role updates/ceiling; T-12: support grant request->tenant review->read->expiry. Jalankan happy path dan negative path untuk setiap journey.
17.4 E2E browser matrix
Viewport 320, 375, 390, 430, 768, 1024, 1280, dan 1440px; Chrome Android dan Safari iOS representatif, plus desktop Chromium/Firefox/WebKit bila tersedia. Fokus keyboard open, image picker permissions, sticky button safe-area, popup, role-switched navigation, no hover-only actions, web reader typography, and form error announcements.
17.5 Load-test k6 workload [DESIGN]
Mix simulasi awal: 65% browsing/list/detail, 20% HIRADC/Laporan mutations, 10% LMS test, 5% upload/download; proporsi ini hipotesis test, bukan data perilaku nyata. Jalankan warm-up 30 VUs, 60, peak 120, stress 180 dan 240; include think time and realistic requests. Catat 5xx, p95/p99 per route, DB pool wait, PostgreSQL locks, Garage throughput/disk IO, worker lag, CPU/mem and data consistency. Ulangi load test setelah perubahan indeks/schema/infra besar.
17.6 Test evidence
Setiap release menyertakan test report dengan commit/image digest, environment, fixtures, test ID, hasil pass/fail, screenshot responsive, traces, load result, ditemukan bug dan pemilik remediation. Tidak boleh mengklaim 'tested' jika hanya menyiapkan checklist.
Roadmap Implementasi, Deliverable & Definition of Done
Phases prototype sampai production, dependensi, owner, dan definisi hasil akhir.
18.1 Phase-based delivery
| Phase | Deliverable | Exit criteria |
|---|---|---|
| Phase 0 - Product foundation | Full PRD, FIS, prototype fixtures, permission schema, API contract baseline | Decision labels explicit, route/field/action mapping complete |
| Phase 1 - UI Foundation | Design tokens, AppShell, auth simulator, role/org switch, responsive nav/dashboard | Mobile viewport 320+, role simulation, 403/404 states |
| Phase 2 - Operational Prototype | Hazard and HIRADC UI + mocked workflow services | T-02..T-08 happy/negative paths |
| Phase 3 - LMS/Admin Prototype | Course LMS, test player, member/RBAC, platform and K3 Studio | T-09..T-12 with versioning and grant simulation |
| Phase 4 - Real Backend Foundation | Postgres schema/RLS/migrations, session, RBAC, NestJS, OpenAPI, Next BFF | RLS/security integration and contract tests |
| Phase 5 - Real Business Modules | HIRADC, Hazard, LMS transactions, Garage, outbox, official PDF | Domain invariants and audit pass |
| Phase 6 - Deployment/Hardening | Compose stacks staging, CI/CD, monitoring, load and UX QA | Go-live gate evidence and sign-offs |
| Phase 7 - Real Data Production | Approved release with backup/restore and durability policy | Go/no-go signed, SLO/incident owner clear |
18.2 Implementation order and dependencies
- Mulai dari global identity, membership, permission registry, RLS, auditable transaction primitives; baru domain services.
- Mock adapter dan real adapter mematuhi interface serupa. Prototype fixtures mencakup 2 tenant, ~10 akun, ~20 risk records, ~20 hazard reports, 4 global courses, attempt variants, support grants.
- File upload & outbox perlu service interfaces sejak awal agar domain submit tidak di-hardcode untuk memanggil SMTP langsung.
- Status registry dan field-level Zod schemas harus berkorespondensi dengan NestJS DTO; generated OpenAPI contract mengurangi drift.
- Mobile QA untuk Laporan Bahaya dan control task dilakukan sejak implementasi awal, bukan tahap akhir.
18.3 Ownership and governance
Product owner mengesahkan perubahan business rule, K3 reviewer mengesahkan istilah/prosedur keselamatan, Tech Lead menyetujui API/DB migration, security reviewer menilai RLS/support/signed URLs, frontend lead memvalidasi desktop/mobile. AI Coding Agent adalah pelaksana yang wajib melaporkan changed files, tests, assumption, dan unresolved risk; tidak menjadi pemegang keputusan produk.
18.4 Prototype vs MVP vs Go-live
Prototipe berhasil apabila 12 business journeys berjalan dengan fake data dan responsive UI; MVP berhasil jika backend nyata transaction+tenant isolation benar di staging; production go-live berhasil hanya setelah risk critical seperti backup/recovery, Garage storage durability, credential/monitoring, release approval, dan peak load tervalidasi. Jangan menyamakan ketiga hasil tersebut.
AI Coding Agent Skills — Wajib Saja
14 skill wajib dari screenshot dan sumber publik, ditambah 3 custom Sikathree SKILL.md.
19.1 Prinsip pemilihan
Bagian ini sengaja memuat skill wajib saja. Tidak memasukkan Find Skills, Skill Installer, Image Gen, OpenAI Docs, Reverse Engineer Anything, accessibility additional skill, maupun GitHub Actions Efficiency sebagai skill opsional. Ketujuh skill dari screenshot yang sebelumnya dinilai wajib tetap tercakup; beberapa skill eksternal wajib melengkapi backend, database, security, Docker dan testing. Skill yang fungsinya tumpang tindih diaktifkan sesuai jenis pekerjaan, bukan semuanya setiap request.
Tingkat ketersediaan: Skill berlabel Built-in dinilai tersedia berdasarkan screenshot pengguna; path dan versi lokal belum diverifikasi melalui repository agent. Skill eksternal memiliki sumber publik untuk diverifikasi ulang sebelum instalasi. Semua skill bersifat panduan kualitas, tidak lebih tinggi dari PRD/peraturan keamanan.
19.2 Mandatory external / built-in skills registry
| ID / Skill | Kelompok | Tugas di Sikathree | Trigger | Source / rujukan |
|---|---|---|---|---|
| frontend-design — Frontend Design | Built-in (tersedia pada agent pengguna) | Visual direction Modern Operational Dashboard, layout responsif, visual hierarchy, mobile UX; selaras desain Sikathree | Merancang halaman atau melakukan visual refinement | Sudah ada pada agent pengguna; URL tidak diverifikasi |
| nextjs-shadcn — Nextjs Shadcn | Built-in (tersedia pada agent pengguna) | Membangun aplikasi Next.js memakai komponen shadcn dan pola frontend | Membangun Next.js pages, layout dan forms | Sudah ada pada agent pengguna; URL tidak diverifikasi |
| shadcn — Shadcn | Built-in/official | Install, style, compose dan debug shadcn/ui components sesuai components.json | Mengubah komponen shadcn atau design system | https://github.com/shadcn-ui/ui/tree/main/skills/shadcn |
| vercel-react-best-practices — Vercel React Best Practices | Built-in/official | Menghindari request waterfalls, meningkatkan rendering dan bundle efficiency | Membangun/review komponen Next.js dan React | https://github.com/vercel-labs/agent-skills/tree/main/skills/react-best-practices |
| web-design-guidelines — Web Design Guidelines | Built-in/official | Review accessibility, form, focus, layout, mobile usability dan web interaction | Audit UI/UX setelah perbedaan layout atau form | https://github.com/vercel-labs/agent-skills/tree/main/skills/web-design-guidelines |
| review-agent — Review Agent | Built-in (tersedia pada agent pengguna) | Mencari bug berdampak nyata dalam perubahan kode, regresi dan konflik kontrak | Setelah implementasi modul atau pull request | Sudah ada pada agent pengguna; URL tidak diverifikasi |
| skill-creator — Skill Creator | Built-in (tersedia pada agent pengguna) | Membuat atau memperbarui tiga custom skill Sikathree dan memvalidasi keterbacaan SKILL.md | Ketika memelihara skill internal atau onboarding agent | Sudah ada pada agent pengguna; URL tidak diverifikasi |
| nextjs-app-architecture — Next.js App Architecture | External verified | App Router, Server/Client boundaries, feature folders dan Suspense | Membangun arsitektur routes, data fetching, frontend server components | https://github.com/aurorascharff/nextjs-app-architecture-skill |
| nestjs-best-practices — NestJS Best Practices | External verified | Modul DI, controller/service, guards, error handling, testing, clean architecture | Menulis NestJS API/worker | https://github.com/Kadajett/agent-nestjs-skills |
| postgres-best-practices — Postgres Best Practices | External verified | Schema, SQL, RLS, locking, indexing, connection pooling PostgreSQL | Migrasi, SQL, RLS, query atau DB performance | https://github.com/supabase/agent-skills/tree/main/skills/supabase-postgres-best-practices |
| security-best-practices — Security Best Practices | External verified | Secure coding review TypeScript/JavaScript, CSRF/XSS, session, authorization | Security review terencana dan secure-by-default coding | https://github.com/openai/skills/tree/main/skills/.curated/security-best-practices |
| docker-build-strategies — Docker Build Strategies | External verified | Multi-stage Dockerfile, non-root images, build cache, image hardening | Menulis/mereview Dockerfiles | https://github.com/docker/skills/tree/main/skills/docker-build-strategies |
| docker-compose-patterns — Docker Compose Patterns | External verified | Compose multi-stack, networks, volumes, healthchecks, secrets | Mengubah Docker Compose config atau networking | https://github.com/docker/skills/tree/main/skills/docker-compose-patterns |
| playwright-cli — Playwright CLI Skill | External verified | E2E desktop/mobile, screenshot, traces, browser workflow testing | Validasi UI, simulasi role, responsive dan end-to-end | https://github.com/microsoft/playwright-cli/tree/main/skills/playwright-cli |
19.3 Tiga mandatory custom Sikathree skills [DESIGN]
| Custom skill | Trigger | Jaminan utama |
|---|---|---|
| sikathree-business-workflows | Setiap fitur HIRADC, Hazard, LMS; status, event, score, version, attempt | Aturan self-approval, 5x5, PIC, critical accepted, attempt fingerprint, immutable history |
| sikathree-multitenancy-rbac | Schema, RLS, query, guards, role editor, support grants | Tenant isolation, membership, delegation ceiling, scoped support, RLS fail-closed |
| sikathree-api-contracts | tRPC BFF ↔ NestJS DTO, OpenAPI, mutation, evidence, outbox | Organization context, validation, idempotency, version conflicts, file lifecycle |
Ketiga custom skill disertakan sebagai file agent-skills/custom/*/SKILL.md asli, bukan hanya referensi nama. Skill Creator dipakai ketika memperbarui skill tersebut. Skill custom wajib menyebut PRD chapter/decision source dan tidak boleh menyalin kredensial.
19.4 Mandatory agent execution rules
- Baca
AGENTS.md, index PRD dan custom skill yang relevan sebelum menulis code domain; baca external skill yang sesuai task. - Hierarki keputusan: hukum dan kebijakan keamanan > keputusan produk
[FINAL]dalam PRD >[DESIGN]terdelegasi > skill guidance > kebiasaan agent. Jika konflik, laporkan, jangan mengubah silently. - Untuk setiap task, catat: feature scope, route, input/output DTO, permission key, state guard, tenant scope, files changed, tests added/run, hasil/resiko.
- Skill Next.js dari Aurora berorientasi App Router dan dapat menyarankan Server Actions, tetapi tidak boleh menggantikan kontrak
Next.js tRPC BFF -> NestJS RESTtanpa approval. - Skill Supabase dipakai untuk PostgreSQL umum, bukan menginstal Supabase Auth/Storage. Skill Docker hanya untuk Docker Compose 1 VPS/environment yang sudah dipilih.
- Jangan menjalankan command dari third-party SKILL.md tanpa meninjau script, permission, network access, filesystem writes dan version pinning. Sumber eksternal dicatat dengan commit SHA/tag setelah dipilih; jangan mengklaim pinned bila belum.
- Review Agent, Web Design Guidelines dan Playwright dipakai sebagai quality gates sesuai fase: code review, UI audit, actual browser tests.
Skill Creatorwajib untuk pemeliharaan skill internal, tidak perlu dimuat untuk semua pekerjaan coding harian.
19.5 Skill-to-workstream mapping
| Workstream | Skill wajib yang dipanggil |
|---|---|
| UI prototype + app shell | Frontend Design, Nextjs Shadcn, Shadcn, Next.js App Architecture, Vercel React Best Practices, sikathree-multitenancy-rbac |
| HIRADC/Hazard/LMS domain | NestJS Best Practices, sikathree-business-workflows, sikathree-api-contracts, sikathree-multitenancy-rbac |
| Postgres schema / RLS | Postgres Best Practices, Security Best Practices, sikathree-multitenancy-rbac |
| Docker deployment | Docker Build Strategies, Docker Compose Patterns, Security Best Practices, sikathree-api-contracts |
| QA / review | Review Agent, Web Design Guidelines, Playwright CLI Skill, three Sikathree custom skills |
| Skill maintenance | Skill Creator, three Sikathree custom skills |
19.6 Instalasi dan verifikasi
Perintah yang telah disediakan hanya contoh, belum dijalankan. Jangan mengunduh seluruh eksternal skills otomatis pada mesin production. Gunakan lockfile/commit pin di tahap implementasi; README paket mencantumkan lokasi dan perintah. Simpan external skill sebagai dependency links, tidak mendistribusikan kembali konten pihak ketiga di ZIP.
Open Decisions, Risk Register & Production Go-Live Gates
16 keputusan/risiko terbuka dengan owner dan go-live gate.
20.1 Decision ledger
Agar developer tidak mengarang aturan bisnis, daftar berikut harus dikonversi menjadi Architecture Decision Record saat menghasilkan keputusan baru. Open decisions tidak boleh diinterpretasikan sebagai fitur yang sudah disahkan production.
| ID | Area | Status | Default / tindakan | Owner | Deadline relatif |
|---|---|---|---|---|---|
| DEC-01 | Public REST vs BFF-only | [PROVISIONAL] | Default BFF-only; final approval sebelum exposure public REST | Backend/Frontend lead | Sebelum API edge deploy |
| DEC-02 | LMS answer recovery saat refresh | [PROVISIONAL] | Default no false autosave; pilih server-durable answer writes/recovery policy | Product/Backend | Sebelum LMS real launch |
| DEC-03 | File size, MIME, malware scan | [PROVISIONAL] | Default 10MB image/20MB PDF, allowlist + sniff, decide scanner | Security/Operations | Sebelum upload production |
| DEC-04 | Support grant duration | [PROVISIONAL] | Default 60 min max 120, no wildcard; owner review | Product/Security | Sebelum support access production |
| DEC-05 | Attempt limit | [PROVISIONAL] | Default 3 per assessment version, configurable | Admin K3/Product | Sebelum course authoring production |
| DEC-06 | Severity/type catalog exact | [DESIGN] | Catalog driven; current demo LOW/MEDIUM/HIGH/CRITICAL and common types | Product/K3 | Sebelum hazard API sign-off |
| DEC-07 | HIRADC K/P descriptions | [PROVISIONAL] | Scores 1..5 fixed, textual criteria must be ratified | K3 domain owner | Sebelum formal assessment go-live |
| DEC-08 | PDF template and library | [PROVISIONAL] | Immutable manifest/issued PDF, select renderer and official layout | Product/K3/Tech | Sebelum official PDFs issued |
| DEC-09 | Garagе single-node durability | [GATE] | Replicating alternative or durable off-host protection + verified recovery | Infra owner | Before real data |
| DEC-10 | Backup/DR RPO-RTO | [GATE] | Define retention, off-host copies, restore test, incident owner | Infra owner | Before real data |
| DEC-11 | VPS sizing & 120 VUs | [GATE] | Validate 8vCPU/32GB candidate or measured smaller configuration | Infra/QA | Before real peak load |
| DEC-12 | Production deploy approval | [GATE] | Enforce actual reviewer/manual approval with documented audit path | Release owner | Before CD prod |
| DEC-13 | Infrastructure configuration ownership | [PROVISIONAL] | Decide infra manifests location and authorized change workflow | Tech lead | Before deployment |
| DEC-14 | Next.js runtime config/image promotion | [GATE] | Ensure image digest promoted without staging values in public bundle | Frontend/DevOps | Before promotion |
| DEC-15 | Email sender/provider & notification sender account | [PROVISIONAL] | Provider, sender, retries, test sink and quotas | Operations | Before transactional email |
| DEC-16 | User account/support operational policy | [PROVISIONAL] | Invite expiry, password rules, inactive org read-only specifics | Product/Security | Before auth sign-off |
20.2 Risiko utama
| Risiko | Severity | Mitigasi wajib | Evidence before go-live |
|---|---|---|---|
| Kebocoran data antarorganisasi | Critical | RLS + composite FK + Nest guards + negative tests | RLS automated test report |
| Hilangnya evidence Garage single-node | Critical | Durable backups/redundancy strategy, restore rehearsal | Signed data durability decision + restore report |
| Tidak tersedia backup/restore | Critical | Off-host backup, encrypted secrets, full restore test | RPO/RTO and restoration evidence |
| Approval/role escalation | Critical | Policy engine, no self HIRADC approval, delegation ceiling, audited support grants | Security test report |
| LMS attempt quota race | High | Atomic quota reservation, idempotency, fingerprint pin | Concurrency test |
| Dua PIC aktif/report double submit | High | Partial unique constraints + idempotent transactions | Concurrency test |
| Semua service satu VPS overload | High | Resource budgeting, 120 VUs load test, alerting, plan scale | Load test & headroom measurements |
| Email outbox backlog | Medium | Retry/dedup/alert, worker resource caps | Failure injection test |
| Mobile usability gagal saat petugas lapangan | High | Mobile-first tests 320px and real devices | E2E screenshot/trace |
| PRD/skills conflict -> AI Agent drift | High | AGENTS precedence, change-control log, review tests | Agent review report |
20.3 Go-live blocker checklist
- [ ] Tenant isolation and RLS prove fail-closed under active membership, revoked membership, and connection pooling.
- [ ] Security audits cover session/CSRF/OTP, cross-tenant IDOR, role escalation and Support Access read-only expiry.
- [ ] Garage durability and backup/DR policy are approved and recovery verified on test data.
- [ ] Production secrets and TLS configured; no staging/prod cross-connect.
- [ ] 120 concurrent mixed-load target tested on staging-comparable infra with integrity checks.
- [ ] Authorized production release approval, digest promotion, migration rehearsal, rollback conditions and monitoring are documented.
- [ ] Mobile E2E critical journeys and accessibility review pass.
- [ ] Business workflows signed off by K3/product stakeholders including field catalogue K/P descriptions, hazard severity and workflow rules.
Glossary, Traceability & Technical References
Istilah, sumber kesepakatan, referensi teknis dan prosedur revisi dokumen.
21.1 Terms and glossary
| Istilah | Definisi dalam Sikathree |
|---|---|
| Sikathree | Sistem Informasi K3 untuk HIRADC, Laporan Bahaya, LMS |
| Tenant/Organization | Unit data organisasi yang dipisahkan melalui membership, authorization, dan RLS |
| Effective Permission | Izin akhir hasil role, scope, delegation policy dan guard |
| Assessment Version | Snapshot penilaian HIRADC atau tes LMS yang terikat fingerprint/version |
| Risk Initial | Skor K×P sebelum pengendalian |
| Planned Residual | Skor proyeksi setelah kontrol direncanakan; belum diverifikasi |
| Verified Residual | Skor faktual setelah semua kontrol wajib diverifikasi |
| Official PDF Snapshot | Dokumen terbit immutable dengan manifest versi yang ditandatangani proses sistem |
| Critical Hazard | Laporan severity CRITICAL yang otomatis ACCEPTED tetapi belum otomatis ditangani |
| Outbox | Tabel event pengiriman asinkron yang dibuat dalam transaksi bisnis |
| RLS | PostgreSQL Row-Level Security yang mengisolasi tenant rows |
| tRPC BFF | Backend-for-frontend pada Next.js yang meneruskan request ke NestJS REST |
| Support Grant | Hak baca sementara pada resource tenant yang disetujui Organization Admin |
21.2 Source of decisions
Dokumen ini disusun berdasarkan kesepakatan langsung dalam diskusi proyek Sikathree sampai 5 November 2026: tiga modul, empat area K3, Dynamic RBAC, sesssion, multi-tenancy/RLS, frontend Next.js tRPC, NestJS REST+worker, PostgreSQL/pgvector, Garage, satu VPS per environment, Docker Compose modular, practical observability, UI/UX direction A dan 120 concurrent estimate. Field/action/state/edge-cases tambahan diberi label [DESIGN] dan belum diklaim sebagai hasil survei lapangan.
21.3 External technical references (informational, subject to version review)
- Next.js App Router: https://nextjs.org/docs/app
- NestJS: https://docs.nestjs.com/
- PostgreSQL RLS: https://www.postgresql.org/docs/current/ddl-rowsecurity.html
- Docker Compose networks: https://docs.docker.com/compose/how-tos/networking/
- Garage S3: https://garagehq.deuxfleurs.fr/documentation/
- Playwright: https://playwright.dev/
- Grafana k6: https://grafana.com/docs/k6/latest/
- Agent skill registry dan custom skills dijabarkan pada Bab 19 serta berkas
agent-skills/dalam ZIP.
21.4 Traceability guidance
Gunakan ID requirement sederhana REQ-{DOMAIN}-{NNN}, referensi route, permissionKey, state transition, affected tables, API operation, dan test ID pada tiket developer. Tidak ada requirement considered Done tanpa evidence implementation, test, screenshot/trace bagi mobile workflow, serta review permission dan tenant-scoping.
21.5 Document maintenance
Untuk revisi PRD, tingkatkan versi dokumen, catat perubahan keputusan [FINAL]/[DESIGN]/[PROVISIONAL], regenerasikan Markdown + PDF + HTML + prd.json dari satu source konten agar hasil saling konsisten. Jika menyetujui keputusan provisional, pindahkan ke [FINAL] via change log dan tambahkan test.
Konten tidak ditemukan
Coba kata kunci lain, seperti “assessment”, “RLS”, “Garage”, atau “mobile”.
Aturan final, rancangan yang didelegasikan, hal provisional, dan go-live gates dibedakan secara eksplisit.
Gunakan pencarian, navigasi kiri, atau shortcut /. Klik ID bab untuk melihat seluruh tabel dan payload.