think
16px
820px

PDP compliance assessment — AHU AI platform

Date: 2026-08-13 · Law: UU No. 27/2022 (Pelindungan Data Pribadi), transition ended 17 Oct 2024 — fully binding today · Scope: OCR engine, chatbot, gateway, observatory audit store, as deployed on ai-ahu staging

Not legal advice. Article mappings are my reading and need counsel sign-off. Every technical finding below is evidence-backed and reproducible.

Bottom line

The platform is architecturally well-placed on the hardest PDP requirement — everything is on-prem, so there is no cross-border transfer problem, which is the issue that sinks most Indonesian AI deployments. What is missing is not infrastructure but the ability to delete, and the paperwork that proves any of it.

One finding is structural and should be treated as blocking before production:

The immutable audit store contains personal data it is physically unable to delete.

# Finding Article Severity
1 Personal data in an append-only store that blocks DELETE Art 8, 9, 43, 44 Blocking
2 retention_days is a dead config knob — nothing enforces it Art 43(2) High
3 Specific (sensitive) personal data processed with no DPIA Art 34 High
4 No governance artifacts at all (RoPA, notice, DPO, breach runbook) Art 31, 46, 53 High
5 177 real KTP records in a staging environment with a dev-login path Art 16(1), 35 Medium

What we actually process

Measured from staging (ExtractedField, 6,329 rows):

Field Rows PDP class
nik 177 personal identifier (also restricted by UU 24/2013 Adminduk)
golongan_darah 177 specific — health data, Art 4(2)(a)
npwp 137 specific — personal financial data, Art 4(2)(f)
agama 177 arguably specific via Art 4(2)(g)
tanggal_lahir, tempat_lahir, jenis_kelamin, status_perkawinan, pekerjaan, kewarganegaraan 177 each general personal data
alamat.* (7 sub-fields) 177 each general personal data

Processing specific personal data is the trigger that makes a DPIA mandatory under Art 34 and materially raises the consent/lawful-basis bar. Blood type arrives incidentally — it is on the KTP and we extract it because it is there, not because any flow needs it. That is the cheapest finding to fix on this whole list: stop extracting it.

Finding 1 — the audit store cannot forget (blocking)

ai_call_bodies is append-only and hash-chained: internal/store/bodies_test.go asserts that both UPDATE and DELETE fail. That immutability is a deliberate, valuable control for an audit trail. The problem is what is inside it.

Proven, not assumed. I decompressed the 5 newest engine=ahu-ocr, operation=chat request bodies (content never printed, sample securely deleted):

Decompressed size of 5 bodies 10.8 MB
16-digit sequences (NIK shape) 38
Occurrences of nik 283
Person markers (Tuan/Nyonya/lahir di) 156
Such bodies in the store 2,466 (ahu-ocr / chat)
Whole store 20,858 rows · 278 MB compressed · from 2026-07-04

The leak path is the LLM extraction prompt: it inlines the full OCR'd document text. By contrast operation=ocr and operation=classify bodies are ~400 bytes and clean — the binary-stubbing work is doing its job. So this is one specific path, not a general failure.

Why it matters: a data subject exercising Art 8 (erasure) or Art 9 (end processing) cannot be fully served. The OCR app can delete its own records — delete routes exist across all flows — but the audit copy survives, by design, forever.

Recommended fix — crypto-shredding. Encrypt PII-bearing bodies with a per-document key held in a separate, deletable keystore. Erasure then destroys the key, not the row: the hash chain stays verifiable over ciphertext, immutability is preserved, and the plaintext is unrecoverable. This is the standard resolution to the immutability-vs-erasure conflict and it keeps both properties instead of trading one away.

Cheaper alternatives, in case that is too much: (b) truncate body plaintext after N days, retaining full_body_sha256 so the chain still verifies; (c) stop storing request bodies for operation=chat on document engines and keep only the hash. (c) is nearly free and would have prevented this entirely — the audit value of the content of an extraction prompt is low compared to its liability.

Finding 2 — retention is declared but not implemented

RetentionDays (default 400) appears in internal/config/config.go:30,581 and deploy/observatory.example.yaml. It appears nowhere else — no deletion code, no TimescaleDB add_retention_policy, no drop_chunks. Nothing expires.

Art 43(2) requires erasure once the retention period ends or the purpose is fulfilled. A knob that reads as compliant while doing nothing is worse than no knob: it will be cited as evidence of a control that does not exist. Either implement it or delete the setting.

Nothing has aged out yet (oldest row 2026-07-04), so there is no backlog to clear — implement it now and the problem never accrues.

Finding 3 — no DPIA, and one is mandatory

Art 34 requires a DPIA for high-risk processing. This deployment hits at least three of the enumerated triggers: specific personal data (health, financial), automated decision-making with legal effect — an OCR+LLM pipeline that classifies and extracts filings for a government registry — and large-scale processing.

Art 10 also gives data subjects the right to object to decisions based solely on automated processing. Relevant here in a concrete way: we now know the classifier mislabels documents at ~13% on distinct deeds while reporting 0.96+ confidence, and that confidence scores are inverted (see the OCR quality findings). A DPIA is the correct place to record that a human verification step is load-bearing rather than decorative.

Finding 4 — no governance artifacts exist

A repo-wide search for PDP/DPIA/retention/privacy-notice material returns only commercial proposals that mention PDP. There is no:

  • Record of processing activities (Art 31) — what data, what basis, what retention, who receives it
  • Privacy notice / lawful basis per flow (Art 20) — notaris upload documents containing third parties' data; the basis for processing those third parties needs to be stated
  • DPO appointment (Art 53) — required for public-service processing, large-scale processing, or specific data. This deployment is all three
  • Breach-notification runbook (Art 46) — 3×24 hours to the agency and affected subjects. Detection improved a lot today (EWS → Keep now pages a real mailbox) but detection is not notification: there is no documented decision path, no template, no named owner
  • Processor agreement (Art 51) between Ditjen AHU as controller and us as processor

These are documents, not engineering. They are also the first thing an audit asks for, and the cheapest items on this list.

Finding 5 — real personal data in staging

Staging holds 177 real KTP records and real notarial deeds, and the ahu-ai-ocr-web container has SECURITY_DEV_LOGIN_SECRET configured. The dev-login path is secret-protected, so this is attack-surface reduction rather than an open hole — but a development authentication path in an environment holding real citizens' identity documents is the kind of finding that reads very badly in an audit. Either use synthetic/redacted fixtures in staging, or drop the dev-login secret from that environment.

What is already right (do not lose these)

Credit where due — several of these are things most projects get wrong:

  • No cross-border transfer. OCR_PROVIDER=azure-on-prem, vLLM on-prem, gateway enforces on_prem upstreams and production refuses external_dev. Art 56 is the requirement that most commonly blocks government AI, and it is designed out rather than mitigated.
  • Authorization is enforced. SECURITY_ENFORCE=on in the running staging container — I expected this to be off and it was not.
  • Body access is itself audited. body_access_audit records reads of stored bodies — exactly the control Art 35 expects around sensitive stores.
  • Erasure is possible app-side. Delete routes exist across all 13 flow route modules.
  • Audit integrity. Hash-chained, tamper-evident, with verified rejection of UPDATE/DELETE.
  • Secrets discipline. No credentials in repos; env-var indirection throughout.

Remediation order

Priority Action Type Effort
1 Stop storing operation=chat request bodies for document engines (or crypto-shred them) eng S–M
2 Implement retention_days, or remove the knob eng S
3 Stop extracting golongan_darah — nothing needs it eng XS
4 Breach-notification runbook (3×24h, named owner, template) doc S
5 RoPA + privacy notice + lawful basis per flow doc M
6 DPIA covering specific data + automated decisioning doc M
7 Appoint/designate a DPO org S
8 Synthetic data in staging; drop dev-login there eng S
9 Processor agreement with Ditjen AHU legal M

Items 1–3 are days of engineering. Items 4–7 are documents that can be drafted in parallel and are what an audit actually asks to see first.

Exposure if ignored

Administrative sanctions (Art 57): written warning, temporary suspension of processing, an order to delete personal data, and fines up to 2% of annual revenue. A suspension order against the processing itself would stop the platform. Criminal provisions (Arts 67–68) reach individuals for unlawful obtaining, disclosure or use, with corporate fines multiplied under Art 70(2).

The realistic risk is not a raid. It is a procurement questionnaire — for the KLH deal or the next AHU contract — asking for the DPIA, the RoPA, and the retention policy, none of which exist today.

What I did not assess

Encryption at rest for the uploads volume and the databases; TLS posture between services; the chatbot's conversation-retention behaviour; whether notaris consent language upstream in SABH covers our processing; and the six non-GPU hosts. Each of those could add findings.