Five layers of a retrieval pipeline report success. The legal answer they produce is confident, checkable, and wrong.
FD Iskandar · AI integration & automation engineering
This is a worked audit of a single question against a real government legal corpus. The pipeline described is not a strawman: it applies a status filter, does vector retrieval, quotes verbatim, and cites its source. Every stage returns success. The answer it produces cites a real article, of a real regulation, that is genuinely in force. The article it quotes was deleted a year ago.
Every claim below is reproducible with the commands in the appendix, against documents anyone can download. The specific corpus is Indonesian wage regulation. The failure modes are structural and corpus-independent.
Twenty minutes into gathering documents for this paper, I ran a regular expression
over a downloaded PDF to extract which regulation it was. The script reported
UU 25/1992. It said so with no hedging, because regular expressions do not
hedge.
The document was PP 7/2021. What my pattern had matched was a regulation
cited inside the considerations clause: the preamble where a regulation lists the
older laws it derives authority from. Every legal document in this corpus opens by naming
other legal documents. An extractor that looks for a citation-shaped string near the top
of the file finds one immediately, and it is usually the wrong one.
I mention this because it is the entire paper in miniature. The failure was not random. It was not a hallucination. It was a correct program, doing exactly what it was told, over a document whose structure made the correct behaviour wrong. No amount of testing on well-formed inputs would have caught it, because the input was well-formed. It just did not mean what the program assumed it meant.
The dangerous failures in retrieval systems are not the ones that look like errors. They are the ones that look like answers.
If you were choosing a domain to demonstrate that retrieval-augmented generation works, you would choose this one. Legal corpora are the friendliest data a retrieval system will ever see. The documents are authoritative; there is exactly one official text. They are stable; a regulation does not change between Tuesday and Wednesday. They are structured, numbered down to the clause, and they are public, published by the government at permanent URLs.
Better still, the metadata appears to solve the hardest problem for you. The government's legal database prints a status on every document: in force or no longer in force. Filter on it and staleness is handled.
The question I will use throughout is deliberately narrow and factual: how is the annual minimum wage adjustment calculated? It has a single correct answer, written as a formula, in one article of one regulation. Payroll providers, employers of record and compliance tools all need it. It is the easy case.
The regulation governing wages is PP 36/2021. Its database entry says
Berlaku, meaning in force. That is true.
The same page records that it has been amended twice, by PP 51/2023 and
PP 49/2025. Both of those are also in force. Nothing is contradictory and
nothing is out of date.
But notice what the status field actually tells you. It tells you the document has not been revoked. It does not tell you whether the article you are about to quote is still the operative text. A regulation is in force as a whole while individual articles inside it are replaced, renumbered, or deleted by later instruments.
Status is a property of documents. Force is a property of articles. No status column in any legal database answers the question a retrieval system is actually asking.
There is a second, subtler problem. On a different regulation in the same database, I have seen this column state that an instrument was in force after it had already been revoked. Here it is correct. That inconsistency is worse than being reliably wrong: spot checks find the column working, conclude it is trustworthy, and the one case that matters slips through. A field that is right most of the time trains you to stop checking it.
The pipeline downloads PP 36/2021 and extracts its text. Extraction
succeeds and returns 3,045 characters. The stage logs success and passes the document on.
The document is 75 pages long. Text exists on two of them.
| Document | Pages | Pages with text | Coverage |
|---|---|---|---|
| PP 36/2021 (the parent, in force) | 75 | 2 | 3% |
| PP 51/2023 (first amendment) | 27 | 27 | 100% |
| PP 49/2025 (second amendment) | 22 | 22 | 100% |
| PP 78/2015 (revoked predecessor) | 49 | 49 | 100% |
Pages 2 through 56 hold the articles, the entire substance of the law. Every one of them is a scanned image. So are the annexes. The two pages that carry text are page 1 and page 57: the cover, and the signature page.
What lands in the index is a document containing no law at all. And the most prominent text in it is the preamble, which names three other regulations. The failure from section 01 is not hypothetical; it is sitting in the index, attached to the single most important document in the corpus.
This does not fail loudly. It returns 3,045 characters, so every check of the form "did extraction succeed?" answers yes. A shallow probe for an embedded text layer also answers yes, because pages 1 and 57 carry the seven fonts the file reports. Both signals are true. Both are useless.
A coverage check would have caught this instantly: 75 pages in, 2 pages of text out. Almost nobody writes that check, because it only matters for documents that are already broken, and you do not know which those are until you write the check.
The vector search now runs over the chain. Three of the four documents are fully
readable; the parent is effectively empty. Retrieval returns its best match:
PP 51/2023, Pasal 26A, which contains the wage adjustment formula, complete
with its coefficient and bounds. It is a genuinely excellent match for the query.
It ranked first because it was the only document in the chain that could be read, not because it was the most current. Embedding similarity has no concept of legal recency. A deleted article and an operative one are equally retrievable if both are in the index, and the deleted one wins whenever the operative text is a photograph.
PP 49/2025, item 8, in its entirety:
8. Pasal 26A dihapus.
Three words. Article 26A is deleted. Not amended, not superseded by a revised version. Removed. The article the pipeline is about to quote does not exist in current law.
Six articles in this chain were amended twice: articles 5, 26A, 28, 34, 34A and 35. A system that walks one hop from the parent to the first amendment has done more work than most retrieval pipelines ever do, and is still wrong for every one of them. Chain traversal has to run to a fixed point, stopping only when nothing amends the amender. Anything short of that is theatre.
The remaining documents do have text, but that text was produced by optical character recognition, and the errors are systematic rather than random.
| In the text layer | Actually |
|---|---|
| Pasal 268 | Pasal 26B |
| Pasal 7l | Pasal 71 |
| ayat (21 | ayat (2) |
In one document, Pasal 7l and Pasal 71 each appear twice.
That is one article, split into two entities, with no signal that anything went wrong. Any
exact-match join on article number silently loses half its rows.
It reaches the answer too. The wage formula, as it exists in the extracted text:
Nilai Penyesuaian UMlt+l) : PE x o x UMttt
The Greek letter alpha has become the letter o. The parentheses have become digits. Quoted verbatim, which is exactly what a well-behaved citation-preserving system does, the formula is not merely imprecise. It is not mathematics.
Assemble the run. One question, five stages, five successes:
| Stage | Reports | Actually |
|---|---|---|
| Status filter | pass | True and useless; status is document-level |
| Text extraction | pass | 3,045 chars from 75 pages; zero articles |
| Vector retrieval | pass | Ranked the only readable document, not the current one |
| Currency check | pass | The cited article was deleted in 2025 |
| Quote fidelity | pass | OCR destroyed the formula being quoted |
The output is an answer that cites a real article, of a real regulation, that is genuinely in force, quoted verbatim from the official PDF, with a working link to the government's own database. It will survive review by a competent engineer. It will survive review by a lawyer who checks that the regulation is current. It fails only against someone who reads the second amendment line by line.
No exception was raised. No confidence score dropped. There is nothing to alert on, because from the inside every component did its job correctly.
Nothing above involves the language model behaving badly. There is no hallucination here. Every fact in the wrong answer traces to a real document, and the model was faithful to the sources it was given. It was given a corrupted view of the corpus and reported it accurately.
Which means the usual remedies do not apply. A larger model does not fix it: it reads the same 3,045 characters. A lower temperature does not fix it; the failure is not stochastic, and the same wrong answer comes back every time. Better prompting does not fix it, because the model cannot ask for text that was never extracted. Citations do not fix it, because the citation is real and the link resolves. Human review does not fix it either, unless the reviewer independently walks the amendment chain, which is the work the system was bought to eliminate.
When a retrieval system is wrong about its sources, every downstream control you have is measuring the wrong thing.
These are ingestion and provenance defects. They have to be caught where documents enter the system, and enforced by something that can refuse.
Not a better model. A set of conditions that must hold before an answer is allowed out, each of which fails closed:
The fifth is the one that gets cut. Refusal looks like failure on every dashboard, scores badly against answer-rate metrics, and disappoints users in the short term. It is also the only control that works when the other four are uncertain, which is the normal condition of any real corpus.
What the demo shows is a system doing exactly that: declining to answer a question it cannot source, and showing its work: the chain it walked, the article it found deleted, the document it could not read. That is a more useful output than a formula, because it is actionable and it is true.
Everything above comes from public documents. No credentials, no scraping of anything behind a login, no private data.
# the amendment chain, walked from the live source curl -s "https://peraturan.bpk.go.id/Details/161909/pp-no-36-tahun-2021" \ | grep -oE 'href="/Details/[0-9]+/[a-z0-9-]+"' | sort -u # the parent regulation: 75 pages, text on 2 of them curl -sL -o pp36.pdf "https://peraturan.bpk.go.id/Download/154587" pdfinfo pp36.pdf | grep Pages pdftotext -layout pp36.pdf - | tr -d '[:space:]' | wc -c # page 5 has no text and one image; it is a photograph of a page pdftotext -f 5 -l 5 pp36.pdf - pdfimages -list -f 5 -l 5 pp36.pdf # one article, two tokens, because OCR read the digit 1 as the letter l curl -sL -o pp51.pdf "https://peraturan.bpk.go.id/Download/328142" pdftotext -layout pp51.pdf - | grep -c 'Pasal 7l' pdftotext -layout pp51.pdf - | grep -c 'Pasal 71' # the deletion curl -sL -o pp49.pdf "https://peraturan.bpk.go.id/Download/402111" pdftotext -layout pp49.pdf - | grep -A1 'Pasal 26A dihapus'
One detail worth noting while you run these. Every download above is served as
application/octet-stream, whatever it actually contains. In the same ID range
I found an identifier that returns a ZIP archive from a URL ending in .pdf.
A pipeline that trusts the content type learns nothing; a pipeline that trusts the file
extension feeds an archive to a PDF parser. Neither is told it was wrong.