I have a pipeline that turns a folder of markdown into a book. Chapters in one end; an epub and a print-ready PDF out the other, rebuilt from scratch every time I change a word. It has produced two books that people have paid money for. It works.
Last week I went and read it, properly, the way you read code you are about to make a claim about. And the print branch decides a book is finished like this:
if (!is_file($pdfPath) || filesize($pdfPath) === 0) {
// ... bail out
}
That is the check. Not the first of several — the whole thing. A print edition of a book I sell is considered built when a file exists at the expected path and contains more than zero bytes. Everything downstream of that line — the database row that says built, the byte count, the download token, the message that says the job completed — is derived from a file existing and not being empty.
I want to be precise about what that proves, because it does prove something. It proves Chromium ran and wrote bytes to disk. That is a real fact and it is worth knowing. It is also, as a statement about whether the book is correct, approximately nothing. A PDF of the right length with every chapter in the wrong typeface passes. A PDF with a table of contents pointing at the wrong pages passes. A PDF that is one enormous blank page passes, provided the blank page is large enough to weigh something.
The part that makes it worth writing about
If the story were "the print output never got validated," there would be no post. Everybody has a gap; a confession is not an insight. The reason I am writing it up is that the discipline isn't missing. It is present, in the same file, on the other branch, and it is annotated.
The epub path has a real gate. After the build reports success, the pipeline shells out to EPUBCheck — the actual specification validator — parses the result, and records whether the file is genuinely clean. If it isn't, I know. And the gate carries a comment:
/* ---- EPUBCheck gate (epub only) — never trust the queue's "complete" ---- */
Never trust the queue's complete. That is the whole principle, stated in the imperative, three lines above the branch that doesn't do it. Somebody working on this thing had the idea clearly enough to write it down for whoever read the file next — and then scoped it to one of the two artefacts the pipeline makes.
I can't tell you who wrote it, and I want to be honest about why: this project has no version control. There is no blame view, no commit, no record of who typed that line or when. It is one of the two projects I run that never got a repository, for the ordinary reason such things happen — it started small and fast and the ceremony felt like ceremony, and then it never got added. So the comment sits there, correct, unattributed, and impossible to trace. I have written elsewhere about what a missing repository costs you on the day it costs you something. This is the quieter version of the same bill: I own a machine that told me the right thing and I cannot find out who put it there.
The commands are adjacent. You ask for epub, or pdf, or both. Choose both and one invocation produces two artefacts, and the pipeline validates exactly one of them and tells you, in the same cheerful tone, that both are done. There is even a --no-validate flag, documented as skip the EPUBCheck gate after an epub build — a flag to turn off a check that only ever ran on one side, which I had apparently never noticed was a strange thing to need.
Why the gate went where it went
So why did the discipline land on the epub and not the print PDF? Not because I decided the print branch mattered less. I never made that decision at all, which is the point — nobody sits down and chooses to ship a book blind.
It went there because EPUBCheck exists.
That is the entire explanation. There is a free, canonical, one-command validator for the epub format, so wiring it in was an afternoon and an obvious thing to do. There is no equivalent for "is this print PDF the book I meant." No single tool you can shell out to that reads a PDF and tells you the typeface is the one you asked for, the margins are what you specified, and the table of contents points where it promises. Building that is real work with no obvious finish line. So the check went where the tool was, and the branch with no convenient tool got no check, and the absence never announced itself because absences don't.
Which is the thing I actually want you to take away, because it is not about books and it is not about me. Your verification pools where the tooling is cheap, not where the risk is. Not through any decision you could point to and defend. It accretes, one reasonable afternoon at a time, and the result is a system whose safety net is shaped like the tools that happened to be lying around rather than like the things that could actually hurt you.
And look at the shape it produced here. The epub is the cheap artefact — if it's wrong I fix it and re-upload and the cost is my embarrassment. The print PDF is the expensive one. It goes to a printer. It becomes physical objects with a unit cost, in boxes, that a stranger opens. It is the only output in my pipeline where being wrong costs real money and cannot be quietly amended at four in the morning. The gate is on the cheap one. The risk is on the other one. Nothing about that is defensible and nothing about it was chosen.
What "we have validation" is worth as a sentence
Here is what unsettled me most, once I'd read it. Before last week, if you had asked me whether my book pipeline validates its output, I would have said yes, without hedging, and I would have been telling the truth. There is a validator. It runs automatically. It is strict, it parses the real result rather than the exit code, and it records what it found. Every word of that is accurate.
It was also a claim I had not examined in a year, and the examination is the whole content of it. "We validate the output" turns out to mean almost nothing until you say which output. It is the same shape as "we have backups" — a sentence that sounds like a guarantee and is actually a question. Which output. How far back. What does it do when it fails. A backup that overwrites itself every night is a real safety net for one day; a validation gate on one of two branches is real safety for one of two artefacts. The existence of the thing tells you almost nothing. Its shape tells you everything, and I had never once asked mine what shape it was.
What makes that sort of claim durable is that nothing contradicts it. The pipeline doesn't lie. It never said the PDF was validated — I inferred that, from the fact that validation existed and lived in the same file, and then I stopped thinking about it and repeated the inference back to myself for a year as though it were something I had checked. There was no failure to notice, no red light, no moment where the system had a chance to correct me. It just quietly built exactly what I told it to build and reported, accurately, that it had done so.
The bit I'm not going to pretend
The tidy ending here is that I found the gap and fixed it. I haven't. There is still no print validator, for the same reason there never was one: writing it is a genuine project, and I have not yet decided it is the most valuable thing I could build this month. That may be the wrong call. It is at least now a call, made with the facts, rather than an assumption I was carrying around and mistaking for a decision.
Because the fix that mattered was never the code. It was going and reading the file. Ten minutes, no cleverness, no tooling — just opening the thing I had been making confident public statements about and looking at what it actually said. Every expensive mistake I have written about on this site has the same cheap check sitting unused at the front of it, and this one is no different: the check that would have caught it cost nothing, and I didn't run it, because I already knew the answer.
I wrote a book called Don't Trust "Done". It argues, for thirteen chapters, that the report of a thing being finished and the thing being finished are different objects, and that the gap between them is where the interesting engineering lives. It was built by a pipeline that decides a print book is finished when the file isn't empty.
I'd like to tell you that's ironic. It isn't. It's just what it looks like from the inside — which is exactly what the book says, and exactly why I didn't see it.