Related reading: BDD Was a Coordination Tax. AI Just Repriced It is the sibling post that this one extends. Evals Are Tests Wearing a Lab Coat and TDD Already Does BDD name the discipline this argument assumes.
The industry has done this exact move three times before.
Every ten years, a new specification tool arrives, presented as the answer to the perennial complaint that developers ship code the business did not ask for. The requirements document was going to fix it. Then Gherkin feature files were going to fix it. Then design docs in a shared wiki were going to fix it. Then hand-edited OpenAPI files were going to fix it. Each of those tools was, at the moment of its introduction, a genuinely reasonable-sounding attempt to solve a genuinely real problem. Each of them arrived with a manifesto. Each of them arrived with dedicated tooling and a small industry of consultants. Each of them arrived with the same promise: a separate, version-controlled artifact that captures what the system should do, before anyone writes the code that does it.
Each of them drifted from the code at exactly the same rate.
Spec-Driven Development is the fifth attempt. The generation is younger, the tooling is glossier, the pitch is aimed at agent-shaped implementers rather than human ones, but the shape is unmistakably familiar. Team writes the spec. Agent generates the implementation. Spec is “the contract.” That contract is a Word document with extra steps. The failure mode is structural, not tooling-specific, and the failure mode is the same failure mode the previous four artifacts hit for the same reason. Kent Beck named it directly in his critique of the wave, calling out the bizarre assumption that nothing will be learned during implementation that should change the specification. The ThoughtWorks Technology Radar placed it in “Assess” and warned of “a bias toward heavy up-front specification and big-bang releases” as an antipattern. Both critiques are from practitioners who have seen this movie before, and both are worth reading in full.
This post is about why the movie always ends the same way, and about the executable test being the only spec that cannot lie.
Every Prose Spec the Industry Has Built Has Drifted
The historical record is not subtle. It is a series of near-identical failures, each documented at the time, each rationalized away by the next attempt as a tooling problem rather than a structural one.
The requirements document was the first. It was going to fix the gap between what the business wanted and what engineering shipped. A room of stakeholders would spend six weeks writing down every requirement. Engineering would implement against that document. QA would verify against that document. Six months later, the document said the login screen accepts email addresses, the code required a username, and the QA plan was written against a version of the document that predated the compromise. Nobody had updated the document because updating the document required a meeting, and the meeting was scheduled for after the release. The release shipped. The document lied. The next requirements cycle started with a fresh document that would definitely be maintained this time.
Gherkin feature files were the second. They were going to fix it by making the spec executable. A file that read Given a logged-in user, When they visit the profile page, Then their name is shown would be parsed by a test framework and would fail if the behavior did not hold. That was the theory. In practice, the executable layer was a thin veneer over step definitions the business analysts could not read and the engineers had to maintain. The spec drifted between the natural-language layer (which the business owned) and the step-definition layer (which the engineers owned) at exactly the rate the two roles diverged on any specific behavior. Feature files rotted. The industry quietly abandoned them. The next specification tool arrived within a year.
Design docs in a shared wiki were the third. They were going to fix the drift by living in the same tool the team already used for everything else. The design doc would describe the system’s intended behavior, get reviewed at the start of a project, and be updated as the system evolved. In practice, the design doc got updated exactly once, at the start of the project. Every subsequent behavior change happened directly in the code, because writing the code was faster than updating the doc and running the doc through review. Six months later, the doc described the system as it was proposed. The system described itself. The doc lied. The next generation of engineers arrived, read the doc, and were confused for a month before someone told them not to trust it.
Hand-edited OpenAPI files were the fourth. They were going to fix the drift by pinning the API contract as machine-readable metadata. The file described every endpoint, every parameter, every response shape. Tooling could generate clients from it, mock servers from it, documentation from it. In practice, the OpenAPI file drifted from the code the moment anyone added a field to a response and forgot to update the file. Some teams generated the OpenAPI from the code, which is the same move as saying the code is the spec, which is the same move the discipline was supposed to have already learned. Other teams kept the OpenAPI as source of truth and watched it lie about the running system every quarter.
Four attempts. Four failures with the same shape. In each case, the artifact was separate from the code, owned by a role that changed on a different cadence than the code changed, and validated against reality only when someone bothered to run a specific ceremony. In each case, the artifact drifted, the ceremony atrophied, and the artifact ended up as a plausible-looking record of what someone once believed the system might become.
The failures were not tooling failures. They were structural. Any artifact separated from the running code, by any tooling, in any format, owned by anyone, will drift. The only artifact that does not drift is the one whose divergence from the code causes an immediate, unmissable, red bar.
SDD Is the Same Pattern at Smaller Scale
Spec-Driven Development takes the fifth turn. The unit is smaller (a spec per feature rather than a requirements document per project), the implementer is different (an agent rather than a team of engineers), and the vocabulary is fresher. The shape is otherwise identical.
The team writes a spec. The spec is a prose document. It describes what the feature should do, what inputs it accepts, what outputs it produces, what error cases it handles. The team reviews the spec. The team approves the spec. The team hands the spec to an agent. The agent generates code. The code is reviewed against the spec. Merge lands. The spec is filed away as the record of what the feature does.
Now the team learns something during implementation, as teams always do. The edge case nobody thought of during the spec review shows up when the code hits real data. The design decision that seemed correct in the abstract turns out to conflict with an existing invariant. The performance requirement that felt achievable requires a shape the spec did not anticipate. The team fixes the code. The team ships the fix. The team does not update the spec, because updating the spec requires re-running the spec-approval workflow, which is slow and social and involves more meetings than the fix warrants.
Six months later, the spec says one thing. The code does another. A new engineer reads the spec, understands the feature as-specified, and writes a downstream feature against the wrong understanding. The bug that ships is not in the new engineer’s code. The bug shipped in the moment the spec stopped matching the system and nobody was alerted.
The same failure mode, at higher throughput, on smaller units, because the tooling made writing the spec faster and doing nothing else changed the maintenance rate.
The generation of specs has gotten cheaper. The maintenance of specs has not. That asymmetry is the entire history of every prose-spec failure. Cheaper generation just produces more artifacts to fall out of sync. It does not produce any new mechanism for keeping the artifacts synchronized. The maintenance mechanism has to come from somewhere, and prose has never provided one.
A Spec That Cannot Be Invalidated by a Failing Test Cannot Be Wrong
This is the load-bearing observation, and it deserves the full sentence.
A specification’s usefulness is inversely proportional to the ease with which it can drift from the running system undetected. The specification that cannot drift undetected is the executable test. The specification that can drift undetected across every commit is the prose document. Every other specification format sits somewhere on that spectrum, and its usefulness sits at the corresponding point.
The prose spec is at the wrong end of the spectrum. Its correctness is unfalsifiable. There is no automated process that will tell the team “the spec now disagrees with the code.” There is no red bar. There is no CI failure. The spec exists. The code exists. Their relationship is whatever anyone assumes it to be. Someone can read the spec on Monday and believe the code does what it says. Someone else can read the code on Tuesday and believe it does something different. Both readers are drawing on the same source material. Neither reader is wrong. The source material contradicts itself, and there is no mechanism to force the contradiction into visibility.
Unfalsifiability sounds like a strength when a specification is being sold. “The spec cannot fail” reads as robustness. In the discipline of specification, it reads as uselessness. A spec whose relationship to the code cannot be tested is a spec whose relationship to the code cannot be trusted. The unfalsifiable spec is a document that has purchased the appearance of authority by giving up the mechanism through which authority can be verified.
The executable test is at the other end of the spectrum. Its correctness is falsified on every run. The test either holds or it does not. If the code changes such that the behavior no longer matches the specification the test encodes, the test fails, the build breaks, the merge is blocked, and someone has to decide whether to update the code or update the test. The team is forced into the awareness that a divergence exists. The team resolves the divergence. The spec (the test) and the code (the implementation) stay in sync because the tooling refuses to let them drift silently.
The prose spec cannot do this. That is not a limitation of prose. That is what prose is. A natural-language document is not runnable. Its relationship to the running code is opaque to every tool anyone has ever built. Even model-based tools that read the spec and inspect the code and try to detect divergences can only report their opinion, which the team is free to accept or ignore, and which is not part of the CI gate. The spec’s authority is social. The code’s authority is executable. When the two disagree, executable wins in reality and social wins in the room where the spec is discussed. That gap is the drift.
The Executable Test Is the Only Spec That Cannot Lie
Every claim above collapses to this one.
The test is a specification. It states, in machine-executable form, what the code is supposed to do. It is version-controlled alongside the code. It is run on every commit. When it fails, the team is forced to notice. When it passes, the specification and the implementation are, in fact, in agreement about the behavior the test encodes. No other specification format offers this guarantee. Every other format leaves the guarantee to social process, and every social process eventually decays because the incentives point the wrong way.
The disciplined test suite has been quietly filling this role for two decades. A well-written test suite reads like a specification because it is one. Loyalty_members_get_a_ten_percent_discount_on_orders_over_fifty_dollars() names a business rule in the business’s language. The setup uses builders that name domain concepts in the business’s language. The assertion states what must be true when the rule holds. A stakeholder can read this test, understand what the system does, and trust that if the test passes today, the system does today what the test says it does. That trust is not aspirational. It is mechanical.
[Fact]
public void Loyalty_members_get_a_ten_percent_discount_on_orders_over_fifty_dollars()
{
var order = anOrder()
.forCustomer(aLoyaltyMember())
.containing(aBookCosting(60.dollars()));
var receipt = checkout.process(order);
receipt.discount.Should().Be(6.dollars());
}
That is the spec. It says what the system does. It says what “does” means (produces this receipt from this input). It stays honest because it is executable. The threshold in the test cannot silently drift from the threshold in the code, because a change to one that does not match a change to the other produces a failing build.
Now compare the prose version of the same spec:
Loyalty discount. Members of the loyalty program receive a ten percent discount on orders over fifty dollars.
The prose is more readable at first glance. The prose is less durable. The prose does not know what the code does. If the threshold moves to seventy-five dollars in the code because a stakeholder decided the promotion was too generous, the prose still says fifty. The prose does not fail. The prose sits in the wiki, or the spec-driven tool, or the design doc, saying fifty forever. A new engineer reads the prose, believes the threshold is fifty, and writes a downstream feature that assumes fifty. The downstream feature is now wrong. The prose is now a source of bugs.
The disciplined test does not have this failure mode. The test that once asserted fifty either still asserts fifty (in which case the code that moved to seventy-five fails the test and the team is forced to reconcile) or has been updated to seventy-five (in which case the spec and the code agree). At no point can the test and the code silently disagree. That property is the whole game. The property is what turns a specification into a specification worth having.
The prose spec is a claim about the code. The test is a receipt of the code.
”You Will Not Learn Anything During Implementation” Is the Wrong Assumption
Kent Beck’s critique of the SDD wave lands on this exact assumption, and it lands hard. The pitch that says “write the spec, then generate the implementation” only works if implementation is a mechanical translation from a complete, correct specification to code that does what the specification says. Every experienced developer knows that is not how implementation works. Implementation is where the spec discovers what it should have said.
The edge case that nobody thought of shows up when the code encounters real data. The performance constraint that felt achievable in the abstract turns out to require an architecture the spec did not anticipate. The error case that seemed like a nice-to-have turns out to be the second most common runtime state and requires a first-class model. The dependency the spec assumed would be available turns out to have a rate limit that shapes the entire retry logic. None of these show up in the spec-review meeting. All of them show up during implementation.
A workflow that locks the spec before implementation locks out this learning. Any change discovered during implementation has to route back through the spec’s approval process, which is slow, social, and psychologically loaded (updating the spec means admitting the spec was wrong). Under time pressure the change gets made in the code and the spec is not updated. The spec becomes fiction. The system diverges.
A workflow that treats the test suite as the spec inverts the dynamic. The learning from implementation lands directly in the specification, because the specification is the test the implementation is being written against. When implementation reveals that the initial assertion was too narrow, the test is updated, the code is updated, both change together in the same commit, and the CI system verifies they are consistent. The spec is not filed away as a static artifact. The spec is continuously refined by the encounter between the team’s intent and the reality of the running system. That is what specification is supposed to do.
Beck’s critique is not a rhetorical flourish. It is a structural observation. Any specification workflow that treats implementation as translation is going to break on the reality that implementation is discovery. SDD, as commonly practiced, treats implementation as translation. It will break the same way.
What SDD Tools Are Actually Selling
The steelman deserves an honest hearing. Spec-driven tools are not selling nothing. They provide real functionality: coordination scaffolding, prompt structuring, traceability between an intent statement and the code generated from it, a common surface for a team to align on before writing anything. Those are genuine features. Some of them are hard to build. The tools are not fraudulent.
They are, however, mispositioned. The value they provide is coordination and prompt engineering. The value they claim to provide is a source of truth. Those are different claims, and only the first is defensible.
Coordination scaffolding is a real problem. Teams need a place to align on what they are building before anyone writes it. A short design note, a decision document, a written intent statement can genuinely reduce the amount of downstream rework by surfacing disagreements early. That is worth having. It does not require positioning the note as the contract the code is held to. Position it as a design note, treat it as scaffolding, keep it around for archaeology, and let the test suite be the artifact that stays in sync with the code.
Prompt structuring is a real problem. Getting a coding agent to produce useful work requires assembling context: the relevant tests, the relevant code, the intent, the constraints. A tool that helps a team compose this context is providing real value. That value is a build-time convenience. It is not a source of truth. The generated code still has to be verified against tests. The intent statement is discarded once the tests exist to encode it.
Traceability between intent and code is a real problem for teams that need to answer regulatory or audit questions about what a system does and why. A tool that links a business requirement to the code that implements it and the tests that verify it is providing real value. That value depends on the tests being the linked artifact, not on the prose intent statement being the linked artifact. The audit trail runs through the executable, or the audit trail lies.
The narrow use cases are defensible. The broad pitch is not. When the tool is sold as the replacement for a disciplined test suite, as the artifact that captures what the system does, as the contract the code is held to, the tool is being sold as something it structurally cannot be. It is being sold as an unfalsifiable specification, and the historical record on unfalsifiable specifications is unbroken.
The Disciplined Test Suite Already Did All of It
Zoom back out to what the SDD wave is actually trying to accomplish. A specification that is version-controlled, readable, aligned with the business, and consulted throughout implementation. That specification already exists. It has existed for two decades. It is what a well-written test suite is.
Domain-named scenarios are specs. Loyalty_members_get_a_ten_percent_discount_on_orders_over_fifty_dollars is a specification, written in the business’s vocabulary, encoded as an executable check, versioned alongside the code it specifies. A stakeholder can read it. An engineer can read it. An agent can read it. All three can trust it, because if it were wrong, the test would fail.
Builders are the grammar. aLoyaltyMember(), anOrderContaining(twoBooks()), aCartReadyForCheckout() compose into sentences that read like the domain. They are not code artifacts that a business stakeholder needs training to understand. They are the vocabulary the business uses, encoded as callable identifiers. A new engineer reading the test suite learns the domain by reading the tests, because the tests are named in the domain’s language.
The suite is the version-controlled artifact. It lives in the repository. It moves with the code. Every change to the code that breaks a specification is caught at the moment of the change, because the CI system runs the specification and reports the failure. Every change to the specification that the code does not yet support is caught at the moment of the change, because the same CI system reports the failure. The two artifacts are structurally forced to stay synchronized. The synchronization is not a policy. The synchronization is what the tooling refuses to allow to fail silently.
SDD reinvented the part that does not work (the separate prose document) and left the part that does (the executable specification woven into the test suite) on the cutting-room floor. The wave arrived proposing a solution to a problem the discipline already had a better answer to, and proposed it in exactly the form the discipline had already tried and abandoned four times.
The industry has done this exact move three times before, and this is the fifth time.
The disciplined test suite is the specification. It is version-controlled. It is executable. It is written in the domain’s language. It stays in sync with the code because the tooling refuses to let it drift. It is what every prose-spec attempt has been an approximation of, and it has quietly outlived four generations of the artifacts that tried to replace it. The wave that arrives every ten years to sell a fresh prose specification is welcome to try again. The failure mode is going to be the same. It has been the same every previous time. Structural failure modes do not learn from being renamed.
The executable test is the only spec that cannot lie. The prose spec is a claim about the code. The test is a receipt of the code. The team that ships receipts, not claims, is the team whose specification is worth trusting.