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 four 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. Then design docs in a shared wiki. Then hand-edited OpenAPI files. Each arrived with a manifesto, dedicated tooling, and a small industry of consultants. Each 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.
Spec-Driven Development is the fifth attempt. The tooling is glossier and the pitch is aimed at agent-shaped implementers rather than human ones, but the shape is familiar. Team writes the spec. Agent generates the implementation. Spec is “the contract.” That contract is a Word document with extra steps. Kent Beck named the core defect in his critique of the wave: the bizarre assumption that nothing will be learned during implementation that should change the specification. The ThoughtWorks Technology Radar placed SDD in “Assess” and warned of “a bias toward heavy up-front specification and big-bang releases” as an antipattern. Neither critique says the idea is worthless. Both say the failure mode is structural, and structural failure modes do not care how good the tooling is.
This post is about why the movie keeps ending the same way, and about where the source of truth actually lives.
Every Prose Spec the Industry Has Built Has Drifted
The historical record 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 came first. Stakeholders spent six weeks writing down every requirement. Engineering implemented against the document, QA verified against it. 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 that predated the compromise. Nobody had updated the document, because updating it required a meeting, and the meeting was scheduled for after the release. The release shipped. The document lied.
Gherkin feature files made the spec executable, in 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, and the two layers drifted apart as fast as the two roles diverged on any specific behavior. Feature files rotted, and most teams quietly shelved them.
Wiki design docs 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 routing the doc through review. Six months later the doc described the system as proposed. The system described itself.
Hand-edited OpenAPI files pinned the API contract as machine-readable metadata, then drifted the moment anyone added a response field and forgot the file. Teams that switched to generating the OpenAPI from code were conceding the point: the code had become the spec. Teams that kept the file as source of truth 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 ceremony. The ceremony atrophied. The artifact ended up as a plausible-looking record of what someone once believed the system might become.
These were not tooling failures. Any artifact separated from the running code will drift. The 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 document per project), the implementer is an agent rather than a team, the vocabulary is fresher. The shape is otherwise identical.
The team writes a prose spec, reviews it, approves it, and hands it to an agent. The agent generates code. The code is reviewed against the spec, the merge lands, and the spec is filed away as the record of what the feature does.
Then the team learns something during implementation, as teams always do. The edge case nobody caught in review shows up in real data. The design that seemed correct in the abstract conflicts with an existing invariant. The team fixes the code and ships. The team does not update the spec, because updating the spec means re-running a slow, social approval workflow that the fix does not warrant.
Six months later the spec says one thing and the code does another. A new engineer reads the spec, understands the feature as-specified, and builds a downstream feature against the wrong understanding. The bug did not ship in the new engineer’s code. It shipped in the moment the spec stopped matching the system and nobody was alerted.
Generation got cheaper. Maintenance did not. That asymmetry is the entire history of prose-spec failure: cheaper generation produces more artifacts to fall out of sync, and no new mechanism for keeping them in sync. The maintenance mechanism has to come from somewhere, and prose has never provided one.
A Spec That Cannot Be Shown Wrong Cannot Be Trusted
This is the load-bearing observation.
A specification’s usefulness is inversely proportional to the ease with which it can drift from the running system undetected. The executable test sits at one end of that spectrum: divergence is detected on every run. The prose document sits at the other: divergence is detected never, or by accident.
The prose spec’s relationship to the code is whatever anyone assumes it to be. Someone reads the spec on Monday and believes the code does what it says. Someone else reads the code on Tuesday and believes it does something different. Neither reader is wrong. The source material contradicts itself, and no mechanism forces 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 is the defect. A spec whose relationship to the code cannot be checked is a spec whose relationship to the code cannot be trusted.
Model-based tooling deserves the honest sentence here. A model that reads the spec, inspects the code, and flags divergence can absolutely gate CI. That gate yields probabilistic evidence, a governed judgment rather than a deterministic proof, and a team that wires one in has built a real drift alarm that none of the previous four generations had. It narrows the window. It does not change the asymmetry: the model renders an opinion about whether two artifacts agree, while the test is one of those artifacts, executing. When the two gates disagree, the executable one is the one reality sides with.
The Test Is a Receipt, Not a Guarantee
The test is a specification. It states, in machine-executable form, what the code is supposed to do. It is version-controlled beside the code. It runs on every commit. When it fails, the team is forced to notice.
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. The assertion states what must be true when the rule holds.
[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. The threshold in the test cannot silently drift from the threshold in the code, because a change to one without the other produces a failing build.
Compare the prose version:
Loyalty discount. Members of the loyalty program receive a ten percent discount on orders over fifty dollars.
More readable at first glance. Less durable. When the threshold moves to seventy-five dollars because a stakeholder decided the promotion was too generous, the prose still says fifty. It says fifty forever, and it never fails. A new engineer reads fifty, assumes fifty, and builds a downstream feature on the wrong number. The prose is now a source of bugs.
Precision matters here, because the counter-claim is easy and correct: tests are not infallible. A test can encode the wrong intent. It can assert too little. It can mock away the reality it was supposed to check, and it can be weakened in the same commit that breaks the behavior it guarded. Those failure modes are real, and they are why mutation testing, flaky-test hygiene, and tamper-resistant test design exist as disciplines. The claim is narrower and stronger: the test cannot drift silently. Every one of its failure modes leaves evidence, in the diff, in the run, in the mutation score. The prose spec’s failure mode leaves no evidence anywhere.
The prose spec is a claim about the system. The test is a receipt.
Implementation Is Discovery, Not Translation
Beck’s critique lands on the assumption underneath the whole workflow. “Write the spec, then generate the implementation” only works if implementation is mechanical translation from a complete, correct specification. It is not. Implementation is where the spec discovers what it should have said.
The edge case nobody thought of shows up when the code meets real data. The performance constraint that felt achievable requires an architecture the spec did not anticipate. The dependency the spec assumed turns out to have a rate limit that shapes the entire retry design. None of these appear in the spec-review meeting. All of them appear during implementation.
A workflow that locks the spec before implementation locks out this learning. Every discovery has to route back through an approval process that is slow, social, and psychologically loaded, because updating the spec means admitting the spec was wrong. Under pressure, the change lands in the code, the spec is not updated, and the spec becomes fiction.
A workflow that treats the test suite as the spec inverts the dynamic. The learning lands directly in the specification, because the specification is the thing the implementation is being written against. Test and code change together in the same commit, and CI verifies they agree. The spec is not filed away. It is continuously refined by contact with reality, which is what a specification is for.
What SDD Tools Are Actually Selling
The steelman deserves a hearing, because the tools are not selling nothing.
Coordination is real value. Teams need a place to align on what they are building before anyone builds it, and a written intent statement surfaces disagreements early. Keep it. Call it a design note, keep it around for archaeology, and do not position it as the contract the code is held to.
Prompt structuring is real value. Assembling the right context for a coding agent (the relevant tests, code, constraints, and intent) is genuine work, and tooling that helps is welcome. That is a build-time convenience, not a source of truth. The generated code still gets verified against tests, and the intent statement has done its job once the tests encode it.
Traceability is real value for teams answering regulatory and audit questions. But the trail has to run through the tests that verify the behavior, not the prose that proposed it. An audit trail that terminates in an unverifiable artifact is an audit trail that lies.
The narrow uses are defensible. The broad pitch is not. Sold as coordination scaffolding, SDD tools are useful. Sold as the source of truth, as the artifact that captures what the system does, they are being sold as something a prose document structurally cannot be. “Assess” is the right verdict on this wave: a workflow worth trying, not settled doctrine about where truth lives.
The Spec Is a Claim. The Test Is a Receipt. Production Is the Verdict.
Zoom out past TDD, because the point is larger than tests.
Prose captures intent. Design notes, intent statements, spec documents: they are how humans align before building, and that is a job worth doing well.
Executable artifacts enforce claims. Tests, contract checks, type systems, policy gates, invariants. This is the layer where a statement about the system stops being an opinion, because divergence turns into a failure someone must resolve.
Production is the verdict. A passing suite does not make the system right; it makes the system consistent with its stated claims. Whether those were the right claims is answered by the running system and the outcomes it produces.
SDD’s structural mistake is filing a planning artifact in the enforcement layer. The disciplined test suite has occupied that layer for two decades: domain-named scenarios are the specs, builders are the grammar, the suite is the version-controlled artifact that cannot drift silently. SDD reinvented the layer that drifts and left the layer that enforces on the cutting-room floor.
The industry has done this exact move four times. The fifth will end the same way, because structural failure modes do not learn from being renamed.
The prose spec is a claim. The test is a receipt. Production is the verdict. The team that ships receipts, not claims, is the team whose specification is worth trusting.