Pendirian Yayasan & Perkumpulan — Implementation Plan
For agentic workers: implement task-by-task. Steps use checkbox (
- [ ]) for tracking.
Design docs: flow-pendirian-yayasan.md, flow-pendirian-perkumpulan.md.
Goal: Ship the FULL PENDIRIAN_YAYASAN and PENDIRIAN_PERKUMPULAN OCR flows (upload → classify → extract → validate → Smart Review → submit terminal, read-only) as TWO new v2-generic tenants of the flow-engine FLOW_REGISTRY. Akta-centric single-document flows; local + asing variants in one phase (D9). A shared organ-aware extractor + context (D10) with a variant discriminator serves both.
Architecture: CONFIG + CONTEXT + RULES consumers of backend/src/flow-engine/ — NOT a fork and NOT customProcess (D8′: forks are retired on this branch — registry.ts:37 "LAST v1-fork retired"; every flow incl. PENDIRIAN_PP is now defineFlow). Mirror the akta-first tenant pendirian-pt.ts:103-128 (primaryAkta + buildContext + rules + rematch + hooks.beforeValidation derive). The organ akta is a NEW document class on a SEPARATE classifier taxonomy (apostille-classifier pattern), never inserted into AKTA_TXN_TYPES (PT-only). Validation is thin (single doc → internal akta consistency + organ/kekayaan completeness + registry name availability). Terminal self-service (both in TERMINAL_TYPES) — Perkumpulan's SABH kasie lives downstream, outside the read-only PoC (D4b).
Tech Stack: Bun + Hono; Prisma 7 (src/generated/prisma/); PostgreSQL 16; backend tests bun test (test DB ahu_ocr_test, bun run db:push:test after schema change); frontend React 19 + Vite + TanStack Query + jotai, bunx vitest run; typecheck bunx tsc --noEmit per package. SABH source = read-only MySQL via lib/sabh-db.ts.
Global Constraints (binding — copy verbatim)
- Enum spellings (exact): SubmissionType
PENDIRIAN_YAYASAN(SABH id_aksi 2, product 50),PENDIRIAN_PERKUMPULAN(id_aksi 14, product 60). DocumentTypeAKTA_PENDIRIAN_YAYASAN,AKTA_PENDIRIAN_PERKUMPULAN,SURAT_PERTIMBANGAN_KEMENLU.ValidationStatus=PASS | WARNING | FAIL | SKIPPED. - v2-generic TENANT, not fork (D8′): each flow is
defineFlow<Ctx>({ engine: "v2-generic", ... }).processorKindForTypereturns"v2-generic"; the registry short-circuit owns routing (submission-dispatch.ts:89-94). Do NOT add app-*-processor-style fork; do NOT setcustomProcess(straight pendirian fits the standard pipeline). - TERMINAL, no verifikator (D4a/D4b): submit →
COMPLETED; both types ADDED toTERMINAL_TYPES(destination-for.ts:22-25). NoVerifikasiPerubahanrow. Perkumpulan SABH kasie is downstream — NOT modeled. - Akta = authoritative (single-doc):
primaryAktaSET (not null, contrast PP). No KTP/NPWP/bukti-setor/surat-pernyataan docs — those are PERUBAHAN in SABH (UploadAktaController.php:218isYayasanPerubahanTransaksiid_aksi 3;:249isPerkumpulanPerubahanTransaksiid_aksi 15). Pendirian upload =file_aktaonly (BadanHukumAkta.php:275,286) + BO questionnaire (a form, not OCR) + (asing yayasan) Kemenlu letter. - D11 rule set (ADJUSTED — KTP/NPWP rules dropped, no anchor):
- Yayasan:
AKTA_PRESENT(FAIL),ORGAN_COMPLETENESS(WARNING),KEKAYAAN_AWAL_PRESENT(WARNING),KEKAYAAN_ASING_MIN(FAIL, asing, ≥Rp100jt — anchorFormDataYayasan.php:434),NAMA_AVAILABILITY(WARNING). - Perkumpulan:
AKTA_PRESENT,ORGAN_COMPLETENESS,ADART_COMPLETENESS(WARNING — 5 fields),NAMA_AVAILABILITY. No kekayaan rules (not required; Rp100jt disabledFormDataPerkumpulan.php:130-131). - DROPPED both:
NIK_KTP_ORGAN,NAMA_NPWP_ORGAN,KTP_COMPLETENESS,NPWP_COMPLETENESS,NAMA_BH_CONSISTENCY, all saham/modal/bukti-setor/KBLI. - Organ (verified): Yayasan Pembina/Pengurus/Pengawas (
OrganYayasanForm.php:11-13). Perkumpulan Rapat Anggota/Pengurus/Pengawas — Rapat Anggota stored in thepembinacolumn (_formPengurusPerkumpulan.php:99-110). Perkumpulan AD/ART required =asas_tujuan, hak_kewajiban, pengelola, mekanisme, pembubaran(FormDataPerkumpulan.php:126-128) —nama_lambangis NOT required (safe rule:116). - D10 — shared, ADDITIVE + regression: shared
OrganMembertypes, organ-aware extractor, and context builder serve both viavariant: "yayasan" | "perkumpulan". All shared/common-file edits MUST be additive (new enum values, newAKTA_SUBTYPE_MAProws, a NEW separate classifier taxonomy) and MUST NOT change PT/PP behavior. Regression suite stays green (see D10 section). - NAMA_AVAILABILITY source: SABH readonly
tbl_order_name(OrderName) +tbl_badan_hukum(BadanHukum), columnbase∈ {yayasan,perkumpulan} (FormDataYayasan.php:389,393) via a newlib/sabh-db.tsquery. Degrade to SKIP when!isSabhAvailable(). - BLOCKED-ON-S5: real akta menyusul; synthetic REJECTED. Akta prompt engineering + tuning + accuracy tests are BLOCKED until akta arrive. Every other task proceeds in parallel. Tasks that touch prompt/accuracy are marked 🔴.
File Structure
| File | Create/Modify | Responsibility |
|---|---|---|
backend/prisma/schema.prisma |
Modify | enum SubmissionType (:70-134) += PENDIRIAN_YAYASAN, PENDIRIAN_PERKUMPULAN; enum DocumentType (:20-60) += AKTA_PENDIRIAN_YAYASAN, AKTA_PENDIRIAN_PERKUMPULAN, SURAT_PERTIMBANGAN_KEMENLU. NO new tables. |
backend/src/flow-engine/registry.ts |
Modify | imports (:3-17) + 2 FLOW_REGISTRY entries (:27-44). Missing key = compile gate. |
backend/src/services/submission-dispatch.ts |
Modify | processorKindForType (:15-77) += 2 cases → "v2-generic" (satisfies never at :72-75). |
backend/src/flow-engine/types.ts |
Modify | RouteFamily union (:16-18) += "yayasan", "perkumpulan". |
backend/src/schema/akta-yayasan.ts |
Create | Yayasan akta extraction schema (organ + kekayaan + AD). 🔴 prompt tuning blocked. |
backend/src/schema/akta-perkumpulan.ts |
Create | Perkumpulan akta schema (organ + AD/ART). 🔴 prompt tuning blocked. |
backend/src/schema/organ-nirlaba.ts |
Create | D10 shared OrganMember + variant types consumed by both schemas. |
backend/src/services/nirlaba-akta-classifier.ts |
Create | Separate classifier taxonomy (apostille-classifier pattern) — yayasan/perkumpulan akta labels; NOT AKTA_TXN_TYPES. |
backend/src/ocr/classifier.ts |
Modify | AKTA_SUBTYPE_MAP (:69-94) += AKTA_PENDIRIAN_YAYASAN → "yayasan", AKTA_PENDIRIAN_PERKUMPULAN → "perkumpulan" (additive rows). |
backend/src/flow-engine/context/nirlaba-pendirian-context.ts |
Create | D10 shared organ-aware buildContext with variant; thin per-flow wrappers. |
backend/src/flow-engine/rules/yayasan-pendirian/index.ts |
Create | YAYASAN_PENDIRIAN_RULE_SET (§7 Yayasan) + YayasanPendirianCtx. |
backend/src/flow-engine/rules/perkumpulan-pendirian/index.ts |
Create | PERKUMPULAN_PENDIRIAN_RULE_SET (§7 Perkumpulan). |
backend/src/flow-engine/rules/nirlaba-shared/index.ts |
Create | D10 shared rules: aktaPresentRule, organCompletenessRule(variant), namaAvailabilityRule(base). |
backend/src/flow-engine/flows/yayasan-pendirian.ts |
Create | defineFlow tenant (primaryAkta AKTA_PENDIRIAN_YAYASAN, companyLookup:false, buildContext, rules, routeFamily:"yayasan"). |
backend/src/flow-engine/flows/perkumpulan-pendirian.ts |
Create | defineFlow tenant (primaryAkta AKTA_PENDIRIAN_PERKUMPULAN, routeFamily:"perkumpulan"). |
backend/src/lib/sabh-db.ts |
Modify | += queryNamaAvailability(nama, namaSingkat, base) (readonly, tbl_order_name + tbl_badan_hukum). |
backend/src/routes/klasifikasi-doc-types.ts |
Modify | += YAYASAN_DOC_TYPES, PERKUMPULAN_DOC_TYPES (:3-158); register in aggregate (:163) + SubmissionType → DOC_TYPES Record (:189-200). |
backend/src/routes/klasifikasi.ts |
Modify | base-selection ladder (:659-683) += 2 branches. |
backend/src/services/submission-type-inference.ts |
Modify | inferSubmissionType (:118-159) += 2 rungs ABOVE the PENDIRIAN_PT fallback (:159); mapToDocumentType (:12-49) + getProcessingOrder (:53-71) += rows for the 3 new doctypes. |
backend/src/routes/yayasan-pendirian.ts |
Create | Route family: review-data + submit (terminal). Mount in index.ts (~:123). |
backend/src/routes/perkumpulan-pendirian.ts |
Create | Route family (terminal). Mount in index.ts. |
frontend/src/lib/classifier-labels.ts |
Modify | CLASSIFIER_LABEL_MAP (:1) += 3 labels. |
frontend/src/lib/flow-steps.ts |
Modify | YAYASAN_LABELS/PERKUMPULAN_LABELS (terminal, :13-86); SubmissionLite.type union (:103); computeFlowStep ladder (:229-243). |
frontend/src/lib/destination-for.ts |
Modify | type union (:1-4); TERMINAL_TYPES (:22-25) += both; base map (:30-50). |
frontend/src/pages/KlasifikasiPage.tsx |
Modify | DetectedFlow→type (~:104); labelType (:132,:343-352); navigateToSubmission (:736-767) += 2 branches. |
frontend/src/pages/YayasanExtractionPage.tsx / PerkumpulanExtractionPage.tsx |
Create | Extraction pages (copy PendirianExtractionPage). |
frontend/src/pages/*ReviewPage (yayasan/perkumpulan) |
Create | Smart Review — organ cards + AD/ART (perkumpulan) + validation. Shared organ component (D10). |
frontend/src/routes.tsx |
Modify | imports (:12-72); route rows (:120-174) += extraction/review/status per flow (status via TERMINAL_TYPES, cf. pendirian :159). |
Phases & Tasks
Phase 0 — Foundation & compile gates (NOT blocked)
- [ ] T0.1
schema.prisma: add 2 SubmissionType + 3 DocumentType;bun run db:push:test. - [ ] T0.2
registry.ts: temporary stub entries → resolve the missing-key compile error with the real flows in T4. - [ ] T0.3
submission-dispatch.ts: 2processorKindForTypecases →"v2-generic". - [ ] T0.4
types.ts:RouteFamily+="yayasan","perkumpulan".
Phase 1 — Classifier taxonomy (label/board NOT blocked; 🔴 akta model tuning blocked)
- [ ] T1.1
nirlaba-akta-classifier.ts: separate taxonomy (2 akta labels +SURAT_PERTIMBANGAN_KEMENLU), keyword-first + LLM seam (apostille pattern). 🔴 keyword tuning refined once akta arrive. - [ ] T1.2
ocr/classifier.ts: additiveAKTA_SUBTYPE_MAProws (yayasan/perkumpulan). - [ ] T1.3
klasifikasi-doc-types.ts+klasifikasi.ts: doc-type lists + base-selection branches. - [ ] T1.4
submission-type-inference.ts: 2 rungs above PENDIRIAN_PT fallback;mapToDocumentType+getProcessingOrderrows. Guard: must NOT steal PT/PP.
Phase 2 — Extraction schema + context + extractor (schema NOT blocked; 🔴 prompt/tuning blocked)
- [ ] T2.1
schema/organ-nirlaba.ts: sharedOrganMember+ variant discriminator (D10). - [ ] T2.2
schema/akta-yayasan.ts,schema/akta-perkumpulan.ts: field shapes + SABH mapping (design docs §4). - [ ] T2.3 🔴 Organ-aware extractor prompt(s) — BLOCKED-ON-S5 (write structure now, tune on real akta).
- [ ] T2.4
context/nirlaba-pendirian-context.ts:buildContext(variant)— derive organ list + kekayaan/AD-ART from extraction.
Phase 3 — Rules (NOT blocked)
- [ ] T3.1
rules/nirlaba-shared/index.ts:aktaPresentRule,organCompletenessRule(variant),namaAvailabilityRule(base). - [ ] T3.2
lib/sabh-db.ts:queryNamaAvailability(readonly). Unit test withisSabhAvailable()false → SKIP. - [ ] T3.3
rules/yayasan-pendirian/index.ts: +kekayaanAwalPresentRule,kekayaanAsingMinRule. - [ ] T3.4
rules/perkumpulan-pendirian/index.ts: +adartCompletenessRule(5 fields).
Phase 4 — Flows + routes + frontend (NOT blocked)
- [ ] T4.1
flows/yayasan-pendirian.ts,flows/perkumpulan-pendirian.ts: realdefineFlow; replace T0.2 stubs inregistry.ts. - [ ] T4.2
routes/yayasan-pendirian.ts,routes/perkumpulan-pendirian.ts: review-data + submit terminal; mount inindex.ts. - [ ] T4.3 Frontend mirrors:
classifier-labels,flow-steps(terminal),destination-for(+TERMINAL_TYPES),KlasifikasiPagebranches. - [ ] T4.4 Frontend pages: extraction (copy pendirian) + Smart Review (shared organ component) +
routes.tsxwiring.
Phase 5 — Tests & regression (structural NOT blocked; 🔴 accuracy blocked)
- [ ] T5.1 Structural: dispatch routing, inference precedence, rule emission (mocked extraction), NAMA_AVAILABILITY (mocked SABH).
- [ ] T5.2 D10 regression (see below) — PT/PP suites stay green.
- [ ] T5.3 🔴 E2E accuracy on real akta — BLOCKED-ON-S5.
- [ ] T5.4 Full-suite +
tsc --noEmit(both packages).
D10 — Shared-code discipline (additive + regression)
Shared files (Yayasan⇄Perkumpulan): schema/organ-nirlaba.ts, context/nirlaba-pendirian-context.ts, rules/nirlaba-shared/index.ts, nirlaba-akta-classifier.ts. Variant driven by variant: "yayasan" | "perkumpulan" — never by string matching on type.
Additive guarantee (must not disturb PT/PP):
- SubmissionType/DocumentType additions are new enum values only.
- AKTA_SUBTYPE_MAP gets NEW rows; existing PT rows untouched.
- Classifier taxonomy is a SEPARATE service — AKTA_TXN_TYPES (akta-txn-classifier.ts:14) is NOT modified.
- inferSubmissionType new rungs sit ABOVE the PENDIRIAN_PT fallback but must not match PT/PP doc-sets (guarded by test).
Regression suite that MUST stay green: flow-engine/__tests__/dispatch-routing.test.ts, flow-parity.test.ts, document-type-additive.test.ts, registry.test.ts, and the inferSubmissionType/submission-type-inference tests. Add cases proving: PT/PP dispatch unchanged; a PT/PP doc-set never routes to yayasan/perkumpulan; AKTA_SUBTYPE_MAP PT lookups unchanged.
Parallelization while waiting on S5
Only T2.3 and T5.3 (akta prompt + accuracy) are blocked. Order to maximize parallel throughput:
1. Now: Phase 0 → Phase 1 (T1.1-T1.4) → Phase 3 (rules + NAMA_AVAILABILITY) → Phase 4 (flows/routes/frontend) → Phase 5 structural + D10 regression. Schema shapes (T2.1-T2.2) + context (T2.4) also proceed.
2. On akta arrival: T2.3 (prompt tuning) → T5.3 (accuracy) → ship.
Result: the entire flow can be built, wired, and structurally tested before any akta exists; only extraction accuracy waits.
Self-Review / open items
- docs/flows convention: this repo has no
docs/flows/— design docs conventionally live indocs/superpowers/specs/. Flow docs were placed indocs/flows/per the explicit task path; relocate tospecs/if the team prefers. - Route family vs submissions.ts: PT pendirian rides
submissions.tscreate/start (no dedicated route file). Yayasan/Perkumpulan MAY do the same (thinner) — the dedicated route files (T4.2) are for review-data/submit only. Confirm preference. - BO questionnaire: treated as a non-OCR prerequisite (form). If the team later wants OCR over BO evidence, that is a separate slice.
- 🔴 S5 dependency: extractor accuracy unverifiable until real akta arrive.