Tidy-up — TASK CHECKLIST
Legend: [ ] todo · [~] in progress · [x] done · [!] blocked
Discipline: TDD (test first) for logic; every fork collapse must pass the old fork's suite as an equivalence oracle before the fork is deleted.
Executable slice = Phase 0–2 (the only slice we build now; re-judge after)
These map to the architecture doc's migration steps 0–2. Phase 0–2 is deliberately additive / low-conflict with the user's concurrent feature work.
Step 0 — Persistence Phase-0 + safety scaffold (unblocks everything, no behaviour change)
- [x] Squashed baseline:
migrate diff --from-empty --to-schema→prisma/migrations/0_baseline(replaced 5 stale migrations) - [x]
migrate resolve --applied 0_baseline(tidy-up DB) → "up to date". ⚠️ SHARED envs (dev/GPU/prod) need the same resolve ONCE before first migrate-deploy — needs Efran/coordination (documented in Dockerfile) - [x] Docker CMD
db push→migrate deploy; addeddb:deploy+db:driftscripts - [~] Local check-gate —
scripts/check.sh+.githooks/pre-push(built & working; runs both packages' typecheck + tests). Hook NOT globally activated (core.hooksPath is shared → would touch the main working dir); documented as opt-in. Cloud CI (GitHub Actions — the remote is GitHub) deferred pending go-ahead. - [x] Centralized 5 jsdom polyfills in
frontend/src/test/setup.ts(DOMMatrix, matchMedia, IntersectionObserver, ResizeObserver, Path2D) — fixed the whole-file import crash onPerbaikanReviewPage.test.tsx; supersedes the ad-hoc per-test IntersectionObserver stub (e9feb12). - [x] Drift gate:
db:drift(migrate diff --from-migrations --to-schema --exit-code) viaSHADOW_DATABASE_URL; wired intocheck.sh(conditional)
PRE-EXISTING REDS surfaced by the gate (both existed on
master, masked by no CI):
- [x]PerbaikanReviewPage.test.tsx— was a stale test that never ran; rewritten against real behavior + fixed a real nested-button bug (65278e8,357e4cc). FE suite green.
- [x] Backend flakes — BOTH fixed: (1) advisory fire-and-forget persist race (1462d87, teardown-safe persistReport); (2) global append-only-trigger toggle race across 21 test files (ff39948, migrated to session-localsession_replication_role). 6× full suite = 1600 pass / 0 fail. Check-gate hard-green.
- [x] Hard test-DB guard —src/lib/assert-test-database.ts(TDD, 7 cases) + wired intotest-setup.ts; throws unless the DB name ends in_test. Bonus: made the test DB configurable viaTEST_DATABASE_URL(loaded from root.env), which is what isolates this worktree ontoahu_ocr_tidyup_test.
- [x].gitignore backend/src/generated+ untrack it (55 files / ~97k LOC removed from tracking; regenerated byprisma generate)
- [x] Add backendtestnpm script ("test": "bun test")
Step 1 — Contract spine (whole-repo leverage, additive) — CORE COMPLETE
- [x]
AppError+ErrorCodes+toErrorBody+ globalapp.onError(JSONnotFounddeferred — collides with GET-only SPA fallback; needs /api-scoped static serve) - [x] Structured logger threading the existing
requestId(getLogger(c); used in onError) - [x]
validateEnv()zod env schema — fail-fast at REAL boot only (import.meta.main-gated; suite-safe) - [x] Repo-root
contract/+@contractalias (frontend tsconfig/vitefs.allow; prod build inlines it) - [x] Prisma →
contract/enums.gen.tsgenerator (db:gen-enums, chained intodb:generate) - [x] Enum-parity test + all 3 live drifts fixed (frontend DocumentType 6→23, SubmissionStatus 7→11; backend field-transformer FieldStatus +MANUAL)
- [x]
validateBody(c, schema)zod request-body helper (400 BAD_REQUEST/VALIDATION via onError) + exemplar adoption on the field-edit route - [x] TAIL:
ApiErrorclient (apiFetchpreserves{status, code, extra}) + M8 raw-fetch hook migration + validateBody migration COMPLETE (7 batches, all strictc.req.json()sites; 5 deliberately-tolerant empty-body sites preserved; suite-gated per batch) - [x] JSON 404 envelope for unmatched
/api/*(catch-all before the SPA fallback; BO test updated to the stronger contract) - [x] Last 7 generic
c.req.json<T>()sites ontovalidateBody→ zero raw request-body reads outside the 5 deliberately-tolerant sites - [x] console.*→logger sweep COMPLETE: variadic (console-compatible) logger upgrade TDD'd; 213 conversions across services/routes/lib/ocr/llm/middleware/index/config;
src/scripts/deliberately stays console (CLI harness output) - STEP 1: 100% COMPLETE. (The typed
RouteContract-with-response-typing is NOT a Step-1 item — per the accepted architecture sequencing it belongs to the ReviewData/contract co-design phase, migration steps 4–5.)
Step 2 — Security Phase-0 (additive, shadow) — 100% COMPLETE
- [x] Dependency-free HS256
TokenVerifier(SABH-SSO-swappable byiss) +signLocalToken(21ef427) - [x]
authenticate()— principal on context, NEVER rejects (53d6b47) - [x]
ROUTE_POLICIES(all 27 /api mounts) + shadowauthorize()logging would-denies + mount-exhaustiveness test (4e7d431) - [x] Real health probes:
/api/health/ready(DB required w/ timeout; GPU/Paddle informational) — the static liar is dead (17f331b) - [x] Metrics scaffold:
observability/metrics.ts+/api/health/metrics(17f331b) - [x]
installSecurity(app)wired into index.ts;SECURITY_JWT_SECRET/SECURITY_ENFORCEin config + env-schema (17f331b) - Scope notes: enforcement mode
ondeliberately runs as shadow + boot warning until token issuance exists (the per-family flip is Phase 4/5 of the accepted architecture). CORS allowlist / upload caps / rate limiting / PII lifecycle are the separate Phase-0b security workstream in the plan — scope question raised with Efran at the checkpoint. - [x] Real liveness/readiness health probes (DB + GPU + external-DB) — done above (
17f331b; stale duplicate line) - [x] Metrics + error-tracking scaffold — done above (
17f331b; stale duplicate line) - [x] Extend
middleware/request-context.ts:+principal +log—+loglanded with the logger work;+principallanded 2026-07-02 (2ca4e37):bindPrincipalstamps the server-VERIFIED JWT identity onto the audit ctx + request logger,authenticateauto-binds, andVerifikasiAuditEventpersistsprincipalSub/principalRolebeside the client-claimed actor (additive migration20260702162613). STEP 2 NOW FULLY COMPLETE. - [x] Outbox drain worker (
81f19ec, beyond original slice):drainOutboxOnce(sender)+startOutboxDrainLoop— at-least-once, per-row isolation, attempts/lastError bookkeeping, terminal FAILED. Capability only: not wired at boot (no real SABH sender yet; wiring = SABH-integration phase).
Roadmap (NOT in this slice — re-judge after Phase 0–2)
- Persistence Phase-1 (satellites + typed JSON codecs + single outbox + owner columns, dual-write)
- 🔴 CRITICAL SYNC: co-design normalised
ReviewData(Pillars 2+4+5) - OCR:
gpuJob()+ generatedgpu-contract.ts;saveExtraction()through the dual-write seam - Flow-engine on perubahan+akuisisi (forks-as-oracle), then flip
engine:'v2-generic' - Frontend review engine (simplest flow first; perubahan-delta last)
- Strangle remaining flows one-by-one (…apostille last)
- Security Phase-4/5: flip
SECURITY_ENFORCEper family - Cleanup: drop god-table columns, delete dispatch switch/if-chain + legacy verification generation
— Release readiness (post-migration): durable submission-workflow queue (backend) + inference queue (GPU svc). See spec §11.
Phase B — ReviewData strangler (per flow: projector + parity oracle + V2 page; copy the T4-5+11 pattern of docs/superpowers/plans/2026-07-03-reviewdata-engine-pp-pendirian.md)
- [x] PENDIRIAN_PP — engine + V2 page; Efran smoke test approved (4 waves) → route flipped to V2, /review-v2 redirects, legacy page + GET .../review-data endpoint retired (2026-07-04)
- [x] PEMBUBARAN_PP — COMPLETE (2026-07-06): engine + V2 page, 2 smoke waves, then Efran-approved flip: bare :id → EntryRedirect dispatcher (KTP done → /review else /upload), /review canonical, /review-v2 redirects; legacy page + usePpPembubaranReviewData + GET /:id/review-data all retired; sidebar PP → "Perseroan Perorangan" spell-out shipped alongside
- [x] PENDIRIAN_PT — COMPLETE (2026-07-08, Efran-approved): route flipped to PtPendirianReviewPageV2, /review-v2 redirects; legacy PendirianReviewPage.tsx + its dead-only use-review.ts hooks (useApproveSection/useUnapproveSection/useUpdateIdentityMatch/useSubmitSubmission) + HelpModal + REQUIRED_PENDIRIAN_SECTION_KEYS all retired; backend POST /:id/submit deleted (PT-V2 finalizes via /:id/review/finalize instead). GET /:id/review-data STAYS — generic endpoint still read by SubmissionStatusPage/AkuisisiDeltaReviewPage/PerubahanDeltaReviewPage until those flows strangle
- [~] PERALIHAN_PP_KE_PT — engine + V2 page BUILT (2026-07-08: projector w/ PADNN-verbatim invariant + pp_asal registry-entity incl. picker candidates; V2 page at /pt/peralihan-pp/:id/review-v2 w/ pp_asal matched/picker renderer, maskNik, finalize pre-gated on match). AWAITING Efran smoke → flip + retire legacy page/review-data
- [ ] PERUBAHAN_PP + PERBAIKAN_DATA_PP (+ collapse the FF4-grandfathered PP fork pair)
- [ ] (post smoke-test) diff wave: PERUBAHAN_PT + AKUISISI_PT; then PERBAIKAN_PT; APOSTILLE last
Phase C — Flow-engine migration (plan: docs/superpowers/plans/2026-07-09-migration-implementation-phases.md; ledger: …-pt-flow-v2-migration-plan.md)
Ruling (Efran 2026-07-09): priority = PP → Apostille → PT, and "all PP flows" = Full A+B — PP is done only when every PP fork processor is deleted (Axis A) AND all PP review projections are flipped (Axis B).
C0 — Prove the engine + shared harness (gate for all flips)
- [ ] C0.1 Efran smoke: PELEBURAN_PT + LAPORAN_RUPS_TAHUNAN end-to-end (only never-driven v2 tenants; fix fallout before anything rides the engine)
- [x] C0.2 DONE (2026-07-09): rematch-parity oracle extended to SIX surfaces — ValidationResult + checklist (pre-existing) + IdentityMatch (doc-id-independent projection; stale-row wipe + KTP-matcher rebuild proven) + ExtractedField immutability + final Submission {status,error,changeTypeResult}; AKUISISI leg added (own fork wipe + forcePeralihanOn changeTypeResult pinned) — both Wave-3b flip candidates covered. Scope note: initial extract/lookup stages are the SAME shared functions on both paths (processDocument/lookupCompany/matchers), so the differential harness targets the orchestration (wipes/matcher set/ordering/status writes) where forks actually diverge — documented in the test header. BE 2264/0
- [x] C0.3 DONE (2026-07-09): companyLookup gains resolve + hasUnresolved hooks; runFlow dispatches through them (missing hooks on a multi-source config → LOUD throw → ERROR, covered by test); peleburan flow config supplies its own resolver pair via lazy imports. Flow-engine + peleburan suites green (124/0), full BE 2263/0
- [ ] C0.4 (opportunistic) fold Peleburan/RUPS bespoke review-data routes into REVIEW_REGISTRY
C1A — PP review projections (finishes PP Axis B; can start parallel with C0)
- [x] C1A.1 PERUBAHAN_PP COMPLETE — FLIPPED 2026-07-09 (Efran ruling: one smoke pass after all PP done): /review renders V2, /review-v2 redirects, legacy page + tests retired (hooks stay — form page reads them; 5 dead hook exports noted for cleanup). Originally: projector = tenant #5 + FIRST DiffModel consumer (jenis-gated Ringkasan diff via new universal DiffSection renderer, registered as the default
kind:"diff"renderer); carrier sections diff-only, KTP/NPWP sections per-field, kbli/changed-BO rosters, pernyataan attestation; finalize Gate 1 relaxed to pernyataan-only (per-field semantics, legacy-safe); V2 page at /pp/perubahan/:id/review-v2 (legacy /review untouched). BE 2258/0 + FE 960/0 green. AWAITING Efran smoke → flip + retire legacy page (legacy /review-data endpoint STAYS — form step reads it) - [x] C1A.2 PERBAIKAN_DATA_PP COMPLETE — FLIPPED 2026-07-09 (same ruling): /review renders V2, /review-v2 redirects, legacy page + tests retired. Originally: tenant #6, second DiffModel consumer — string section keys mapped to jenis codes for the shared buildPerubahanDiffModel (data_pemilik→[6,9], nama never diffs); NO field-list sections (carrier scalars diff-only; confirmable surfaces = kbli + changed-BO rosters); konfirmasi lifecycle via prepare() lazy-expire + meta.expiresAt + KonfirmasiBanner + pembubaran slow-poll reuse; finalize section-gate relaxed to pernyataan-only (mirrors C1A.1). V2 page at /pp/perbaikan-pp/:id/review-v2. BE 2262/0 + FE 960/0 green. AWAITING Efran smoke → flip + retire legacy page (legacy /review-data STAYS — form step reads it)
C1B–D — PP processing migration (Axis A; briefs to be expanded when C0+C1A near done)
- [~] C1B spec DRAFTED + S1/S2 IMPLEMENTED dormant (2026-07-09; seams-s1-s2.test.ts pins seed-first/LOUD-fail/dormancy + selector-resolution/deselection-cleanup; S2 shipped WITHOUT allRules — notIn(emitted) delete already removes deselected rows, deviation recorded in spec §2): docs/specs/2026-07-09-flow-engine-pp-seams-spec.md — S1 hooks.seedDocuments (carrier synth, LOUD-fail) · S2 rules-as-fn + allRules delete-stale universe (ghost-row invariant) · S3 likely zero new surface (preValidationSteps exists; verify edit path, red-oracle-gated escalation) · S4 gates[] designed, impl deferred to Apostille w/ peleburan as first internal consumer. Non-seams recorded: konfirmasi handshake, BO doc-CRUD, jenis selection (all route-owned). IMPL awaiting Efran: spec review + whether S1/S2 may land (dormant) ahead of the C0.1 smoke gate
- [~] C1C STARTED (2026-07-09) — leg 1/4 PEMBUBARAN_PP done: PpPembubaranCtx (KTP fields + oldData owner + PRE-RESOLVED tri-state active check, peleburan-deviation-#5 convention) + 4 wrappers (bodies stay in the fork validator) + ppPembubaranShadow (tests-only, NOT registered) + pp-pembubaran-parity oracle (3 fixtures: rich 4×PASS · adversarial FAIL/SKIPPED tiers · dissolved-PP guard) — all green first run. Leg 2/4 PERUBAHAN_PP done (2026-07-09): fork's ~200-line gather EXTRACTED into shared gatherPerubahanValidationInputs + pure buildPerubahanCandidates (fork suites 1220/0 prove behavior-preserving); 22 wrappers over the SAME inputs type; selectPerubahanRules = FIRST S2 selector (jenis gate + conditional BO emission); 4-scenario parity oracle green (jenis gating, BO with/without changes, deselection stale-row removal). S1 carrier-synthesis extraction deferred to C1D (flip). Legs 3+4 done (2026-07-09): PENDIRIAN_PP gather+candidates extracted (fork suites 1305/0), 17 static wrappers, 2-fixture parity (founder-matched-doc preference pinned, bare SKIPPED tier); PERBAIKAN_DATA_PP gather+candidates extracted (57/0), 14 wrappers + selectPerbaikanRules (SECOND S2 consumer, string section keys), 3-fixture parity (exact code set, KBLI failure tier, conditional BO emission). C1C COMPLETE — all 4 PP shadows green. Ruling update (Efran): flips UNBLOCKED, smoke happens once after ALL PP done
- [~] C1D flips — 1/4 done: PEMBUBARAN_PP FLIPPED 2026-07-09 (first PP v2-generic tenant): shadow promoted to flows/pp-pembubaran.ts, registry + dispatch flipped (stuck submissions now RECOVER through runFlow instead of ERROR — reprocess-entrypoint gap closed), edit path routes via reMatchAndValidateFlow (parity leg pins live-path equivalence + end-status), route-inline validator calls stay (shared body, no fork file exists). 2+3/4 done: PERUBAHAN_PP + PERBAIKAN_DATA_PP FLIPPED 2026-07-09 — new hooks.beforeValidation derive seam (post-extraction, pre-validation, LOUD); fork pipelines split into seedXCarrier (S1) + deriveXAfterExtraction (derive) hook bodies; processPpPerubahan/processPpPerbaikan DELETED; /start routes dispatch through the registry; processor tests repointed at dispatchProcessor (now REAL engine-pipeline e2e tests); recorded deviation: engine validates 'submission' mode (fork initial ran draft) — indistinguishable at dispatch (changedBos empty; routes own draft-mode form-phase calls). 4/5 done: PENDIRIAN_PP FLIPPED 2026-07-09 — buildContext gains a PHASE param (dormant; engine passes 'initial' from continueFlow, 'rematch' from reMatchAndValidateFlow) so the fork's draft/submission split survives (initial=draft keeps BO kriteria SKIPPED for the projector's prepare() promotion; edit path=submission); ALL hard-stop guards (SP present+extracted, KTP/NPWP present) moved into the derive hook as THROWS with the fork's exact messages; processPpPendirian DELETED; processor + BO e2e tests repointed at dispatchProcessor (real engine pipeline). 5/5 DONE: PERALIHAN_PP_KE_PT FLIPPED 2026-07-09 — the deed IS primaryAkta (fork's exact error messages); matchAsalPp = derive hook + rematch preValidationSteps (fork edit-path parity); FIRST validationScope consumer (namespace-scoped delete-stale — this flow SHARES ValidationResults with the generic PT deed pipeline; scoped-persist guard test proves generic rows survive); processPeralihanSubmission DELETED. ★ C1D COMPLETE — ALL FIVE PP FLOWS ON v2-generic, ALL FORKS DELETED. PP = Full A+B done pending Efran's single smoke pass ★
C2 — Apostille (after PP done; S4 gate seam lands here w/ peleburan as first internal consumer; jalur-prioritas = requirements doc only)
C3 — PT (port PEMBUBARAN_PT→BERAKHIRNYA_STATUS_PT reconcile-not-overwrite; flip PERUBAHAN+AKUISISI [smoke akuisisi FIRST] then PENDIRIAN+PERBAIKAN; build PENGGABUNGAN fresh; endgame: delete dispatch switch, merge registries)
Phase C-R — smoke-revision batch (Efran, 2026-07-09 smoke: "very impressive, minor revision")
- [x] R1 Pendirian: Unduh Template + Unduh Contoh Surat Pernyataan buttons (files pulled from valbox:/home/efran/temp_ahuai_data → backend/templates/; GET /api/pp/pendirian/template-surat + /contoh-surat, surat-keterangan idiom; buttons on the PP klasifikasi upload page)
- [x] R2 Pendirian review: KBLI rows click→bbox (projector emits bbox-guarded cellFieldRefs kbli..kode/.judul; page adopts the PT two-convention focusField)
- [x] R3 Perubahan: NAMA/MODAL jenis must not cross-edit each other's field + step-2 'Data Perseroan' title for MODAL (split data_perseroan → nama_perseroan + modal_perseroan sections; Rebuild checked: write layer is whole-form but name-check gates on jenis 1 — per-jenis field scoping is the intended UX)
- [x] R4 Perubahan: camelCase field titles on the form → proper labels
- [x] R5 Perubahan: 'Lanjut ke Tinjau' enable delay after a field edit (diff is read-time from field values — refetch review-data immediately on edit success instead of only the 2s/5s validation polls)
- [x] R6 Perubahan + Perbaikan: show document preview in step 2 whenever uploaded docs exist
- [x] R7 Pembubaran upload (Verifikasi Identitas Pemilik card): show the KTP preview in/beside Verifikasi Identitas Pemilik
- [x] ROADMAP → DONE (Phase G, 2026-07-10): PP Pendirian document BUILDER for the Surat Pernyataan, like PT's Surat Keterangan Perbaikan generator (template now served at R1; builder = form → filled docx/pdf)
Phase G — smoke-revision batch 3 (Efran 2026-07-10)
- [x] G1 berakhirnya step-3 vs step-2 doc-requirement drift: BSBHP_REQUIRED_DOCS no longer hard-requires KTP/NPWP (label → "6 PDF"); flow requiredDocs declaration aligned; shared KTP/NPWP rules already degrade to SKIPPED/WARNING when absent
- [x] G2 berakhirnya extraction crash ("extraksi rusak"): SURAT_PERMOHONAN + LAPORAN_LIKUIDASI had NO processDocument case → fell into the generic Akta handler whose doc-classifier gate rejected them ("Classifier confidence too low: PENETAPAN_GANTI_NAMA"). New OCR-only case 6l (mirrors SURAT_LIKUIDATOR's 6k); grounded extraction stays in the flow-engine focusedPasses
- [x] G3 AI summary EVERYWHERE (async, non-blocking, Laporan-RUPS flow): shared AiSummaryCard component; /summarize gained AKUISISI/PENGGABUNGAN (delta-family builder w/ type-aware heading), PEMBUBARAN (deed + likuidator letter), BERAKHIRNYA (6-PDF package + sisa kekayaan + pengumuman), PELEBURAN (pendirian-style dump); card added to PT pendirian/perubahan/akuisisi/penggabungan V2 pages + pembubaran/berakhirnya/peleburan review pages
- [x] G4 SP Pendirian PP BUILDER (the roadmap item, now built): SpPendirianPp model (ONE JSON payload — all letter fields, unlike SK's per-field corrections) + /api/pp/sp-pendirian routes (draft autosave accepts partials; generate/preview 409-gated on requiredFieldGaps) + docxtemplater template (scripts/build-sp-pendirian-template.ts, OCRA font, KBLI/BO loops, 3 statutory pernyataan clauses, terbilang) + libreoffice pdf; FE: /pp/sp-pendirian list + /pp/sp-pendirian/new all-fields builder (SK look & feel, NO field toggles — completeness checklist gates Terbitkan), sidebar entry, "Buat Surat Pernyataan" button on /pp/pendirian/klasifikasi
Phase H — smoke-revision batch 4 (Efran 2026-07-10, all done)
- [x] H1 'Belum punya Surat Pernyataan?' card redesigned (accent panel, icon block, primary CTA, downloads → quiet text links)
- [x] H2 OCR-A → Arial on BOTH docx template sets (SP Pendirian PP + SK Perbaikan; templates rebuilt — Liberation Sans substitutes in-container, real Arial in Word)
- [x] H3 Ringkasan AI cached on Submission.aiSummary (13.2s first run → 0.08s reopens, verified live; {refresh:true} via the "Perbarui" button regenerates); card renders LLM markdown (react-markdown + scoped typography) justified
- [x] H4 TAHUN_LAPORAN_VALID anchored to submission.createdAt on ALL surfaces (rule + PUT + review-data tahunWindow the dropdown renders from) — no more wall-clock window shifting under a valid pick / browser-clock dropdown offering years the server rejects
- [x] H5 Bukti Setor REWRITE built (was roadmap): setoran-facts pass (PaddleOCR + grounded 3B, digit-run grounding for amounts) on slip bank/rekening koran/SK bank/neraca → BuktiSetorExtraction.transferFacts; review section = "Daftar Setoran" (one row per fact across files, DYNAMIC columns, no file/jenis — source doc in Detail expander, display-only). NO backwards compat: legacy scans w/o facts contribute no rows. Verified live on 341b7930: 18 setoran rows compiled from slip+rekening koran+SK bank
Phase F — smoke-revision batch 2 (Efran 2026-07-10, all done)
- [x] F1/G1 Dashboard READY rows enter at STEP 3 (/review via destinationFor), never the extraction page
- [x] F2/G2 SABH doc-requirement re-check for the new flows (verified against ahu-online sabh module 2026-07-10): pembubaran + penggabungan wizards are DATA-ENTRY (no uploads) → KTP/NPWP/domisili/kontak downgraded to optional cross-check extras; berakhirnya = EXACTLY the 6-PDF package (_formPerubahanStatusDetail) → KTP/NPWP optional; deed + likuidator letter stay required (they carry the payload)
- [x] F3/P1 perubahan review: NEW top-most "Data Perseroan Terdaftar (SABH)" registry-entity card (display-only, UNCHANGED data only — fields covered by an appearing jenis are omitted); "Semua Field Akta" slimmed to the IDENTIFYING subset (akta nomor/tanggal/notaris, perseroan name, SK refs — mirrors what the SABH perubahan wizard anchors on) and renamed "Identitas Akta & Perseroan"
- [x] F4/P2 snake_case field titles → human labels (backend pt-akta-field-labels.ts map + humanizer; laporan-rups akta card too)
- [x] F5/P3 diff rosters: icon-only +/−/✎ badges with ONE legend per section ("+ = Ditambah · − = Dihapus · ✎ = Diubah · = Tetap")
- [x] F6/L1 laporan-rups extraction crash: share-count columns (AktaModal.jumlahSaham/jumlahSahamModalDasar, AktaFounder.jumlahSaham, AktaPemegangSaham.jumlahSaham) widened Int→Float — real deed carried 2,580,009,000 lembar (> Int32); migration 20260710040000
- [x] F7/L2 laporan-rups AI summary: /summarize gained a LAPORAN_RUPS_TAHUNAN builder (RUPS identity + tahun/audit + roster kehadiran + docs + validation notes); review page auto-generates it on step-3 entry with a skeleton, error state has retry
- [x] Reminders saved to memory (Bukti Setor rebuild, SP Pendirian PP generator)
Phase E — smoke-revision batch (Efran 2026-07-10, all done + DEPLOYED to demo @5e6fac38)
- [x] R1 per-file extraction time: DONE/ERROR docs stop ticking when currentDocumentId lingers through post-extraction phases (the 36s file vs 1m30s pipeline mismatch — the 36s was correct)
- [x] R2 step-4 entry point: DashboardPage defers to destinationFor for EVERY PT type (stale base map fixed); new /perbaikan/:id/status page; COMPLETED/IN_VERIFICATION never lands on review again
- [x] R3 pendirian Bukti Setor: per-entry pemegang-saham rows REMOVED (doc-level rows only, no confirm affordance). ROADMAP: full Bukti Setor section rebuild (what should it verify? doc-level totals × modal disetor cross-ref UI?)
- [x] R4 sticky PDF column capped to viewport + self-scrolls (all 9 V2 pages)
- [x] R5 failing-rule gate-hint buttons right-aligned (button UA default was center)
- [x] R6 review-route FLIPS: /perubahan /akuisisi /perbaikan → V2 ReviewData pages CANONICAL (legacy pages unrouted — delete post-smoke); PENGGABUNGAN projector (delta-factory tenant #3) + type-aware title. NOTE deltas vs legacy for the smoke: in-review jenis-selection editing, notaris-pengganti override UI, and the koran/pengumuman card did NOT carry over to the V2 perubahan page (jenis selection lives in the step-2 wizard; NP/koran are advisory) — flag if needed
- [x] R7 'belum didukung' for pembubaran/penggabungan/berakhirnya = STALE DEMO. Redeployed x056.ahu-demo.ocr.val.id (ahu-ai-ocr-web @192.168.83.20:3520) from 5e6fac38; migrations auto-applied; endpoints verified live
- [x] R8 peleburan samples (peleburan.zip): current build classifies all 3 CORRECTLY (BPR-RAY/KBU → peleburan via recital tier; DANA-MASTER-LOTARA → pendirian, boilerplate pasal ignored) — was a stale-deploy artifact; regression fixtures pin both directions
Phase D — PT transformation + forgotten-SABH flows (Efran ruling 2026-07-09 evening: EVERYTHING in scope; priority chain diverted to PT; ONE smoke pass after all done; feat/pt-flows-registry is FROZEN — port freely; verifikator-side pages OUT of scope → future roadmap)
Mission framing: modernize SABH's document workflows — automated upload-and-process + cross-doc consistency checks (e.g. Akta nama/NIK × KTP nama/NIK).
- [x] D0 fix the red klasifikasi-akta-txn assertion (fixture label outdated by the PENETAPAN_GANTI_NAMA contaminated-class commit; intent preserved on KORAN)
- [x] D1 PORT PEMBUBARAN_PT (done 2026-07-09, committed WITH D2 — routing-layer entangled) from frozen feat/pt-flows-registry (flows/pembubaran-pt + context + rules + routes/pembubaran + 3 services + tests; reconcile onto current engine interfaces; registry stub → real flow; BUILT_AKTA_TXN_TYPES += pembubaran)
- [x] D2 PORT BERAKHIRNYA_STATUS_PT (done 2026-07-09) (SubmissionType enum member + additive migration + prisma regen; flows/berakhirnya-status-pt + context + rules + routes + berakhirnya-doc-classifier + shared likuidasi extractors + tests; BSBHP_REGISTRY_STATE prerequisite gate; BUILT += berakhirnya; depends on D1)
- [x] D3 FLIP PERUBAHAN_PT (done 2026-07-09) (shadow → registered flow; delete processPerubahanSubmission/continuePerubahanProcessing fork half; routes delegate; verifikasi staging + advisory stay route-owned; oracles = the bar)
- [x] D4 FLIP AKUISISI_PT (done 2026-07-09) (same; forcePeralihanOn hook already in shadow; keep classification-override extraction)
- [x] D5 SHADOW + FLIP PENDIRIAN_PT (done 2026-07-09; engine gained extraction.concurrency/docTimeoutMs + rematch.beforeMatchers seams) (submission-processor.ts 943 lines; gather-extraction recipe; checklist build + matchers ride engine rematch/standardPtMatchers)
- [x] D6 FLIP PERBAIKAN_DATA_PT (done 2026-07-09; custom-pipeline tenant — customProcess + rematch.custom seams; no shadow needed, validator shared not forked) (initializePerbaikan + reMatchAndValidatePerbaikan; perbaikan is form-driven — check S1/derive needs)
- [x] D7 AXIS B: PERUBAHAN_PT projector + V2 page (done 2026-07-09 — NEW ROUTE ONLY /perubahan/:id/review-v2; legacy delta page stays canonical until the smoke pass, PP tenant-#5 precedent). Was ORDER SWAPPED after D10 (2026-07-09 autopilot call: the legacy delta pages WORK; the missing merger flow was the bigger gap; a rushed rewrite of the 3174-line PerubahanDeltaReviewPage right before the smoke pass would risk a working surface). Projection inventory (derived, don't re-derive): jenis-appearing sections pad:nama/tempatKedudukan/statusPerseroan/jangkaWaktu/modal/maksudTujuan + ppad:pasalYangDiubah + nonpad:direksi/perubahanNamaPemegangSaham/peralihanSaham/alamat (MANUAL_APPROVAL_SECTIONS → PerubahanSectionApproval, the submit gate's table) + data-kontak (derived approval) + attestation; diff = computePerubahanDiff(loadDiffInput) re-expressed as DiffModel scalar-list/roster/pasal parts; per-row akta confirm refs via aktaRowMetaByName; "Semua Field Akta" panel
- [x] D8 AXIS B: AKUISISI_PT projector + V2 page (done 2026-07-09 — delta-factory tenant; advisory legal-path radios; NEW ROUTE ONLY /akuisisi/:id/review-v2; also fixed the D10 gap: PENGGABUNGAN_PT joins the sections-only submit gate branch)
- [x] D9 AXIS B: PERBAIKAN_DATA_PT projector + V2 page (done 2026-07-09 — per-correction sections mirroring the 4 submit blockers; per-item attestation posts; NEW ROUTE ONLY /perbaikan/:id/review-v2)
- [x] D10 BUILD PENGGABUNGAN (done 2026-07-09 — rides the perubahan family end-to-end like AKUISISI, survivor lookup + grounded merging-source extraction into shared PeleburanSourcePt + 3 penggabungan rules; FE reuses perubahan pages) (merger, SABH id_aksi 11) — new flow modeled on Peleburan (surviving-PT variant): enum + migration, classifier BUILT flip, flow config + context + rules (cross-doc consistency incl. akta×KTP identity), routes, review projection + FE, tests. NO donor anywhere — greenfield on the engine
- [x] D11 CLEANUP + ROADMAP (CLOSED 2026-07-09: roadmap notes written; code cleanup DELIBERATELY GATED on Efran's smoke pass — deleting fork validation halves/oracles/dead branches right before the smoke would trade regression safety for tidiness; the inventory below is the post-smoke worklist):
- CLEANUP inventory (all provably safe post-D3..D6): dead reMatchAndValidate type-branches in submission-processor.ts (every branch below the v2 short-circuit except APOSTILLE is unreachable — all those types are v2-generic now); fork VALIDATION halves + their oracle aliases (perubahan/akuisisi/pendirian) retire TOGETHER with flow-parity/rematch-parity/pendirian-flow-parity once Efran's smoke pass blesses the flips; runPerubahanCrossValidation call sites in routes/perubahan.ts (selected-jenis PATCH/DELETE) migrate to queueReMatchAndValidate at the same time; grandfathered fork pairs (berakhirnya|pembubaran routes → shared section-approval helper; penggabungan|peleburan source extractors → parameterized extractor); FE dead-hooks sweep
- ROADMAP: (1) D7-D9 Axis B PT projections (inventory in D7 above); (2) verifikator-side ReviewData pages incl. BERAKHIRNYA/PEMBUBARAN PT verifikator surfaces; (3) PP Surat Pernyataan BUILDER; (4) Apostille wave (S4 gates + jalur prioritas); (5) PENGGABUNGAN enrichment: per-source resolve/select UI (rows exist, resolutionStatus stays PENDING — validation is WARNING-first by design), SABH outbox onFinalize; (6) legacy /review-data retirement after Axis B
- Original scope: dead reMatchAndValidate type-branches, dead FE hooks sweep, ProcessorKind residue; roadmap notes → verifikator-side ReviewData pages, PP Surat Pernyataan builder, Apostille wave (S4 gates + jalur prioritas), BERAKHIRNYA/PEMBUBARAN PT verifikator surfaces
- GATES per step: fork suites green BEFORE shadow work · parity oracle green BEFORE flip · full BE+FE suites green BEFORE commit · commit per step