Forming the Spec | WhiteFish Creative

Forming the Spec

Spoke 1 of 6 in the spec-driven development series. Start with the pillar if you haven’t.

TL;DR. Forming a spec is the discipline of not starting. A request is not a requirement, it’s a pointer to forty decisions nobody has made yet, and your job is to drag each one into the open before a line of code exists. Write the problem before any solution. Write, separately, the list of things you are not building, with a reason on every line, because that’s the document that actually saves you. Write acceptance criteria a stranger could check, and a short list of things that must never happen. None of it is code. None of it needs a tool. The whole skill is the willingness to be uncomfortably specific, and to know which tasks don’t warrant it.

Most specifications fail at the first step, and the first step is not writing. It’s refusing to.

Someone hands you a request. “We need a login system.” “Build a way for admins to export their data.” “The onboarding needs to be smoother.” And the pressure in that moment, from them, from your own discomfort at sitting in ambiguity, from an agent that will cheerfully start generating the second you let it, the pressure is all pushing one direction: start. Open the file. Make the thing.

Forming a spec is the discipline of not starting. Of treating the request as the beginning of an interrogation rather than the end of one. Everything that goes wrong later, the drift, the ten-percent that never closes, the feature nobody asked for, most of it is a decision that could have been made in this conversation and wasn’t.

So let’s do this part properly, because it’s the part that pays for all the others.

The request is not the requirement

“We need a login system” is not a requirement. It’s a pointer to a place where requirements might live. Between that sentence and a specification there are perhaps forty decisions, and the person who said it has made almost none of them. Not because they’re careless, but because they didn’t know the decisions existed.

Here’s the move that separates this from taking notes. When someone tells you what they want, your job is not to write it down. Your job is to find the decisions hiding inside it and drag each one into the open.

“We need a login system” contains, at minimum: what identifies a user, what a password may be, what happens on the fifth wrong attempt, whether existing sessions survive a password change, what the system reveals when an email doesn’t exist, how it behaves when the database is unreachable, and what must never under any circumstances be written to a log. Every one of those is a decision. Every one has a right answer and several plausible wrong ones. And if you don’t extract them, they don’t go away. They get made later, silently, by whoever is typing, and you find out which answer they picked in production.

The junior developer standing by your desk asking “what happens if the email already exists” is doing the most valuable thing anyone does in this entire process. Forming a spec is being that junior developer to yourself, on purpose, before a line is written, and being far more annoying about it than any junior would dare.

Problem before solution, every time

The single most common defect in a spec is that it specifies a solution to a problem nobody stated.

Someone asks for a dashboard. You spec a dashboard. Six weeks later it turns out they wanted to know when a particular number went bad, and they’d have been better served by an alert, and the dashboard sits unopened because looking at it is a chore they wanted to stop doing. You built exactly what was asked. It was the wrong thing, and it was the wrong thing from the first sentence.

So before the spec, the brief. What is the problem? Who has it? What does it cost them? What happens if we do nothing? These questions feel like a detour when everyone can already “see” the solution, and they are the opposite of a detour. They are the only thing standing between you and efficiently building the wrong artefact.

The template for this is the Problem Brief, and the discipline it enforces is brutal: you may not describe any software in it. If you can’t state the problem without reaching for a feature, you don’t understand the problem yet, and the spec you’re about to write is a guess wearing a suit.

Half the time, and I mean this literally, it’s close to half, writing the problem down properly changes what gets built. Sometimes it reveals the thing isn’t worth building at all, which is the cheapest possible outcome and the one nobody thanks you for.

The list of things you are not building

Here is the artefact that will save your project, and it’s the one everybody skips because it feels like admin.

A specification says what you’re building. A good specification also says, explicitly and separately, what you are not building, and why. Not as an afterthought buried in section nine. As its own document, versioned, with a reason on every line.

This matters more than it sounds, and it matters for a specific mechanical reason. Scope doesn’t usually die in one big betrayal. It dies by forty small, individually reasonable additions. “While we’re in here, should we also…” Each one sounds fine. Each one is fine, in isolation. It’s only the aggregate that killed the timeline, and by the time the aggregate is visible, nobody can reconstruct how you got there, because no single addition was the culprit.

The not-building list is that reconstruction, written in advance. When someone says “should we also add social login,” you don’t have a debate about social login on a Tuesday when everyone’s tired. You point at the line that says social login, not building, because our users have work email and their employer already handles identity; zero requests for it in Q1. The decision was made once, deliberately, when everyone was thinking clearly. Now it just has to be looked up.

And when you’re working with an agent, this list is load-bearing in a way it never was with human teams. A capable, literal thing building a login form will build the login form it has seen ten thousand times, with the remember-me box and the password strength meter and the social buttons, because that’s what a login form looks like in its training. The not-building list is one of the few things that stands between its pattern-matching and your actual requirements. It’s not documentation. It’s a fence.

Acceptance criteria that can actually be checked

Once you know what you’re building and what you’re not, you write down how you’d know it worked. This is where most specs quietly give up.

An acceptance criteria is a statement that can be checked by someone who wasn’t in the room when it was written. That’s the whole bar, and most criteria fail it. “The login should be secure” is not a criteria. “The login should be fast” is not a criteria. “Errors should be handled gracefully” is not a criteria. They’re feelings with the word “should” in front of them, and they will be marked as met by whoever is holding the checklist, because they can’t be marked any other way.

The format that works is Given/When/Then, and it works because it forces you to be concrete about all three. Given a starting state. When an action. Then an observable result.

Not “the system should prevent brute force” but: given five failed login attempts against one account within fifteen minutes, when a sixth attempt is made with any password including the correct one, then it fails with the generic error and the lock persists for fifteen minutes from the fifth attempt.

The second one can be tested. More importantly, writing the second one forces you to answer questions the first one let you dodge. Five attempts or three? Per account or per IP? Does the correct password work during the lock, obviously not, but is that written down? Fifteen minutes from the first attempt or the fifth? You cannot write the precise version without making those decisions, which is exactly why the precise version is the point. The criteria isn’t valuable because it can be tested. It’s valuable because it couldn’t be written without deciding.

There’s a list of words that, when you see them in a criteria, mean it isn’t one yet: fast, secure, intuitive, robust, seamless, graceful, clean, simple, appropriate, properly. Each is a place where a decision should be and isn’t. When you find one, you haven’t written a criteria. You’ve marked a spot where the thinking still has to happen.

What must never happen

Most of a spec describes what the system should do. A small, critical part describes what it must never do, and that part is worth separating out and staring at, because prohibitions are the cheapest safety you will ever buy.

Never log the password. Never charge the card twice. Never let one tenant see another tenant’s data. Never return a different error for “wrong password” and “no such account,” because the difference between those two errors is a list of every account you have, handed to anyone who asks politely.

These aren’t features. Nobody requests them. They won’t come up in the conversation about what the system should do, because they’re about what it should refuse to do, and refusal isn’t a feature anyone imagines wanting until its absence has cost them. Writing them down is the difference between a system that fails safely and one that fails in a way that ends up in a newspaper.

And when you’re generating code rather than writing it, this section does specific work: it’s the set of things a literal, capable, unsupervised thing would never think to avoid, because avoiding them isn’t implied by any request. “Build me a login” does not contain “and don’t log the password.” You have to say it. It will not be inferred.

When not to do any of this

I’d be selling you something if I told you every task deserves this treatment. It doesn’t, and the discipline that makes spec-forming valuable is the same discipline that tells you when to skip it.

The heuristic: if you’d be annoyed at someone interpreting it differently than you meant, spec it. If you’d fix it in one follow-up prompt, don’t. Renaming a button doesn’t need a Given/When/ Then. A payment flow does. Most things are somewhere between, and the judgement about which is which is the actual skill, a skill that a rigid process applied to everything will destroy just as thoroughly as no process at all.

There’s a formal notation for requirements called EARS, a structured template for writing unambiguous requirement sentences, and it’s genuinely good for the things that need it: safety-critical logic, complex conditional behaviour, anything where an ambiguous sentence has a body count. For a CRUD form it’s overkill, and overkill applied indiscriminately is how a useful discipline curdles into ceremony, and ceremony is how you get back to waterfall by the scenic route. Know that EARS exists. Reach for it when the cost of ambiguity is high, and not before.

Where this leaves you

At the end of forming a spec you have: a problem stated without reference to any solution, a list of what you’re building, a separate and more important list of what you’re not, a set of acceptance criteria concrete enough to be checked by a stranger, and a short list of things that must never happen.

You’ll notice none of that is code, and none of it required a tool. It’s a handful of markdown files and several hours of being uncomfortably specific about things you’d rather wave your hand at. That discomfort is the work. The willingness to sit in it, to keep asking “but what happens when” until there’s nothing left to ask, is the entire skill, and it’s the one thing none of the tooling can do for you.

Next, decomposing it: turning this specification into units of work small enough that each one can be verified on its own, because a spec you can’t break into checkable pieces is a spec you can’t prove you’ve met.


The Problem Brief, the Spec, the Feature Spec, and the Not Building Register templates are all in the action pack. The worked examples carry a login feature the whole way through, which is the same one that turns up bleeding in The Audit.