acceptance-harness
Design acceptance scenarios with PASS/FAIL/BLOCKED semantics, collect per-scenario evidence, and inject faults to prove resilience. Use when defining what 'done' means, writing acceptance criteria, or
SKILL.mdskills/engineering/acceptance-harness/SKILL.md
GitHubStandard: name/description/length rules — agentskills.io standard.
Acceptance Harness Skill (body <500 lines)
Full procedure: references/acceptance-harness-procedure.md
Sources: references/SOURCES.md
Evals: references/EVALS.md
Helpers: scripts/ (shipped: check_audit_selectors.py, report_result.py,
make_harness.sh — all offline, deterministic, self-tested).
Cross-links:
check_keyboard.pyandcheck_axe_evidence.pylive in../playwright-a11y-e2e/scripts/(keyboard and axe evidence are E2E concerns, not acceptance-harness concerns). Call them with the relative path above.
When to use this skill (trigger)
- Defining what "done" means for a feature or change.
- Writing scenarios that must produce PASS, FAIL, or BLOCKED.
- Collecting evidence that a requirement is actually met.
- Proving failure paths and recovery (fault injection).
- A reviewer asks "what would break this, and how do we know?"
Disambiguation
- Fires: defining what 'done' means, writing PASS/FAIL/BLOCKED scenarios, fault injection, collecting evidence a requirement is met.
- Does NOT fire for: a unit test (that is a test, not an acceptance gate), a code-review checklist, or a release checklist.
When NOT to use this skill
- Exploratory testing without a defined scenario (use a QA session).
- Pure performance or load testing (use a benchmark harness).
- Scenarios whose PASS criteria are opinions, not observable facts.
- A unit test that asserts one function — that is a unit test, not an acceptance scenario; the harness governs end-to-end behaviour with evidence.
- A "it works on my machine" claim — the harness asks for a runnable pointer, not a story.
- Retesting something that has not changed — re-run only if the evidence pointer is missing or older than the code it points at.
Decision table — scenario outcome
| Requirement type | Outcome | Evidence to attach |
|---|---|---|
| Functional happy path | PASS | test log + screenshot/trace |
| Functional error path | FAIL (expected) | error log + recovery action |
| Non-functional (perf, security) | PASS | metric report (p95, count) |
| Blocked by external dependency | BLOCKED | blocker note + ticket/owner |
| Fault tolerance / recovery | FAIL then PASS | fault injection log |
| Ambiguous / not yet specified | BLOCKED | open question + owner |
Numbered procedure (summary; full recipe in references/acceptance-harness-procedure.md)
- Enumerate every requirement; one scenario per requirement, never two requirements per scenario.
- Write the PASS criteria as an observable assertion, not a feeling.
- Choose the evidence type: log, screenshot, metric, or trace.
- Write the rollback plan: how to undo the scenario's effects.
- Run the scenario; record the outcome (PASS/FAIL/BLOCKED) and the evidence pointer.
- If FAIL, distinguish "system under test failed" from "scenario is wrong"; fix the scenario, not the claim, only if the requirement genuinely changed.
- Inject a fault on a passing scenario; the system must recover and the scenario must return to PASS.
- Commit the scenario file, evidence, and outcome; re-run on every change.
Worked examples
Good (one requirement, one scenario, one evidence type, one rollback):
# scenarios/rls-tenant-isolation.yml
id: rls_tenant_isolation
requirement: "A tenant's rows are invisible to another tenant"
expected: PASS
evidence: logs/rls-tenant-isolation.log
rollback: "revert migration r3-07; no data written"
assertion: "SELECT count(*) FROM orders WHERE tenant_id = 'other' = 0"
Bad (scenario with no discriminating power — always passes):
id: ui_renders
requirement: "The page renders"
expected: PASS
assertion: "page loads" # not observable; every run passes
Bad (two requirements in one scenario — hides which one failed):
id: mixed
requirement: "Create order AND send email"
# if email fails, is it the create or the send that broke? Unknown.
Pitfalls from research
- Scenarios without a concrete PASS criterion drift into opinion; every scenario needs a checkable assertion.
- Evidence not attached to a scenario is unverifiable; a pointer must open in under 60 seconds.
- Fault injection that crashes the system without recovery is destructive, not resilience proof: require recovery and a return to PASS.
- A scenario that always passes is not a scenario; it has no discriminating power.
- Reusing one scenario for two requirements hides which requirement failed.
Near-miss triggers (act before the incident)
- "I'll just eyeball the result" — write the assertion first, then look.
- "This scenario is probably fine" — run the fault-injection variant.
- "The evidence is in my head" — put it in a file; a pointer must open in <60s.
- "Let's bundle three checks into one scenario" — split them; one scenario = one requirement.
Verification checklist (all must pass before you finish)
- 1 scenario = 1 requirement = 1 expected outcome = 1 evidence type = 1 rollback plan.
- Every PASS has a runnable evidence pointer.
- Every FAIL names the failing assertion and the recovery step.
- Every BLOCKED names an owner and a concrete next question.
- At least one fault-injection scenario proves recovery.
Related skills
- honest-status — PASS/FAIL/BLOCKED outcomes are status buckets.
- problem-json-contracts — a failure in a scenario is a problem-detail payload.
- module-contract-first — a scenario's criteria are module contracts: enumerate them, do not hand-wave the API surface.
- long-test-discipline — a scenario that takes minutes is a long test; schedule it in the foreground with a recorded seed.
- shared-tree-git — the scenario file, evidence and outcome are committed with a task-id prefix.