YAML Source is the Network Storage authoring standard.
Use the s&box Library Manager install for auto-updates. GitHub is best for agents, source review, contributions, and manual installs.

Source Authoring

YAML Source is the current standard for authoring Network Storage resources. Use YAML for endpoints, workflows, collections, tests, and shared libraries. Prefer...

# Source Authoring

YAML Source is the current standard for authoring Network Storage resources. Use YAML for endpoints, workflows, collections, tests, and shared libraries. Prefer `.yml` for new source filenames. `.yaml` is also accepted and treated the same.

Runtime API request and response bodies are still JSON. The YAML format only defines resources that Network Storage compiles and runs.

## File Extensions

Use these extensions for new source-authored files. `.yml` is preferred for new work; `.yaml` remains fully supported and equivalent.

| Resource | Preferred YAML Source | Also accepted | Legacy, deprecated |
|----------|-----------------------|---------------|--------------------|
| Endpoint | `endpoints/*.endpoint.yml` or `endpoints/*.yml` | `endpoints/*.endpoint.yaml` or `endpoints/*.yaml` | `endpoints/*.json` |
| Workflow | `workflows/*.workflow.yml` or `workflows/*.yml` | `workflows/*.workflow.yaml` or `workflows/*.yaml` | `workflows/*.json` |
| Collection | `collections/*.collection.yml` or `collections/*.yml` | `collections/*.collection.yaml` or `collections/*.yaml` | `collections/*.json` |
| Test | `tests/*.test.yml` or `tests/*.yml` | `tests/*.test.yaml` or `tests/*.yaml` | `tests/*.json` |
| Library | `libraries/*.library.yml` or `libraries/*.yml` | `libraries/*.library.yaml` or `libraries/*.yaml` | n/a |

## YAML Subset

Network Storage accepts a predictable YAML subset:

- top-level mappings
- nested mappings and arrays
- strings, numbers, booleans, and null
- comments
- quoted or unquoted scalar values

These YAML features are intentionally not supported:

| Feature | Reason | Use instead |
|---------|--------|-------------|
| Anchors and aliases (`&name`, `*name`) | They hide what is compiled | Duplicate the value or use a source library |
| Merge keys (`<<:`) | They make resource shape implicit | Write the merged fields explicitly |
| Custom tags (`!Tag`) | They are not portable to runtime JSON | Plain mappings, arrays, and scalars |

## Source Envelope

Every YAML Source file starts with an explicit envelope.

```yml
sourceVersion: "1"
kind: endpoint
```

| Field | Required | Values | Notes |
|-------|----------|--------|-------|
| `sourceVersion` | yes | `"1"` | Pinned source format version. `dslVersion` is also accepted by the low-level DSL and is normalized to `sourceVersion`. |
| `kind` | yes | `endpoint`, `workflow`, `collection`, `test`, `library` | `resourceKind`, plural forms, `endpoint-test`, and `shared-library` are accepted aliases where APIs pass them through. |
| `imports` | no | array of library ids | Available on endpoint and workflow sources for `call` steps. |
| `resource` | no | mapping | Optional wrapper. Fields inside `resource:` are merged with top-level fields. |
| `definition` | no | mapping | Optional wrapper for imported or exported definitions. Fields inside `definition:` are merged with top-level fields. |
| `<kind>` | no | mapping | Optional wrapper named after the resource kind, such as `endpoint:` or `workflow:`. |
| `notes` | no | string | Author-facing text. Preserved with the resource and ignored by runtime. |

## Accepted YAML Layouts

Prefer flat top-level YAML for new source files:

```yml
sourceVersion: "1"
kind: endpoint
slug: award-match-xp
method: POST
input: {}
steps: []
response: { status: 200, body: { ok: true } }
```

Legacy wrapper layouts still compile and load. The backend merges wrapper contents into the same canonical definition:

```yml
sourceVersion: "1"
kind: endpoint
definition:
slug: award-match-xp
method: POST
input: {}
steps: []
response:
status: 200
body:
ok: true
```

`resource:`, `definition:`, and kind-named wrappers such as `endpoint:` are accepted for compatibility. Backend source-upgrade flows currently accept the `flat` and `definition-wrapper` layouts, then write back flat canonical YAML when the upgrade is unambiguous and safe to auto-write.

## Endpoint Source

Endpoint YAML defines a callable server-side pipeline.

```yml
sourceVersion: "1"
kind: endpoint
name: Award Match XP
slug: award-match-xp
method: POST
enabled: true
skipSboxAuth: false
requiresSecretKey: false
requireReauth: false
input:
type: object
properties:
matchId:
type: string
xp:
type: number
required:
- matchId
- xp
steps:
- id: amount
type: transform
expression: "clamp({{input.xp}}, 0, 250)"
- id: save
type: write
collection: player_data
key: "{{playerKey}}"
ops:
- op: inc
path: xp
value: "{{amount}}"
source: match
reason: "Match {{input.matchId}}"
response:
status: 200
body:
ok: true
xpAwarded: "{{amount}}"
```

Endpoint responses can also use `echo` to include selected step outputs without repeating each field in `body`:

```yml
response:
status: 200
body:
ok: true
echo:
- "{{amount}}"
- "{{player.xp}}"
```

### Endpoint Fields

| Field | Required | Type | Default | Notes |
|-------|----------|------|---------|-------|
| `id` | no | string | resource id or `slug` | Internal id when present. |
| `name` | no | string | `slug` | Display name. |
| `slug` | no | string | `id` or `endpoint` | URL segment used by `/v3/endpoints/{projectId}/{slug}`. |
| `method` | no | `POST` or `GET` | `POST` | Method accepted by the endpoint route. |
| `description` | no | string | empty | User-facing description. |
| `enabled` | no | boolean | `true` | Disabled endpoints are not callable. |
| `skipSboxAuth` | no | boolean | `false` | Allows external calls without s&box token verification. |
| `requiresSecretKey` | no | boolean | `false` | Requires a verified Network Storage secret key on endpoint calls. Use for dedicated-server-only actions. |
| `requireReauth` | no | boolean | `false` | Requires a fresh auth flow when enforced by the route. |
| `authPolicy` | no | object | none | Advanced auth policy metadata. |
| `input` | no | schema object | empty object schema | Validates request JSON before steps run. |
| `steps` | no | array | `[]` | Ordered runtime pipeline. |
| `let` | no | object | none | Template aliases for repeated long paths. Example: `chum: player.activeChum` lets templates use `{{chum.expiresAtUnixSeconds}}`. |
| `response` | no | object | `200` with `{ ok: true }` | Templates are resolved after queued writes succeed. |
| `imports` | no | array | `[]` | Library ids used by `call` steps. |
| `notes` | no | string | none | Preserved metadata. |
| `exposure` | no | `public`, `internal` | `public` for endpoints | Internal endpoints are reusable flows and are not callable through the public endpoint route. |

### Endpoint Runtime Input

Endpoint calls receive JSON request input and auth metadata from the request. Steps can reference:

| Template | Value |
|----------|-------|
| `{{input.field}}` | Request body field after input validation. |
| `{{steamId}}` | Calling player's Steam ID, or the on-behalf-of Steam ID in proxy mode. |
| `{{playerKey}}` | Default record key. Uses `steamId` or `steamId_saveId` when the project uses player-save keys. |
| `{{_hasSecretKey}}` | `true` when the request includes a verified Network Storage secret key. |
| `{{_isDedicatedServer}}` | Alias of `_hasSecretKey` for dedicated-server workflows. |
| `{{values.group.key}}` | Game Value constants and tables for the project. |
| `{{now}}` | ISO timestamp for the execution. |
| `{{_unixMs}}`, `{{_unixS}}` | Unix time in milliseconds or seconds. |
| `{{_dateUTC}}`, `{{_timeUTC}}`, `{{_datetimeUTC}}` | UTC date/time strings from the same execution instant. |
| `{{_year}}`, `{{_month}}`, `{{_day}}`, `{{_hour}}`, `{{_minute}}`, `{{_dayOfWeek}}` | UTC date parts. |
| `{{stepId}}`, `{{stepId.field}}` | Output from a previous step. |

`steamId` and `playerKey` are reserved runtime identity fields. Nested `run` calls inherit them from the caller, and route params or returned values cannot overwrite them.

### Endpoint Runtime Output

Normal execution returns:

```yml
ok: true
status: 200
body:
ok: true
timing:
total: 12
steps:
# per-step timings appear here when present
storageDelta: 128
```

Failures return `ok: false`, an HTTP `status`, a JSON `body.error`, and `timing`. Expected game-logic rejections from `condition` or workflow failures include `conditionRejection: true`; author them with 4xx-style statuses, because 5xx statuses are reserved for internal server failures and are normalized away from server-error reporting. Dry-run tests include `_dryRun.steps`, `_dryRun.pendingWrites`, `_dryRun.pendingWebhooks`, `traceBytes`, `maxTraceBytes`, and `truncated`.

## Workflow Source

Workflows are reusable pipelines called from endpoint steps. They can validate, compute, read, write, delete, call other workflows, and return values.

```yml
sourceVersion: "1"
kind: workflow
id: validate_purchase
name: Validate Purchase
description: Shared purchase validation
params:
player:
type: object
item_id:
type: string
steps:
- id: item
type: lookup
source: values
table: shop_items
where:
field: item_id
op: "=="
value: "{{item_id}}"
- id: can_afford
type: condition
check:
field: "{{player.gold}}"
op: ">="
value: "{{item.cost}}"
onFail:
status: 403
errorCode: NOT_ENOUGH_GOLD
message: "Not enough gold."
returns:
item: "{{item}}"
cost: "{{item.cost}}"
```

### Workflow Fields

| Field | Required | Type | Default | Notes |
|-------|----------|------|---------|-------|
| `id` | no | string | resource id or generated id | Referenced by endpoint `workflow` steps. |
| `name` | no | string | `id` | Display name. |
| `description` | no | string | empty | User-facing description. |
| `params` | no | object | `{}` | Declares values the caller should pass. |
| `steps` | no | array | `[]` | Multi-step workflow body. |
| `returns` | no | object | all step outputs | Values mapped back to the caller under the workflow step id. |
| `condition` | no | condition | none | Legacy condition-only workflow mode. |
| `requires` | no | object | none | Legacy requirement metadata. |
| `onFail` | no | object | none | Fail action defaults for condition-only workflows. |
| `imports` | no | array | `[]` | Library ids used by `call` steps. |
| `notes` | no | string | none | Preserved metadata. |

### Calling A Workflow

```yml
steps:
- id: player
type: read
collection: player_data
key: "{{playerKey}}"
- id: purchase_check
type: workflow
workflow: validate_purchase
params:
player: "{{player}}"
item_id: "{{input.itemId}}"
```

The result is available as `{{purchase_check.item}}`, `{{purchase_check.cost}}`, and any other returned fields.

### Route Fields

Condition steps support explicit true/false route outcomes. `onTrue` and `onFalse` are accepted as source aliases, but saved/compiled output prefers `routes.true` and `routes.false`.

```yml
- id: gate
type: condition
check:
field: "{{input.mode}}"
op: ==
value: cached
routes:
true:
action: return
status: 200
body: { ok: true, cached: true }
false:
action: run
flow: rebuild_cache
as: rebuild
```

Route actions are `continue`, `reject`, `return`, `goto`, and `run`. `goto` targets a step id in the current flow. `run` targets a workflow or internal endpoint and executes in-process with the caller context; it does not make a public HTTP call or perform auth twice.

Backward `goto` edges and recursive `run` edges must be intentionally bounded. `route.maxVisits` and `route.maxTransitions` tell validation that a branch is expected to revisit earlier logic, but they are only metadata. Actual runtime safety comes from `retry.maxAttempts`, the default step-visit cap of 20, the route-transition cap of 1000, the nested-flow depth cap of 4, and the overall wall-time budget.

### Sleep and Retry

Use `sleep` only for short bounded polling or retries. The runtime rejects sleep durations above the configured maximum and still enforces the endpoint wall-time budget.

```yml
- id: wait
type: sleep
durationMs: 250
- id: poll_ready
type: condition
check: { field: "{{status.ready}}", op: ==, value: true }
retry:
maxAttempts: 5
sleepMs: 250
maxElapsedMs: 1500
routes:
true: { action: continue }
false: { action: goto, step: wait }
```

Execution limits for route-aware polling:

- `durationMs` / `retry.sleepMs`: **0-1000ms** each
- `retry.maxAttempts`: **1-10**
- default per-step visits without retry bounds: **20**
- nested reusable-flow depth: **4**
- total route transitions: **1000**
- total execution wall time: **30000ms**

Read-like steps inside polling loops use the normal per-execution read cache unless configured with `refresh: true` or `refreshCache: true`. Fresh reads still count against read limits.
## Collection Source

Collection YAML defines storage shape, constants, and tables.

```yml
sourceVersion: "1"
kind: collection
name: player_data
description: Player progression and inventory
collectionType: per-steamid
accessMode: endpoint
visibility: private
maxRecords: 1
allowRecordDelete: false
requireSaveVersion: true
rateLimits:
mode: none
rateLimitAction: reject
schema:
type: object
required:
- xp
properties:
xp:
type: number
min: 0
_ledger: true
inventory:
type: array
items:
type: object
constants:
- key: combat.xp_per_win
value: 100
tables:
- name: shop_items
columns:
- key: id
type: string
- key: cost
type: number
rows:
- id: potion
cost: 25
```

### Collection Fields

| Field | Required | Type | Default | Notes |
|-------|----------|------|---------|-------|
| `id` | no | string | resource id or `name` | Internal id when present. |
| `name` | no | string | `id` | Collection name used by endpoint steps. |
| `description` | no | string | empty | User-facing description. |
| `collectionType` | no | `per-steamid`, `global` | `per-steamid` | Per-player documents or shared global records. |
| `accessMode` | no | `endpoint` | `endpoint` | Collections are endpoint/query controlled; direct collection APIs require secret keys. Legacy `public` values are normalized to `endpoint`. |
| `visibility` | no | `private` | `private` | Collections are private implementation detail behind endpoints/queries. Legacy values are normalized to `private`. |
| `maxRecords` | no | number | `1` | Save-slot count for per-player collections. UI clamps to `1` through `50`. |
| `allowRecordDelete` | no | boolean | `false` | Allows explicit record deletion where exposed by the UI/API. |
| `requireSaveVersion` | no | boolean | `false` | Adds and increments save-version fields on endpoint writes. |
| `schema` | no | object | `{}` | Lightweight schema used on writes. |
| `rateLimits` | no | object | `{ mode: "none" }` | Collection-level save limits. |
| `rateLimitAction` | no | `reject`, `clamp` | `reject` | Action for collection save limits. |
| `webhookOnRateLimit` | no | boolean/string/object | none | Rate-limit notification metadata where configured. |
| `constants` | no | array | `[]` | Server-authored constants exposed under `values`. |
| `tables` | no | array | `[]` | Server-authored row tables exposed under `values`. |
| `notes` | no | string | none | Preserved metadata. |

### Schema Fields

The validator supports:

| Schema field | Applies to | Notes |
|--------------|------------|-------|
| `type` | all fields | `object`, `array`, `string`, `number`, `boolean`, `player`, `playerSave`, `datetime`. |
| `required` | object | Array of required child property names. |
| `properties` | object | Child field definitions. Optional for free-form object maps. |
| `additionalProperties` | object | Schema for dynamic object-map values, such as upgrade-id to level maps. |
| `items` | array | Item schema. |
| `min`, `max` | number | Numeric bounds. |
| `_ledger` | number | Enables immutable ledger audit entries for changes. |
| `_maxPerMin`, `_maxPerHour`, `_maxPerDay`, `_maxPerWeek`, `_maxPerYear` | number | Legacy rate-tracking metadata. |
| `_unique` | metadata | Preserved metadata. |

Extra stored fields are allowed during validation so older saved data can continue to exist after schema changes.

## Test Source

Tests define endpoint fixtures for validation and benchmark-friendly dry runs.

```yml
sourceVersion: "1"
kind: test
id: award_match_xp_basic
name: Award match XP basic
endpoint: award-match-xp
method: POST
input:
matchId: match_123
xp: 75
inputMode:
steamId: "76561198000000000"
mockData:
player_data:
xp: 10
expect:
outcome: pass
status: 200
body:
ok: true
skipWebhooks: true
tags:
- smoke
```

### Test Fields

| Field | Required | Type | Default | Notes |
|-------|----------|------|---------|-------|
| `id` | no | string | resource id or `name` | Stable test id. |
| `name` | no | string | `id` or `Source Test` | Display name. |
| `description` | no | string | empty | User-facing description. |
| `endpoint` | no | string | none | Endpoint slug or id under test. |
| `method` | no | string | `POST` | Request method. |
| `input` | no | object | `{}` | Request body used for the dry run. |
| `inputMode` | no | object | `{}` | Caller/auth fixture metadata. |
| `mockData` | no | object | `{}` | Collection mock data keyed by collection name. |
| `expect` | no | object | `{ outcome: "pass" }` | Expected outcome/status/body metadata. |
| `skipWebhooks` | no | boolean | false | Collect webhook payloads without sending them. |
| `tags` | no | array | `[]` | Test grouping metadata. |

## Library Source And Imports

Libraries provide reusable blocks for endpoint and workflow sources. A block body can contain the same step types as an endpoint/workflow.

```yml
sourceVersion: "1"
kind: library
id: economy
blocks:
can_afford:
requiredInputs:
- player
- cost
body:
- id: enough
type: return_on
condition:
field: "{{player.gold}}"
op: ">="
value: "{{cost}}"
status: 403
error: NOT_ENOUGH_GOLD
message: "Not enough gold."
```

Use it from an endpoint or workflow:

```yml
sourceVersion: "1"
kind: endpoint
slug: buy-item
imports:
- economy
steps:
- id: afford
type: call
block: economy.can_afford
inputs:
player: "{{player}}"
cost: "{{item.cost}}"
```

### Library Fields

| Field | Required | Type | Default | Notes |
|-------|----------|------|---------|-------|
| `id` | no | string | resource id | Import id used by `imports`. |
| `blocks` | no | object | `{}` | Block definitions keyed by block name. |
| `blocks.<name>.requiredInputs` | no | array | `[]` | Inputs that must be supplied by `call.inputs`. |
| `blocks.<name>.body` | no | array | `[]` | Source steps inlined at compile time. |

## Step Basics

Every step must have:

| Field | Required | Notes |
|-------|----------|-------|
| `id` | yes | Unique step id in the local context. Later steps reference it as `{{id}}` or `{{id.field}}`. |
| `type` | yes | One of the runtime or source-only step types. |
| `as` | no | Supported by read/lookup/filter/lookup_many/random_select result assignment. Defaults to `id`. |
| `notes` | no | Author-facing comment preserved through compilation where possible. |

Endpoint and workflow execution is ordered. Read-like steps run immediately. Write and delete steps are queued and committed after all steps pass, so validation can reject before storage changes are made.

## Runtime Step Types

### `read`

Loads one record from a collection.

```yml
- id: player
type: read
collection: player_data
key: "{{playerKey}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `collection` | yes | Collection name or id. |
| `key` | yes | Record key template. `{{steamId}}_default` resolves to the first save slot where applicable. |
| `as` | no | Result context key. |

Output: stored record object or `null`.

### `lookup`

Finds the first matching row from Game Values or a collection scan.

```yml
- id: item
type: lookup
source: values
table: shop_items
where:
field: id
op: "=="
value: "{{input.itemId}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `source` | yes | `values` for Game Values; any other value scans `collection`. |
| `table` | values source | Game Values table name. |
| `collection` | collection source | Collection name or id. |
| `where` | yes | Condition leaf, `all`/`any` group, or array of leaves. Arrays use AND logic. |
| `as` | no | Result context key. |

Output: matched row/record or `null`.

### `filter`

Returns every matching row from Game Values or a collection scan.

```yml
- id: unlocked_items
type: filter
source: values
table: shop_items
where:
field: level
op: "<="
value: "{{player.level}}"
limit: 20
```

| Field | Required | Notes |
|-------|----------|-------|
| `source` | yes | `values` or collection source. |
| `table` | values source | Game Values table name. |
| `collection` | collection source | Collection name or id. |
| `where` | yes | Condition leaf, `all`/`any` group, or array of leaves. Arrays use AND logic. |
| `limit` | no | Preserved metadata; current runner returns all matches. |
| `as` | no | Result context key. |

Output: array of matched rows/records.

### `lookup_many`

Fetches several rows by key from Game Values or a collection scan.

```yml
- id: items
type: lookup_many
source: values
table: shop_items
keyField: id
keys: "{{input.itemIds}}"
asMap: true
```

| Field | Required | Notes |
|-------|----------|-------|
| `source` | yes | `values` or collection source. |
| `table` | values source | Game Values table name. |
| `collection` | collection source | Collection name or id. |
| `keyField` | no | Field to match. Defaults to `id`. |
| `keys` or `values` | yes | Array or template resolving to an array. |
| `asMap` | no | Defaults to `true`. Use `false` to return an array in the same order as keys. |
| `as` | no | Result context key. |

Output: object keyed by requested key, or an array when `asMap: false`. Missing rows are `null`.

### `random_select`

Filters rows and returns one random row, optionally weighted.

```yml
- id: reward
type: random_select
source: values
table: rewards
where:
- field: tier
op: "=="
value: "{{input.tier}}"
- field: disabled
op: "!="
value: true
weightField: weight
```

| Field | Required | Notes |
|-------|----------|-------|
| `source` | yes | `values` or collection source. |
| `table` | values source | Game Values table name. |
| `collection` | collection source | Collection name or id. |
| `where` | yes | Condition leaf, `all`/`any` group, or array of leaves. Arrays use AND logic. |
| `weightField` | no | Numeric weight field. If omitted, selection is uniform. |
| `as` | no | Result context key. |

Output: selected row/record or `null`.

### `condition`

Checks a condition. On failure it can reject, skip steps, clamp input, flag, or fire a fail webhook.

```yml
- id: enough_gold
type: condition
check:
field: "{{player.gold}}"
op: ">="
value: "{{item.cost}}"
onFail:
status: 403
errorCode: NOT_ENOUGH_GOLD
message: "Not enough gold."
```

| Field | Required | Notes |
|-------|----------|-------|
| `check` | required unless `workflow` is used | Condition object. |
| `workflow` | no | Legacy condition-only workflow id. |
| `bindings` | no | Map workflow variable names to existing step ids for condition-only workflow mode. |
| `onFail` | no | Fail action object. |

Output: `true` or `false` in context when execution continues.

### `assert`

Fails immediately when a condition is false.

```yml
- id: has_gold
type: assert
check:
field: "{{player.gold}}"
op: ">="
value: "{{input.cost}}"
status: 403
errorCode: NOT_ENOUGH_GOLD
message: You need more gold.
```

| Field | Required | Notes |
|-------|----------|-------|
| `check` | yes | Condition object. |
| `status` | no | Defaults to `400`. |
| `errorCode` or `error` | no | Machine-readable error code. Defaults to `ASSERTION_FAILED`. |
| `message` or `errorMessage` | no | Human-readable failure message. |

Output: `true` when the assertion passes.

### `transform`

Returns either a resolved template/literal value or a math expression result.

```yml
- id: reward_xp
type: transform
expression: "floor({{player.level}} * 12.5)"
```

```yml
- id: display_name
type: transform
value: "{{default(input.name, 'Unknown')}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `expression` | one of `expression`, `value` | Safe math expression. |
| `value` | one of `expression`, `value` | Literal or template-resolved value. |

Output: the computed value.

### `object`

Builds one object from named fields, templates, and literals.

```yml
- id: response_meta
type: object
fields:
steamId: "{{steamId}}"
mode: "{{input.mode}}"
rewardsGranted: true
```

| Field | Required | Notes |
|-------|----------|-------|
| `fields` | yes | JSON/YAML object. Values are resolved deeply. |

Output: object containing the resolved fields.

### `array`

Builds one ordered array from templates, literals, and nested values.

```yml
- id: reward_list
type: array
items:
- starter_pack
- "{{input.extraReward}}"
- kind: gold
amount: "{{input.gold}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `items` | yes | Array resolved deeply in order. |

Output: resolved array.

### `merge`

Shallow-merges one or more objects into a new object. Later sources overwrite earlier properties.

```yml
- id: payload
type: merge
sources:
- "{{player}}"
- title: Rookie
grantedRewards: "{{reward_list}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `sources` | yes | Array of objects or object templates. Resolved left to right. |

Output: merged object.

### `sort`

Sorts an array into a new ordered array. The original array is not mutated.

```yml
- id: leaderboard
type: sort
source: "{{input.entries}}"
by: score
direction: desc
```

| Field | Required | Notes |
|-------|----------|-------|
| `source` | yes | Template resolving to an array. |
| `by` | no | Plain item field path such as `score` or `stats.rank`. |
| `expression` | no | Math/template expression evaluated with `item` and `index`. Overrides `by` when present. |
| `direction` | no | `asc` or `desc`. Defaults to `asc`. |

Output: sorted array.

### `switch`

Chooses the first matching case value from an ordered list of conditions.

```yml
- id: reward_tier
type: switch
cases:
- when:
field: "input.mode"
op: "=="
value: admin
then: admin
- when:
field: "input.gold"
op: ">="
value: 100
then: rich
default: starter
```

| Field | Required | Notes |
|-------|----------|-------|
| `cases` | yes | Ordered array of objects with `when` and `then`. |
| `default` | no | Fallback value when nothing matches. |

Output: the first matching `then` value, or `default`, or `null` when neither is present.

### `compute`

Builds an object from several fields. Fields can reference earlier fields in the same compute output as `{{stepId.field}}`.

```yml
- id: reward
type: compute
values:
base:
value: "{{values.combat.base_xp}}"
bonus:
expression: "floor({{base}} * 0.25)"
total:
expression: "{{base}} + {{bonus}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `values` or `fields` | yes | Object of output fields. |
| `mode` | no | Use `template` when string field definitions should resolve as templates instead of math. |

Each field definition can be an object with `expression`, an object with `value`, a nested object/array, a string, or a literal.

Output: object containing all resolved fields.

### `random`

Generates random values.

```yml
- id: roll
type: random
mode: int
min: 1
max: 101
```

| Mode | Fields | Output |
|------|--------|--------|
| `int` | `min`, `max` | Integer in `[min, max)`. Defaults: `0`, `100`. |
| `float` | `min`, `max`, `precision` | Float in `[min, max]`. Defaults: `0`, `1`, precision `2`. |
| `weighted` | `source`, `table`, `from`, `items`, `where`, `weightField` | Weighted item from a values table, previous array, or inline array. |
| `beta` | `alpha`, `beta`, `min`, `max`, `precision` | Beta-distributed number. Defaults: `1`, `1`, `0`, `1`, precision `2`. |

### `workflow`

Executes a saved workflow.

```yml
- id: purchase
type: workflow
workflow: validate_purchase
params:
player: "{{player}}"
item_id: "{{input.itemId}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `workflow` | yes | Workflow id. |
| `params` | no | Values passed into multi-step workflows. |
| `bindings` | no | Legacy alias for condition-only workflow variable bindings. |

Output: workflow `returns` object, or all workflow step outputs when `returns` is omitted.

### `write`

Queues changes to a collection record. All queued writes commit after the pipeline passes.

```yml
- id: save_player
type: write
collection: player_data
key: "{{playerKey}}"
ops:
- op: inc
path: gold
valueExpression: "{{reward.total}}"
source: reward
reason: "Awarded by endpoint"
when:
field: "{{reward.total}}"
op: ">"
value: 0
```

| Field | Required | Notes |
|-------|----------|-------|
| `collection` | yes | Collection name or id. |
| `key` | yes | Record key template. |
| `ops` | yes | Non-empty array of operations. |
| `rateLimitBypasses` | no | Object keyed by rate-limit rule id. Each value is a condition that bypasses that rule when true. |

Operation fields:

| Operation | Required fields | Notes |
|-----------|-----------------|-------|
| `set` | `path`, `value` | Sets a field. Numeric increases count for rate limits. |
| `inc` | `path`, numeric `value` | Increments a number. Positive increments count for rate limits. |
| `push` | `path`, `value` | Appends to an array, creating it if needed. |
| `pull` | `path`, `match` | Removes array items matching all object fields, or primitive items matching `match.value`. |
| `remove` | `path`, `value` | Removes primitive values or object values matching all supplied fields. |
| `delete` | `path`, `value` | Alias for `remove` inside write operations. |

Every operation can also use:

| Field | Notes |
|-------|-------|
| `valueExpression` | Math expression used to produce `value`. |
| `expression` | Legacy alias for `valueExpression`. |
| `when` | Condition object or boolean. Operation is skipped when false. |
| `if` | Alias for `when`. |
| `source` | Ledger metadata, truncated to 64 characters. |
| `reason` | Ledger metadata, truncated to 256 characters. |

Output: writes do not expose a context value during normal execution. Test/dry-run output lists pending writes and resolved operations.

### `delete`

Queues deletion of an entire collection record.

```yml
- id: delete_save
type: delete
collection: player_data
key: "{{playerKey}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `collection` | yes | Collection name or id. |
| `key` | yes | Record key template. |
| `when` | no | Preserved by source schema; runtime delete is normally gated with a preceding `condition`. |

Output: deletion result appears in write results/dry-run pending writes.

### `webhook`

Sends a Discord webhook embed.

```yml
- id: alert
type: webhook
url: "{{values.alerts.discord_webhook}}"
title: "Suspicious reward"
description: "Player {{steamId}} requested {{input.amount}}"
color: "ffcc00"
fields:
- name: Endpoint
value: award-match-xp
inline: true
```

| Field | Required | Notes |
|-------|----------|-------|
| `url` | yes at runtime | Must be a Discord webhook URL. |
| `target` | accepted by schema | Older docs used `target`; current runner sends to `url`. |
| `title` | no | Embed title template. |
| `description` or `message` | no | Runner uses `description`; schema also preserves `message`. |
| `color` | no | Hex color without `#`. Defaults to Discord blurple. |
| `fields` | no | Array of `{ name, value, inline }`. |

Output: `{ ok, status }`, or `{ ok, status, skipped, payload }` when webhooks are skipped in tests.

### `response`

Visual-editor compatibility step. Runtime skips it. Define endpoint output with the top-level `response` field instead.

## Source-Only Step Types

Source-only steps compile to runtime steps. The runtime never sees these types directly.

### `foreach`

Unrolls a loop over an array or object path.

```yml
- id: each_item
type: foreach
over: "{{input.items}}"
as: item
indexAs: item_index
maxIterations: 10
body:
- id: save_item
type: write
collection: inventory
key: "{{playerKey}}"
ops:
- op: push
path: items
value: "{{item}}"
```

| Field | Required | Default | Notes |
|-------|----------|---------|-------|
| `over` | yes | none | Template or path resolving to iterable data. |
| `as` | yes | none | Binding name for current item. |
| `indexAs` | no | none | Binding name for zero-based index. |
| `maxIterations` | no | `64` | Clamped to `1..64`. |
| `body` | yes | none | Non-empty array of source steps. |

### `while`

Repeats a body while the condition remains true.

```yml
- id: consume_queue
type: while
condition:
field: "{{queue_remaining}}"
op: ">"
value: 0
maxIterations: 8
body:
- id: queue_remaining
type: transform
expression: "{{queue_remaining}} - 1"
```

| Field | Required | Notes |
|-------|----------|-------|
| `condition` | yes | Condition checked before each unrolled iteration. |
| `maxIterations` | yes | Positive number, clamped to `1..32`. |
| `body` | yes | Non-empty array of source steps. |

### `until`

Repeats a body until the condition becomes true. It compiles like `while` with an inverted guard.

```yml
- id: roll_until_rare
type: until
condition:
field: "{{roll.rarity}}"
op: "=="
value: rare
maxIterations: 5
body:
- id: roll
type: random_select
source: values
table: rewards
where:
field: enabled
op: "=="
value: true
```

### `return_on`

Runs optional body steps, then returns early when `condition` is true.

```yml
- id: reject_if_banned
type: return_on
condition:
field: "{{player.banned}}"
op: "=="
value: true
status: 403
error: BANNED
message: "This account is banned."
```

| Field | Required | Notes |
|-------|----------|-------|
| `condition` | yes | Return when true. Compiler emits an inverted runtime `condition` fail. |
| `status` | no | Response status for the early return. |
| `error` | no | Error code. |
| `message` | no | Error message template. |
| `body` | no | Steps to run before the return check. |

### `call`

Inlines an imported library block.

```yml
- id: afford
type: call
block: economy.can_afford
inputs:
player: "{{player}}"
cost: "{{item.cost}}"
```

| Field | Required | Notes |
|-------|----------|-------|
| `block` | yes | Dotted library block name, such as `economy.can_afford`. |
| `inputs` | no | Object of values bound to block `requiredInputs` and templates. |

The compiler prefixes inlined block step ids with the call step id to avoid collisions.

## Conditions

Conditions support leaf checks, `all`, and `any`.

```yml
field: "{{player.gold}}"
op: ">="
value: "{{item.cost}}"
```

`field` can also be written as `left`, and `value` can also be written as `right`.

```yml
all:
- field: "{{player.level}}"
op: ">="
value: 10
- any:
- field: "{{player.role}}"
op: "=="
value: admin
- field: "{{player.vip}}"
op: "=="
value: true
```

| Operator | Aliases | Notes |
|----------|---------|-------|
| `==` | `eq` | Loose equality, useful for number/string comparisons. |
| `!=` | `neq`, `ne` | Loose inequality. |
| `>` | `gt` | Numeric comparison. |
| `<` | `lt` | Numeric comparison. |
| `>=` | `gte`, `ge` | Numeric comparison. |
| `<=` | `lte`, `le` | Numeric comparison. |
| `contains` | `includes`, `has` | String substring or array contains. |
| `in` | none | Right-hand string or array contains the field value. |
| `not_contains` | `not_includes`, `not_has`, `notcontains` | Negated contains. |
| `not_in` | `notin` | Negated `in`. |
| `starts_with` | `startswith` | String prefix. |
| `not_starts_with` | `notstartswith` | Negated string prefix. |
| `exists` | none | Field is not `undefined` and not `null`. |
| `not_exists` | `not_exist`, `notexists` | Field is missing or null. |

## Fail Actions

`condition.onFail` and workflow `onFail` accept these fields:

| Field | Type | Notes |
|-------|------|-------|
| `reject` | boolean | Defaults to rejecting. Set `false` to continue after failure. |
| `status` | number | HTTP status on rejection. Defaults to `200` for game-logic failures. |
| `errorCode` or `error` | string | Error code returned in `body.error.code`. |
| `errorMessage`, `message` | string | Template-resolved message returned in `body.error.message`. |
| `severity` | string | Returned as response metadata. |
| `flag` | boolean | Returned as `flagged` response metadata and logged by workflow execution. |
| `webhook` | boolean/object | Sends fail webhook where configured. |
| `clamp` | boolean | For simple input max checks, clamps the checked `input.<field>` value instead of rejecting. |
| `action` | string | Use `skip` to skip upcoming steps. |
| `skip` | number | Number of following steps to skip. |
| `skip` | string | `next` skips one step; any other string skips the step with that id. |
| `skip` | array | Skips steps with matching ids. |
| `skipSteps` or `steps` | number/string/array | Aliases for `skip`. |
| `skip.until` | string | Skip until the named step id is reached. |
| `skip.count` | number | Object form for skipping a number of following steps. |

Example skip gate:

```yml
- id: optional_guard
type: condition
check:
field: "{{input.runBonus}}"
op: "=="
value: true
onFail:
action: skip
skip: 2
```

## Templates

Templates use `{{path.to.value}}`. If the whole string is one template and the value is a number, boolean, array, or object, the raw value is returned. If a string contains multiple templates, the result is a string.

```yml
value: "{{player.gold}}"
reason: "Spent {{item.cost}} on {{input.itemId}}"
```

Supported helpers inside templates:

| Helper | Example | Notes |
|--------|---------|-------|
| `default(value, fallback)` | `{{default(input.name, 'Unknown')}}` | First non-null, non-empty value. |
| `coalesce(value, fallback)` | `{{coalesce(player.nickname, player.name, 'Player')}}` | Alias-style fallback helper. |
| `num(value, fallback)` | `{{num(input.amount, 0)}}` | Converts to number with fallback. |
| `get(root, path..., fallback)` | `{{get(player, 'inventory', input.slot, null)}}` | Safely reads a dynamic nested path. |

Template details:

- `{{-path}}` negates a numeric value.
- Nested template paths are supported, such as `{{player.inventory.{{input.slot}}.itemId}}`.
- Forbidden path segments are `__proto__`, `constructor`, and `prototype`.
- Maximum path depth is 10.
- Maximum template nesting depth is 8.

## Math Expressions

Math expressions are used by `transform.expression`, `compute` fields, `write.ops[].valueExpression`, and random bounds.

Supported operators:

| Operator | Meaning |
|----------|---------|
| `+` | add |
| `-` | subtract or unary negative |
| `*` | multiply |
| `/` | divide |
| `%` | modulus |
| `()` | grouping |

Supported functions:

| Function | Arguments | Notes |
|----------|-----------|-------|
| `floor(x)` | 1 | Round down. |
| `ceil(x)` | 1 | Round up. |
| `round(x)` | 1 | Nearest integer. |
| `min(a, b, ...)` | 2+ | Minimum. |
| `max(a, b, ...)` | 2+ | Maximum. |
| `sum(collection, path)` | 1-2 | Sum array/object items; optional field path or lambda selector. |
| `avg(collection, path)` | 1-2 | Average array/object items; optional field path or lambda selector. Empty collections return `0`. |
| `count(collection, predicate)` | 1-2 | Count array/object items; optional lambda predicate. |
| `any(collection, predicate)` | 1-2 | True when at least one array/object item matches. |
| `all(collection, predicate)` | 1-2 | True when every array/object item matches. |
| `first(collection, predicate)` | 1-2 | First item, optionally matching a lambda predicate. |
| `find(collection, predicate)` | 1-2 | Alias for first matching item. |
| `pluck(collection, path)` | 2 | Return an array of selected field/lambda values. |
| `abs(x)` | 1 | Absolute value. |
| `random(min, max)` | 2 | Random integer including max. |
| `pow(base, exponent)` | 2 | Power. |
| `log10(x)` | 1 | Base-10 log of a positive finite number. |
| `clamp(value, min, max)` | 3 | Clamp value to range. |
| `now()` | 0 | Current Unix milliseconds. |
| `nowS()` | 0 | Current Unix seconds. |
| `hour()` | 0 | Current UTC hour. |
| `dayOfWeek()` | 0 | Current UTC day, Sunday is 0. |
| `diffMs(a, b)` | 2 | `a - b` in milliseconds. |
| `diffS(a, b)` | 2 | `(a - b) / 1000`. |

Aggregate functions accept arrays or object maps and let endpoints avoid hard-coded index lists:

```yml
- id: inventory_totals
type: compute
values:
total_value: "sum({{player.inventory}}, Value)"
unsold_count: "count({{player.inventory}}, item => item.sold == false)"
has_legendary: "any({{player.inventory}}, item => item.rarity == \"legendary\")"
first_rod: { value: "{{first(player.inventory, item => item.type == \"rod\")}}" }
value_list: { value: "{{pluck(player.inventory, Value)}}" }
```

## Budgets And Limits

| Limit | Value | Applies to |
|-------|-------|------------|
| Runtime steps | 500 | Compiled endpoint/workflow steps. |
| Read-like steps | 25 | `read`, `lookup`, `filter`, `lookup_many`, `random_select` across endpoint and workflows. |
| Queued write/delete steps | 100 | Endpoint execution. |
| Write operations | 200 | Resolved write ops plus deletes. |
| Wall time | 30000 ms | Endpoint execution. |
| Workflow nesting | 8 | Nested `workflow` calls. |
| Debug trace | 64 KB | Dry-run/test trace before truncation. |
| Collection scan | 500 records | Lookup/filter/random collection scans. |
| `foreach` compile expansion | 64 iterations | Per source-only loop. |
| `while`/`until` compile expansion | 32 iterations | Per source-only loop. |
| Canonical plan nodes | 500 | Source compiler budget. |
| Source iterations | 256 | Sum of source loop max iterations in canonical plan. |
| Source nested calls | 8 | `call` plus `workflow` count in canonical plan. |

## Management API

The Management API accepts source-authored resources by sending `authoringMode`, `sourceFormat`, and `sourceText` with the resource.

```yml
authoringMode: source
sourceFormat: yaml
sourcePath: endpoints/award-match-xp.endpoint.yml
sourceText: "sourceVersion: \"1\"\nkind: endpoint\nname: Award Match XP\nslug: award-match-xp\n\
method: POST\nsteps: []\n"
```

`sourceFormat` accepts `yaml` or `yml`. A `.yml` or `.yaml` `sourcePath` also selects YAML Source.

Responses preserve authoring metadata and include compiler output:

| Field | Notes |
|-------|-------|
| `authoringMode` | `source` for YAML Source resources. |
| `sourceFormat` | `yaml` or `yml`. |
| `sourceVersion` | Source version string. |
| `sourceText` | Normalized source text. |
| `sourceObject` | Parsed source object. |
| `sourcePath` | Authored source path when supplied. System-generated paths are hidden at API/view boundaries. |
| `canonicalDefinition` | Normalized resource definition used by the compiler. |
| `canonicalHash` | Hash of canonical definition. |
| `executionPlan` | Runtime plan, source maps, dependency list, and budgets. |
| `executionPlanHash` | Hash of execution plan. |
| `diagnostics` | Errors, warnings, source paths, suggested fixes, and budget details. |
| `compilerFingerprint` | Compiler/schema/runtime version fingerprint. |
| `sourceHash` | Hash of the source text. |
| `dependencyHash` | Hash of source imports. |
| `compileStatus` | `compiled`, `stale`, or `recompile_failed`. |
| `compiledAt` | ISO compile timestamp. |
| `previousKnownGoodPlan` | Last valid plan retained after failed recompilation. |

## Compiled Runtime Output

Compiled runtime output strips source-only metadata such as `sourceText`, `sourceObject`, hashes, diagnostics, compiler fingerprints, and execution plans before execution.