Drift Is Not a Bug
Spec-driven development has a drift problem.
It is not a bug in anyone's implementation. It is what the architecture predicts.
Go and look at where the conversation actually is. In the largest spec-driven toolkit on GitHub, the open issues carrying the most engagement are the drift issues. On two separate Hacker News threads about spec-driven development, both north of nine hundred points, the same question surfaces and goes unanswered: how do you address spec drift? Not "is drift real." How do you address it? The premise is conceded. Nobody has the answer.
Meanwhile, Tessl, funded at $125M explicitly to build spec-centric development, moved away from it toward skills governance. I won't characterize their reasoning; I don't have it. But when the best-capitalized attempt at the thesis redirects, that is market information.
I think the category has a structural problem, and I think it is worth naming plainly.
What the category actually built
Spec, plan, tasks, code. Every serious entrant runs some version of that pipeline, and it works — it produces better output than prompting cold, and the market's convergence on it is not a mistake. AI-native development is permanent now. Nobody is going back.
But look at what the specification is in these systems. It is markdown. A model reads it, forms an interpretation, and emits code. That is the entire relationship. The spec has no grammar, no type system, no compiler, and — decisively — no failure mode. It cannot reject anything. A document that cannot fail to parse cannot enforce anything.
The category built better prompts and called it engineering.
That is a real contribution, and I am not sneering at it. Structured intent beats unstructured intent, reliably. But it means the specification's authority ends at generation. Once code exists, the spec has spent itself. And two things follow that nobody in the category has answered.
The first is that tests are derived from the wrong source. Tests get written after the code exists — by a person reading the implementation, or increasingly by a model reading the implementation. Either way, the assertion is derived from observed behavior rather than stated intent. So the suite measures what got built. It cannot measure what was asked for, because it was never shown what was asked for. That circle closes green, forever, around a system that may have stopped being the thing you specified several releases ago.
The second is non-determinism. Where a tool does generate tests from specs, it generates them with a model. Run it twice, get two suites. Which means you cannot ask the only question that matters at release time — do these tests still represent this specification? — because there is no stable answer to compare against. If a model derives your tests, you have relocated the interpretation problem, not removed it.
And this is exactly the moment those two failures get expensive. In an AI-native SDLC, code stops being the artifact humans maintain by hand. The spec becomes the only thing a person authors on purpose. Which makes it load-bearing in a way it has never been before — and the category is shipping it as a document.
We are industrializing the production of code that no human specified, and calling the specification a prompt.
Drift is a release-time phenomenon
Here is the part I think is genuinely misdiagnosed.
Drift does not begin when the code is written. At the instant of generation, spec and code agree — that is the one moment they are guaranteed to agree. Drift begins at the first release, and at every release after it, because a released system enters a lifecycle. It gets patched under incident pressure. It gets refactored. It gets a hotfix at 2 am that nobody writes down. It gets a second feature that quietly changes the first one's behavior. At no point in that lifecycle does anything go back and ask whether the system still does what the specification said it would.
The spec was never a contract. It was a prompt with better formatting.
This is why drift cannot be patched. You cannot ship a linter that fixes it or a smarter model that avoids it, because the divergence is not a defect in generation — it accumulates in the space between releases, where the specification has no standing to object. As long as the spec is consumed at generation and discarded, drift is not a failure mode. It is the design.
The fix is not a better document. The fix is giving the specification the authority to refuse.
What we built instead
A .feat file has two halves, and the split is the whole idea.
One half is natural language for the agent doing the work — where files go, what to import, the sequence of validation, the constraints it must honor. Freeform. Any directive is valid. The compiler never reads it.
The other half is strict grammar. For every scenario, you declare the complete observable footprint: what the response is, what lands in the event store, what lands in the projection store, what goes on the broker — and where nothing should land, you declare that too. An empty declaration is an assertion.
That inversion is the mechanism. You are not enumerating the things you thought to check. You are declaring the finite set of everything that should happen, and everything outside that set is a violation by default. Captured but not predicted: violation; predicted but not captured: violation; and captured with the wrong shape: violation.
Four things fall out of that, and they are why I think this is the right shape for teams in the future.
The compiler emits the tests, not a model. Because the declaration is complete and quantified, there is nothing left to infer. No step definitions. No glue code. No human-authored test file anywhere in the loop — which removes the layer where meaning has always leaked.
Emission is deterministic. Same spec, byte-identical output. That is not a performance detail; it is what makes feat verify possible. Verification is a byte comparison. If the committed tests no longer match the current spec, the build fails. Not a warning. A failure.
Verification covers infrastructure, not just responses. Most contract testing stops at the API boundary. Predictions are quantified per configured service, so an unexpected write to a projection store, a stray event on a broker, a silent extra record — these are violations, caught at PR, by a spec that named the surface it cared about.
The prose half is future-proof by construction. Models will keep improving, and the agent-facing directives will keep getting better at eliciting good implementations. None of that changes your test suite, because the compiler never reads that half. You get to adopt every improvement in the agent layer without your verification surface moving underneath you.
The spec stops being a document you consulted once. It becomes a gate every release has to pass through — which is also what stops it being outlived. A specification that gates delivery cannot quietly become historical.
The compiler told me no
Two weeks ago I had a bug in our own dashboard. Invitations sending, emails arriving, memberships never appearing. I traced it, found the seam, and put the claim logic in the me resolver in our account subgraph. Two lines. Obvious.
The compiler refused to build it. That spec is declared type query, and the language forbids a query from predicting a write. QUERY_SIDE_EFFECT.
I was annoyed for about a minute, and then I realized the refusal was correct — and not on a technicality. What I had written was a write hidden inside a read. The kind of thing that passes review, ships, and lives in a codebase for four years until someone puts a cache in front of the read and cannot work out why data stops persisting.
The language caught a design mistake made by the person who designed the language. Enforcement that only ever agrees with its author is not enforcement.
We also settled the obvious objection before shipping — that a machine deriving tests from a spec will derive different tests than a competent engineer would. Working from the specs alone, I hand-derived the suites by mechanically applying the derivation rules, before the generator existed. Twenty-seven cases. Then we built the generator, pointed it at the same specs, and diffed—zero semantic assertion disagreements. One error in the exercise, mine, caught while deriving.
For teams operating at scale: evidence, not attestation
There is a second thing that falls out of release-time verification, and for regulated organizations I think it is the more valuable one.
Every verification run produces a record of what your software actually did, in a real environment, at a known commit, under a known CI identity. Sign those records per environment with an asymmetric key, publish the public keys at a well-known URL, chain them with sequence numbers per specification per environment, and write them once to storage that cannot be altered afterward.
That produces two properties that ordinary compliance evidence does not have.
Completeness is provable. The records are sequenced and chained. A missing record leaves a detectable gap. You cannot quietly drop the run that failed, and more to the point, you can demonstrate that you did not.
Sampling is verifiable. The sample is derived from the chain digest rather than selected by the party being audited. The organization under examination does not choose which evidence gets examined.
This matters because of what auditors call information produced by the entity. Most of the evidence in an audit is IPE — you produced it, so its reliability has to be tested before it can be relied upon. And most IPE is a CSV export or a dashboard screenshot that somebody could have edited. Chained, signed, independently verifiable records answer that reliability question directly, and a third party can verify them without our involvement at all. That is the point of publishing the keys.
The distinction from compliance automation is clean, and I want to be precise about it. Those platforms attest that your organization operates its controls — onboarding, access reviews, vendor management. This attests that your software behaves as specified. Different claim, different evidence, narrow overlap around change management and system operations. And because verification can run on a schedule against live environments, the evidence describes operation across a period rather than a point in time.
To be explicit: we do not issue opinions, and no software vendor can. Your auditor tests information you produced. We produce it in a form that survives being tested.
What I am not claiming
The adapter shelf is thin — first-party adapters for a handful of surfaces, and if your stack sits outside them you will be writing one against a published conformance kit. That is a deliberate bet, not a solved problem.
And this is one team dogfooding its own tool. Twenty-seven cases is a real result and a small number, with no independent benchmark yet. Demonstrated, not proven. Read every figure above with that attached.
Where it is
The language and toolchain are open source and free, and the local half is complete—parse, generate, verify, run —in your repo, on your CI, no account, nothing phoning home. There is a hosted ledger for teams that want the history kept and chained per environment.
The free half is not a trial. If the argument above is right, enforcement has to live in your build — and a build gate you have to pay to keep running is not a gate.
Get new posts in your inbox. No spam, unsubscribe anytime.