dataset-generate — synthetic, business-coherent test data
qaia-core only ever produces small inline examples inside a scenario — test data is a separate
concern, deliberately kept out of the generator so a scenario stays readable and the data stays
reusable. This skill is the separate producer of standalone, reusable datasets: entity collections and
scenario-oriented "cases" rich and coherent enough that qaia-playwright:automate's generated
tests can seed real state from them instead of inventing a literal per test.
Prerequisites
- The source US (
.qaia/state/<US-ID>/00-source.md/01-extraction.md, or an equivalent gold-set-style file) — the acceptance criteria are what the dataset's entities, fields and boundaries are derived from. - Ideally the design/test book (
.qaia/state/<US-ID>/03-design.md,.featurefiles under.qaia/testbooks/<US-ID>/) so cases can cite the actual derived condition/scenario IDs. If only the US is available, generate from its acceptance criteria alone and say so — do not block on a test book that does not exist yet.
Steps
Model the entities first. Read the acceptance criteria and extract the nouns and relationships they imply (reference records, actors, transactional records, the foreign keys between them). Write the entity list and its relationships down before generating a single row — a dataset invented row-by-row with no schema is how referential integrity breaks silently.
Business-coherent values, not random noise. For each field, choose a domain (units, ranges, distributions) a domain reviewer would recognize as plausible. This is what "riche et cohérent métier" means in practice: a shopper's loyalty tier and their spend history agree, a patient's age and the drug's age floor are both present and comparable, a cumulative total is never contradicted by the rows that should sum to it.
Cover the boundaries the AC demands, mirroring
istqb-design's EP/BVA techniques (equivalence partitioning + boundary value analysis): one or more concrete rows per condition, not just happy-path rows. Tag every case with the AC(s) and condition it targets (coversAC, and acoversCondition/scenario ID when a test book exists) so a generated test can request "the row for AC3's upper boundary" instead of grep-hunting the fixture.Enforce cross-entity coherence, and check it, don't just claim it. Foreign keys must resolve, computed fields must agree with the raw rows they derive from (a cumulative total equals the sum of the intake/line rows it summarizes), and no two facts about the same entity contradict each other. Recompute totals; do not eyeball them.
Anti-fabrication discipline applied to data, not just scenarios — honest recall beats fabricated recall: never let an invented value read as a sourced one. Three situations:
- The US states a threshold/rule — use it, cite the AC.
- The US is silent on a concrete value the fixture still needs (an exact mg threshold,
a tier spending cutoff's exact rounding) — invent a plausible one for fixture purposes,
but mark the field
synthetic: truewith a short note, and never let it read as a sourced clinical/legal/business fact. - When the US is internally ambiguous (inclusive/exclusive boundary wording, rolling vs.
calendar window, and similar), do not silently pick a side: record the interpretation
chosen for the fixture as a named, citable assumption (
_meta.assumptions[],ASM-n) and tag every case it touches (assumptionRefs). Where the ambiguity is genuinely a fork with no defensible default, build a case that exposes the fork instead of resolving it: itsexpectedResult.statusis literally"[open]", listing both interpretations, mirroring the[open]/[assumption]disciplineistqb-design/need-understandingalready apply to scenarios — the honest recall ceiling applies to invented data exactly as it does to invented test logic.
⚠ VALIDATION — when the fixture rests on an invented value or a chosen interpretation, surface it with this callout, verbatim, rather than burying it in
_meta:If you own the product rather than the tests, read this. You do not need the rest of this page.
- What you're being asked: to build test data we needed concrete values the specification never gave — an exact threshold, a cut-off, whether a limit counts as "reached" or "exceeded". We made them up, plausibly, and listed them below. You are being asked whether any of them is wrong.
- Why it matters: these values are what the tests are built around. If our invented threshold sits on the wrong side of your real one, the tests will pass while checking the wrong boundary — and a boundary is exactly where defects live. This is the one kind of error that leaves no trace: everything is green and nothing was verified.
- If you don't answer: the fixture ships with those values marked as invented
(
synthetic), so nobody later mistakes them for your rules. Where the choice was a genuine fork with no safe default, the case is left deliberately unresolved instead of being decided for you.
No real data is ever used here — every person, address and identifier is fabricated on purpose, even when the real thing would be easier to obtain.
No real data, no PII, ever. Every person-like entity (patient, physician, customer, employee, ...) gets a clearly synthetic identity: a name pattern that signals fixture data at a glance (e.g.
<first name> Sample-NN), an@example.invalid-style email (RFC 2606 reserved TLD — guaranteed to never resolve to a real domain), no real-world-resolvable identifier, and asynthetic: trueflag. Never reuse a real drug/company/product name, a real address, or any value that could be mistaken for a real record — a reviewer must be able to tell at a glance this is fixture data, not exported real data. This holds with extra weight in health and other regulated domains, and applies identically to every domain the skill is used in outside them.Emit the dataset. Primary format: one JSON file (
<US-ID>-dataset.json) with:_meta: US-ID, generation date, a plain-language non-fabrication disclaimer, and theassumptions[]list from step 5;- one array per entity (referential integrity per step 4);
- a
cases[]array of scenario-oriented rows —id,description, references to the entities/rows involved,coversAC/coversCondition,assumptionRefs, and anexpectedResult(status + the rule/condition it exercises, or"[open]"per step 5). This last array is what a generated test actually iterates over. CSV export of a single entity array is optional, on request, for tooling that needs it. Never emit a database dump or a real-service payload shape unless the US actually specifies one — invented "realism" beyond what's needed is its own risk (a fixture that looks more authoritative than it is).
Document the injection pattern — don't ship code. Show how
qaia-playwright:automate's POM-as-fixtures convention (page objects exposed as Playwright fixtures) consumes the file: atestDatafixture infixtures.jsthat reads and parses the JSON once and exposes it to every test — the sametest.extend()mechanismexamples/medibook/tests/fixtures.jsalready uses forloginPage/bookingPage/patient. This is documentation, or an in-session snippet the generating skill or the user materializes when wiring tests — the plugin itself ships no runtime code: skills stay 100% Markdown, and executable code is generated and run in-session, never distributed, so installing a plugin can never introduce a supply-chain payload. See../../fixture/fixtures.jsfor a worked, executed example.Traceability. Write (or update)
dataset-map.md: one row percases[]entry — case ID, AC(s), condition/scenario ID if a test book exists, expected outcome, assumption refs. This is the dataset's own coverage matrix, mirroring the AC → scenario → status patterntestbook-generatealready uses, so a reviewer audits the data the same way they audit the tests.Optional manifest merge (shared output contract,
../../OUTPUT-CONTRACT.md). If.qaia/reports/<US-ID>/manifest.jsonalready exists, merge intoproducers[]and add anartifacts[]entry (kind: "dataset",format: "json",path), pluskind: "dataset-map"if you emitted a map. Two rules bound this, both learned the hard way: thepathis relative to that run's report directory and may not climb out of it, and you only ever merge into the manifest of the run you are part of — declaring your output inside someone else's manifest makes their run claim work it never did. Append-only, never touching another producer's section (design/execution/gate/statusstay byte-for-byte untouched, contract rule 2). If no manifest exists yet this is skipped, not a blocker for delivering the dataset itself.
Guardrails
- Never real data, never PII, ever. Synthetic identity markers on every person-like row
(step 6). A dataset that could be mistaken for exported real records is a defect, not a
feature — this is stricter than, and in addition to, the shared contract's PII-masking rule
for ingested source content (
https://github.com/QAIA-Project/QAIA/blob/main/plugins/qaia-core/skills/README.mdrule 5): here nothing real ever enters in the first place. - Never fabricate a domain fact as if sourced when the US is silent — invent, but flag
(
synthetic: true,_meta.assumptions[], or an explicit[open]case). Honest recall beats fabricated recall, applied to data exactly as to scenarios. - No auto-executed code ships with the plugin. The fixture-wiring pattern in step 8 is a documented convention the user/skill materializes in their own test repo, never a script this plugin runs on its own.
- Scoped writes. This skill's only outputs are the dataset file(s),
dataset-map.md, and optionally the manifest'sproducers[]/artifacts[]— it never edits.featurefiles, checkpoints, or another producer's manifest section. - Portable. Plain JSON/CSV/Markdown, no network, no API key, no runtime dependency to
generate the data. The optional Playwright fixture wiring shown in step 8 is consumed
downstream, by whatever test suite the user or
qaia-playwright:automatebuilds — it is not a dependency of this skill.
Worked example
../../fixture/ builds and validates a real dataset for eval/gold-set/US-002-dosage-validation.md
(prescription dosage validation, health domain — chosen because no example dataset exists for it
elsewhere in the repo) — 4 synthetic drugs, 3 synthetic physicians, 11 synthetic patients, 20
intake records and 17 boundary-focused cases covering all 8 ACs, including one case
(C-015) that deliberately surfaces a genuine AC ambiguity as "[open]" rather than resolving
it. See ../../fixture/VALIDATION.md for what was actually run (a real npx playwright test
pass over a fixture-injection spec, not a narrated claim).