Skip to content
Back to skills

Acceptance Criteria

ASecurity

Use when generating the acceptance criteria — write specs/validation/acceptance/F<n>-<slug>.feature, one Gherkin file per feature, from the requirements and the prototype, for the features a design run designs.

  • 25 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 19, 2026
ai-agentsgoapidatabasesecurity

Works with

  • api

Security analysis

A100/100

Scanned October 19, 2026

npx -y skills add wso2/labs-agentic-engineer --skill acceptance-criteria --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Acceptance Criteria?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Acceptance Criteria
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/wso2-acceptance-criteria/badge)](https://www.skillsdirectory.com/skills/wso2-acceptance-criteria)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: acceptance-criteria
description: Use when generating the acceptance criteria — write specs/validation/acceptance/F<n>-<slug>.feature, one Gherkin file per feature, from the requirements and the prototype, for the features a design run designs.
metadata:
  aep:
    kind: platform
    audience: [design]
---

# Generate the acceptance criteria

You are producing the **acceptance criteria** as Gherkin: `Rule:` blocks
that state what the product must do, each illustrated by concrete `Scenario:`
examples. It is written to be read by a person and executed by an agent driving
the live app — there are no step definitions and no generated test code, so the
scenario text IS the test.

**Faithfulness to the requirement matters more than volume.** An invented rule
becomes a check the product was never asked to pass.

## Input — the requirement, and the prototype that shows it

Write criteria for the features this design run designs (`/design F1 F2`
names them; a bare `/design` means every feature it designs). The design
writes them last, after the prototypes. For each feature, read:

- its file under `specs/requirements/features/` — its User Stories are the
  spine, and its Decisions (the `*assumed*` lines included) are requirements
  too;
- `specs/requirements/prd.md`, whose Actors section names the people your
  scenarios are about, and the product-wide items that apply to it;
- **the prototype, where the feature has screens.** A web application serves
  the feature when its `specs/design/components/<name>/design.json` lists one
  of the feature's stories in `stories`. Read that component's
  `prototype.json` (its flows are the walks a reviewer did, one story each)
  and `prototype.tsx` (its screens and its sample records).

Take from the prototype what the reviewer saw: the actors' names, the sample
records (`"Asha"`, `"$42.00"`, `"Taxi to the airport"`), the words on the
screens, and the order of the steps in each flow. Take from the requirements
what the product must do. A rule comes from a requirement, never from a
screen: a message, a label or a layout that only the prototype shows is how
the requirement is met, not a requirement. A feature with no screens (a
nightly job, a machine-facing service) gets its scenarios from the
requirements alone.

**Do not read** the rest of the design (`design.cell`, `domain-model.md`,
`security.json`, `flows/`, `decisions.md`, `openapi.yaml`, a dependency's
files) or any source code. The criteria grade the build of those files, and
must not repeat them.

## Output — Gherkin under `specs/validation/acceptance/`

Write one file per feature: `specs/validation/acceptance/F<n>-<slug>.feature`,
named as the feature's own file is (`features/F1-rounds.md` →
`acceptance/F1-rounds.feature`). Use `addFile`, replacing the contents if the
file already exists; a feature whose design this run updates gets its file
rewritten. Its `Feature:` line is the feature's ID and name.

```gherkin
Feature: F1 Lunch rounds

  @story-F1.1
  Rule: Only one round may be open at a time

    @negative
    Scenario: A second round is refused
      Given a round is open for "Bridge Cafe"
      When Olivia tries to open a round for "Riverside Kitchen"
      Then she is told a round is already open

  @story-F1.1 @story-F1.4
  Rule: Any signed-in teammate may open the day's round

    Scenario: Opening the day's first round
      Given no round is open
      When Olivia opens a round for "Bridge Cafe" at "12:30"
      Then the round is open with a cutoff of "12:30"

    @negative
    Scenario: A cutoff in the past is rejected
      Given no round is open
      When Olivia opens a round for "Bridge Cafe" at "09:00" and the time is "11:00"
      Then she is told the cutoff must be later than now
```

### Structure rules

| Element | Rule |
|---|---|
| `Feature:` | The feature's ID and name: `Feature: F1 Lunch rounds`. One file per feature. |
| `Rule:` | One thing the product must do, in one sentence. This is **your inference** — the PRD has no rules section — so state it plainly enough that a reviewer can reject it. Carry its constraint (see below). |
| `@story-F<n>.<m>` on a `Rule:` | The IDs of the stories the rule comes from — this feature's stories only. Every rule carries at least one. Every story of the feature appears on some rule; the build refuses a retired or unknown ID, and an uncovered story. |
| `Scenario:` | One concrete illustration of its rule. Name the distinguishing circumstance, not the steps. |
| `@negative` | On any scenario whose outcome is the system **refusing, rejecting, or limiting**. See below for where it goes. |
| Steps | `Given` present-tense state · `When` the one action · `Then` the observable outcome. |

### Regenerating over an existing set

Story IDs are permanent by contract, so a tag stays correct across
regeneration. Read what is already in `specs/validation/acceptance/` before writing.

- **Keep the file named after its feature.** A renamed feature renames its
  file (the ID stays); a retired feature's file goes. A file that is not named
  `F<n>-<slug>.feature` is from before features — remove it once every rule it
  held lives in a feature's file.
- **Keep rules that still hold, and their wording.** Rewrite only what the
  requirement actually changed.
- **Each rule lives in exactly one file**, the file of the feature whose
  stories it comes from. A product-wide requirement has no file of its own:
  where it shapes a feature's stories, state it as that feature's rule, tagged
  with that feature's stories.

## Authoring discipline

- **Business language.** Words the PRD and the prototype's screens use, for
  things the product has. Never a CSS selector, an element id, a route, an HTTP
  verb, a table name, or a component name.
- **Real data, always.** `"Bridge Cafe"`, `"12:30"`, `"3 items"` — never "a
  valid restaurant", "some time", "appropriate input". A vague step is one an
  agent will satisfy any way it likes, and the check becomes meaningless.
  Concrete data is what makes a scenario falsifiable.
- **Name the actor.** Draw personas from the PRD's Actors section and give them
  names — `Olivia the organizer`, `Dan the diner`; where the prototype's
  records name the people, use those names. Never `I`: it hides which
  actor is acting and falls apart the moment two of them appear.
- **One scenario, one behaviour.** One `When`. A second `When` after a `Then` is
  a second scenario. Split any "X and Y" into two.
- **A second scenario under a rule must earn its place.** It has to show
  something the first does not — a different outcome, a boundary, a refusal.
  "Olivia extends the cutoff" followed by "Olivia shortens the cutoff" is one
  behaviour written twice; the second buys nothing and costs a run.
- **Brief.** Most scenarios are five steps or fewer. Past that you are usually
  describing a procedure instead of illustrating a rule.
- **Essential only.** Every step must contribute to illustrating *this* rule.
  A password in a scenario about balances is noise.
- **Each scenario stands alone.** Its `Given` steps establish everything it
  needs; it never depends on a scenario above it having run. Note that a
  `Background:` re-runs before every scenario, so it saves writing, not work —
  use it only for state the reader genuinely needs in order to read the file.
- **Own what you assert about.** The app under test is deployed and keeps its
  data, so state a scenario did not create belongs to someone else. Where the
  product has a container — a round, a board, a list — the `Given` creates one
  and every later step stays inside it: `Given Olivia has opened a new round`,
  then assert on *that* round. Owning the container is what makes "stands
  alone" true against a shared database, and it is why nothing has to be reset.
- **Say the state, not the steps that reach it.** `Given a round is open for
  "Bridge Cafe"`, not four steps that open one.
- **Data that gets created needs to survive a second run.** Owning a container
  keeps scenarios apart from each other; it does not keep this run apart from the
  last one. When a scenario creates something whose name must be unique, say so
  in the step — `When Olivia opens a round for a restaurant named uniquely for
  this run` — rather than pinning a literal that collides the next time.

## Cover the refusals, and mark them

The single most common defect in generated acceptance specs is that everything
is a happy path. A rule that only says what works has not been specified: the
interesting half of `Only one round may be open at a time` is the second round
being refused.

For every rule, ask what it forbids, what it limits, who it shuts out — a
capability the requirement puts behind sign-in has a case for someone who is not
signed in — and what it does when the input is wrong. Write those scenarios — but
only where the requirement actually says so. A rule that genuinely refuses
nothing (`the opener sees the consolidated order`) gets no refusal invented for
it; inventing one is a worse defect than omitting it.

**Assert what the refusal DOES, not how the user hears about it.** A `Then`
written as "is told X" presumes the product answers a rejected action with a
message. Plenty of products refuse by not offering the action at all — the
control is absent or disabled — and then no one is ever *told* anything, so the
scenario cannot be satisfied however correct the product is.

That wording also fails the test this skill already applies to steps: if the
sentence would have to change when the implementation changes, it contains
implementation. And a message is not what the rule guarantees. `A bought item
cannot be edited` guarantees the item does not change; whether a toast appears
is a separate product decision, and an unstated one is not yours to invent.

| Instead of | Write |
|---|---|
| `Then he is told "Milk" is already on the list` | `Then the list still has exactly one item` |
| `Then she is told the item name cannot be empty` | `Then no item is added to the list` |
| `Then he is told a bought item cannot be edited` | `Then the quantity of "Milk" is unchanged` |

Both shapes of product satisfy the right-hand column, and both are still
refuted by one that actually allows the action — which is the whole job.

Beware quoting a value inside a "told" step: `is told "milk" is already on the
list` silently demands the product echo the user's own casing back, which
almost none do. State the effect and the quoting problem disappears.

**Count the whole set, not the part that matches what you expected.** An effect
assertion has to be written so the defect it guards against cannot slip past
its own filter. `the list still has exactly one item named "Milk"` reads right
and is vacuous: if the product wrongly accepted `" milk "`, the list now holds
a `"Milk"` AND a `" milk "`, and a count filtered to `"Milk"` is still one — so
the scenario passes on exactly the bug it exists to catch. Count the whole of
the container the scenario owns — `the list still has exactly one item` — which
is only sound because the `Given` created that list.

Where the product has no container to own, count the *change* rather than the
total: `Then one more order is on the round than before`. A wrongly accepted
duplicate makes the change two and the scenario still fails, where a filtered
count would have passed.

The same trap waits wherever a refusal is asserted by looking only at what you
expected to see. Ask what the product would do if the rule were missing, and
make sure the assertion would notice it.

**Where `@negative` goes.** Tags inherit down `Feature` → `Rule` → `Scenario`,
so put it at the level where it is true:

- The rule *permits* something and one scenario shows a refusal → tag **that
  scenario**.
- The rule is *itself* a prohibition (`A teammate cannot change another
  teammate's item`) → tag the **`Rule:`**, and every scenario under it inherits.

Judge each scenario by its own outcome, not by how its rule is worded. A
scenario whose `Then` is "she is told a round is already open" is a refusal and
carries the tag — even when it sits under a rule phrased as a permission. That
one is the easiest to miss.

**State a rule with its constraint, not as a bare permission.** When the
requirement scopes a capability to one actor or one window, that scope IS the
rule, and phrasing it away loses the refusal:

- `Only the opener may close her round early` — the limit is visible, and the
  scenario where someone else tries writes itself.
- `The opener may close her round early` — the same requirement with the limit
  phrased out. Nothing prompts the refusal, and it goes unwritten.

A story that says "As the **opener**, I want to close my round" is scoping the
capability to the opener. Say so in the rule.

## Report ambiguity, don't fill it in

When the requirement is silent or ambiguous, do **not** invent a rule to cover
the gap. Write what the requirement supports, and **list the ambiguities and
any assumptions in your reply to the user** — never in the `.feature` files. An
open question belongs in the conversation; the files stay on-shape.

## Do not

- Do not read the design beyond the prototypes, or any source code.
- Do not write step definitions, test code, or a runner — this skill produces
  `.feature` files and nothing else.
- Do not invent scope, screens, endpoints, or features the requirement lacks.
- Do not put prose, comments, or notes outside the Gherkin structure.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.