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-versions

The planner analyzes your project in place but writes everything it generates - the discovery output, the scenarios document, and the recipe file itself - to its own output directory at ~/.autonoma/<project-slug>/. Nothing is written into the repository, so the source paths below point into that directory, not at committed files.

Top-level shape

{
"version": 1,
"source": {
"discoverPath": "string",
"scenariosPath": "string"
},
"validationMode": "sdk-check" | "endpoint-lifecycle",
"recipes": [ /* at least one ScenarioRecipe */ ]
}
FieldTypeRequiredNotes
versioninteger, must equal 1yesContract version. Currently only 1 is accepted. Not a string.
sourceobjectyesProvenance pointers into the planner’s output directory. Additional keys are preserved.
source.discoverPathstringyesPath to the discovery output the recipes were derived from, inside the planner’s output directory (~/.autonoma/<project-slug>/), e.g. autonoma/discover.json. Required - omitting it causes Zod to fail with Invalid input: expected string, received undefined.
source.scenariosPathstringyesPath to the human-readable scenarios document, in the same output directory, e.g. autonoma/scenarios.md.
validationMode"sdk-check" | "endpoint-lifecycle"yesHow Autonoma validated the recipes before upload. sdk-check = checkScenario/checkAllScenarios. endpoint-lifecycle = real HTTP up/down.
recipesarray, minimum length 1yesOne 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
}
}
FieldTypeRequiredNotes
namestringyesStable identifier. Must match the scenario name used in the LLM-facing docs.
descriptionstringyesHuman-readable summary of the scenario state.
createobjectyesThe model graph passed to the SDK’s createScenario / up flow. A flat map: keys are model names, values are arrays of seeded rows. Rows link with _alias / _ref, and a _ref is resolved wherever it appears - including nested inside an object or an array. Extra keys are preserved.
variablesobject (map of name → definition)noDeprecated. Still accepted for recipes that already declare it, but no longer generated or documented. Use the built-in tokens below instead.
validationobjectyesProof that the recipe was validated. All fields must be present.
validation.statusliteral string "validated"yes
validation.methodone of "checkScenario", "checkAllScenarios", "endpoint-up-down"yesWhich validator produced this result.
validation.phaseliteral string "ok"yes
validation.up_msnon-negative integernoMilliseconds the up phase took.
validation.down_msnon-negative integernoMilliseconds the down phase took.

Built-in tokens

Every value in create must be concrete, with exactly two exceptions. These tokens are written in double braces and need no declaration - Autonoma substitutes them when it provisions the scenario.

TokenValue
{{testRunId}}The id of this provisioning run - the same value Autonoma sends the Environment Factory as the up request’s testRunId, so your recipe and your handler agree on one identity.
{{testRunShortId}}An 8-character hash of {{testRunId}}, for columns too short to hold a UUID (usernames, slugs, subdomains).

They exist for one reason: concurrent runs of the same scenario would otherwise collide on unique columns. Use them anywhere a value must be unique per run, including inside a longer string.

"User": [
{
"_alias": "admin",
"email": "admin-{{testRunShortId}}@acme.test",
"externalId": "{{testRunId}}"
}
]

Any other {{token}} is rejected on upload - there is no general variable mechanism.

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": [{ "_alias": "org-1", "name": "Acme" }],
"User": [
{
"email": "admin-{{testRunShortId}}@acme.test",
"role": "admin",
"organizationId": { "_ref": "org-1" }
}
],
"Project": [
{ "name": "Alpha", "organizationId": { "_ref": "org-1" } },
{ "name": "Beta", "organizationId": { "_ref": "org-1" } }
]
},
"validation": {
"status": "validated",
"method": "checkScenario",
"phase": "ok",
"up_ms": 142,
"down_ms": 61
}
}
]
}

Common rejection reasons

  • Invalid input: expected string, received undefined under source.discoverPath - the source object is missing discoverPath. Both discoverPath and scenariosPath are required.
  • These tokens resolve to nothing: {{<name>}}. Autonoma only substitutes {{testRunId}} and {{testRunShortId}}; replace the rest with concrete values. - the create graph uses a {{token}} that is not one of the two built-ins. Replace it with a concrete value.
  • These _ref targets match no _alias in the graph - a row references an alias no row declares. Add the _alias, or drop the reference.
  • The create graph must map each model name to an array of records - a model maps to a scalar or a bare array of non-objects. The Environment Factory rejects the whole seed in that shape.
  • version must be literal 1 - don’t send "1" or "1.0". Integer 1.
  • recipes must contain at least 1 element - empty arrays are rejected.
  • validation.status / validation.phase mismatch - both are fixed literals ("validated" / "ok"). Any other value fails.
  • Test Planner - how scenarios are designed and recipes are validated before upload.
  • Environment Factory - the up / down / discover SDK that consumes these recipes at runtime.
Link copied