Skip to main content

Preview with the gate's own code

16 October 2026 · Testing & verification · the engineering team

The story

Our publisher refuses a document for any of about fifteen reasons, and an author who wants a page to go public needs to know which of those reasons stand in their way before they ask. So we gave them a preview: a command that reads a document, runs the checks and prints what would block it, section by section. It read "zero means the public build would pass" in its own help text.

That sentence was not true, and it took an architecture review to notice. The preview called the checking pipeline, but the build had grown two more stages after that pipeline: a check over summary cards that are derived from the document, and a last pass over the finished page data that catches anything added after the main checks ran. The preview knew about neither. An author could see a clean preview, ask for publication, and watch the build refuse on a card title the preview had never looked at.

Nobody had made a mistake in the usual sense. The preview was correct on the day it was written. The gate had moved on and the preview had not, because they were two pieces of code that happened to agree, and nothing held them together.

The pattern

The preview calls the gate. We replaced the two call sites with one function. The build calls it to decide what to write. The preview calls it to decide what to print. The test that guards the output calls it to re-check the files on disk. There is now exactly one definition of "would this publish", and a preview that says yes is the build saying yes, because it is the same code path with the same inputs.

Prove the equivalence with a test, not a comment. We wrote a test that walks every fixture document we have, asks the preview for its verdict one document at a time, asks the build for its verdict over the whole set, and requires them to agree, document by document. The test is cheap and it will fail the day someone adds a stage to one path and not the other. That is the point of it.

Make the preview strict in the same direction. When the two paths cannot be identical, the preview must err toward refusing. Ours sees one document at a time, so a link from one public document to another resolves in the build and not in the preview. The preview reports a refusal the build would not. That is the acceptable mismatch: an author rewrites one link they did not need to, instead of trusting a preview that was quieter than the gate.

Give the preview the gate's inputs, not copies of them. The list of forbidden identifier patterns, the deny-list of terms, the allowed hosts for external links: each of these used to be assembled by hand in three places. Each place was right when written. We moved the assembly into one helper and had every caller use it. A preview that builds its own copy of the rules is a preview that will one day run with last month's rules.

The shape generalises beyond publishing. Anywhere there is a gate and a "would this pass" preview of it, the two will drift unless the preview is the gate. Dry runs, lint previews, deployment plans, cost estimates: the same thing happens. The fix is never a more careful preview. It is to delete the preview's logic and call the real thing with a flag that says "do not write".

Try it

Find a preview, dry run or "check" command in your system that promises to predict what a later gate will do. Read both. If they share no code, write the test first: for each input you already have fixtures for, run both and assert they agree. Then make the preview call the gate's function. Keep the test; it is what stops the next person from adding a stage to only one of them.

testingcipublishingpreviews