think
16px
820px

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). DocumentType AKTA_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", ... }). processorKindForType returns "v2-generic"; the registry short-circuit owns routing (submission-dispatch.ts:89-94). Do NOT add a pp-*-processor-style fork; do NOT set customProcess (straight pendirian fits the standard pipeline).
  • TERMINAL, no verifikator (D4a/D4b): submit → COMPLETED; both types ADDED to TERMINAL_TYPES (destination-for.ts:22-25). No VerifikasiPerubahan row. Perkumpulan SABH kasie is downstream — NOT modeled.
  • Akta = authoritative (single-doc): primaryAkta SET (not null, contrast PP). No KTP/NPWP/bukti-setor/surat-pernyataan docs — those are PERUBAHAN in SABH (UploadAktaController.php:218 isYayasanPerubahanTransaksi id_aksi 3; :249 isPerkumpulanPerubahanTransaksi id_aksi 15). Pendirian upload = file_akta only (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 — anchor FormDataYayasan.php:434), NAMA_AVAILABILITY (WARNING).
  • Perkumpulan: AKTA_PRESENT, ORGAN_COMPLETENESS, ADART_COMPLETENESS (WARNING — 5 fields), NAMA_AVAILABILITY. No kekayaan rules (not required; Rp100jt disabled FormDataPerkumpulan.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 the pembina column (_formPengurusPerkumpulan.php:99-110). Perkumpulan AD/ART required = asas_tujuan, hak_kewajiban, pengelola, mekanisme, pembubaran (FormDataPerkumpulan.php:126-128) — nama_lambang is NOT required (safe rule :116).
  • D10 — shared, ADDITIVE + regression: shared OrganMember types, organ-aware extractor, and context builder serve both via variant: "yayasan" | "perkumpulan". All shared/common-file edits MUST be additive (new enum values, new AKTA_SUBTYPE_MAP rows, 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), column base ∈ {yayasan,perkumpulan} (FormDataYayasan.php:389,393) via a new lib/sabh-db.ts query. 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: 2 processorKindForType cases → "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: additive AKTA_SUBTYPE_MAP rows (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 + getProcessingOrder rows. Guard: must NOT steal PT/PP.

Phase 2 — Extraction schema + context + extractor (schema NOT blocked; 🔴 prompt/tuning blocked)

  • [ ] T2.1 schema/organ-nirlaba.ts: shared OrganMember + 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 with isSabhAvailable() 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: real defineFlow; replace T0.2 stubs in registry.ts.
  • [ ] T4.2 routes/yayasan-pendirian.ts, routes/perkumpulan-pendirian.ts: review-data + submit terminal; mount in index.ts.
  • [ ] T4.3 Frontend mirrors: classifier-labels, flow-steps (terminal), destination-for (+TERMINAL_TYPES), KlasifikasiPage branches.
  • [ ] T4.4 Frontend pages: extraction (copy pendirian) + Smart Review (shared organ component) + routes.tsx wiring.

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 in docs/superpowers/specs/. Flow docs were placed in docs/flows/ per the explicit task path; relocate to specs/ if the team prefers.
  • Route family vs submissions.ts: PT pendirian rides submissions.ts create/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.