Engineering Journal
Pdf Processor
Pdf Processor

The guard that tested my assumption instead of the code

2026-07-04

TLDR: I wrote a regression guard asserting that a single-column financial document should produce zero paragraph-continuation links. The first run produced thirteen, the guard screamed, and half of those links turned out to be correct behavior I had not predicted. The lesson: a guard encodes your model of the system, and when it fires you have to debug the model with the same rigor as the code.

The assumption that seemed reasonable

I was building paragraph-continuation detection for a document extractor: find the seams where a paragraph breaks at a column boundary and link the fragments.

The mental model was clean. Continuations happen where columns meet. A single-column document has no column seams. Therefore a single-column document must produce zero links, and any link on it is a false positive.

That became a test expectation: expectLinks: false on the single-column corpus document. It looked like the safest guard in the whole suite.

When it failed

First corpus run: thirteen links on the single-column document. Guard broken, big red X.

I started reading the links expecting garbage. Some were garbage: numeric rows from a missed table chained into prose, contact-page email addresses joined across blocks.

But then: "today announced financial results ⟶ for its fourth quarter ended December 31." That is one sentence. The extractor had split it into two regions because a structural boundary intervened, and the linker had correctly rejoined it.

What was actually wrong

My seam model was richer than my guard's model. The implementation created seams in two places: between adjacent columns, and between adjacent vertical zones of the page.

Zone seams exist on single-column pages. A paragraph interrupted by a full-width structural element, or split by a zone boundary the classifier drew, is still one paragraph. The linker was doing exactly what the design doc said it should. I had written the design doc. The guard still encoded the simpler model I had started with.

So the failure decomposed into two unrelated populations: correct links my assumption said were impossible, and genuinely bad links from table debris. One binary counter had flattened them into a single failure.

What got deleted

The expectLinks: false assertion. It was unfixable as written because the correct number of links on that document is not zero and not any other constant. It depends on how many zone boundaries happen to cut through sentences.

What replaced it

A property, not a count. The guard now asserts that no link has a numeric-debris side: fragments where digits rival letters, rule lines, axis ticks. That property holds regardless of how many legitimate links exist.

The debris check also became a blocker inside the linker itself, which killed the actual false positives. The final run shows the same document with eight links, all prose continuations, zero debris.

The generalizable lesson

A test that asserts a count asserts your model of the system. When the system is heuristic, counts are the weakest possible property, because both the code and your model produce numbers and the test cannot tell you which one is wrong.

Assert invariants instead. "No link touches a numeric fragment" survives every legitimate change in link volume. "Zero links on this file" dies the first time the system is smarter than you were when you wrote it.

The deeper habit: when a guard fires, split the failures into populations before fixing anything. If any population looks correct, the guard is a suspect equal to the code. I nearly "fixed" correct sentence-rejoining because the test called it a regression, and that would have been the real regression.

Read this post in the full Engineering Journal →