Build it without a server · chapter 27 of 41 · release candidate v2.0.0-rc2 · 2,095 words · read and review

Shipped means the user has it

In August 2026 two consecutive releases of sgit.ai pushed cleanly, verified themselves against both remotes, printed "both remotes in sync, done", and never reached anybody. The site served a two-release-old page for forty minutes. A person on a phone looked at the version badge and asked: "is this the latest version?"

Everything about that sentence is worth sitting with. The checks were not weak. They were checking the wrong boundary. And for a founder the word at stake is the one the whole company turns on. Shipping does not mean a proof of concept, a demo, a video of a demo, or a prototype you walk somebody through on a call. It means "a thing somebody else can go and use, on their own, and get value from". It can be held together with tape; nobody using it cares how it was built. Getting a working thing into a stranger's hands is still, in 2026, genuinely hard, and solving that is not overhead. It is the first real product you build.

Green does not mean live

The release script did five things, and four of them were good. Build the site from source. Run the validator. Push the encrypted vault and confirm it is in sync. Push the git mirror and confirm the local head matches the remote. That last check is strong; it compares hashes, not hopes. The problem is what it means. It means the bytes arrived at the code host. It does not mean anybody can read them.

Publishing the site has three boundaries: source to remote, remote to deployment, deployment to reader. We were verifying the first two. Between the second and third sits a hosted build job with its own failure modes, and nothing in our release looked at it. The deploy job died before running a single step of its own, because the download of a third-party action it depends on was rate-limited: "429 (Too Many Requests)", three attempts, give up. It is the ordinary case: the most likely thing to break in a deploy is not your code. It is a piece of shared infrastructure you do not run, on a day you were not thinking about it.

The 2018 outline on CI testing had the line, borrowed from a talk on chaos engineering: "config changes can be more dangerous than code changes", because we do not test them the way we test code. The outline on test-driven development had a story next to it that I never wrote up, about a site that went down for hours because of a one-character change to a web server's configuration file.

The fix is one question, asked at the end. A release now finishes by asking the live site what version it is serving, polling with a cache-buster for up to eight minutes, because the origin's own answer is the only one that counts. If the version never appears it aborts loudly and names the build page. The cost is real: up to eight minutes on every release. We took it, because the alternative had already been demonstrated twice in one afternoon: telling somebody a fix was live when it was not, twice, in writing.

The site already had two rules of exactly this shape, and the third made the pattern obvious. A page nothing links to is unpublished; the build fails on an orphan. A page the machine index omits is unpublished; the build fails if a page would be missing from the index for agents. A page the deploy never served is unpublished; now the release fails on it. All three are the same claim, and it is the title of this chapter: shipped is a fact about the reader, not about your repository. Every one was learned by getting it wrong first, which is the only reason the list is short and specific rather than long and aspirational.

Files make releases cheap, and they also make it easy to ship something incomplete. When the newsroom moved to its own domain, 51 of the 527 figures in its articles did not come with it, and nothing failed in a way a server would have noticed, because the pages were files and they served. That is the cost of the speed Serverless, for real argues for, and it is why every release there ends by checking what is actually live.

Tests that never run are not tests

The 2018 outline on testing had two lines I still say. If you make a change and no test breaks, you are making random changes, and you have no way of knowing whether they are still in effect tomorrow. And: if you do not have full coverage, what are the bits of code not covered by tests, and what happens when they change? The more coverage you have, the more changes you are happy to make, and hundreds of small changes are the only way I know to build something good that lasts. Now that an agent writes the harness, there is no excuse for not having that.

In October 2026 I measured the newsroom against that standard, and it is a fair picture of explorer code that has just started taking money. The live reader code was about 2,700 lines. The tests that covered it were 433 lines, and the one workflow the repository had never ran them. The same application had two upstreams, with two diverging copies of the meter. No page set a content security policy. It is also exactly what has to change before the next wave, because a reader can now pay £5 by card. A mistake in the meter is a mistake with someone's money, and the day your company takes its first payment is the day this chapter stops being advice.

The line that moved is the one Explorer coding, not vibe coding, and the handover draws. Explorer code is read for intent, mostly by agents. Villager code is read line by line, because that is where changes land and where the blast radius lives. The newsroom's reader features had crossed from the first to the second, and the right move at that moment is to go slow to go fast. Go slow to go fast is the brief, in six phases. Map everything: every file, capability and control. Move the reader code to its own repository as a pure refactoring: the same behaviour and the same bugs, proved not by a reviewed diff but by hashing every file the live site serves before and after. Test: only test code is written, in real browsers, against a real pre-production copy, with every known bug written as a test that passes, recording the current behaviour, so the fix later is a visible change to a test and not a silent one. Refactor, with every test passing after every commit, and an adversarial exercise: try to change what a reader sees or pays without a test failing, and treat every change that slips through as a missing test. Only then does the explorer return.

The pipeline is the other half. The reader application is plain JavaScript and static files, so the target is under two minutes on every commit: lint, unit tests, three browser engines, and a build that produces the same bytes twice. On every merge, a pre-production copy with a payment link in test mode that can never charge anyone. On every release, the question from the first section, asked of the live site. Going slow now is what lets me keep going fast without the fear that each new feature breaks something a reader has paid for. The 2018 outline on DevOps had a joke I still like: "Congratulations, you now own/manage a Data Center". The 2026 version is gentler and the same: congratulations, you have a user; now everything you ship lands on them.

Run it, unmodified, against a twin

Most teams test against mocks they wrote themselves and hope production behaves the same. There is a better shape, and it follows from the twin in Serverless, for real: an interface to a real service that the application cannot tell from the real thing. If the application cannot tell the difference, the same unmodified code can run against an in-memory twin, in milliseconds, on a laptop; against a twin replaying last week's production journal; against a twin injecting the failures you never see in staging, a timeout, a throttle, a permission that disappears halfway through a job; and against the real service, for the final check. The environment becomes a setting rather than a guess, and that is a dramatic change in how much you can know before you ship.

I would go further, and make it a requirement for anything your company builds: it must be able to run, unmodified, against a twin of every external service it depends on, without knowing. Software that can only run against one provider's live API is software you cannot test properly, cannot move, and cannot put a policy in front of. That last clause is why the twin is also where the journal and the behaviour policy from Controlling your agent: RiskMandate live: every call passes through it. The 2018 outline on chaos engineering wanted to inject failure and watch; the twin is where you do it without touching production. The S3 twin exists as open source today; the rest is designed, not shipped, and I say so because this is exactly the kind of claim that should be checked.

Five people who do not exist

A green pipeline tells you the code does what the tests say. It does not tell you what a person sees. For that, the cheapest method I have found is synthetic users: five to ten invented people, played by a model, each driving a real browser through the site one screenshot at a time, saying what they see, think, ask and do, and then being interviewed about it. How to run synthetic users on your own site is the how-to, from three studies, and it takes an afternoon.

A synthetic user is a record, not a character: a situation, a question they arrive with, what they know and do not know, what makes them leave, their patience and their screen size. Two habits have paid off every time: include somebody who will never buy, because the sceptic reads the site adversarially and finds the claims that do not hold; and put at least one person on a phone. Write the expected journey before any run, so the difference between what you expected and what happened is a finding you can point at. Then the one rule of the run itself: give the agent the screenshot, the same thing a person would have, and not the page's code. An agent that reads the code finds every link every time, and so finds no confusion, which is the only thing worth running this for. Every person is invented; do not model real people, and say so where nobody can scroll past it.

On the newsroom, five invented readers took 62 steps, asked 51 questions the site did not answer, and produced 31 findings, six of them blocking. Four of the five left. Their ratings were 2, 2, 3, 3 and 2. The blocking findings were all about the step after reading: no contact for a pilot, nothing showing real money moving, a top-up page that says it is simulated while asking for money. Then came the step that separates a study you can stand behind from a pile of plausible paragraphs: check every surprising finding against the browser's own record. Two did not survive; they were caused by our own helper, and they are kept in the study, labelled as artefacts. One got bigger: a reader hit "screenshot unavailable" where the evidence should have been, and that is how the 51 missing figures were found.

It is not user research, and the study says so. The plan is simple, and it is the habit I would look for in anyone I was hiring to ship, and the one I would hold a founder to: every site gets five users before it is called done, and the same five again after the fixes, because a finding that disappears is a fix that worked, and a new one is something the fix broke. Then ask the live site what version it is serving, and only then say it shipped.