Provided, Otherwise: The Missing Guard in Gherkin
If AI agents write the code, the work that remains sits at the two ends of the pipeline: saying what should be built, and checking that it was. The industry has noticed. Spec-driven development tools promise that a well-written specification, given enough tokens, turns into working software.¹ The claim is plausible. It also moves the burden somewhere that was never built to carry it.
For decades, code was where ambiguity surfaced. A developer reading a vague requirement had to decide what it meant, and the decision showed up in a pull request, a question in a stand-up, a bug report. When an agent writes the code, that friction disappears. The agent decides silently, and its decision looks exactly like the intended one.
So the specification has to carry more than it used to. It has to be precise enough that an agent cannot misread it, and structured enough that a second agent can derive tests from it that check something. The most common format for business-readable acceptance criteria, Gherkin, has a structural gap that matters precisely here. This essay describes that gap, proposes a small extension that closes most of it, and shows how agents can help on both sides: writing the specification and using it.
One word in a familiar scenario
The cash machine is the textbook example of behaviour-driven development. It goes back to Dan North’s Introducing BDD,² and versions of it appear in countless tutorials. A typical one reads:
Scenario: Account has sufficient funds
Given the account balance is $100
And the card is valid
And the machine contains enough money
When the Account Holder requests $20
Then the ATM should dispense $20
And the account balance should be $80
And the card should be returnedIt reads well. Most reviewers would accept it. But one line is not like the others. “The account balance is $100” describes the world before anything happens. “The machine contains enough money” does not. Enough for what? For the $20 that only appears in the When. The Given cannot be understood, let alone checked, without first reading the event it is supposed to precede.
This is not a timing paradox. The cash in the machine is ordinary prior state; it exists whether or not anyone walks up. What cannot exist before the When is the comparison between that state and the requested amount. The author has performed that comparison in their head and written down only the result.
That comparison has a name in other disciplines: it is a guard. A precondition is a fact about the state of the world. A guard is a relation between that state and the parameters of the event that arrives. In a statechart, it is written request(amount) [cash ≥ amount] / dispense. The condition sits on the transition, because that is where it belongs.
Gherkin has three places to put things: state, event, outcome. A guard is none of them. So authors push it into the Given, where it becomes an adjective that hides the very rule the scenario was meant to illustrate: enough, sufficient, valid, appropriate.
Why more examples don’t fix it
Gherkin’s defenders have a good answer, and it deserves to be stated fairly. Gherkin is an example language on purpose. A scenario is one instance, not a rule. With concrete values everywhere, the guard’s outcome is already determined by the data. The rule itself is meant to emerge across several instances, and Gherkin 6 even added a Rule keyword to name it:³
Rule: The ATM dispenses only if it holds at least the requested amount
Scenario Outline: Cash availability
Given the ATM contains <cash> in cash
When the Account Holder requests <amount>
Then the ATM should <outcome>
Examples:
| cash | amount | outcome |
| $500 | $20 | dispense $20 |
| $20 | $20 | dispense $20 |
| $10 | $20 | refuse the request |For a threshold rule, this works. There is one boundary, the examples probe it, and a reader can infer the rule. The Rule line is prose, not executable, but the examples carry it.
Now consider what a cash machine does. It does not hold “money”; it holds notes. A machine with three $10 notes can pay out $10, $20 or $30, but not $25. A machine holding one $50 and three $20 notes can pay out $90, but not $80. The guard is no longer cash ≥ amount. It is “the amount can be composed from the available notes”, and that condition is non-monotone: there is no single boundary to probe. No small set of examples lets a reader reliably infer it. The examples remain correct; they simply stop explaining anything.
The same detail breaks the Then. With two $20 notes and four $10 notes, a request for $40 can be paid three different ways. “Then the ATM should contain…” is either nondeterministic, or it can only be pinned down by fixing the note-selection algorithm in the scenario. That is implementation detail leaking into a business specification. Gherkin has no way to say any valid combination.
So the gap is not that Gherkin cannot evaluate a guard. Every concrete instance is fully determined. The gap is that Gherkin cannot state a guard, and its fallback strategy, demonstrating the rule through examples, fails exactly for the rules that are hardest to get right.
What spec-driven tools do today
The newer spec-driven tools have quietly sidestepped the problem instead of solving it. OpenSpec,⁴ one of them, writes requirements as SHALL statements with scenarios underneath. Its scenarios use only WHEN and THEN:
### Requirement: Theme selection
The app SHALL let users switch between light and dark themes,
defaulting to the system preference.
#### Scenario: User toggles dark mode
- **WHEN** the user clicks the theme toggle
- **THEN** the app switches to dark mode and persists the choiceThe Given is gone. The preconditions have not disappeared, though. They go to one of three places: into the When (“WHEN a user with insufficient funds requests a withdrawal”), into the SHALL sentence of the requirement above the scenario, or nowhere, with the state simply assumed.
The first option is closer to the truth than Gherkin. “WHEN the requested amount cannot be composed from the available notes THEN the ATM SHALL refuse” puts the guard on the event, where it belongs. But it pays a price: concrete data drops out. The format has moved from specification by example to specification by rule, close to the EARS requirement templates.⁵ Rules are naturally written in relative terms. The “enough” problem is no longer an occasional slip; it is how the format works.
For a coding agent that is convenient, because an agent generating code needs rules rather than examples. But it means that the boundary values, the cases where the guard flips, are left almost entirely to the agent’s judgement. And the third option, the unstated state, is the most dangerous of all. It is exactly what a model fills in from its training data.
The proposal: Provided and Otherwise
The fix needs two words that business people already read every day in contracts and insurance terms: provided and otherwise.
Given the ATM holds one $50 note and three $20 notes
When the customer asks for <amount>
Provided the ATM can make up <amount> from its notes
Then it pays out notes that sum to exactly <amount>
And its stock of notes is reduced by exactly those notes
Otherwise it refuses the request
And nothing else changes
| amount | result | why |
| $90 | pays | $50 + 2 × $20 |
| $80 | refuses | no combination makes $80 |
| $110 | pays | every note in the machine |
| $120 | refuses | exceeds the notes held |Read aloud, it is one sentence: given the ATM holds these notes, when the customer asks for $80, provided the ATM can make up $80 from its notes, it pays it out; otherwise it refuses and nothing else changes. Clunky on paper, perhaps. But anyone who has read a contract clause can follow it.
The extension comes with two writing rules. Both can be checked mechanically.
- Given must not refer to the event. No enough, no sufficient, nothing that needs the When to be understood. A Given describes the world as it is.
- Provided must refer to the event. A Provided line that mentions nothing from the When is really a precondition, and belongs in the Given.
Together they give the guard a precise definition: a condition that only makes sense once the event is known. That is the line the original scenario crossed.
Three smaller conventions complete the format. Otherwise is mandatory: every guard forces a statement about what happens when it does not hold, including the frame condition nothing else changes, and negative cases and frame conditions are among the things specifications most often forget. Outcomes may be relations: “pays out notes that sum to exactly the amount” admits every valid combination, which resolves the nondeterministic Then without dictating an algorithm. And named conditions must be defined: can make up the amount from its notes is a term, composable for short, and it belongs in a glossary with a definition and an owner. An undefined term in a Provided line is a visible hole, not a silent assumption.
Several guards with different consequences (“otherwise, if the balance is too low…, otherwise, if the notes don’t fit…”) become unreadable quickly. That is best treated as a signal to split the rule: one Provided, one Otherwise per rule.
None of this is new in principle. Event-B writes an event as ANY amount WHERE guard THEN action, with parameters, guard and effect as separate parts. Statecharts write event [guard] / action. Design by Contract lets preconditions refer to parameters. The separation of state, event, guard and effect is standard in formal methods, and formal notation never reached acceptance criteria because it is unreadable to the people who own the rules. Structured natural language has come part of the way. EARS, developed at Rolls-Royce for aero-engine requirements, makes the author separate the preconditions under which a requirement can be invoked from the trigger that initiates it, in plain words, not notation. Its ordering rule is telling: the preconditions must already hold when the trigger arrives. A condition that relates the trigger to the state has no slot of its own and ends up inside the trigger or in an IF clause, and nothing requires the alternative to be stated.
The closest relative is older, and was written for business readers from the start. The fully dressed use case, in Alistair Cockburn’s version, states its precondition as an assertion about the world at the moment the use case opens, attaches each extension condition to the step at which the system detects it, requires every condition to be handled, and lists minimal guarantees that hold however the use case ends.⁶ Precondition, guard, Otherwise and frame condition were all there, along with something close to the first writing rule. What the template does not have is the second. Its own example of an extension condition is insufficient funds: the condition sits on the right step, and the relation it stands for is left unstated. Nor does it carry examples to derive tests from. Gherkin, the format agile teams more often reach for today, kept the examples and has no slot for the guard, the alternative or the frame condition. Provided and otherwise put the slots back into the example format, in words the owners of the rules already use, with a rule that makes the guard state its relation to the event. That is all the extension (call it Guarded Gherkin) adds.
Upstream: an agent that asks instead of answers
The obvious objection to any richer format is that business people will not write it. In practice the objection understates the problem. Getting acceptance criteria written at all is hard; getting them in Gherkin is harder; getting examples is rarer still. Gherkin’s defence assumes examples that, in many organisations, nobody writes. That is a practitioner’s observation, not a measured result, but it changes what a format can ask for. They do not have to write it alone. An authoring agent, packaged as a skill that encodes the format and its rules, can sit beside them and take the writing off their hands, though not the answering. The crucial design decision is what that agent is allowed to do.
It should interrogate, not formulate. It flags relative terms in a Given and asks what they are relative to. It insists on an Otherwise for every Provided. It asks for examples on both sides of every guard, and checks that each condition in a Provided is varied by at least one example. It notices outcomes that would only hold under one particular implementation, and asks whether any valid outcome is acceptable. And it refuses to define a named condition itself, asking the owner of the rule to define it instead.
That last refusal matters most. An agent that helpfully writes “composable means…” has quietly taken over a domain decision that belongs to the business. In a familiar domain it will probably guess right, which makes the habit more dangerous, not less.
In an unfamiliar domain it has nothing to guess from. Acceptance criteria are written before any code, data or usage exists. For a new product, nobody has a reference: not the business, and not a model trained on yesterday’s software. This splits the holes in a specification into two kinds.
Get Bruno Baketarić’s stories in your inbox
Join Medium for free to get updates from this writer.
Structural holes follow from the form of the specification: relative terms, unvaried conditions, missing negative cases, ambiguous outcomes, contradictory rules. They can be detected in any domain, and an agent will find them more reliably than a tired reviewer.
Domain holes follow from what the specification is about. The note-denomination problem is one. Finding them requires knowledge of the domain. An agent can offer analogies (composability problems recur in quotas, capacity planning and licence pools), but analogies are prompts for a human, never answers. Nor does the authoring agent replace the person who knows the system. A domain hole is often found first by whoever will have to build around it, and the authoring session is a good place for that person to be.
The format helps with both. It makes structural holes checkable, and it turns domain holes into a list of undefined terms. That list is an honest statement of what is not yet known. It is exactly what an agent must not fill in on its own.
The result of authoring is therefore two artefacts, not one: the rules, and a glossary of conditions that the rules refer to. Rules point to terms, terms have definitions, definitions have owners. Most of the real domain knowledge lives in the glossary. It needs no special tooling: a plain list of terms, each with a definition and an owner, kept next to the rules, is enough.
Naming a condition is not yet naming the right one. A document tool that refuses to edit text while a comment thread is still open on it has a guard in the right place: provided the thread is open, refuse. Its instructions tell an agent to answer the comment, resolve the thread, then edit. An agent that follows them to the letter gets its edit through, and leaves its answer attached to deleted text in a closed thread, where the person who asked will not see it. The guard did what it said. What it said was a proxy. The condition that mattered was that the commenter had read the answer; “the thread is resolved” is a state the actor can set by itself.⁷ A Provided protects something only if it names the real condition, not one the actor can satisfy on its own. No format can check that. It is a question for the owner of the rule, and one more reason the authoring agent asks instead of answering.
Downstream: tests that check something
There are two ways an agent can derive tests from acceptance criteria, and they fail differently.
Literal translation turns each concrete scenario into an executable check. It is safe, and it is thin: three examples give three checks. For a non-monotone guard, that is almost no coverage.
Generalisation infers the rule and generates further cases. This runs into the oracle problem. Every added case needs an expected outcome, and the specification only supplies outcomes for the cases it states. So the agent invents the rule. Given “the machine contains enough money”, it will almost certainly invent cash ≥ amount. The generated tests will be green, consistent and wrong about denominations. Nobody notices, because they look exactly as intended.
The extended format changes this, because each part maps to a kind of test:
- Examples → example-based tests. They check the stated cases, including both sides of the guard.
- Provided plus a relational outcome → property-based tests. For every amount, money is paid out exactly when the amount can be composed, and the dispensed notes sum to the amount.
- Otherwise plus “nothing else changes” → frame tests. After a refusal, the balance and the note stock are unchanged.
- Glossary definitions → tests of the conditions themselves.
composablebehaves as its owner defined it.
The agent no longer has to invent the rule. It is stated in the Provided, and its terms are defined in the glossary. If a term is undefined, the agent stops and reports the gap instead of guessing.
One further rule matters as much as the format: tests and code must not come from the same context. An agent that derives both from the same reading of the specification will make the same mistakes in both, and the tests will confirm the code rather than check it. Validation only means something if the two interpretations are independent.
This is the arrangement proposed here: a test agent and a coding agent, each reading the specification on its own, neither seeing the other’s output before both are done. A failing test then becomes a signal rather than a verdict. One of the two misread the specification, and the specification decides which. Two independent readings can be compared; a single reading cannot be checked against anything.
A stricter variant, offered here as an outlook, not a recommendation, lets the coding agent see only the tests, never the specification. The tests become the full contract. Anything the test agent did not translate will not be built, so gaps show up as missing behaviour instead of silent interpretation. The variant carries three risks. The first is teaching to the test: an agent that sees only tests may special-case them (if amount == 80: refuse). Property-based tests derived from the Provided cannot be satisfied with a lookup table; example tests alone can. The second is lost vocabulary. Tests say what, not in which terms; a middle ground is to give the coding agent the glossary but not the rules, so that domain language arrives directly and behaviour only through tests. The third is a single point of failure. If the test agent misreads the specification, the code faithfully implements the misreading, so the test agent's output needs its own review against the specification.
Either arrangement gains from a device borrowed from machine learning, the hold-out set. Whatever tests the coding agent is eventually allowed to see, from the outset in the strict variant or as failures after the first round in the proposed one, a reserved set, ideally property-based, is used only for validation. Code that passes the visible tests and fails the hidden ones has overfitted.
The arrangement is not new either. Cleanroom Software Engineering, developed under Harlan Mills at IBM’s Federal Systems Division in the 1980s, handed testing to an independent certification team that built its tests from a specification of expected usage, with no knowledge of the design. Developers did not even execute their own code before it reached that team.⁸ Cleanroom stayed confined to organisations that could staff it with skilled and committed engineers.⁹ For agents, the separation itself costs almost nothing; what Cleanroom demanded of its engineers is another matter.
Across rules: conflicts become visible
A single rule can be reviewed by reading it. A system has hundreds of them, written by different people over years. Explicit guards make the whole set analysable in a way prose rules are not. Three kinds of conflict become detectable. Overlaps are two rules for the same event whose guards can both hold, with different outcomes: a contradiction. Gaps are combinations of conditions that no rule covers: undefined behaviour. And Otherwise clauses can collide, one rule saying “refuse, nothing else changes” while another says “log the failed attempt” for the same case.
Where the glossary defines conditions precisely enough, an agent can translate guards into logic and hand the overlap question to a constraint solver. The agent translates; the solver decides. The translation is still a reading of the glossary and needs review, but the verdict on whether two guards overlap no longer depends on anyone’s sense of the wording. In WHEN/THEN prose the same conflicts stay buried in the text.
The set of rules also needs maintenance. It should be a living specification of current behaviour, not an archive of every scenario ever written. Rules that no longer apply are removed; otherwise the set fills with stale and contradictory examples, and checking it gets more expensive the longer the system lives. Checking belongs at the point of change: when a rule is added or modified, check that rule and its neighbours, not the whole set, all the time.
Some holes will only ever be found in operation: an incident, a support ticket, a user doing something nobody anticipated. Those findings have to flow back into the rules and the glossary. Without that loop, “specify upfront, validate afterwards” is waterfall with a larger budget.
What this does not solve
It is tempting to read all of this as a path to provably correct software. It is not. A program can be proven correct only relative to a specification, and formal verification does exactly that. What cannot be proven is that the specification itself is right. Every check needs a checker, and that checker would need checking in turn. Verification ends somewhere; validation does not.
This is especially true at the start. Before anything exists, acceptance criteria are hypotheses about what will be useful. Treating them as settled facts, and freezing them into green tests, turns uncertainty into the appearance of certainty. The format cannot prevent that. It can only make the uncertainty visible: as undefined terms, as missing Otherwise clauses, as guards nobody has tested from both sides.
The realistic goal, then, is not correctness. It is making errors visible early and cheaply. Measured against that goal, Guarded Gherkin’s two keywords and two writing rules do more than their size suggests. The guard gets a home and stops hiding in the Given, the negative case becomes mandatory, and what nobody yet knows shows up as an undefined term, not as a guess that downstream agents quietly make on everyone’s behalf.
The extension also asks very little of the people who own the rules. It does not require them to think formally. It only requires them to say what happens when the condition does not hold. Most of them already do that in conversation. The use case once asked. Gherkin never has.
Follow-up: I tested this format against a running system: what it held, what it couldn’t, and where decisions went that never reached the specification. Decided Elsewhere
2ⁿᵈ follow-up: In the meantime, I’ve built spec-driven-development tooling to implement the idea; and it was built using the idea itself. It’s available on Github.
¹ See also “The Spec Is Not the Method”, on why the large-upfront form spec-driven development first took repeats a mechanism the field has already paid for. The argument here assumes the opposite kind of specification: one that stays open and is revised as findings come back.
² NORTH, Dan, 2006. Introducing BDD. Better Software. March 2006. Republished on the author’s site: dannorth.net/blog/introducing-bdd. North’s original scenario reads “the dispenser contains cash”, and its event, “the customer requests cash”, carries no amount; the insufficient-cash case appears only as a further scenario to consider. The “enough money” wording is the later tutorial form.
³ Cucumber, Gherkin Reference, section “Rule”: cucumber.io/docs/gherkin/reference. The keyword arrived with Gherkin 6 in 2018, to align the syntax with Example Mapping.
⁴ OpenSpec, Fission AI: github.com/Fission-AI/OpenSpec.
⁵ MAVIN, Alistair, WILKINSON, Philip, HARWOOD, Adrian and NOVAK, Mark, 2009. Easy Approach to Requirements Syntax (EARS). In: 2009 17th IEEE International Requirements Engineering Conference. Atlanta, Georgia, USA: IEEE. August 2009. pp. 317–322. DOI 10.1109/RE.2009.9. The paper builds on Event-Condition-Action rules, in which an event triggers the rule and a separate condition decides whether the action follows (p. 318); its own templates keep preconditions and trigger, with no separate condition (p. 319). Unwanted behaviour, which the authors found a major source of omissions, gets a syntax of its own (IF … THEN), not a counterpart required for every requirement. The motivation is the one shared here: formal and graphical notations create a language barrier between developers and stakeholders (p. 318).
⁶ COCKBURN, Alistair, [2001] 2011. Writing effective use cases. 23. print. Boston: Addison-Wesley. The Agile software development series. pp. 81–84, 100–103. ISBN 978–0–201–70225–5. Preconditions are enforced before the use case starts and not checked again during it (p. 81); minimal guarantees are the fewest promises the system makes when the goal cannot be delivered (p. 83). The extension “2a. Insufficient funds” appears on p. 103 and again on p. 108, where it is handled by asking the customer for a new amount.
⁷ Ibid., p. 102. His Guideline 11 asks that an extension condition say what the system detects, not what happened: not “customer forgets PIN” but a time-out. The proxy problem is the other side of the same discipline. A condition the system can detect is not thereby one the actor cannot set.
⁸ MILLS, Harlan D., DYER, Michael and LINGER, Richard C., 1987. Cleanroom software engineering. IEEE Software. 4(5), pp. 19–25. Full text. The three projects reported there reached system test without debugging (p. 19). The team structure is described in LINGER, Richard C., 1993. Cleanroom software engineering for zero-defect software. In: Proceedings of the 15th International Conference on Software Engineering. IEEE. pp. 2–13: software enters system testing “with no execution by the development team”, and the certification team has deep knowledge of expected usage but none of design internals. The functional specification is usually written by the development team, by a separate team only on large projects.
⁹ SOMMERVILLE, Ian, 2008. Cleanroom experience. Web companion to Software engineering, 9th edition. Sommerville notes that the method seems to work in the hands of skilled and committed engineers, and that reports of its use dried up after the late 1990s.








