"Design a new Arazzo setup recipe that provisions test fixtures via real OpenAPI operations so scenarios start from a known state. Use when a scenario needs precondition entities no existing recipe provides; walks composition (extends chains, depth limits, output contracts, $system actor) and validates with zero forbidden extensions."
Install
npx skillscat add specfuse/authoring/design-recipe Install via the SkillsCat registry.
Design a new Arazzo setup recipe for scenario preconditions. Recipes are infrastructure -- they provision test fixtures using real OpenAPI operations so that scenarios start from a known state.
Enforces: .specfuse/authoring/handbooks/Arazzo_Handbook.md
Audience: architects and lead developers, not PMs.
Before writing any files, read and internalize the authoritative rules:
- Read
.specfuse/authoring/handbooks/Arazzo_Handbook.md-- Section 7 (setup recipes: composition, extends chain, scope, discriminator, implicit $system actor) - Read
.specfuse/authoring/samples/recipe-samples.yaml-- canonical recipe template (covers the full foundational chain and a domain-specific example) - Note the report format of the
scenario-validatorsubagent (provided by the specfuse-authoring plugin) -- you will spawn it to validate
Then scan the existing recipe files to understand what is already available:
api/specs/v1/scenarios/setup-recipes/foundational/*.recipe.yamlapi/specs/v1/scenarios/setup-recipes/domain-specific/**/*.recipe.yaml
What this command produces
A single Arazzo 1.0.1 recipe file (.recipe.yaml) placed in:
api/specs/v1/scenarios/setup-recipes/foundational/for domain-agnostic foundational recipesapi/specs/v1/scenarios/setup-recipes/domain-specific/{domain}/for domain-specific recipes
Recipes are Arazzo workflows with the x-recipe extension. They are NOT scenarios -- the following extensions are forbidden on recipes:
x-actors-- recipes run as the implicit$systemactor mapped toAdminx-as-- every recipe step runs as$system(no actor binding on steps)x-setup-- recipes compose viaextends, not$setupx-mcp-- recipes are infrastructure, not user-facing toolsx-ui-- no user-facing interactions in fixture setup
Input clarification
Ask the user these questions before designing:
Purpose. What precondition state does the recipe establish?
"What entities need to exist before the scenario starts? For example: 'a tenant with 3 customers and a fulfilled order.'"
Fixture type. What is the recipe for?
Purpose Behavior test-fixtureFast, minimal, disposable. Created and torn down per scenario. Most common. demo-fixtureRich, realistic, persistent. For demos, UAT, stakeholder walkthroughs. NOT cleaned up after execution. dev-fixtureLike demo-fixture but refreshable. For local dev seeding. Domain. Which domain does this recipe primarily serve? This determines
x-domainand file placement.Composition. Present the existing recipe tree (see step 1 below) and ask: does this recipe extend an existing one?
Output contract. What entity IDs and values should the recipe expose for scenarios to reference via
$setup.outputs.X?"What outputs do scenarios need from this recipe? Think about the actors and resources the scenario will reference -- each needs a distinct output name."
This is where mistakes happen. If a scenario needs
requesterIdandapproverId(two different customers), the recipe must create both and expose both with distinct output names. Ask about this explicitly.
Design process
Step 1: Map to existing chain
Build and present the current recipe composition tree by scanning existing recipe files. Show the extends relationships:
minimal-tenant (foundational, tenant)
outputs: tenantId, adminUserId
└── minimal-customer (foundational, customer)
outputs: + customerId, managerUserId
└── basic-orders (foundational, order)
outputs: + orderId1, orderId2, requesterId, approverId,
catalogItemId, customerCatalogId
└── basic-fulfilled-orders (order)
outputs: + fulfillmentId, templateId, lineId1,
lineId2, lineIdIdentify the deepest existing recipe that provides a subset of the needed state. Propose extending it:
"The recipe 'basic-orders' already provides a tenant, customer, manager, and two orders. Your recipe needs fulfilled orders on top of that. I recommend extending 'basic-orders' (depth 3). Does that work, or should we extend 'basic-fulfilled-orders' (depth 4) instead?"
If extending would push the chain past depth 6 (the maximum), warn:
"Extending '{recipe}' would create a chain of depth {N}, exceeding the maximum of 6. Consider restructuring: can any intermediate recipe be flattened or bypassed?"
If the recipe needs entities from multiple unrelated chains, it must extend the chain that covers the most and create the rest from scratch.
Step 2: Identify additional operations
List the OpenAPI operations needed to create the state beyond what the parent recipe provides.
Glob
api/specs/v1/domains/{domain}/operations/*.yamland present the available write operations (POST endpoints).If the recipe serves a different domain than its parent, also scan that domain's operations.
Confirm each operationId exists. If an operation is missing, stop:
"The recipe needs to create a '{entity}' but no 'create{Entity}' operation exists in OpenAPI. Design the endpoint first."
Present the operation list and confirm:
"To build on top of '{parentRecipe}', these additional operations are needed:
createOrderTemplate-- create the order templategenerateFulfillment-- generate the fulfillment from the templateaddOrderLine-- add lines for each itemConfirm or adjust."
Step 3: Design steps
For each operation identified in step 2, design a recipe step:
stepId(kebab-case, descriptive)operationId(confirmed from step 2)parametersandrequestBodyusing:$steps.parent-setup.outputs.Xfor values from the parent recipe$steps.{stepId}.outputs.Xfor values from earlier steps in this recipe$inputs.Xfor parameterized values with defaults- Date tokens (
@today+3d) for date fields
successCriteriamatching OpenAPI conventions (POST -> 201)outputscapturing the entity IDs the recipe needs to expose
No x-as on any step. Recipe steps run as the implicit $system actor.
Present each step for confirmation. Keep them minimal -- recipes create only what scenarios need, nothing more.
Workflow inputs: identify values that should be parameterizable (dates, names, counts). Define them as workflow inputs with sensible defaults. Callers (child recipes or scenarios) can override these.
Step 4: Define output contract
The output contract is the recipe's public API. Scenarios reference these via $setup.outputs.X.
List all outputs the recipe will expose (its own + inherited from the parent chain).
Check for namespace collisions. If any output name in this recipe matches a parent output name, it is a validator error. Rename one of them.
Use descriptive names:
requesterIdnotid1,fulfillmentIdnotplanId.Present the complete merged output namespace:
"Scenarios using this recipe will have access to:
- From
minimal-tenant: tenantId, adminUserId - From
minimal-customer: customerId, managerUserId - From
basic-orders: orderId1, orderId2, requesterId, approverId, catalogItemId, customerCatalogId - From this recipe (new): fulfillmentId, templateId, lineId1, lineId2
Any name collisions? Any missing outputs?"
- From
Output stability rule: adding outputs later is safe. Removing or renaming outputs is a breaking change that requires updating all dependent scenarios and child recipes.
Step 5: Generate and validate
Generate the complete recipe YAML file including:
arazzo: "1.0.1"headerinfowith a descriptive title andversion: "1.0.0"sourceDescriptionswith the correct relative path toopenapi.yamlx-version: { current: 1, status: draft }x-domainmatching the confirmed domainx-recipewithpurpose,extends(if applicable),idempotent,estimatedDurationMs,scopeworkflowswith a singlesetupworkflow containing steps and outputs
Determine the
sourceDescriptions[].urlrelative path based on file location:- Foundational:
../../../openapi.yaml(fromscenarios/setup-recipes/foundational/) - Domain-specific:
../../../../openapi.yaml(fromscenarios/setup-recipes/domain-specific/{domain}/)
- Foundational:
Write the file to disk.
Spawn the
scenario-validatorsubagent (provided by the specfuse-authoring plugin) to validate, following its pipeline:./scripts/specfuse/validate-arazzo-spectral.sh(Spectral lint)./scripts/specfuse/validate-arazzo.sh(structural + cross-spec checks)
Process the validation report:
- Auto-fix mechanical issues (missing defaults, casing).
- Surface any cross-spec errors (unresolved operationIds, chain depth, output collisions).
- Re-validate until zero errors.
Report the file path and the complete output contract.
Composition rules
These are validator-enforced. The command must check them before generating:
| Rule | Constraint |
|---|---|
| Single inheritance | extends is an ordered array expressing a chain, not multiple parents |
| Max depth 6 | Count the full chain from root to this recipe. Depth > 6 = error. |
| No cycles | A recipe cannot extend itself or form a circular chain (A -> B -> A) |
| No output collisions | If a parent and child both declare an output with the same name, the validator errors. The flat namespace keeps $setup.outputs.X unambiguous. |
| No scenario extensions | Recipes must NOT have x-actors, x-mcp, x-setup, or x-ui |
Implicit $system actor |
All steps run as $system mapped to Admin. No x-as on steps. |
$setup forbidden |
Recipes cannot reference $setup. Use $steps.parent-setup.outputs.X for parent outputs. |
File placement rules
| Type | Location | Example |
|---|---|---|
| Foundational (domain-agnostic) | api/specs/v1/scenarios/setup-recipes/foundational/ |
minimal-tenant.recipe.yaml |
| Domain-specific | api/specs/v1/scenarios/setup-recipes/domain-specific/{domain}/ |
basic-fulfilled-orders.recipe.yaml |
File naming: {descriptive-name}.recipe.yaml (kebab-case). The .recipe.yaml suffix is mandatory -- it is the file-level discriminator that tooling uses to classify files.
Foundational vs domain-specific: a recipe is foundational if it creates entities used by many domains (tenants, customers, base users). It is domain-specific if it creates entities tied to a single domain (orders, refunds, compliance items).
Post-creation checklist
Verify all of these before reporting completion:
- Recipe file written to the correct directory (foundational or domain-specific)
- All
operationIdvalues confirmed against OpenAPI -
x-recipefields set:purpose,extends(if applicable),idempotent,estimatedDurationMs,scope -
x-versionpresent withcurrent: 1andstatus: draft -
x-domainpresent and matches the recipe's primary domain -
extendschain valid: depth <= 6, acyclic, parent recipe exists - No output namespace collisions with parent outputs
- Output contract clearly documented (both new outputs and inherited ones noted)
-
sourceDescriptions[].urlis a correct relative path toopenapi.yaml - No forbidden extensions present (
x-actors,x-mcp,x-setup,x-ui,x-as) - No hardcoded dates -- date tokens used in inputs and step payloads
- No language-specific references
- Validation passes with zero errors
Tips for good recipes
- Minimal by design. Create only what the scenario needs. A recipe with 20 steps creating entities "just in case" is over-engineered. If a scenario needs more, create a child recipe that extends this one.
- Extend, do not duplicate. If
basic-ordersalready creates a tenant, customer, and orders, do not repeat those steps. Extend it and add only the new entities. - Output names matter. Scenarios bind actors and parameters to recipe outputs.
requesterIdis clear;id1is not. Use descriptive names that tell the scenario author what the value represents. idempotent: trueis the default. Test fixtures should be safe to re-run. If the recipe creates entities with unique constraints (e.g., unique tenant codes), use parameterized inputs with defaults that the Specfuse runtime can make unique per run.- Estimate duration realistically.
estimatedDurationMsis informational (for test scheduling). A recipe that calls 5 API endpoints with typical latency should estimate 1000-2000ms, not 100ms. - Recipes do not mint tokens. Token provisioning is the Specfuse runtime's responsibility. Recipes create entities (users, customers); the runtime maps scenario actors to those entities and mints auth tokens. Never call auth endpoints in a recipe.
- Date tokens keep recipes time-independent. Use
@today+3dinstead of"2026-05-01". Date tokens resolve only in workflowinputsdefaults and steprequestBody.payload/parametersvalues.