Engineering Practice
Acceptance Criteria Are an Executable Contract
Well-formed acceptance criteria translate an idea into observable behavior, boundaries, failure cases, and evidence that both humans and agents can verify before handoff.
- Software Practice
- Acceptance Criteria
- Requirements
- Testing
- Code Review
Good acceptance criteria translate an idea into observable behavior, boundaries, failure cases, and evidence that a reviewer can actually verify. They’re not a wish list or a vague commitment to “work well.” They’re the executable contract between the person who defines the work and the person (or agent) who builds it.
When criteria are weak, reviewers can’t fail the work even when they should. The builder has no clear stop condition. Agents make plausible-sounding choices that drift from intent. The feedback loop stretches from days to weeks. When criteria are sharp, everyone agrees on done before a line of code ships.
Observable Outcomes
An acceptable outcome is something a reviewer can measure or see without reading the code. Not “fast,” but “returns results in under 200ms for the test dataset.” Not “handles errors,” but “returns HTTP 400 with a JSON error body containing an error_code field when the request body is not valid JSON.”
Each criterion should answer: What changes in the system’s behavior after this work?
Observable outcomes live at three levels:
- API or interface level: What does the caller see? What data flows in and out? What signals does a user observe?
- Constraint level: What latency, throughput, size, or resource bounds apply? What thresholds trigger a failure or warning?
- Artifact level: What logs, metrics, or traces should be emitted so a reviewer or operator can verify the outcome after the fact?
A criterion like “authentication works” is unobservable. A reviewer opens the app and sees a login form. Did auth work, or is it just rendered? A criterion like “users can log in with a GitHub token by POSTing to /auth/login with a JSON body containing {"github_token": "..."}, and the response is HTTP 200 with a JSON body containing a session_id field that is a 32-character hex string” is observable and testable by a person or a tool.
When in doubt, include the format. “Returns a JSON object” is better than “returns data.” “Returns HTTP 200” is better than “succeeds.” “Adds a log line with fields timestamp, component, action, status, duration_ms” is better than “log the operation.”
Negative Cases and Non-Goals
Acceptance criteria should name what does not happen and what is out of scope.
A negative case is an input, state, or condition where the system must not do the thing. If the acceptance criterion is “users can delete a document,” a negative case is “users cannot delete a document owned by another user.” Another is “attempting to delete a non-existent document returns HTTP 404, not HTTP 200.”
Without negative cases, a builder might implement the happy path and call it done. A reviewer might not notice that the authorization check is missing. An agent might optimize away a safety condition because it didn’t appear in the positives.
Non-goals prevent scope creep and make the boundary explicit. “This work enables document deletion. It does not implement versioning, soft deletes, or audit trail archival.” Those are separate and real; naming them as out-of-scope for this slice prevents a builder from redesigning the entire system. It also signals to a reviewer: if you want versioning, request a new ticket.
The Criteria as a Boundary Between Design and Execution
Acceptance criteria are where the architect and the builder meet. The architect specifies what must be observable; the builder chooses how.
If a criterion says “search results return in under 200ms,” the builder can use a database index, a cache tier, or denormalization. The criterion doesn’t mandate the choice. The builder owns the implementation.
If a criterion says “all errors are logged with error_code and context fields,” the builder can choose a structured logging library, inline JSON formatting, or a wrapper function. Again, the criterion sets the contract, not the recipe.
This boundary is crucial for two reasons: (1) it lets builders solve problems with tools and patterns they know, and (2) it lets reviewers hold builders accountable to intent without micromanaging. A reviewer can verify the outcome by checking the logs or running a test, not by auditing every function call.
Evidence and the Reviewer Handoff
Before handoff, the builder must provide evidence that each criterion is met. Evidence is not “the code compiles.” It’s reproducible and observable.
For an API endpoint, evidence is: a curl command and its response, or a test that sends the request and asserts the response. For a performance criterion, evidence is a run of the load generator or profile against the test dataset showing latency under the bound. For a logging criterion, evidence is a screenshot or log dump showing the expected fields present.
A reviewer’s job is to:
- Check that the evidence actually demonstrates the criterion. (A test that only checks the status code, not the response body, is incomplete evidence.)
- Verify the evidence is reproducible in the reviewer’s environment. (Run the same curl command. Run the same test. See the same logs.)
- Spot-check negative cases. (Try to delete another user’s document. Verify the 403 is returned.)
If the criterion is ambiguous after the builder provides evidence, that’s a design failure, not an execution failure. Rewrite the criterion and reopen the work.
For AI-assisted work, the handoff is the same. The agent must provide evidence that the behavior matches the observable outcome. The reviewer must verify the evidence is real in their environment, not a hallucination or a cached artifact.
An Acceptance Criteria Worksheet
When writing acceptance criteria, work through this sequence for each work item:
What is the primary outcome? state one measurable change in system behavior
Who observes this outcome? operator, end user, API client, or system monitor?
What is the observable proof? what log, metric, HTTP response, UI state, or trace shows it happened?
What are the constraints? latency, throughput, resource, or availability bounds?
What must not happen? list the negative cases: wrong user, missing data, invalid input, concurrent conflict
What is not included? name any related work that is out of scope
How will the builder prove it works? what test, command, or artifact will the reviewer run to confirm?
Use this worksheet early in design. If you cannot fill it, the work is not ready to assign. If the builder returns evidence that does not match the worksheet, the work is not done.
When Criteria Stop Working
Acceptance criteria work best when scope is small and the outcome is localized. A single endpoint, a single feature, a bounded refactor.
They become fragile when:
- The work spans multiple failure domains (databases, services, networks) and the outcome depends on coordination between them. Specify the behavior at the boundaries, not the internals.
- The criterion is a performance target but the test harness does not match production. (A 200ms latency criterion is wrong if the test runs against a cached replica and production does not.)
- The work is an abstraction or platform that will be used in ways not yet defined. Specify the interface contract; let the usage emerge.
- Non-goals pile up so high that the work becomes a political negotiation instead of a technical one. When that happens, clarify scope with a decision-maker and close the loop with explicit acceptance.
A Grounded Takeaway
Acceptance criteria are not busy work. They are the agreement that prevents a builder and a reviewer from working at cross-purposes. They scale: a person reading them understands the contract; a tool parsing them can run tests; an agent using them as context can make sound decisions about tradeoffs.
If you write criteria that are observable, bounded, and explicit about what is out of scope, you compress the feedback loop. You make it easier for a reviewer to say “yes, this is done.” You give a builder a clear target. And you create a record of what was actually agreed, which is invaluable when the work is done and something breaks.
If you’re designing systems or mentoring builders, start here. The first question is never “how do we build this?” It’s “how will we know it works?” Answer that, and the rest follows.