Scenario Recipe Schema
This page documents the canonical upload contract for scenario recipes. It is language-agnostic: the schema is described as JSON with per-field expectations. The source of truth lives in packages/types/src/schemas/scenarios.ts (ScenarioRecipesFileSchema).
The file is posted as the JSON body of:
POST /v1/setup/setups/:setupId/scenario-recipe-versionsTop-level shape
{ "version": 1, "source": { "discoverPath": "string", "scenariosPath": "string" }, "validationMode": "sdk-check" | "endpoint-lifecycle", "recipes": [ /* at least one ScenarioRecipe */ ]}| Field | Type | Required | Notes |
|---|---|---|---|
version | integer, must equal 1 | yes | Contract version. Currently only 1 is accepted. Not a string. |
source | object | yes | Provenance pointers. Additional keys are preserved. |
source.discoverPath | string | yes | Path (relative to the application repo) to the discovery output, e.g. autonoma/discover.json. Required - omitting it causes Zod to fail with expected string, received undefined. |
source.scenariosPath | string | yes | Path to the human-readable scenarios document, e.g. autonoma/scenarios.md. |
validationMode | "sdk-check" | "endpoint-lifecycle" | yes | How Autonoma validated the recipes before upload. sdk-check = checkScenario/checkAllScenarios. endpoint-lifecycle = real HTTP up/down. |
recipes | array, minimum length 1 | yes | One entry per scenario. See below. |
ScenarioRecipe (one entry in recipes[])
{ "name": "string", "description": "string", "create": { /* arbitrary model graph, see below */ }, "variables": { /* optional, see below */ }, "validation": { "status": "validated", "method": "checkScenario" | "checkAllScenarios" | "endpoint-up-down", "phase": "ok", "up_ms": 0, "down_ms": 0 }}| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Stable identifier. Must match the scenario name used in the LLM-facing docs. |
description | string | yes | Human-readable summary of the scenario state. |
create | object | yes | The model graph passed to the SDK’s createScenario / up flow. Keys are model names; values are arrays or objects of seeded rows. Extra keys are preserved. |
variables | object (map of name → definition) | no | Per-recipe dynamic values. See Variable definitions below. |
validation | object | yes | Proof that the recipe was validated. All fields must be present. |
validation.status | literal string "validated" | yes | |
validation.method | one of "checkScenario", "checkAllScenarios", "endpoint-up-down" | yes | Which validator produced this result. |
validation.phase | literal string "ok" | yes | |
validation.up_ms | non-negative integer | no | Milliseconds the up phase took. |
validation.down_ms | non-negative integer | no | Milliseconds the down phase took. |
Variable definitions
variables is a map from variable name to a tagged union discriminated by the strategy field. Exactly one of the three shapes below is valid per entry. Unknown strategy values are rejected.
literal
Emits a fixed scalar on every run.
{ "strategy": "literal", "value": "admin@example.com"}| Field | Type | Required | Notes |
|---|---|---|---|
strategy | literal "literal" | yes | |
value | string | number | boolean | null | yes | Any JSON scalar. Objects and arrays are not allowed. |
derived
Derives a deterministic value from the test run ID (so every invocation of the same test gets the same value, but different runs get different values).
{ "strategy": "derived", "source": "testRunId", "format": "user-{shortId}@example.com"}| Field | Type | Required | Notes |
|---|---|---|---|
strategy | literal "derived" | yes | |
source | literal "testRunId" | yes | Only testRunId is supported today. |
format | string | yes | Template. The token {shortId} is replaced with a short hash of the run ID. |
faker
Generates a fresh random value per run using Faker.
{ "strategy": "faker", "generator": "internet.email"}| Field | Type | Required | Notes |
|---|---|---|---|
strategy | literal "faker" | yes | |
generator | dotted Faker method path | yes | e.g. internet.email, person.firstName, commerce.productName. |
Full example
{ "version": 1, "source": { "discoverPath": "autonoma/discover.json", "scenariosPath": "autonoma/scenarios.md" }, "validationMode": "sdk-check", "recipes": [ { "name": "adminWithTwoProjects", "description": "Organization with an admin user and two projects.", "create": { "Organization": [{ "id": "org-1", "name": "Acme" }], "User": [ { "id": "user-1", "email": "{adminEmail}", "role": "admin", "organizationId": "org-1" } ], "Project": [ { "id": "proj-1", "name": "Alpha", "organizationId": "org-1" }, { "id": "proj-2", "name": "Beta", "organizationId": "org-1" } ] }, "variables": { "adminEmail": { "strategy": "derived", "source": "testRunId", "format": "admin-{shortId}@acme.test" } }, "validation": { "status": "validated", "method": "checkScenario", "phase": "ok", "up_ms": 142, "down_ms": 61 } } ]}Common rejection reasons
expected string, received undefinedundersource.discoverPath- thesourceobject is missingdiscoverPath. BothdiscoverPathandscenariosPathare required.- Discriminated union error under
recipes[n].variables.<name>- an unknown or missingstrategykey. Use exactly one of"literal","derived","faker". versionmust be literal1- don’t send"1"or"1.0". Integer1.recipesmust contain at least 1 element - empty arrays are rejected.validation.status/validation.phasemismatch - both are fixed literals ("validated"/"ok"). Any other value fails.
Related
- Scenarios step (test-planner) - how scenarios are authored.
- Validate step (test-planner) - how recipes are validated before upload.
- Environment Factory guide - the
up/down/discoverSDK that consumes these recipes at runtime.