There’s a phase of a project that nobody writes blog posts about, because nothing you build during it is visible to a user. ChoreMojo — the multi-tenant SaaS fork of Tally, the kids’ chore app — has had all its features for a while. Parents sign up, kids earn allowance, jars fill up, penalties apply. What it hasn’t had is the boring scaffolding that lets it survive contact with a real server: a way to verify an email, a way to reset a forgotten password, a container to run in, a pipeline to prove it still works. Today was two full plans of exactly that unglamorous work — Plan 5a and the first track of 5b — and it ended with the repo at 626 passing tests, three green gates, and a Dockerfile that boots against an empty database and migrates itself into shape.
But the moment I actually want to write down is the one where I almost shipped a regression and the only thing that caught it was arithmetic.
A number that went the wrong way
I run this kind of work as a fan-out: an implementation plan gets carved into a dozen small TDD tasks, and each task goes to a subagent that writes the failing test, makes it pass, and reports back. It’s fast and it parallelizes well, and it has one specific failure mode that I’ve now been bitten by enough times to watch for.
Task 5 of the readiness plan was “add a rate limit to password-reset token issuance.” The subagent did that. It wrote the rate-limit test, wired up the limiter, went green, came back clean. On its own terms, it succeeded completely.
The problem is how it added the test. The file password-reset.test.ts already existed — it already held five domain tests covering requestPasswordReset and resetPassword from an earlier task. The subagent needed to add a sixth test to that file. Instead of editing the file to append, it used Write, which doesn’t append — it replaces. So the file came out the other side holding exactly one test: the new rate-limit one. The five that were already there were gone, cleanly, with no error, because overwriting a file is a completely legal thing to do and nothing about it looks like a mistake.
Here’s what saved it: the total suite count dropped from 622 to 618. Not up by one, as a new test should push it — down by four, net. Five tests deleted, one added. If I hadn’t been watching the aggregate number across tasks, this would have sailed straight into the tag. Every individual gate was green. The build passed. The new feature worked. The only tell was that the population of tests had shrunk while I was supposedly only adding to it, and shrinking test suites are the kind of thing that should never happen silently during a feature plan.
The fix (911542b) restored the five domain tests alongside the new rate-limit one, sharing the setup helper, and the suite came back at 623. But the fix isn’t the interesting part. The interesting part is that “the tests still pass” is a weaker claim than it sounds, because it only speaks to the tests that currently exist — and the set of tests that exist is itself mutable, and can quietly get smaller. A green checkmark tells you the survivors passed. It says nothing about who didn’t survive. The health of a test suite isn’t just its pass rate; it’s also its census, and the census is the number I now refuse to stop tracking.
Guards for conditions that haven’t happened yet
The rest of the day had a through-line I didn’t plan but noticed on the way out: almost everything I built was a guard against a state the app has never actually been in.
ChoreMojo has never had two copies of itself boot at the same moment — but on Cloud Run it will, because that’s what horizontal scaling is, and both copies will try to run database migrations against the same Postgres on startup. So runMigrations now grabs a Postgres advisory lock inside a single transaction (8692957) — the second booter blocks until the first finishes, sees the migrations already applied, and moves on. There is no way to test this against a bug that can’t occur locally, so I wrote a concurrency test that spins up parallel migration calls and asserts they serialize. Building the guardrail and the proof that the guardrail holds, for a race that has literally never fired.
Same shape with email. The app has never sent a real email — the whole email layer is a seam that logs to the console unless a RESEND_API_KEY is present. The danger isn’t sending email; it’s sending real email that points at localhost, because a verification link to http://localhost:5173 in a stranger’s inbox is worse than no email at all. So there’s now a validateRuntimeConfig that throws at boot (acf7fde) if you’ve configured a live email key while your public origin still points at a dev address. It’s a fuse that only ever blows during a misconfigured deploy — a deploy that hasn’t happened. Expired auth tokens get garbage-collected; token issuance is rate-limited to three per fifteen minutes; password reset returns the same neutral response whether or not the account exists, so it can’t be used to enumerate who’s registered. All of it defending against traffic the app has never seen.
That’s the texture of production-readiness work. You spend feature development reacting to things that break in front of you. You spend readiness work imagining things that will break in front of someone else, later, when you’re not watching — and closing them now, in the quiet, while you still have the whole picture in your head.
The lockfile I couldn’t regenerate
One honest scar to close on. The Dockerfile (eb11fd7) and the CI pipeline both install dependencies with npm install rather than the stricter npm ci, and that bugs me, because npm ci is the correct choice — it installs exactly the locked versions and fails if the lockfile is stale. The reason I couldn’t use it is embarrassingly physical: I’m developing on Windows, the container builds run through a podman machine, and the podman machine can’t bind-mount the Windows project path in a way that lets me regenerate the lockfile as a Linux artifact. The committed lockfile is a Windows-resolved tree; a Linux runner doing npm ci against it can trip on platform-specific resolution. So npm install it is, for now — with a note in the plan that restoring npm ci waits for Track B, the live cutover, where the build finally runs on real Linux and the lockfile can be born there. I don’t love shipping a “for now.” But I’d rather write down exactly why the shortcut exists than let a future me discover the npm install and assume it was a preference instead of a constraint.
Both plans tagged clean — choremojo-production-readiness-v0 and choremojo-deploy-artifacts-v0 — and the container smoke test did the thing I most wanted to see: empty database in, HTTP 200 out, twenty migrations applied on first boot. The app has never been deployed. But for the first time, it’s shaped like something that could be. Next comes the part where the guards I wrote today meet the conditions they were written for. That’s Track B, and that’s a different kind of nervous.
