openapi-ingest — the specification as requirement
us-ingest takes a user story written for humans. This skill takes the other common entry point:
a formal, machine-readable specification. Same chain afterwards — istqb-design,
prioritize, testbook-generate all work unchanged, because the output shape is the same.
Why this entry point earns its place
The external campaign kept in eval/external-application-2026-08-08/ found two real
defects in a 75,000-star project by generating from its documentation and never from its code.
The defect that mattered most was a one-character mismatch between what the documentation promised
and what the implementation read. A suite written by looking at the code cannot find that class of
defect: it copies the mistake.
Most real APIs carry something better than prose — a schema. Enums, required, minLength,
maximum, pattern, declared response codes: each is a promise stated precisely enough that a
test condition falls out of it without interpretation. That is the opposite of the ambiguity tax
paid on prose, and it is why this skill exists.
The rule that makes it honest
A specification is a promise, not a fact. It describes what the API is supposed to do; it is
routinely out of date with what it does. Every condition derived here is of the form "the spec
promises X" — never "the API does X". Confirming or refuting is contract-probe's job, and
mixing the two is how a specification becomes a rubber stamp.
What is derived, and from what
| Source in the spec |
Derived |
ISTQB technique |
enum |
one valid partition per value, plus one value outside the enum |
equivalence partitioning |
required (schema or parameter) |
one refusal path per required field, omitted in turn |
equivalence partitioning, invalid class |
minimum / maximum / minLength / maxLength |
the bound, and just outside it |
boundary value analysis |
pattern, format |
one conforming, one not |
equivalence partitioning |
declared responses codes |
one condition per declared code — including the error codes |
specification-based |
security on an operation |
absent credential, invalid credential, insufficient scope |
specification-based |
type (integer, boolean…) |
one wrong-type case |
equivalence partitioning, invalid class |
prose in description |
never a condition — an open question (see below) |
|
The four contradictions to look for, every time
A specification is written by hand and drifts against itself. These four are common enough to be
worth a systematic pass, and each is an ambiguity to raise, never a defect to file:
- A required parameter that carries a default. If it is required, the default is unreachable;
if the default applies, it is not required. The spec does not say which wins.
- The same field constrained in one place and not in another. A property with an
enum in a
schema, and a same-named query parameter typed only as string.
security declared with no failure code declared. An operation that requires a credential
but never states what happens without one leaves the whole refusal path unspecified — and the
refusal path is where the interesting defects live.
- A constraint stated in prose but absent from the schema.
description: "IDs above 1000 will generate errors" with no maximum: 1000. Machines read the schema, so the constraint is not
enforced by anything.
Each becomes # open: Qn in the test book, resolved by a human, exactly as with an ambiguous user
story. Do not guess which reading is right — a specification-derived book that quietly picks
one interpretation is worse than a prose-derived one, because its precision is misleading.
Steps
Freeze the spec. Copy it into the run's sources/ and record its sha256 in
REQUIREMENT-SOURCE.json, at the run root, in this shape — the file is read by tooling,
so its keys are not free:
{
"testbook": "<path to the .feature this source produced>",
"note": "<why this source is frozen>",
"sources": [
{ "label": "<what it is, and when it was fetched>",
"path": "sources/<file>",
"origin": "<the URL it came from>",
"sha256": "<hex>" }
]
}
This shape is spelled out since 2026-08-11. It was previously left implicit, and an agent
following this step faithfully invented its own keys — the schema existed only inside a
checker's source code, which an installer never sees. A step that names an artifact owes its
form. A spec is a URL that changes without warning; a test book whose
requirement cannot be re-read at the version it was generated from cannot be argued about later.
check_requirement_drift.py then fails the day it moves.
Inventory. Count paths, operations, schemas, declared codes. This is what the coverage
matrix will be measured against.
Resolve $ref. A condition that depends on an unresolved reference is not a condition.
Derive, per the table above, one operation at a time.
Run the contradiction pass, per the four above.
Emit the same structure us-ingest emits, so istqb-design and the rest need no change —
plus the two things only this entry point can supply:
[level: api] on every derived condition. A clause of a service contract is observable
in HTTP by construction, so the level is not a judgment call here the way it is on prose
(ADR 0008). istqb-design may still raise a condition to e2e — a spec clause whose
real promise is what the user sees — but it does so explicitly and with a reason, rather than
inheriting a blank.
- The clause reference, not just the operation. Each condition cites
<operationId> · <spec element> — requestBody.required, responses.404, security,
parameters.limit.maximum. testbook-generate carries it into the scenario's # contract:
comment (testbook-generate/references/api-steps.md), which is what makes the chain spec
clause → condition → scenario → test → result traceable end to end. Citing only the
operation stops one link short: it says where the promise lives, never which promise.
What this skill must refuse
- Turning prose in a
description into an assertion. It is a hint written for a human reader.
It becomes an open question, not a test.
- Deriving from an operation whose
$ref does not resolve.
- Probing the live server. This skill reads a document. It never sends a request — and never to
a host the user does not own or has not explicitly authorised.
- Treating
default: as documentation of behaviour. It documents what the client library
sends, which is not what the server does when the field is absent.
Applied for real
Applied to the Swagger Petstore specification (OpenAPI 3.0.4, version 1.0.27, 13 paths, 19
operations): eval/openapi-ingest-2026-08-08/. The derivation found all four contradiction
classes present in that single spec — including nine operations that declare a security scheme
while no operation in the whole document declares a 401 or a 403.
No request was sent to the Petstore server: it is not ours.
1---2name: openapi-ingest3description: Ingest an OpenAPI or Swagger specification as the requirement source and derive test conditions from it - equivalence partitions from enums, boundaries from schema constraints, refusal paths from required fields and declared error codes, and the contradictions the specification carries. Use when an API has a formal spec instead of a user story, before istqb-design and testbook-generate.4---56# openapi-ingest — the specification as requirement78`us-ingest` takes a user story written for humans. This skill takes the other common entry point:9a **formal, machine-readable specification**. Same chain afterwards — `istqb-design`,10`prioritize`, `testbook-generate` all work unchanged, because the output shape is the same.1112## Why this entry point earns its place1314The external campaign kept in `eval/external-application-2026-08-08/` found two real15defects in a 75,000-star project by generating **from its documentation and never from its code**.16The defect that mattered most was a one-character mismatch between what the documentation promised17and what the implementation read. A suite written by looking at the code cannot find that class of18defect: it copies the mistake.1920Most real APIs carry something better than prose — a schema. Enums, `required`, `minLength`,21`maximum`, `pattern`, declared response codes: each is a promise stated precisely enough that a22test condition falls out of it without interpretation. That is the opposite of the ambiguity tax23paid on prose, and it is why this skill exists.2425## The rule that makes it honest2627**A specification is a promise, not a fact.** It describes what the API is supposed to do; it is28routinely out of date with what it does. Every condition derived here is of the form *"the spec29promises X"* — never *"the API does X"*. Confirming or refuting is `contract-probe`'s job, and30mixing the two is how a specification becomes a rubber stamp.3132## What is derived, and from what3334| Source in the spec | Derived | ISTQB technique |35|---|---|---|36| `enum` | one valid partition per value, **plus one value outside the enum** | equivalence partitioning |37| `required` (schema or parameter) | one refusal path per required field, omitted in turn | equivalence partitioning, invalid class |38| `minimum` / `maximum` / `minLength` / `maxLength` | the bound, and just outside it | boundary value analysis |39| `pattern`, `format` | one conforming, one not | equivalence partitioning |40| declared `responses` codes | one condition per declared code — **including the error codes** | specification-based |41| `security` on an operation | absent credential, invalid credential, insufficient scope | specification-based |42| `type` (integer, boolean…) | one wrong-type case | equivalence partitioning, invalid class |43| prose in `description` | **never a condition** — an open question (see below) | |4445## The four contradictions to look for, every time4647A specification is written by hand and drifts against itself. These four are common enough to be48worth a systematic pass, and each is an **ambiguity to raise**, never a defect to file:49501. **A required parameter that carries a default.** If it is required, the default is unreachable;51 if the default applies, it is not required. The spec does not say which wins.522. **The same field constrained in one place and not in another.** A property with an `enum` in a53 schema, and a same-named query parameter typed only as `string`.543. **`security` declared with no failure code declared.** An operation that requires a credential55 but never states what happens without one leaves the whole refusal path unspecified — and the56 refusal path is where the interesting defects live.574. **A constraint stated in prose but absent from the schema.** `description: "IDs above 1000 will58 generate errors"` with no `maximum: 1000`. Machines read the schema, so the constraint is not59 enforced by anything.6061Each becomes `# open: Qn` in the test book, resolved by a human, exactly as with an ambiguous user62story. **Do not guess which reading is right** — a specification-derived book that quietly picks63one interpretation is worse than a prose-derived one, because its precision is misleading.6465## Steps66671. **Freeze the spec.** Copy it into the run's `sources/` and record its sha256 in68 `REQUIREMENT-SOURCE.json`, **at the run root, in this shape** — the file is read by tooling,69 so its keys are not free:7071 ```json72 {73 "testbook": "<path to the .feature this source produced>",74 "note": "<why this source is frozen>",75 "sources": [76 { "label": "<what it is, and when it was fetched>",77 "path": "sources/<file>",78 "origin": "<the URL it came from>",79 "sha256": "<hex>" }80 ]81 }82 ```8384 *This shape is spelled out since 2026-08-11. It was previously left implicit, and an agent85 following this step faithfully invented its own keys — the schema existed only inside a86 checker's source code, which an installer never sees. A step that names an artifact owes its87 form.* A spec is a URL that changes without warning; a test book whose88 requirement cannot be re-read at the version it was generated from cannot be argued about later.89 `check_requirement_drift.py` then fails the day it moves.902. **Inventory.** Count paths, operations, schemas, declared codes. This is what the coverage91 matrix will be measured against.923. **Resolve `$ref`.** A condition that depends on an unresolved reference is not a condition.934. **Derive**, per the table above, one operation at a time.945. **Run the contradiction pass**, per the four above.956. **Emit** the same structure `us-ingest` emits, so `istqb-design` and the rest need no change —96 plus the two things only this entry point can supply:9798 - **`[level: api]` on every derived condition.** A clause of a service contract is observable99 in HTTP by construction, so the level is not a judgment call here the way it is on prose100 ([ADR 0008](https://github.com/QAIA-Project/QAIA/blob/main/docs/adr/0008-test-level-is-a-design-property.md)). `istqb-design` may still *raise* a condition to `e2e` — a spec clause whose101 real promise is what the user sees — but it does so explicitly and with a reason, rather than102 inheriting a blank.103 - **The clause reference, not just the operation.** Each condition cites `<operationId> ·104 <spec element>` — `requestBody.required`, `responses.404`, `security`,105 `parameters.limit.maximum`. `testbook-generate` carries it into the scenario's `# contract:`106 comment (`testbook-generate/references/api-steps.md`), which is what makes the chain **spec107 clause → condition → scenario → test → result** traceable end to end. Citing only the108 operation stops one link short: it says *where* the promise lives, never *which* promise.109110## What this skill must refuse111112- **Turning prose in a `description` into an assertion.** It is a hint written for a human reader.113 It becomes an open question, not a test.114- **Deriving from an operation whose `$ref` does not resolve.**115- **Probing the live server.** This skill reads a document. It never sends a request — and never to116 a host the user does not own or has not explicitly authorised.117- **Treating `default:` as documentation of behaviour.** It documents what the *client library*118 sends, which is not what the server does when the field is absent.119120## Applied for real121122Applied to the Swagger Petstore specification (OpenAPI 3.0.4, version 1.0.27, 13 paths, 19123operations): `eval/openapi-ingest-2026-08-08/`. The derivation found **all four contradiction124classes present in that single spec** — including nine operations that declare a security scheme125while **no operation in the whole document declares a 401 or a 403**.126127No request was sent to the Petstore server: it is not ours.