think
16px
820px

Proposal to dev_4: one optional ttl field on OrganMember

From: dev_5 (Pendirian Yayasan) · To: dev_4, who owns schema/organ-nirlaba.ts
Date: 2026-08-11 · Status: awaiting a decision

The ask

One optional field on the shared OrganMember type:

 export interface OrganMember {
   nama: string | null;
   jabatan?: string | null;
   nik?: string | null;
   npwp?: string | null;
   paspor?: string | null;
   kitas?: string | null;
   negara?: string | null;
+  /** Birth date, ISO YYYY-MM-DD. Needed to cross-check the NIK. */
+  tanggal_lahir?: string | null;
 }

That is the whole request. Nothing else in your files changes, there is no
migration (the organ lives in rawExtractionJson, not a table), and no behaviour
changes until a rule reads the field.

Why: a NIK verifies its own birth date

A NIK is laid out PPKKCC DDMMYY NNNN — six digits of area code, then the birth
date, then a four-digit serial. For women 40 is added to the day (08 becomes
48), which also means the holder's gender can be read back without storing one.

So a NIK and a stated birth date check each other with no second document
involved
. That matters precisely here: D11 dropped every identity rule from the
nirlaba flows on the grounds that they have no anchor document, which is true.
This check does not need an anchor — the deed's own contents are enough.

The evidence, from two real deeds

Run across 13 people in the two Pendirian Yayasan samples (2026-08-11):

Result Count Notes
Exact match 11 including the +40 convention on three women
Match, but the NIK has 17 digits 2 one stray digit at the tail; one before the date block
Disagreement 1 the NIK says 8 March 2001, the deed text says 8 May 2001

dev5 checked that last one by hand against the PDF: the extraction is right
and the deed itself is wrong. So this is not a theoretical check — on its first
run it found a real document defect that nothing else in the flow would catch.

The two 17-digit NIKs were diagnosed by the same check: because their date block
is still findable, its position shows where the stray digit sits.

Already built, touching nothing you own

backend/src/lib/nik-birthdate.ts on dev/5 — pure, dependency-free, two strings
in and one verdict out:

checkNikBirthdate(nik, birthdate, referenceYear)
//   { status: "MATCH",       nikBirthdate, gender }
// | { status: "MISMATCH",    nikBirthdate, stated, gender }
// | { status: "UNCHECKABLE", reason }

Twelve tests, all green. The design decisions that matter to you:

  • UNCHECKABLE is first-class and never folded into MISMATCH. A missing
    NIK, a 17-digit one, or a date the model wrote out in words must never reach a
    notaris as "these two disagree".
  • referenceYear is a parameter, not the system clock, so the century split
    (yy ≤ 26 reads as 2000s) stays deterministic in tests.
  • Every NIK in the test file is fabricated. The real ones live only in the
    gitignored sample golden set.
  • It sits in lib/ rather than the yayasan rules because it is generic:
    PENDIRIAN_PP already holds founderNik and founderTanggalLahir side by side
    with no rule relating them, and every KTP-bearing flow has the same pair.

The rule this unlocks

ORGAN_NIK_TTL_MATCHWARNING, not FAIL.

WARNING because the deed itself can be wrong (now proven) and this flow is
read-only. Failing a document defect a notaris may not be able to fix would just
make overrides routine, and routine overrides stop meaning anything.

  • SKIPPED when no organ member has both a NIK and a birth date — a foreign
    member identified by passport is simply skipped, not treated as a failure.
  • The message names which member disagrees, with both dates. Without that the
    notaris has to guess which row is meant.
  • Members that come back UNCHECKABLE are counted separately in details rather
    than passed over in silence.

dev_5 writes the rule, in rules/nirlaba-shared, so Perkumpulan inherits it
without writing anything — its organ uses the very same OrganMember.

A bonus that needs nobody's approval

ORGAN_NIK_FORMAT — length and numeric shape — needs only nik, which is
already on the type. The asing sample has 2 malformed NIKs out of 8, so the
rule has evidence and could be built at any time without waiting on anyone. It is
unbuilt only because adding a rule beyond the agreed D11 list is a scope call,
not a technical one.

Cost and risk

Aspect Assessment
Change to dev_4's files one optional field
DB migration none — the organ lives in rawExtractionJson
Impact on Perkumpulan none until they choose to use it; optional, may stay null forever
Impact on PT/PP none — this type is not used there
Regression risk nothing reads the field until ORGAN_NIK_TTL_MATCH lands

Decisions needed

  1. dev_4: happy to add tanggal_lahir? to OrganMember? If you prefer the
    name ttl (as the flow doc §4 uses), say so — we will follow. What matters is
    one name across both variants.
  2. dev5: should ORGAN_NIK_TTL_MATCH join the set as a seventh rule,
    beyond the agreed D11 list? And ORGAN_NIK_FORMAT as an eighth?
  3. If both are yes, the Yayasan extraction prompt needs a small addition so each
    organ member carries a birth date. The deeds already state it in the penghadap
    block ("lahir di Kebumen, pada tanggal 08-07-1974"), so the data is there — it
    just has to be asked for.