Peleburan PT — whole-flow review fixes
Branch: feat/pt-flows-registry. All items applied, verified once, committed.
Fixes
C1 — verifikator could never APPROVE a peleburan (empty roster → 422)
- Roster source chosen: the
AKTA_PELEBURANdeed'sAktaPemegangSahamrows (the NEW PT's post-transaction roster). Peleburan has NOoldData.pemegang_saham(null by design), and the deed is extracted with the pendirian schema, so it always carriesAktaPemegangSaham(proven:review-dataalready mapsakta.pemegangSahamfrom that relation, andPtAktaCtxreads it). No product fork — deed roster rows exist in practice. - Widening kept roster-LOCAL:
PERUBAHAN_FAMILY_AKTAis also consumed by the dispatch-context finder +isPerubahanFamilyAkta, so I did NOT widen the shared const.resolveVoterRosteruses a localROSTER_AKTA_TYPES = [...PERUBAHAN_FAMILY_AKTA, "AKTA_PELEBURAN"]with a comment. - "PublicVotingPage guard": there is NO frontend type guard (verified —
PublicVotingPage.tsxrenders unconditionally). The real place that resolved placeholders for peleburan was the backendvote.tsakta-doc finder (andresolveAktaDispatchContextfor the invitation email). Widened both to acceptAKTA_PELEBURANso the public voting page + email show the real NEW-PT name/date/notaris instead of placeholders. - Tests: BE decide-APPROVE (
verifikator-peleburan.test.ts) seeds a peleburan submission + verifikasi + deedAktaPemegangSaham→ APPROVE →MENUNGGU_VOTING+ oneShareholderVoteper deed pemegang saham; roster-resolver unit case inshareholder-voting.test.ts.
C2 — sources panel unreachable + zero-source hole
- (a) FE: "Kelola PT Sumber" button on
PeleburanReviewPage's sources section (visible only when status READY) opens the existingPeleburanSourcesPanel. Added an optionalonCloseprop to the panel (dismissible in READY; still blocking/no-close in AWAITING).onResolved→ invalidatepeleburan-review. Test asserts the button renders + opens the panel. - (b) Gate semantics chosen:
PELEBURAN_MIN_SOURCESnow FAILs at 0 when the deed is extracted; SKIP kept ONLY while the deed hasn't extracted yet. Distinguisher =ctx.aktaFields.length > 0(the ctx knows the akta status; validation only ever runs post-extraction — Phase 5 / re-validate — so in practice an approved empty set FAILs). Ps.122 citation in the rule + tests updated (0→FAIL).
I1 — SABH hang timeout
peleburan-sources.ts: hardPromise.racetimeout (apostille-registry-hang pattern) around all three SABH primitives (lookupCompany,loadOldData,loadPerseroanBlokirState) via awithTimeouthelper. Budget =SourceResolveDeps.timeoutMs, defaultPELEBURAN_SABH_TIMEOUT_MS || 10000, injectable for tests. Hang→throw→existing per-row catch degrades the row toAWAITING_SELECTION. New test: never-resolving injected lookup + 100ms budget → row degrades within timeout.
I2 — partial-verification WARNING
PELEBURAN_SOURCE_BLOKIR+PELEBURAN_SOURCE_STATUS: when SOME rows verified and SOME unknown (MANUAL / no registry state) → WARNING naming the unchecked rows (was a clean PASS). All-unknown stays SKIPPED; all-verified-clean stays PASS. Tests add a mixed case → WARNING.
I3 — submit 409 envelope
routes/peleburan.tssubmit wrong-status 409 now carriescode: "WRONG_STATUS". Routes test asserts the code.
I4 — loud comment
flow-engine/processor.tsmulti-source branch: comment warning theimport("../services/peleburan-sources")is peleburan-coupled; a second multi-source tenant must add a per-flow resolve hook on the companyLookup config, not copy this wiring.
Minor folds
runPeleburanDeedPassesper-row loop → singlecreateMany(atomic count for MIN_SOURCES).- Stale comments refreshed:
focused-passes.ts("later flows" — bukti-pengumuman is live),registry.ts("until Task 6 registers" stub note above two live tenants), and thelaporan-rups-routing.test.tsladder describe-title (now includes Peleburan).
Verification
bunx tsc --noEmit— backend + frontend: clean.- Backend:
rules-peleburan+shareholder-voting+peleburan-sources= 26 pass;src/flow-engine+peleburan-routes+verifikator-peleburan+verifikator-decide+laporan-rups-routing= 143 pass;peleburan-integration(E2E) +vote-public.integration= 8 pass. - Frontend:
PeleburanReviewPage+PeleburanSourcesPanel= 13 pass.
Concerns
- The C1 email dispatch is fire-and-forget (background); in demo mode (SMTP unset, as in tests) it no-ops. Verified the APPROVE transition + vote rows are committed inside the tx before the response.
PELEBURAN_SOURCE_STATUSpartial-WARNING keys off "no registry state" which is also the SABH-unavailable signal — acceptable (all-unavailable still SKIPs; only a genuine mix warns).