My verifier was checking the font

Kenneth, sixteen, Oshkosh, Wisconsin — 2026-08-30

I built a checker to stop my AI agent quoting things that were not in the source

document. It worked. Then it rejected a quotation that was perfectly correct,

properly sourced, and present in the document word for word.

The model had written 15‑minute demand. The document said 15-minute demand.

Those are different characters. The first is U+2011, a non-breaking hyphen. The

second is U+002D, the one on your keyboard. They render almost identically, they

mean exactly the same thing, and a substring comparison treats them as unrelated.

My verifier was not checking the claim. It was checking the font.


Why this is worse than it sounds

A verifier that produces false rejections does not merely annoy you. **It trains

you to ignore it.**

The first time it fires wrongly you investigate. The third time, you glance. By the

tenth you have learned that it cries wolf, and you dismiss the one that matters —

at which point you have kept all the confidence of having a verifier and lost the

verification.

A checker that flags the truth is more expensive than no checker at all, because

no checker at least leaves you appropriately nervous.


The characters that actually do this

Every one of these appears in extracted PDFs and in model output. Several are

visually indistinguishable at normal reading size.

Looks likeActually isWhere it comes from
-U+002D hyphen-minusYour keyboard
U+2011 non-breaking hyphenTypesetting, and models imitating it
U+2010 hyphenUnicode's "correct" hyphen, rarely typed
U+2013 en dashRanges — 10–20
U+2014 em dashParenthetical asides
U+2212 minus signMathematics, and some PDF extractors
' 'U+2018/2019Smart quotes, applied automatically
" "U+201C/201DSame
­U+00AD soft hyphenInvisible. Justified text, survives extraction
U+200B zero-width spaceInvisible. Web copy-paste
U+00A0 non-breaking spaceLooks exactly like a space
U+FB01/FB02 ligaturesPDF fonts, silently substituted

Four of those are invisible or indistinguishable. You cannot debug this by

looking at the string.


The rule that resolves it

Normalise typography. Never relax wording.

Those sound similar and they are opposites.

Normalising typography means every dash above becomes - before comparison,

every smart quote becomes ', the invisible characters are stripped, ligatures are

expanded. 15‑minute and 15-minute become the same string, because they were

always the same claim.

Relaxing wording means fuzzy matching, edit distance, stemming, embeddings —

anything that lets nearly the same text pass. And that destroys the whole

instrument, because in the documents I work with:

```

"the monthly demand charge of $8.540 per kW"

"the monthly demand charge of $9.540 per kW"

```

is an edit distance of one, a cosine similarity of about 0.999, and **a completely

different fact.** Any system loose enough to forgive a stray hyphen is loose enough

to forgive that digit, and the second failure is the one that reaches a client.

So: **$8.540 and $9.540 stay different forever. 15‑minute and 15-minute

become identical.** The line between those two is the entire design.


What is not typography, and must never be normalised

Filed documents contain characters that carry meaning and look like noise. A real

electricity tariff reads:

"the monthly demand charge of $8.540 per kW R will be reduced by $0.05266…"

That stray R is a revision marker flagging a changed line. It sits mid-sentence.

A quotation typed from memory omits it, and the checker refuses — correctly.

It is tempting to strip it. Do not. You cannot distinguish a revision marker from a

letter that belongs to the sentence without understanding the document, and a

checker that starts understanding documents is a checker that starts being wrong in

new ways.

**The correct answer is to work in the other direction: search the document, and

let it tell you its own wording.** Then quote that.


The part where I did it again

Last week my agent drafted a message to a prospect offering *"a cloud-based

development environment."* We do not have one. Everything runs on one laptop.

So I wrote a second checker — this one verifying claims about us rather than

about documents. I shipped it. It missed the cloud claim entirely, because the

agent had written cloud‑based with a non-breaking hyphen.

**Same character. Same failure. One hour after I had cited the original fix in a

commit message.**

I had a normaliser. I had written it, tested it, and published it. I simply did not

reach for it in the new place, because the new checker felt like a different kind of

problem and it was not.

Then I fixed that, and the checker blocked an honest sentence — a draft that said

the agent had "claimed a cloud environment, which it does not have." Every word

true. It flagged the truth, which is the failure at the top of this piece, arriving

from the opposite direction inside the same hour.

Three wrong versions of one checker in sixty minutes. They are all in the git

history with timestamps.


What I would tell you to do

1 · Normalise before every comparison, in every place you compare. Not in the

library — at every call site, or better, make the comparison impossible to perform

without it. I knew the rule and still wrote a new comparison without it, because

knowing a rule is not the same as being unable to break it.

2 · Test both directions and weight them differently. A missed lie costs you a

client. A flagged truth costs you the checker itself, which costs you every

future lie. My tests now assert both, and the second set is longer.

3 · Never let a normaliser touch meaning. Dashes, quotes, invisible characters,

ligatures, whitespace. Nothing else. The moment it stems a word or drops a

punctuation mark that could be structural, you have built a fuzzy matcher with a

verifier's reputation.


The library is quoted. The normaliser is fifteen lines — the whole module is

sixty-six with the comments explaining why each substitution is there. It is the

least clever code in the project, which is why it is the part that keeps saving me.

(I first wrote "about forty lines" in this paragraph, then counted. In a piece

about unchecked comparisons that seemed worth mentioning rather than quietly

fixing.)

**If you have built one of these and normalise something I do not, I would like to

know what and why.** My list came from failures rather than from knowing the field,

and that is a poor way to arrive at a specification.

— Kenneth